数码之家

 找回密码
 立即注册
搜索
查看: 2103|回复: 10

[C51] 长春疫重居家防疫,调试并分享STC15W@HTU21D模拟I2C驱动代码【附逻辑分析图】

[复制链接]
发表于 2022-3-17 10:50:57 | 显示全部楼层 |阅读模式
本帖最后由 慕名而来 于 2022-3-17 10:56 编辑

闲话:
先上开场诗一首抒怀:
新年才过冬将去,
南风初至柳含绿。
各行才张人抖擞,
冰河初开新流急。
一夜醒来疫又起,
居家不出再防疫。
阳光融尽昨日雪,
长春不日定胜利。

话说,居家不出百无聊赖,又有前日拙作打油诗为证:

限行闭户宅内囚,
常看视讯常忧愁。
早茶午餐过晌酒,
只盼睡醒三杆头。
喇叭声声扰晨梦,
严遮密裹忙下楼。
相视无语远排队,
验明正身捅咽喉。

---------✄------------✄---------------------------------------------------
正题:
前几天发帖求教关于单片机模拟I2C总线以非主机模式驱动HTU21D时对单片机其他工作是否有影响?请教关于I2C总线问题https://www.mydigit.cn/thread-301611-1-1.html(出处: 数码之家)
虽然没能弄的很懂,但是闲来无事调试的这段STC15W单片机驱动HTU21D的代码还是调试完成了,分享于此也留作备份,新手用到时可以看看,高手发现问题时敬请指正。
注释:
1.代码没有拆分I2C.c和HTU21D.c,用到的朋友可以自己改编,如果用于各种屏幕显示时请自行匹配显示函数,如果用于串口助手演示时请自行匹配串口代码。
2.代码包含详细注解,后面的逻辑分析图片上也有注解,此处只粘贴代码不加文字讲解了。
3.代码由网上例程改编,如有雷同并不意外。

  1. //单片机:STC15W408AS
  2. //温湿度传感器:HTU21D
  3. #include <STC15W.H>

  4. #define uchar unsigned char         
  5. #define uint  unsigned int
  6. sbit SDAK=P1^2;
  7. sbit SCLK=P1^3;
  8. //----IIC总线的开始信号-------
  9. void i2c_start()
  10. {
  11.     SDAK=1;
  12.     delay_I2C();
  13.         SCLK=1;                //SCL高电平
  14.         delay_I2C();
  15.     SDAK=0;                //SCL高电平时期间SDA出现下降沿
  16.     delay_I2C();

  17. }
  18. //----IIC总线的停止信号--------
  19. void i2c_stop()
  20. {
  21.     SDAK=0;
  22.     delay_I2C();
  23.     SCLK=1;                //SCL高电平
  24.     delay_I2C();
  25.     SDAK=1;                //SCL高电平期间SDA出现上升沿
  26.     delay_I2C();
  27. }
  28. //----从机响应ACK状态查询函数-----------------------------
  29. //返回0--ACK、返回1--NAK
  30. bit Receive_ACK()
  31. {
  32. uchar i=0,ACK_Flag=0;

  33. SCLK=1;
  34. delay_I2C();
  35. //SCL高电平期间检测SDA是否被从机拉低或者超时未响应
  36. while((SDAK==1)&&i<250)
  37. {
  38. i++;
  39. }
  40. SCLK=0;
  41. delay_I2C();
  42. //以下返回检测状态
  43. if(i==250)//超时未响应为无应答状态
  44. {
  45. ACK_Flag=1;//返回NAK
  46. }
  47. if(i<250)//从机给出了应答、数据线出现了下跳信号
  48. {
  49. ACK_Flag=0;//返回ACK
  50. }
  51. return ACK_Flag;
  52. }
  53. //----主机向IIC总线发送ACK--------
  54. void Send_ack()
  55. {
  56.    SCLK=0;
  57.    delay_I2C();        
  58.    SDAK=0;
  59.    delay_I2C();
  60.    SCLK=1;
  61.    delay_I2C();        
  62.    SCLK=0;
  63.    delay_I2C();        
  64.    SDAK=1;
  65. }
  66. //----主机向IIC总线发送NACK--------
  67. void Send_nack()
  68. {
  69.    SCLK=0;
  70.    delay_I2C();        
  71.    SDAK=1;  
  72.    delay_I2C();
  73.    SCLK=1;
  74.    delay_I2C();
  75.    SCLK=0;
  76.    delay_I2C();        
  77. }
  78. //----向总线上写一个字节数据-------------------------------
  79. //通过数据左移利用C51寄存器可以位操作的CY位、发送数据的最高位
  80. void write_dat(uchar date)
  81. {
  82.     uchar i;
  83.     for(i=0; i<8; i++)
  84.     {
  85.         date=date<<1;        //将字节的第一位左移入CY位
  86.         SCLK=0;                        //拉低时钟准备主机发送
  87.         delay_I2C();
  88.         SDAK=CY;                //发送一位数据
  89.         delay_I2C();
  90.         SCLK=1;                        //拉高时钟等待从机读取
  91.         delay_I2C();
  92.     }
  93.     //完成一个8位字节数据的发送后释放总线
  94.     SCLK=0;                                //拉低SCL线准备主机后续的读写操作
  95.     delay_I2C();
  96.     SDAK=1;                                //拉高SDA线等待读写数据
  97.     delay_I2C();
  98. }
  99. //----在总线上读取一个字节数据--------------
  100. uchar read_dat()
  101. {
  102.     uchar i,k=0;
  103.     //首先释放总线
  104.         SCLK=0;                                //拉低SCL线准备主机后续的读写操作
  105.     delay_I2C();
  106.     SDAK=1;                                //拉高SDA线等待读写数据
  107.     delay_I2C();
  108.     for(i=0; i<8; i++)
  109.     {
  110.         SCLK=1;                //拉高时钟线读取SDA的状态(只有在SCL=1时SDA是稳定数据)
  111.         delay_I2C();
  112.         k=(k<<1)|SDAK;//K中的数据的最后一位根据SDA状态改变后再左移后存入K中
  113.         SCLK=0;                //拉低时钟线允许从机改变SDA线的状态
  114.         delay_I2C();
  115.     }
  116.     return k;
  117. }
  118. //----SHT20软件复位,主函数中调用----------------------------------------------------
  119. void SoftReset()
  120. {
  121. //复位I2C总线
  122.     SDAK = 1;
  123.     delay_I2C();
  124.     SCLK = 1;
  125.     delay_I2C();
  126. //复位HTU21D
  127.     i2c_start();     
  128.     write_dat(0x80);
  129.         Send_ack();
  130.     write_dat(0xfe);//发送复位指令
  131.         Send_ack();
  132.     i2c_stop();      
  133.         delay_ms(30);//等待复位操作完成
  134. }
  135. //----非主机模式,读取函数函数---------------------------------------------
  136. uint ReadSht20(uchar whatdo)
  137. {
  138.     float temp;//设置用于浮点运算的变量
  139.     uchar MSB,LSB;
  140.         uint HT;
  141.         delay_ms(100);
  142.     i2c_start();
  143.         write_dat(0x80);
  144.         Receive_ACK();
  145.         write_dat(whatdo);
  146.         Receive_ACK();
  147.         do  
  148.         {  
  149.         delay_I2C();  
  150.         i2c_start();   
  151.         write_dat(0x81);
  152.         }while(Receive_ACK()==1);//非主机模式、检测到SDA抬起时器件操作结束开始取出数据
  153.         MSB = read_dat();
  154.         Send_ack();
  155.         LSB = read_dat();
  156.         Send_nack();
  157.         i2c_stop();
  158.             LSB &= 0xfc;                              
  159.             temp = MSB*256 + LSB;                     
  160.             if (whatdo==0xf5)                  
  161.             {        
  162.                                 HT=(temp*125)/65536-6;
  163.             }
  164.                         if (whatdo==0xf3)                                         
  165.             {   
  166.                                 HT=(temp*175.72)/65536-46.85;
  167.                         }               
  168.     return HT*100;//数据放大100倍显示小数点后两位
  169. }
