STM32 / GD32第六章:使用软件I2C读取温度传感器LM75AD
关联:STM32总结超全笔记【秋招自用】
【引言】
学习IIC通讯协议之前,我们回忆一下之前学习的第一个通信协议USART串口。
串口通信协议的特点
是串行
的、
异步
的、
半双工
通信。
【问】串口通信有几根线?
两根:TX和RX,一条收,一条发。
【IIC特点】
本章学习的IIC通信协议的特点是串行的、同步的、半双工的。
IIC是同步的,说明它必须有一条时钟线SCL,另外数据线有几条呢?
只有一条SDA数据线,所以决定了他是半双工的,来看一下图示:
如图:设备有主机STM32和若干从机,每个连接到总线的从机都有唯一的地址,任何器件也都可以作为主机,但是同一时刻只能拥有一个主机。
【问】之前在GPIO模式,讲到开漏输出的时候说到IIC特别适合开漏输出,为什么呢?
这是由IIC总线内部结构决定的,总线内部使用漏极开漏输出驱动器,所以SDA和SCL两根线都使用一个上拉电阻(上图),使用的时候就拉低为低电平,但是不能被驱动为高电平,平时为高阻态。
PS:这个上拉值有一个典型值4.7KΩ。
【IIC通信协议】
【问】那么IIC的通信过程是什么样的??
一般分为四个部分:
- 通信开始
- 地址传送(通过地址选择传送数据目标)
- 数据传送(收发数据)
- 通信结束
我觉得大家可以思考一下,如果只给你两根线,一根时钟线一根数据线,条件是如此的艰难,那么要实现主机对从从机,或者是从机对主机的选择,以及数据的发送,数据的接收,甚至还需要应答机制,不然对方怎么知道数据收没收到??你会怎么做????
【数据帧格式】
数据帧格式,对应上方的通信过程。
【起始和结束信号】
【应答机制】
【数据有效性】
【总结】
最后详细叙述一下通讯过程:
1.开始:主设备把SDA从高拉低,再把SCL从高拉低,对总线上的从机说:我要开始和你们中的某个人通信了。
2.主设备发送要与之通信的从机的7位地址(一般是7位),第八位是读写位(读1写0)
3.总线上的从设备把主设备发送的地址与自己的地址比较,如果匹配:从设备将SDA拉低一位表示应答(此时从设备获得SDA的控制权,之前SDA都是主设备控制的)。如果不拉低,SDA为高就表示非应答。
4.主设备发送(或接收)数据给从设备。SCL为高,读(写)数据,SCL为低,准备下一位的数据。
5.数据传输完毕,从设备返回一个应答给主设备
6.停止:主设备把SCL从低拉高,再把SDA从低拉高,表示停止通信。
【软件模拟IIC】
了解了通信协议,我们具体怎么使用呢?
这里我们使用软件模拟IIC通信协议,软件模拟I2C不需要额外的硬件支持,只需要使用微控制器的GPIO引脚和软件实现即可。此外灵活性和可移植性非常高。
我们一步步来,这章使用的是GD32F1103,但其实和STM32是完全一样的。
首先我们要控制SDA和SCL的电平,就要封装一个写引脚SDA和SCL 1或0 的函数:
(IIC的引脚都宏定义了,函数基本和STM32一样的)
【起始条件】SDA从高拉低,再把SCL从高拉低
【结束条件】SCL从低拉高,再把SDA从低拉高
【应答】从设备将SDA拉低表示应答
【非应答】从设备将SDA拉高表示非应答
【主机发送一个字节数据给从机】
【主机读从机一个字节数据】
【主机读应答】
这个读SDA也就是把 gpio_input_bit_get 封装了一下:
那么软件模拟IIC已经被我们封装好了,接下来做实验。
【例程11】软件IIC读取温度传感器LM75AD
首先硬件连接: PB6 — SCL PB7 — SDA
实验目的: 温度寄存器的值拿出来转化为浮点型的数值,通过串口发送到主机
【IIC和GPIO初始化】
第一步不用想也是初始化
#define I2C_SOFT_RCU RCU_GPIOB
#define I2C_SOFT_PORT GPIOB
#define I2C_SOFT_SCL_PIN GPIO_PIN_6
#define I2C_SOFT_SDA_PIN GPIO_PIN_7
//初始化函数
void my_i2c_init(void){
//打开IIC时钟
rcu_periph_clock_enable(I2C_SOFT_RCU);
//初始化GPIO
gpio_init(I2C_SOFT_PORT, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, I2C_SOFT_SCL_PIN|I2C_SOFT_SDA_PIN);
//GPIO引脚置1(高阻态)
gpio_bit_set(I2C_SOFT_PORT, I2C_SOFT_SCL_PIN|I2C_SOFT_SDA_PIN);
}
【读温度寄存器的值(未转换)】
首先封装一个函数,用来向IIC总线上写入器件地址以及要用到的温度寄存器地址
uint8_t lm75a_write_addr(uint8_t id_rw, uint8_t reg_addr){
my_i2c_start();
my_i2c_send_byte(id_rw);
my_i2c_read_ack();
my_i2c_send_byte(reg_addr);
my_i2c_read_ack();
return 0;
}
然后就是读温度寄存器的值。
#define LM75A_I2C_ADDR 0x9E //LM75A的从机地址
#define LM75A_TEMP_REG 0x00 //温度寄存器的指针地址
#define IIC_WRITE 0
#define IIC_READ 1
/***
功能:读温度寄存器的值
输入:
uint8_t lm75a_id: lm75a的iic从机地址
uint8_t reg:要操作的寄存器的指针
uint8_t *p:读取结果存放的位置
uint8_t len:寄存器的字节长度(1 or 2)
返回:无
*****/
void lm75a_read_reg(uint8_t lm75a_id, uint8_t reg, uint8_t *p, uint8_t len){
//向iic总线上写入器件地址、指针字节
lm75a_write_addr(lm75a_id|IIC_WRITE, reg);
my_i2c_start();
my_i2c_send_byte(lm75a_id|IIC_READ);
my_i2c_read_ack();
uint8_t i;
for(i = 0; i < len; i++){
*p++ = my_i2c_read_byte();
if(i != (len-1))
my_i2c_ack();
}
my_i2c_nack();
my_i2c_stop();
}
详细如下图:
【读温度传感器的温度寄存器的值并转换为温度值】
// 读温度传感器的温度寄存器的值并转换为温度值
float lm75a_get_temp(void){
float temp_result;
//读温度寄存器值
uint8_t byte_data[2];
lm75a_read_reg(LM75A_I2C_ADDR, LM75A_TEMP_REG, byte_data, 2);
//将温度寄存器值转为温度值
uint16_t temp_reg = byte_data[0]<<3 | byte_data[1]>>5;
if((temp_reg & 0x0400) == 0){
temp_result = temp_reg * 0.125;
}else{
temp_reg = (~((temp_reg&0x03ff)-1)) & 0x03ff; //补码到原码转换
temp_result = temp_reg * (-0.125);
}
return temp_result;
}
查阅数据手册,得到温度寄存器和温度值的转换方法:
判断这个寄存器的第十位是0还是1,对应着不同的计算方法:
【主函数】
lm75a_init();
while(1){
temp_result = lm75a_get_temp();
sprintf(temp_string, "temperature is: %.3f C.\n", temp_result);
usart0_send_string((uint8_t *)temp_string);
delay_1ms(1000); //等待1s
}
作者:XJHDZ