嵌入式硬件通信接口-IIC

IIC协议简介

IIC(Inter-Integrated Circuit)也叫I2C,集成电路总线。在1982年由飞利浦半导体公司(现在叫恩智浦半导体)发明的一种同步的,多主机,多从机,数据包交换,半双工,串行计算机总线。于2006-10-10正式开源

IIC协议特点

IIC应用只需要两根双向的集电极开路或漏极开路线:串行数据线(SDA)和串行时钟线(SCL)与上拉电阻。虽然系统允许采用各种工作电压,不过一般IIC应用在+5V与+3.3V的系统

IIC总线的节点数量有地址空间与总线上的电容(不超过400pf)决定的,这也约束了通讯距离在几米之内。同时为了有相对较高的阻抗和较低的噪声要求有一个相同的对地点平,这再次约束实际应用只能在同一块板子/PC上

IIC协议是一种带有时钟(SCL)和寻址的数据(SDA)总线,该总线在节点中充当两个角色:主机和从机:

  • 主机节点-产生时钟信号并发起与从节点的通信
  • 从机节点-接收时钟信号并响应主机发出的寻址

IIC总线是多主机总线,这意味着可以存在任意数量的主机节点。同时,可以在消息之间更改主角色和从角色(发送停止信号后),对于设定好的总线设备可以有四种操作模式,尽管实际通信只采用一到两种:

  • 主机发送-主机节点发送数据到从机
  • 主机接受-主机节点接受从机的数据
  • 从机发送-从机节点发送数据到主机
  • 从机接收-从机节点接受主机的数据

数据通信过程中,IIC总线上的主机控制SCL产生脉冲然后发送起始START信号通知从机开始通信,然后发送要与之通信从机的(7位/10位)地址&读写位,数据传输阶段接收方每接收一字节数据后需要控制SDA产生应答ACK/不应答NACK信号,最后主机发送STOP停止信号结束本次通信

IIC定义了基本的通信类型,每次都以START(起始)信号为开始,STOP(停止)信号为结束:

  • 单个消息-主机向从机写入数据
  • 单个消息-主机向从机读取数据
  • 组合格式-主机向一个或多个从机发出至少两个读/写操作

在一次组合传输中,每次读/写都是以STSRT(起始)信号和从地址开始,第一个之后的STSRT(起始)信号也叫重复起始条件,在之前没有STOP(停止)信号,这就是从机判断下一次消息是同一次传输的一部分的方法
从机只响应其产品文档中指定的特定消息

地址和数据字节是最高位先发送;起始位是SCL位于高电平时,SDA由高变低来表示;停止位是SCL位于高电平时,SDA由低变高来表示;其他的情况中,SDA的变化只发生在SCL位于低电平时

在实际的硬件设计中,SCL线与SDA线都采用开漏设计,因此它们需要上拉电阻。当输出逻辑0时把线拉到GND;当输出逻辑1时状态是浮空的,因为由上拉电阻存在会变为高。线不会主动变为高(只能被上拉电阻拉高),这种连接方式允许多个节点接入到总线上而不会因为信号引起短路。高速系统(和其他一些系统)可以使用电流源来取代SCL或SCL和SDA的上拉电阻来适应更高的总线电容和更快的上升时间

从上述可以得出一个结论:多个节点可以同时驱动线路。如果任意一个节点把线拉低,那么该线就会处于低状态,当有节点试图把线拉高就会检测出此线处于低状态,从而得出有其他节点正在使用总线。这种情况发生在SCL线上时,被称为时钟延展,作为从机流控制机制;当发生在SDA线上时,被称为仲裁,作为一次通信只能有一个发送方