复制代码


















本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

打赏

参与人数 3家元 +142 收起 理由
家睦 + 120
cutter + 12
jf201006 + 10 原創內容

查看全部打赏

发表于 2022-3-17 12:33:09 | 显示全部楼层
没必要那么麻烦。
启动转换后,延时比“转换位数-最长转换时间”稍长的时间再去读取转换值就可以了,比如14位温度转换至少需要85mS,可以启动转换100mS后再去读取温度值,湿度12位转换最多需要29mS,为公用程序,可以统一做成100mS后读取。转换期间I2C总线是空闲的,CPU可以与其它设备通讯或执行其它任务。





本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

发表于 2022-3-17 12:55:58 | 显示全部楼层
为保证数据可靠,数据要做CRC8校验,SHT21 CRC8计算方法为:
  1. 生成多项式=x8+x5+x4+1,初值Init=0x00,输入反转RefIn=false,输出反转RefOut=false,执行完RefOut之后,对结果进行异或全0或者全1 XorOut=0x00
复制代码
可以用表格快速计算,表格:
  1. //CRC8计算表格
  2. //生成多项式=x8+x5+x4+1,初值Init=0x00,输入反转RefIn=false,输出反转RefOut=false,执行完RefOut之后,对结果进行异或全0或者全1 XorOut=0x00
  3. const unsigned char CRC8_0x131[256]=
  4. {
  5.         0x00,0x31,0x62,0x53,0xC4,0xF5,0xA6,0x97,0xB9,0x88,0xDB,0xEA,0x7D,0x4C,0x1F,0x2E,
  6.         0x43,0x72,0x21,0x10,0x87,0xB6,0xE5,0xD4,0xFA,0xCB,0x98,0xA9,0x3E,0x0F,0x5C,0x6D,
  7.         0x86,0xB7,0xE4,0xD5,0x42,0x73,0x20,0x11,0x3F,0x0E,0x5D,0x6C,0xFB,0xCA,0x99,0xA8,
  8.         0xC5,0xF4,0xA7,0x96,0x01,0x30,0x63,0x52,0x7C,0x4D,0x1E,0x2F,0xB8,0x89,0xDA,0xEB,
  9.         0x3D,0x0C,0x5F,0x6E,0xF9,0xC8,0x9B,0xAA,0x84,0xB5,0xE6,0xD7,0x40,0x71,0x22,0x13,
  10.         0x7E,0x4F,0x1C,0x2D,0xBA,0x8B,0xD8,0xE9,0xC7,0xF6,0xA5,0x94,0x03,0x32,0x61,0x50,
  11.         0xBB,0x8A,0xD9,0xE8,0x7F,0x4E,0x1D,0x2C,0x02,0x33,0x60,0x51,0xC6,0xF7,0xA4,0x95,
  12.         0xF8,0xC9,0x9A,0xAB,0x3C,0x0D,0x5E,0x6F,0x41,0x70,0x23,0x12,0x85,0xB4,0xE7,0xD6,
  13.         0x7A,0x4B,0x18,0x29,0xBE,0x8F,0xDC,0xED,0xC3,0xF2,0xA1,0x90,0x07,0x36,0x65,0x54,
  14.         0x39,0x08,0x5B,0x6A,0xFD,0xCC,0x9F,0xAE,0x80,0xB1,0xE2,0xD3,0x44,0x75,0x26,0x17,
  15.         0xFC,0xCD,0x9E,0xAF,0x38,0x09,0x5A,0x6B,0x45,0x74,0x27,0x16,0x81,0xB0,0xE3,0xD2,
  16.         0xBF,0x8E,0xDD,0xEC,0x7B,0x4A,0x19,0x28,0x06,0x37,0x64,0x55,0xC2,0xF3,0xA0,0x91,
  17.         0x47,0x76,0x25,0x14,0x83,0xB2,0xE1,0xD0,0xFE,0xCF,0x9C,0xAD,0x3A,0x0B,0x58,0x69,
  18.         0x04,0x35,0x66,0x57,0xC0,0xF1,0xA2,0x93,0xBD,0x8C,0xDF,0xEE,0x79,0x48,0x1B,0x2A,
  19.         0xC1,0xF0,0xA3,0x92,0x05,0x34,0x67,0x56,0x78,0x49,0x1A,0x2B,0xBC,0x8D,0xDE,0xEF,
  20.         0x82,0xB3,0xE0,0xD1,0x46,0x77,0x24,0x15,0x3B,0x0A,0x59,0x68,0xFF,0xCE,0x9D,0xAC,
  21. };
