123下一页
返回列表 发新帖本帖赏金 200.00元(功能说明)

[MM32软件] 【瞎折腾系列】MM32F103空气质量检测仪

[复制链接]
 楼主| 呐咯密密 发表于 2021-7-27 11:42 | 显示全部楼层 |阅读模式
[url=home.php?mod=space&uid=760190]@21小跑堂 #申请原创#[/url]
还是我的风格,开篇先啰嗦:
闲来无事,太难的不会,就想玩玩手里的吃灰板子。
去年在灵动的活动中获得一块MM32L073为主控的开发板,型号为eMiniBoard MB-023。当时测评就写了一个开箱和串口测试,现在重新捡起来,玩点小应用,因为手头的传感器有限,只能做一个空气质量检测仪,主要包含三个功能:空气温度检测,空气湿度检测和PM2.5浓度检测。
但是在调试温湿度检测的时候翻车了,使用的传感器是DHT11模块,该模块在使用时需要微秒级别的延时,但是我在MM32L073的库中找不到us的延时函数,当然排除空函数的粗延时,使用SysTick只能做到ms级别的延时,自己写一个us的延时函数,发现根本不起作用,具体如下:
  1. void delay_init()
  2. {
  3.         RCC_ClocksTypeDef RCC_Clocks;

  4.     if (SysTick_Config(SystemCoreClock / 1000))
  5.     {
  6.         /* Capture error */
  7.         while (1);
  8.     }
  9.     /* Configure the SysTick handler priority */
  10.     NVIC_SetPriority(SysTick_IRQn, 0x0);//SysTick中断优先级设置
  11. }
以上是官方库中的延时函数初始化,SysTick_Config函数中给定系统时钟(48M)除以1000,使SysTick以1毫秒进入一次中断,如果我将SystemCoreClock 除以1000000应该是1us进入一次中断,但是这个会导致延时函数卡死,在debug后发现程序卡死在SysTick_Config()函数,继续追踪:
  1. __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
  2. {
  3.   if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk)  
  4.           return (1);      /* Reload value impossible */

  5.   SysTick->LOAD  = ticks - 1;                                  /* set reload register */
  6.   NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Systick Interrupt */
  7.   SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  8.   SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
  9.                    SysTick_CTRL_TICKINT_Msk   |
  10.                    SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  11.   return (0);                                                  /* Function successful */
  12. }
程序卡死在SysTick->LOAD = ticks - 1;也就是SysTick的重装载值寄存器写入47卡死,忘了在哪篇帖子看到有人说该值不能小于255,否则会自动将255写入。经过测试确实如此,此处我没有深入探究,改用定时器。ps:该问题在STM32F030和GD32E230均不存在,不知这其中有何缘由,希望了解的大佬给个提示。
经过测试,定时器的中断是无法达到1us进入一次中断的,无论如何设置,定时器最短只能在3us左右进一次中断。该方案作废。
路程已经过半,总不能半途而废,于是在某宝买了一个MM32F103CBT6最小系统,花了七十多大洋,涨价真离谱啊!
啰里啰嗦一大堆,终于可以进入正文了
本文一共三个模块:
OLED模块,使用模拟IIC驱动。
DHT11温湿度模块,IO口的读写操作。
夏普GP2Y10粉尘传感器,UART操作。