IIC的通信的运行模式可能会是下面几种之一。100kbit/s标准模式(Standard Mode)是所有设备都兼容的,也一直被使用,但是在同一总线上同时具有不同功能的设备可能会导致问题,如下:

  • 快速模式(Fast Mode)是大部分设备兼容的,并简单地收缩了几个时间参数来达到400kbit/s的速度。快速模式广泛被从机设备支持,因此,只要总线电容和上拉强度允许,主机就能使用它
  • 快速模式+(Fast Mode Plus)能达到1Mbit/s,使用更强大的(20ma)驱动和上拉来实现加快上升和下降沿时间,如果减少通讯是的下拉强度能实现与标准模式和快速模式兼容
  • 高速模式(High Speed Mode)3.4Mbit/s在同一总线上与正常IIC设备兼容,但是要求在高速传输时主机有一个主动上拉的时钟线,第一个数据位以正常的开漏上升时钟沿传输,该时钟可能被延展,剩余的7个数据位和应答位,主机在适当驱动时钟线为高,从机不能对其延展。所有高速传输之前都有一个使用标准/高速传输单字节的“master code”,这个coed有三个用途:
    • 告诉高速从机设备更改为高速时间设置规则
    • 它确保快速/正常速度设备不会尝试参与传输(因为与他们的地址不匹配)
    • 因为它标识了主代码(有8个主代码,每个主代码必须使用一个不同的主代码),所以它确保仲裁在传输的高速部分之前完成,因此高速部分不需要考虑这种能力
  • 超快速模式(Ultra-Fast Mode)本质上时一个只写的IIC子设备,它与其他模式不适合兼容,除了容易添加到现有的IIC硬件设计。只允许有一个主机,并且在任何时候都主动驱动时钟线和信号线以达到5Mbit/s的传输速率,时钟延展、仲裁、读传输、和应答都被忽略。它的主要作用于动态LED显示,其中传输错误只会导致一个无关紧要的短暂视觉故障,与其他IIC总线模式相似之处在于:
  • 起始和停止条件用于划分传输
  • IIC地址允许多从机设备挂在,但与SPI总线不同的是没有从机选择信号
  • 每个字节发送的第九个时钟脉冲用于表示应答位

IIC协议应用

IIC适用于外围设备简单和制造成本比速度重要的场合,常见的IIC应用有:

  • 通过小型ROM配置表描述可连接设备,以支持“即插即用”操作,如
    • 双内联内存模块(DIMMs)上的串行状态检测(SPD) EEPROMs
    • 通过VGA、DVI和HDMI连接器为监视器提供扩展显示识别数据(EDID)
  • 通过SMBus对PC系统进行系统管理
    • SMBus引脚在传统PCI和PCI Express连接器中都有配置
  • 访问实时时钟和NVRAM芯片来保存用户设置
  • 访问低速的ADCs与DACs
  • 改变显示器中的对比度、色相和色彩平衡设置(通过显示数据通道)
  • 改变智能扬声器的音量
  • 控制小的(如功能性手机)OLED或LCD显示器
  • 读取硬件监视器和诊断传感器,如风扇的速度
  • 开/关系统各部件的电源

微控制器(MCU)中的IIC特点是只需要两个通用的IO口就能实现对多个设备进行控制,能有效的节省资源,相对的缺点就是速度较慢

IIC协议版本

IIC协议版本发展历史:

  • 1982年,原始的100kbin/s的IIC系统作为一个简单的内部总线系统建立控制与各种飞利浦电子芯片被创建出来
  • 1992年,版本1;添加400kbit/s快速模式(Fm)和10位地址模式增加兼容到1008个节点,这是第一个标准的版本
  • 1998年,版本2;添加3.4Mbit/s高速模式(Hs)和电压电流节能需求
  • 2000年,版本2.1;延续版本2,没有重大的功能改变
  • 2007年,版本3;添加1Mbit/s增强版快速模式(Fm+)(使用20ma驱动)和驱动ID机制
  • 2012年,版本4;添加5Mbit/s超快速(UFm),使用新的USDA(数据线)和USCL(时钟线),使用上拉逻辑,取消上拉电阻和添加一个制定的制造商ID表。它是一个单向总线
  • 2012年,版本5;纠正错误
  • 2014年,版本6;纠正两个图形,这是最新的协议标准

IIC协议时序

IIC的传输在串行数据线(SDA)和串行时钟线(SCL)上完成,IIC在通信过程有两个角色:主机和从机;主机是控制SCL线的一方。IIC通信的数据是高位先发送

IIC的起始&结束时序

IIC的数据传输开始与一个起始START位,结束于一个停止STOP位。起始位时序:在SCL处于高状态时,SDA被拉低(SDA由高变为低),即在SCL处于高状态时,SDA产生一个下降沿;停止位时序:在SCL处于高状态时,SDA在低状态变为高状态(SDA由低变为高),即在SCL处于高状态时,SDA产生一个上升沿

