STM32通訊硬體 I2C

2021-05-20 03:00:01

20.1關於 I2C

STM32F103系列的I²C控制器,可作為通訊主機或從機,因此有四種工作模式可選擇:主機傳送模式、主機接收模式、從機傳送模式、從機接收模式。

傳輸速度上,支援標準模式(Standard mode,最高速度100kHz)和快速模式(Fast mode,最高速度400kHz)。同時,還支援SMBus2.0(System Management Bus,系統管理匯流排)和PMBus (Power Management Bus,電源管理匯流排)。

I²C控制器結構如圖 20.1.1 所示,可以看作四部分組成。

  1. 引腳:I²C協定只需要兩個引腳(SDA和SCL),SMBA引腳僅用於SMBus模式的Alert引腳,通常不用管。

  2. 資料收發:主要涉及到資料暫存器(Data Register,DR)和資料移位暫存器(Data Shift Register,DSR)。
    當傳送資料時,將傳送的位元組寫入DR暫存器,硬體會把DR中的位元組搬到DSR中,然後在時鐘訊號的配 合下,把DSR最高位的資料放到傳輸線SDA上,並對DSR進行移位元運算。
    當接收資料時,資料控制器(Data Control)根據時鐘訊號,把SDA線上的高低電平轉換為「1」或「0」的
    資料,寫到DSR的最低位,同時DSR移位元運算,當接收完一個位元組的8位元資料後,把DSR中的資料搬到DR暫存器中。

  3. 時鐘訊號:時鐘控制器(Clock Control)用於驅動同步時鐘訊號線SCL。通過設定時鐘控制暫存器(ClockControl Register,CCR),可以調整SCL的頻率。

  4. 控制邏輯:有兩個控制暫存器(Control Register 1,CR1)和(Control Register 2,CR2)用於控制邏輯。通過它們可以觸發起始和停止訊號,做出ACK響應,設定外設時脈頻率,開啟DMA和中斷的功能。同時控制邏輯的狀態會反饋到(Status Register 1,SR1)和(Status Register 2,SR2)兩個狀態暫存器上,根據它們可以知道當前匯流排是否被佔用,本機是主裝置還是從裝置,資料是否傳送完畢等。
    在這裡插入圖片描述

20.1.2 AP3426 介紹

AP3426晶片整合了光強感測器(ALS:AmbientLight Sensor)、鄰近感測器(PS: Proximity Sensor)、紅 外LED(IR LED),最常見的應用就是手機。當我們接聽電話時,耳朵靠近手機前置揚聲器附近,也就靠近了該感測器,此時距離感測器就告訴CPU可以關閉螢幕顯示,以防誤觸。光強感測器能識別當前環境光,告訴CPU對應調節螢幕亮度,手機部分感測器如圖 20.1.2 所示。

AP3426的結構如圖 20.1.3 所示,左邊兩個光電二極體採集光照的強度,右邊一個發光二極體發射940nm的紅外光。

由圖 20.1.4 可知,兩個光電二極體的頻譜響應,ALS光電二極體對450nm-700nm波長光有響應,PS光電二極體對850nm-1000nm波長的光有響應。

由圖 20.1.5 可知,450nm-700nm波長的光在可見光範圍內,而850nm-1000nm波長的光屬於紅外線。
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在明亮環境中,環境光直接照射在ALS和PS上,當物體遮住AP3426,光電二極體的光照強度則會降低,即可判斷物體接遮住。

在黑暗環境中,AP3426發出紅外線照射在靠近物體上,反射到PS光電二極體上,當物體遮住AP3426, PS光電二極體的光照強度則會降低,即可判斷物體遮住。
在這裡插入圖片描述

20.2 硬體設計

如圖 20.2.1 為開發AP3426部分的原理圖,U5為AP3426晶片。不同於AT24CXX可以電路設定裝置地址,AP3426的裝置地址是固定的,由晶片手冊可以知為0x1E。

I²C1的SCL使用的PB6引腳,SDA使用的PB7引腳,此外,AP3426的中斷引腳連線的PE5。
在這裡插入圖片描述

20.3 軟體設計

20.3.1 軟體設計思路