OLED驱动
oled模块手里有两块7针0.96寸的屏幕,但是在测试的时候都不好用,无论如何也点亮不了,于是在买最小系统板的同时也顺带买了一个4针的oled 0.96的屏,今天测试同样无法使用,换了几个方法,又拿stm32的板子,用例程测试,都是不行,最后发现是杜邦线断了,在内部断了,外表看不出来。我真的是一言难尽
关于该屏幕的介绍这里就不啰嗦了,网上到处都是,这里分享一下我的驱动。oled.c
  1. #include "oled.h"
  2. #include "stdlib.h"
  3. #include "oledfont.h"           
  4. #include "delay.h"
  5. //OLED的显存
  6. //存放格式如下.
  7. //[0]0 1 2 3 ... 127        
  8. //[1]0 1 2 3 ... 127        
  9. //[2]0 1 2 3 ... 127        
  10. //[3]0 1 2 3 ... 127        
  11. //[4]0 1 2 3 ... 127        
  12. //[5]0 1 2 3 ... 127        
  13. //[6]0 1 2 3 ... 127        
  14. //[7]0 1 2 3 ... 127                           
  15. /**********************************************
  16. //IIC Start
  17. **********************************************/
  18. /**********************************************
  19. //IIC Start
  20. **********************************************/
  21. void IIC_Start()
  22. {

  23.         OLED_SCLK_Set() ;
  24.         OLED_SDIN_Set();
  25.         OLED_SDIN_Clr();
  26.         OLED_SCLK_Clr();
  27. }

  28. /**********************************************
  29. //IIC Stop
  30. **********************************************/
  31. void IIC_Stop()
  32. {
  33. OLED_SCLK_Set() ;
  34. //        OLED_SCLK_Clr();
  35.         OLED_SDIN_Clr();
  36.         OLED_SDIN_Set();
  37.         
  38. }

  39. void IIC_Wait_Ack()
  40. {

  41.         //GPIOB->CRH &= 0XFFF0FFFF;        //设置PB12为上拉输入模式
  42.         //GPIOB->CRH |= 0x00080000;
  43. //        OLED_SDA = 1;
  44. //        delay_us(1);
  45.         //OLED_SCL = 1;
  46.         //delay_us(50000);
  47. /*        while(1)
  48.         {
  49.                 if(!OLED_SDA)                                //判断是否接收到OLED 应答信号
  50.                 {
  51.                         //GPIOB->CRH &= 0XFFF0FFFF;        //设置PB12为通用推免输出模式
  52.                         //GPIOB->CRH |= 0x00030000;
  53.                         return;
  54.                 }
  55.         }
  56. */
  57.         OLED_SCLK_Set() ;
  58.         OLED_SCLK_Clr();
  59. }
  60. /**********************************************
  61. // IIC Write byte
  62. **********************************************/

  63. void Write_IIC_Byte(unsigned char IIC_Byte)
  64. {
  65.         unsigned char i;
  66.         unsigned char m,da;
  67.         da=IIC_Byte;
  68.         OLED_SCLK_Clr();
  69.         for(i=0;i<8;i++)               
  70.         {
  71.                         m=da;
  72.                 //        OLED_SCLK_Clr();
  73.                 m=m&0x80;
  74.                 if(m==0x80)
  75.                 {OLED_SDIN_Set();}
  76.                 else OLED_SDIN_Clr();
  77.                         da=da<<1;
  78.                 OLED_SCLK_Set();
  79.                 OLED_SCLK_Clr();
  80.                 }


  81. }
  82. /**********************************************
  83. // IIC Write Command
  84. **********************************************/
  85. void Write_IIC_Command(unsigned char IIC_Command)
  86. {
  87.    IIC_Start();
  88.    Write_IIC_Byte(0x78);            //Slave address,SA0=0
  89.         IIC_Wait_Ack();        
  90.    Write_IIC_Byte(0x00);                        //write command
  91.         IIC_Wait_Ack();        
  92.    Write_IIC_Byte(IIC_Command);
  93.         IIC_Wait_Ack();        
  94.    IIC_Stop();
  95. }
  96. /**********************************************
  97. // IIC Write Data
  98. **********************************************/
  99. void Write_IIC_Data(unsigned char IIC_Data)
  100. {
  101.    IIC_Start();
  102.    Write_IIC_Byte(0x78);                        //D/C#=0; R/W#=0
  103.         IIC_Wait_Ack();        
  104.    Write_IIC_Byte(0x40);                        //write data
  105.         IIC_Wait_Ack();        
  106.    Write_IIC_Byte(IIC_Data);
  107.         IIC_Wait_Ack();        
  108.    IIC_Stop();
  109. }
  110. void OLED_WR_Byte(unsigned dat,unsigned cmd)
  111. {
  112.         if(cmd)
  113.                         {

  114.    Write_IIC_Data(dat);
  115.    
  116.                 }
  117.         else {
  118.    Write_IIC_Command(dat);
  119.                
  120.         }


  121. }


  122. /********************************************
  123. // fill_Picture
  124. ********************************************/
  125. void fill_picture(unsigned char fill_Data)
  126. {
  127.         unsigned char m,n;
  128.         for(m=0;m<8;m++)
  129.         {
  130.                 OLED_WR_Byte(0xb0+m,0);                //page0-page1
  131.                 OLED_WR_Byte(0x00,0);                //low column start address
  132.                 OLED_WR_Byte(0x10,0);                //high column start address
  133.                 for(n=0;n<128;n++)
  134.                         {
  135.                                 OLED_WR_Byte(fill_Data,1);
  136.                         }
  137.         }
  138. }


  139. /***********************Delay****************************************/
  140. void Delay_50ms(unsigned int Del_50ms)
  141. {
  142.         unsigned int m;
  143.         for(;Del_50ms>0;Del_50ms--)
  144.                 for(m=6245;m>0;m--);
  145. }

  146. void Delay_1ms(unsigned int Del_1ms)
  147. {
  148.         unsigned char j;
  149.         while(Del_1ms--)
  150.         {        
  151.                 for(j=0;j<123;j++);
  152.         }
  153. }

  154. //坐标设置

  155.         void OLED_Set_Pos(unsigned char x, unsigned char y)
  156. {         OLED_WR_Byte(0xb0+y,OLED_CMD);
  157.         OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
  158.         OLED_WR_Byte((x&0x0f),OLED_CMD);
  159. }            
  160. //开启OLED显示   
  161. void OLED_Display_On(void)
  162. {
  163.         OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
  164.         OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ON
  165.         OLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
  166. }
  167. //关闭OLED显示     
  168. void OLED_Display_Off(void)
  169. {
  170.         OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
  171.         OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFF
  172.         OLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
  173. }                                            
  174. //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!         
  175. void OLED_Clear(void)  
  176. {  
  177.         u8 i,n;                    
  178.         for(i=0;i<8;i++)  
  179.         {  
  180.                 OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
  181.                 OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址
  182.                 OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   
  183.                 for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
  184.         } //更新显示
  185. }
  186. void OLED_On(void)  
  187. {  
  188.         u8 i,n;                    
  189.         for(i=0;i<8;i++)  
  190.         {  
  191.                 OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
  192.                 OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址
  193.                 OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   
  194.                 for(n=0;n<128;n++)OLED_WR_Byte(1,OLED_DATA);
  195.         } //更新显示
  196. }
  197. //在指定位置显示一个字符,包括部分字符
  198. //x:0~127
  199. //y:0~63
  200. //mode:0,反白显示;1,正常显示                                 
  201. //size:选择字体 16/12
  202. void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
  203. {              
  204.         unsigned char c=0,i=0;        
  205.                 c=chr-' ';//得到偏移后的值                        
  206.                 if(x>Max_Column-1){x=0;y=y+2;}
  207.                 if(Char_Size ==16)
  208.                         {
  209.                         OLED_Set_Pos(x,y);        
  210.                         for(i=0;i<8;i++)
  211.                         OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
  212.                         OLED_Set_Pos(x,y+1);
  213.                         for(i=0;i<8;i++)
  214.                         OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
  215.                         }
  216.                         else {        
  217.                                 OLED_Set_Pos(x,y);
  218.                                 for(i=0;i<6;i++)
  219.                                 OLED_WR_Byte(F6x8[c][i],OLED_DATA);
  220.                                 
  221.                         }
  222. }
  223. //m^n函数
  224. u32 oled_pow(u8 m,u8 n)
  225. {
  226.         u32 result=1;         
  227.         while(n--)result*=m;   
  228.         return result;
  229. }                                 
  230. //显示2个数字
  231. //x,y :起点坐标         
  232. //len :数字的位数
  233. //size:字体大小
  234. //mode:模式        0,填充模式;1,叠加模式
  235. //num:数值(0~4294967295);                           
  236. void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
  237. {                 
  238.         u8 t,temp;
  239.         u8 enshow=0;                                                   
  240.         for(t=0;t<len;t++)
  241.         {
  242.                 temp=(num/oled_pow(10,len-t-1))%10;
  243.                 if(enshow==0&&t<(len-1))
  244.                 {
  245.                         if(temp==0)
  246.                         {
  247.                                 OLED_ShowChar(x+(size2/2)*t,y,' ',size2);
  248.                                 continue;
  249.                         }else enshow=1;
  250.                           
  251.                 }
  252.                  OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2);
  253.         }
  254. }
  255. //显示一个字符号串
  256. void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
  257. {
  258.         unsigned char j=0;
  259.         while (chr[j]!='\0')
  260.         {                OLED_ShowChar(x,y,chr[j],Char_Size);
  261.                         x+=8;
  262.                 if(x>120){x=0;y+=2;}
  263.                         j++;
  264.         }
  265. }
  266. //显示汉字
  267. void OLED_ShowCHinese(u8 x,u8 y,u8 no)
  268. {                                 
  269.         u8 t,adder=0;
  270.         OLED_Set_Pos(x,y);        
  271.     for(t=0;t<16;t++)
  272.                 {
  273.                                 OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
  274.                                 adder+=1;
  275.      }        
  276.                 OLED_Set_Pos(x,y+1);        
  277.     for(t=0;t<16;t++)
  278.                         {        
  279.                                 OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
  280.                                 adder+=1;
  281.       }                                       
  282. }
  283. /***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
  284. void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
  285. {         
  286. unsigned int j=0;
  287. unsigned char x,y;
  288.   
  289.   if(y1%8==0) y=y1/8;      
  290.   else y=y1/8+1;
  291.         for(y=y0;y<y1;y++)
  292.         {
  293.                 OLED_Set_Pos(x0,y);
  294.     for(x=x0;x<x1;x++)
  295.             {      
  296.                     OLED_WR_Byte(BMP[j++],OLED_DATA);                    
  297.             }
  298.         }
  299. }

  300. //初始化SSD1306                                            
  301. void OLED_Init(void)
  302. {         

  303.          

  304.          
  305.          GPIO_InitTypeDef  GPIO_InitStructure;
  306.          

  307.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);         //使能A端口时钟
  308.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;         
  309.          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                  //推挽输出
  310.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
  311.          GPIO_Init(GPIOA, &GPIO_InitStructure);          //初始化GPIOD3,6
  312.          GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_7);        


  313. delay_ms(800);
  314. OLED_WR_Byte(0xAE,OLED_CMD);//--display off
  315.         OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
  316.         OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
  317.         OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  
  318.         OLED_WR_Byte(0xB0,OLED_CMD);//--set page address
  319.         OLED_WR_Byte(0x81,OLED_CMD); // contract control
  320.         OLED_WR_Byte(0xFF,OLED_CMD);//--128   
  321.         OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap
  322.         OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
  323.         OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
  324.         OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
  325.         OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
  326.         OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
  327.         OLED_WR_Byte(0x00,OLED_CMD);//
  328.         
  329.         OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
  330.         OLED_WR_Byte(0x80,OLED_CMD);//
  331.         
  332.         OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
  333.         OLED_WR_Byte(0x05,OLED_CMD);//
  334.         
  335.         OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
  336.         OLED_WR_Byte(0xF1,OLED_CMD);//
  337.         
  338.         OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
  339.         OLED_WR_Byte(0x12,OLED_CMD);//
  340.         
  341.         OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
  342.         OLED_WR_Byte(0x30,OLED_CMD);//
  343.         
  344.         OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
  345.         OLED_WR_Byte(0x14,OLED_CMD);//
  346.         
  347.         OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
  348. }
oled.h
  1. #ifndef __OLED_H
  2. #define __OLED_H                                   
  3. #include "sys.h"
  4. #include "stdlib.h"                    
  5. #define OLED_MODE 0
  6. #define SIZE 8
  7. #define XLevelL                0x00
  8. #define XLevelH                0x10
  9. #define Max_Column        128
  10. #define Max_Row                64
  11. #define        Brightness        0xFF
  12. #define X_WIDTH         128
  13. #define Y_WIDTH         64                                                              
  14. //-----------------OLED IIC端口定义----------------                                             

  15. #define OLED_SCLK_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_5)//SCL
  16. #define OLED_SCLK_Set() GPIO_SetBits(GPIOA,GPIO_Pin_5)

  17. #define OLED_SDIN_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_7)//SDA
  18. #define OLED_SDIN_Set() GPIO_SetBits(GPIOA,GPIO_Pin_7)

  19.                      
  20. #define OLED_CMD  0        //写命令
  21. #define OLED_DATA 1        //写数据


  22. //OLED控制用函数
  23. void OLED_WR_Byte(unsigned dat,unsigned cmd);  
  24. void OLED_Display_On(void);
  25. void OLED_Display_Off(void);                                                                                          
  26. void OLED_Init(void);
  27. void OLED_Clear(void);
  28. void OLED_DrawPoint(u8 x,u8 y,u8 t);
  29. void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);
  30. void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size);
  31. void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);
  32. void OLED_ShowString(u8 x,u8 y, u8 *p,u8 Char_Size);         
  33. void OLED_Set_Pos(unsigned char x, unsigned char y);
  34. void OLED_ShowCHinese(u8 x,u8 y,u8 no);
  35. void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]);
  36. void Delay_50ms(unsigned int Del_50ms);
  37. void Delay_1ms(unsigned int Del_1ms);
  38. void fill_picture(unsigned char fill_Data);
  39. void Picture();
  40. void IIC_Start();
  41. void IIC_Stop();
  42. void Write_IIC_Command(unsigned char IIC_Command);
  43. void Write_IIC_Data(unsigned char IIC_Data);
  44. void Write_IIC_Byte(unsigned char IIC_Byte);

  45. void IIC_Wait_Ack();
  46. #endif  
这里只有一点需要说明,就是Write_IIC_Byte(0x78); 写IIC地址,改地址一般默认0x78,该地址是可以通过屏幕背面的电阻修改的。
DHT11温湿度模块DHT11模块为单总线通信,一根数据线即可完成数据的交互,MCU发送数据请求后,等待模块回传数据即可,一次通讯的时间是4ms左右,速度较慢,所以只适合一般的引用场景,一次完整的数据是40bit,数据格式如下:
数据格式:8bit湿度整数数据+8bit湿度小数数据
+8bi温度整数数据+8bit温度小数数据
+8bit校验和
数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据
+8bi温度整数数据+8bit温度小数数据”所得结果的末8位。
用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集, 用户可选择读取部分数据.从模式下,DHT11接收到开始信号触发一次温湿度采集, 如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集.采集数据后转换到低速模式。
总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。 DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送80us低电平响应信号.主机发送开始信号结束后,延时等待20-40us后, 读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可, 总线由上拉电阻拉高。
根据以上信息,我们便可编写DTH11的代码:


该模块在正点原子的例程中也有,但是正点原子使用的是GPIO的位带操作,在使用MM32L073时候无法使用,M0内核好像没有位带操作,只能使用伪位带操作或者直接用函数来修改GPIO的输入输出。因为我之前在MM32L073玩了两天,所以这里也不采用位带操作,直接函数控制。
DHT11.C
  1. #include "dht11.h"
  2. #include "delay.h"
  3. void DHT11_IO_IN(void)//温湿度模块输入函数
  4. {
  5. GPIO_InitTypeDef GPIO_InitStructure;

  6. //RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB,ENABLE);
  7. GPIO_InitStructure.GPIO_Pin=IO_DHT11;
  8. GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
  9. GPIO_Init(GPIO_DHT11,&GPIO_InitStructure);
  10. }

  11. void DHT11_IO_OUT(void)//温湿度模块输出函数
  12. {
  13. GPIO_InitTypeDef GPIO_InitStructure;
  14. // RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB,ENABLE);
  15. GPIO_InitStructure.GPIO_Pin=IO_DHT11;
  16. GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  17. GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
  18. GPIO_Init(GPIO_DHT11,&GPIO_InitStructure);
  19. }

  20. //复位DHT11
  21. void DHT11_Rst(void)   
  22. {                 
  23.    DHT11_IO_OUT(); //SET OUTPUT
  24.     DHT11_DQ_Low; //DQ=0
  25.     delay_ms(20);    //拉低至少18ms
  26.     DHT11_DQ_High; //DQ=1
  27.         delay_us(30);     //主机拉高20~40us
  28. }

  29. //等待DHT11的回应
  30. //返回1:未检测到DHT11的存在
  31. //返回0:存在
  32. u8 DHT11_Check(void)   
  33. {   
  34. u8 retry=0;//定义临时变量
  35. DHT11_IO_IN();//SET INPUT
  36.   while ((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==1)&&retry<100)//DHT11会拉低40~80us
  37. {
  38.         retry++;
  39.         delay_us(1);
  40. };
  41. if(retry>=100)return 1;
  42. else retry=0;
  43.     while ((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==0)&&retry<100)//DHT11拉低后会再次拉高40~80us
  44. {
  45.         retry++;
  46.         delay_us(1);
  47. };
  48. if(retry>=100)return 1;   
  49. return 0;
  50. }
  51. //从DHT11读取一个位
  52. //返回值:1/0
  53. u8 DHT11_Read_Bit(void)  
  54. {
  55. u8 retry=0;
  56. while((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==1)&&retry<100)//等待变为低电平
  57. {
  58.         retry++;
  59.         delay_us(1);
  60. }
  61.         retry=0;
  62. while((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==0)&&retry<100)//等待变高电平
  63. {
  64.         retry++;
  65.         delay_us(1);
  66. }
  67.         delay_us(40);//等待40us
  68. if(GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==1)
  69. return 1;
  70. else
  71. return 0;   
  72. }
  73. //从DHT11读取一个字节
  74. //返回值:读到的数据
  75. u8 dat;
  76. u8 DHT11_Read_Byte(void)   
  77. {        
  78.     u8 i;
  79.     dat=0;
  80. for (i=0;i<8;i++)
  81. {
  82.    dat<<=1;
  83.     dat|=DHT11_Read_Bit();
  84.     }   
  85.     return dat;
  86. }

  87. //从DHT11读取一次数据
  88. //temp:温度值(范围:0~50°)
  89. //humi:湿度值(范围:20%~90%)
  90. //返回值:0,正常;1,读取失败
  91. u8 buf[5];
  92. u8 DHT11_Read_Data(u8 *temp,u8 *humi)   
  93. {        
  94.         
  95.         u8 i;
  96.         DHT11_Rst();
  97. if(DHT11_Check()==0)
  98. {
  99.         for(i=0;i<5;i++)//读取40位数据
  100.         {
  101.         buf[i]=DHT11_Read_Byte();
  102.         }
  103. if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
  104. {
  105.         *humi=buf[0];
  106.         *temp=buf[2];
  107. }
  108. }else return 1;
  109. return 0;   
  110. }
  111. //初始化DHT11的IO口 DQ 同时检测DHT11的存在
  112. //返回1:不存在
  113. //返回0:存在     
  114. u8 DHT11_Init(void)
  115. {   
  116.         GPIO_InitTypeDef GPIO_InitStructure;
  117.         
  118.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  119.         
  120.         GPIO_InitStructure.GPIO_Pin=IO_DHT11;
  121.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  122.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
  123.         GPIO_Init(GPIO_DHT11,&GPIO_InitStructure);
  124.         GPIO_SetBits(GPIO_DHT11,IO_DHT11);        
  125.         
  126.         DHT11_Rst();  //复位DHT11
  127.         return DHT11_Check();//等待DHT11的回应
  128. }


  129. /*****************************************************************/
