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

2023-10-31 19:59

本文主要是介绍[MM32软件]【瞎折腾系列】MM32F103空气质量检测仪,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

还是我的风格,开篇先啰嗦:
闲来无事,太难的不会,就想玩玩手里的吃灰板子。
去年在灵动的活动中获得一块MM32L073为主控的开发板,型号为eMiniBoard MB-023。当时测评就写了一个开箱和串口测试,现在重新捡起来,玩点小应用,因为手头的传感器有限,只能做一个空气质量检测仪,主要包含三个功能:空气温度检测,空气湿度检测和PM2.5浓度检测。
但是在调试温湿度检测的时候翻车了,使用的传感器是DHT11模块,该模块在使用时需要微秒级别的延时,但是我在MM32L073的库中找不到us的延时函数,当然排除空函数的粗延时,使用SysTick只能做到ms级别的延时,自己写一个us的延时函数,发现根本不起作用,具体如下:

void delay_init(){RCC_ClocksTypeDef RCC_Clocks;if (SysTick_Config(SystemCoreClock / 1000)){ /* Capture error */ while (1);}/* Configure the SysTick handler priority */NVIC_SetPriority(SysTick_IRQn, 0x0);//SysTick中断优先级设置}

以上是官方库中的延时函数初始化,SysTick_Config函数中给定系统时钟(48M)除以1000,使SysTick以1毫秒进入一次中断,如果我将SystemCoreClock 除以1000000应该是1us进入一次中断,但是这个会导致延时函数卡死,在debug后发现程序卡死在SysTick_Config()函数,继续追踪:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks){if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk)  return (1);      /* Reload value impossible */SysTick->LOAD  = ticks - 1;                                  /* set reload register */NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Systick Interrupt */SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |SysTick_CTRL_TICKINT_Msk   |SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */return (0);                                                  /* Function successful */}

程序卡死在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 