實驗目的:本實驗通過使用MCU的硬體I2C,獲取AP3426的資料。

  1. 初始化I2C協定相關引數:設定速度、定址長度模式等;
  2. 初始化I2C硬體相關引數:I2C時鐘使能、GPIO埠時鐘使能、GPIO引腳設定為I2C複用;
  3. 使用HAL提供的I2C對AP3426讀寫,封裝AP3426初始化函數、資料讀取函數;
  4. 主函數編寫控制邏輯:按下按鍵KEY1(KEY_U),讀取一次AP3426資料,並將資料通過串列埠列印;

本實驗配套程式碼位於「5_程式原始碼\12_通訊—硬體I2C\」。

20.3.2 軟體設計講解

  1. GPIO選擇與介面定義
    首先定義使用的哪一個I2C、SCL和SDA引腳,如程式碼段 20.3.1 所示。
    程式碼段 20.3.1 模擬 I2C 引腳相關定義(driver_i2c.h)
/************************* I2C 硬體相關定義 *************************/
#define I2Cx I2C1
#define I2Cx_CLK_EN() __HAL_RCC_I2C1_CLK_ENABLE()
#define I2Cx_ClockSpeed (400000)
#define I2Cx_FORCE_RESET() __HAL_RCC_I2C1_FORCE_RESET()
#define I2Cx_RELEASE_RESET() __HAL_RCC_I2C1_RELEASE_RESET()
#define SCL_PIN GPIO_PIN_6
#define SCL_PORT GPIOB
#define SCL_PIN_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()
#define SDA_PIN GPIO_PIN_7
#define SDA_PORT GPIOB
#define SDA_PIN_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()
/************************* I2C 硬體相關定義結束 *************************/
  1. 初始化I2C I2C初始化和前面UART初始化類似,包含兩部分:協定部分和硬體部分。
    協定部分初始化如程式碼段 20.3.2 所示。
    程式碼段 20.3.2 I2C 協定初始化(driver_i2c.c)
I2C_HandleTypeDef hi2c;
/*
* 函數名:void I2C_Init(void)
* 輸入引數:
* 輸出引數:無
* * 返回值:無
* 函數作用:初始化 I2C 速率和地址格式
*/
void I2C_Init(void) {
hi2c.Instance = I2Cx;
hi2c.Init.ClockSpeed = I2Cx_ClockSpeed; // 設定 SCL 時脈頻率(最高 400000)
hi2c.Init.DutyCycle = I2C_DUTYCYCLE_2; // 設定 I2C 的 SCL 時鐘的佔空比(都可以)
hi2c.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 設定廣播呼叫模式(關閉)
hi2c.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; // 設定禁止時鐘延長模式(關閉)
hi2c.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 設定 I2C 定址長度模式(通常 7bit)
hi2c.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 是否使用兩個 STM32 的裝置地址(關閉)
hi2c.Init.OwnAddress1 = 0x0A; // STM32 的裝置地址 1(支援 7bit 或 10bit)
hi2c.Init.OwnAddress2 = 0; // STM32 的裝置地址 2(只支援 7bit)
if(HAL_I2C_Init(&hi2c) != HAL_OK)
{
Error_Handler(); } }
  • 14~21行:設定I2C協定引數;
    – 14行:設定I2C的傳輸速率,最高不超過400kHz;
    – 15行:設定SCL時鐘的佔空比,即低電平時間比高電平時間,可設定有I2C_DutyCycle_2(2:1)和
    I2C_DutyCycle_16_9(16:9),一般要求不高,任意即可;
    – 16行:I2C作為從機模式時,廣播呼叫模式設定,通常用不上,關閉即可;
    – 17行:I2C作為從機模式時,禁止時鐘延長,通常用不上,關閉即可;
    – 18行:設定I2C定址長度模式,需要根據所接裝置的地址長度決定,通常為7bit;
    – 19行:STM32作為從機模式時,支援同時對兩個裝置地址作出響應,這裡作為主機,關閉即可;
    – 20行:設定STM32的裝置地址1,這裡作為主機,只要裝置地址不與從機一樣即可;
    – 21行:設定STM32的裝置地址2,沒用到,不需要設定;
  • 23行:使用「HAL_I2C_Init()」初始化前面的「hi2c」,「HAL_I2C_Init()」會呼叫「HAL_I2C_MspInit()」
    進行硬體相關初始化,「HAL_I2C_MspInit()」的內容需要自己編寫,如程式碼段 20.3.3 所示;

