加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

STM32設(shè)置為I2C從機模式(HAL庫版本)

04/08 14:28
4237
閱讀需 17 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

我之前出過一篇關(guān)于STM32設(shè)置為I2C從機的博客,現(xiàn)在應(yīng)粉絲要求,出一篇HAL庫版本的I2C從機編程。

基于官方庫版本的可以看下我之前發(fā)的文章:STM32設(shè)置為I2C從機模式

1 硬件連接

測試芯片STM32F103RCT6

測試方法:用一個USB轉(zhuǎn)I2C的工具接到STM32的I2C引腳上,通過上位機工具進行讀寫操作。如果沒有這個工具,也可以用另外一組I2C作為主機或者其他設(shè)備測試通訊,同時也可以借助示波器或者邏輯分析儀來輔助調(diào)試。

硬件連接:

STM32這邊使用硬件I2C1(PB6、PB7),并外接上拉電阻。

在這里插入圖片描述
在這里插入圖片描述

本次測試中使用的USB轉(zhuǎn)I2C的工具如下圖所示:

在這里插入圖片描述

2 軟件編程

2.1 步驟分解

1、初始化I2C配置

注:除了最后的HAL_I2C_EnableListen_IT()函數(shù),其他代碼都可以用STM32CubeMX自動生成

參考代碼:

static void MX_I2C1_Init(void)
{
  hi2c1.Instance = I2C1;                                // 配置I2C1                   
  hi2c1.Init.ClockSpeed = 100000;                       // 時鐘頻率:100k                            
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;               // 占空比:1/2                                    
  hi2c1.Init.OwnAddress1 = 0x80;                        // 本機地址:0x80(若作為從設(shè)備則是從機地址)                           
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;  // 地址模式:7位                                                 
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 禁止雙地址                                                  
  hi2c1.Init.OwnAddress2 = 0;                           // 第二地址                        
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 禁止廣播                                                  
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;     // 禁止時鐘拉伸                                              
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)    // I2C1初始化                                                  
  {                                                      
    Error_Handler();                                                      
  }                                                      
  HAL_I2C_EnableListen_IT(&hi2c1);       // 使能I2C1的偵聽中斷  
}

2、初始化I2C引腳和中斷

參考代碼:

注:這個代碼可以用STM32CubeMX自動生成

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hi2c->Instance==I2C1)
  {
    // 配置GPIO
    __HAL_RCC_GPIOB_CLK_ENABLE();   
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // 配置I2C中斷
    /* Peripheral clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE();
    /* I2C1 interrupt Init */
    HAL_NVIC_SetPriority(I2C1_EV_IRQn, 0, 0);  // 事件中斷(必須有)
    HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
    HAL_NVIC_SetPriority(I2C1_ER_IRQn, 0, 0);  // 錯誤中斷(非必須)
    HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);
  }
}

3、配置I2C中斷服務(wù)函數(shù)

參考代碼:

注:這個代碼可以用STM32CubeMX自動生成

// I2C1事件中斷服務(wù)函數(shù)(必須有)
void I2C1_EV_IRQHandler(void)
{
  HAL_I2C_EV_IRQHandler(&hi2c1);
}

// I2C1錯誤中斷服務(wù)函數(shù)(非必須)
void I2C1_ER_IRQHandler(void)
{
  HAL_I2C_ER_IRQHandler(&hi2c1);
}

4、配置I2C從機回調(diào)處理函數(shù)

參考代碼:

static uint8_t ram[256];             // 模擬I2C從機數(shù)據(jù)寄存器(主機讀寫的數(shù)據(jù)都放在這塊內(nèi)存)
uint8_t offset;                      // 從機寄存器當(dāng)前偏移地址
static uint8_t first_byte_state = 1; // 是否收到第1個字節(jié),也就是偏移地址(0:已收到,1:沒有收到)

