当前位置:   article > 正文

物联网实战--入门篇之(五)嵌入式-IIC驱动(SHT30温湿度)

物联网实战--入门篇之(五)嵌入式-IIC驱动(SHT30温湿度)

目录

一、IIC简介

二、IIC驱动解析

三、SHT30驱动

四、总结


一、IIC简介

        不管是IIC还是串口,亦或SPI,它们的本质区别在于有各自的规则,就是时序图;它们的相同点就是只要你理解了时序图,你就可以用最普通的IO引脚模拟出各自的通讯总线,但是一般来讲没那必要,特别是串口,模拟比较麻烦,而且速率较高,使用频率较高,很费系统资源,不合算。可以发现 我的代码里IIC驱动是用自己模拟的,主要是因为1、硬件IIC有时候会卡死;2、IIC速率比较低,且使用频率较低;3、便于在各个芯片平台上移植;4、有时候IIC的设备比较多,模拟引脚选择灵活,便于PCB设计。

        那么,下面看下模拟IIC的文件,其实并不难,就是按时序图来就行了,具体时序图就不贴了,总的就下图这几个函数,然后再根据具体IIC从设备的要求读写相应数据就行了。

二、IIC驱动解析

        接下来讲解下驱动代码,先从结构体开始,主要就是保存应用层的SDA和SCL引脚信息,还有个延时,正常默认5us,不需要改动。SDA_0等这些宏定义主要是为了程序的简洁以及驱动文件移植时便于修改,只要替换各自平台的引脚操作函数即可。

        SCL是时钟引脚,总是作为输出,而SDA有时候是输出有时候是输入,所以需要IIC_SdaInMode()和IIC_SdaOutMode()进行引脚模式的切换。

        以下是IIC驱动的引脚相关函数,移植到其他平台的时候需要修改成相应的函数。

  1. /*
  2. ================================================================================
  3. 描述 : IIC引脚初始化
  4. 输入 :
  5. 输出 :
  6. ================================================================================
  7. */
  8. void IIC_GPIOInit(I2cDriverStruct *pDriver)
  9. {
  10. GPIO_InitTypeDef GPIO_InitStruct;
  11. GPIO_InitStruct.GPIO_Pin = pDriver->pin_sda;
  12. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  13. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  14. GPIO_Init(pDriver->port_sda, &GPIO_InitStruct);
  15. GPIO_InitStruct.GPIO_Pin = pDriver->pin_scl;
  16. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  17. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  18. GPIO_Init(pDriver->port_scl, &GPIO_InitStruct);
  19. pDriver->delay_time=IIC_DELAY_TIME;
  20. }
  21. /*
  22. ================================================================================
  23. 描述 : SDA设置成输入模式
  24. 输入 :
  25. 输出 :
  26. ================================================================================
  27. */
  28. void IIC_SdaInMode(I2cDriverStruct *pDriver)
  29. {
  30. GPIO_InitTypeDef GPIO_InitStruct;
  31. GPIO_InitStruct.GPIO_Pin = pDriver->pin_sda;
  32. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
  33. GPIO_Init(pDriver->port_sda, &GPIO_InitStruct);
  34. }
  35. /*
  36. ================================================================================
  37. 描述 : SDA设置成输出模式
  38. 输入 :
  39. 输出 :
  40. ================================================================================
  41. */
  42. void IIC_SdaOutMode(I2cDriverStruct *pDriver)
  43. {
  44. GPIO_InitTypeDef GPIO_InitStruct;
  45. GPIO_InitStruct.GPIO_Pin = pDriver->pin_sda;
  46. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  47. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  48. GPIO_Init(pDriver->port_sda, &GPIO_InitStruct);
  49. }

        以下是根据IIC时序图写的信号代码,不同人模拟的代码首尾可能略有差别,但是最核心的信号状态是一样的,这个不用太纠结。具体的每个信号是什么作用、该怎么使用,等等结合SHT30温湿度的驱动再说明。

  1. /*
  2. ================================================================================
  3. 描述 : 起始信号
  4. 输入 :
  5. 输出 :
  6. ================================================================================
  7. */
  8. void IIC_Start(I2cDriverStruct *pDriver)
  9. {
  10. SCL_1;
  11. SDA_1;
  12. delay_us(pDriver->delay_time);
  13. SDA_0;
  14. delay_us(pDriver->delay_time);
  15. SCL_0;
  16. delay_us(pDriver->delay_time);
  17. }
  18. /*
  19. ================================================================================
  20. 描述 : 停止信号
  21. 输入 :
  22. 输出 :
  23. ================================================================================
  24. */
  25. void IIC_Stop(I2cDriverStruct *pDriver)
  26. {
  27. SDA_0;
  28. SCL_1;
  29. delay_us(pDriver->delay_time);
  30. SDA_1;
  31. delay_us(pDriver->delay_time);
  32. SCL_0;
  33. }
  34. /*
  35. ================================================================================
  36. 描述 : 应答
  37. 输入 :
  38. 输出 :
  39. ================================================================================
  40. */
  41. void IIC_Ack(I2cDriverStruct *pDriver)
  42. {
  43. SDA_0;
  44. delay_us(pDriver->delay_time);
  45. SCL_1;
  46. delay_us(pDriver->delay_time);
  47. SCL_0;
  48. delay_us(pDriver->delay_time);
  49. SDA_1;
  50. delay_us(pDriver->delay_time);
  51. }
  52. /*
  53. ================================================================================
  54. 描述 : 非应答
  55. 输入 :
  56. 输出 :
  57. ================================================================================
  58. */
  59. void IIC_NAck(I2cDriverStruct *pDriver)
  60. {
  61. SCL_0;
  62. delay_us(pDriver->delay_time);
  63. SDA_1;
  64. SCL_1;
  65. delay_us(pDriver->delay_time);
  66. }
  67. /*
  68. ================================================================================
  69. 描述 : 等待回复
  70. 输入 :
  71. 输出 :
  72. ================================================================================
  73. */
  74. bool IIC_WaitAck(I2cDriverStruct *pDriver)
  75. {
  76. u32 wait_tickets=0;
  77. SCL_0;
  78. IIC_SdaInMode(pDriver);
  79. delay_us(pDriver->delay_time);
  80. while(SDA_READ()>0)
  81. {
  82. wait_tickets++;
  83. if(wait_tickets>250)
  84. {
  85. IIC_SdaOutMode(pDriver);
  86. delay_us(pDriver->delay_time);
  87. IIC_Stop(pDriver);
  88. return false;
  89. }
  90. delay_us(1);
  91. }
  92. SCL_1;
  93. delay_us(pDriver->delay_time);
  94. SCL_0;
  95. delay_us(pDriver->delay_time);
  96. IIC_SdaOutMode(pDriver);
  97. return true;
  98. }

        以下是IIC的字节读写函数,也是根据时序来就行了,传输过程是先高位后低位,读的时候SDA引脚要先设置成输入模式。

  1. /*
  2. ================================================================================
  3. 描述 : 读取一个字节
  4. 输入 :
  5. 输出 :
  6. ================================================================================
  7. */
  8. u8 IIC_ReadByte(I2cDriverStruct *pDriver)
  9. {
  10. u8 i, data=0;
  11. IIC_SdaInMode(pDriver);
  12. for(i=0;i<8;i++)
  13. {
  14. SCL_0;
  15. delay_us(pDriver->delay_time);
  16. SCL_1;
  17. delay_us(pDriver->delay_time);
  18. data=data<<1;//左移,高位先读取
  19. if(SDA_READ()>0)
  20. {
  21. data|=0x01;
  22. }
  23. }
  24. SCL_0;
  25. IIC_SdaOutMode(pDriver);
  26. delay_us(pDriver->delay_time);
  27. return data;
  28. }
  29. /*
  30. ================================================================================
  31. 描述 : 写入一个字节
  32. 输入 :
  33. 输出 :
  34. ================================================================================
  35. */
  36. void IIC_WriteByte(I2cDriverStruct *pDriver, u8 data)
  37. {
  38. u8 i;
  39. for(i=0;i<8;i++)
  40. {
  41. SCL_0;
  42. if(data&0x80)//高位先写
  43. {
  44. SDA_1;
  45. }
  46. else
  47. {
  48. SDA_0;
  49. }
  50. delay_us(pDriver->delay_time);
  51. SCL_1;
  52. delay_us(pDriver->delay_time);
  53. data=data<<1;
  54. }
  55. }

        以上基本是模拟IIC的驱动文件的全部内容了,自己看会发现每个函数输入都有一个I2cDriverStruct结构体,这样便于多个IIC设备驱动,比如2个SHT30+2个AT24C64一起使用都是没问题的,只要把各自的引脚定义清楚来就行了,互不干扰。