程式碼段 20.3.3 I2C 硬體初始化(driver_i2c.c)

/*
* 函數名:void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
* 輸入引數:hi2c-I2C 控制程式碼
* 輸出引數:無
* 返回值:無
* 函數作用:使能 I2C 的時鐘,使能引腳時鐘,並設定引腳的複用功能
*/
void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(hi2c->Instance==I2Cx) {
I2Cx_CLK_EN();
SCL_PIN_CLK_EN();
SDA_PIN_CLK_EN();
GPIO_InitStruct.Pin = SCL_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SCL_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = SDA_PIN;
HAL_GPIO_Init(SDA_PORT, &GPIO_InitStruct);
I2Cx_FORCE_RESET(); // 強制復位
I2Cx_RELEASE_RESET(); // 釋放復位
} }

I2C硬體初始化的內容比較簡單,依舊是先使能時鐘,然後設定引腳複用,最後還需要復位下I2C。初始化後,便可使用HAL庫提供的I2C傳送/接收函數,HAL提供三種主機收發函數:

  • HAL_I2C_Master_Receive()/HAL_I2C_Master_Transmit():I2C收發資料,使用超時管理模式;
  • HAL_I2C_Master_Receive_IT()/HAL_I2C_Master_Transmit_IT():I2C收發資料,使用中斷模式;
  • HAL_I2C_Master_Receive_DMA()/HAL_I2C_Master_Transmit_DMA():I2C收發資料,使用DMA模式;這裡三種收發函數都可滿足需求,這裡簡單處理,沒有使用中斷和DMA,因此使用超時管理模式。
  1. 初始化和讀寫AP3426
    由AP3426資料手冊,可知AP3426寫時序如圖 20.3.1 所示,首先傳送裝置地址,其次是命令程式碼(寄存
    器地址),最後是資料內容。根據時序,編寫程式碼如程式碼段 20.3.4 所示。
    在這裡插入圖片描述
    程式碼段 20.3.4 AP3426 寫時序(driver_ap3426.c)
/*
* 函數名:void AP3426_WriteOneByte(uint8_t reg, uint8_t data)
* 輸入引數:reg 待寫 AP3426 暫存器地址 data 待寫資料
* 輸出引數:無
* * 返回值:無
* 函數作用:寫 AP3426 一位元組資料
*/
void AP3426_WriteOneByte(uint8_t reg, uint8_t data)
{
uint16_t write_data = reg | (data<<8);
if(HAL_I2C_Master_Transmit(&hi2c, (AP3426_ADDR << 1) | AP3426_W , (uint8_t*)&write_data, 2, 300) != HAL_OK)
{
Error_Handler(); }
while (HAL_I2C_GetState(&hi2c) != HAL_I2C_STATE_READY);
}
  • 10行:將暫存器地址和待傳送的資料,組成一個資料;
  • 12行:使用「HAL_I2C_Master_Transmit()」傳送資料,該函數需要五個引數:
    – 第一個:指定哪一個I2C;
    – 第二個:指定裝置地址,最後一位為讀/寫位;
    – 第三個:待傳輸的資料所指向的指標;
    – 第四個:指定資料大小,前面將暫存器地址和待傳送資料組在了一次,因此這裡資料長度為2;
    – 第五個:指定超時時間,多長時間沒有收到響應訊號則表示傳輸失敗;
  • 16行:等待正常傳輸完成;

由AP3426資料手冊,可知AP3426讀時序如圖 20.3.1 所示,首先傳送裝置地址,其次是命令程式碼(暫存器地址),然後重新啟動,傳送裝置地址,最後讀取資料內容。根據時序,編寫程式碼如圖 20.3.2 所示。
在這裡插入圖片描述
程式碼段 20.3.5 AP3426 讀時序(driver_ap3426.c)