复制代码
查表计算程序
  1. unsigned char  Calc_CRC8(unsigned char  *pBuffer,unsigned char  len)
  2. {
  3.         unsigned char  crc8;
  4.        
  5.         crc8=0;        //初值=0
  6.        
  7.         do
  8.         {
  9.                 crc8=crc8^(*pBuffer);
  10.                 crc8=CRC8_0x131[crc8];
  11.                
  12.                 pBuffer++;
  13.         }while(--len);
  14.        
  15.         return crc8;
  16. }
复制代码


温度值转换没必要使用浮点数,如果是51单片机,算浮点数很耗时。
温度值可以按放大100倍计算
温度值=(17572*数据/65536)-4685;        //*100倍
这个算式,实际上只需要计算(17572*数据)取高16位,然后减去4685就可以了,只需要做一次16位*16位乘法和一次16位-16位减法。



打赏

参与人数 1家元 +12 收起 理由
cutter + 12

查看全部打赏

回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2022-3-18 21:31:25 | 显示全部楼层
mmxx2015 发表于 2022-3-17 12:55
为保证数据可靠,数据要做CRC8校验,SHT21 CRC8计算方法为:
可以用表格快速计算,表格:
查表计算程序

真心感谢你分享的数据表和应用代码,回头我会将其移植到我的代码中去的,此前你说的用延时来替代循环等待的方法应该是合理的,如果弄一个定时器延时或许真的不浪费单片机的时间的,另外,你说的计算变量设置问题我的理解是,如果不用浮点的话想要获得放大100倍的数据也是需要长整型变量来计算的,我不知道C51处理浮点和处理长整型计算差别有多大或许长整型会更加合理一些。
回复 支持 反对