// 偵聽完成回調(diào)函數(shù)(完成一次完整的i2c通信以后會進入該函數(shù))
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c)
{
  // 完成一次通信,清除狀態(tài)
  first_byte_state = 1;
  offset = 0;
  HAL_I2C_EnableListen_IT(hi2c); // slave is ready again
}

// I2C設(shè)備地址回調(diào)函數(shù)(地址匹配上以后會進入該函數(shù))
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{
  if(TransferDirection == I2C_DIRECTION_TRANSMIT) 
  {// 主機發(fā)送,從機接收
    if(first_byte_state) 
    {// 準(zhǔn)備接收第1個字節(jié)數(shù)據(jù)
      HAL_I2C_Slave_Seq_Receive_IT(hi2c, &offset, 1, I2C_NEXT_FRAME);  // 每次第1個數(shù)據(jù)均為偏移地址
    } 
  } 
  else 
  {// 主機接收,從機發(fā)送
    HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &ram[offset], 1, I2C_NEXT_FRAME);  // 打開中斷并把ram[]里面對應(yīng)的數(shù)據(jù)發(fā)送給主機
  }
}

// I2C數(shù)據(jù)接收回調(diào)函數(shù)(在I2C完成一次接收時會關(guān)閉中斷并調(diào)用該函數(shù),因此在處理完成后需要手動重新打開中斷)
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  if(first_byte_state) 
  {// 收到的第1個字節(jié)數(shù)據(jù)(偏移地址)
    first_byte_state = 0;
  } 
  else 
  {// 收到的第N個字節(jié)數(shù)據(jù)
    offset++;  // 每收到一個數(shù)據(jù),偏移+1
  }
  // 打開I2C中斷接收,下一個收到的數(shù)據(jù)將存放到ram[offset]
  HAL_I2C_Slave_Seq_Receive_IT(hi2c, &ram[offset], sizeof(ram), I2C_NEXT_FRAME);  // 接收數(shù)據(jù)存到ram[]里面對應(yīng)的位置
}

// I2C數(shù)據(jù)發(fā)送回調(diào)函數(shù)(在I2C完成一次發(fā)送后會關(guān)閉中斷并調(diào)用該函數(shù),因此在處理完成后需要手動重新打開中斷)
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  offset++;  // 每發(fā)送一個數(shù)據(jù),偏移+1
  HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &ram[offset], sizeof(ram), I2C_NEXT_FRAME);  // 打開中斷并把ram[]里面對應(yīng)的數(shù)據(jù)發(fā)送給主機
}

2.2 測試用例

1、測試方法

使用USB轉(zhuǎn)I2C的工具接入到MCU的I2C上面,然后使用上位機工具進行讀寫操作,最后通過串口把I2C通訊過程中的幾個重要節(jié)點打印出來,驗證結(jié)果是否正確。

2、測試程序

其實和上面講解的代碼是一樣的,只是初始化時先把ram[]賦初值。

參考測試代碼:

#include "stm32f1xx_hal.h"

static uint8_t ram[256];             // 模擬I2C從機數(shù)據(jù)寄存器(主機讀寫的數(shù)據(jù)都放在這塊內(nèi)存)
uint8_t offset;                      // 從機寄存器當(dāng)前偏移地址
static uint8_t first_byte_state = 1; // 是否收到第1個字節(jié),也就是偏移地址(0:已收到,1:沒有收到)

// 偵聽完成回調(diào)函數(shù)(完成一次完整的i2c通信以后會進入該函數(shù))
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c)
{
  // 完成一次通信,清除狀態(tài)
  first_byte_state = 1;
  offset = 0;
  HAL_I2C_EnableListen_IT(hi2c); // slave is ready again
}