/*
* 函數名:uint8_t AP3426_ReadOneByte(uint8_t reg)
* 輸入引數:reg 待讀 AP3426 暫存器地址
* 輸出引數:無
* 返回值:讀取的 AP3426 資料
* 函數作用:讀 AP3426 一位元組資料
*/
uint8_t AP3426_ReadOneByte(uint8_t reg)
{
uint8_t read_data = 0;
if(HAL_I2C_Master_Transmit(&hi2c, (AP3426_ADDR << 1) | AP3426_W , (uint8_t*)&reg, 1, 300) != HAL_OK)
{
Error_Handler(); }
while (HAL_I2C_GetState(&hi2c) != HAL_I2C_STATE_READY);
if(HAL_I2C_Master_Receive(&hi2c, (AP3426_ADDR << 1) | AP3426_R , (uint8_t*)&read_data, 1, 300) != HAL_OK)
{
Error_Handler(); }
return read_data;
}
  • 12行:使用「HAL_I2C_Master_Transmit()」傳送暫存器地址;
  • 18行:使用「HAL_I2C_Master_Receive ()」讀取暫存器資料;

AP3426的讀寫時序,分別與前面AT24Cxx隨機讀、位元組寫時序非常類似。對於其它I2C裝置/模組,也差不多。

有了讀寫AP3426的函數,便可以操作暫存器,初始化和讀取AP3426資料。AP3426的初始化比較簡單,流程如下:

  1. 復位:設定System Control暫存器(地址:0x00)為「SW reset」(值:0x04);
  2. 設定工作模式:設定System Control暫存器(地址:0x00)為「ALS and PS+IR functions active」(值:
    0x03),即IR+PS+ALS三個都啟用使用;
  3. 設定中斷(這裡沒使用中斷);
    在這裡插入圖片描述
    程式碼段 20.3.6 AP3426 初始化(driver_ap3426.c)
/*
* 函數名:uint8_t AP3426_Init(void)
* 輸入引數:無
* 輸出引數:無
* 返回值:0 成功 1 失敗
* 函數作用:初始化 AP3426
*/
uint8_t AP3426_Init(void) {
uint8_t ret_value = 0;
AP3426_WriteOneByte(SYS_CONFIG_ADDR, SYS_SW_RESET); // 系統軟體復位
HAL_Delay(50);
AP3426_WriteOneByte(SYS_CONFIG_ADDR, SYS_ALS_PS_IR_ACT); // IR+PS+ALS 三個都啟用使用
HAL_Delay(50);
ret_value = AP3426_ReadOneByte(SYS_CONFIG_ADDR); // 讀取設定暫存器值
if(ret_value != SYS_ALS_PS_IR_ACT) // 判斷是否與設定的一致
{
return 1; }
printf("I2C 設定暫存器:0x%x\n\r", SYS_CONFIG_ADDR);
printf("I2C 設定值為:0x%x\n\r", SYS_ALS_PS_IR_ACT);
printf("I2C 讀取到的設定值:0x%x\n\r", ret_value);
return 0; }
  • 11行:復位AP3426;
  • 14行:設定AP3426的IR(環境紅外光)、PS(距離感應)、ALS(光照強度)都啟用使用;
  • 16~20行:讀取System Control暫存器的值,以便確認是否設定正確;
  • 21~23行:列印偵錯資訊;

初始化設定完後,就可以讀取AP3426暫存器資料,如圖 20.3.4 所示,分別是IR、ALS、PS的資料暫存器,每個資料佔據相鄰兩位。以IR為例,分別讀取IR_DATA_LOW(0x0A)的8位元和IR_DATA_HIGH(0x0B)暫存器的低兩位,再合併就得到IR的資料,同理可以得到ALS、PS的資料,如程式碼段 20.3.7 所示。
在這裡插入圖片描述
程式碼段 20.3.7 獲取 AP3426 資料(driver_ap3426.c)

