完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
|
|
相关推荐
1个回答
|
|
4.8 I2C总线
I2C总线(Inter-Integrated Circuit Bus)是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。某些书籍或者文档中也写作IIC,读作“I方C”。 I2C是嵌入式中最常见的,也是最重要的总线通信协议之一。很多传感器、外围芯片都使用I2C协议。它具有如下特点: (1)硬件线路简单:I2C总线只需要一根数据线和一根时钟线两根线, (2)灵活:数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。 (3)可以连接设备数量多:连接到相同总线上的IC数量只受总线最大电容的限制。 4.8.1 I2C器件地址 I2C总线是一个主从结构的总线,所有的数据传输都必须由主机发起,通常单片机做主机,其他连接在I2C总线的设备称之为从机或者器件。 I2C还有一个重要的概念:器件地址。连接在I2C总线上的设备,除了主机之外,每个器件都有自己的地址。主机想要和某个器件通信时,先往I2C总线发送器件地址。 I2C器件地址一般为8位,最后一位是读写标志位。0表示主机要读取器件的数据;1表示主机要往器件写数据。 4.8.2 I2C时序 I2C总线只需要两根线,分别是时钟线(SCL)、数据线(SDA)。其中时钟线提供时间周期,时间周期越短则数据传输速率越快。数据线用来传输起始位、应答位,数据等。时序图如4.37所示。 图4.37 I2C时序图 数据格式主要有起始位、停止位、数据位、应答位(ACK)、NACK。 1. 起始位 当主机想要启动I2C数据传输时,需要要先往I2C总线发送起始位。起始位的条件是SCL线为高电平时,SDA线从高电平向低电平切换。 2. 停止位 当主机想要终止I2C数据传输时,需要往I2C总线发送停止位,释放I2C总线的占用。停止位的条件是SCL线为高电平时,SDA线从低电平向高电平切换。 3. 数据位 SDA数据线上的每个字节必须是8位,每次传输的字节数量没有限制。每个字节后必须跟一个响应位(ACK)。首先传输的数据是最高位(MSB),SDA上的数据必须在SCL高电平周期时保持稳定,数据的高低电平翻转变化发生在SCL低电平时期。 4. 应答位 每个字节传输必须带响应位,相关的响应时钟也由主机产生,在响应的时钟脉冲期间(第9个时钟周期),发送端释放SDA线,接收端把SDA拉低。 5. NACK位 以下情况会导致出现NACK位: (1)接收机没有发送机响应的地址,接收端没有任何ACK发送给发射机 (2)由于接收机正在忙碌处理实时程序导致接无法接收或者发送 (3)传输过程中,接收机识别不了发送机的数据或命令 (4)接收机无法接收 (5)主机接收完成读取数据后,要发送NACK结束告知从机 4.8.3模拟I2C I2C属于比较简单的总线,完全可以根据I2C的时序,使用I/O模拟I2C。本文将使用STM32的GPIO口实现模拟I2C的功能,帮助读者理解I2C的时序控制。 打开Chapter4 5_I2C_24c02mdkIIC24c02.uvproj工程文件,接着打开24c02.c文件,模拟IIC的代码都在这个文件中。 1. I2C初始化 I2C的初始化部分代码主要是对STM32的GPIO进行初始化。GPIOB_9作为数据引脚(SDA),GPIOB_8作为时钟引脚(SCL),代码如下: //Chapter4 5_I2C_24c02USER24C0224c02.c 5行 //I2C初始化 void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); //打开GPIOB时钟 //GPIOB8,B9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出模式 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //开漏输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 IIC_Stop(); //先给停止信号,复位I2C总线上所有的设备 } 2. 起始信号 当SCL为高电平时,SDA出现一个下降沿表示I2C总线启动信号,代码如下: //Chapter4 5_I2C_24c02USER24C0224c02.c 23行 //I2C启动信号 void IIC_Start(void) { //先把SDA输出引脚置高 IIC_SDAOUT=1; //SCL引脚置高 IIC_SCL=1; //等待4us delay_us(4); //SDA引脚拉低 IIC_SDAOUT=0; //等待4us delay_us(4); //SCL引脚拉低 IIC_SCL=0; //准备发送数据或者接收数据 } IIC_SCL指I2C的SCL引脚,IIC_SDAOUT 指I2C的SDA引脚输出,在24c02.h文件中分别被定义成PBout(8)和PBout(9),代码如下: //Chapter4 5_I2C_24c02USER24C0224c02.h 8行 #define IIC_SCL PBout(8) //SCL #define IIC_SDAOUT PBout(9) // SDA IIC_SDAOUT=1表示SDA引脚输出高电平。这里是GPIO输出高低电平的另外一种写法,等价于之前的GPIO_WriteBit(GPIOB, GPIO_Pin_9, Bit_SET)。 IIC_Start函数使用SDA、SCL引脚,通过输出高低电平和延时的操作,模拟了I2C启动信号。其时序如图4.38所示。 图4.38 I2C启动信号时序 3. 停止信号 当SCL高电平时,SDA出现一个上升沿表示I2C总线停止信号,代码如下: //Chapter4 5_I2C_24c02USER24C0224c02.c 34行 void IIC_Stop(void) { //SDA先低电平,这样才能出现上升沿 IIC_SDAOUT=0; delay_us(4); //SCL高电平 IIC_SCL=1; delay_us(4); //SDA由低电平变高电平,此时出现一个上升沿。 IIC_SDAOUT=1; } 4.应答信号 I2C总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,在响应的时钟脉冲期间(第9个时钟周期),由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK)。主机等待从机应答信号的相关代码如下: //Chapter4 5_I2C_24c02USER24C0224c02.c 50行 //返回值:1表示NACK, 0表示 ACK u8 MCU_Wait_Ack(void) { u8 ack; IIC_SDAOUT=1; delay_us(1); IIC_SCL=1; delay_us(1); //读取SDA总线电平 if (IIC_SDAIN) { ack = 1; //高电平则表示NACK应答 } else { ack = 0; //低电平则表示NACK应答 } IIC_SCL=0; delay_us(1); return ack; } 5.数据位发送 在I2C总线上传送的每一位数据都有一个时钟脉冲相对应。在SCL呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。只有在SCL为低电平期间,才允许SDA上的电平改变状态。逻辑0的电平为低电压,而逻辑1则为高电平。时序如图4.39所示。 图4.39 数据位发送时序 6.发送一个字节 I2C写一个字节相当于往I2C总线发送了8个数据位,根据图4.39数据位发送时序,我们可以用I/O模拟,代码如下: //Chapter4 5_I2C_24c02USER24C0224c02.c 113行 //参数:Senddata 要发送的数据 void IIC_write_OneByte(u8 Senddata) { u8 t; IIC_SCL=0; for(t=0;t<8;t++) { //先发送高位 IIC_SDAOUT=(Senddata&0x80)>>7; //左移1位 Senddata=(Senddata<<1); delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } } 其中比较关键的代码是Senddata的移位操作。 根据 & 和 >> 的特性,(Senddata&0x80)>>7相当于保留Senddata的最高位,其它位清零,同时再把最高位右移到最低位。相当于把Senddata最高位的数值赋给IIC_SDAOUT,从而实现SDA引脚根据Senddata的最高位输出响应的高低电平。 之后Senddata=(Senddata<<1),把Senddata的第2高位通过左移1位的方式,使Senddata的第2高位变成最高位。 再通过for循环,重复这两步操作,把Senddata的每一位都发送出去。为了方便直观理解,我们假设Senddata等于170,十六进制为:0xAA,二进制为:10101010。整个for循环的移位操作可以用图4.40直观的表示出来。 图4.40 移位操作流程图 7.读一个字节 读时序和发送时序相同,不同的是发送时需要在SCL低电平的时候更改SDA数据位,而读时需要在SCL高电平的时候读取SDA数据位。同时,每读取一位数据,都需要左移1位,保证高位在前。读完数据后需要发送ACK或者NACK应答信号。代入如下: //Chapter4 5_I2C_24c02USER24C0224c02.c 137行 u8 IIC_Read_OneByte(u8 ack) { u8 i,receivedata=0; for(i=0;i<8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receivedata<<=1; if(IIC_SDAIN) { receivedata++; } delay_us(1); } if (!ack) MCU_NOAck(); else MCU_Send_Ack(); return receivedata; } 4.8.4小结 I2C是嵌入式中最常见的总线通信协议,读者需要熟练掌握,了解IIC的时序,并能使用I/O模拟I2C操作。 |
|
|
|
只有小组成员才能发言,加入小组>>
3320 浏览 9 评论
3000 浏览 16 评论
3496 浏览 1 评论
9069 浏览 16 评论
4089 浏览 18 评论
1190浏览 3评论
612浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
603浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2339浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1899浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-27 13:04 , Processed in 1.323113 second(s), Total 78, Slave 59 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号