使用道具 举报

发表于 2022-3-18 22:20:39 | 显示全部楼层
慕名而来 发表于 2022-3-18 21:31
真心感谢你分享的数据表和应用代码,回头我会将其移植到我的代码中去的,此前你说的用延时来替代循环等待 ...

提醒一下,CRC算法并不通用,只有生成多项式、初值、输入反转、输出反转等设置一样时,才是一样的算法,不同的参数,查表法计算用的表格不一样,这个表格是sensirion在官网上提供的。
我说的延时和你理解的延时不太一样,我说的做法是累积一段短时间达到一段长时间,比如累积10次1毫秒得到10毫秒,每次累积时间内可以去做别的事,做完再看看时间到了没有,这样,CPU不做事的时间大大减少。就你这个应用而言,每一位的的传输应该用定时器中断来实现,因为就是1kbps速率也影响不大,这样,I2C传输几乎不占用CPU资源,可以腾出更多时间做其它事,如采集按键、刷新显示、把数据传给其它设备。

回复 支持 反对

使用道具 举报

发表于 2022-3-19 01:22:40 | 显示全部楼层
慕名而来 发表于 2022-3-18 21:31
真心感谢你分享的数据表和应用代码,回头我会将其移植到我的代码中去的,此前你说的用延时来替代循环等待 ...

就这个温度转换而言,整数计算比浮点数计算快多了。
下面用Keil C51 V9.52模拟AT89C52 24PC使用不同方法计算用时差异。软件模拟是,寄存器窗口下有个“sec”项以秒为单位统计运行时间,分别记录算前算后的时间,根据时间差就可以算出程序执行时间,如果是硬件实测,可以通过翻转IO测量脉宽精确测量。

浮点数计算和整数计算用时对比:


  1. 数据=0x5EFC

  2. 浮点计算法
  3. t1=0.00019650
  4. t2=0.00072150
  5. △t=0.00072150-0.00019650=0.000525(S)=525(μS)
  6. T1=1800 --损失精度

  7. 整数计算法
  8. t1=0.00072200
  9. t2=0.00076200
  10. △t=0.00097250-0.00076200=0.0002105(S)=210.5(μS)
  11. T2=1818
复制代码
因为Keil C51编译器做16位*16位计算得不到正确结果,需强制转换为32位*32位,但用时还是比浮点数计算少很多。
你的算式会损失精度,需改成
  1. HT=((temp*175.72)/65536.0-46.85)*100.0;
复制代码
才不会损失精度,而且改后执行时间还短一些。

  1. 浮点计算法
  2. t1=0.00019650
  3. t2=0.00072100
  4. △t=0.00072100-0.00019650=0.0005245(S)=524.5(μS)
  5. T1=1818 --不损失精度
复制代码
因为Keil C51编译器把16位*16位做复杂了,用汇编程序计算,效率更高,执行时间只需要浮点数计算的十二分之一。

  1. 汇编整数计算法
  2. t1=0.00076250
  3. t2=0.00080600
  4. △t=0.00080600-0.00076250=0.0000435(S)=43.5(μS)
  5. T2=1818
