STM32 IIC雙機通信—— HAL庫硬件IIC版


參考傳送門

  關於IIC的原理這里我就不多說了,網上有很多很好的解析,如果要看我個人對IIC的理解的話,可以點擊查看,這里主要講一下怎樣利用STM32CubeMx實現IIC的通訊,經過個人實踐,感覺HAL庫的硬件IIC要比標准庫的穩定。好了,下面就從STM32CubeMx 配置開始一步步實現IIC通訊。

  STM32CubeMx的配置,這里關於新建工程的步驟我就不細說了,如果還不會操作STM32CubeMx 的可以點擊查看, 這里主要對IIC的配置進行說明。

  

  了解IIC的都知道,IIC通信有主從機之分,用兩片STM32進行IIC通信當然也不例外,不過使用STM32CubeMx 配置有一個好處,就是不用分別配置主從機,在STM32CubeMx 配置里面,主從機的配置是一樣,唯一不同的就是IIC的地址如上圖,這個地址很重要,只要配置好了,基本就成功了

  還有一個要注意的,就是IIC的SDA、SCK引腳要配置成NPP模式,不然容易出現信號線忙,檢測不到從機的情況。

  

  配置配好后我們生成代碼,就可以進行通信了,主從機核心代碼如下:

 

  下面是主機的重要代碼:

/* I2C2 init function  IIC配置*/
static void MX_I2C2_Init(void)
{

  hi2c2.Instance = I2C2;
  hi2c2.Init.Timing = 0x10805D88;
  hi2c2.Init.OwnAddress1 = 20;        //用戶自己配置的地址
  hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c2.Init.OwnAddress2 = 0;        
  hi2c2.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
  hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure Analogue filter 
    */
  if (HAL_I2CEx_ConfigAnalogFilter(&hi2c2, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure Digital filter 
    */
  if (HAL_I2CEx_ConfigDigitalFilter(&hi2c2, 0) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}


while(HAL_I2C_Master_Transmit_IT(&hi2c2 ,0x0b,&BUFF[0], 1)!= HAL_OK){}   
//IIC主機發送函數,主要IIC配置好了,這個可以添加到main函數里面測試

 

  關於STM32CubeMx的HAL庫IIC收發有幾種函數,用戶可以根據自己不同的需求進行選擇,以下就是主要的幾個HAL庫IIC收發函數:

/* 第1個參數為I2C操作句柄
   第2個參數為從機設備地址
   第3個參數為從機寄存器地址
   第4個參數為從機寄存器地址長度
   第5個參數為發送的數據的起始地址
   第6個參數為傳輸數據的大小
   第7個參數為操作超時時間   */
HAL_I2C_Mem_Write(&hi2c2,salve_add,0,0,PA_BUFF,sizeof(PA_BUFF),0x10);

HAL_I2C_Mem_Write_IT();

HAL_I2C_Mem_Read();

HAL_I2C_Mem_Read_IT();

HAL_I2C_Mem_Read_DMA();

HAL_I2C_Mem_Write_DMA();


/*    不需要用到寄存器地址的主機HAL庫IIC收發函數   */
HAL_I2C_Master_Receive();     //STM32 主機接收,不需要用到寄存器地址
HAL_I2C_Master_Transmit();

HAL_I2C_Master_Receive_IT();   //中斷IIC接收
HAL_I2C_Master_Receive_DMA();  //DMA 方式的IIC接收  

HAL_I2C_Master_Transmit_IT();  
//中斷IIC發送

HAL_I2C_Master_Transmit_DMA();  
//DMA 方式的IIC發送

HAL_I2C_Master_Transmit(
&hi2c2,0x0B,PA_BUFF,sizeof(PA_BUFF),0x10); //STM32 主機發送

/* 不需要用到寄存器地址的從機HAL庫IIC收發函數   */
HAL_I2C_Slave_Receive();    
//STM32 從機機接收,不需要用到寄存器地址

HAL_I2C_Slave_Transmit();  
//STM32 從機機發送,不需要用到寄存器地址

HAL_I2C_Slave_Receive_IT();

HAL_I2C_Slave_Receive_DMA();

HAL_I2C_Slave_Transmit_IT();

HAL_I2C_Slave_Transmit_DMA();

 

  我這里因為只是做兩個STM32間的單向通行而已,不需要對寄存器進行寫數據。

  所以主機發送函數選擇了 HAL_I2C_Master_Transmit( ); 函數,而我從機則選擇HAL_I2C_Slave_Receive( );函數,從機代碼如下:

/*   I2C2 init function 從機IIC初始化配置   */
static void MX_I2C2_Init(void)
{

  hi2c2.Instance = I2C2;
  hi2c2.Init.Timing = 0x10805D88;
  hi2c2.Init.OwnAddress1 = 0x0A;
  hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c2.Init.OwnAddress2 = 0;
  hi2c2.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
  hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure Analogue filter 
    */
  if (HAL_I2CEx_ConfigAnalogFilter(&hi2c2, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure Digital filter 
    */
  if (HAL_I2CEx_ConfigDigitalFilter(&hi2c2, 0) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

while(HAL_I2C_Slave_Receive(&hi2c2, RE_BUFF, 1, 100)!= HAL_OK) {} 
// 在配置好IIC后,可直接把該函數放到main函數測試,第一個參數是IIC通道選擇,第二個參數是接收緩存,第三個數據的是接收長度,第四個參數是超時時間

  經過測試,發現如果發送數據過多,用硬件I2C收發的話,使用中斷會比較穩定

  作為從機,要與主機完成通信,有一個特別要注意的事情,就是IIC配置的地址要與主機發送的地址一致,否則無法完成應答。我一開始就是直接發送自己在軟件配置的IIC地址,可是沒有通信成功,檢查才發現,軟件配置好IIC后,生成代碼時,地址會最后一位補0,自動補夠8位;而我從機發送出來的地址,也會把你發送的地址最后一位改為0再發送,這個我查了一下函數地層,好像是因為這是是發送函數,所以直接幫你把R/W位改為0了。所以導致我一開始怎么都調不通,使用邏輯分析儀后,分析采集到的數據才發現這個問題,於是我,直接手動改從機的IIC地址,改成邏輯分析儀發出來的地址一樣,這樣就通了。

 

  通信結果如圖所示:

  

 

補充2點要注意的:

  1. IIC硬件連接的時候SDA跟SCL總線注意都要上拉一個4.7K的電阻,否則IIC無法工作;

  2. 使用主從收發函數時,發送的地址要注意。如果是發送函數,發送出的地址最后一位 R/W 位如果不為0,則HAL庫函數會地址把最后一位修改為0,再把地址發送出去,但是如果地址最后一位 R/W 位為0,函數就不會對地址進行修改,直接把地址發送出去;如果是接收函數,發送出去的地址最后一位 R/W位 不為1,則HAL庫函數會把地址最后一位修改為1,再把地址發送出去,但是如果地址最后一位 R/W 位為1,函數就不會對地址進行修改,直接把地址發送出去.。所以從機地址設置,一定要與主機發出的地址一致才能正確應答

  例如:

  (1) HAL_I2C_Master_Receive(&hi2c2,0x0a,(uint8_t *)test,sizeof(test),1000); 因為這個是讀出數據函數,這里發送的地址位第8位地址位 R/W 位應該為 1,發送地址 0x0a 硬件會把最后一位改為0,把地址變成0xb 再把 0x0b這個地址發送出去 ;如果地址最后一位R/W是 1,發送地址是 0x0b ,因為最后一位為 1,那么該函數發送的地址就不會對發送的地址最后一位R/W為做出修改,直接把 0x0b 發送出去,發出去的地址依然為0x0b。

  (2) HAL_I2C_Master_Transmit(&hi2c2,0x0f,(uint8_t *)test,sizeof(test),1000); 因為這個是寫入數據函數,R/W 位為 0,發送地址 0x0f 硬件會把最后一位改為 0 變成 0xe,如果發送的地址是 0xa ,則不會對地址進行修改,直接把地址 0x0a 發送出去。

  

  現在我以F40X系列的STM32 的 IIC 讀函數為例子來翻一下函數地層分析造成這個問題的原因:

  

  來到地層我們找到發送地址的函數;

  

  

  

  上面跳轉到這里我們就接近真相了,上圖1是跳轉下去的 IIC 寫函數的地層,2 是 IIC 跳轉下去的讀函數的地層,這里 ADDRESS 就是用戶寫進IIC發送讀取函數里的尋址地址了,接下來到重點了,對於I2C_OAR1_ADD0 我們再次跳轉分析下去看看;

  

  從這里接收函數我們可以看到,I2C_OAR1_ADD0 最終的值應該為 0x00000001,我們依舊以地址 0x 0b 為例: 0x 0b = 0000 1011 , 由或運算的定義我們可知,數據再做或運算 (|)時, 參加運算的兩個對象只要有一個為1,其值為1 , 所以 (00000001) | (0000 1011)= 0000 1011= 0x0b 。這時如果地址為 0x0xa(0000 1010),則會出現地址位最后一位R/W位被改為1的情況,運算過程為(00000001) | (0000 1010)= 0000 1011 = 0x0b ,結合這個地層運算,就不難解析為什么使用HAL庫的 IIC 接收函數,發送出去的地址會出現變化的情況了。

  至於發送函數同理,I2C_OAR1_ADD0 最終的值應該為 0x00000001取反后就變成了 0x 1111 1110 ,由與運算(&)的定義可知兩位同時為“1”,結果才為“1”,否則為0。所以這里我們依舊以地址0x0b = 0000 1011 為例,(1111 1110) &(0000 1011)= 0000 1010 = 0x0b,所以發送函數把地址最后一位的R/W位改為了 0 ,而當地址為 0x0a = 0000 1010 時,我們再進行運算一下可得(1111 1110) &(0000 1010)= 0000 1010  = 0x0a , 地址沒有改變,所以這也就解析了上面我說的地址變化的問題了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM