IIC总线(在有些资料中也写作I2C总线),之前在很多模块中都有用到。初步接触的时候,看了其定义和逻辑,最后在使用上时却直接使用了现车的库函数。
最近在别人问上关于什么是IIC,具体的机制等等时,我却一时间没办法答上来,只能支支吾吾的将脑海生成的内容翻出来,没法让人满意和信服,故这里准备将关于IIC 和 SPI(之后的计划)通讯协议进行一个整理。方便初学者初步掌握以及我之后的学习回顾。
注:之后通称IIC。
在我的下一篇博客(DS3231时钟模块使用,IIC协议实践。(基于STM32))有关于IIC的实践项目,结合起来看更容易理解,相当详细易懂。
后文有附在stm32下的软件模拟IIC代码,若需要可以直接查阅。
IIC( Inter-Integrated Circuit (集成电路总线 ) ),是IICBus的简称。为一种串行通讯总线,采用多主从架构。
由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。I²C的正确读法为“I平方C”(“I-squared-C”),而“I二C”(“I-two-C”)则是另一种错误但被广泛使用的读法。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。
PS:我们一般读作“ I 方 C”。
注意上面的关键字,连接低速周边设备 。以及需要注意IIC为半双工通讯。
IIC总线只需要这两根线,用于数据传输,SDA即数据线,SCL即时钟线,值得注意的是,两根总线需要用上拉电阻拉高,即默认为高电平,主机和从机只能通过IO将其拉低,来实现传输数据。即若下文中出现释放SDA线即SDA线因为上拉电阻变为高电平;占用SCL,即刻意拉低SCL。
如果从机要完成一些其他功能后,例如一个内部中断服务程序 ,能接收或发送下一个完整的数据字节,可以使时钟线 SCL 保持低电平迫使主机进入等待状态,当从机准备好接收下一个数据字节并释放时钟线 SCL 后 数据传输继续。
IIC总线是一个多主机的总线。就是说可以连接多于一个能控制总线的器件到总线。
即是说,要实现一个控制器控制多个外围模块或设备,将控制器和支持IIC的模块接在同一条IIC总线上即可。这样也极大的节约了IO口资源。
了解IIC通讯协议具体,首先得知道如何将数据有效的传出,参考下面数据有效性。
SDA线上的数据必须在时钟线SCL高电平周期保持稳定。即在SCL低电平时候控制SDA线改变,在SCL高电平的时候保持SDA有效电平稳定。
注意SCL和SDA皆为总线,故总线上的设备都可以检测到这个起始符。总线在起始条件后被认定为处于忙的状态。在坚持到结束条件后,系统再次被认为处于空闲状态。
发送到SDA线上的每个字节必须是8位,但每次传输的字节数量却是不受限制的。而每个字节之后需要跟一个响应位。,数据传输从最高位(MSB)开始传输。
数据传输必须带响应 相关的响应时钟脉冲由主机产生 在响应的时钟脉冲期间 发送器释放 SDA 线,即使SDA线为高。
在响应的时钟脉冲期间,接收器(从机)需将SDA线拉低,使它在这个时钟脉冲高电平期间保持稳定的低电平,即完成了响应。
首先在起始条件(S)后,发送一个从机地址+数据方向位(R/W,0为W,1为R)。数据传输以主机产生的停止位(P)终止。
主机可以在不终止的情况下,寻找另一个从机,它可以产生重复的起始条件(Sr)和寻址另一个从机,不用先产生一个停止条件。
另外:
从机地址由一个固定和一个可编程的部分构成 。由于很可能在一个系统中有几个同样的器件,从机地址的可编程部分使最大数量的这些器件可以连接到 I2C 总线上。器件可编程地址位的数量由它可使用的管脚决定,例如 如果器件有 4 个固定的和 3 个可编程的地址位,那么相同的总线上共可以连接 8 (2^3)个相同的器件。
在第一次响应后,主机- - 发送器 变为了主机 - -接收器,从机 - -接收器变为从机- -发送器。第一次响应仍由从机产生。之前发送了一个不响应信号 A 的主机产生停止条件。
传输方向改变的时候,起始条件以及从机的地址都会重复,而R/W位被取反。
一下在基于stm32标准库函数的软件模拟IO实现IIC代码,供参考。
宏定义:
//位带操作,实现51类似的GPIO控制功能
//IO口操作宏定义
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define IIC_SDA PAout(10)
#define IIC_SCL PAout(11)
主要函数:
void IIC_Init(void) //初始化
{
GPIO_InitTypeDef GPIO_InitStructer;
SDA_RCCPeriphClockcmd(SDASCL_GPIO_RCC, ENABLE);
GPIO_InitStructer.GPIO_Pin=SCL_GPIO_Pin | SDA_GPIO_Pin; //10--SCL 11--SDA //PB10 PB11
GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(SDA_GPIOx, &GPIO_InitStructer);
}
void SDA_OUT(void) //设置主机SDA输出模式
{
GPIO_InitTypeDef GPIO_InitStructer;
GPIO_InitStructer.GPIO_Pin= SDA_GPIO_Pin;
GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(SDA_GPIOx, &GPIO_InitStructer);
}
void SDA_IN(void) //设置主机SDA输入上拉模式
{
GPIO_InitTypeDef GPIO_InitStructer;
GPIO_InitStructer.GPIO_Pin= SDA_GPIO_Pin;
GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructer.GPIO_Mode=GPIO_Mode_IPU; //输入上拉模式
GPIO_Init(SDA_GPIOx, &GPIO_InitStructer);
}
void IIC_Start(void) //产生起始条件(符)
{
SDA_OUT();
IIC_SDA=1; //先确保 SDA为高
IIC_SCL=1; //拉高SCL
SysTick_Delay_us(4);
IIC_SDA=0; //产生下降沿
SysTick_Delay_us(4);
IIC_SCL=0; //将时钟线拉低,只有时钟线拉低才运行SDA数据变化有效
SysTick_Delay_us(4);
}
void IIC_Stop(void) //产生结束条件(符)
{
SDA_OUT();
IIC_SCL = 0;
IIC_SDA=0; //先让SDA 为低电平
IIC_SCL=1; //将SCL拉高
SysTick_Delay_us(4); // 延时,保证时长
IIC_SDA=1; //SDA拉高 产生上升沿
SysTick_Delay_us(4); // 结束后释放SDA , SDA变高
}
// 下面两个 wait_Ask 函数,用来等待从机的ACK, 第一个不检测只等待,第二个检测且等待,没有就返回错误。
// uint8_t IIC_Wait_Ask(void)
//{
// SDA_IN();
// IIC_SCL=1;
// SysTick_Delay_us(4);
// IIC_SCL=0;
// SysTick_Delay_us(4);
// return 0;
//}
uint8_t IIC_Wait_Ask(void)
{
uint16_t tempTime = 0;
SDA_IN();
IIC_SDA = 1; //释放数据总线,交由从机控制
SysTick_Delay_us(4);
IIC_SCL = 1;
SysTick_Delay_us(1);
while (GPIO_ReadInputDataBit(IIC_SDA_GPIOx,IIC_SDA_GPIO_Pin)) //读到 0 ,即接收到ACK,循环跳出
{
tempTime++;
if(tempTime > 300)
{
IIC_Stop();
printf("no ack\r\n");
return 1; //超时返回1
}
}
IIC_SCL = 0;
return 0; //接收到 ACK 返回0
}
//主机产生应答信号ACK
void I2C_Ack(void)
{
IIC_SCL = 0 ;
SDA_OUT();
IIC_SDA = 0;
SysTick_Delay_us(2);
IIC_SCL = 1;
SysTick_Delay_us(5);
IIC_SCL = 0;
}
//主机不产生应答信号NACK
void I2C_NAck(void)
{
IIC_SCL = 0;
SDA_OUT();
IIC_SDA = 1;
SysTick_Delay_us(2);
IIC_SCL = 1;
SysTick_Delay_us(5);
IIC_SCL = 0;
}
void IIC_WriteByte(u8 data)
{
u8 i;
SDA_OUT();
for(i=0;i<8;i++)
{
IIC_SCL=0;
SysTick_Delay_us(4);
if(data & 0x80)
IIC_SDA=1;
else
IIC_SDA=0;
IIC_SCL=1;
SysTick_Delay_us(4);
IIC_SCL=0;
data<<=1;
}
}
主要参考文献:IIC协议中文版.pdf (广州周立功单片机发展有限公司),下载也是从CSDN下载的。
代码部分主要参考正点原子的IIC代码,以及从其他博主那参考。