dht11.h
  1. #ifndef __DHT11_H__
  2. #define __DHT11_H__


  3. #include "sys.h"

  4. #define IO_DHT11 GPIO_Pin_12 //引入中间变量,方便移植
  5. #define GPIO_DHT11 GPIOB //引入中间变量,方便移植

  6. #define DHT11_DQ_High GPIO_SetBits(GPIO_DHT11,IO_DHT11)
  7. #define DHT11_DQ_Low  GPIO_ResetBits(GPIO_DHT11,IO_DHT11)

  8. void DHT11_IO_OUT(void);//温湿度模块输出函数
  9. void DHT11_IO_IN(void); //温湿度模块输入函数
  10. u8 DHT11_Init(void);  //初始化DHT11
  11. u8   DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
  12. u8   DHT11_Read_Byte(void);             //读出一个字节
  13. u8   DHT11_Read_Bit(void);              //读出一个位
  14. u8   DHT11_Check(void);                 //检测是否存在DHT11
  15. void DHT11_Rst(void);                   //复位DHT11
  16. #endif
SHARP GP2Y1051AU0F粉尘传感器
粉尘传感器中有LED光源和光敏检测,当空气中含有粉尘时会使光发生散射,光敏元件会检测到这些散射的光而输出不同的电压,粉尘浓度的不同输出的电压也不同,该模块直接由串口输出,直接读取串口的数据,提取电压值,计算后便可获得粉尘浓度值。
串口输出参数:
波特率:2400 bit/s
数据发送格式:
起始位Vout(H) Vout(L)  Vref(H)  Vref(L)  校验位 结束位
0xAA 如:0x01 如:0x3A 如:0x00 如:0x7A 如:0xD0 0xFF
数据处理:
Vout = (Vout(H)*256+Vout(L))/1024*5
粉尘浓度计算:Ud= A*Vout,A为比例系数,一般用800。
注意:模块数据不是按照数据包输出,也不需要MCU发送指令,因此只需接模块的TX,RX悬空便可,仅仅是10ms输出一个字节,一共7个字节,结束位输出完成后,下一个10ms到来,继续输出下一个起始位,所以在接收数据时不能按照整包接收的方式,要逐个接收并判断起始位。
我采用开启串口接收中断,每一次触发中断接收数据后都做数据的判断处理:
  1. void UART1_IRQHandler(void)                 //串口1中断服务程序
  2. {
  3.     u8 Res;
  4.     if(UART_GetITStatus(UART1, UART_IT_RXIEN)  != RESET) { //接收中断(接收到的数据必须是0x0d 0x0a结尾)
  5.         UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
  6.         Res = UART_ReceiveData(UART1);  //读取接收到的数据

  7.                 PM2_5_DATA_COUNT(Res);

  8.     }

  9. }