数据阶段时序

IIC的数据是先发送高位,数据在传输过程是以bit为单位,在SCL产生的时序脉冲下通过SDA线传输,发送方(可能是主机或从机)每传输1Byte数据需要接收一个应答位,应答位由接收方发出,接收方在接收完1Byte数据,在第九个脉冲产生控制SDA产生应答,用于告诉发送方接下来通信事宜,接收方在第九个脉冲拉低SDA表示应答发送方,拉高SDA表示不应答

数据阶段,SCL处于低状态时,允许SDA改变状态,这时发送方把数据放到SDA上;SCL处于高状态时,SDA不允许改变状态,接收方从SDA上读取数据。从SCL角度理解,当SCL产生下降沿时,发送方传输数据到SDA上,当SCL产生上升沿时,接收方读取SDA上的数据

时钟延展时序

时钟延展是IIC协议一个很重要的特点。一个编址的从机设备在接收(或发送)一个字节后可以保持SCL线为低状态,表示它还没有准备好处理更多的数据,正在与它进行通讯的主机可能无法完成当前位的传输,也必须等到SCL实际变为高为止。如果从机发起时钟延展,SCL线必须保持低状态(因为硬件上SCL是开漏输出),同样的,主机必须观察等待SCL线变为高状态

尽管理论上在通信过程的任何一位置都可以使用时钟延展,但是时钟延展通常使用在应答位前/后。例如,如果从机是一个MCU设备,它的IIC接口可以在每个字节后延展时钟,直到软件决定是否发出一个应答/不应答

时钟延展是唯一一次从机驱动SCL线(正常通信中由主机控制SCL)。很多从机不需要时钟延展,因此严格上可以认为SCL是一个没有电路驱动的输入。一些主机,如专门定制的ASICs很多都不支持时钟延展,通常这些设备叫做”两线式接口(two-wire interface)”而不是IIC

SDA仲裁时序

每个主机监控着总线的START(起始)和STOP(停止)位,当其他主机使得总线忙时不启动通讯。无论如何,两个主机有可能同时启动传输,在这种情况下产生仲裁。当一个主机寻址多个从机时,从机发送模式也能产生仲裁,但是这种情况不常见。与在发出重试之前使用随机回退延迟的协议(如以太网)不同,IIC有一个确定性的仲裁政策,每个发送方都检测SDA线电平并与预期的电平作比较,如果他们不匹配,则发送方失去仲裁并推出协议交互

如果一个发送方设置SDA状态位1(高),另一个发送方将SDA设置为0(低),此时SDA线为地状态。第一个发送方检测到总线的SDA电平与预期的不一样,可以得出另一个节点正在发送,第一个注意到这种情况的节点是仲裁失败的节点:它将停止驱动SDA,如果它是主机,它也停止驱动SCL并等待STOP(停止)信号,然后它可能尝试重新发送整个消息。在此期间,其他节点没有注意到SDA实际的电平与预期的不一样,因此可以继续传输。因为到目前为止,信号完全符合它的预期,没有其他发送方干扰它的信息

如果两个主机发送一个消息到不同的从机,发送较低从机地址的一方总是在地址阶段“赢得”仲裁。一旦两个主机可能发送信息给同一个地址从机,而地址有时涉及多个从机,那么仲裁必须继续进入到数据阶段

仲裁一般很少发生,但是对一些特定的多主机支持是必须的,就像时钟延展一样,不是所有设备都支持仲裁。如果支持,通常会贴上支持“多主机”通信的标签

一个主机可能会因为接收到的消息而失去仲裁,并且必须及时将其角色从主机变为从机,以确认其自己的地址

在极其罕见的情况下,两个主机同时发送相同的消息,双方都认为通信时成功的,但是总计只能识别到一个信息。因此,当一个从机可以被多个主机访问时,从机识别的每一条命令要么是幂等(都一样的意思)的,要么必须保证永远都不会是两个主机同一时间发出(比如,一个命令是由一个主机发出的就不是幂等,当某种互斥机制确保在任何给定的时间只能由一个主机发出的命令也不是幂等)

