一.項目背景
? ? ? ? 最近為了完成老師交付的任務,遂重制了一下小項目用STM32做一個小型的環境監測系統。
? ? ? ? 項目整體示意框圖如下:
二.器件選擇
- 單片機(STM32F103)
- 數字溫濕度模塊(DHT11)
- 液晶顯示模塊(0.8寸OLED)
- 粉塵傳感器模塊(GP2Y10)
- 報警模塊(蜂鳴器)
- 按鍵控制模塊(獨立按鍵)
????????由于筆者覺得時鐘模塊沒什么必要性,就沒再加DS1302上去了。?
三.PCB繪制
? ? ? ? PCB推薦使用嘉立創專業版繪制,對于初學者來說簡單易上手,而且這種DIY的小玩意繪制的PCB大小控制在10*10以內還可以免費打樣。
? ? ? ? 嘉立創很方便的一點是可以使用它內部自帶的在線庫,省去了自己畫封裝畫元件的步驟,非常簡單,但是注意要辨別這里的元件封裝的正確性,因為有些元件封裝是用戶貢獻的,所以有些地方不一定正確,各位讀者如果是小白的話一定要注意辨別是否符合自己的需求!!!
? ? ? ? 這里PCB布線也沒什么好說的,很簡單的幾個模塊,而且板子的大小很大空間完全足夠布線。
四.核心代碼
以下只對部分核心代碼做展示。
項目文件目錄
GP2Y10粉塵傳感器的驅動程序
GP2Y10.h
#ifndef __GP2Y10_H__
#define __GP2Y10_H__#include <stdint.h>
#include <stdio.h>
#include "main.h"
#include "stm32f1xx_hal.h"
#include "driver_timer.h"
#include "driver_lcd.h"extern ADC_HandleTypeDef hadc1;#define LIMIT(x, min, max) ( (x) < (min) ? (min) : ( (x) > (max) ? (max) : (x) ) ) // 限幅函數
#define GP2Y10_LED_ON() HAL_GPIO_WritePin(GP_LED_GPIO_Port, GP_LED_Pin, GPIO_PIN_RESET) // 傳感器LED燈開
#define GP2Y10_LED_OFF() HAL_GPIO_WritePin(GP_LED_GPIO_Port, GP_LED_Pin, GPIO_PIN_SET)// 傳感器LED燈關#define PM25_LED_H HAL_GPIO_WritePin(GP_LED_GPIO_Port, GP_LED_Pin, GPIO_PIN_SET);
#define PM25_LED_L HAL_GPIO_WritePin(GP_LED_GPIO_Port, GP_LED_Pin, GPIO_PIN_RESET);#define GP2Y10_SAMP_TIME 280 // 傳感器采樣時間280us
#define GP2Y10_LEDON_TIME 320 // LED燈開持續時間
#define GP2Y10_PULSE_PERIOD 10000 // 傳感器測量脈沖一個周期的時間#define PM25_READ_TIMES 20void GP2Y10_Init(void);
float GP2Y10_Value(void);
float Get_PM25_Average_Data(void);
void GP2Y10_Test(void);#endif
GP2Y10.c
#include "GP2Y10.h"void GP2Y10_Init(void)
{GP2Y10_LED_OFF(); // 初始化傳感器LED燈為關HAL_ADC_Start(&hadc1); //開啟ADCHAL_ADC_PollForConversion(&hadc1, 50); //等待轉換完成,50為最大等待時間,單位為ms
}// 計算粉塵濃度
float GP2Y10_Value(void)
{uint16_t ADCVal;int dustVal = 0;float Voltage;PM25_LED_H; //置1 開啟內部LEDudelay(280); // 開啟LED后的280us的等待時間ADCVal = HAL_ADC_GetValue(&hadc1); //PA1 采樣,讀取AD值udelay(19); //延時19us,因為這里AD采樣的周期為239.5,所以AD轉換一次需耗時21us,19加21再加280剛好是320usPM25_LED_L; //置0 關閉內部LEDudelay(9680); //需要脈寬比0.32ms/10ms的PWM信號驅動傳感器中的LEDVoltage = 3.3f * ADCVal / 4096.f * 2; //獲得AO輸出口的電壓值dustVal = (0.17*Voltage-0.1)*1000; //乘以1000單位換成ug/m3//if (dustVal < 0)dustVal = 0; //限位//if (dustVal>500) dustVal=500;return dustVal;
}float Get_PM25_Average_Data(void)
{float temp_val=0;uint8_t t;for(t=0;t<PM25_READ_TIMES;t++) //#define PM25_READ_TIMES 20 定義讀取次數,讀這么多次,然后取平均值{temp_val+=GP2Y10_Value(); //讀取ADC值mdelay(5);}temp_val/=PM25_READ_TIMES;//得到平均值return temp_val;//返回算出的ADC平均值
}void GP2Y10_Test(void)
{GP2Y10_Init();float concentration;while(1){LCD_PrintString(0,0,"CON:");concentration = GP2Y10_Value();LCD_PrintSignedVal(0,2,concentration);mdelay(1000);}
}
按鍵讀取程序
#include "Key.h"void Key_Init(void)
{}uint8_t Key_GetValue(void)
{uint8_t value = 0;if(HAL_GPIO_ReadPin(GPIOA,KEY_A_Pin) == GPIO_PIN_SET)value = 1;if(HAL_GPIO_ReadPin(GPIOA,KEY_B_Pin) == GPIO_PIN_SET)value = 2;if(HAL_GPIO_ReadPin(GPIOA,KEY_C_Pin) == GPIO_PIN_SET)value = 3;if(HAL_GPIO_ReadPin(GPIOA,KEY_D_Pin) == GPIO_PIN_SET)value = 4;return value;
}uint8_t Key_Scan(void)
{uint8_t key_number = 0;key_number = Key_GetValue();if(key_number != 0){mdelay(20);while( Key_GetValue() != 0);mdelay(20);return key_number;}return 0;
}void Key_Test(void)
{uint8_t Key_Number = Key_Scan();if(Key_Number == 1)printf("A\n");else if(Key_Number == 2)printf("B\n");else if(Key_Number == 3)printf("C\n");else if(Key_Number == 4)printf("D\n");
}
定時程序
????????這里原本用的是韋東山老師的RTOS里的非阻塞延時,但是由于筆者最終選擇使用裸機完成整個功能,于是便把ms延時函數換成了HAL庫的官方延時函數。
#include "driver_timer.h"
#include "stm32f1xx_hal.h"
#define CPU_FREQUENCY_MHZ 72 // STM32時鐘主頻void udelay(uint32_t delay)
{int last, curr, val;int temp;while (delay != 0){temp = delay > 900 ? 900 : delay;last = SysTick->VAL;curr = last - CPU_FREQUENCY_MHZ * temp;if (curr >= 0){do{val = SysTick->VAL;}while ((val < last) && (val >= curr));}else{curr += CPU_FREQUENCY_MHZ * 1000;do{val = SysTick->VAL;}while ((val <= last) || (val > curr));}delay -= temp;}
}void mdelay(int ms)
{
// for (int i = 0; i < ms; i++)
// udelay(1000);HAL_Delay(ms);
}
邏輯功能實現
Task.h
#ifndef _TASK_H__
#define _TASK_H__#include <stdint.h>
#include <stdbool.h>
#include "HeaderConfig.h"#define HUM_MAX 100
#define HUM_MIN 10
#define TEM_MAX 100
#define TEM_MIN 10
#define DUS_MAX 100
#define DUS_MIN 10void Task_Test(void);
void Device_Init(void);
void Task_Prime(void);#endif
Task.c
#include "Task.h"uint8_t Prime_Mode = 0;
uint8_t Start_Mode = 0;
uint8_t Threshold_Mode = 0;//溫度調節模式
bool isCollecting = false;int Humidity = 0,Temperature = 0;
float Dust_Concentration = 0;
uint8_t TemperatureMax = 30;
uint8_t TemperatureMin = 10;uint8_t DataRead_Count = 0;//數據采集計次
uint8_t Blink_Count = 0;//閃爍
uint16_t Timer_2000ms = 0;//數據采集間隔
uint16_t Timer_Blink = 0;//void Task_Test(void)
{//LCD_Test();//Key_Test();
}void Device_Init(void)
{LCD_Init();Buzzer_Init();DHT11_Init();GP2Y10_Init();Key_Init();
}void Task_Key(void)
{uint8_t Key_Number = Key_Scan();if(Prime_Mode == 2){if(Key_Number == 1){LCD_Clear();Start_Mode = 1;Prime_Mode = 0;isCollecting = false;}}else{if(Key_Number == 1){LCD_Clear();Start_Mode ^= 1;Prime_Mode = 0;isCollecting = false;}}if(Prime_Mode == 2){if(Key_Number == 2){LCD_Clear();Threshold_Mode ^= 1;}}else{if(Key_Number == 2){LCD_Clear();Prime_Mode = 1;isCollecting = false;}}//設置報警閾值if(Key_Number == 3){LCD_Clear();Prime_Mode = 2;isCollecting = false;if(Threshold_Mode == 0)//高溫閾值++{++TemperatureMax;}if(Threshold_Mode == 1)//低溫閾值++{++TemperatureMin;}}if(Key_Number == 4){LCD_Clear();Prime_Mode = 2;isCollecting = false;if(Threshold_Mode == 0)//高溫閾值++{--TemperatureMax;}if(Threshold_Mode == 1)//低溫閾值++{--TemperatureMin;}}
}void filter_and_smooth(float *data, int size, float *smoothed_data) {// 濾除0int count = 0;for (int i = 0; i < size; i++) {if (data[i] != 0) {smoothed_data[count++] = data[i];}}// 平滑曲線for (int i = 0; i < count - 2; i++) {smoothed_data[i] = (smoothed_data[i] + smoothed_data[i + 1] + smoothed_data[i + 2]) / 3.0;}
}void Task_DataCollect(void)
{//DHT11_Read(&Humidity,&Temperature);if ( DHT11_Read(&Humidity,&Temperature) !=0 ){DHT11_Init();}Dust_Concentration = Get_PM25_Average_Data();//采集粉塵濃度數據
// printf("Humidity:%d\n",Humidity);
// printf("Temperature:%d\n",Temperature);
// printf("PM2.5:%f\n",Dust_Concentration);printf("%f\n",Dust_Concentration/10);
}void Task_Alarm(void)
{if(Temperature > TemperatureMax || Temperature < TemperatureMin){Buzzer_Control(ON);mdelay(20);Buzzer_Control(OFF);mdelay(20);}
}void Task_OLED(void)
{switch(Prime_Mode){case 0:{//控制啟動關機if(Start_Mode == 1)//開機{LCD_PrintString(0,0,"Welcome to:");LCD_PrintString(5,2,"System");}else//關機LCD_Clear();break;}case 1://溫濕度,PM2.5濃度{if(Start_Mode == 1){if(!isCollecting){LCD_PrintString(2,3,"Collecting...");mdelay(3000);LCD_Clear();isCollecting = true;}LCD_PrintString(0,0,"Data=>");LCD_PrintString(0,2,"Humidity:");LCD_PrintSignedVal(9, 2, Humidity);LCD_PrintString(0,4,"Temperature:");LCD_PrintSignedVal(12, 4, Temperature);LCD_PrintString(0,6,"PM2.5:");LCD_PrintSignedVal(6, 6, (int)Dust_Concentration);}break;}case 2://設置報警閾值{if(Start_Mode == 1){LCD_PrintString(0,2,"High:");LCD_PrintString(0,4,"Low:");if(Threshold_Mode == 0){if(Blink_Count == 0)LCD_PrintSignedVal(6, 2, TemperatureMax);elseLCD_PrintString(6,2," ");LCD_PrintSignedVal(6, 4, TemperatureMin);}if(Threshold_Mode == 1){LCD_PrintSignedVal(6, 2, TemperatureMax);if(Blink_Count == 0)LCD_PrintSignedVal(6, 4, TemperatureMin);elseLCD_PrintString(6,4," ");}}break;}
// case 3://出行建議
// {
// if(Start_Mode == 1)
// {
// if(Humidity > HUM_MAX)
// LCD_PrintString(0,0,"The humidity is too high!!!");
// else if(Humidity < HUM_MIN)
// LCD_PrintString(0,0,"The humidity is too low!!!");
// }
// break;
// } }
}void Task_Prime(void)
{Task_Key();if(DataRead_Count == 1){Task_DataCollect();DataRead_Count = 0;}Task_Alarm();Task_OLED();
}void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM2){++Timer_2000ms;++Timer_Blink;if(Timer_2000ms >= 2000){DataRead_Count++;Timer_2000ms = 0;}if(Timer_Blink >= 650){Blink_Count ^= 1;Timer_Blink = 0;}}
}
五.最終效果
實物
功能演示?
基于STM32的PM2.5監測系統
六.總結
? ? ? ? 通過本項目的實踐,不僅實現了基礎功能監測,還為未來更復雜的項目打下了堅實的基礎。希望讀者通過該項目,也能夠掌握模塊化開發的思路,逐步進階!