第10章 I2C介绍及应用
第十章 I2C介绍及应用
1. I2C简介
如果我们直接控制 STM32 的两个 GPIO 引脚,分别用作 SCL 及 SDA,按照上述信号的时序要求,直接像控制 LED 灯那样控制引脚的输出 (若是接收数据时则读取 SDA 电平),就可以实现 I2C 通讯。同样,假如我们按照 USART 的要求去控制引脚,也能实现 USART 通讯。所以只要遵守协议,就是标准的通讯,不管您如何实现它,不管是 ST 生产的控制器还是 ATMEL 生产的存储器,都能按通讯标准交互。
由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。
相对地,还有“硬件协议”方式, STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来, CPU 只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理 I2C 协议的方式减轻了CPU 的工作,且使软件设计更加简单。
2. I2C初始化结构体
跟其它外设一样, STM32 标准库提供了 I2C 初始化结构体及初始化函数来配置 I2C 外设。初始化结构体及函数定义在库文件“stm32f10x_i2c.h”及“stm32f10x_i2c.c”中
typedef struct {
uint32_t I2C_ClockSpeed; // 设置 SCL 时钟频率,此值要低于 400000
uint16_t I2C_Mode; //指定工作模式,可选 I2C 模式及 SMBUS 模式
uint16_t I2C_DutyCycle; // 指定时钟占空比,可选 low/high = 2:1 及 16:9 模式
uint16_t I2C_OwnAddress1; // 指定自身的 I2C 设备地址
uint16_t I2C_Ack; // 使能或关闭响应 (一般都要使能)
uint16_t I2C_AcknowledgedAddress; // 指定地址的长度,可为 7 位及 10 位
}I2C_InitTypeDef;
1. I2C_ClockSpeed
(uint32_t)
- 说明:该字段用于设置 SCL (时钟线) 的频率。I2C 总线的时钟频率应该小于 400 kHz,适用于标准模式 (100 kHz) 或快速模式 (400 kHz)。这个值决定了数据传输的速率。
- 典型值:例如,设置为 100000 表示 100 kHz 时钟,设置为 400000 表示 400 kHz 时钟。
2. I2C_Mode
(uint16_t)
-
说明:该字段用于指定 I2C 工作模式。可以选择以下几种模式:
- I2C_Mode_I2C:I2C 模式,常用模式。
- I2C_Mode_SMBUS:SMBus 模式,SMBus 是 I2C 的一个扩展,主要用于更低功耗和更简单的设备。
该字段控制了外设的工作模式,具体设置依据应用需求而定。
3. I2C_DutyCycle
(uint16_t)
-
说明:该字段指定 I2C 时钟的占空比,即高电平与低电平的比例。I2C 时钟信号的占空比决定了信号的波形,通常有两种选择:
- I2C_DutyCycle_2:占空比为 2:1,表示高电平的时间是低电平的两倍。
- I2C_DutyCycle_16_9:占空比为 16:9,表示高电平的时间是低电平的 16/9 倍。
在高频模式下(例如 400 kHz),使用 16:9 的占空比能提供更高的稳定性。
4. I2C_OwnAddress1
(uint16_t)
- 说明:该字段设置设备的 I2C 地址。I2C 总线上的每个设备都有一个唯一的地址,用于区分不同设备。
- I2C 地址通常为 7 位或 10 位。此字段设置设备的 7 位地址(或者低 7 位,10 位地址的配置需要额外设置)。
5. I2C_Ack
(uint16_t)
-
说明:该字段用于使能或禁用响应(ACK)。在 I2C 通信中,ACK 是设备与主机之间交换数据时的响应信号。
- I2C_Ack_Enable:使能 ACK 响应,通常都是启用此选项。
- I2C_Ack_Disable:禁用 ACK 响应,通常用于一些特殊的通信场景。
使能 ACK 是 I2C 通信的标准操作。
6. I2C_AcknowledgedAddress
(uint16_t)
-
说明:该字段指定设备的地址长度,支持 7 位或 10 位地址:
- I2C_AcknowledgedAddress_7bit:7 位地址模式。
- I2C_AcknowledgedAddress_10bit:10 位地址模式。
地址的位数决定了设备能够连接到 I2C 总线的数量。在选择时,需要根据实际硬件的地址规格进行配置。
3. I2C使用示例
EEPROM 是一种掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的时候加载之。 EEPOM 芯片最常用的通讯方式就是 I2C 协议,本小节以 EEPROM 的读写实验为大家讲解 STM32 的 I2C 使用方法。实验中 STM32 的 I2C 外设采用主模式,分别用作主发送器和主接收器,通过查询事件的方式来确保正常通讯。
本实验板中的 EEPROM 芯片 (型号: AT24C02) 的 SCL 及 SDA 引脚连接到了 STM32 对应的 I2C引脚中,结合上拉电阻,构成了 I2C 通讯总线,它们通过 I2C 总线交互。 EEPROM 芯片的设备地址一共有 7 位,其中高 4 位固定为: 1010 b,低 3 位则由 A0/A1/A2 信号线的电平决定
按照我们此处的连接, A0/A1/A2 均为 0,所以 EEPROM 的 7 位设备地址是: 101 0000b,即 0x50。由于 I2C 通讯时常常是地址跟读写方向连在一起构成一个 8 位数,且当 R/W 位为 0 时,表示写方向,所以加上 7 位地址,其值为“0xA0”,常称该值为 I2C 设备的“写地址”;当 R/W 位为 1时,表示读方向,加上 7 位地址,其值为“0xA1”,常称该值为“读地址”。
EEPROM 芯片中还有一个 WP 引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时,可写入数据,我们直接接地,不使用写保护功能。
3.1 I2C_EE相关参数宏定义
#ifndef I2C_EEPROM_H
#define I2C_EEPROM_H
#include "stm32f10x.h"
// 硬件配置
#define EEPROM_I2C I2C1
#define EEPROM_I2C_CLK RCC_APB1Periph_I2C1
#define EEPROM_GPIO_PORT GPIOB
#define EEPROM_GPIO_CLK RCC_APB2Periph_GPIOB
#define EEPROM_SCL_PIN GPIO_Pin_6
#define EEPROM_SDA_PIN GPIO_Pin_7
#define EEPROM_ADDRESS 0xA0 // 7位地址 (0x50 << 1)
#define EEPROM_PAGE_SIZE 8 // AT24C02页大小
/*
AT24C02 2kb = 2048bit = 2048/8 B = 256 B
32 pages of 8 bytes each
Device Address
1 0 1 0 A2 A1 A0 R/W
1 0 1 0 0 0 0 0 = 0XA0
1 0 1 0 0 0 0 1 = 0XA1
EEPROM地址宏定义
#define EEPROM_Block0_ADDRESS 0xA0
#define EEPROM_Block1_ADDRESS 0xA2
#define EEPROM_Block2_ADDRESS 0xA4
#define EEPROM_Block3_ADDRESS 0xA6
*/
// 超时设置
#define I2C_TIMEOUT 100000 // 超时计数器值
// 初始化函数
void I2C_EEPROM_Init(void);
// 读写函数
uint8_t I2C_EEPROM_WriteByte(uint16_t addr, uint8_t data);
uint8_t I2C_EEPROM_ReadByte(uint16_t addr, uint8_t* data);
uint8_t I2C_EEPROM_WritePage(uint16_t addr, uint8_t* data, uint8_t len);
uint8_t I2C_EEPROM_ReadBuffer(uint16_t addr, uint8_t* buffer, uint16_t len);
#endif // I2C_EEPROM_H
3.2 I2C_EE初始化
// I2C初始化
void I2C_EEPROM_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;
// 1. 使能时钟
RCC_APB1PeriphClockCmd(EEPROM_I2C_CLK, ENABLE);
RCC_APB2PeriphClockCmd(EEPROM_GPIO_CLK, ENABLE);
// 2. 配置GPIO为开漏输出
GPIO_InitStruct.GPIO_Pin = EEPROM_SCL_PIN | EEPROM_SDA_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(EEPROM_GPIO_PORT, &GPIO_InitStruct);
// 3. 配置I2C参数
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = 0x0A; // 主机地址,可设为任意值
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed = 400000; // 400kHz
I2C_Init(EEPROM_I2C, &I2C_InitStruct);
I2C_Cmd(EEPROM_I2C, ENABLE);
}
3.3 I2C_EE基本工作函数
// 超时处理
static uint8_t I2C_CheckTimeout(uint32_t timeout) {
while (timeout--) {
if (!I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY)) {
return 1; // 操作成功
}
}
return 0; // 超时
}
// 单字节写入
uint8_t I2C_EEPROM_WriteByte(uint16_t addr, uint8_t data) {
// 1. 生成START条件
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
if (!I2C_CheckTimeout(I2C_TIMEOUT)) return 0;
// 2. 发送设备地址+写模式
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
return 0;
// 3. 发送内存地址高字节 (对于16位地址)
I2C_SendData(EEPROM_I2C, (addr >> 8) & 0xFF);
if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
return 0;
// 4. 发送内存地址低字节
I2C_SendData(EEPROM_I2C, addr & 0xFF);
if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
return 0;
// 5. 发送数据
I2C_SendData(EEPROM_I2C, data);
if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
return 0;
// 6. 生成STOP条件
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
return 1;
}
// 单字节读取
uint8_t I2C_EEPROM_ReadByte(uint16_t addr, uint8_t* data) {
// 1. 生成START条件(写模式)
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
if (!I2C_CheckTimeout(I2C_TIMEOUT)) return 0;
// 2. 发送设备地址+写模式
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
return 0;
// 3. 发送内存地址高字节
I2C_SendData(EEPROM_I2C, (addr >> 8) & 0xFF);
if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
return 0;
// 4. 发送内存地址低字节
I2C_SendData(EEPROM_I2C, addr & 0xFF);
if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
return 0;
// 5. 重新生成START条件(读模式)
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
if (!I2C_CheckTimeout(I2C_TIMEOUT)) return 0;
// 6. 发送设备地址+读模式
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Receiver);
if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
return 0;
// 7. 准备接收单个字节(禁用ACK,生成STOP)
I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE);
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
// 8. 等待数据接收完成
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED));
// 9. 读取数据
*data = I2C_ReceiveData(EEPROM_I2C);
// 10. 重新使能ACK
I2C_AcknowledgeConfig(EEPROM_I2C, ENABLE);
return 1;
}
// 页写入
uint8_t I2C_EEPROM_WritePage(uint16_t startAddr, uint8_t* data, uint8_t len) {
// 检查长度是否超过页边界
uint8_t pageOffset = startAddr % EEPROM_PAGE_SIZE;
if (len > (EEPROM_PAGE_SIZE - pageOffset))
return 0;
// 1. 生成START条件
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
// 等待START完成
uint32_t timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {
if (timeout-- == 0) return 0;
}
// 2. 发送设备地址+写模式
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
// 等待地址发送完成
timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
if (timeout-- == 0) return 0;
}
// 3. 发送8位内存地址(AT24C02只需要一个字节地址)
I2C_SendData(EEPROM_I2C, startAddr & 0xFF);
// 等待地址发送完成
timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
if (timeout-- == 0) return 0;
}
// 4. 发送数据
for (uint8_t i = 0; i < len; i++) {
I2C_SendData(EEPROM_I2C, data[i]);
// 等待数据发送完成
timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING)) {
if (timeout-- == 0) return 0;
}
}
// 5. 等待最后一个字节传输完成
timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
if (timeout-- == 0) return 0;
}
// 6. 生成STOP条件
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
return 1;
}
// 多字节读取(适用于8位地址的EEPROM)
uint8_t I2C_EEPROM_ReadBuffer(uint16_t startAddr, uint8_t* buffer, uint16_t len)
{
// 1. 生成START条件(写模式)
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
// 等待START完成
uint32_t timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {
if (timeout-- == 0) return 0;
}
// 2. 发送设备地址+写模式
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
// 等待地址发送完成
timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
if (timeout-- == 0) return 0;
}
// 3. 发送8位内存地址
I2C_SendData(EEPROM_I2C, startAddr & 0xFF);
// 等待地址发送完成 - 使用更通用的状态检查
timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING)) {
if (timeout-- == 0) return 0;
}
// 4. 重新生成START条件(读模式)
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
// 等待START完成
timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {
if (timeout-- == 0) return 0;
}
// 5. 发送设备地址+读模式
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Receiver);
// 等待地址发送完成
timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) {
if (timeout-- == 0) return 0;
}
// 6. 读取多个字节
for (uint16_t i = 0; i < len; i++) {
if (i == len - 1) {
// 在接收最后一个字节前禁用ACK并生成STOP
I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE);
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
}
// 等待数据接收完成
timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED)) {
if (timeout-- == 0) return 0;
}
// 读取数据
buffer[i] = I2C_ReceiveData(EEPROM_I2C);
}
// 7. 重新使能ACK
I2C_AcknowledgeConfig(EEPROM_I2C, ENABLE);
return 1;
}
3.4 主函数测试
#include "stm32f10x.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "i2c_ee.h"
#include <stdio.h>
#define EEPROM_Firstpage 0x00 // EEPROM的起始地址
#define PAGE_SIZE 8 // AT24C02页大小
#define BUFFER_SIZE 256
#define I2C_TIMEOUT 10000 // 超时计数器值
uint8_t I2c_Buf_Write[BUFFER_SIZE]; // 写入缓冲区
uint8_t I2c_Buf_Read[BUFFER_SIZE]; // 读取缓冲区
uint8_t I2C_TEST(void);
int main(void)
{
SysTick_Init();
BSP_LED_Init();
USARTx_Init();
I2C_EEPROM_Init();
// 等待所有初始化完成
Delay_ms(100);
if(I2C_TEST() == 1)
{
LED_ON(GREEN_LED_Pin);
}
else
{
LED_ON(RED_LED_Pin);
}
while(1);
}
// I2C EEPROM读写测试
uint8_t I2C_TEST(void)
{
uint16_t i = 0;
// 初始化写入缓冲区
printf("Preparing write data:\r\n");
for(i = 0; i < BUFFER_SIZE; i++)
{
I2c_Buf_Write[i] = i;
printf("0x%02X ", I2c_Buf_Write[i]);
if(i % 16 == 15)
printf("\r\n");
}
printf("\r\n");
// 分页写入(AT24C02页大小为8字节)
printf("Writing data to EEPROM...\r\n");
uint16_t addr = EEPROM_Firstpage;
uint8_t writeSuccess = 1;
for(i = 0; i < BUFFER_SIZE; i += PAGE_SIZE)
{
uint8_t bytesToWrite = (BUFFER_SIZE - i) > PAGE_SIZE ? PAGE_SIZE : (BUFFER_SIZE - i);
if(!I2C_EEPROM_WritePage(addr, &I2c_Buf_Write[i], bytesToWrite))
{
printf("Write failed at address 0x%02X\r\n", addr);
writeSuccess = 0;
break;
}
addr += bytesToWrite;
Delay_ms(5); // EEPROM需要5ms写入时间
printf(".");
}
if(!writeSuccess)
{
printf("\r\nWrite operation failed!\r\n");
return 0;
}
printf("\r\nWrite completed successfully!\r\n");
// 等待EEPROM完成所有内部写入
Delay_ms(20);
// 读取数据
printf("Reading data from EEPROM...\r\n");
if(!I2C_EEPROM_ReadBuffer(EEPROM_Firstpage, I2c_Buf_Read, BUFFER_SIZE))
{
printf("Read failed!\r\n");
return 0;
}
printf("Read completed successfully!\r\n");
// 验证数据
printf("\r\nVerifying data:\r\n");
uint8_t errors = 0;
for(i = 0; i < BUFFER_SIZE; i++)
{
if(I2c_Buf_Read[i] != I2c_Buf_Write[i])
{
errors++;
printf("Error at 0x%02X: Wrote 0x%02X, Read 0x%02X\r\n",
i, I2c_Buf_Write[i], I2c_Buf_Read[i]);
}
else
{
printf("0x%02X ", I2c_Buf_Read[i]);
}
if(i % 16 == 15)
printf("\r\n");
}
if(errors > 0)
{
printf("\r\n%d errors found!\r\n", errors);
return 0;
}
printf("\r\nAll data verified successfully!\r\n");
return 1;
}
4. I2C常见函数(STD库)
4.1 I2C 配置步骤
4.1.1 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 使能I2C1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIO时钟
4.1.2 配置GPIO引脚
GPIO_InitTypeDef GPIO_InitStruct;
// SCL引脚配置(PB6)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// SDA引脚配置(PB7)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStruct);
4.1.3 I2C参数配置
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // I2C模式
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; // 时钟占空比
I2C_InitStruct.I2C_OwnAddress1 = 0x30; // 本设备地址
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // 使能ACK
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址
I2C_InitStruct.I2C_ClockSpeed = 100000; // 100kHz时钟
I2C_Init(I2C1, &I2C_InitStruct); // 应用配置
I2C_Cmd(I2C1, ENABLE); // 使能I2C
4.2 核心操作函数
4.2.1 主模式发送
// 1. 产生起始条件
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 等待EV5
// 2. 发送从机地址(写)
I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 等待EV6
// 3. 发送数据
I2C_SendData(I2C1, data_byte);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 等待EV8_2
// 4. 产生停止条件
I2C_GenerateSTOP(I2C1, ENABLE);
4.2.2 主模式接收
// 1. 产生起始条件
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // EV5
// 2. 发送从机地址(读)
I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); // EV6
// 3. 接收数据
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); // EV7
uint8_t data = I2C_ReceiveData(I2C1);
// 4. 产生停止条件
I2C_GenerateSTOP(I2C1, ENABLE);
4.2.3 从模式配置
// 设置从机地址
I2C_InitStruct.I2C_OwnAddress1 = 0x30; // 7位地址
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
// 使能地址匹配中断
I2C_ITConfig(I2C1, I2C_IT_ADDR, ENABLE);
4.3 事件检测宏
事件宏 | 描述 |
---|---|
I2C_EVENT_MASTER_MODE_SELECT |
起始条件已发送 (EV5) |
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED |
地址发送完成+ACK (EV6) |
I2C_EVENT_MASTER_BYTE_TRANSMITTED |
数据发送完成+ACK (EV8_2) |
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED |
地址发送完成+ACK (EV6) |
I2C_EVENT_MASTER_BYTE_RECEIVED |
数据接收完成 (EV7) |
4.4 中断配置
4.4.1 使能中断
// 使能事件和错误中断
I2C_ITConfig(I2C1, I2C_IT_EVT | I2C_IT_ERR | I2C_IT_BUF, ENABLE);
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = I2C1_EV_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
4.4.2 中断服务函数
void I2C1_EV_IRQHandler(void) {
switch (I2C_GetLastEvent(I2C1)) {
case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED: // 从机地址匹配
// 准备接收数据
break;
case I2C_EVENT_SLAVE_BYTE_RECEIVED: // 从机接收数据
uint8_t data = I2C_ReceiveData(I2C1);
// 处理数据
break;
case I2C_EVENT_SLAVE_STOP_DETECTED: // 检测到停止位
I2C_ClearFlag(I2C1, I2C_FLAG_STOPF);
break;
}
}
4.5 DMA 配置
4.5.1 使能I2C DMA请求
// 使能I2C DMA发送请求
I2C_DMACmd(I2C1, I2C_DMAReq_Tx, ENABLE);
// 使能I2C DMA接收请求
I2C_DMACmd(I2C1, I2C_DMAReq_Rx, ENABLE);
4.5.2 配置DMA通道
// 以I2C1发送为例(DMA1通道6)
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)tx_buffer;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST; // 内存->外设
// ...其他DMA配置
DMA_Init(DMA1_Channel6, &DMA_InitStruct);
4.6 错误处理
4.6.1 错误标志检测
if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF)) { // 应答失败
I2C_ClearFlag(I2C1, I2C_FLAG_AF);
// 错误处理
}
if (I2C_GetFlagStatus(I2C1, I2C_FLAG_BERR)) { // 总线错误
I2C_ClearFlag(I2C1, I2C_FLAG_BERR);
// 错误处理
}
4.6.2 超时机制
#define I2C_TIMEOUT 100000
uint32_t timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
if (timeout-- == 0) {
// 超时处理
I2C_GenerateSTOP(I2C1, ENABLE);
return ERROR_TIMEOUT;
}
}