一、IIC(Inter-Integrated Circuit)介绍
IIC(Inter-Integrated Circuit)是一种具有两线传输的串行通信总线,适用于数据量不大且传输距离短的场合。
IIC串行总线由两根信号线组成,一根是双向的数据线SDA,另一根是单向的时钟线SCL,在空闲状态时,SDA和SCL线都置’1‘,为高电平。
IIC为同步的半双工通信方式,常见的传输速率有:100kb/s、300kb/s、3.4Mkb/s。
二、传输协议
IIC由两根通信信号线组成,SCL是由主模块输入的时钟信号,是单向的信号,而SDA是由主机或从机控制的数据信号,是双向信号。
在空闲状态下,SCL及SDA都是置高的状态,当需要进行一次IIC传输时,由START信号指示当前数据传输开始,由STOP信号指示当前的数据传输结束。
START信号的标识是在SCL高电平情况下,SDA信号由高变低 ,即视为START开始;
STOP信号标识是在SCL高电平情况下,SDA信号由低变高,即视为STOP结束;
起始和结束时序图如下:
用到的IIC引脚定义:
其中SCL接到了PA11,SDA接到了PA12;
//-----------------OLED端口定义----------------
#define GPIO_SCL_PORT GPIOA
#define GPIO_SCL_PIN GPIO_Pin_11
#define GPIO_SCL_ENABLE() do{ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); }while(0) /* 所在IO口时钟使能 */
#define GPIO_SDA_PORT GPIOA
#define GPIO_SDA_PIN GPIO_Pin_12
#define GPIO_SDA_ENABLE() do{ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); }while(0) /* 所在IO口时钟使能 */
#define OLED_SCL(x) GPIO_SCL_PORT->BSRR = GPIO_SCL_PIN << (16*(!x))
#define OLED_SDA(x) GPIO_SDA_PORT->BSRR = GPIO_SDA_PIN << (16*(!x))
1.IIC_STAR
//起始信号,SCL高电平期间拉低SDA
void I2C_Start(void)
{
SDA_OUT();//将SDA口配置成输出
OLED_SDA(1);
OLED_SCL(1);
IIC_delay();
OLED_SDA(0);
IIC_delay();
OLED_SCL(0);
IIC_delay();
}
2.IIC_STOP
结束信号,SCL高电平情况下,SDA信号由低变高。故在拉高SCL之前将SDA拉低;
//结束信号,SCL高电平情况下,SDA信号由低变高
void I2C_Stop(void)
{
SDA_OUT();//将SDA口配置成输出
OLED_SDA(0);
OLED_SCL(1);
IIC_delay();
OLED_SDA(1);
}
3.等待信号响应 :Wait_Ack
发送完一个字节后,主机在下一个时钟接收一位数据,判断从机是否应答。0为应答,1为非应答;其时序就是接收时序,只不过是接收一位数据,而接收数据是八位数据。
将其配置为输入模式(51单片机中是释放SDA,即将SDA数据线置1),拉高SCL读取一位数据,再拉低进行后续操作。
//等待信号响应
void I2C_WaitAck(void) //测数据信号的电平
{
SDA_IN();//SDA设置为输入
IIC_delay();
OLED_SCL(1);
IIC_delay();
OLED_SCL(0);
IIC_delay();
}
4.发送应答位
接收完一个字节后,主机在下一个时钟发送一位数据。0为应答,表示不需要从机交出控制权,即继续接收,1为非应答,表示已经结束,从机交出控制权;
//写入一个字节
void Send_Ack(u8 ack)
{
u8 i;
OLED_SCL(0);
SDA_OUT();//将SDA口配置成输出
if(ack)
OLED_SDA(1);
else
OLED_SDA(0);
IIC_delay();
OLED_SCL(1);
IIC_delay();
OLED_SCL(0);
}
5.发送一个字节
SCL低电平期间,主机将数据依次放到SDA上(高位在前),再拉高SCL,从机在SCL高电平期间读取SDA。(SCL高电平期间SDA不能有数据变化)循环八次发送一个字节。最后等待ACK结束;
//写入一个字节
void Send_Byte(u8 dat)
{
u8 i;
OLED_SCL(0);
SDA_OUT();//将SDA口配置成输出
for(i=0;i<8;i++)
{
OLED_SDA((BitAction)(dat&(0x80>>i)));
IIC_delay();
OLED_SCL(1);
IIC_delay();
OLED_SCL(0);
}
}
由于是高位写入,故将要写入的dat高位写入,与上0x80取出高位。当然这样还不够,应该强制转换成BitAction类型数据,否则会通信失败。我正是一直通信不上才发现是这里的问题。利用for循环循环八次依次写入高位。
6.接收一个字节数据
这里我就不在附上时序图了,至于为什么呢?其实接收与发送的时序大差不差,你听我给你描述一下:
接收一个字节:SCL低电平期间,从机将数据依次放到SDA上(高位在前),再拉高SCL,主机在SCL高电平期间读取SDA。(SCL高电平期间SDA不能有数据变化)循环八次接收一个字节。
没错,描述几乎一模一样!只是操作对象从机换成了主机,主机换成了从机!也就是谁发谁操作数据线。
这里拓展一下:
在51单片机做法是从机发主机得先释放SDA数据线(也就是将SDA置1)!只有这样从机才能相应控制SDA数据线。
在32中就不用这么做了,可以用寄存器将SDA配置成输入模式,但相应的前面主机操作都应该加上将SDA配置成输出模式;(也就是在start,stop,发送字节,发送应答都配置为输出!加上SDA_OUT()语句,至于如何利用寄存器去配置可以参考我的STM32通过寄存器来控制GPIO)
//IO方向设置
#define SDA_IN() {GPIOA->CRH &= 0XFFF0FFFF;GPIOA->CRH |= (u32)8<<16;}
#define SDA_OUT() {GPIOA->CRH &= 0XFFF0FFFF;GPIOA->CRH |= (u32)3<<16;}
#define READ_SDA() (BitAction)(GPIOA->IDR & GPIO_Pin_12)
当然,oled没有用到读函数,这里顺带将IIC时序都介绍一下。
uint8_t Receive_Byte(void)
{
unsigned char i,dat=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
OLED_SCL(0);
IIC_delay();
OLED_SCL(1);
dat<<=1;
if(READ_SDA)
dat |= 1;
IIC_delay();
}
return dat;
}
首先将SDA配置为输入模式,将SCL拉低接收数据改变,再将SCL拉高读取数据,如果为高电平,dat加1;向高位移动一位,这样运行七次刚好将数据移到最高位!这里的七次并未算第一次,因为第一次位移并不影响第一次的接收,0怎么位移结果还是0;如果放到后面就相当于多位移了一次,数据从第0位移到第八位只需要七次位移就够了。