#include "oled.h"#include "stdlib.h"#include "oledfont.h"           #include "delay.h"//OLED的显存//存放格式如下.//[0]0 1 2 3 ... 127        //[1]0 1 2 3 ... 127        //[2]0 1 2 3 ... 127        //[3]0 1 2 3 ... 127        //[4]0 1 2 3 ... 127        //[5]0 1 2 3 ... 127        //[6]0 1 2 3 ... 127        //[7]0 1 2 3 ... 127                            /**********************************************//IIC Start**********************************************//**********************************************//IIC Start**********************************************/void IIC_Start(){OLED_SCLK_Set() ;OLED_SDIN_Set();OLED_SDIN_Clr();OLED_SCLK_Clr();}/**********************************************//IIC Stop**********************************************/void IIC_Stop(){OLED_SCLK_Set() ;//        OLED_SCLK_Clr();OLED_SDIN_Clr();OLED_SDIN_Set();}void IIC_Wait_Ack(){//GPIOB->CRH &= 0XFFF0FFFF;        //设置PB12为上拉输入模式//GPIOB->CRH |= 0x00080000;//        OLED_SDA = 1;//        delay_us(1);//OLED_SCL = 1;//delay_us(50000);/*        while(1){if(!OLED_SDA)                                //判断是否接收到OLED 应答信号{//GPIOB->CRH &= 0XFFF0FFFF;        //设置PB12为通用推免输出模式//GPIOB->CRH |= 0x00030000;return;}}*/OLED_SCLK_Set() ;OLED_SCLK_Clr();}/**********************************************// IIC Write byte**********************************************/void Write_IIC_Byte(unsigned char IIC_Byte){unsigned char i;unsigned char m,da;da=IIC_Byte;OLED_SCLK_Clr();for(i=0;i<8;i++)                {m=da;//        OLED_SCLK_Clr();m=m&0x80;if(m==0x80){OLED_SDIN_Set();}else OLED_SDIN_Clr();da=da<<1;OLED_SCLK_Set();OLED_SCLK_Clr();}}/**********************************************// IIC Write Command**********************************************/void Write_IIC_Command(unsigned char IIC_Command){IIC_Start();Write_IIC_Byte(0x78);            //Slave address,SA0=0IIC_Wait_Ack();        Write_IIC_Byte(0x00);                        //write commandIIC_Wait_Ack();        Write_IIC_Byte(IIC_Command); IIC_Wait_Ack();        IIC_Stop();}/**********************************************// IIC Write Data**********************************************/void Write_IIC_Data(unsigned char IIC_Data){IIC_Start();Write_IIC_Byte(0x78);                        //D/C#=0; R/W#=0IIC_Wait_Ack();        Write_IIC_Byte(0x40);                        //write dataIIC_Wait_Ack();        Write_IIC_Byte(IIC_Data);IIC_Wait_Ack();        IIC_Stop();}void OLED_WR_Byte(unsigned dat,unsigned cmd){if(cmd){Write_IIC_Data(dat);}else {Write_IIC_Command(dat);}}/********************************************// fill_Picture********************************************/void fill_picture(unsigned char fill_Data){unsigned char m,n;for(m=0;m<8;m++){OLED_WR_Byte(0xb0+m,0);                //page0-page1OLED_WR_Byte(0x00,0);                //low column start addressOLED_WR_Byte(0x10,0);                //high column start addressfor(n=0;n<128;n++){OLED_WR_Byte(fill_Data,1);}}}/***********************Delay****************************************/void Delay_50ms(unsigned int Del_50ms){unsigned int m;for(;Del_50ms>0;Del_50ms--)for(m=6245;m>0;m--);}void Delay_1ms(unsigned int Del_1ms){unsigned char j;while(Del_1ms--){        for(j=0;j<123;j++);}}//坐标设置void OLED_Set_Pos(unsigned char x, unsigned char y) {         OLED_WR_Byte(0xb0+y,OLED_CMD);OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);OLED_WR_Byte((x&0x0f),OLED_CMD); }             //开启OLED显示    void OLED_Display_On(void){OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ONOLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON}//关闭OLED显示     void OLED_Display_Off(void){OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFFOLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF}                                            //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!          void OLED_Clear(void)  {  u8 i,n;                    for(i=0;i<8;i++)  {  OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); } //更新显示}void OLED_On(void)  {  u8 i,n;                    for(i=0;i<8;i++)  {  OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   for(n=0;n<128;n++)OLED_WR_Byte(1,OLED_DATA); } //更新显示}//在指定位置显示一个字符,包括部分字符//x:0~127//y:0~63//mode:0,反白显示;1,正常显示                                 //size:选择字体 16/12 void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size){              unsigned char c=0,i=0;        c=chr-' ';//得到偏移后的值                        if(x>Max_Column-1){x=0;y=y+2;}if(Char_Size ==16){OLED_Set_Pos(x,y);        for(i=0;i<8;i++)OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);OLED_Set_Pos(x,y+1);for(i=0;i<8;i++)OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);}else {        OLED_Set_Pos(x,y);for(i=0;i<6;i++)OLED_WR_Byte(F6x8[c][i],OLED_DATA);}}//m^n函数u32 oled_pow(u8 m,u8 n){u32 result=1;         while(n--)result*=m;    return result;}                                  //显示2个数字//x,y :起点坐标         //len :数字的位数//size:字体大小//mode:模式        0,填充模式;1,叠加模式//num:数值(0~4294967295);                           void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2){                 u8 t,temp;u8 enshow=0;                                                   for(t=0;t<len;t++){temp=(num/oled_pow(10,len-t-1))%10;if(enshow==0&&t<(len-1)){if(temp==0){OLED_ShowChar(x+(size2/2)*t,y,' ',size2);continue;}else enshow=1; }OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2); }} //显示一个字符号串void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size){unsigned char j=0;while (chr[j]!='\0'){                OLED_ShowChar(x,y,chr[j],Char_Size);x+=8;if(x>120){x=0;y+=2;}j++;}}//显示汉字void OLED_ShowCHinese(u8 x,u8 y,u8 no){                                  u8 t,adder=0;OLED_Set_Pos(x,y);        for(t=0;t<16;t++){OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);adder+=1;}        OLED_Set_Pos(x,y+1);        for(t=0;t<16;t++){        OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);adder+=1;}                                        }/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]){         unsigned int j=0;unsigned char x,y;if(y1%8==0) y=y1/8;      else y=y1/8+1;for(y=y0;y<y1;y++){OLED_Set_Pos(x0,y);for(x=x0;x<x1;x++){      OLED_WR_Byte(BMP[j++],OLED_DATA);                    }}} //初始化SSD1306                                            void OLED_Init(void){         GPIO_InitTypeDef  GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);         //使能A端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                  //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHzGPIO_Init(GPIOA, &GPIO_InitStructure);          //初始化GPIOD3,6GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_7);        delay_ms(800);OLED_WR_Byte(0xAE,OLED_CMD);//--display offOLED_WR_Byte(0x00,OLED_CMD);//---set low column addressOLED_WR_Byte(0x10,OLED_CMD);//---set high column addressOLED_WR_Byte(0x40,OLED_CMD);//--set start line address  OLED_WR_Byte(0xB0,OLED_CMD);//--set page addressOLED_WR_Byte(0x81,OLED_CMD); // contract controlOLED_WR_Byte(0xFF,OLED_CMD);//--128   OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverseOLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 dutyOLED_WR_Byte(0xC8,OLED_CMD);//Com scan directionOLED_WR_Byte(0xD3,OLED_CMD);//-set display offsetOLED_WR_Byte(0x00,OLED_CMD);//OLED_WR_Byte(0xD5,OLED_CMD);//set osc divisionOLED_WR_Byte(0x80,OLED_CMD);//OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode offOLED_WR_Byte(0x05,OLED_CMD);//OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge PeriodOLED_WR_Byte(0xF1,OLED_CMD);//OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartionOLED_WR_Byte(0x12,OLED_CMD);//OLED_WR_Byte(0xDB,OLED_CMD);//set VcomhOLED_WR_Byte(0x30,OLED_CMD);//OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enableOLED_WR_Byte(0x14,OLED_CMD);//OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel} 