时序补充说明

除了START(起始)信号和STOP(停止)信号,SDA线只在SCL线为低状态时发生改变,传输一个数据位时,先保持SDA线稳定在所需的状态然后拉高SCL。当SCL处于低状态时,发送方(通常指主机)将SDA设置为所需的状态然后释放SCL,(一个小延迟后让值传播)让SCL变为高状态,主机等待SCL线自动变为高。SCL的上升时间有一定的延迟(取决上拉电阻的RC时间常数和总线的寄生电容),或者进入了从机的始终延展。一旦SCL变为高,主机将等待一个最小时间(主机是SCL的发送方),标准IIC速度为4μs,来确保该数据为被发送出去/被从机接收到,然后再次拉低SCL,这就是传输一个数据位的过程

在一个方向上每8个数据位之后,一个应答位由另一个方向发送,发送方和接收方因为这个应答为而交换角色,原本的接收方会发送一个信号”0”作为应答,如果发送的是“1”:

  • (如果是主机发送给从机)从机无法接收数据。没有这样的从机、命令无法解析或不能接收更多的数据
  • (如果是从机发送给主机)主机希望传输在这个数据之后停止

在SDA线状态在应答位期间发生改变(SCL线不变),SCL总是由主机控制。在应答位之后,SCL拉低,主机接下来可能会做下面三件事之一:

  • 开始传输另一字节数据:发送方设置SDA状态,然后把SCL状态变为高(释放SCL)
  • 发送一个STOP(停止)信号:设置SDA为低,把SCL状态变为高,然后让SDA为高,释放IIC总线
  • 发送一个重复STSRT(起始)信号,设置SDA为高,把SCL状态变为高,拉低SDA。这是重新开始一个IIC总线消息而不是释放总线

软件实现

本文的IIC软件实现部分采用STM32F103C8T6学习板作为主机,BM1383AGLV(大气压传感器)作为从机进行通信讲解,主要分析IIC相关部分,实际的传感器通信过程可以参考源码。需要注意的是,STM32F103C8T6的IO口配置开漏输出模式下,可以通过IDR寄存器读取IO口的电平
注:本次的例子为单主机&单从机的通信,在多主机或多从机通信中,本例子不适用

起始&停止时序软件实现

IIC的起始条件。总线空闲的情况下,SCL与SDA线都因为上拉电阻而处于高电平状态,在SCL处于高电平状态期间,SDA由高电平状态转为低电平状态时,IIC总线上产生一个起始信号,SDA与SCL的数据建立与保持需要一定的时间(根据通讯速度而定),软件实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @函数名 IIC_Start
* @功 能 IIC起始条件
* @参 数 无
* @返回值 IIC状态
*/
void IIC_Start(void)
{
IIC_SDA_HIGH; //拉高SDA线
IIC_Delay(); //延迟
IIC_SCL_HIGH; //拉高SCL线
IIC_Delay(); //延迟,重复起始建立时间,最小4.7μs
IIC_SDA_LOW; //SCL为高期间,SDA线由1->0,产生起始信号
IIC_Delay(); //延迟
IIC_SCL_LOW; //拉低SCL,准备进入数据传输阶段
IIC_Delay(); //延迟
}

IIC的停止条件。当总线通信数据传输完成后,需要一个停止条件来结束此次的通信,在SCL处于高电平状态期间,SDA由低电平状态转为高电平状态时,IIC总线上产生一个结束信号,SDA与SCL的数据建立与保持需要一定的时间(根据通讯速度而定),软件实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @函数名 IIC_Stop
* @功 能 IIC停止条件
* @参 数 无
* @返回值 无
*/
void IIC_Stop(void)
{
IIC_SCL_LOW; //拉低SCL
IIC_Delay(); //延迟
IIC_SDA_LOW; //拉低SDA线
IIC_Delay(); //延迟
IIC_SCL_HIGH; //拉高SCL线
IIC_Delay(); //延迟
IIC_SDA_HIGH; //SCL为高期间,SDA线由0->1,产生结束信号
IIC_Delay(); //延迟
}

注:通讯过程起始信号与结束信号都由通信主机发起

发送时序&应答检测软件实现