三、SHT30驱动

        净化器项目跟IIC相关的就是SHT30温湿度传感器了,我们一般就是读取温湿度值就行了,所以用起来比较简单,具体看下图,其中结构体Sht30WorkStruct内容就是器件地址、IIC结构体和温湿度数值,函数主要是初始化和读取温湿度,设置地址在特殊情况下才用。

        以下是初始化代码,主要是引脚初始化和配置默认的器件地址,这个器件地址是所有IIC从机设备都有的,根据芯片厂家和硬件设计来确定,比如这里的STH30,默认是0x44;如果ADDR引脚上拉则是0x45,数据手册截图如下所示。如果器件地址是0x45的话那就在应用层调用drv_sht30_set_addr进行设置即可。

  1. /*
  2. ================================================================================
  3. 描述 : 器件引脚初始化
  4. 输入 :
  5. 输出 :
  6. ================================================================================
  7. */
  8. void drv_sht30_init(Sht30WorkStruct *pSht30Work)
  9. {
  10. IIC_GPIOInit(&pSht30Work->tag_iic);
  11. pSht30Work->dev_addr=0x44;//默认器件地址
  12. }

        核心的就是温湿度读取了,具体代码如下,其中0x2C06是温湿度所在的寄存器地址,要读取6个字节,分别是温度高8位、温度低8位、温度校验码、湿度高8位、湿度低8位和湿度校验码,按顺序先读取出来后再自己根据公式进行整合即可。

        IIC读取的常规流程是先写入器件地址,同时通过配置器件地址的最低位说明下一步是写数据,也就是写入寄存器地址,这里是两个字节,先高8位后低8位,写完后先停止并充分延时下,让SHT30做好准备,否则不能正确读取;随后再次启动传输,写入器件地址并配置读数据需求,紧接着连续读取6字节数据,最后就是根据公式转换成实际的温湿度值就行了。

        在读取温湿度过程中会发现,IIC的时序函数起到了调度指挥的作用,起始、等待回复、停止等等,都是按顺序来的,具体自己结合代码看下。

  1. /*
  2. ================================================================================
  3. 描述 : 读取温湿度数据
  4. 输入 :
  5. 输出 :
  6. ================================================================================
  7. */
  8. void drv_sht30_read_th(Sht30WorkStruct *pSht30Work)
  9. {
  10. u16 reg_addr=0x2C06;//温湿度的寄存器地址,由数据手册得来
  11. u8 dev_addr=pSht30Work->dev_addr;
  12. I2cDriverStruct *pIIC=&pSht30Work->tag_iic;
  13. IIC_Start(pIIC);
  14. IIC_WriteByte(pIIC, dev_addr<<1|0x00);//准备写入寄存器地址
  15. IIC_WaitAck(pIIC);
  16. IIC_WriteByte(pIIC, reg_addr>>8);//写入寄存器地址高8位
  17. IIC_WaitAck(pIIC);
  18. IIC_WriteByte(pIIC, reg_addr&0xFF);//写入寄存器地址低8位
  19. IIC_WaitAck(pIIC);
  20. IIC_Stop(pIIC);
  21. delay_ms(20);//这个延时要稍微长点20ms以上
  22. IIC_Start(pIIC);
  23. IIC_WriteByte(pIIC, dev_addr<<1|0x01);//准备读取数据
  24. IIC_WaitAck(pIIC);
  25. u8 buff[10]={0};
  26. for(u8 i=0; i<6; i++)//读取温湿度和校验值状态
  27. {
  28. buff[i]=IIC_ReadByte(pIIC);
  29. if(i<5)IIC_Ack(pIIC);
  30. else IIC_NAck(pIIC);
  31. }
  32. IIC_Stop(pIIC);
  33. u16 temp=buff[0]<<8|buff[1];//温度寄存器值
  34. u16 humi=buff[3]<<8|buff[4];//湿度寄存器值
  35. pSht30Work->temp_value=175.f*(float)temp/65535.f-45.f ;//转换成温度-℃
  36. pSht30Work->humi_value=100.f*(float)humi/65535.f;//转换为湿度-%
  37. printf("temp=%.1f C, humi=%.1f%%\n", pSht30Work->temp_value, pSht30Work->humi_value);
  38. }

        由于系统可能挂载多个温湿度传感器,所以SHT30驱动程序函数入口都有一个Sht30WorkStruct结构体。

        在应用层,主要就是定义SHT30结构体、初始化引脚和读取操作了,具体如下所示。

四、总结

        IIC模拟驱动可以用在其它各种IIC器件,比如AT24Cxx系列的EEPROM、RC522 RFID感应模块等等,底层的IIC驱动过程都是一样的,剩下的就是根据数据手册,配置不同的器件地址和操作不同的寄存器地址了,基本原理是一样的,后续有机会再多写一些IIC设备的驱动。

        

本项目的交流QQ群:701889554

   写于2024-3-30

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/361084
推荐阅读
相关标签
  

闽ICP备14008679号