oled.h 

#ifndef __OLED_H#define __OLED_H                                   #include "sys.h"#include "stdlib.h"                    #define OLED_MODE 0#define SIZE 8#define XLevelL                0x00#define XLevelH                0x10#define Max_Column        128#define Max_Row                64#define        Brightness        0xFF #define X_WIDTH         128#define Y_WIDTH         64                                                              //-----------------OLED IIC端口定义----------------                                             #define OLED_SCLK_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_5)//SCL#define OLED_SCLK_Set() GPIO_SetBits(GPIOA,GPIO_Pin_5)#define OLED_SDIN_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_7)//SDA#define OLED_SDIN_Set() GPIO_SetBits(GPIOA,GPIO_Pin_7)#define OLED_CMD  0        //写命令#define OLED_DATA 1        //写数据//OLED控制用函数void OLED_WR_Byte(unsigned dat,unsigned cmd);  void OLED_Display_On(void);void OLED_Display_Off(void);                                                                                          void OLED_Init(void);void OLED_Clear(void);void OLED_DrawPoint(u8 x,u8 y,u8 t);void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size);void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);void OLED_ShowString(u8 x,u8 y, u8 *p,u8 Char_Size);         void OLED_Set_Pos(unsigned char x, unsigned char y);void OLED_ShowCHinese(u8 x,u8 y,u8 no);void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]);void Delay_50ms(unsigned int Del_50ms);void Delay_1ms(unsigned int Del_1ms);void fill_picture(unsigned char fill_Data);void Picture();void IIC_Start();void IIC_Stop();void Write_IIC_Command(unsigned char IIC_Command);void Write_IIC_Data(unsigned char IIC_Data);void Write_IIC_Byte(unsigned char IIC_Byte);void IIC_Wait_Ack();#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

 

#include "dht11.h"#include "delay.h"void DHT11_IO_IN(void)//温湿度模块输入函数{GPIO_InitTypeDef GPIO_InitStructure;//RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB,ENABLE);GPIO_InitStructure.GPIO_Pin=IO_DHT11;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;GPIO_Init(GPIO_DHT11,&GPIO_InitStructure);}void DHT11_IO_OUT(void)//温湿度模块输出函数{GPIO_InitTypeDef GPIO_InitStructure;// RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB,ENABLE);GPIO_InitStructure.GPIO_Pin=IO_DHT11;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_Init(GPIO_DHT11,&GPIO_InitStructure);}//复位DHT11void DHT11_Rst(void)   {                 DHT11_IO_OUT(); //SET OUTPUTDHT11_DQ_Low; //DQ=0delay_ms(20);    //拉低至少18msDHT11_DQ_High; //DQ=1 delay_us(30);     //主机拉高20~40us}//等待DHT11的回应//返回1:未检测到DHT11的存在//返回0:存在u8 DHT11_Check(void)    {   u8 retry=0;//定义临时变量DHT11_IO_IN();//SET INPUT while ((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==1)&&retry<100)//DHT11会拉低40~80us{retry++;delay_us(1);}; if(retry>=100)return 1;else retry=0;while ((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==0)&&retry<100)//DHT11拉低后会再次拉高40~80us{retry++;delay_us(1);};if(retry>=100)return 1;    return 0;}//从DHT11读取一个位//返回值:1/0u8 DHT11_Read_Bit(void)  {u8 retry=0;while((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==1)&&retry<100)//等待变为低电平{retry++;delay_us(1);}retry=0;while((GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==0)&&retry<100)//等待变高电平{retry++;delay_us(1);}delay_us(40);//等待40usif(GPIO_ReadInputDataBit(GPIO_DHT11,IO_DHT11)==1)return 1;else return 0;   }//从DHT11读取一个字节//返回值:读到的数据u8 dat;u8 DHT11_Read_Byte(void)    {        u8 i;dat=0;for (i=0;i<8;i++) {dat<<=1; dat|=DHT11_Read_Bit();}    return dat;}//从DHT11读取一次数据//temp:温度值(范围:0~50°)//humi:湿度值(范围:20%~90%)//返回值:0,正常;1,读取失败u8 buf[5];u8 DHT11_Read_Data(u8 *temp,u8 *humi)    {        u8 i;DHT11_Rst();if(DHT11_Check()==0){for(i=0;i<5;i++)//读取40位数据{buf[i]=DHT11_Read_Byte();}if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]){*humi=buf[0];*temp=buf[2];}}else return 1;return 0;    }//初始化DHT11的IO口 DQ 同时检测DHT11的存在//返回1:不存在//返回0:存在     u8 DHT11_Init(void){   GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitStructure.GPIO_Pin=IO_DHT11;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_Init(GPIO_DHT11,&GPIO_InitStructure);GPIO_SetBits(GPIO_DHT11,IO_DHT11);        DHT11_Rst();  //复位DHT11return DHT11_Check();//等待DHT11的回应}/*****************************************************************/

dht11.h

#ifndef __DHT11_H__#define __DHT11_H__#include "sys.h"#define IO_DHT11 GPIO_Pin_12 //引入中间变量,方便移植#define GPIO_DHT11 GPIOB //引入中间变量,方便移植#define DHT11_DQ_High GPIO_SetBits(GPIO_DHT11,IO_DHT11) #define DHT11_DQ_Low  GPIO_ResetBits(GPIO_DHT11,IO_DHT11)void DHT11_IO_OUT(void);//温湿度模块输出函数void DHT11_IO_IN(void); //温湿度模块输入函数u8 DHT11_Init(void);  //初始化DHT11u8   DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度u8   DHT11_Read_Byte(void);             //读出一个字节u8   DHT11_Read_Bit(void);              //读出一个位u8   DHT11_Check(void);                 //检测是否存在DHT11void DHT11_Rst(void);                   //复位DHT11 #endif

SHARP GP2Y1051AU0F粉尘传感器
粉尘传感器中有LED光源和光敏检测,当空气中含有粉尘时会使光发生散射,光敏元件会检测到这些散射的光而输出不同的电压,粉尘浓度的不同输出的电压也不同,该模块直接由串口输出,直接读取串口的数据,提取电压值,计算后便可获得粉尘浓度值。
串口输出参数:
波特率:2400 bit/s
数据发送格式:

起始位Vout(H)Vout(L)  Vref(H)  Vref(L)  校验位结束位
0xAA如:0x01如:0x3A如:0x00如:0x7A如:0xD00xFF

数据处理:
Vout = (Vout(H)*256+Vout(L))/1024*5
粉尘浓度计算:Ud= A*Vout,A为比例系数,一般用800。
注意:模块数据不是按照数据包输出,也不需要MCU发送指令,因此只需接模块的TX,RX悬空便可,仅仅是10ms输出一个字节,一共7个字节,结束位输出完成后,下一个10ms到来,继续输出下一个起始位,所以在接收数据时不能按照整包接收的方式,要逐个接收并判断起始位。


我采用开启串口接收中断,每一次触发中断接收数据后都做数据的判断处理:

void UART1_IRQHandler(void)                 //串口1中断服务程序{u8 Res;if(UART_GetITStatus(UART1, UART_IT_RXIEN)  != RESET) { //接收中断(接收到的数据必须是0x0d 0x0a结尾)UART_ClearITPendingBit(UART1, UART_IT_RXIEN);Res = UART_ReceiveData(UART1);  //读取接收到的数据PM2_5_DATA_COUNT(Res);}}

接收到一位数据后进入数据处理函数PM2_5_DATA_COUNT(); 

 

