I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
I2C总线是一个真正的多主机总线,如果两个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据破坏,每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。
I2C有3种总线速率——标速(100kHz)、快速(400kHz)和高速(3.4MHz),但GD32F103C8T6只支持标速和快速两种速率。
I2C的通信由一个起始信号开始,传输结束后由一个停止信号结束。
开始信号为SDA线由高电平拉低,接着SCL线开始输出同步时钟信号;传输结束后SCL线停止同步时钟输出,接着SDA线也由低拉高,发出结束信号。
I2C是一个同步传输协议,因此数据的更新需根据同步时钟线来决定。
I2C规定,时钟信号的高电平期间SDA线上的数据必须稳定。只有在时钟信号SCL变低的时候数据线SDA的电平才能跳变。
上面说过,I2C是一个多主机总线协议,因此当多个主机同时申请总线的控制权时,则需要对其进行仲裁,以决定哪一个主机获得总线的控制权。
I2C的仲裁比较特别,跟之前介绍的DMA的仲裁不同,DMA的仲裁是通过检查优先级的方式来进行的,而I2C的仲裁是通过比较实际输出电平的方式进行的。
在每一位数据的发送期间,当SCL为高时,每个主机都检查SDA电平是否和自己发送的相同。理论上讲,如果两个主机所传输的内容完全相同,那么他们能够成功传输而不出现错误。如果一个主机发送高电平但检测到SDA电平为低,则认为自己仲裁失败并关闭自己的SDA输出驱动,而另一个主机则继续完成自己的传输。
I2C总线上可以连接多个从设备,每个从设备都共享一条SDA和SCL线。
所以当主机想要给总线上的某一个从设备通信就需要有一个机制来寻找对应的从设备,这个机制就是从设备地址。
每个从设备都有一个自己的地址可以是7位的,也可以是10位的,地址的值的范围具体由厂商来决定。
主机在向总线发送开始信号并成功获得总线控制权后,将会向总线发送对应从机的地址,这个地址所有的从机都会接收,然后从机将收到的地址与自己的地址比较,若地址一致则该从机将会向总线发送ACK信号,也就是确认应答,表示可以开始通信;而其他的从机将会忽略总线上的信号,直到下一个开始信号来临。
主机与从机每传输一组数据,从机接收后都必须发送ACK信号,表示接收成功。
I2C具有4种运行状态——主机发送方、主机接收方、从机发送方、从机接收方。对于这4种不同的运行状态都有不同的软件控制流程。
这一部分所有的流程图都是基于10位地址I2C通信的,因此如果各位使用的是7位地址的I2C通信,那么把地址头发送的那一部分忽略即可。
简要流程:
说明:
SBSEND全称Start Bit Send,“起始位发送”标志位。
ADDSEND全称Address Send,“地址发送”标志位,同样ADD10SEND就是“10位地址发送”标志位
TBE全称Transfer Buffer Empty,“发送缓冲区空”标志位。
BTC全称Byte Transfer Complete,“字节传输完成”标志位。
简要流程:
说明:
RBNE全称Read Buffer Not Empty,“接收缓冲区非空”标志位。
ACKEN全称Acknowledge Enable,“应答帧使能”标志位。
简要流程:
简要流程:
根据I2C协议,I2C主机将不会对接收到的最后一个字节发送应答,所以在最后一个字节发
送结束后,I2C从机的AERR会置1以通知软件发送结束,软件写0到AERR位可以清除此
位。
AERR全称Acknowledge Error,“应答错误”标志位。
简要流程:
GD32F103C8T6上有2个I2C设备,正好用来测试,它们的对应管脚如下表所示。
设备 | SCL | SDA |
---|---|---|
I2C0 | PB6 | PB7 |
I2C1 | PB10 | PB11 |
在对管脚初始化时,I2C的两个管脚都必须设置为复用开漏模式。
因为GD32F10C8T6的I2C外设没有内部上拉电阻,所以在接线的时候要在SDA和SCL分别加外部上拉电阻,如下图。
VCC为电源电压,一般为3.3V或5V;上拉电阻的阻值推荐为4.7kΩ或10kΩ。
现象:主机每秒向从机发送一串数据
i2c.c文件
#include "i2c.h"
void I2C_MasterInit(void)
{
/* 初始化GPIO */
rcu_periph_clock_enable(RCU_GPIOB);
gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6 | GPIO_PIN_7);
/* 初始化I2C */
rcu_periph_clock_enable(RCU_I2C0);
/* 初始化时钟,频率10kHz,占空比50% */
i2c_clock_config(I2C0, 100000, I2C_DTCY_2);
/* 设置地址,7位 */
i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x82);
/* 使能I2C0 */
i2c_enable(I2C0);
/* 使能应答 */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
}
void I2C_SlaveInit(void)
{
/* 初始化GPIO */
rcu_periph_clock_enable(RCU_GPIOB);
gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_10 | GPIO_PIN_11);
/* 初始化I2C */
rcu_periph_clock_enable(RCU_I2C1);
/* 初始化时钟,频率10kHz,占空比50% */
i2c_clock_config(I2C1, 100000, I2C_DTCY_2);
/* 设置地址,7位 */
i2c_mode_addr_config(I2C1, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x72);
/* 使能I2C1 */
i2c_enable(I2C1);
/* 使能应答 */
i2c_ack_config(I2C1, I2C_ACK_ENABLE);
}
void I2C_MasterSendSlaveReceive(void)
{
uint8_t i2c_transmitter[16];
uint8_t i2c_receiver[16];
memset(i2c_transmitter, 0x00, sizeof(i2c_transmitter));
memset(i2c_receiver, 0x00, sizeof(i2c_receiver));
printf("Master transmit: ");
for(uint8_t i = 0; i < 16; i++)
{
i2c_transmitter[i] = i + 0x80;
printf("%x ", i2c_transmitter[i]);
}
printf("\n");
/* 等待总线空闲 */
while(i2c_flag_get(I2C0, I2C_FLAG_I2CBSY));
/* 主机发送起始位 */
i2c_start_on_bus(I2C0);
/* 主机等待SBSEND置1 */
while(!i2c_flag_get(I2C0, I2C_FLAG_SBSEND));
/* 主机发送从机地址 */
i2c_master_addressing(I2C0, 0x72, I2C_TRANSMITTER);
/* 等待ADDSEND置1 */
while(!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND));
while(!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));
/* ADDSEND置0 */
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
i2c_flag_clear(I2C1, I2C_FLAG_ADDSEND);
for(uint8_t i = 0; i < 16; i++)
{
/* 发送一个字节 */
i2c_data_transmit(I2C0, i2c_transmitter[i]);
/* 等待主机发送完成 */
while(!i2c_flag_get(I2C0, I2C_FLAG_TBE));
/* 等待从机接收完成 */
while(!i2c_flag_get(I2C1, I2C_FLAG_RBNE));
/* 从机读取数据 */
i2c_receiver[i] = i2c_data_receive(I2C1);
}
/* 发送停止位 */
i2c_stop_on_bus(I2C0);
/* 等待STPDET位置1 */
while(I2C_CTL0(I2C0)&0x0200);
while(!i2c_flag_get(I2C1, I2C_FLAG_STPDET));
/* STPSET置0 */
i2c_enable(I2C0);
printf("Slave receive: ");
for(uint8_t i = 0; i < 16; i++)
{
printf("%x ", i2c_receiver[i]);
}
printf("\n");
}
main.c文件
#include "gd32f10x.h"
#include "main.h"
#include "systick.h"
#include "usart.h"
#include "i2c.h"
#include
#include
int main(void)
{
systick_config();
USART_Config();
I2C_MasterInit();
I2C_SlaveInit();
while(1)
{
I2C_MasterSendSlaveReceive();
delay_ms(1000);
}
}
现象:主机每秒接收从机发送的一串数据
i2c.c文件
#include "i2c.h"
void I2C_MasterInit(void)
{
/* 初始化GPIO */
rcu_periph_clock_enable(RCU_GPIOB);
gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6 | GPIO_PIN_7);
/* 初始化I2C */
rcu_periph_clock_enable(RCU_I2C0);
/* 初始化时钟,频率10kHz,占空比50% */
i2c_clock_config(I2C0, 100000, I2C_DTCY_2);
/* 设置地址,7位 */
i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x82);
/* 使能I2C0 */
i2c_enable(I2C0);
/* 使能应答 */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
}
void I2C_SlaveInit(void)
{
/* 初始化GPIO */
rcu_periph_clock_enable(RCU_GPIOB);
gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_10 | GPIO_PIN_11);
/* 初始化I2C */
rcu_periph_clock_enable(RCU_I2C1);
/* 初始化时钟,频率10kHz,占空比50% */
i2c_clock_config(I2C1, 100000, I2C_DTCY_2);
/* 设置地址,7位 */
i2c_mode_addr_config(I2C1, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x72);
/* 使能I2C1 */
i2c_enable(I2C1);
/* 使能应答 */
i2c_ack_config(I2C1, I2C_ACK_ENABLE);
}
void I2C_MasterReceiveSlaveSend(void)
{
uint8_t i2c_transmitter[16];
uint8_t i2c_receiver[16];
memset(i2c_transmitter, 0x00, sizeof(i2c_transmitter));
memset(i2c_receiver, 0x00, sizeof(i2c_receiver));
printf("Slave transmit: ");
for(uint8_t i = 0; i < 16; i++)
{
i2c_transmitter[i] = i + 0x80;
printf("%x ", i2c_transmitter[i]);
}
printf("\n");
/* 等待总线空闲 */
while(i2c_flag_get(I2C0, I2C_FLAG_I2CBSY));
/* 主机发送起始信号 */
i2c_start_on_bus(I2C0);
/* 主机等待SBSEND置1 */
while(!i2c_flag_get(I2C0, I2C_FLAG_SBSEND));
/* 主机发送从机地址 */
i2c_master_addressing(I2C0, 0x72, I2C_RECEIVER);
/* 主机等待ADDSEND置1 */
while(!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND));
/* 主机ADDSEND置0 */
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
/* 从机等待ADDSEND置1 */
while(!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));
/* 从机ADDSEND置0 */
i2c_flag_clear(I2C1, I2C_FLAG_ADDSEND);
/* 从机发送n-1个字节 */
for(uint8_t i = 0; i < 15; i++)
{
/* 发送一个字节 */
i2c_data_transmit(I2C1, i2c_transmitter[i]);
/* 从机等待发送完成 */
while(!i2c_flag_get(I2C1, I2C_FLAG_TBE));
/* 主机等待接收完成 */
while(!i2c_flag_get(I2C0, I2C_FLAG_RBNE));
/* 获取一个字节 */
i2c_receiver[i] = i2c_data_receive(I2C0);
}
/* 关闭ACK */
i2c_ack_config(I2C0, I2C_ACK_DISABLE);
/* 发送最后一个字节 */
i2c_data_transmit(I2C1, i2c_transmitter[15]);
/* 从机等待发送完成 */
while(!i2c_flag_get(I2C1, I2C_FLAG_TBE));
/* 主机等待应答错误位置1 */
while(!i2c_flag_get(I2C1, I2C_FLAG_AERR));
/* 主机发送停止信号 */
i2c_stop_on_bus(I2C0);
while(I2C_CTL0(I2C0)&0x0200);
/* 获取最后一个字节 */
i2c_receiver[15] = i2c_data_receive(I2C0);
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
/* 从机AERR置0 */
i2c_flag_clear(I2C1, I2C_FLAG_AERR);
printf("Master receive: ");
for(uint8_t i = 0; i < 16; i++)
{
printf("%x ", i2c_receiver[i]);
}
printf("\n");
}
main.c文件
#include "gd32f10x.h"
#include "main.h"
#include "systick.h"
#include "usart.h"
#include "i2c.h"
#include
#include
int main(void)
{
systick_config();
USART_Config();
I2C_MasterInit();
I2C_SlaveInit();
while(1)
{
I2C_MasterReceiveSlaveSend();
delay_ms(1000);
}
}