温度传感器、重力传感器等等这些传感器模块其实在市面上很多设备都有广泛的应用,可能大家平时没有留心去观察。举个例子我们经常用的平板电脑或者电子书阅读器都有“自动旋转”的功能,这里面其实就是用了重力传感器,硬件上重力传感器去检测当前用户拿设备的姿势,并传入CPU一个信号量,软件上再根据该信号去进行屏幕的自动旋转。温度传感器也同样常用,现在太多的嵌入式产品都有温度显示的功能,这里面毫无疑问都用了温度传感器在实时采集当前设备的温度,典型的就有:LM75和DS18B20,它们是实际应用最多的两种温度传感器。
Artix7开发板上配有LM75温度传感器,同时LM75也是一款支持IIC通讯的外设,通过不断读LM75的当前采样温度,我们也实际去动手实践一次IIC时序逻辑的Verilog实现,IIC通讯协议在实际工作中也很常用,比如下一例程的Eeprom也是典型的IIC通讯,相信大家在学习实践过本例程后一定可以触类旁通,举一反三地去完成下一个例程的代码编写,明白了通讯原理和实现方法,再在实际工作后遇到调试IIC通讯的其他外设也是应用自如、得心应手了。
IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备,是半双工通信方式,所以程序设计上我们要把sda做成inout信号。IIC 标准速率为 100kbit/s,快速模式 400kbit/s,由数据线 SDA 和时钟 SCL 构成串行总线;每个模块都有唯一的地址作为标示。
IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了主从设备互联的成本。IIC总线的另一个优点是,支持多机通讯,支持多主控模块,但同一时刻只允许有一个主控,并且任何能够进行发送和接收的设备都可以成为主总线,IIC总线通信示意图如图1所示。
图1 IIC总线通信示意图
1.主机发送起始信号启用总线
2.主机发送一个字节数据指明从机地址和后续字节的传送方向
3.被寻址的从机发送应答信号回应主机
4.发送器发送一个字节数据
5.接收器发送应答信号回应发送器
… … (循环步骤4、5)
n.通信完成后主机发送停止信号释放总线
IIC总线上传送的数据是广义的,既包括地址,又包括真正的数据。主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,'0'表示主机发送数据,'1'表示主机接收数据;总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定为发送器或接收器。
图2 IIC总线地址传输示意图
如图3所示是LM75的硬件电路图,图4所示截图至LM75的芯片手册,这里详细描述了如何对LM75模块进行读操作读取当前采集的实时温度,对于初次接触IIC的同学们,开始可能看起来比较晦涩,因为IIC由于其通信协议的设置就决定了本身为半双工的通讯,i2c_sda传输数据,某一个时刻只能作为输入或者输出,同时该协议支持一个主机在同一总线上挂不同地址的IIC外设,且每次传输完数据后都需要有确认位,确认数据是否接受正常,通常0表示正常,1表示异常。根据芯片手册,我们可以把读LM75的流程具体总结如下:
图3 开发板Artix7上LM75电路
图4 LM75读当前采集温度的IIC时序逻辑图
读LM75流程:
1. Master发送I2C addr(8bit),等待ACK
2. Slave发送ACK
3. Slave发送data1(8bit),即寄存器里的值
4. Master发送ACK
5. Slave发送data2(8bit),即寄存器里的值
6. Master发送NACK
7. 结束本次对LM75的读写
本例程具有很好的工程应用价值,设计用按键触发读取LM75当前温度数值,key_scan模块会产生lm75_en信号送至下游模块i2c_lm75,在i2c_lm75模块收到发送来的lm75_en信号即开始工作,读取当前温度传感器所采集到的温度值,这里请注意该温度值为一个16位的数据,需要FPGA从LM75连续读两次,第一次是读MSB数据(Most Significant Data Byte),第二次是读LSB数据(Least Significant Data Byte)。然后完成读取16位温度数据后,需要把i2c_lm75读取的数据lm75_dout信号和数据发送lm75_dout_vld信号一起传到下游的lm75bcd_ascii模块。
i2c_lm75模块主要是结合IIC的通信原理和LM75芯片手册,本模块在没有接收到上游模块的开始信号之前,一直处于IDLE状态,再收到了开始lm75_en信号后,IDLE状态跳转到START状态,同时把i2c_sda拉低(i2c_sda默认为高电平),进入SEND_DEVICE_RDINFO状态,由FPGA向LM75发送8位的地址信号“1001|0001”,这里简单说明下高四位固定为“1001”,如上图3所示,后四位是“A2A1A0”和“R/W”组成,因为PCB电路板上把A2、A1、A0引脚接地了,默认为低电平,而且读操作为1,写操作为0,所以后四位就为“0001”,当主机发送完一字节的地址信号后,需要进入WAIT_DEVICE_RDACK状态,这时i2c_sda变为输入信号,当FPGA接收到0后,表示LM75通讯正常,进入下一个状态,如果收到1则代表通讯异常跳到IDLE状态,当进入GET_READ_DATA状态后,LM75会向FPGA发送第一个字节的温度寄存器的值,FPGA收到后进入SEND_RDDATA_ACK状态,接收完毕该字节的数据后通过i2c_sda发送0,重新进入GET_READ_DATA状态,再次接收到LM75第二个字节的温度寄存器的值,FPGA接收完毕后通过i2c_sda发送1,进入STOP状态结束本次读LM75传感器操作,整个状态机的逻辑如图5所示,由于代码300行比较长,所以在博客里就不再全部粘贴了只切选代码片段,工程整理好后下周会给出百度网盘下载链接,大家可以结合代码再思考下。
图5 LM75读温度寄存器数据的状态机
通过这个模块,想在这里说明下FPGA中对三态门的设计,比如IIC,MDIO等等接口,对于FPGA来说是inout信号,即为输出也为输入,常用的设计办法就是用dir信号区分,dir信号为高对于FPGA表示输出,dir信号为低对于FPGA表示输入,如下图6所示,通过i2c_sda_dir信号控制i2c_sda_out和i2c_sda_in信号作为FPGA的输出和输入,在连接到外围器件的i2c_sda上,IIC的时序逻辑设计如图7所示。
图6 FPGA控制IIC总线的三态门示意图
图7 i2c_lm75模块FPGA控制IIC总线的三态门代码设计
在这里我们选取250Khz作为IIC的时钟,实战项目工程中,IIC的时钟上限随着外设的变化而变化,笔者也见过datasheet上标注最快速度为1Mhz,但是实际往往达不到,而250Khz在项目中完全可以胜任各种IIC时钟的设计要求,这里取TIM_SCLK为4us即250Khz时钟,而TIM_SETUP建立时间为320ns,TIM_HOLD保持时间为3.2us,也都是一个前人积累的经验值,有朋友用示波器实际测试过IIC的波形,如图8和9所示,分别是 i2c_lm75模块下读取LM75内2字节数据的时序设计,以及整体的状态机跳转设计。
图8 i2c_lm75模块的读取LM75温度数据输出下游模块代码设计
图9 i2c_lm75模块状态机跳转的代码设计
如图10所示是所读到温度寄存器上各位数据的含义,我们可以清楚地看到,虽然从i2c_lm75模块发的是16位宽的数据,但是LSB上后5位数据是无效的,在计算温度的时候,我们需要将MSB上的8位数据作为整数位转换为十进制数,把LSB的高三位作为小数位乘以125,比如从i2c_lm75模块读到LM75的温度寄存器的当前值为1EA0,这里都换算成二进制数即为:0001|1100|1010|0000,去除后5位即11位为000|1110|0101,再提取前8位为温度的整数位:0001|1110为1E,转化为10进制即为30,后三位为101转为10进制为5,乘以125,则代表0.625度,合到一起即为当前温度为30.625度。
图10 温度寄存器中各位数据的含义
在lm75bcd_ascii模块,就需要首先把上游i2c_lm75模块送到的16位温度寄存器读到的值转为实际的温度值,这个过程如刚才所说,比如从上游模块读到了1EA0,要把这个1EA0计算转为温度30.625度,当然仅仅做到这一步是不够的,我们还需要把这个30.625度送到串口调试助手上打印出来,这就需要把30.625度的各个位的数据连同正负号,小数点,空格符转化为ASCII进行打印,ASCII码与十进制数的转换表如图11所示,可以查表看到正负号的ASCII码为十进制的43和45,即为十六进制的2B和2D;空格符的ASCII码为十六进制的0A,小数点的ASCII码为十六进制的2E,同时将十六进制的数转为其对应的ASCII码值,如果是0-9需要加48,如果是A-F则需要加55,大家查表即可清楚地看到。在这里我们也把lm75bcd_ascii模块最后的72位ASCII码数据依次存入一个读写位宽均为8位的FIFO中,这里因为本身采样温度小数点前有三位,小数点后又有三位,加上正负号,小数点和换行符一共一次性需要向串口发送9个ASCII码。
图11 ASCII码与十进制数的转换表
如图12所示,这里先把LM75中读到的16位数据通过上面所说的方法转换成BCD码,再把BCD码转换成ASCII码,再通过位拼接的方法把9字节数据拼接起来写入FIFO中,在这里因为大家都知道串口一个字节一个字节发送是需要时间等待的,所以直接将拼接的9字节数据写入FIFO中起到缓冲作用。
图12 ASCII码与十进制数的转换表
最后在顶层模块temperature_sensor_top下把uart_transfer,key_scan,lm75bcd_ascii,i2c_lm75模块相关信号量都例化到一起即可,按下按键就可以看到FPGA读取一次LM75温度传感器的数据并把数据处理后送串口调试助手显示如图13所示。
图13 串口调试助手接收LM75读取的温度数据
源工程代码下载链接(含datasheet):
链接:https://pan.baidu.com/s/1pxKyQ5hSkqMApxkb0OhMfw
提取码:54es