// I2C設(shè)備地址回調(diào)函數(shù)(地址匹配上以后會進入該函數(shù))
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{
  if(TransferDirection == I2C_DIRECTION_TRANSMIT) 
  {// 主機發(fā)送,從機接收
    if(first_byte_state) 
    {// 準(zhǔn)備接收第1個字節(jié)數(shù)據(jù)
      HAL_I2C_Slave_Seq_Receive_IT(hi2c, &offset, 1, I2C_NEXT_FRAME);  // 每次第1個數(shù)據(jù)均為偏移地址
    } 
  } 
  else 
  {// 主機接收,從機發(fā)送
    HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &ram[offset], 1, I2C_NEXT_FRAME);  // 打開中斷并把ram[]里面對應(yīng)的數(shù)據(jù)發(fā)送給主機
  }
}

// I2C數(shù)據(jù)接收回調(diào)函數(shù)(在I2C完成一次接收時會關(guān)閉中斷并調(diào)用該函數(shù),因此在處理完成后需要手動重新打開中斷)
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  if(first_byte_state) 
  {// 收到的第1個字節(jié)數(shù)據(jù)(偏移地址)
    first_byte_state = 0;
  } 
  else 
  {// 收到的第N個字節(jié)數(shù)據(jù)
    offset++;  // 每收到一個數(shù)據(jù),偏移+1
  }
  // 打開I2C中斷接收,下一個收到的數(shù)據(jù)將存放到ram[offset]
  HAL_I2C_Slave_Seq_Receive_IT(hi2c, &ram[offset], sizeof(ram), I2C_NEXT_FRAME);  // 接收數(shù)據(jù)存到ram[]里面對應(yīng)的位置
}

// I2C數(shù)據(jù)發(fā)送回調(diào)函數(shù)(在I2C完成一次發(fā)送后會關(guān)閉中斷并調(diào)用該函數(shù),因此在處理完成后需要手動重新打開中斷)
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  offset++;  // 每發(fā)送一個數(shù)據(jù),偏移+1
  HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &ram[offset], sizeof(ram), I2C_NEXT_FRAME);  // 打開中斷并把ram[]里面對應(yīng)的數(shù)據(jù)發(fā)送給主機
}

// 測試用例:初始化把ram設(shè)置為從0到255的數(shù)
void i2c_test(void)
{
  for (uint16_t i = 0; i < 256; i++)
  {
    ram[i] = i;
  }
}

3 運行測試

3.1 I2C連續(xù)寫入

通過上位機工具寫入:

請?zhí)砑訄D片描述

通過邏輯分析儀抓取波形:

請?zhí)砑訄D片描述

3.2 I2C連續(xù)讀取

通過上位機工具連續(xù)讀取256字節(jié):

在這里插入圖片描述

通過邏輯分析儀抓取波形:

在這里插入圖片描述

在這里插入圖片描述

3.3 I2C單次讀寫測試

通過上位機工具讀取原值,再寫入新值,最后再讀取新值:
請?zhí)砑訄D片描述

通過邏輯分析儀抓取波形:
請?zhí)砑訄D片描述

4 總結(jié)

通過上位機工具的測試以及邏輯分析儀的解析,STM32的硬件I2C從機通信正常且穩(wěn)定,讀寫速度測試了100k和400k,沒有發(fā)現(xiàn)問題,至此測試完成。
好了,關(guān)于STM32如何設(shè)置從機模式就介紹到這里,如果你們有什么問題,歡迎評論區(qū)留言。

需要完整源碼工程的同學(xué)可以自行下載:源碼下載地址

如果這篇文章能夠幫到你,就…懂的。
請?zhí)砑訄D片描述

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風(fēng)險等級 參考價格 更多信息
S25FL512SAGMFIR10 1 Spansion Flash, 512MX1, PDSO16, 0.300 INCH, LEAD FREE, PLASTIC, MO-013EAA, SOIC-16
$11.03 查看
PS2801-4-F3-A 1 NEC Electronics America Inc Transistor Output Optocoupler, 4-Element, 2500V Isolation, LEAD FREE, PLASTIC, SSOP-16
$3.65 查看
AFBR-5972EZ 1 Foxconn Transceiver,
$193.55 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