IIC总线主机发送数据过程中,高位线发送,SDA数据建立在SCL处于低状态,SDA数据保持在SCL处于高状态,SCL低状态(至少保持一定时间)到高状态(至少保持一定时间)为一个脉冲,接收方每接收一字节数据,需要发送一个应答信号给主机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* @函数名 IIC_WirteByte
* @功 能 IIC写一个字节
* @参 数 无
* @返回值 et_IIC_ERROR:IIC状态
*/
et_IIC_ERROR IIC_WirteByte(uint8_t TxByte)
{
et_IIC_ERROR error = IIC_NO_ERROR; //枚举变量记录IIC通信状态,初始为NO_ERROR
uint8_t mask; //定义变量

for(mask = 0x80; mask > 0; mssk >>= 1) //循环8次传输一字节数据,高位先发送
{
if((mask & TxByte) == 0) //根据位状态来控制SDA线
IIC_SDA_LOW;
else
IIC_SDA_HIGH;

IIC_Delay(); //延迟,数据保持稳定需要时间
IIC_SCL_HIGH; //拉高SCL线,传输SDA线上的数据
IIC_Delay(); //延迟,数据保持最少4μs,让接收方有充足时间读取
IIC_SCL_LOW; //拉低SCL,准备下一个bit传输
IIC_Delay(); //延迟,SDA数据建立需要时间
}

IIC_SDA_HIGH; //数据传输结束,释放SDA线
IIC_SCL_HIGH; //拉高SCL,产生一个脉冲,读取接收方发送的应答位
IIC_Delay(); //延迟,SDA数据建立需要时间

if(IIC_SDA_READ) //读取SDA应答位,如果SDA = 1,则接收方无应答
error = IIC_NACK_ERROR;

IIC_SCL_LOW; //拉低SCL,结束当前写数据通信
IIC_Delay(); //延迟

return error; //返回IIC状态
}

注:每次发送完,需要把SCL拉低以避免引起总线时序错误

读取时序&发送应答软件实现

IIC总线主机接收数据过程中,SCL线由主机控制产生脉冲进行读取数据,数据高位先读取,需要注意的是,在产生脉冲时SCL线需要一定的延迟来保证SDA上的数据建立完成和保持稳定(软件模拟需要),主机接收完一个字节后需要发送一个应答/不应答信号给从机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* @函数名 IIC_ReadByte
* @功 能 IIC读取一个字节
* @参 数 RxByte:存储读取的字节地址;ack:读取后是否发送应答
* @返回值 et_IIC_ERROR:IIC状态
*/
et_IIC_ERROR IIC_ReadByte(uint8 *RxByte, et_IIC_ACK ack)
{
et_IIC_ERROR error = IIC_NO_ERROR; //枚举变量记录IIC通信状态,初始为NO_ERROR
uint8_t mask; //定义变量
*RxByte = 0x00; //初始化RxByte地址上的数据为0x00

IIC_SDA_HIGH; //释放SDA
IIC_Delay(); //延迟
for(mask = 0x80; mask > 0; mask >>= 1) //循环8次读取一字节数据,高位先读取
{
IIC_SCL_HIGH; //拉高SCL,产生脉冲
IIC_Delay(); //延迟,SDA数据保持

#ifdef IIC_Clock_Stretching //如果IIC从机支持时钟延展,则检测时钟延展
error = IIC_Wait_ClockStreching();
IIC_Delay();
#endif

if(IIC_SDA_READ) //读取SDA上的数据位
*RxByte |= mask;
IIC_SCL_LOW; //拉低SCL,准备下一bit传输
IIC_Delay(); //延迟,SDA数据建立
}

if(ack == IIC_ACK) //如果需要发送应答,拉低SDA线发送应答信号
IIC_SDA_LOW;
else //否则拉高SDA线发送不应答信号
IIC_SDA_HIGH;
IIC_Delay(); //延迟,SDA数据建立

IIC_SCL_HIGH; //拉高SCL
IIC_Delay(); //延迟,SDA数据保持
IIC_SCL_LOW; //读取完毕,拉低SCL线
IIC_SDA_HIGH; //释放SDA线
IIC_Delay(); //延迟

return error; //返回IIC状态
}

注:每次发送完,需要把SCL拉低以避免引起总线时序错误

