目录
[一. I2C 简介](#一. I2C 简介)
[1.1 I2C 介绍](#1.1 I2C 介绍)
[二. I2C 基本时序与基本知识](#二. I2C 基本时序与基本知识)
[2.1 起始位](#2.1 起始位)
[2.2 停止位](#2.2 停止位)
[2.3 数据传输](#2.3 数据传输)
[2.4 应答信号 ACK](#2.4 应答信号 ACK)
[2.5 对从机地址的规定](#2.5 对从机地址的规定)
[三. I2C 主机与从机之间的通信时序](#三. I2C 主机与从机之间的通信时序)
[3.1 写一个字节时序](#3.1 写一个字节时序)
[3.2 读一个字节时序](#3.2 读一个字节时序)
[3.3 写多个字节时序](#3.3 写多个字节时序)
[3.4 读多个字节时序](#3.4 读多个字节时序)
[四. IMX6ULL 中与 I2C 相关的寄存器](#四. IMX6ULL 中与 I2C 相关的寄存器)
[4.1 I2Cx_IFDR 分频器寄存器](#4.1 I2Cx_IFDR 分频器寄存器)
[4.2 I2Cx_I2CR I2C控制寄存器](#4.2 I2Cx_I2CR I2C控制寄存器)
[4.3 I2Cx_I2SR I2C状态寄存器](#4.3 I2Cx_I2SR I2C状态寄存器)
[4.4 I2Cx_I2DR 数据输入输出寄存器](#4.4 I2Cx_I2DR 数据输入输出寄存器)
[五. 使用I2C协议与AT24C02进行通信](#五. 使用I2C协议与AT24C02进行通信)
[5.1 I2C 初始化代码](#5.1 I2C 初始化代码)
[5.2 I2C 主机写AT24C02示例代码](#5.2 I2C 主机写AT24C02示例代码)
[5.3 I2C主机读AT24C02示例代码](#5.3 I2C主机读AT24C02示例代码)
[六. 使用I2C协议与 LM75A 进行通信](#六. 使用I2C协议与 LM75A 进行通信)
[6.1 LM75A 芯片简介](#6.1 LM75A 芯片简介)
[6.2 LM75A 的主要参数](#6.2 LM75A 的主要参数)
[6.3 LM75A 的设备地址](#6.3 LM75A 的设备地址)
[6.4 LM75A 的寄存器地址](#6.4 LM75A 的寄存器地址)
[6.5 LM75A 的时序](#6.5 LM75A 的时序)
[6.6 读取温度示例代码](#6.6 读取温度示例代码)
I2C 是最常用的通信接口,众多的传感器都会提供 I2C 接口来和主控相连,比如陀螺仪、 加速度计、触摸屏、温度传感器等等。所以 I2C 是必须掌握的
一. I2C 简介
串行,同步,半双工
1.1 I2C 介绍
IIC(Inter-Integrated Circuit )又称I2C ,是是 IICBus 简称,所以中文应该叫集成电路总线。是飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展而来的一种同步串行半双工通信总线方式。该总线允许同时连接多个设备(芯片)。每块芯片在总线上拥有特定的地址
I2C 是很常见的一种总线协议,I2C 是 NXP 公司设计的,I2C 使用两条线在主机和从机之间进行数据通信。一条是 SCL(Serial Clock)(串行时钟线) ,另外一条是 SDA(Serial Data )(串行数据线) ,这两条数据线需要接上拉电阻,目的是为了总线空闲的时候 SCL 和 SDA 处于高电平。
I2C 总线标准模式 下速度可以达到 100Kbit/s ,快速模式 下可以达到 400Kbis/s

I2C 是支持多从机的,也就是一个 I2C 控制器下可以挂多个 I2C 从设备,这些不同的 I2C 从设备有不同的器件地址,这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C 设备了,一个 I2C 总线连接多个 I2C 设备如图所示

图中 SDA 和 SCL 这两根线必须要接一个上拉电阻,范围是 4.7K - 10K 之间,一般是 4.7K。其余的 I2C 从 器件都挂接到 SDA 和 SCL 这两根线上,这样就可以通过 SDA 和 SCL 这两根线来访问多个 I2C 设备
二. I2C 基本时序与基本知识
2.1 起始位
也就是 I2C 通信起始标志,通过这个起始位就可以告诉 I2C 从机,要开始进行 I2C 通信了。在 SCL 为高电平的时候,SDA 出现下降沿就表示为起始位

2.2 停止位
停止位就是停止 I2C 通信的标志位,和起始位的功能相反。在 SCL 位高电平的时候,SDA 出现上升沿就表示为停止位

2.3 数据传输
发送数据:MSB 高位先行原则
规定:当时钟线 SCL 高电平时,数据线 SDA 要保持稳定,因为 I2C 接收方要采样数据;当时钟线 SCL 为低电平时,数据线 SDA 信号可改变,I2C发送方发送数据


2.4 应答信号 ACK
当 I2C 主机发送完 8 位数据以后会将数据线 SDA 设置为输入状态 ,等待 I2C 从机应答 ACK,也就是等到 I2C 从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完 8 位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信号,表示通信成功,否则表示通信失败

2.5 对从机地址的规定
从机地址:
起始信号后的第一个字节,发送即为从机地址,总共八位
2^8 = 256 --- 可得 I2C 总线上可挂载 256 个从机
但是规定:前七个bit作为从机地址,在2^7 = 128 --- 总共可挂载 128 个从机
最后一个 bit 位来表示数据的流向位
0: 主机发给从机,低电平,写
**1:**从机发给主机,高电平,读
总结:
七位从机地址最多可挂载2^7 = 128个从机,但是I2C会空留出一个,即127个从机,
九位从机地址:最多2^9 - 1 个从机
三. I2C 主机与从机之间的通信时序
此节以 AT24C02 (EEPROM) 芯片为例,来说明主机与从机使用 I2C 总线进行通信的时序
关于 AT24C0x 系列芯片的说明**:**
AT24C02A、AT24C04A、AT24C08A 这些芯片都是 EEPROM
AT24C02A:容量:2K (256 x 8),注意这里的 2K 表示的是 bit,而不是字节, 字节 = 2 K / 8 = 256 字节
AT24C04A:容量:4K (512 x 8)
AT24C08A:容量:8K (1024 x 8)
芯片图:

设备地址:
此芯片的 A2 - A0 默认接地,故芯片地址为:1010 0000 = 0xA0

设备的寄存器地址:
由于使用的是 AT24C02A ,容量为 256 字节,所以内部的寄存器地址为:0x00 - 0xFF
可以对其进行单字节的写操作,也可以对其进行 页 的写操作,AT24C02A一页 八字节
3.1 写一个字节时序
主机通过 I2C 总线与从机之间进行通信的写时序:

1)、**开始信号:**主机发送起始信号,通信时,起始信号总是由主机发起
2)、发送 I2C 设备地址: 每个 I2C 器件都有一个设备地址,通过发送具体的设备地址来决 定访问哪个 I2C 器件。这是一个 8 位的数据,其中高 7 位是设备地址,最后 1 位是读写位,为 1 的话表示这是一个读操作,为 0 的话表示这是一个写操作。也就是低电平写,高电平读
3)、 **读写位:**I2C 器件地址后面跟着一个读写位,为 0 表示写操作,为 1 表示读操作
4)、ACK 应答: 从机向主机回复 ACK 应答信号
5)、**寄存器地址:**主机向从机发送要写入数据的寄存器地址
6)、ACK 应答: 从机向主机回复 ACK 应答信号
7)、主机向从机发送要写入寄存器的数据
8)、ACK 应答: 从机向主机回复 ACK 应答信号
9)、**结束标志:**主机发送 STOP 结束标志
3.2 读一个字节时序
主机通过 I2C 总线与从机之间进行通信的读时序:

I2C 单字节读时序分为 4 大步,第一步是发送设备地址,第二步是发送要读取的寄存器地址,第三步重新发送设备地址,最后一步就是 I2C 从器件输出要读取的寄存器值。
1)、主机发送起始信号
2)、主机发送要读取的 I2C 从设备地址 + 一位写位,低电平。
3)、读写控制位,因为是向 I2C 从设备发送数据,因此是写信号---注意第一次发送的是写信号,因为要先发送一次寄存器地址
4)、从机向主机发送的 ACK 应答信号。
5)、主机发送要读取的寄存器地址。
6)、从机向主机发送的 ACK 应答信号。
7)、主机重新发送 START 信号 --- 注意这里主机会重新发送一次起始信号
8)、重新发送要读取的 I2C 从机设备地址 + 一位读位,高电平
9)、读写控制位,这里是读信号,表示接下来是从 I2C 从设备里面读取数据。
10)、从机向主机发送的 ACK 应答信号。
11)、主机从 I2C 从机器件里面读取数据。
12)、主机向从机回复 NO ACK 信号,表示读取完成,不需要从机再发送数据了。
13)、主机发出 STOP 信号,停止 I2C 通信
3.3 写多个字节时序

I2C 多字节写时序与单字节的写时序差不多,分为 四大步,第一步是发送设备地址与写位并等待从机的ACK应答,第二步是发送要读取的寄存器地址并等待ACK应答,第三步就是主机不断的向从机发送要写的数据并且等待从机的ACK应答,最后主机向从机发送停止位 STOP 即可
3.4 读多个字节时序

I2C 多字节读时序与单字节读时序也差不多,分为 五大步:
第一步是发送7位的设备地址 + 一位写位,低电平,并且等待从机的 ACK 应答;
第二步是主机向从机发送要读取的寄存器地址,并且等待从机的 ACK 应答;
第三步重新发送起始位,并且重新发送7位的设备地址 + 一位读位,并且等待从机的 ACK 应答;
第四步就是从机不断的向主机发送要读取的数据,主机不断的向从机回复 ACK 应答
第五步就是主机向从机回复 NO ACK 信号(高电平),表示读取完成,不需要从机再发送 ACK 信号了,并且主机发送一个结束位 STOP
四. IMX6ULL 中与 I2C 相关的寄存器
IMX6ULL 总共有 4 个 I2C 控制器
IMX6ULL 提供了 4 个 I2C 外设,通过这四个 I2C 外设即可完成与 I2C 从器件进行通信,IMX6ULL 的 I2C 外设特性如下:
①、与标准 I2C 总线兼容。
②、多主机运行
③、软件可编程的 64 中不同的串行时钟序列。
④、软件可选择的应答位。
⑤、开始/结束信号生成和检测。
⑥、重复开始信号生成。
⑦、确认位生成。
⑧、总线忙检测
4.1 I2Cx_IFDR 分频器寄存器
寄存器 I2Cx_IFDR 只有 IC(bit5:0) 这个位,用来设置 I2C 的波特率,I2C 的时钟源可以选择 IPG_CLK_ROOT = 66MHz,通过设置 IC 位既可以得到想要的 I2C 波特率

IC 位可选的设置 如图所示:
不像其他外设的分频设置一样可以随意设置,图中列出了 IC 的所有可选值。比如现在 I2C 的时钟源为 66MHz,要设置 I2C 的波特率为 100KHz(标准模式) ,那么 IC 就可以设置为 0x15,也就是 640 分频。66000000/640=103.125KHz≈100KHz

4.2 I2Cx_I2CR I2C控制寄存器
|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| IEN(bit7) | I2C 使能位 1 :使能 I2C 0 :关闭 I2C 设置波特率之前最好将该位清零,设置好波特率之后再置1 |
| IIEN(bit6) | I2C 中断使能位 1 :使能 I2C 中断 0 :关闭 I2C 中断 |
| MSTA(bit5) | Master/Slave mode select bit 主从模式选择位,设置 IIC 工作在主模式还是从模式 1 :工作在主模式 0 :工作在从模式 注意:作为主机时启动通信前将此位置1,之后会发送一个起始位。在发送过程中向此位 清零,会立即产生一个停止位 |
| MTX(bit4) | Transmit/Receive mode select bit 传输方向选择位,用来设置是进行发送还是接收 0 :接收 1 :发送 注意:发送从机地址时,此位总是1,接收数据时此位为0,修改此位并不会对数据流向位带来任何影响 |
| TXAK(bit3) ????? | Transmit acknowledge enable 传输应答位使能 0 :发送 ACK 信号 1 :发送 NO ACK 信号 关于这一位有两种说法: -----------------------说法一------------------------ 两种情况,主机作为接收方 0:发送ACK---主机还要继续接收,请从机继续发送下一个字节 1:发送NO ACK---主机不再接收,传输结束 主机作为发送方时此位不生效 -----------------------说法二------------------------ 在发送数据时表明是否检测应答,接收数据时表明是否回应应答 主机作为接收方 0:发送ACK 1:发送 NO ACK 主机作为发送方 0:检测从机的ACK---注意这里只是检测,并不阻塞 1:检测从机的NO ACK |
| RSTA(bit2) | Repeat start 重复开始信号,为 1 的话产生一个重新开始信号 此位用来重发起始信号 |

4.3 I2Cx_I2SR I2C状态寄存器
|----------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| ICF(bit7) | Data transferring bit 数据传输状态位 0:表示数据正在传输 1:表示数据传输完成 用于判断数据是否正在传输,只读 用法: 等待之前的数据传输完成 while( (I2C1->I2SR & (1 << ICF)) == 0 ); |
| IAAS(bit6) | 1:表示 I2C 地址,也就是 I2Cx_IADR 寄存器中的地址是从设备地址 0:表示没有地址 只读 |
| IBB(bit5) | I2C bus busy bit I2C 总线忙标志位 0 :表示 I2C 总线空闲 1 :表示 I2C 总线忙 注意:发送完停止位以后该位清零,启动传输前根据此位判断总线是否空闲 完全由硬件自动置 1、自动清零,并且是只读位 |
| IAL(bit4) | Arbitration lost 仲裁丢失位 1:表示发生仲裁丢失----一般用在多主机情况下 0:无仲裁丢失 注意:在启动通信前,需要将 IAL 位清零 |
| SRW(bit2) | Slave read/write 从机读写状态位 当 I2C 作为从机的时候使用,此位用来表明主机发送给从机 的是读还是写命令 0: 表示主机要向从机写数据 1: 表示主机要从从机读取数据 |
| IIF(bit1) | I2C interrupt I2C 中断挂起标志位 1 :表示有中断挂起 0:无中断挂起 此位需要软件清零 注意:在启动通信前,需要将 IIF 位清零 此位在通信过程中十分重要,无论接收还是发送数据前都需要将此位清零,之后开始传输数据,直到此位置位。 |
| RXAK(bit0) | Received acknowledge 应答信号标志位 0 :表示接收到 ACK 应答信号 1 :表示检 测到 NO ACK 信号 |

4.4 I2Cx_I2DR 数据输入输出寄存器
|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| DATA | 发送时无论发送设备地址还是数据,都将要发生的内容写到这个寄存器中,当要发送的数据写入后I2C控制器就开始传输数据,传输完毕后(收到ACK或NACK)就会使IIF(12Cx_I2SRbit1)置位 对于接收数据来说,当从该寄存器读走一个字节后 ,I2C 控制器就开始传输数据,直到IIF置位,因此在读取过程中,第一个读到的字节是无效的 |

五. 使用I2C协议与AT24C02进行通信
5.1 I2C 初始化代码
IMX6ULL中与 I2C 相关的原理图:


1、可以看到 I2C1 的 SCL 与 SDL 分别接在了核心板的 UART4_TXD 与 UART4_RXD 上,故在初始化时要对这两个引脚进行初始化
2、注意:可以看到,在引脚复用时,第二个参数传了 "1" ,要求强制打开引脚的硬件输入通路,SION = 1
|------------|---------------------------------------------------|
| SION=0 | PAD 输入通路关闭;I2C 外设无法采样外部真实引脚电平 |
| SION=1 | 强制打开 PAD 输入通路;I2C 控制器可以采集总线电平,识别 ACK、仲裁、起始 / 停止信号 |
原因:I2C 控制器硬件必须时刻采样引脚上真实物理电平,例如:主机发送完 8bit 数据,释放 SDA,等待从机拉低 SDA 产生 ACK 应答。 此时芯片内部 I2C 外设需要读取外部 PAD 电平,判断从机有没有回复应答
cpp
IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1);
IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);
3、在配置引脚的电气属性时,要设置引脚上拉电阻

4、在设置 I2C 的波特率时,一定要先失能,设置好波特率后,再使能
5、示例代码如下:
cpp
//初始化I2C
void i2c_init(void)
{
//1、配置引脚复用
IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1); //UART4_RX引脚复用为I2C的数据引脚
IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1); //UART4_TX引脚复用为I2C的时钟引脚
//2、配置引脚电器属性
IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0xF0B0); //I2C一定要加上拉电阻,保证空闲时两条线的电平为高电平
IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0xF0B0);
//3、设置波特率 :100KHz
I2C1->I2CR &= ~(1 << 7); //先失能
I2C1->IFDR = 0x15; //设置640分频,接近660分频即可
I2C1->I2CR |= (1 << 7); //使能
}
5.2 I2C 主机写AT24C02示例代码
1、流程图:

2、示例代码:
cpp
//I2C写函数
void i2c_write(I2C_Type *base, unsigned char dev_address, unsigned char reg_address, const unsigned char *data, int len)
{
//1、清除仲裁标志位
base->I2SR &= ~(1 << IAL);
//2、清除中断标志位
base->I2SR &= ~(1 << IIF);
//3、等待之前的数据传输完成
while( (base->I2SR & (1 << ICF)) == 0 );
//4、主机通信前置位此位,发送一个起始位
base->I2CR |= (1 << MSTA);
//5、发送从机地址时,此为总是置1;发送写位
base->I2CR |= (1 << MTX);
//6、等待从机ACK信号
base->I2CR &= ~(1 << TXAK); //只用了这一次来等待从机的 ACK ??? 当主机作为发送方时此为不生效
//7、清零 IIF 位
base->I2SR &= ~(1 << IIF);
//8、发送从机地址
base->I2DR = dev_address << 1;
//9、等待从机回复 ACK
while( (base->I2SR & (1 << IIF)) == 0 );
//10、清零 IIF 位
base->I2SR &= ~(1 << IIF);
//11、发送寄存器地址
base->I2DR = reg_address;
//12、等待从机回复 ACK
while( (base->I2SR & (1 << IIF)) == 0 );
//13、发送 len 字节数据
while(len--)
{
//清零 IIF 位
base->I2SR &= ~(1 << IIF);
base->I2DR = *data++;
//等待从机回复 ACK
while( (base->I2SR & (1 << IIF)) == 0 );
}
//14、发送结束标志 STOP
base->I2CR &= ~(1 << MSTA);
//15、等待总线空闲
while( (base->I2SR & (1 << IBB)) != 0 )
{
delay_us(100);
}
}
5.3 I2C主机读AT24C02示例代码
1、流程图:

2、示例代码:
注意:
1、这里主机读取从机数据前要进行假读一次,原因是读写时共用一个I2DR,里面可能会有上一次写的残留数据
2、在发送从机地址时,若从机地址是 0xA0 ,则在传参时需要传递 0x50 ,因为从机地址是七位,还有一位读写位的影响,从机地址在函数中需要进行左移,故要注意传参
cpp
//I2C 读函数
void i2c_read(I2C_Type *base, unsigned char dev_address, unsigned char reg_address, unsigned char *data, int len)
{
//1、清除仲裁标志位,此位一般多主机使用
base->I2SR &= ~(1 << IAL);
//2、清除中断标志位
base->I2SR &= ~(1 << IIF);
//3、等待之前的数据传输完成
while( (base->I2SR & (1 << ICF)) == 0 );
//4、主机通信前置位此位,发送一个起始位
base->I2CR |= (1 << MSTA);
//5、主机模式:写模式
base->I2CR |= (1 << MTX);
//6、等待从机ACK信号,表示去检测从机的 ACK 应答
base->I2CR &= ~(1 << TXAK); //只用了这一次来等待从机的 ACK ??? 当主机作为发送方时此为不生效
//7、清零 IIF 位
base->I2SR &= ~(1 << IIF);
//8、发送从机地址 + 一位写位
base->I2DR = dev_address << 1;
//9、等待从机回复 ACK
while( (base->I2SR & (1 << IIF)) == 0 );
//10、清零 IIF 位,不管是接收到 ACK,还是发送 ACK 都会引起 IIF 位置位,故要清除此位
base->I2SR &= ~(1 << IIF);
//11、发送寄存器地址
base->I2DR = reg_address;
//12、等待从机回复 ACK
while( (base->I2SR & (1 << IIF)) == 0 );
//13、主机重新发送起始位
base->I2CR |= (1 << RSTA);
//14、清零 IIF 位
base->I2SR &= ~(1 << IIF);
//15、发送从机地址 + 一位读位
base->I2DR = dev_address << 1 | 1;
//16、等待从机回复 ACK
while( (base->I2SR & (1 << IIF)) == 0 );
//17、改变主机模式到:读模式,接收数据
base->I2CR &= ~(1 << MTX);
//18、清零 IIF 位
base->I2SR &= ~(1 << IIF);
//19、判断要读取的数据长度是否是1,Y:发送 NO ACK
if(len == 1)
{
//置位 TXAK ,发送 NO ACK
base->I2CR |= (1 << TXAK);
}
//20、假读一次,读出上一次写进的残留数据
*data = base->I2DR;
//21、循环读取
while(len-- != 0)
{
//等待主机回复 ACK,表示主机读取到一字节数据
while( (base->I2SR & (1 << IIF)) == 0 );
//清零 IIF 位
base->I2SR &= ~(1 << IIF);
if(len == 0)
{
//清零 MSTA:发送停止位
base->I2CR &= ~(1 << MSTA);
//清零 TXAK:以后通信等待的是 ACK。修改为 ACK
base->I2CR &= ~(1 << TXAK);
//等待总线空闲
while( (base->I2SR & (1 << IBB)) != 0 )
{
delay_us(100);
}
}
else if(len == 1)
{
//表示是最后一个字节数据
//置位 TXAK:发送NO ACK
base->I2CR |= (1 << TXAK);
}
//读取数据
*data++ = base->I2DR;
}
}
六. 使用I2C协议与 LM75A 进行通信
6.1 LM75A 芯片简介
LM75A是一款数字温度传感器
采集的温度格式是双字节,并且低七位无效,如下图所示:

芯片引脚定义如下图所示:


6.2 LM75A 的主要参数
传感器的参数
工作电压: 2.7 - 5.5v之间
故在与 IMX6ULL 进行通信时接3.3 v即可
精度:
25 - 100℃:±2℃
55 - 125℃:±3℃
测量范围:
在精度是±2℃下:-25 - 100℃
在精度是±3℃下:-55 - 125℃
分辨率:
0.5 ℃
最高位表示符号位

6.3 LM75A 的设备地址
芯片手册中对 LM75A 从机地址做了如下描述:
LM75A 在 I2C 总线上作为从设备运行,因此 SCL 线是输入端(LM75A 不会生成时钟信号),而 SDA 线则是双向串行数据通路。根据 I2C 总线规范,LM75A 具有 7 位的从设备地址。从机地址的四个最重要的位是直接固化在 LM75A 中的,其值为"1001"。地址的三个最不重要的位分配给了引脚 A2 至 A0,并通过将这些引脚连接到地(低电平,0)或连接到 +VS(高电平,1)来设置。
本次示例此芯片的地址为:1001 000 ,在传参时:0x48 << 1 即可

6.4 LM75A 的寄存器地址
在芯片手册中:
可知,温度寄存器的地址是0x00,并且是只读

温度寄存器:

产品ID寄存器:

6.5 LM75A 的时序
就是 I2C 的读时序

6.6 读取温度示例代码
注意:
1、这里进行了浮点数的运行,故在启动代码中需要增加使能FPU单元的函数,不然无法进行浮点数的运算,示例代码如下:
cpp
_enable_fpu:
// 1. 设置CPACR寄存器使能FPU访问
mrc p15, 0, r0, c1, c0, 2 // 读取CPACR
orr r0, r0, #(0xF << 20) // 设置CP10和CP11为完全访问
mcr p15, 0, r0, c1, c0, 2 // 写回CPACR
// 2. 使能FPU
mov r0, #0x40000000 // 设置FPEXC的EN位
vmsr fpexc, r0 // 写入FPEXC
// 3. 配置FPSCR
mov r0, #0x00000000 // 清除所有标志位
vmsr fpscr, r0 // 写入FPSCR
bx lr // 返回
2、在移植的 stdio 中,功能不全,无法使用 "%f" 进行打印小数 ,故这里要使用**"定点数"**的方法来打印小数温度
cpp
//温度传感器采集数据
void get_lm75_temperature(void)
{
unsigned char buff[2] = { 0 };
float f = 0;
short s = 0;
int k = 0, n = 0, m = 0;
i2c_read(I2C1, 0x48, 0, buff, 2);
//MSB 原则,先发高位后发低位
s |= buff[0] << 8;
s |= buff[1];
//LM75芯片规定:Temperature Register 的第七位为无效数据,故要右移七位
s >>= 7;
//乘分辨率0.5,计算出最终温度
f = 0.5 * s;
//移植的stdio库功能不全,无法打印浮点数,故采用定点数的方法打印
k = f * 10;
n = k / 10; //小数点左边的数
m = k % 10; //小数点右边的数
//输出
printf("temperature is %d.%d\n",n,m);
}
结果:
