说到IIC(一般也叫I2C,其实都是同样的)通信,是一种最简单的通信协议。在学习STM32时第一个接触的就是串口USART通信协议,接下来就是IIC通信协议了还有的就是SPI协议,SPI咱们下一章再说,这一章就说说IIC吧。不少模块都用到过IIC通信,最多见的就是4针的0.96寸OLED显示屏,固然啦在学习STM32是咱们通常最早接触到就是经过IIC来与EEPROM进行通信,可是本章咱们只讲协议自己。编程
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于链接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线通常可达 400kbps 以上。 I2C 总线在传送数据过程当中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。 开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。 结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。 应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际状况做出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。这些信号中,起始信号是必需的,结束信号和应答信号均可以不要。 IIC使用 SDA信号线来传输数据,使用 SCL信号线进行数据同步。SDA数据线在 SCL的每一个时钟周期传输一位数据。传输时,SCL为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时,SDA的数据无效,通常在这个时候SDA进行电平切换,为下一次表示数据作好准备。每次数据传输都以字节为单位,每次传输的字节数不受限制。 若是咱们直接控制STM32的两个GPIO 引脚,分别用做 SCL和SDA,按照上述信号的时序要求,直接像控制 LED 灯那样控制引脚的输出(如果接收数据时则读取 SDA电平),就能够实现 IIC通信。一样假如咱们按照 USART的要求去控制引脚,也能实现 USART通信。因此只要遵照协议,就是标准的通信,无论您如何实现它,无论是ST生产的控制器仍是ATMEL生产的存储器, 都能按通信标准交互。 因为直接控制 GPIO 引脚电平产生通信时序时,须要由 CPU 控制每一个时刻的引脚状态,因此称之为“软件模拟协议”方式。相对地,还有“硬件协议”方式,STM32 的 IIC片上外设专门负责实现IIC通信协议,只要配置好该外设,它就会自动根据协议要求产生通信信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理IIC协议的方式减轻了 CPU 的工做,且使软件设计更加简单。缓存
I2C通讯的时候,通讯双方地位是不对等的,而是分主设备和从设备。通讯由主设备发起,由主设备主导,从设备只是按照I2C协议被动的接受主设备的通讯,并及时响应。markdown
谁是主设备、谁是从设备是由通讯双方来定的(I2C协议并没有规定),通常来讲一个芯片能够只能作主设备、也能够只能作从设备、也能够既能当主设备又能当从设备(软件配置)。函数
有不少人认为在通讯时单片机是主设备,器件是从设备,这是不严谨的。STM32单片机也能够当从设备,只是你没见到过罢了。学习
如何牢记IIC通讯的起始信号和结束信号的时序? 咱们把IIC通讯看作一条游荡在水中小船,把船面当作SDA数据线,水面波澜起伏当作IIC通讯的时钟SCK,没有水船就不能走,同理没有时钟线就没有通讯。由于SCL 维持高电平,SDA 线发生一个从高到低的降低沿起始信号就开始了,因此咱们能够把船头当作起始信号(想象一下,在湖面上一条弯弯的小船在顺水而行)。同时SCL 维持高电平,SDA 线发生一个从低到高的上升沿就是中止信号,故咱们能够把船尾看作中止信号(小船的船尾是否是与水面夹角为45°)。所以若是你记不住IIC通讯的时序,请想象一下,你坐在一条小船上,顺水而下坐在船上拿着电脑写着IIC驱动程序就能够了。 ui
功能:配置IIC的时钟线和数据线spa
void IIC_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
IIC_SCL=1;
IIC_SDA=1;
}
复制代码
由于是软件模拟IIC那么咱们选择IIC通信的引脚就相对来讲说比较随意,具体使用的引脚可查阅《STM32F1xx 规格书》,以它为准。这里咱们就选择PC十一、PC12做为IIC的数据和时钟引脚。设置为推挽输出便可。设计
功能: CPU发起I2C总线起始信号code
void IIC_Start(void) {
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:当 CLK 为高电平时,DATA 从高到低改变
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
delay_us(4);
}
复制代码
起始信号产生后,全部从机设备就开始等待STM32紧接下来的从机地址信号。在IIC总线上,每一个设备的地址都是惟一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略以后的数据信号。根据IIC协议,这个从机地址能够是 7位或10位。在地址位以后,是传输方向的选择位,该位为 0时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为 1时,则相反,即主机由从机读数据。orm
功能:CPU 产生一个时钟,并读取器件的 ACK 应答信号
//返回值:1,接收应答失败 0,接收应答成功
u8 IIC_Wait_Ack(void) {
u8 re;
IIC_SDA=1;delay_us(1);//CPU释放SDA总线
IIC_SCL=1;delay_us(1);//CPU驱动SCL=1,此时器件会返回ACK应答
if(READ_SDA){//CPU读取SDA口线状态
re=1;
}else{
re=0;
}
IIC_SCL=0;//时钟输出0
return re;
}
复制代码
该函数用于 STM32 做为发送方时,等待及处理接收方传来的响应或非响应信号, 即通常调用前面的 IIC_SendByte 函数后,再调用本函数检测响应。 STM32控制 SDA 信号线输出高阻态,释放它对 SDA的控制权,由接收方控制;控制 SCL 信号线切换高低电平,产生一个时钟信号,根据IIC协议,此时接收方若把 SDA 设置为低电平,就表示返回一个“应答”信号,若 SDA 保持为高电平,则表示返回一个“非应答 ”信号;在 SCL 切换高低电平之间,有个延时确保给予了足够的时间让接收方返回应答信号,延时后使用宏SDA_READ 读取 SDA 线的电平,根据电平值赋予 re 变量的值; 函数的最后返回 re的值,接收到响应时返回 0,未接收到响应时返回 1。当 STM32 做为数据接收端,调用 IIC_ReadByte 函数后,须要给发送端返回应答或非应答信号,此时可以使用 IIC_Ack及 IIC_Nack 函数处理,该处理与 IIC_Wait_Ack函数相反,此时 SDA线也由 STM32控制。
功能: CPU 产生一个 ACK 信号
//CPU产生一个ACK信号
void IIC_Ack(void) {
IIC_SDA=0;//CPU驱动SDA=0
delay_us(2);
IIC_SCL=1;//CPU产生一个时钟
delay_us(2);
IIC_SCL=0;
delay_us(2);
IIC_SDA=1;//CPU释放SDA总线
}
//CPU产生1个NACK信号
void IIC_Nack (void) {
IIC_SDA=1();//CPU驱动SDA=1
delay_us(2);
IIC_SDA=1;//CPU产生1个时钟
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
复制代码
I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。做为数据接收端时,当设备接收到 I2C 传输的一个字节数据或地址后,若但愿对方继续发送数据,则须要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端但愿结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个中止信号,结束信号传输。 代码的具体流程就是:根据要返回“应答”仍是“非应答”信号,先准备好 SDA 线的电平,IIC_Ack函数中把 SDA 线设置为低电平,表示“应答”信号,IIC_Nack 函数中把 SDA 线设置为高电平,表示“非应答”信号;控制 SCL 线进行高低电平切换,产生一个时钟信号,在 SCL 线的高低电平之间加入一个延时,确保有足够的时间让通信的另外一方接收到 SDA信号线的电平;在 IIC_Ack 函数的末尾,响应信号发送结束后,从新把 SDA 线设置为高电平以释放总线的控制权,方便后续的通信。
功能:CPU 发起 I2C 总线中止信号
{
IIC_SDA=0;//STOP:当 CLK 为高电平时候, SDA出现一个上调表示IIC总线中止信号
IIC_SCL=1;
delay_us(4);
IIC_SDA=1;//发送I2C总线结束信号
}
复制代码
中止信号直接看是时序图就能够搞定了,在SCL和SDA都为低电平的状况下,首先把时钟线SCL拉高,再把数据线SDA拉高,IIC就会结束传输了。 以上就是软件模拟IIC协议了,在平时的应用中咱们实际上不须要掌握这些具体的代码,只要知道IIC协议的过程原理就好了,应为通常来讲咱们用的都是别人写好的代码,咱们只须要会用就能够了,若是你的代码和我这些有出入也没有关系,只要能正常通信便可,固然若是你的设计在过程当中出现了一些问题,或者显示不正常,咱们首先考虑的也不是底层协议的问题,而是你代码的其余问题。
功能: CPU向I2C总线设备发送8bit数据
void IIC_SendByte(u8 Byte) {
u8 i;
/* 先发送字节的高位bit7 */
for (i = 0; i < 8; i++)
{
if (Byte & 0x80)
{
IIC_SDA=1;
}
else
{
IIC_SDA=0;
}
i2c_Delay();
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
if (i == 7)
{
IIC_SDA=1;// 释放总线
}
Byte <<= 1; /* 左移一个bit */
delay_us(2);
}
}
复制代码
该函数以其输入参数做为要使用 I2C 协议输出的数据,该数据大小为一字节。函数的主体是一个 8 次的 for 循环,循环体执行一次将会对外发送一个数据位,循环结束时恰好发送完该字节数据。步骤分解以下: 首先程序对输入参数Byte 和 0x80“与”运算,判断其最高位的逻辑值,为 1 时控制 SDA输出高电平,为 0则控制 SDA输出低电平;接下来 延时,以此保证 SDA 线输出的电平已稳定,再进行后续操做;以后控制 SCL线产生高低电平跳变,也就是产生 I2C协议中 SCL线的通信时钟; 在 SCL线高低电平之间有个延时,该延时期间 SCL线维持高电平,根据 I2C协议,此时数据有效,通信的另外一方会在此时读取 SDA 线的电平逻辑,高电平时接收到该位为数据 1,不然为 0;就这样一次循环体执行结束,Byte 左移一位以便下次循环发送下一位的数据;如次循环 8 次,把Byte 中的 8 位个数据位发送完毕,在最后一位发送完成后(此时循环计数器 i=7),控制 SDA 线输出 1(即高阻态),也就是说发送方释放 SDA总线,等待接收方的应答信号。
功能: CPU从IIC总线设备读取8bit数据
u8_t IIC_ReadByte(void) {
u8 i;
u8 value;
//读到第1个bit为数据的bit7
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
IIC_SCL=1;
delay_us(2);
if (DA_READ)
{
value++;
}
IIC_SCL=0;
delay_us(2);
}
return value;
}
复制代码
IIC_ReadByte 函数也是以 for 循环为主体,循环体会被执行 8次,执行完毕后将会接收到一个字节的数据,循环体接收数据的流程以下: 首先使用一个变量 value 暂存要接收的数据,每次循环开始前先对 value 的值左移 1 位,以给 value 变量的 bit0 腾出空间,bit0 将用于缓存最新接收到的数据位,一位一位地接收并移位,最后拼出完整的 8位数据;而后控制 SCL线进行高低电平切换,输出 I2C 协议通信用的时钟; 在 SCL 线高低电平切换之间,有个延时,该延时确保给予了足够的时间让数据发送方进行处理,即发送方在 SCL 时钟驱动下经过 SDA 信号线发出电平逻辑信号,而这个延时以后,做为数据接收端的 STM32 使用宏 SDA_READ读取 SDA信号线的电平,若信号线为 1,则 value++,即把它的 bit0置 1,不然不 操做(这样该位将保持为 0),这样就读取到了一位的数据;接下来SCL线切换成低电平后,加入延时,以便接收端根据须要切换 SDA 线输出数据;直到循环结束后,value 变量中包含有 1 个字节数据,使用 return 把它做为函数返回值返回;
相对来讲,硬件IIC直接使用外设来控制引脚,能够减轻 CPU 的负担。不过使用硬件IIC 时必须使用某些固定的引脚做为 SCL 和 SDA,软件模拟IIC则可使用任意 GPIO 引脚,相对比较灵活。 STM32的IIC外设可用做通信的主机或从机,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7位、10位设备地址,支持 DMA数据传输,并具备数据校验功能。它的IIC外设还支持 SMBus2.0协,SMBus 协议与IIC相似,主要应用于笔记本电脑的电池管理中。 STM32 芯片有多个IIC外设,它们的IIC通信信号引出到不一样的 GPIO 引脚上,使用时必须配置到这些指定的引脚,GPIO引脚的复用功能,可查阅《STM32F1xx 规格书》,以它为准。
void IIC_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;// 开漏输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
IIC_SCL=1;
IIC_SDA=1;//给一个中止信号, 复位I2C总线上的全部设备到待机模式
}
复制代码
由于是硬件IIC直接使用外设来控制引脚,那么咱们选择IIC通信的引脚就比较固定,具体使用的引脚可查阅《STM32F1xx 规格书》,以它为准。能够看到PB6和PB7两个引脚能够做为IIC的通信引脚,并且PB6为SCL时钟线,而PB7则为SDA数据线,并设置为开漏输出。 这里为啥设置为开漏输出的方式呢? 这是因为使用的是软件模拟IIC方式,而IIC协议的 GPIO 必须的开漏输出模式,开漏输出模式在输出高电平时实际输出高阻态,当IIC该总线上全部设备都输出高阻态时,由外部的上拉电阻上拉为高电平。另外当 STM32 的 GPIO 配置成开漏输出模式时,它仍然能够经过读取GPIO 的输入数据寄存器获取外部对引脚的输入电平,也就是说它同时具备浮空输入模式的功能,所以在后面控制 SDA线对外输出电平或读取 SDA线的电平信号时不须要切换 GPIO的模式。 另外在应交IIC协议之下,它的起始信号、等待应答信号、应答信号、中止信号都与软件模拟IIC协议之下的函数相同,在这里我就不重复说明了。 总结:IIC通信协议很简单,在实际项目中咱们不须要掌握具体的IIC协议代码,只要会用便可,做为最多见且经常使用的协议,咱们最好可以背下来或者有所了解。如今IIC通信不陌生了吧!