IIC的时钟延展&总线仲裁时序

IIC的时钟延展是由从机发起,在通信过程中,从机接收主机发送的命令后,由于还没有准备好数据或通信就会主动拉低SCL来告诉主机,此时主机应该等待SCL释放,当然从机拉低SCL应该由一个时限,不然通信就会一直阻塞在这里,主机也可以设一个超时时限
注:不是所有IIC从机都拥有时钟延展特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @函数名 IIC_Wait_ClockStreching
* @功 能 IIC等待时钟延展
* @参 数 无
* @返回值 IIC状态
*/
static et_IIC_ERROR IIC_Wait_ClockStreching(void)
{
et_IIC_ERROR error = IIC_NO_ERROR; //枚举变量记录IIC通信状态,初始为NO_ERROR
static uint8_t timeout = 100; //定义超时时间100*IIC_Delay(),视实际从机协议而定

while(IIC_SDA_READ == 0) //等待SCL释放
{
if((timeout--) == 0) //时钟延展超时
{
timeout = 100; //重新赋值
return IIC_TIMEOUT_ERROR; //返回IIC错误状态
}
IIC_Delay(); //延迟
}

return error; //返回IIC状态
}

IIC总线上如果存在多个主机,那么可能会出现SDA仲裁。当一个主机正在通信时,另一个主机想要控制IIC总线进行通信就会引发仲裁,SDA仲裁应用在多主机系统总,当一个主机想要发起通信,这时它应该检测SDA线是否在一个时间内处于高状态,如果是,表面IIC系统空闲,否则表面IIC系统中存在通信,系统忙。SDA仲裁比较少见,检测也比较简单

BM1383传感器数据读取

BM1383的一些基本的寄存器地址和数据的计算方式等细节可以参考传感器官方手册,这里根据手册提供的IIC时序通信,实现数据的读取,相关读写时序如下图

代码实现读写寄存器如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* @函数名 BM1383_WriteReg
* @功 能 BM1383写寄存器
* @参 数 Reg_Addr:寄存器地址;Reg_Data:写入寄存器的数据
* @返回值 et_IIC_ERROR:IIC通讯状态
*/
et_IIC_ERROR BM1383_WriteReg(uint8_t Reg_Addr, uint8_t Reg_Data)
{
et_IIC_ERROR error = IIC_NO_ERROR; //枚举变量记录IIC通信状态,初始为NO_ERROR

IIC_Start(); //起始信号
error = IIC_WirteByte(BM1383_ADDR + 0); //写从机地址+写模式
error = IIC_WirteByte(Reg_Addr); //写寄存器地址
error = IIC_WirteByte(Reg_Data); //往寄存器写入数据
IIC_Stop(); //停止信号

return error; //返回IIC状态
}


/**
* @函数名 BM1383_ReadReg
* @功 能 BM1383读寄存器
* @参 数 Reg_Addr:寄存器地址;*Reg_Data:读出寄存器数据存放的地址
* @返回值 et_IIC_ERROR:IIC通讯状态
*/
et_IIC_ERROR BM1383_ReadReg(uint8_t Reg_Addr, uint8_t *Reg_Data)
{
et_IIC_ERROR error = IIC_NO_ERROR; //枚举变量记录IIC通信状态,初始为NO_ERROR
uint8_t data;

IIC_Start(); //起始信号
error = IIC_WirteByte(BM1383_ADDR + 0); //写从机地址+写模式
error = IIC_WirteByte(Reg_Addr); //写寄存器地址

IIC_Start(); //重启IIC总线
error = IIC_WirteByte(BM1383_ADDR + 1); //写从机地址+读模式
error = IIC_ReadByte(&data, IIC_NACK); //读取一个字节+不应答
IIC_Stop(); //停止信号

*Reg_Data = data; //接收读取到的数据

return error; //返回IIC状态
}

