• 【第六章】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. 通信开始
    2. 地址传送(通过地址选择传送数据目标)
    3. 数据传送(收发数据)
    4. 通信结束

    我觉得大家可以思考一下,如果只给你两根线,一根时钟线一根数据线,条件是如此的艰难,那么要实现主机对从从机,或者是从机对主机的选择,以及数据的发送,数据的接收,甚至还需要应答机制,不然对方怎么知道数据收没收到??你会怎么做????

     【数据帧格式】

     数据帧格式,对应上方的通信过程。

     【起始和结束信号】

    【应答机制】

     【数据有效性】

     【总结】

    最后详细叙述一下通讯过程:

    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初始化】

    第一步不用想也是初始化

    1. #define I2C_SOFT_RCU RCU_GPIOB
    2. #define I2C_SOFT_PORT GPIOB
    3. #define I2C_SOFT_SCL_PIN GPIO_PIN_6
    4. #define I2C_SOFT_SDA_PIN GPIO_PIN_7
    5. //初始化函数
    6. void my_i2c_init(void){
    7. //打开IIC时钟
    8. rcu_periph_clock_enable(I2C_SOFT_RCU);
    9. //初始化GPIO
    10. gpio_init(I2C_SOFT_PORT, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, I2C_SOFT_SCL_PIN|I2C_SOFT_SDA_PIN);
    11. //GPIO引脚置1(高阻态)
    12. gpio_bit_set(I2C_SOFT_PORT, I2C_SOFT_SCL_PIN|I2C_SOFT_SDA_PIN);
    13. }

    【读温度寄存器的值(未转换)】

    首先封装一个函数,用来向IIC总线上写入器件地址以及要用到的温度寄存器地址

    1. uint8_t lm75a_write_addr(uint8_t id_rw, uint8_t reg_addr){
    2. my_i2c_start();
    3. my_i2c_send_byte(id_rw);
    4. my_i2c_read_ack();
    5. my_i2c_send_byte(reg_addr);
    6. my_i2c_read_ack();
    7. return 0;
    8. }

    然后就是读温度寄存器的值。

    1. #define LM75A_I2C_ADDR 0x9E //LM75A的从机地址
    2. #define LM75A_TEMP_REG 0x00 //温度寄存器的指针地址
    3. #define IIC_WRITE 0
    4. #define IIC_READ 1
    5. /***
    6. 功能:读温度寄存器的值
    7. 输入:
    8. uint8_t lm75a_id: lm75a的iic从机地址
    9. uint8_t reg:要操作的寄存器的指针
    10. uint8_t *p:读取结果存放的位置
    11. uint8_t len:寄存器的字节长度(1 or 2)
    12. 返回:无
    13. *****/
    14. void lm75a_read_reg(uint8_t lm75a_id, uint8_t reg, uint8_t *p, uint8_t len){
    15. //向iic总线上写入器件地址、指针字节
    16. lm75a_write_addr(lm75a_id|IIC_WRITE, reg);
    17. my_i2c_start();
    18. my_i2c_send_byte(lm75a_id|IIC_READ);
    19. my_i2c_read_ack();
    20. uint8_t i;
    21. for(i = 0; i < len; i++){
    22. *p++ = my_i2c_read_byte();
    23. if(i != (len-1))
    24. my_i2c_ack();
    25. }
    26. my_i2c_nack();
    27. my_i2c_stop();
    28. }

    详细如下图:

    【读温度传感器的温度寄存器的值并转换为温度值】

    1. // 读温度传感器的温度寄存器的值并转换为温度值
    2. float lm75a_get_temp(void){
    3. float temp_result;
    4. //读温度寄存器值
    5. uint8_t byte_data[2];
    6. lm75a_read_reg(LM75A_I2C_ADDR, LM75A_TEMP_REG, byte_data, 2);
    7. //将温度寄存器值转为温度值
    8. uint16_t temp_reg = byte_data[0]<<3 | byte_data[1]>>5;
    9. if((temp_reg & 0x0400) == 0){
    10. temp_result = temp_reg * 0.125;
    11. }else{
    12. temp_reg = (~((temp_reg&0x03ff)-1)) & 0x03ff; //补码到原码转换
    13. temp_result = temp_reg * (-0.125);
    14. }
    15. return temp_result;
    16. }

     查阅数据手册,得到温度寄存器和温度值的转换方法:

    判断这个寄存器的第十位是0还是1,对应着不同的计算方法:

    【主函数】

    1. lm75a_init();
    2. while(1){
    3. temp_result = lm75a_get_temp();
    4. sprintf(temp_string, "temperature is: %.3f C.\n", temp_result);
    5. usart0_send_string((uint8_t *)temp_string);
    6. delay_1ms(1000); //等待1s
    7. }

  • 相关阅读:
    在线小说阅读系统
    反射 - 枚举 - Lambda表达式
    B3627 立方根
    设计模式:迭代器模式
    c语言提高学习笔记——02-c提高03day
    【基础】JDK新特性
    SpringBoot打包部署最佳实践
    PyTorch 模型性能分析和优化 - 第 6 部分
    unity发布微信小游戏,未找到 game.json报错原因
    力扣练习——51 搜索二维矩阵
  • 原文地址:https://blog.csdn.net/Xiaoxuexxxxx/article/details/138137091