接收到一位数据后进入数据处理函数PM2_5_DATA_COUNT();
  1. float vout;
  2. void PM2_5_DATA_COUNT(u8 dat)
  3. {
  4.         u16 sum = 0;//用于计算校验和
  5.         
  6.         if(dat==170)//判断起始位 ,起始位固定位0xAA=170,
  7.         {
  8.                 j = 0;
  9.                 DST_Buffer[j] = dat;
  10.         }
  11.         else{
  12.                 j=j+1;
  13.                 DST_Buffer[j] = dat;
  14.                 if(j==6)
  15.                 {
  16.                         sum = DST_Buffer[1]+DST_Buffer[2]+DST_Buffer[3]+DST_Buffer[4];
  17.                         if(sum==DST_Buffer[5]&&DST_Buffer[6]==0xFF)
  18.                         {
  19.                         vout = (float)((DST_Buffer[1]*256+DST_Buffer[2]));
  20.                         vout = vout/(1024*5);
  21.                         PM2_5 = 1000*vout;

  22.                         }               
  23.                 }
  24.         }
  25. }
其中vout变量可以定义为局部变量,这里我为了调试拿了出来,但是肯定要定义成浮点型,因为计算的电压是小数,此函数是为了找到起始位,并将数据按照数据格式排列在数组中,这样方便最后取数据去计算。我在一开始使用的是DMA的接收方式,数据会错乱摆放,导致寻找数据很麻烦,后面才换成串口中断的方式。
如果想要提高精度可将多次测量进行平均,我因为太懒,不搞了。
主函数:
  1. int main(void)
  2. {
  3.         u8 wd=0;      
  4.         u8 sd=0;
  5.         delay_init();
  6.         DHT11_Init();
  7.     uart_initwBaudRate(2400);
  8.         OLED_Init();                        //初始化OLED  
  9.         OLED_Clear();
  10.         while(1)
  11.         {               
  12.                 OLED_ShowString(0,0,"PM2.5:",16);
  13.                 OLED_ShowString(0,3,"temp:",16);  
  14.                 OLED_ShowString(0,6,"humi:",16);                 
  15.                 OLED_ShowString(85,0,"ug/m3",16);
  16.                 OLED_ShowString(85,3,"C",16);
  17.                 OLED_ShowString(85,6,"%RH",16);                 
  18.                 OLED_ShowNum(45,0,PM2_5,3,16);        
  19.                 DHT11_Read_Data(&wd,&sd);//读取温湿度值
  20.                 OLED_ShowNum(50,3,wd,3,16);
  21.                 OLED_ShowNum(50,6,sd,3,16);

  22.         }
  23. }
