基于STM32的I2C总线程序设计与实现
STM32 I2C总线驱动程序,包含主机模式、从机模式、中断传输、DMA传输、多主机通信和常见设备驱动。
一、I2C基础与配置
1.1 I2C核心定义
/* I2C 核心数据结构定义 */
#include "stm32f1xx_hal.h"
#include <stdio.h>
#include <string.h>
// I2C错误码定义
typedef enum {
I2C_OK = 0,
I2C_ERROR = 1,
I2C_BUSY = 2,
I2C_TIMEOUT = 3,
I2C_NACK = 4,
I2C_ARBITRATION_LOST = 5,
I2C_BUS_ERROR = 6
} I2C_Status;
// I2C操作模式
typedef enum {
I2C_MODE_MASTER_TX, // 主机发送模式
I2C_MODE_MASTER_RX, // 主机接收模式
I2C_MODE_SLAVE_TX, // 从机发送模式
I2C_MODE_SLAVE_RX // 从机接收模式
} I2C_Mode;
// I2C传输结构体
typedef struct {
uint8_t *data; // 数据缓冲区
uint16_t size; // 数据大小
uint16_t index; // 当前索引
uint8_t address; // 设备地址
uint8_t subaddress; // 寄存器地址
uint8_t subaddr_size; // 寄存器地址大小(1-4字节)
I2C_Status status; // 传输状态
uint32_t timeout; // 超时时间
} I2C_Transfer;
// 全局I2C句柄
I2C_HandleTypeDef hi2c1;
I2C_HandleTypeDef hi2c2;
1.2 I2C引脚定义
/* I2C引脚配置 */
// I2C1引脚定义
#define I2C1_SCL_PORT GPIOB
#define I2C1_SCL_PIN GPIO_PIN_6
#define I2C1_SDA_PORT GPIOB
#define I2C1_SDA_PIN GPIO_PIN_7
// I2C2引脚定义
#define I2C2_SCL_PORT GPIOB
#define I2C2_SCL_PIN GPIO_PIN_10
#define I2C2_SDA_PORT GPIOB
#define I2C2_SDA_PIN GPIO_PIN_11
// 上拉电阻(如果外部没有,可以启用内部上拉)
#define I2C_PULLUP_ENABLE 0 // 0=禁用内部上拉,1=启用
二、I2C主机模式配置
2.1 I2C主机初始化
/* I2C主机初始化 */
void I2C1_Init_Master(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIO时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
// 使能I2C1时钟
__HAL_RCC_I2C1_CLK_ENABLE();
// 配置I2C引脚
// SCL引脚配置
GPIO_InitStruct.Pin = I2C1_SCL_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 开漏输出
GPIO_InitStruct.Pull = (I2C_PULLUP_ENABLE) ? GPIO_PULLUP : GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(I2C1_SCL_PORT, &GPIO_InitStruct);
// SDA引脚配置
GPIO_InitStruct.Pin = I2C1_SDA_PIN;
HAL_GPIO_Init(I2C1_SDA_PORT, &GPIO_InitStruct);
// 初始化I2C1
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000; // 100kHz标准模式
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // 占空比
hi2c1.Init.OwnAddress1 = 0x00; // 主机模式设为0
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 7位地址
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
Error_Handler();
}
printf("I2C1 Master Initialized (100kHz)\n");
}
/* 配置不同的I2C速率 */
void I2C1_SetSpeed(uint32_t speed) {
hi2c1.Init.ClockSpeed = speed;
if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
Error_Handler();
}
printf("I2C1 speed changed to: %ld Hz\n", speed);
}
/* 选择I2C速率 */
void I2C1_SelectSpeed(uint8_t mode) {
switch (mode) {
case 0: // 标准模式
I2C1_SetSpeed(100000);
break;
case 1: // 快速模式
I2C1_SetSpeed(400000);
break;
case 2: // 快速模式+
I2C1_SetSpeed(1000000);
break;
default:
I2C1_SetSpeed(100000);
break;
}
}
2.2 轮询方式通信
/* 轮询方式I2C通信 */
I2C_Status I2C1_Master_Transmit(uint8_t dev_addr, uint8_t *data, uint16_t size, uint32_t timeout) {
HAL_StatusTypeDef status;
status = HAL_I2C_Master_Transmit(&hi2c1, dev_addr << 1, data, size, timeout);
if (status == HAL_OK) {
return I2C_OK;
} else if (status == HAL_TIMEOUT) {
return I2C_TIMEOUT;
} else {
return I2C_ERROR;
}
}
I2C_Status I2C1_Master_Receive(uint8_t dev_addr, uint8_t *data, uint16_t size, uint32_t timeout) {
HAL_StatusTypeDef status;
status = HAL_I2C_Master_Receive(&hi2c1, dev_addr << 1, data, size, timeout);
if (status == HAL_OK) {
return I2C_OK;
} else if (status == HAL_TIMEOUT) {
return I2C_TIMEOUT;
} else {
return I2C_ERROR;
}
}
/* 带寄存器地址的读写 */
I2C_Status I2C1_Mem_Write(uint8_t dev_addr, uint16_t mem_addr, uint16_t mem_addr_size,
uint8_t *data, uint16_t size, uint32_t timeout) {
HAL_StatusTypeDef status;
status = HAL_I2C_Mem_Write(&hi2c1, dev_addr << 1, mem_addr, mem_addr_size, data, size, timeout);
if (status == HAL_OK) {
return I2C_OK;
} else {
return I2C_ERROR;
}
}
I2C_Status I2C1_Mem_Read(uint8_t dev_addr, uint16_t mem_addr, uint16_t mem_addr_size,
uint8_t *data, uint16_t size, uint32_t timeout) {
HAL_StatusTypeDef status;
status = HAL_I2C_Mem_Read(&hi2c1, dev_addr << 1, mem_addr, mem_addr_size, data, size, timeout);
if (status == HAL_OK) {
return I2C_OK;
} else {
return I2C_ERROR;
}
}
2.3 中断方式通信
/* 中断方式I2C通信 */
volatile uint8_t i2c_tx_complete = 0;
volatile uint8_t i2c_rx_complete = 0;
volatile uint8_t i2c_error = 0;
/* 主机发送中断 */
I2C_Status I2C1_Master_Transmit_IT(uint8_t dev_addr, uint8_t *data, uint16_t size) {
i2c_tx_complete = 0;
i2c_error = 0;
HAL_StatusTypeDef status = HAL_I2C_Master_Transmit_IT(&hi2c1, dev_addr << 1, data, size);
if (status == HAL_OK) {
return I2C_OK;
} else {
return I2C_ERROR;
}
}
/* 主机接收中断 */
I2C_Status I2C1_Master_Receive_IT(uint8_t dev_addr, uint8_t *data, uint16_t size) {
i2c_rx_complete = 0;
i2c_error = 0;
HAL_StatusTypeDef status = HAL_I2C_Master_Receive_IT(&hi2c1, dev_addr << 1, data, size);
if (status == HAL_OK) {
return I2C_OK;
} else {
return I2C_ERROR;
}
}
/* 传输完成回调 */
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) {
if (hi2c->Instance == I2C1) {
i2c_tx_complete = 1;
}
}
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) {
if (hi2c->Instance == I2C1) {
i2c_rx_complete = 1;
}
}
/* 错误回调 */
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) {
if (hi2c->Instance == I2C1) {
i2c_error = 1;
printf("I2C Error: 0x%02X\n", hi2c->ErrorCode);
}
}
2.4 DMA方式通信
/* DMA方式I2C通信 */
void I2C1_DMA_Init(void) {
// 使能DMA时钟
__HAL_RCC_DMA1_CLK_ENABLE();
// 配置DMA通道
DMA_HandleTypeDef hdma_i2c1_tx = {0};
DMA_HandleTypeDef hdma_i2c1_rx = {0};
// 发送DMA配置
hdma_i2c1_tx.Instance = DMA1_Channel6;
hdma_i2c1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_i2c1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_i2c1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_i2c1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_i2c1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_i2c1_tx.Init.Mode = DMA_NORMAL;
hdma_i2c1_tx.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_i2c1_tx);
__HAL_LINKDMA(&hi2c1, hdmatx, hdma_i2c1_tx);
// 接收DMA配置
hdma_i2c1_rx.Instance = DMA1_Channel7;
hdma_i2c1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_i2c1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_i2c1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_i2c1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_i2c1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_i2c1_rx.Init.Mode = DMA_NORMAL;
hdma_i2c1_rx.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_i2c1_rx);
__HAL_LINKDMA(&hi2c1, hdmarx, hdma_i2c1_rx);
// 使能DMA中断
HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);
printf("I2C1 DMA Initialized\n");
}
/* DMA传输 */
I2C_Status I2C1_Master_Transmit_DMA(uint8_t dev_addr, uint8_t *data, uint16_t size) {
i2c_tx_complete = 0;
i2c_error = 0;
HAL_StatusTypeDef status = HAL_I2C_Master_Transmit_DMA(&hi2c1, dev_addr << 1, data, size);
if (status == HAL_OK) {
return I2C_OK;
} else {
return I2C_ERROR;
}
}
I2C_Status I2C1_Master_Receive_DMA(uint8_t dev_addr, uint8_t *data, uint16_t size) {
i2c_rx_complete = 0;
i2c_error = 0;
HAL_StatusTypeDef status = HAL_I2C_Master_Receive_DMA(&hi2c1, dev_addr << 1, data, size);
if (status == HAL_OK) {
return I2C_OK;
} else {
return I2C_ERROR;
}
}
三、I2C从机模式配置
3.1 I2C从机初始化
/* I2C从机初始化 */
void I2C1_Init_Slave(uint8_t slave_address) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIO时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
// 使能I2C1时钟
__HAL_RCC_I2C1_CLK_ENABLE();
// 配置I2C引脚
GPIO_InitStruct.Pin = I2C1_SCL_PIN | I2C1_SDA_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 开漏输出
GPIO_InitStruct.Pull = (I2C_PULLUP_ENABLE) ? GPIO_PULLUP : GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(I2C1_SCL_PORT, &GPIO_InitStruct);
// 初始化I2C1为从机模式
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = slave_address << 1; // 从机地址
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
Error_Handler();
}
// 使能从机中断
HAL_NVIC_SetPriority(I2C1_EV_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
HAL_NVIC_SetPriority(I2C1_ER_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);
printf("I2C1 Slave Initialized (Address: 0x%02X)\n", slave_address);
}
/* 从机接收中断 */
volatile uint8_t slave_rx_buffer[256];
volatile uint8_t slave_rx_index = 0;
volatile uint8_t slave_rx_size = 0;
volatile uint8_t slave_rx_complete = 0;
void I2C1_Slave_Receive_IT(void) {
slave_rx_complete = 0;
slave_rx_index = 0;
HAL_I2C_Slave_Receive_IT(&hi2c1, (uint8_t *)slave_rx_buffer, sizeof(slave_rx_buffer));
}
/* 从机发送中断 */
volatile uint8_t slave_tx_buffer[256];
volatile uint8_t slave_tx_index = 0;
volatile uint8_t slave_tx_size = 0;
volatile uint8_t slave_tx_complete = 0;
void I2C1_Slave_Transmit_IT(uint8_t *data, uint16_t size) {
if (size > sizeof(slave_tx_buffer)) {
size = sizeof(slave_tx_buffer);
}
memcpy((uint8_t *)slave_tx_buffer, data, size);
slave_tx_size = size;
slave_tx_complete = 0;
HAL_I2C_Slave_Transmit_IT(&hi2c1, (uint8_t *)slave_tx_buffer, size);
}
/* 从机回调函数 */
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c) {
if (hi2c->Instance == I2C1) {
slave_tx_complete = 1;
}
}
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) {
if (hi2c->Instance == I2C1) {
slave_rx_complete = 1;
slave_rx_size = sizeof(slave_rx_buffer) - hi2c->XferCount;
}
}
四、I2C设备驱动示例
4.1 AT24CXX EEPROM驱动
/* AT24CXX EEPROM驱动 */
#include "at24cxx.h"
#define AT24C01_ADDR 0xA0
#define AT24C02_ADDR 0xA0
#define AT24C04_ADDR 0xA0
#define AT24C08_ADDR 0xA0
#define AT24C16_ADDR 0xA0
#define AT24C32_ADDR 0xA0
#define AT24C64_ADDR 0xA0
#define AT24C128_ADDR 0xA0
#define AT24C256_ADDR 0xA0
#define AT24C512_ADDR 0xA0
// 检测设备是否存在
uint8_t AT24CXX_Detect(uint8_t dev_addr) {
uint8_t test_byte = 0;
HAL_StatusTypeDef status;
status = HAL_I2C_IsDeviceReady(&hi2c1, dev_addr, 3, 10);
if (status == HAL_OK) {
printf("AT24CXX Found at 0x%02X\n", dev_addr);
return 1;
} else {
printf("AT24CXX Not Found at 0x%02X\n", dev_addr);
return 0;
}
}
// 读取一个字节
uint8_t AT24CXX_ReadByte(uint8_t dev_addr, uint16_t mem_addr) {
uint8_t data = 0;
if (I2C1_Mem_Read(dev_addr, mem_addr, I2C_MEMADD_SIZE_8BIT, &data, 1, 100) != I2C_OK) {
printf("AT24CXX Read Error\n");
return 0xFF;
}
return data;
}
// 写入一个字节
uint8_t AT24CXX_WriteByte(uint8_t dev_addr, uint16_t mem_addr, uint8_t data) {
if (I2C1_Mem_Write(dev_addr, mem_addr, I2C_MEMADD_SIZE_8BIT, &data, 1, 100) != I2C_OK) {
printf("AT24CXX Write Error\n");
return 0;
}
// 等待写入完成
HAL_Delay(5);
return 1;
}
// 读取多个字节
uint8_t AT24CXX_ReadBuffer(uint8_t dev_addr, uint16_t mem_addr, uint8_t *buffer, uint16_t size) {
if (I2C1_Mem_Read(dev_addr, mem_addr, I2C_MEMADD_SIZE_8BIT, buffer, size, 100) != I2C_OK) {
printf("AT24CXX Read Buffer Error\n");
return 0;
}
return 1;
}
// 写入多个字节(页写入)
uint8_t AT24CXX_WriteBuffer(uint8_t dev_addr, uint16_t mem_addr, uint8_t *buffer, uint16_t size) {
uint16_t page_size = 8; // AT24C01页大小为8字节
uint16_t bytes_written = 0;
while (bytes_written < size) {
// 计算当前页剩余字节
uint16_t page_remaining = page_size - (mem_addr % page_size);
uint16_t write_size = (size - bytes_written < page_remaining) ?
(size - bytes_written) : page_remaining;
// 写入一页
if (I2C1_Mem_Write(dev_addr, mem_addr, I2C_MEMADD_SIZE_8BIT,
buffer + bytes_written, write_size, 100) != I2C_OK) {
printf("AT24CXX Write Page Error\n");
return 0;
}
// 等待写入完成
HAL_Delay(5);
bytes_written += write_size;
mem_addr += write_size;
}
return 1;
}
// 测试EEPROM
void AT24CXX_Test(uint8_t dev_addr) {
uint8_t test_data[16];
uint8_t read_data[16];
printf("Testing AT24CXX EEPROM...\n");
// 生成测试数据
for (uint8_t i = 0; i < 16; i++) {
test_data[i] = i + 0x30; // '0' 到 '?'
}
// 写入测试数据
printf("Writing test data: ");
for (uint8_t i = 0; i < 16; i++) {
printf("%c ", test_data[i]);
}
printf("\n");
if (!AT24CXX_WriteBuffer(dev_addr, 0x00, test_data, 16)) {
printf("Write failed\n");
return;
}
// 读取测试数据
if (!AT24CXX_ReadBuffer(dev_addr, 0x00, read_data, 16)) {
printf("Read failed\n");
return;
}
printf("Read test data: ");
for (uint8_t i = 0; i < 16; i++) {
printf("%c ", read_data[i]);
}
printf("\n");
// 验证数据
uint8_t success = 1;
for (uint8_t i = 0; i < 16; i++) {
if (test_data[i] != read_data[i]) {
success = 0;
break;
}
}
if (success) {
printf("EEPROM Test PASSED!\n");
} else {
printf("EEPROM Test FAILED!\n");
}
}
4.2 MPU6050陀螺仪驱动
/* MPU6050 6轴运动传感器驱动 */
#include "mpu6050.h"
// MPU6050寄存器定义
#define MPU6050_ADDR 0x68
#define MPU6050_WHO_AM_I 0x75
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_GYRO_XOUT_H 0x43
// 初始化MPU6050
uint8_t MPU6050_Init(void) {
uint8_t check = 0;
// 检测设备
I2C1_Mem_Read(MPU6050_ADDR, MPU6050_WHO_AM_I, 1, &check, 1, 100);
if (check != 0x68) {
printf("MPU6050 Not Found (ID: 0x%02X)\n", check);
return 0;
}
printf("MPU6050 Found (ID: 0x%02X)\n", check);
// 唤醒MPU6050
uint8_t data = 0x00;
I2C1_Mem_Write(MPU6050_ADDR, MPU6050_PWR_MGMT_1, 1, &data, 1, 100);
// 设置采样率
data = 0x07; // 1kHz采样率
I2C1_Mem_Write(MPU6050_ADDR, MPU6050_SMPLRT_DIV, 1, &data, 1, 100);
// 配置DLPF
data = 0x00; // 带宽260Hz
I2C1_Mem_Write(MPU6050_ADDR, MPU6050_CONFIG, 1, &data, 1, 100);
// 配置陀螺仪
data = 0x18; // ±2000度/秒
I2C1_Mem_Write(MPU6050_ADDR, MPU6050_GYRO_CONFIG, 1, &data, 1, 100);
// 配置加速度计
data = 0x00; // ±2g
I2C1_Mem_Write(MPU6050_ADDR, MPU6050_ACCEL_CONFIG, 1, &data, 1, 100);
printf("MPU6050 Initialized\n");
return 1;
}
// 读取原始数据
void MPU6050_ReadRawData(int16_t *accel, int16_t *gyro, int16_t *temp) {
uint8_t buffer[14];
// 读取14个字节数据
I2C1_Mem_Read(MPU6050_ADDR, MPU6050_ACCEL_XOUT_H, 1, buffer, 14, 100);
// 解析数据
accel[0] = (int16_t)((buffer[0] << 8) | buffer[1]); // X轴加速度
accel[1] = (int16_t)((buffer[2] << 8) | buffer[3]); // Y轴加速度
accel[2] = (int16_t)((buffer[4] << 8) | buffer[5]); // Z轴加速度
*temp = (int16_t)((buffer[6] << 8) | buffer[7]); // 温度
gyro[0] = (int16_t)((buffer[8] << 8) | buffer[9]); // X轴角速度
gyro[1] = (int16_t)((buffer[10] << 8) | buffer[11]); // Y轴角速度
gyro[2] = (int16_t)((buffer[12] << 8) | buffer[12]); // Z轴角速度
}
// 转换为实际值
void MPU6050_ConvertToReal(int16_t *accel, int16_t *gyro, int16_t temp,
float *accel_g, float *gyro_dps, float *temp_c) {
// 加速度转换 (±2g范围)
// 灵敏度: 16384 LSB/g
accel_g[0] = accel[0] / 16384.0;
accel_g[1] = accel[1] / 16384.0;
accel_g[2] = accel[2] / 16384.0;
// 角速度转换 (±2000度/秒)
// 灵敏度: 16.4 LSB/(度/秒)
gyro_dps[0] = gyro[0] / 16.4;
gyro_dps[1] = gyro[1] / 16.4;
gyro_dps[2] = gyro[2] / 16.4;
// 温度转换
// 公式: T(C) = TEMP_OUT / 340 + 36.53
*temp_c = temp / 340.0 + 36.53;
}
// 姿态计算(简化版)
void MPU6050_CalculateAttitude(float *accel_g, float *pitch, float *roll) {
// 计算俯仰角
*pitch = atan2(accel_g[0], sqrt(accel_g[1]*accel_g[1] + accel_g[2]*accel_g[2])) * 180.0 / 3.14159;
// 计算横滚角
*roll = atan2(accel_g[1], sqrt(accel_g[0]*accel_g[0] + accel_g[2]*accel_g[2])) * 180.0 / 3.14159;
}
4.3 OLED显示屏驱动
/* SSD1306 OLED显示屏驱动 */
#include "ssd1306.h"
#define OLED_ADDRESS 0x78
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_PAGE_COUNT 8
// OLED显存
uint8_t oled_buffer[OLED_WIDTH * OLED_PAGE_COUNT];
// 发送命令
void OLED_WriteCommand(uint8_t cmd) {
uint8_t buffer[2] = {0x00, cmd};
I2C1_Master_Transmit(OLED_ADDRESS, buffer, 2, 100);
}
// 发送数据
void OLED_WriteData(uint8_t *data, uint16_t size) {
uint8_t buffer[129];
buffer[0] = 0x40; // 数据控制字节
if (size > 128) {
size = 128;
}
memcpy(&buffer[1], data, size);
I2C1_Master_Transmit(OLED_ADDRESS, buffer, size + 1, 100);
}
// 初始化OLED
void OLED_Init(void) {
// 初始化序列
OLED_WriteCommand(0xAE); // 关闭显示
OLED_WriteCommand(0xD5); // 设置显示时钟分频
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); // 设置多路复用
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); // 设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); // 设置起始行
OLED_WriteCommand(0x8D); // 电荷泵设置
OLED_WriteCommand(0x14);
OLED_WriteCommand(0x20); // 内存寻址模式
OLED_WriteCommand(0x00);
OLED_WriteCommand(0xA1); // 设置段重映射
OLED_WriteCommand(0xC8); // 设置COM扫描方向
OLED_WriteCommand(0xDA); // 设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); // 设置对比度
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); // 设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); // 设置VCOMH电平
OLED_WriteCommand(0x40);
OLED_WriteCommand(0xA4); // 关闭整个显示开启
OLED_WriteCommand(0xA6); // 设置正常显示
OLED_WriteCommand(0xAF); // 开启显示
// 清屏
OLED_Clear();
}
// 清屏
void OLED_Clear(void) {
memset(oled_buffer, 0, sizeof(oled_buffer));
OLED_Update();
}
// 更新显示
void OLED_Update(void) {
for (uint8_t page = 0; page < OLED_PAGE_COUNT; page++) {
OLED_WriteCommand(0xB0 + page); // 设置页地址
OLED_WriteCommand(0x00); // 设置列地址低4位
OLED_WriteCommand(0x10); // 设置列地址高4位
// 发送该页的128字节数据
OLED_WriteData(&oled_buffer[page * OLED_WIDTH], OLED_WIDTH);
}
}
// 显示字符
void OLED_PutChar(uint8_t x, uint8_t y, char ch) {
if (x >= OLED_WIDTH || y >= OLED_HEIGHT) {
return;
}
// 简单的6x8字体
const uint8_t font_6x8[95][6] = {
{0x00,0x00,0x00,0x00,0x00,0x00}, // 空格
{0x00,0x00,0x5F,0x00,0x00,0x00}, // !
// ... 其他字符定义
};
uint8_t char_index = ch - 32;
if (char_index >= 95) {
char_index = 0;
}
for (uint8_t i = 0; i < 6; i++) {
if (x + i < OLED_WIDTH) {
oled_buffer[y / 8 * OLED_WIDTH + x + i] = font_6x8[char_index][i];
}
}
}
// 显示字符串
void OLED_PutString(uint8_t x, uint8_t y, const char *str) {
while (*str) {
OLED_PutChar(x, y, *str);
x += 6;
if (x >= OLED_WIDTH - 6) {
x = 0;
y += 8;
}
str++;
}
}
参考代码 STM32 I2C程序 www.youwenfan.com/contentcnt/133713.html
五、I2C总线诊断工具
5.1 I2C总线扫描
/* I2C总线扫描 */
void I2C_ScanBus(void) {
uint8_t devices[128] = {0};
uint8_t device_count = 0;
HAL_StatusTypeDef status;
printf("Scanning I2C Bus...\n");
printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
for (uint8_t i = 0; i < 128; i++) {
uint8_t addr = i << 1;
if (i % 16 == 0) {
printf("%02X: ", i);
}
status = HAL_I2C_IsDeviceReady(&hi2c1, addr, 3, 10);
if (status == HAL_OK) {
devices[device_count++] = i;
printf("%02X ", i);
} else {
printf("-- ");
}
if (i % 16 == 15) {
printf("\n");
}
}
printf("\nFound %d I2C device(s):\n", device_count);
for (uint8_t i = 0; i < device_count; i++) {
printf(" 0x%02X (Write: 0x%02X, Read: 0x%02X)\n",
devices[i], devices[i] << 1, (devices[i] << 1) | 0x01);
}
}
/* 读取I2C寄存器 */
void I2C_DumpRegisters(uint8_t dev_addr, uint8_t start_reg, uint8_t count) {
uint8_t buffer[16];
printf("Registers of device 0x%02X:\n", dev_addr);
for (uint8_t i = 0; i < count; i += 16) {
printf("%02X: ", start_reg + i);
uint8_t read_count = (count - i > 16) ? 16 : count - i;
if (I2C1_Mem_Read(dev_addr, start_reg + i, 1, buffer, read_count, 100) == I2C_OK) {
for (uint8_t j = 0; j < read_count; j++) {
printf("%02X ", buffer[j]);
}
} else {
printf("Read Error");
}
printf("\n");
}
}
5.2 I2C总线监控
/* I2C总线监控器 */
#define I2C_MONITOR_BUFFER_SIZE 256
typedef struct {
uint8_t data[I2C_MONITOR_BUFFER_SIZE];
uint8_t address[I2C_MONITOR_BUFFER_SIZE];
uint8_t direction[I2C_MONITOR_BUFFER_SIZE]; // 0=写, 1=读
uint32_t timestamp[I2C_MONITOR_BUFFER_SIZE];
uint16_t index;
uint8_t enabled;
} I2C_Monitor;
I2C_Monitor i2c_monitor = {0};
// 开始监控
void I2C_Monitor_Start(void) {
i2c_monitor.index = 0;
i2c_monitor.enabled = 1;
printf("I2C Monitor Started\n");
}
// 停止监控
void I2C_Monitor_Stop(void) {
i2c_monitor.enabled = 0;
printf("I2C Monitor Stopped\n");
}
// 记录传输
void I2C_Monitor_Record(uint8_t addr, uint8_t *data, uint16_t size, uint8_t dir) {
if (!i2c_monitor.enabled) return;
for (uint16_t i = 0; i < size; i++) {
if (i2c_monitor.index >= I2C_MONITOR_BUFFER_SIZE) {
i2c_monitor.index = 0; // 循环缓冲
}
i2c_monitor.data[i2c_monitor.index] = data[i];
i2c_monitor.address[i2c_monitor.index] = addr >> 1; // 转换为7位地址
i2c_monitor.direction[i2c_monitor.index] = dir;
i2c_monitor.timestamp[i2c_monitor.index] = HAL_GetTick();
i2c_monitor.index++;
}
}
// 打印监控记录
void I2C_Monitor_Print(void) {
printf("I2C Monitor Log:\n");
printf("Index Time(ms) Addr Dir Data\n");
printf("--------------------------------\n");
for (uint16_t i = 0; i < I2C_MONITOR_BUFFER_SIZE; i++) {
if (i2c_monitor.address[i] != 0 || i2c_monitor.data[i] != 0) {
printf("%5d %8lu 0x%02X %c 0x%02X\n",
i,
i2c_monitor.timestamp[i],
i2c_monitor.address[i],
i2c_monitor.direction[i] ? 'R' : 'W',
i2c_monitor.data[i]);
}
}
}
六、错误处理与恢复
6.1 I2C总线错误处理
/* I2C错误检测与恢复 */
void I2C_CheckStatus(void) {
uint32_t i2c_sr1 = hi2c1.Instance->SR1;
uint32_t i2c_sr2 = hi2c1.Instance->SR2;
printf("I2C Status:\n");
printf(" SR1: 0x%08lX\n", i2c_sr1);
printf(" SR2: 0x%08lX\n", i2c_sr2);
// 检查错误标志
if (i2c_sr1 & I2C_SR1_AF) {
printf(" Error: Acknowledge Failure\n");
}
if (i2c_sr1 & I2C_SR1_ARLO) {
printf(" Error: Arbitration Lost\n");
}
if (i2c_sr1 & I2C_SR1_BERR) {
printf(" Error: Bus Error\n");
}
if (i2c_sr1 & I2C_SR1_OVR) {
printf(" Error: Overrun/Underrun\n");
}
if (i2c_sr1 & I2C_SR1_TIMEOUT) {
printf(" Error: Timeout\n");
}
// 检查状态标志
if (i2c_sr1 & I2C_SR1_SB) {
printf(" Status: Start Bit\n");
}
if (i2c_sr1 & I2C_SR1_ADDR) {
printf(" Status: Address Sent\n");
}
if (i2c_sr1 & I2C_SR1_BTF) {
printf(" Status: Byte Transfer Finished\n");
}
if (i2c_sr1 & I2C_SR1_RXNE) {
printf(" Status: Receive Buffer Not Empty\n");
}
if (i2c_sr1 & I2C_SR1_TXE) {
printf(" Status: Transmit Buffer Empty\n");
}
}
/* I2C总线复位 */
void I2C_ResetBus(void) {
printf("Resetting I2C Bus...\n");
// 停止I2C
HAL_I2C_DeInit(&hi2c1);
// 生成停止条件
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 配置SDA为输出
GPIO_InitStruct.Pin = I2C1_SDA_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(I2C1_SDA_PORT, &GPIO_InitStruct);
// 生成9个时钟脉冲
for (uint8_t i = 0; i < 9; i++) {
// 拉低SCL
HAL_GPIO_WritePin(I2C1_SCL_PORT, I2C1_SCL_PIN, GPIO_PIN_RESET);
HAL_Delay(1);
// 检查SDA状态
if (HAL_GPIO_ReadPin(I2C1_SDA_PORT, I2C1_SDA_PIN) == GPIO_PIN_SET) {
// SDA为高电平,生成停止条件
HAL_GPIO_WritePin(I2C1_SCL_PORT, I2C1_SCL_PIN, GPIO_PIN_SET);
HAL_Delay(1);
HAL_GPIO_WritePin(I2C1_SDA_PORT, I2C1_SDA_PIN, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(I2C1_SCL_PORT, I2C1_SCL_PIN, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(I2C1_SDA_PORT, I2C1_SDA_PIN, GPIO_PIN_SET);
HAL_Delay(1);
break;
}
// 拉高SCL
HAL_GPIO_WritePin(I2C1_SCL_PORT, I2C1_SCL_PIN, GPIO_PIN_SET);
HAL_Delay(1);
}
// 重新初始化GPIO
GPIO_InitStruct.Pin = I2C1_SDA_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = (I2C_PULLUP_ENABLE) ? GPIO_PULLUP : GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(I2C1_SDA_PORT, &GPIO_InitStruct);
// 重新初始化I2C
HAL_I2C_Init(&hi2c1);
printf("I2C Bus Reset Complete\n");
}
七、多主机通信
7.1 多主机总线仲裁
/* 多主机总线管理 */
#define MAX_RETRY_COUNT 3
#define BUS_IDLE_TIMEOUT 100 // 100ms
// 尝试获取总线控制权
uint8_t I2C_AcquireBus(void) {
uint32_t start_time = HAL_GetTick();
while (1) {
// 检查总线是否空闲
if (HAL_GPIO_ReadPin(I2C1_SDA_PORT, I2C1_SDA_PIN) == GPIO_PIN_SET &&
HAL_GPIO_ReadPin(I2C1_SCL_PORT, I2C1_SCL_PIN) == GPIO_PIN_SET) {
// 总线空闲,尝试发送起始条件
return 1;
}
// 检查超时
if (HAL_GetTick() - start_time > BUS_IDLE_TIMEOUT) {
printf("Bus busy timeout\n");
return 0;
}
HAL_Delay(1);
}
}
// 多主机发送(带重试)
I2C_Status I2C_MultiMaster_Transmit(uint8_t dev_addr, uint8_t *data, uint16_t size, uint32_t timeout) {
uint8_t retry_count = 0;
while (retry_count < MAX_RETRY_COUNT) {
// 获取总线
if (!I2C_AcquireBus()) {
retry_count++;
continue;
}
// 发送数据
I2C_Status status = I2C1_Master_Transmit(dev_addr, data, size, timeout);
if (status == I2C_OK) {
return I2C_OK;
} else if (status == I2C_ARBITRATION_LOST) {
// 仲裁丢失,等待随机时间后重试
retry_count++;
uint32_t wait_time = HAL_GetTick() % 10 + 1; // 1-10ms随机等待
HAL_Delay(wait_time);
continue;
} else {
return status;
}
}
return I2C_ERROR;
}
八、完整示例应用
8.1 主程序示例
/* 主程序 - 完整的I2C应用示例 */
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
int main(void) {
// HAL库初始化
HAL_Init();
// 系统时钟配置
SystemClock_Config();
// 外设初始化
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_I2C1_Init();
printf("\r\n===== STM32 I2C Demo =====\r\n");
// 初始化I2C为主机模式
I2C1_Init_Master();
// 扫描I2C总线
I2C_ScanBus();
// 测试EEPROM
AT24CXX_Test(0xA0);
// 初始化MPU6050
if (MPU6050_Init()) {
printf("MPU6050 Ready\r\n");
}
// 初始化OLED
OLED_Init();
OLED_PutString(0, 0, "I2C Demo");
OLED_PutString(0, 16, "STM32F1");
OLED_Update();
// 主循环
while (1) {
// 读取MPU6050数据
int16_t accel_raw[3], gyro_raw[3], temp_raw;
float accel_g[3], gyro_dps[3], temp_c;
float pitch, roll;
MPU6050_ReadRawData(accel_raw, gyro_raw, &temp_raw);
MPU6050_ConvertToReal(accel_raw, gyro_raw, temp_raw, accel_g, gyro_dps, &temp_c);
MPU6050_CalculateAttitude(accel_g, &pitch, &roll);
// 在OLED上显示
char buffer[20];
snprintf(buffer, sizeof(buffer), "P:%.1f R:%.1f", pitch, roll);
OLED_PutString(0, 32, buffer);
snprintf(buffer, sizeof(buffer), "T:%.1fC", temp_c);
OLED_PutString(0, 48, buffer);
OLED_Update();
// 串口输出
printf("Accel: X=%.2fg, Y=%.2fg, Z=%.2fg\r\n",
accel_g[0], accel_g[1], accel_g[2]);
printf("Gyro: X=%.1f°/s, Y=%.1f°/s, Z=%.1f°/s\r\n",
gyro_dps[0], gyro_dps[1], gyro_dps[2]);
printf("Temp: %.1f°C\r\n", temp_c);
printf("Pitch: %.1f°, Roll: %.1f°\r\n", pitch, roll);
HAL_Delay(1000);
}
}
/* 串口接收处理 */
void process_serial_command(char *cmd) {
if (strcmp(cmd, "SCAN") == 0) {
I2C_ScanBus();
} else if (strcmp(cmd, "STATUS") == 0) {
I2C_CheckStatus();
} else if (strcmp(cmd, "RESET") == 0) {
I2C_ResetBus();
} else if (strcmp(cmd, "MONITOR START") == 0) {
I2C_Monitor_Start();
} else if (strcmp(cmd, "MONITOR STOP") == 0) {
I2C_Monitor_Stop();
} else if (strcmp(cmd, "MONITOR PRINT") == 0) {
I2C_Monitor_Print();
} else if (strcmp(cmd, "EEPROM TEST") == 0) {
AT24CXX_Test(0xA0);
} else if (strcmp(cmd, "HELP") == 0) {
printf("Available commands:\r\n");
printf(" SCAN - Scan I2C bus\r\n");
printf(" STATUS - Check I2C status\r\n");
printf(" RESET - Reset I2C bus\r\n");
printf(" MONITOR START - Start I2C monitor\r\n");
printf(" MONITOR STOP - Stop I2C monitor\r\n");
printf(" MONITOR PRINT - Print monitor log\r\n");
printf(" EEPROM TEST - Test EEPROM\r\n");
printf(" HELP - Show this help\r\n");
} else {
printf("Unknown command\r\n");
}
}
九、优化与最佳实践
9.1 I2C时序优化
/* I2C时序优化 */
void I2C_OptimizeTiming(void) {
// 配置I2C时序寄存器以获得最佳性能
// 计算时序参数
uint32_t i2c_clock = 8000000; // 假设APB1时钟8MHz
uint32_t freq = 100000; // 目标频率100kHz
// 计算预分频
uint32_t presc = (i2c_clock / (freq * 2)) - 1;
if (presc > 0x0F) presc = 0x0F;
// 配置时序寄存器
I2C1->TIMINGR = (presc << 28) | // PRESC
(0x4 << 20) | // SCLDEL
(0x2 << 16) | // SDADEL
(0x0F << 8) | // SCLH
(0x0F << 0); // SCLL
printf("I2C Timing optimized\n");
}
/* 自适应速率调整 */
void I2C_AdaptiveSpeed(uint8_t success_count) {
static uint8_t consecutive_success = 0;
static uint8_t consecutive_failure = 0;
static uint32_t current_speed = 100000;
if (success_count > 0) {
consecutive_success += success_count;
consecutive_failure = 0;
// 连续成功,尝试提高速率
if (consecutive_success >= 10) {
if (current_speed < 1000000) { // 不超过1MHz
current_speed *= 2;
I2C1_SetSpeed(current_speed);
printf("I2C speed increased to %ld Hz\n", current_speed);
consecutive_success = 0;
}
}
} else {
consecutive_failure++;
consecutive_success = 0;
// 连续失败,降低速率
if (consecutive_failure >= 3) {
if (current_speed > 10000) { // 不低于10kHz
current_speed /= 2;
I2C1_SetSpeed(current_speed);
printf("I2C speed decreased to %ld Hz\n", current_speed);
consecutive_failure = 0;
}
}
}
}
十、错误处理与诊断
10.1 完整的错误处理
/* 完整的I2C错误处理 */
typedef struct {
uint32_t total_transactions;
uint32_t successful_transactions;
uint32_t failed_transactions;
uint32_t nack_errors;
uint32_t arbitration_errors;
uint32_t bus_errors;
uint32_t timeout_errors;
} I2C_Statistics;
I2C_Statistics i2c_stats = {0};
// 更新统计信息
void I2C_UpdateStats(I2C_Status status) {
i2c_stats.total_transactions++;
switch (status) {
case I2C_OK:
i2c_stats.successful_transactions++;
break;
case I2C_NACK:
i2c_stats.nack_errors++;
i2c_stats.failed_transactions++;
break;
case I2C_ARBITRATION_LOST:
i2c_stats.arbitration_errors++;
i2c_stats.failed_transactions++;
break;
case I2C_BUS_ERROR:
i2c_stats.bus_errors++;
i2c_stats.failed_transactions++;
break;
case I2C_TIMEOUT:
i2c_stats.timeout_errors++;
i2c_stats.failed_transactions++;
break;
default:
i2c_stats.failed_transactions++;
break;
}
}
// 打印统计信息
void I2C_PrintStats(void) {
printf("I2C Statistics:\n");
printf(" Total Transactions: %lu\n", i2c_stats.total_transactions);
printf(" Successful: %lu (%.1f%%)\n",
i2c_stats.successful_transactions,
(float)i2c_stats.successful_transactions / i2c_stats.total_transactions * 100);
printf(" Failed: %lu (%.1f%%)\n",
i2c_stats.failed_transactions,
(float)i2c_stats.failed_transactions / i2c_stats.total_transactions * 100);
printf(" NACK Errors: %lu\n", i2c_stats.nack_errors);
printf(" Arbitration Errors: %lu\n", i2c_stats.arbitration_errors);
printf(" Bus Errors: %lu\n", i2c_stats.bus_errors);
printf(" Timeout Errors: %lu\n", i2c_stats.timeout_errors);
}
总结
这个完整的STM32 I2C程序实现了以下功能:
核心功能:
- I2C主机/从机模式:完整的初始化配置
- 多种传输方式:轮询、中断、DMA传输
- 错误处理:完善的错误检测与恢复机制
- 总线管理:多主机仲裁、总线复位
- 诊断工具:总线扫描、寄存器读取、总线监控
设备驱动:
- AT24CXX EEPROM:完整的读写功能
- MPU6050传感器:姿态解算与数据处理
- OLED显示屏:图形显示与字符显示
高级特性:
- 性能优化:时序优化、自适应速率
- 统计分析:传输统计、错误统计
- 调试工具:串口命令接口、监控工具
这个程序可以直接用于STM32的I2C项目开发,特别是需要连接多个I2C设备的应用场景。
浙公网安备 33010602011771号