飛書文檔https://x509p6c8to.feishu.cn/wiki/MsB7wLebki07eUkAZ1ec12W3nsh
一、簡介
IIC協議,又稱I2C協議,是由PHILP公司在80年代開發的兩線式串行總線,用于連接微控制器及其外圍設備,IIC屬于半雙工同步通信方式。
IIC是一種同步的串行通信總線協議,它可以在多個設備之間傳輸數據。IIC總線由兩根線組成:數據線(SDA)和時鐘線(SCL)。它使用主從模式,其中一個設備作為主設備控制總線并向其他設備發出命令。IIC協議可以支持高速數據傳輸和多設備通信,但它的距離限制較短。 |
多主控(multimastering)
其中任何能夠進行發送和接收的設備都可以成為主總線,一個主控能夠控制信號的傳輸和時鐘頻率。當然,在任何時間點上只能有一個主控。
特征:簡單性和有效性
兩根線,在標準模式下,I2C總線的最大長度為5米,最大速率為100 kbit/s。在快速模式下,I2C總線的最大長度為1米,最大速率為400 kbit/s。在高速模式下,I2C總線的最大長度為0.4米,最大速率為3.4 Mbit/s。需要注意的是,總線長度的實際限制還取決于總線上的電容負載和電纜質量等因素。
IIC完成的通訊過程如下:
IIC完整的通訊過程
- 1、總線是空閑狀態,SCL=1,SDA =1;
- 2、要開始傳輸數據了,此時SCL還是高電平,SCL=1,主機將SDA從1變成0;
- 3、跟哪個從機通訊,把從機的地址發出去。一般地址是8個bit(也有16個bit的),這8個bit其實真實的地址是7個bit,最后1個bit是用來表示讀或者寫的。1表示讀,0表示寫;這個過程相當于主機往SDA上發了8個bit的數據(地址也是數據啊);
- 4、主機發地址的過程,相當于在找從機,從機是要給應答信號的,就是ACK,你老板喊你,你也得先回答聲A吧;
- 5、應答之后,就是要傳輸數據了,如果第3步中發的地址是寫操作,那就由主機來控制SDA的電平變化,如果第3步中發的地址是讀操作,那就由從機來控制SDA的電平變化;
- 6、每次8bit的數據傳輸完成,都要有個應答信號,誰接收數據,誰來應答
- 7、完事之后,在SCL高電平時,主機把SDA從低電平拉高,表示結束。
STM32中的I2C
STM32 芯片有多個 I2C 外設,它們的 I2C 通訊信號引出到不同的 GPIO 引腳上,使用時必須配置到這些指定的引腳。
SMBus(系統管理總線—System Management Bus)
SMBus總線和I2C是比較類似的,所以STM32兼容了這兩種設計,一般場景比較少用SMBus,I2C則是非常多外設使用的接口,我們本節課以I2C為主,看看如何使用STM32的I2C功能。
選擇為I2C功能后,會自動選擇對應的IO作為I2C的IO,這里I2C是支持重映射的,我們可以根據需要手動修改。
然后,我們可以設置I2C的主從模式,這里我們設置主機模式,模式為Standard Mode,速率為100000Hz。
Master?features?主模式特性
Master 為主機模式相關參數,如果是驅動觸摸屏、傳感器、EEPROM等外設,只需配置這里的參數。
Slave 為從機模式相關參數,如果是開發觸摸屏,傳感器本身,則需要配置從機參數。
?
Standard Mode:標準模式
Fast Mode:高速模式
這兩種模式支持的通訊速率不同,在標準模式中,最大只能設置100KHz,作為主機時,速率大小要看從機支持的最大速率,一般來說100KHz可以滿足上述的觸摸屏、傳感器、EEPROM的驅動。
Slave features?從模式特性
作為主機使用時,這里無需修改
Clock?No?Stretch?Mode:?時鐘沒有擴展模式 |
然后就可以生成MDK工程,這里主要用的函數有四個:
?HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, |
EEPROM
EEPROM (Electrically Erasable Programmable read only memory)是指帶電可擦可編程存儲器。是一種掉電后數據不丟失的存儲芯片。
https://item.szlcsc.com/320744.html
BL24C02是一個2Kbit的EEPROM, 內部含有256個字節可以存儲數據,總共有32頁,每頁8Byte。
| |
設備讀寫地址說明
其中設備地址如下,A2 A1 A0對應芯片硬件接的電平
?
如果我們把A2 A1 A0都接到GND,這時候,
寫數據時,設備地址字節應該是0b1010 0000=0xA0
讀數據時,設備地址字節應該是0b1010 0001=0xA1
參考飛書文檔
字節寫
每次寫入一個Byte數據
- 先發送起始信號
- 發送從設備地址+寫入標志數據(Wbit=0),等待應答
- 發送寫入地址,等待應答
- 發送數據,等待應答
- 發送結束信號。
頁寫
每次可以寫入一頁(8Byte)的數據
讀字節
連續讀
連續讀操作可通過立即讀或選擇性讀操作啟動。在 24C02 發送完一個 8 位字節數據后,主器件產生一個應答信號來響應,告知 24C02? 主器件要求更多的數據,對應每個主機產生的應答信號 24C02 將發送一個 8 位數據字節。當主器件不發送應答信號而發送停止位時結束此操作。
STM32CUBEMX開啟I2C1,對應PB6 PB7
然后打開USART1用于打印日志,方便查看
注意,要勾選MicroLIB哦,否則printf打印不了數據
然后添加代碼如下:
main.c/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes *//* USER CODE BEGIN 0 */
#define ADDR_24LCxx_Write 0xA0
#define ADDR_24LCxx_Read 0xA1
#define BufferSize 8
uint8_t WriteBuffer[BufferSize] = {1,2,3,4,5,6,7,8};
uint8_t ReadBuffer[BufferSize] = {0};
/* USER CODE END 0 */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */printf("start to test i2c eeprom\n");if(HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, 0, I2C_MEMADD_SIZE_8BIT, WriteBuffer, sizeof(WriteBuffer), 0xff) == HAL_OK){printf("EEPROM 24C02 Write Test OK \r\n");}HAL_Delay(10);??????????/* read date from EEPROM */HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT, ReadBuffer, sizeof(ReadBuffer), 0xff);for(int i = 0; i < sizeof(ReadBuffer); i++){printf("0x%02X? ",ReadBuffer[i]);}HAL_Delay(1000);}/* USER CODE END 3 *//* USER CODE BEGIN 4 */
int fputc(int ch, FILE *f)
{HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);return ch;
}
打印如下
溫濕度傳感器
溫濕度傳感器:CJ-GXHT3L
GXHT3L-DIS 是中科銀河芯開發的新一代單芯片集成溫濕度一 體傳感器。
★ I2C 接口,通信速度高達 1MHz
★ 兩個用戶可選擇的地址
★ GXHT3L 典型精度為±4%RH 和±0.5°C
★ GXHT30 典型精度為±3%RH 和±0.3°C
★ GXHT31 典型精度為±2%RH 和±0.3°C
★ 單芯片集成溫濕傳感器
★ 高可靠性和長期穩定性
★ 測量 0-100%范圍相對濕度
★ 測量-45-130℃范圍內溫度
https://item.szlcsc.com/3199174.html
| | |
關于設備地址與ADDR管腳說明:
這里要注意的是,0x44指的是I2C地址的高7位,第八位為讀寫標志位。
?
0x44 = 0b0100 0100,把最高位去掉 = 0b100 0100
寫數據時,設備地址字節應該是0b1000 1000=0x88
讀數據時,設備地址字節應該是0b1000 1001=0x89
高重復率和周期轉換頻率,例如0x2130中,21代表每秒轉換一次,30代表高重復率。
設置進入連續轉換模式的命令
* USER CODE BEGIN Includes */
#include <stdio.h>
#define??? GXHT3L_ADDR_WRITE??? 0x44<<1???????? //10001000
#define??? GXHT3L_ADDR_READ???? (0x44<<1)+1???? //10001001typedef enum
{/* 軟件復位命令 */SOFT_RESET_CMD = 0x30A2,???/* 加熱使能/禁能命令 */PREHEAT_ENABLE_CMD = 0x306D,PREHEAT_DISENABLE_CMD = 0x3066,/* 芯片狀態命令 */DEVICE_STATUS_CMD = 0xF32D,/*單次測量模式命名格式:Repeatability_CS_CMDCS:Clock stretching*/HIGH_ENABLED_CMD??? = 0x2C06,MEDIUM_ENABLED_CMD? = 0x2C0D,LOW_ENABLED_CMD???? = 0x2C10,HIGH_DISABLED_CMD?? = 0x2400,MEDIUM_DISABLED_CMD = 0x240B,LOW_DISABLED_CMD??? = 0x2416,/*周期測量模式命名格式:Repeatability_MPS_CMDMPS:measurement per second*/HIGH_0_5_CMD?? = 0x2032,MEDIUM_0_5_CMD = 0x2024,LOW_0_5_CMD??? = 0x202F,HIGH_1_CMD???? = 0x2130,MEDIUM_1_CMD?? = 0x2126,LOW_1_CMD????? = 0x212D,HIGH_2_CMD???? = 0x2236,MEDIUM_2_CMD?? = 0x2220,LOW_2_CMD????? = 0x222B,HIGH_4_CMD???? = 0x2334,MEDIUM_4_CMD?? = 0x2322,LOW_4_CMD????? = 0x2329,HIGH_10_CMD??? = 0x2737,MEDIUM_10_CMD? = 0x2721,LOW_10_CMD???? = 0x272A,/* 周期測量模式讀取數據命令 */READOUT_FOR_PERIODIC_MODE = 0xE000,
} GXHT3L_CMD;
/* USER CODE END Includes *//* USER CODE BEGIN 0 */
/*** @brief??? 向GXHT3L發送一條指令(16bit)* @param??? cmd —— GXHT3L指令(在GXHT3L_MODE中枚舉定義)* @retval??? 成功返回HAL_OK
*/
static uint8_t GXHT3L_Send_Cmd(GXHT3L_CMD cmd)
{uint8_t cmd_buffer[2];cmd_buffer[0] = cmd >> 8;cmd_buffer[1] = cmd;return HAL_I2C_Master_Transmit(&hi2c2, GXHT3L_ADDR_WRITE, (uint8_t*) cmd_buffer, 2, 0xFFFF);
}/*** @brief??? 復位GXHT3L* @param??? none* @retval??? none
*/
static void GXHT3L_Reset(void)
{GXHT3L_Send_Cmd(SOFT_RESET_CMD);HAL_Delay(20);
}void GXHT3L_Preheat_Disable(void)
{GXHT3L_Send_Cmd(PREHEAT_DISENABLE_CMD);HAL_Delay(20);
}uint8_t GXHT3L_Read_Status(uint8_t* dat)
{GXHT3L_Send_Cmd(DEVICE_STATUS_CMD);return HAL_I2C_Master_Receive(&hi2c2, GXHT3L_ADDR_READ, dat, 3, 0xFFFF);
}/*** @brief??? 初始化GXHT3L* @param??? none* @retval??? 成功返回HAL_OK* @note??? 周期測量模式
*/
uint8_t GXHT3L_Init(void)
{return GXHT3L_Send_Cmd(MEDIUM_2_CMD);
}/*** @brief??? 從GXHT3L讀取一次數據* @param??? dat —— 存儲讀取數據的地址(6個字節數組)* @retval??? 成功 —— 返回HAL_OK
*/
uint8_t GXHT3L_Read_Dat(uint8_t* dat)
{GXHT3L_Send_Cmd(READOUT_FOR_PERIODIC_MODE);return HAL_I2C_Master_Receive(&hi2c2, GXHT3L_ADDR_READ, dat, 6, 0xFFFF);
}#define CRC8_POLYNOMIAL 0x31uint8_t CheckCrc8(uint8_t* const message, uint8_t initial_value)
{uint8_t? remainder;??????? //余數uint8_t? i = 0, j = 0;? //循環變量/* 初始化 */remainder = initial_value;for(j = 0; j < 2;j++){remainder ^= message[j];/* 從最高位開始依次計算? */for (i = 0; i < 8; i++){if (remainder & 0x80){remainder = (remainder << 1)^CRC8_POLYNOMIAL;}else{remainder = (remainder << 1);}}}/* 返回計算的CRC碼 */return remainder;
}/*** @brief??? 將GXHT3L接收的6個字節數據進行CRC校驗,并轉換為溫度值和濕度值* @param??? dat? —— 存儲接收數據的地址(6個字節數組)* @retval??? 校驗成功? —— 返回0*???????????? 校驗失敗? —— 返回1,并設置溫度值和濕度值為0
*/
uint8_t GXHT3L_Dat_To_Float(uint8_t* const dat, float* temperature, float* humidity)
{uint16_t recv_temperature = 0;uint16_t recv_humidity = 0;/* 校驗溫度數據和濕度數據是否接收正確 */if(CheckCrc8(dat, 0xFF) != dat[2] || CheckCrc8(&dat[3], 0xFF) != dat[5])return 1;/* 轉換溫度數據 */recv_temperature = ((uint16_t)dat[0]<<8)|dat[1];*temperature = -45 + 175*((float)recv_temperature/65535);/* 轉換濕度數據 */recv_humidity = ((uint16_t)dat[3]<<8)|dat[4];*humidity = 100 * ((float)recv_humidity / 65535);return 0;
}/* USER CODE BEGIN 1 */uint8_t recv_dat[6] = {0};uint8_t recv_status[3] = {0};float temperature = 0.0;float humidity = 0.0;/* USER CODE END 1 *//* USER CODE BEGIN 2 */GXHT3L_Reset();if(GXHT3L_Init() == HAL_OK)printf("GXHT3L init ok.\n");elseprintf("GXHT3L init fail.\n");if(GXHT3L_Read_Status(recv_status) == HAL_OK){printf("GXHT3L Read Status ok. Status = 0x%x%x\n",recv_status[0],recv_status[1]);}elseprintf("GXHT3L Read Status fail.\n");/* USER CODE END 2 */while (1){/* USER CODE BEGIN 3 */HAL_Delay(1000);if(GXHT3L_Read_Dat(recv_dat) == HAL_OK){if(GXHT3L_Dat_To_Float(recv_dat, &temperature, &humidity)==0){printf("temperature = %f, humidity = %f\n", temperature, humidity);}else{printf("crc check fail.\n");}}else{printf("read data from GXHT3L fail.\n");}}/* USER CODE END 3 *//* USER CODE BEGIN 4 */
int fputc(int ch, FILE *f)
{HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);return ch;
}
/* USER CODE END 4 */
最終工程,可以參考淘寶旺旺發送的源碼部分哦