因为PM2.5是中断处理的,主函数中添加一个温湿度的读取,所有的数据打印在OLED便可。最后看一下我的实物图。
图片.jpg
1627356814312.gif
动态图展示了空气质量变化的动态效果,因为室内不能用烟雾,测试用的电子烟,实际上不属于粉尘,所以数值变化不明显。
此文到此便结束了,因为只是瞎倒腾,所以使用的模块都是比较粗糙的,外壳也没有,我的3D打印机还是没钱买,凑活过吧。
简单效果,很多人都会,不喜勿喷。

打赏榜单

21小跑堂 打赏了 200.00 元 2021-07-28
理由:恭喜通过原创文章审核!请多多加油哦!

 楼主| 呐咯密密 发表于 2021-7-27 13:10 | 显示全部楼层
添加一下工程 MM32F103.rar (3.25 MB, 下载次数: 69)
littlelida 发表于 2021-7-27 15:02 | 显示全部楼层
DHT11可能不需要us延迟

评论

主机需要发送80us的低电平响应信号呀,当然这个时间不是很严苛,用MM32L073也是可以的,但是不准,不太舒服  发表于 2021-7-27 15:07
Gavin3389 发表于 2021-7-28 08:51 | 显示全部楼层
你这动图,干得漂亮
15302656163 发表于 2021-7-29 09:56 | 显示全部楼层
以后不要在某宝花高价买了,找我买,优惠给您!!!MM32F103CBT6/MM32F003TW  、MM32F0010A1T  都有,绝对惊喜价!
hjl2832 发表于 2021-7-29 12:42 | 显示全部楼层
步错,PM2.5+OLED显示。
ulystronglll 发表于 2021-8-1 22:14 | 显示全部楼层
资料还是相当全面的,开发板也不错                                 
lzmm 发表于 2021-8-1 22:14 | 显示全部楼层
正准备学习的朋友推荐下载。                                 
pklong 发表于 2021-8-1 22:14 | 显示全部楼层
  应用还是很广泛的                                 