读取BM1383的ID程序实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @函数名 BM1383_ReadID
* @功 能 BM1383读ID
* @参 数 Reg_Addr:寄存器地址;*ID_Data:存储BM1383的ID地址
* @返回值 et_IIC_ERROR:IIC通讯状态
*/
et_IIC_ERROR BM1383_ReadID(uint16_t *ID_Data)
{
et_IIC_ERROR error = IIC_NO_ERROR; //枚举变量记录IIC通信状态,初始为NO_ERROR
uint8_t bytes[2]; //定义临时数组,存储读取的数据


IIC_Start(); //起始信号
error = IIC_WirteByte(BM1383_ADDR + 0); //写从机地址+写模式
error = IIC_WirteByte(BM1383_ID1); //写寄存器地址

IIC_Start(); //重启IIC总线
error = IIC_WirteByte(BM1383_ADDR + 1); //写从机地址+读模式
error = IIC_ReadByte(&bytes[0], IIC_ACK); //读取第一个字节+应答
error = IIC_ReadByte(&bytes[1], IIC_NACK); //读取第二个字节+不应答
IIC_Stop(); //停止信号

*ID_Data = ((bytes[0] << 8) | bytes[1]); //合成数据

return error; //返回IIC状态
}

读取BM1383传感器大气压&温度数据程序实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* @函数名 BM1383_OneShot_ReadData
* @功 能 BM1383读数据,单次模式,时间30ms
* @参 数 *My_Data:存储数据地址
* @返回值 et_IIC_ERROR:IIC通讯状态
*/
et_IIC_ERROR BM1383_OneShot_ReadData(BM1383_Data *My_Data)
{
et_IIC_ERROR error = IIC_NO_ERROR; //枚举变量记录IIC通信状态,初始为NO_ERROR
uint8_t status; //定义临时变量
uint32_t pressure; //定义临时变量
uint16_t temperature; //定义临时变量

error = BM1383_WriteReg(BM1383_PowerDown, 0x01); //BM1383上电
Delay_ms(2); //延迟
error = BM1383_WriteReg(BM1383_Reset, 0x01); //BM1383复位
error = BM1383_WriteReg(BM1383_ModeControl, (0x18 | Time_30_60 | Mode_OneShot)); //配置BM1383,单词测量模式,时间30ms

while(DRDY_PORT->IDR & DRDY_PIN); //等待测量完成
error = BM1383_ReadReg(BM1383_Status, &status); //读取状态寄存器,清除RD_DRDY位

IIC_Start(); //起始信号
error = IIC_WirteByte(BM1383_ADDR + 0);
error = IIC_WirteByte(BM1383_PressureMsb); //写从机地址+写模式
IIC_Start(); //重启IIC总线
error = IIC_WirteByte(BM1383_ADDR + 1); //写从机地址+读模式
error = IIC_ReadByte(&My_Data->PressureMsb, IIC_ACK); //读取第一个字节+应答
error = IIC_ReadByte(&My_Data->PressureLsb1, IIC_ACK); //读取第二个字节+应答
error = IIC_ReadByte(&My_Data->PressureLsb2, IIC_ACK); //读取第三个字节+应答
error = IIC_ReadByte(&My_Data->TemperatureMsb, IIC_ACK); //读取第四个字节+应答
error = IIC_ReadByte(&My_Data->TemperatureLsb, IIC_NACK); //读取第五个字节+不应答
IIC_Stop(); //停止信号

//合成数据
pressure = My_Data->PressureMsb;
pressure = ((pressure << 8) | My_Data->PressureLsb1);
pressure = ((pressure << 6) | (My_Data->PressureLsb2 >> 2));
My_Data->Pressure_Value = pressure >> 11;

temperature = My_Data->TemperatureMsb;
temperature = (temperature << 8 | My_Data->TemperatureLsb);
My_Data->Temperature_Value = temperature >> 5;

return error; //返回IIC状态
}

最后,在主函数中实现读取BM1383的ID并打印,然后大约每500ms读取一次大气压&温度数据并打印,程序效果如下

BM1383手册下载连接:http://02.xiaolinjun.top/bm1383aglv-e.pdf
例程地址:https://github.com/LGG001/MyApplication_Program/tree/master/Sensor

本文标题:嵌入式硬件通信接口-IIC

文章作者:LGG001

发布时间:2019年02月19日 - 23:02

最后更新:2019年03月19日 - 23:03

原始链接:http://yoursite.com/2019/02/19/嵌入式硬件通信接口-IIC/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------本文结束感谢您的阅读-------------
Thank You For Your Approval !