复制代码
16位*16位汇编程序如下:
  1. $NOMOD51

  2. NAME        MUL_ASM


  3. ?PR?_uint_mul_uint?MUL_ASM        SEGMENT        CODE

  4.         PUBLIC        _uint_mul_uint

  5. ; //******************************************************************


  6. ; //******************************************************************
  7. ; // 函数名        :unsigned long uint_mul_uint(unsigned int x,unsigned int y)
  8. ; // 功能        :16位*16位
  9. ; // 参数        :16位x,16位y
  10. ; // 返回值        :32位乘积
  11. ; //******************************************************************
  12. ;R4,R5 * R6,R7


  13.         RSEG ?PR?_uint_mul_uint?MUL_ASM

  14. _uint_mul_uint:
  15.        
  16.         USING 0
  17.        
  18.         MOV        AR0,        R4        ;暂存
  19.         MOV        AR1,        R5
  20.         MOV        AR2,        R6
  21.         MOV        AR3,        R7
  22.        
  23.         MOV        A,        R5        ;R5*R7
  24.         MOV        B,        R7
  25.         MUL        AB
  26.         XCH        A,        R7
  27.         MOV        R6,        B
  28.        
  29.         MOV        B,        R4        ;+((R4*R7)<<8)
  30.         MUL        AB       
  31.         ADD        A,        R6
  32.         MOV        R6,        A
  33.         CLR        A
  34.         ADDC        A,        B
  35.         MOV        R5,        A
  36.         CLR        A
  37.         ADDC        A,        #00H
  38.         MOV        R4,        A
  39.        
  40.         MOV        A,        AR1        ;+((R5*R6)<<8)
  41.         MOV        B,        AR2
  42.         MUL        AB
  43.         ADD        A,        R6
  44.         MOV        R6,        A
  45.         MOV        A,        R5
  46.         ADDC        A,        B
  47.         MOV        R5,        A
  48.         CLR        A
  49.         ADDC        A,        #00H
  50.         MOV        R4,        A
  51.        
  52.         MOV        A,        AR0        ;+((R4*R6)<<16)
  53.         MOV        B,        AR2
  54.         MUL        AB
  55.         ADD        A,        R5
  56.         MOV        R5,        A
  57.         MOV        A,        R4
  58.         ADDC        A,        B
  59.         MOV        R4,        A
  60.        
  61.         RET

  62.         END
复制代码


测试工程


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

打赏

参与人数 1家元 +20 收起 理由
慕名而来 + 20 精彩回帖

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2022-3-19 07:12:25 | 显示全部楼层
都是高手,一般只会改改io口
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-3-19 09:48:45 | 显示全部楼层
mmxx2015 发表于 2022-3-19 01:22
就这个温度转换而言,整数计算比浮点数计算快多了。
下面用Keil C51 V9.52模拟AT89C52 24PC使用不同方法 ...

好的明白了,昨晚再调试代码时改用长整形运算发现确实精度不同,原本小数点后两位为0的数据结果都有了数值,好多东西细究起来真的学问不少的,这次改编程序算是下功夫较大的一次学习了很多,很愿意和你交流。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-3-21 14:18:50 | 显示全部楼层
mmxx2015 发表于 2022-3-18 22:20
提醒一下,CRC算法并不通用,只有生成多项式、初值、输入反转、输出反转等设置一样时,才是一样的算法, ...

@mmxx2015 再次请教,在crc8查表验算时,初值为crc8=0;是怎么来的,本例代码中我移植了你分享的函数和数据表,很好用,但是当我将其移植到另一个AHT25温湿度传感器的工程中时就不好用了,经过与官网例程对比后发现,需要的初值应该是crc8=0xff,改动后也顺利的移植成功了,我对crc校验理解不多所有搞不明白。
回复 支持 反对

使用道具 举报

发表于 2022-3-21 17:13:33 | 显示全部楼层
慕名而来 发表于 2022-3-21 14:18
@mmxx2015 再次请教,在crc8查表验算时,初值为crc8=0;是怎么来的,本例代码中我移植了你分享的函数和数 ...

初值、反转是自行约定的。
CRC-8校验原理及软件实现
https://blog.csdn.net/eurphan_y/article/details/85161286



回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-3-22 09:40:46 | 显示全部楼层
本帖最后由 慕名而来 于 2022-3-22 09:42 编辑
mmxx2015 发表于 2022-3-21 17:13
初值、反转是自行约定的。
CRC-8校验原理及软件实现
https://blog.csdn.net/eurphan_y/article/details/8 ...

问题解决了,在HTU21D的一个手册中找到了答案,但AHT**手册中没有提及,这个初值是器件自身的通讯协议规定的,HTU21D的CRC初值为0x00,而AHT25的CRC初值也就是0xff了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-5-21 05:44 , Processed in 0.265201 second(s), 16 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

快速回复 返回顶部 返回列表