第19章 I2C介绍及应用
第十九章 I2C介绍及应用
1. I2C简介
I₂C(读作“I-squared-C”或“I-two-C”)是一种由飞利浦公司(现为NXP Semiconductors)在1980年代初开发的串行通信总线协议,用于连接低速外围设备与主控制器,广泛应用于嵌入式系统、传感器、EEPROM、实时时钟(RTC)、LCD驱动器等设备之间的通信。
基本特点:
- 双线制通信:
- SDA(Serial Data Line):串行数据线,用于传输数据。
- SCL(Serial Clock Line):串行时钟线,由主设备控制,同步数据传输。
- 半双工通信:
- 同一根数据线用于发送和接收,不能同时进行。
- 多主多从架构:
- 支持多个主设备和多个从设备挂接在同一总线上。
- 通过仲裁机制避免多个主设备同时通信造成冲突。
- 地址寻址机制:
- 每个从设备具有唯一的7位或10位地址。
- 主设备通过地址选择目标从设备进行通信。
- 低速但可靠:
- 常见速率:标准模式(100 kbps)、快速模式(400 kbps)、高速模式(3.4 Mbps)。
- 适合短距离板内通信。
- 开漏输出 + 上拉电阻:
- SDA 和 SCL 通常使用开漏(Open-Drain)输出结构,需要外接上拉电阻(通常为4.7kΩ)。
- 支持“线与”逻辑,实现多设备共享总线。
| 信号 | 触发条件 | 方向 | 作用 |
|---|---|---|---|
| Start | SCL高,SDA由高→低 | 主设备 | 开始通信 |
| Stop | SCL高,SDA由低→高 | 主设备 | 结束通信 |
| Repeated Start | 同 Start,但之前无 Stop | 主设备 | 切换读写不释放总线 |
| Data Bit | SCL低时变化,高时稳定 | 主/从 | 传输数据 |
| ACK | 接收方在第9位时拉低SDA | 接收方 | 确认接收成功 |
| NACK | 接收方不拉低SDA | 接收方 | 拒绝接收或结束 |
2. I2C应用示例
2.1 I2C初始化
#include "i2c.h"
#include "delay.h"
// I2C延时
static void i2c_delay(void)
{
delay_us(2);
}
// I2C起始信号
void i2c_start(void)
{
I2C_SDA(1);
I2C_SCL(1);
i2c_delay();
I2C_SDA(0); /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */
i2c_delay();
I2C_SCL(0); /* 钳住I2C总线,准备发送或接收数据 */
i2c_delay();
}
void i2c_stop(void)
{
I2C_SDA(0); /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
i2c_delay();
I2C_SCL(1);
i2c_delay();
I2C_SDA(1); /* 发送I2C总线结束信号 */
i2c_delay();
}
// 等待应答信号 1:接收应答失败 0:接收应答成功
uint8_t i2c_wait_ack(void)
{
uint8_t wait_time = 0;
uint8_t wait_ack = 0;
I2C_SDA(1);
i2c_delay();
I2C_SCL(1);
i2c_delay();
while(I2C_READ_SDA)
{
wait_time++;
if(wait_time > 250)
{
i2c_stop();
wait_ack = 1;
break;
}
}
I2C_SCL(0);
i2c_delay();
return wait_ack;
}
// 产生ACK应答
void i2c_ack(void)
{
I2C_SDA(0);
i2c_delay();
I2C_SCL(1);
i2c_delay();
I2C_SCL(0);
i2c_delay();
I2C_SDA(1);
i2c_delay();
}
// 产生NACK应答
void i2c_nack(void)
{
I2C_SDA(1);
i2c_delay();
I2C_SCL(1);
i2c_delay();
I2C_SCL(0);
i2c_delay();
}
// I2C发送一个字节
void i2c_send_byte(uint8_t data)
{
uint8_t i;
for(i=0;i<8;i++)
{
I2C_SDA((data&0x80)>>7);
i2c_delay();
I2C_SCL(1);
i2c_delay();
I2C_SCL(0);
data <<= 1;
}
I2C_SDA(1);
}
// I2C读取一个字节
// ack=1时,发送ack; ack=0时,发送nack
uint8_t i2c_read_byte(uint8_t ack)
{
uint8_t i,reve = 0;
for(i=0;i<8;i++)
{
reve <<= 1;
I2C_SCL(1);
i2c_delay();
if(I2C_READ_SDA)
{
reve++;
}
I2C_SCL(0);
i2c_delay();
}
if(!ack)
{
i2c_nack();
}
else
{
i2c_ack();
}
return reve;
}
// I2C GPIO初始化
void i2c_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
__HAL_RCC_GPIOB_CLK_ENABLE();
// SCL-PB8 SDA-PB9
GPIO_InitStructure.Pin = GPIO_PIN_8;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStructure.Pull = GPIO_PULLUP; // 上拉
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_9;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
i2c_stop();
}
2.2 AT24CXX相关宏定义
#ifndef __24C02_H
#define __24C02_H
#include "sys.h"
#include "i2c.h"
#include "delay.h"
#define AT24C01 127
#define AT24C02 255
#define AT24C04 511
#define AT24C08 1023
#define AT24C16 2047
#define AT24C32 4095
#define AT24C64 8191
#define AT24C128 16383
#define AT24C256 32767
#define MY_TYPE AT24C02
void at24cxx_init(void);
uint8_t at24cxx_read_one_byte(uint16_t addr);
void at24cxx_write_one_byte(uint16_t addr, uint8_t data);
uint8_t at24cxx_check(void);
void at24cxx_read(uint16_t addr, uint8_t *pbuf, uint16_t len);
void at24cxx_write(uint16_t addr, uint8_t *pbuf, uint16_t len);
#endif /* __24C02_H */
2.3 AT24CXX读写相关函数
#include "24c02.h"
void at24cxx_init(void)
{
i2c_init();
}
/**
* @brief 在AT24CXX指定地址读出一个数据
* @param readaddr: 开始读数的地址
* @retval 读到的数据
*/
uint8_t at24cxx_read_one_byte(uint16_t addr)
{
uint8_t temp = 0;
i2c_start(); // 发送起始信号
/* 根据不同的24CXX型号, 发送高位地址
* 1, 24C16以上的型号, 分2个字节发送地址
* 2, 24C16及以下的型号, 分1个低字节地址 + 占用器件地址的bit1~bit3位 用于表示高位地址, 最多11位地址
* 对于24C01/02, 其器件地址格式(8bit)为: 1 0 1 0 A2 A1 A0 R/W
* 对于24C04, 其器件地址格式(8bit)为: 1 0 1 0 A2 A1 a8 R/W
* 对于24C08, 其器件地址格式(8bit)为: 1 0 1 0 A2 a9 a8 R/W
* 对于24C16, 其器件地址格式(8bit)为: 1 0 1 0 a10 a9 a8 R/W
* R/W : 读/写控制位 0,表示写; 1,表示读;
* A0/A1/A2 : 对应器件的1,2,3引脚(只有24C01/02/04/8有这些脚)
* a8/a9/a10: 对应存储整列的高位地址, 11bit地址最多可以表示2048个位置, 可以寻址24C16及以内的型号
*/
if(MY_TYPE > AT24C16) // 24C16以上的型号, 分2个字节发送地址
{
i2c_send_byte(0xA0); // 发送写命令, IIC规定最低位是0, 表示写入
i2c_wait_ack(); // 每次发送完一个字节,都要等待ACK
i2c_send_byte(addr >> 8); // 发送高字节地址
}
else
{
i2c_send_byte(0xA0 + ((addr >> 8) << 1)); // 发送器件 0xA0 + 高位a8/a9/a10地址,写数据
}
i2c_wait_ack(); // 每次发送完一个字节,都要等待ACK
i2c_send_byte(addr % 256);// 发送低位地址
i2c_wait_ack(); // 等待ACK, 此时地址发送完成了
i2c_start(); // 重新发送起始信号
i2c_send_byte(0xA1); // 进入接收模式, IIC规定最低位是1, 表示读取
i2c_wait_ack(); // 次发送完一个字节,都要等待ACK
temp = i2c_read_byte(0); // 接收一个字节数据
i2c_stop(); // 产生一个停止条件
return temp;
}
/**
* @brief 在AT24CXX指定地址写入一个数据
* @param addr: 写入数据的目的地址
* @param data: 要写入的数据
* @retval 无
*/
void at24cxx_write_one_byte(uint16_t addr, uint8_t data)
{
/* 原理说明见:at24cxx_read_one_byte函数, 本函数完全类似 */
i2c_start();
if (MY_TYPE > AT24C16)
{
i2c_send_byte(0xA0);
i2c_wait_ack();
i2c_send_byte(addr >> 8);
}
else
{
i2c_send_byte(0xA0 + ((addr >> 8) << 1));
}
i2c_wait_ack();
i2c_send_byte(addr % 256);
i2c_wait_ack();
/* 因为写数据的时候,不需要进入接收模式了,所以这里不用重新发送起始信号了 */
i2c_send_byte(data); // 发送1字节
i2c_wait_ack();
i2c_stop();
delay_ms(10); // 注意: EEPROM 写入比较慢,必须等到10ms后再写下一个字节
}
/**
* @brief 检查AT24CXX是否正常
* @note 检测原理: 在器件的末地址写如0X55, 然后再读取, 如果读取值为0X55
* 则表示检测正常. 否则,则表示检测失败.
*
* @param 无
* @retval 检测结果
* 0: 检测成功
* 1: 检测失败
*/
uint8_t at24cxx_check(void)
{
uint8_t temp;
uint16_t addr = MY_TYPE;
temp = at24cxx_read_one_byte(addr); // 避免每次开机都写AT24CXX
if (temp == 0x55) // 读取数据正常
{
return 0;
}
else
{
at24cxx_write_one_byte(addr, 0x55); // 先写入数据
temp = at24cxx_read_one_byte(255); // 再读取数据
if (temp == 0x55)return 0;
}
return 1;
}
// 在AT24CXX里面指定地址开始读出指定个数的数据
void at24cxx_read(uint16_t addr, uint8_t *pbuf, uint16_t len)
{
while (len--)
{
*pbuf++ = at24cxx_read_one_byte(addr++);
}
}
// 在AT24CXX里面的指定地址开始写入指定个数的数据
void at24cxx_write(uint16_t addr, uint8_t *pbuf, uint16_t len)
{
while(len--)
{
at24cxx_write_one_byte(addr, *pbuf);
addr++;
pbuf++;
}
}
2.4 主函数测试
#include "bsp_init.h"
#include "stdio.h"
#include "24c02.h"
// 要写入的字符串
const uint8_t text_buf[] = {"STM32 I2C TEST"};
#define TEXT_SIZE sizeof(text_buf)
int main(void)
{
uint16_t i = 0;
uint8_t data[TEXT_SIZE];
bsp_init();
at24cxx_init();
LCD_ShowString(30,110,200,16,16,"KEY1:Write KEY0:Read");
while(at24cxx_check())
{
LCD_ShowString(30,130,200,16,16,"24C02 Check Failed!");
}
LCD_ShowString(30,130,200,16,16,"24C02 Ready!");
while(1)
{
if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY0_GPIO_Pin) == 0) // KEY0按下写入
{
LCD_ShowString(30,150,200,16,16,"Start Write 24C02....");
at24cxx_write(0, (uint8_t*)text_buf, TEXT_SIZE);
LCD_ShowString(30,150,200,16,16,"24C02 Write Finished!");
}
if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY1_GPIO_Pin) == 0)
{
LCD_ShowString(30,150,200,16,16,"Start Read 24C02.... ");
at24cxx_read(0, data, TEXT_SIZE);
LCD_ShowString(30,150,200,16,16,"The Data Readed Is: ");
LCD_ShowString(30,170,200,16,16,(char*)data);
}
i++;
if(i == 20)
{
LED_TOGGLE(LED0_GPIO_Pin);
i = 0;
}
delay_ms(10);
}
}
3. I2C常见函数(HAL库)
3.1 I2C 初始化与配置
3.1.1 HAL_I2C_Init()
函数原型:
HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c)
参数:
hi2c: I2C 句柄指针
配置结构体:
typedef struct {
I2C_TypeDef *Instance; // I2C实例 (I2C1, I2C2, I2C3)
uint32_t ClockSpeed; // 时钟速度 (Hz) 标准模式100K, 快速模式400K
uint32_t DutyCycle; // 快速模式占空比:
// I2C_DUTYCYCLE_2 (Tlow/Thigh = 2)
// I2C_DUTYCYCLE_16_9 (Tlow/Thigh = 16/9)
uint32_t OwnAddress1; // 主设备自身地址 (7位或10位)
uint32_t AddressingMode; // 地址模式:
// I2C_ADDRESSINGMODE_7BIT
// I2C_ADDRESSINGMODE_10BIT
uint32_t DualAddressMode; // 双地址模式: I2C_DUALADDRESS_DISABLE/ENABLE
uint32_t OwnAddress2; // 第二个自身地址 (仅双地址模式)
uint32_t GeneralCallMode; // 广播呼叫模式: I2C_GENERALCALL_DISABLE/ENABLE
uint32_t NoStretchMode; // 时钟拉伸模式: I2C_NOSTRETCH_DISABLE/ENABLE
} I2C_InitTypeDef;
示例配置 (主模式):
I2C_HandleTypeDef hi2c1;
void I2C_Init(void) {
__HAL_RCC_I2C1_CLK_ENABLE();
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000; // 400KHz (快速模式)
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // Tlow/Thigh = 2
hi2c1.Init.OwnAddress1 = 0; // 主设备不需要地址
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
Error_Handler();
}
}
3.2 I2C 主模式操作
3.2.1 主设备发送数据
// 阻塞模式发送
HAL_StatusTypeDef HAL_I2C_Master_Transmit(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout)
// 中断模式发送
HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint8_t *pData,
uint16_t Size)
// DMA模式发送
HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint8_t *pData,
uint16_t Size)
示例 (发送数据到从设备):
#define I2C_ADDRESS 0x68 // DS3231 RTC地址
uint8_t data[2] = {0x00, 0x01}; // 寄存器地址和值
// 阻塞模式发送
HAL_I2C_Master_Transmit(&hi2c1, I2C_ADDRESS << 1, data, 2, 100);
3.2.2 主设备接收数据
// 阻塞模式接收
HAL_StatusTypeDef HAL_I2C_Master_Receive(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout)
// 中断模式接收
HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint8_t *pData,
uint16_t Size)
// DMA模式接收
HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint8_t *pData,
uint16_t Size)
示例 (从设备读取数据):
uint8_t reg_addr = 0x00; // 寄存器地址
uint8_t value = 0;
// 发送寄存器地址
HAL_I2C_Master_Transmit(&hi2c1, I2C_ADDRESS << 1, ®_addr, 1, 100);
// 接收寄存器值
HAL_I2C_Master_Receive(&hi2c1, I2C_ADDRESS << 1, &value, 1, 100);
3.3 I2C 从模式操作
3.3.1 从设备接收数据
// 阻塞模式接收
HAL_StatusTypeDef HAL_I2C_Slave_Receive(
I2C_HandleTypeDef *hi2c,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout)
// 中断模式接收
HAL_StatusTypeDef HAL_I2C_Slave_Receive_IT(
I2C_HandleTypeDef *hi2c,
uint8_t *pData,
uint16_t Size)
// DMA模式接收
HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(
I2C_HandleTypeDef *hi2c,
uint8_t *pData,
uint16_t Size)
3.3.2 从设备发送数据
// 阻塞模式发送
HAL_StatusTypeDef HAL_I2C_Slave_Transmit(
I2C_HandleTypeDef *hi2c,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout)
// 中断模式发送
HAL_StatusTypeDef HAL_I2C_Slave_Transmit_IT(
I2C_HandleTypeDef *hi2c,
uint8_t *pData,
uint16_t Size)
// DMA模式发送
HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(
I2C_HandleTypeDef *hi2c,
uint8_t *pData,
uint16_t Size)
3.4 I2C 内存访问 (常用于EEPROM/Sensor)
3.4.1 写内存
// 阻塞模式写内存
HAL_StatusTypeDef HAL_I2C_Mem_Write(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint16_t MemAddress,
uint16_t MemAddSize,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout)
// 中断模式写内存
HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint16_t MemAddress,
uint16_t MemAddSize,
uint8_t *pData,
uint16_t Size)
// DMA模式写内存
HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint16_t MemAddress,
uint16_t MemAddSize,
uint8_t *pData,
uint16_t Size)
3.4.2 读内存
// 阻塞模式读内存
HAL_StatusTypeDef HAL_I2C_Mem_Read(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint16_t MemAddress,
uint16_t MemAddSize,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout)
// 中断模式读内存
HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint16_t MemAddress,
uint16_t MemAddSize,
uint8_t *pData,
uint16_t Size)
// DMA模式读内存
HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint16_t MemAddress,
uint16_t MemAddSize,
uint8_t *pData,
uint16_t Size)
示例 (读写EEPROM):
#define EEPROM_ADDR 0xA0 // 24C02 EEPROM地址
uint16_t mem_addr = 0x0010; // 内存地址
uint8_t write_data[4] = {0x12, 0x34, 0x56, 0x78};
uint8_t read_data[4];
// 写数据到EEPROM
HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, mem_addr, I2C_MEMADD_SIZE_16BIT, write_data, 4, 100);
HAL_Delay(5); // EEPROM写入需要时间
// 从EEPROM读取数据
HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, mem_addr, I2C_MEMADD_SIZE_16BIT, read_data, 4, 100);
3.5 I2C 中断处理
3.5.1 中断服务函数
// I2C事件中断服务函数
void I2C1_EV_IRQHandler(void) {
HAL_I2C_EV_IRQHandler(&hi2c1);
}
// I2C错误中断服务函数
void I2C1_ER_IRQHandler(void) {
HAL_I2C_ER_IRQHandler(&hi2c1);
}
3.5.2 回调函数 (用户实现)
// 主发送完成回调
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
// 主接收完成回调
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
// 从发送完成回调
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c)
// 从接收完成回调
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
// 内存操作完成回调
void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c)
void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)
// 错误回调
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
示例实现:
// 主发送完成回调
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
// 主接收完成回调
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
// 从发送完成回调
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c)
// 从接收完成回调
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
// 内存操作完成回调
void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c)
void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)
// 错误回调
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
3.6 I2C 高级功能
3.6.1 SMBus 特定功能
// 发送SMBus警报
HAL_StatusTypeDef HAL_I2C_Slave_Default_Callback(I2C_HandleTypeDef *hi2c)
// 启用/禁用SMBus超时
HAL_StatusTypeDef HAL_I2C_EnableListen_IT(I2C_HandleTypeDef *hi2c)
HAL_StatusTypeDef HAL_I2C_DisableListen_IT(I2C_HandleTypeDef *hi2c)
3.6.2 多主机仲裁
// 检查总线是否空闲
HAL_StatusTypeDef HAL_I2C_IsDeviceReady(
I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint32_t Trials,
uint32_t Timeout)
示例 (检查设备存在):
// 检查地址0x68的设备是否存在
if (HAL_I2C_IsDeviceReady(&hi2c1, 0x68 << 1, 3, 100) == HAL_OK) {
// 设备在线
} else {
// 设备未响应
}
3.7 I2C 状态管理
3.7.1 状态获取函数
// 获取I2C状态
HAL_I2C_StateTypeDef HAL_I2C_GetState(I2C_HandleTypeDef *hi2c)
// 获取错误代码
uint32_t HAL_I2C_GetError(I2C_HandleTypeDef *hi2c)
3.7.2 错误代码常量
HAL_I2C_ERROR_NONE // 无错误
HAL_I2C_ERROR_BERR // BERR错误
HAL_I2C_ERROR_ARLO // 仲裁丢失
HAL_I2C_ERROR_AF // 应答失败
HAL_I2C_ERROR_OVR // 溢出/欠载错误
HAL_I2C_ERROR_DMA // DMA传输错误
HAL_I2C_ERROR_TIMEOUT // 超时错误
HAL_I2C_ERROR_SIZE // 大小错误

浙公网安备 33010602011771号