/*
* 函數名:void AP3426_Read_IR_Data(uint16_t *pIR)
* 輸入引數:無
* 輸出引數:IR 資料
* 返回值:無
* 函數作用:讀 AP3426 IR(環境紅外光)資料
*/
void AP3426_Read_IR_Data(uint16_t *pIR)
{
uint8_t ir_l = 0, ir_h = 0;
ir_l = AP3426_ReadOneByte(IR_DATA_LOW);
ir_h = AP3426_ReadOneByte(IR_DATA_HIGH);
if( (ir_l&0x80) == 0x80) // Invalid IR and PS data
{ *pIR = 0; }
else // ir_l Bit[1:0] is data, ps_l bits[3:0] ans ps_h bits[5:0] is data
{ *pIR = (ir_h<<8) | (ir_l&0x03);
} }
/*
* 函數名:void AP3426_Read_PS_Data(uint16_t *pPS)
* 輸入引數:無
* 輸出引數:PS 資料
* 返回值:無
* 函數作用:讀 AP3426 PS(距離)資料
*/
void AP3426_Read_PS_Data(uint16_t *pPS)
{
uint8_t ps_l = 0, ps_h = 0;
ps_l = AP3426_ReadOneByte(PS_DATA_LOW);
ps_h = AP3426_ReadOneByte(PS_DATA_HIGH);
if( (ps_l&0x40)==0x40) // Invalid IR and PS data
{ *pPS = 0; }
else // ir_l Bit[1:0] is data, ps_l bits[3:0] ans ps_h bits[5:0] is data
{ *pPS = ((ps_h&0x1F)<<8) | (ps_l&0x0F);
} }
/*
* 函數名:vvoid AP3426_Read_ALS_Data(uint16_t *pALS)
* 輸入引數:無
* 輸出引數:ALS 資料
* 返回值:無
* 函數作用:讀 AP3426 ALS(光照)資料
*/
void AP3426_Read_ALS_Data(uint16_t *pALS)
{
uint8_t als_l = 0, als_h = 0;
als_l = AP3426_ReadOneByte(ALS_DATA_LOW);
als_h = AP3426_ReadOneByte(ALS_DATA_HIGH); *pALS = (als_h<<8) | (als_l);
}

然後將三個讀取函數,封裝並列印讀取結果。
程式碼段 20.3.8 AP3426 測試函數(driver_ap3426.c)

/*
* 函數名:void AP3426_ReadDataTest(void)
* 輸入引數:無
* 輸出引數:無
* 返回值:無
* 函數作用:測試讀取 AP3426 所有資料
*/
void AP3426_ReadDataTest(void) {
uint16_t ir = 0, ps = 0, als = 0;
AP3426_Read_IR_Data(&ir);
AP3426_Read_PS_Data(&ps);
AP3426_Read_ALS_Data(&als);
printf("\n\r");
printf("AP3426 IR = 0x%x\n\r", ir);
printf("AP3426 PS = 0x%x\n\r", ps);
printf("AP3426 ALS = 0x%x\n\r", als); }

最後還需設定偵錯串列埠和使用者按鍵,相關程式碼這裡不在贅述。

  1. 主函數控制邏輯
    在主函數裡,每按一下按鍵,呼叫「AP3426_ReadDataTest()」獲取一次AP3426資料,如程式碼段 20.3.9所示。
    程式碼段 20.3.9 主函數控制邏輯(main.c)
// 初始化按鍵
KeyInit();
// 在 windows 下字串\n\r 表示回車
// 如果工程在編譯下面這句中文的時候報錯,請在「Option for target」->"C/C++"->"Misc Controls"新增「 --locale=english」
printf("**********************************************\n\r");
printf("-->百問科技:www.100ask.net\n\r");
printf("-->硬體 I2C 讀取 AP3426 實驗\n\r");
printf("**********************************************\n\r");
// 初始化 I2C
I2C_Init();
// 初始化 AP3426
AP3426_Init();
while(1) {
if(key_flag) // 按鍵按下
{
key_flag = 0;
AP3426_ReadDataTest(); // 讀取 AP3426 資料
} }

20.4 實驗效果

本實驗對應配套資料的「5_程式原始碼\ 12_通訊—硬體I2C\」。開啟工程後,編譯,下載,按下按鍵KEY1(KEY_U)即可獲取一次AP3426資料並在串列埠列印。

AP3426在圖 3.3.1 中編號37處。分別在正常情況、手電筒照射AP3426、遮住AP3426的情況下,獲取資料如圖 20.4.1 所示。

在這裡插入圖片描述


百問網技術論壇:
http://bbs.100ask.net/

百問網嵌入式視訊官網:
https://www.100ask.net/index

百問網開發板:
淘寶:https://100ask.taobao.com/
天貓:https://weidongshan.tmall.com/

技術交流群2(鴻蒙開發/Linux/嵌入式/驅動/資料下載)
QQ群:752871361

微控制器-嵌入式Linux交流群:
QQ群:536785813