aspoke 发表于 2021-8-1 22:14 | 显示全部楼层
资料还是相当全面的,开发板也不错                                 
maudlu 发表于 2021-8-1 22:14 | 显示全部楼层
以后多交流交流                  
hellosdc 发表于 2021-8-1 22:14 | 显示全部楼层
以后学习就方便了                                 
chenci2013 发表于 2021-8-1 22:14 | 显示全部楼层
相当全的资料,很适合初学者                                 
linfelix 发表于 2021-8-1 22:14 | 显示全部楼层
有时间需要好好看看   不错                                 
benjaminka 发表于 2021-8-1 22:14 | 显示全部楼层
楼主太好了,非常感谢            
qiufengsd 发表于 2021-8-1 22:15 | 显示全部楼层
资料的确是很全面                                 
tabmone 发表于 2021-8-1 22:15 | 显示全部楼层
资料很实用,谢谢楼主!                                 
myiclife 发表于 2021-8-1 22:15 | 显示全部楼层
感谢分享,提供的例程很实用                                 
pixhw 发表于 2021-8-1 22:15 | 显示全部楼层
以后学习就方便了                                 
iamaiqiyi 发表于 2021-8-1 22:15 | 显示全部楼层
具有一定的参考价值,需要的朋友可以参考一下。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:苏州澜宭自动化科技嵌入式工程师
简介:本人从事磁编码器研发工作,负责开发2500线增量式磁编码器以及17位、23位绝对值式磁编码器,拥有多年嵌入式开发经验,精通STM32、GD32、N32等多种品牌单片机,熟练使用单片机各种外设。

567

主题

4086

帖子

56

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