在嵌入式开发中,I2C总线因其简单的两线制设计和多主多从的灵活性,成为设备间通信的常见选择。本文将带你完整实现树莓派作为I2C主机与STM32F407作为从机的通信系统,重点解决实际开发中遇到的HAL库函数陷阱问题。
正确的物理连接是通信成功的第一步。I2C总线只需要两根信号线:
| 信号线 | 树莓派引脚 | STM32F407引脚 |
|---|---|---|
| SDA | GPIO2 | PB7 |
| SCL | GPIO3 | PB6 |
| GND | 任意GND | GND |
提示:务必确保两设备共地,这是I2C通信正常工作的基础条件。
在树莓派终端执行以下命令启用I2C接口:
bash复制sudo raspi-config
选择"Interfacing Options" → "I2C" → "Yes"启用接口。安装测试工具:
bash复制sudo apt-get install i2c-tools
验证I2C设备是否识别:
bash复制ls /dev/i2c-*
正常应显示/dev/i2c-1设备文件。
在main.c中添加以下代码实现从机功能:
c复制uint8_t rx_buff[10] = {0};
uint8_t tx_buff[260] = { /* 初始化数据 */ };
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) {
__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_ADDR);
if(TransferDirection == I2C_DIRECTION_RECEIVE) {
// 主机请求数据,从机需要发送
HAL_I2C_Slave_Seq_Transmit_IT(&hi2c1, tx_buff, 1, I2C_FIRST_FRAME);
} else {
// 主机发送数据,从机需要接收
HAL_I2C_Slave_Seq_Receive_IT(&hi2c1, rx_buff, 1, I2C_FIRST_AND_NEXT_FRAME);
}
}
检测从机设备是否存在:
bash复制i2cdetect -y 1
正常应显示类似如下输出:
code复制 0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: 30 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
读取从机数据:
bash复制i2cget -y 1 0x30 0x00
写入数据到从机:
bash复制i2cset -y 1 0x30 0x00 0x55
连续读写测试:
bash复制i2ctransfer -y 1 w2@0x30 0x00 0xAA r2
注意:使用
i2ctransfer需要安装较新版本的i2c-tools(v4.0以上)
原始代码中常见的错误用法:
c复制// 错误用法:会导致总线挂起
HAL_I2C_Slave_Transmit_IT(&hi2c1, tx_buff, 1);
HAL_I2C_Slave_Receive_IT(&hi2c1, rx_buff, 1);
正确应使用序列函数:
c复制// 正确用法
HAL_I2C_Slave_Seq_Transmit_IT(&hi2c1, tx_buff, 1, I2C_FIRST_FRAME);
HAL_I2C_Slave_Seq_Receive_IT(&hi2c1, rx_buff, 1, I2C_FIRST_AND_NEXT_FRAME);
在HAL_I2C_SlaveRxCpltCallback中实现自动重载:
c复制void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) {
// 处理接收到的数据...
// 重新启用接收
HAL_I2C_Slave_Seq_Receive_IT(hi2c, rx_buff, 1, I2C_FIRST_AND_NEXT_FRAME);
}
添加错误处理回调:
c复制void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) {
// 清除错误标志
__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF);
// 重新初始化I2C
HAL_I2C_DeInit(hi2c);
MX_I2C1_Init();
HAL_I2C_EnableListen_IT(hi2c);
}
修改CubeMX中的I2C配置:
| 参数 | 标准模式 | 快速模式 |
|---|---|---|
| Clock Speed (kHz) | 100 | 400 |
| Rise Time (ns) | 1000 | 300 |
| Fall Time (ns) | 300 | 300 |
注意:提高速率需确保硬件布线质量,过长或干扰大的线路可能导致通信失败
扩展从机功能,实现类似EEPROM的读写:
c复制#define EEPROM_SIZE 256
uint8_t virtual_eeprom[EEPROM_SIZE];
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) {
static uint8_t address = 0;
static uint8_t is_address_set = 0;
if(!is_address_set) {
address = rx_buff[0]; // 第一个字节为地址
is_address_set = 1;
} else {
virtual_eeprom[address] = rx_buff[0]; // 第二个字节为数据
is_address_set = 0;
}
HAL_I2C_Slave_Seq_Receive_IT(hi2c, rx_buff, 1, I2C_FIRST_AND_NEXT_FRAME);
}
STM32F407支持双地址功能,可在CubeMX中配置:
c复制hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_ENABLE;
hi2c1.Init.OwnAddress2 = 0x40; // 第二个从机地址
hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
推荐配置:
在STM32中添加调试输出:
c复制printf("I2C状态: 方向=%s, 地址=0x%02X\n",
(TransferDirection == I2C_DIRECTION_RECEIVE) ? "读" : "写",
AddrMatchCode);
实时监控I2C总线活动:
bash复制sudo apt-get install sigrok
sigrok-cli -d fx2lafw --continuous -o i2c.sr