日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【小项目】STM32环境监测 | MQ2可燃气体传感器+雨滴传感器+DHT11温湿度传感器+OLED屏幕

發布時間:2023/12/9 编程问答 60 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【小项目】STM32环境监测 | MQ2可燃气体传感器+雨滴传感器+DHT11温湿度传感器+OLED屏幕 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • 概述
  • 固件編寫
    • DHT11溫濕度傳感器
      • 概述
      • 代碼
    • MQ2可燃氣體傳感器和雨滴傳感器
      • 概述
      • ADC外設
      • 代碼
    • OLED屏幕
      • 概述
      • 代碼
    • main.c文件
      • 概述
      • 代碼
  • 總結
    • 項目代碼下載

概述

前些陣子參加了廣東省電賽,因為疫情的原因比賽不能在線下進行,甚至連回學校調試也不行,于是乎就水了一個省三。
備賽的時候,隊長給我布置了這個小項目,說有可能會用在比賽的作品中,但實際就不知道了,所以在這里分享一下代碼。

固件編寫

這個需求對性能要求不高,像人見人愛、人手一個的STM32F103C8T6也能勝任,但因為我沒帶回家,所以用了野火的霸道V2開發板。

這塊開發板用的是STM32F103ZET6芯片。

DHT11溫濕度傳感器

概述

溫濕度傳感器玩過單片機的小伙伴都應該特別熟悉了,在這里就不再贅述了。但還是放幾張時序圖解釋一下。
總體來說DHT11是串行通信,只用一根數據線進行通信,通信的形式有點像IIC,都是先給一個響應信號,然后傳感器應答后開始傳輸數據,直到從機或主機發送結束信號。

下面是主機發送響應信號的時序。

從機接收到主機的響應信號后會按以下時序產生應答信號,表示接受響應。

實際發送的數據都是以一低一高的形式表示,“0”和“1”的區別在于高電平的持續時間,利用這一點主機可以區分“0”和“1”比特。

代碼

下面是接收一個字節和接收溫濕度數據的代碼:

/* * 從DHT11讀取一個字節,MSB先行*/ static uint8_t DHT11_ReadByte ( void ) {uint8_t i, temp=0;for(i=0;i<8;i++) { /*每bit以50us低電平標置開始,輪詢直到從機發出 的50us 低電平 結束*/ while(DHT11_Dout_IN() == RESET);/*DHT11 以26~28us的高電平表示“0”,以70us高電平表示“1”,*通過檢測 x us后的電平即可區別這兩個狀 ,x 即下面的延時 */DHT11_DELAY_10US(4); //延時x us 這個延時需要大于數據0持續的時間即可 if(DHT11_Dout_IN() == SET)/* x us后仍為高電平表示數據“1” */{/* 等待數據1的高電平結束 */while(DHT11_Dout_IN() == SET);temp|=(uint8_t)(0x01<<(7-i)); //把第7-i位置1,MSB先行 }else // x us后為低電平表示數據“0”{ temp&=(uint8_t)~(0x01<<(7-i)); //把第7-i位置0,MSB先行}}return temp;}/** 一次完整的數據傳輸為40bit,高位先出* 8bit 濕度整數 + 8bit 濕度小數 + 8bit 溫度整數 + 8bit 溫度小數 + 8bit 校驗和 */ uint8_t DHT11_Read_TempAndHumidity(DHT11_Data_TypeDef *DHT11_Data) { /*輸出模式*/DHT11_Mode_Out_PP();/*主機拉低*/DHT11_Dout_0;/*延時18ms*/DHT11_DELAY_MS(18);/*總線拉高 主機延時30us*/DHT11_Dout_1; DHT11_DELAY_10US(3); //延時30us/*主機設為輸入 判斷從機響應信號*/ DHT11_Mode_IPU();/*判斷從機是否有低電平響應信號 如不響應則跳出,響應則向下運行*/ if(DHT11_Dout_IN() == RESET) {/*輪詢直到從機發出 的80us 低電平 響應信號結束*/ while(DHT11_Dout_IN() == RESET);/*輪詢直到從機發出的 80us 高電平 標置信號結束*/while(DHT11_Dout_IN() == SET);/*開始接收數據*/ DHT11_Data->humi_int= DHT11_ReadByte();DHT11_Data->humi_deci= DHT11_ReadByte();DHT11_Data->temp_int= DHT11_ReadByte();DHT11_Data->temp_deci= DHT11_ReadByte();DHT11_Data->check_sum= DHT11_ReadByte();/*讀取結束,引腳改為輸出模式*/DHT11_Mode_Out_PP();/*主機拉高*/DHT11_Dout_1;/*檢查讀取的數據是否正確*/if(DHT11_Data->check_sum == DHT11_Data->humi_int + DHT11_Data->humi_deci + DHT11_Data->temp_int+ DHT11_Data->temp_deci)return SUCCESS;else return ERROR;}elsereturn ERROR;}

MQ2可燃氣體傳感器和雨滴傳感器

概述

因為可燃氣體檢測和雨滴感應的傳感器原理較相似,所以放到一起講了。
可燃氣體傳感器用的是MQ2。

下面是雨滴模塊的樣子(簡單到連名字都沒有)

兩個模塊都是用模擬信號通信,所以使用STM32的ADC外設就可以輕松讀取數據。

ADC外設

根據STM32的用戶手冊知道我們使用的這款芯片有3個ADC外設,為了讓單片機能更快處理轉換模擬信號,所以使用了雙ADC模式;為了讓我們能隨時調整采樣的時間,我還使用了ADC外設的外部觸發功能;為了保證數據傳輸的效率我還使用了DMA進行傳輸。
解釋一下雙ADC模式,通俗講就是正常是一個ADC處理2個模擬信號的轉換,現在2個ADC分別負責2個模擬信號的轉換,并且是硬件控制同時開始轉換的,因此效率會高很多。

STM32支持的雙ADC模式有好幾個,具體可參考用戶文檔,在這里我們使用最常用的同步規則模式即可。
但需要注意的是雙ADC模式只能使用ADC1和ADC2外設

代碼

接下來放核心代碼吧,對于STM32的ADC玩法還挺多的,計劃在另外寫一篇文章來講一下。
adc.c文件:

#include "adc/adc.h"static uint32_t ADC_RAW_DATA = 0;void ADC_Config(void) {/* DMA初始化 */DMA_InitTypeDef DMA_Init_Struct = {0};RCC_AHBPeriphClockCmd(DMA_PERI_CLK_PORT, ENABLE);DMA_DeInit(ADC_DMA_CHAL);DMA_Init_Struct.DMA_PeripheralBaseAddr = ADC1_DR_Address; // 外設基地址DMA_Init_Struct.DMA_MemoryBaseAddr = (uint32_t)&ADC_RAW_DATA; // 內存基地址DMA_Init_Struct.DMA_DIR = DMA_DIR_PeripheralSRC; // 外設到內存DMA_Init_Struct.DMA_BufferSize = 1; // 數據塊數量DMA_Init_Struct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 關閉外設地址自增DMA_Init_Struct.DMA_MemoryInc = DMA_MemoryInc_Disable; // 關閉內存地址自增DMA_Init_Struct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // 字DMA_Init_Struct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; // 字DMA_Init_Struct.DMA_Mode = DMA_Mode_Circular; // 循環模式DMA_Init_Struct.DMA_Priority = DMA_Priority_High; // 優先級高DMA_Init_Struct.DMA_M2M = DMA_M2M_Disable; // 關閉內存至內存模式DMA_Init(ADC_DMA_CHAL, &DMA_Init_Struct);DMA_Cmd(ADC_DMA_CHAL, ENABLE);DMA_ClearFlag(DMA1_FLAG_TC1);/* 初始化計時器 */TIM_TimeBaseInitTypeDef TIM_BaseInit_Struct = {0};RCC_APB1PeriphClockCmd(TIM_PERI_CLK_PORT, ENABLE);TIM_BaseInit_Struct.TIM_ClockDivision = TIM_CKD_DIV1; // 輸入捕獲才用到,默認值即可TIM_BaseInit_Struct.TIM_CounterMode = TIM_CounterMode_Up; // 向上計數TIM_BaseInit_Struct.TIM_Period = TIM_PERIOD_VAL; // 周期,即在(1 / 2kHz) * (TIM_PERIOD_VAL + 1) = CONV_TIME(ms)后產生上溢,這也是ADC的觸發周期TIM_BaseInit_Struct.TIM_Prescaler = (36000 - 1); // 預分頻,即分頻后的時鐘頻率為72Mhz / (35999 + 1) = 2kHzTIM_BaseInit_Struct.TIM_RepetitionCounter = 0; // 不使用重裝載寄存器TIM_TimeBaseInit(TIM_PORT, &TIM_BaseInit_Struct);TIM_SelectOutputTrigger(TIM_PORT, TIM_TRGOSource_Update); // 使能輸出觸發TIM_ClearFlag(TIM_PORT, TIM_FLAG_Update);/* 初始化ADC */ADC_InitTypeDef ADC_Init_Struct = {0};RCC_APB2PeriphClockCmd(MQ2_ADC_PERI_CLK_PORT | WaterSensor_ADC_PERI_CLK_PORT, ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC 6分頻,即采樣頻率為72 / 6 = 12MHzADC_Init_Struct.ADC_ContinuousConvMode = DISABLE; // 關閉連續采樣模式ADC_Init_Struct.ADC_DataAlign = ADC_DataAlign_Right; // 數據右對齊ADC_Init_Struct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO; // ADC1外部觸發,由計時器3 TRGO事件負責ADC_Init_Struct.ADC_Mode = ADC_Mode_RegSimult; // 同步掃描模式ADC_Init_Struct.ADC_NbrOfChannel = 1; // 1個通道ADC_Init_Struct.ADC_ScanConvMode = DISABLE; // 關閉掃描模式ADC_Init(MQ2_ADC_PORT, &ADC_Init_Struct);ADC_Init_Struct.ADC_ContinuousConvMode = DISABLE; // 關閉連續采樣模式ADC_Init_Struct.ADC_DataAlign = ADC_DataAlign_Right; // 數據右對齊ADC_Init_Struct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // ADC2關閉外部觸發ADC_Init_Struct.ADC_Mode = ADC_Mode_RegSimult; // 同步掃描模式ADC_Init_Struct.ADC_NbrOfChannel = 1; // 1個通道ADC_Init_Struct.ADC_ScanConvMode = DISABLE; // 關閉掃描模式ADC_Init(WaterSensor_ADC_PORT, &ADC_Init_Struct);ADC_DMACmd(MQ2_ADC_PORT, ENABLE); // 使能ADC DMA功能ADC_Cmd(MQ2_ADC_PORT, ENABLE);ADC_Cmd(WaterSensor_ADC_PORT, ENABLE);ADC_ExternalTrigConvCmd(MQ2_ADC_PORT, ENABLE); // 使能ADC1外部觸發轉換ADC_ExternalTrigConvCmd(WaterSensor_ADC_PORT, ENABLE); // 使能ADC2外部觸發轉換/* 轉換時間 (1 / 12MHz) * (55.5 + 12.5) ≈ 5.7us */ADC_RegularChannelConfig(MQ2_ADC_PORT, MQ2_ADC_CHAL, 1, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(WaterSensor_ADC_PORT, WaterSensor_ADC_CHAL, 1, ADC_SampleTime_55Cycles5);ADC_ResetCalibration(MQ2_ADC_PORT); // 復位ADC1while(ADC_GetResetCalibrationStatus(MQ2_ADC_PORT));ADC_StartCalibration(MQ2_ADC_PORT); // 校正ADC1while(ADC_GetCalibrationStatus(MQ2_ADC_PORT));ADC_ResetCalibration(WaterSensor_ADC_PORT); // 復位ADC2while(ADC_GetResetCalibrationStatus(WaterSensor_ADC_PORT));ADC_StartCalibration(WaterSensor_ADC_PORT); // 校正ADC2while(ADC_GetCalibrationStatus(WaterSensor_ADC_PORT));TIM_Cmd(TIM_PORT, ENABLE); // 開啟計時器,開啟ADC轉換 }uint16_t ADC_GetMQ2RawData(void) {uint32_t tmp = ADC_RAW_DATA;return (uint16_t)(tmp & 0x0000ffff); }uint16_t ADC_GetWaterSensorRawData(void) {uint32_t tmp = ADC_RAW_DATA;return (uint16_t)(tmp >> 16); }

adc.h文件:

/* 相關配置 */ #define CONV_TIME 100 // 采樣周期,單位ms/* 其他宏定義 */ #define TIM_PERIOD_VAL (CONV_TIME * 2 - 1)/* 管腳宏定義 */ #define TIM_PORT TIM3 #define TIM_PERI_CLK_PORT RCC_APB1Periph_TIM3#define DMA_PERI_CLK_PORT RCC_AHBPeriph_DMA1 #define ADC1_DR_Address ((uint32_t)0x4001244C) #define ADC_DMA_CHAL DMA1_Channel1void ADC_Config(void); uint16_t ADC_GetMQ2RawData(void); uint16_t ADC_GetWaterSensorRawData(void);

OLED屏幕

概述

OLED屏幕應該也有很多小伙伴玩過了。

這個屏幕的通信方式有挺多種的,比如說8080時序、IIC、SPI,具體是哪種通信方式要看買的時候商家的說明。
我買的這個是IIC的接口,我使用了硬件IIC對其通信。

代碼

簡單貼一下代碼:
礙于篇幅就只放初始化和傳輸一個字節的代碼吧。

/*** @brief 初始化OLED針腳* @param None* @retval None*/ static void OLED_GPIO_Init(void) {GPIO_InitTypeDef GPIO_Init_Struct;RCC_APB2PeriphClockCmd(OLED_PERI_CLK, ENABLE);GPIO_Init_Struct.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_Init_Struct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init_Struct.GPIO_Pin = OLED_SCL_PIN;GPIO_Init(OLED_SCL_PORT, &GPIO_Init_Struct);GPIO_Init_Struct.GPIO_Pin = OLED_SDA_PIN;GPIO_Init(OLED_SDA_PORT, &GPIO_Init_Struct);RCC_APB1PeriphClockCmd(IIC_PERI_CLK, ENABLE);I2C_InitTypeDef I2C_Init_Struct;I2C_Init_Struct.I2C_Ack = I2C_Ack_Enable; // 使能ACkI2C_Init_Struct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位從機地址I2C_Init_Struct.I2C_ClockSpeed = 1000000; // 速度100kHzI2C_Init_Struct.I2C_DutyCycle = I2C_DutyCycle_2;I2C_Init_Struct.I2C_Mode = I2C_Mode_I2C;I2C_Init_Struct.I2C_OwnAddress1 = 0x00; // STM32自己的地址,任意值即可I2C_Init(IIC_PORT, &I2C_Init_Struct);I2C_Cmd(IIC_PORT, ENABLE); }/*** @brief 初始化SSD1306* @param None* @retval None*/ void OLED_Init(void) {OLED_GPIO_Init();OLED_SendCmd(0xAE);//--turn off oled panelOLED_SendCmd(0x00);//---set low column addressOLED_SendCmd(0x10);//---set high column addressOLED_SendCmd(0x40);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)OLED_SendCmd(0x81);//--set contrast control registerOLED_SendCmd(0xCF); // Set SEG Output Current BrightnessOLED_SendCmd(0xA1);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常OLED_SendCmd(0xC8);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常OLED_SendCmd(0xA6);//--set normal displayOLED_SendCmd(0xA8);//--set multiplex ratio(1 to 64)OLED_SendCmd(0x3f);//--1/64 dutyOLED_SendCmd(0xD3);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)OLED_SendCmd(0x00);//-not offsetOLED_SendCmd(0xd5);//--set display clock divide ratio/oscillator frequencyOLED_SendCmd(0x80);//--set divide ratio, Set Clock as 100 Frames/SecOLED_SendCmd(0xD9);//--set pre-charge periodOLED_SendCmd(0xF1);//Set Pre-Charge as 15 Clocks & Discharge as 1 ClockOLED_SendCmd(0xDA);//--set com pins hardware configurationOLED_SendCmd(0x12);OLED_SendCmd(0xDB);//--set vcomhOLED_SendCmd(0x40);//Set VCOM Deselect LevelOLED_SendCmd(0x20);//-Set Page Addressing Mode (0x00/0x01/0x02)OLED_SendCmd(0x02);//OLED_SendCmd(0x8D);//--set Charge Pump enable/disableOLED_SendCmd(0x14);//--set(0x10) disableOLED_SendCmd(0xA4);// Disable Entire Display On (0xa4/0xa5)OLED_SendCmd(0xA6);// Disable Inverse Display On (0xa6/a7) OLED_SendCmd(0xAF);//--turn on oled panelOLED_SendCmd(0xAF); /*display ON*/OLED_Clear();OLED_Set_Pos(0,0); }/*** @brief 向SSD1106寫入一個字節* @param dat 要寫入的數據/命令* @param cmd 數據/命令標志 0,表示命令;1,表示數據* @retval*/ static void OLED_WR_Byte(uint8_t dat, uint8_t cmd) {// FlagStatus bitstatus = RESETwhile (I2C_GetFlagStatus(IIC_PORT, I2C_FLAG_BUSY)); // 檢查I2C總線是否繁忙 I2C_GenerateSTART(IIC_PORT, ENABLE); // 打開IIC總線// ErrorStatus status = ERROR, ERROR是個枚舉類型, 值為0 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 檢查總線是否打開I2C_Send7bitAddress(IIC_PORT, IIC_ADDR, I2C_Direction_Transmitter); // 配置STM32的IIC設備自己的地址,每個連接到IIC總線上的設備都有一個自己的地址,作為主機也不例外。while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 檢查地址是否發送if (cmd) I2C_SendData(IIC_PORT, 0x40); // 進入數據模式else I2C_SendData(IIC_PORT, 0x00); // 進入命令模式while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 等待發送數據完成I2C_SendData(IIC_PORT, dat); //發送數據while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 等待發送數據完成I2C_GenerateSTOP(IIC_PORT, ENABLE); // 關閉IIC總線 }

main.c文件

概述

應隊長的要求這個小項目用了FreeRTOS系統,但實際上除了用到最基本的任務調度外,其他的功能都沒有用到。
至于FreeRTOS系統的移植網上都有好多的教程了,這里就不贅述了。
簡單說主任務做的就是一段時間后切換OLED屏幕的顯示內容(因為一塊OLED屏幕顯示不了所有傳感器的數據)

代碼

簡單貼一下代碼:

static void MainTask(void* params) {uint8_t status = 0;while(1){if (status){DHT11_Read_TempAndHumidity(&dht11_data);OLED_ShowTempAndHumi(dht11_data.temp_int, dht11_data.temp_deci, dht11_data.humi_int, dht11_data.humi_deci);printf("Displaying temperature and humidity\n");}else{OLED_ShowPpmAndWaterLevel(MQ2_GetPPM(), WaterSensor_GetWaterLevel());printf("Displaying PPM and water level\n");}status = !status;vTaskDelay(2000);} }static void AppInitTask(void* params) {taskENTER_CRITICAL(); // 進入臨界段NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);USART_Config();MQ2_Init();WaterSensor_Init();ADC_Config();TIMER_Init();DHT11_Init();OLED_Init();printf("Init completed\n");vTaskDelay(100);xTaskCreate(MainTask, "MainTask", 1024, NULL, 5, NULL);printf("Main task created\n");vTaskDelete(NULL); // 刪除這個任務taskEXIT_CRITICAL(); // 退出臨界段 }/*** @brief 主函數* @param None* @retval None*/ int main(void) {BaseType_t xReturn = pdPASS;xReturn = xTaskCreate(AppInitTask, "AppInitTask", 1024, NULL, 5, NULL);if (xReturn == pdPASS) vTaskStartScheduler(); // 開啟任務調度else while(1); }

總結

總的來說,這個項目還是蠻適合對STM32已經入門然后想進階的小伙伴。
項目包含了對各種常見模塊的代碼撰寫,每種模塊對應的通信協議、用到的外設也不盡相同;項目還涉及了對FreeRTOS系統的基本移植和使用,對于想進階STM32的小伙伴是非常好的。

項目代碼下載

鏈接:https://download.csdn.net/download/JackieCoo/86506760

總結

以上是生活随笔為你收集整理的【小项目】STM32环境监测 | MQ2可燃气体传感器+雨滴传感器+DHT11温湿度传感器+OLED屏幕的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。