float vout;void PM2_5_DATA_COUNT(u8 dat){u16 sum = 0;//用于计算校验和if(dat==170)//判断起始位 ,起始位固定位0xAA=170,{j = 0;DST_Buffer[j] = dat;}else{j=j+1;DST_Buffer[j] = dat;if(j==6){sum = DST_Buffer[1]+DST_Buffer[2]+DST_Buffer[3]+DST_Buffer[4];if(sum==DST_Buffer[5]&&DST_Buffer[6]==0xFF){vout = (float)((DST_Buffer[1]*256+DST_Buffer[2]));vout = vout/(1024*5);PM2_5 = 1000*vout;}                }}}

其中vout变量可以定义为局部变量,这里我为了调试拿了出来,但是肯定要定义成浮点型,因为计算的电压是小数,此函数是为了找到起始位,并将数据按照数据格式排列在数组中,这样方便最后取数据去计算。我在一开始使用的是DMA的接收方式,数据会错乱摆放,导致寻找数据很麻烦,后面才换成串口中断的方式。
如果想要提高精度可将多次测量进行平均,我因为太懒,不搞了。


主函数:

int main(void){u8 wd=0;      u8 sd=0;delay_init();DHT11_Init();uart_initwBaudRate(2400);OLED_Init();                        //初始化OLED  OLED_Clear(); while(1) {                OLED_ShowString(0,0,"PM2.5:",16); OLED_ShowString(0,3,"temp:",16);  OLED_ShowString(0,6,"humi:",16);                 OLED_ShowString(85,0,"ug/m3",16); OLED_ShowString(85,3,"C",16); OLED_ShowString(85,6,"%RH",16);                 OLED_ShowNum(45,0,PM2_5,3,16);        DHT11_Read_Data(&wd,&sd);//读取温湿度值 OLED_ShowNum(50,3,wd,3,16);OLED_ShowNum(50,6,sd,3,16);}}

因为PM2.5是中断处理的,主函数中添加一个温湿度的读取,所有的数据打印在OLED便可。最后看一下我的实物图。

 



动态图展示了空气质量变化的动态效果,因为室内不能用烟雾,测试用的电子烟,实际上不属于粉尘,所以数值变化不明显。
此文到此便结束了,因为只是瞎倒腾,所以使用的模块都是比较粗糙的,外壳也没有,我的3D打印机还是没钱买,凑活过吧。
简单效果,很多人都会,不喜勿喷。
---------------------
作者:呐咯密密
链接:https://bbs.21ic.com/icview-3150216-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。 

 

这篇关于[MM32软件]【瞎折腾系列】MM32F103空气质量检测仪的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/317765

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

软件设计师备考——计算机系统

学习内容源自「软件设计师」 上午题 #1 计算机系统_哔哩哔哩_bilibili 目录 1.1.1 计算机系统硬件基本组成 1.1.2 中央处理单元 1.CPU 的功能 1)运算器 2)控制器 RISC && CISC 流水线控制 存储器  Cache 中断 输入输出IO控制方式 程序查询方式 中断驱动方式 直接存储器方式(DMA)  ​编辑 总线 ​编辑

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

免费也能高质量!2024年免费录屏软件深度对比评测

我公司因为客户覆盖面广的原因经常会开远程会议,有时候说的内容比较广需要引用多份的数据,我记录起来有一定难度,所以一般都用录屏工具来记录会议内容。这次我们来一起探索有什么免费录屏工具可以提高我们的工作效率吧。 1.福晰录屏大师 链接直达:https://www.foxitsoftware.cn/REC/  录屏软件录屏功能就是本职,这款录屏工具在录屏模式上提供了多种选项,可以选择屏幕录制、窗口

HomeBank:开源免费的个人财务管理软件

在个人财务管理领域,找到一个既免费又开源的解决方案并非易事。HomeBank&nbsp;正是这样一个项目,它不仅提供了强大的功能,还拥有一个活跃的社区,不断推动其发展和完善。 开源免费:HomeBank 是一个完全开源的项目,用户可以自由地使用、修改和分发。用户友好的界面:提供直观的图形用户界面,使得非技术用户也能轻松上手。数据导入支持:支持从 Quicken、Microsoft Money

GPT系列之:GPT-1,GPT-2,GPT-3详细解读

一、GPT1 论文:Improving Language Understanding by Generative Pre-Training 链接:https://cdn.openai.com/research-covers/languageunsupervised/language_understanding_paper.pdf 启发点:生成loss和微调loss同时作用,让下游任务来适应预训

PDF 软件如何帮助您编辑、转换和保护文件。

如何找到最好的 PDF 编辑器。 无论您是在为您的企业寻找更高效的 PDF 解决方案,还是尝试组织和编辑主文档,PDF 编辑器都可以在一个地方提供您需要的所有工具。市面上有很多 PDF 编辑器 — 在决定哪个最适合您时,请考虑这些因素。 1. 确定您的 PDF 文档软件需求。 不同的 PDF 文档软件程序可以具有不同的功能,因此在决定哪个是最适合您的 PDF 软件之前,请花点时间评估您的