前言:本项目主要是基于freertos的小项目,目的是为了巩固近期学习的知识,功能较简单,可自行扩充。
一、项目基本架构
项目基本功能:通过STM32单片机的freertos操作系统,将温湿度数据显示在oled屏幕上,并将数据通过蓝牙发送到手机上显示。
项目基本思路:使用freeRTOS创建两个任务,一个任务用于读取温湿度的数据,然后将读取的内容放入队列当中,另一个任务从队列中读取温湿度数据,然后显示在屏幕上并通过串口发给蓝牙。
项目调试难题:1、多任务进行时,需要考虑优先级的问题,防止优先级反转;2、创建队列时需要使用合适的队列深度和项目大小,防止队列满溢出;3、在任务中使用延时时,可能会导致高优先级任务阻塞,从而暂时执行就绪态的低优先级任务,而出现任务执行出现卡住错误。
二、项目配置
时钟树
1、任务的创建(这里确保读取温湿度任务的优先级高于显示温湿度任务的优先级即可)
2、队列的创建(这里项目的大小我设置为uint64_t,因为温湿度以及校验位有5个字节,共40位bit数据)
3、IIC设置(这里MOde选择iic即可,下面涉及到主从模式,这里并没有用到)
4、串口(串口选择串口一,MODE选择异步通信)
5、gpio设置(细心会发现并没有DHT11的数据引脚,因为该引脚既需要当输入引脚又需要当输出引脚,这里需要我们手动在代码设置即可)
三、代码实现
1、温湿度读取任务
/* USER CODE END Header_StartDht11Task */
void StartDht11Task(void const * argument)
{/* USER CODE BEGIN StartDht11Task */BaseType_t status;/* Infinite loop */for(;;){//不断读取温湿度数据Read_Data_From_DHT();//这里需要加段延时等待数据读取稳定,否则可能会出现卡住的现象。osDelay(500);//将读取到的数据丢到队列里面去status = xQueueSend(Dht11QueueHandle,datas,0);//将该任务暂时挂起,防止该任务一直执行。osThreadSuspend(dht11TaskHandle);//判断写入队列状态if(status == pdTRUE){printf("写入成功\r\n");}else{printf("写入失败\r\n");}osDelay(1000);}/* USER CODE END StartDht11Task */
}
2、温湿度显示任务
void StartReadDht11Task(void const * argument)
{/* USER CODE BEGIN StartReadDht11Task */uint8_t dht[5] = {0};//队列数据缓冲区BaseType_t status;char message[32] = {'\0'};/* Infinite loop */for(;;){//高优先级任务调用osDelay函数会使任务阻塞,从而执行低优先级任务,加入延时可保证队列的写入先执行。osDelay(1000);//将队列里面的数据读取出来status = xQueueReceive(Dht11QueueHandle,dht,100);//将接收温湿度数据的任务恢复,方便下次接收。osThreadResume(dht11TaskHandle);if(status == pdTRUE){printf("读取成功\r\n");printf("Temp: %d.%d ", dht[2], dht[3]);//温度printf("Humi: %d.%d\r\n", dht[0], dht[1]);//湿度memset(message,'\0',sizeof(message));sprintf(message,"%d.%d",dht[0], dht[1]);Oled_Show_Str(2,40,message);memset(message,'\0',sizeof(message));sprintf(message,"%d.%d",dht[2], dht[3]);Oled_Show_Str(1,40,message);}else{printf("读取失败\r\n");}osDelay(2000);}/* USER CODE END StartReadDht11Task */
}
3、DHT11
void delay_us(uint16_t cnt)
{uint8_t i;while(cnt){for (i = 0; i < 10; i++){}cnt--;}
}
//设置引脚模式
void DHT_GPIO_Init(uint32_t Mode)
{GPIO_InitTypeDef GPIO_InitStruct = {0};__HAL_RCC_GPIOB_CLK_ENABLE();//用外设需要打开对应时钟GPIO_InitStruct.Pin = GPIO_PIN_5;GPIO_InitStruct.Mode = Mode;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//DHT11初始化
void DHT11_Start(void)
{DHT_GPIO_Init(GPIO_MODE_OUTPUT_PP);//配置为输出引脚DHT_HIGHT;DHT_LOW;HAL_Delay(30);DHT_HIGHT;DHT_GPIO_Init(GPIO_MODE_INPUT);//配置为输入引脚while(DHT_VALUE);while(!DHT_VALUE);while(DHT_VALUE);
}
//读取DHT11数据
void Read_Data_From_DHT(void)
{int i;//轮int j;//每一轮读多少次char tmp;char flag;DHT11_Start();//判断硬件是否存在DHT_GPIO_Init(GPIO_MODE_INPUT);配置为输入引脚for(i= 0;i < 5;i++){for(j=0;j<8;j++){while(!DHT_VALUE);//等待卡g点delay_us(40);if(DHT_VALUE == 1){flag = 1;while(DHT_VALUE);}else{flag = 0;}tmp = tmp << 1;tmp |= flag;}datas[i] = tmp;}
}
4、串口重定向
int fputc(int ch, FILE *f){unsigned char temp[1] = {ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);return ch;
}
打开USB MicroLIB选项
5、OLED
#include "oled.h"
#include "oledfont.h"
#include "i2c.h"int i =0;
char w1[16] = {0x10,0x60,0x02,0x8C,0x00,0x00,0xFE,0x92,0x92,0x92,0x92,0x92,0xFE,0x00,0x00,0x00};//温上半部分
char w2[16] = {0x04,0x04,0x7E,0x01,0x40,0x7E,0x42,0x42,0x7E,0x42,0x7E,0x42,0x42,0x7E,0x40,0x00};//温下办部分char d1[16] = {0x00,0x00,0xFC,0x24,0x24,0x24,0xFC,0x25,0x26,0x24,0xFC,0x24,0x24,0x24,0x04,0x00};//度上半部分
char d2[16] = {0x40,0x30,0x8F,0x80,0x84,0x4C,0x55,0x25,0x25,0x25,0x55,0x4C,0x80,0x80,0x80,0x00};//度下办部分char s1[16] = {0x10,0x60,0x02,0x8C,0x00,0xFE,0x92,0x92,0x92,0x92,0x92,0x92,0xFE,0x00,0x00,0x00};//湿上半部分
char s2[16] = {0x04,0x04,0x7E,0x01,0x44,0x48,0x50,0x7F,0x40,0x40,0x7F,0x50,0x48,0x44,0x40,0x00};//湿下办部分
char c1[8] = {0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00};//冒号上半部分
char c2[8] = {0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00};//冒号下半部分void Oled_Write_Cmd(uint8_t dataCmd)//写命令
{HAL_I2C_Mem_Write(&hi2c1,0x78,0x00,I2C_MEMADD_SIZE_8BIT,&dataCmd,1,0xff);
}
void Oled_Write_Data(uint8_t dataData)//写数据
{HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,&dataData, 1, 0xff);
}
//oled屏初始化
void Oled_Init(void){Oled_Write_Cmd(0xAE);//--display offOled_Write_Cmd(0x00);//---set low column addressOled_Write_Cmd(0x10);//---set high column addressOled_Write_Cmd(0x40);//--set start line addressOled_Write_Cmd(0xB0);//--set page addressOled_Write_Cmd(0x81); // contract controlOled_Write_Cmd(0xFF);//--128Oled_Write_Cmd(0xA1);//set segment remapOled_Write_Cmd(0xA6);//--normal / reverseOled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)Oled_Write_Cmd(0x3F);//--1/32 dutyOled_Write_Cmd(0xC8);//Com scan directionOled_Write_Cmd(0xD3);//-set display offsetOled_Write_Cmd(0x00);//Oled_Write_Cmd(0xD5);//set osc divisionOled_Write_Cmd(0x80);//Oled_Write_Cmd(0xD8);//set area color mode offOled_Write_Cmd(0x05);//Oled_Write_Cmd(0xD9);//Set Pre-Charge PeriodOled_Write_Cmd(0xF1);//Oled_Write_Cmd(0xDA);//set com pin configuartionOled_Write_Cmd(0x12);//Oled_Write_Cmd(0xDB);//set VcomhOled_Write_Cmd(0x30);//Oled_Write_Cmd(0x8D);//set charge pump enableOled_Write_Cmd(0x14);//Oled_Write_Cmd(0xAF);//--turn on oled panel}
//温湿度初始化
void Oled_Meg_Slow(void)
{//page0Oled_Write_Cmd(0xB0);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(w1[i]);}for(i=0;i<16;i++){Oled_Write_Data(d1[i]);}for(i=0;i<8;i++){Oled_Write_Data(c1[i]);}//page1Oled_Write_Cmd(0xB1);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(w2[i]);}for(i=0;i<16;i++){Oled_Write_Data(d2[i]);}for(i=0;i<8;i++){Oled_Write_Data(c2[i]);}//page2Oled_Write_Cmd(0xB2);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(s1[i]);}for(i=0;i<16;i++){Oled_Write_Data(d1[i]);}for(i=0;i<8;i++){Oled_Write_Data(c1[i]);}//page3Oled_Write_Cmd(0xB3);Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);for(i=0;i<16;i++){Oled_Write_Data(s2[i]);}for(i=0;i<16;i++){Oled_Write_Data(d2[i]);}for(i=0;i<8;i++){Oled_Write_Data(c2[i]);}}
void Oled_Show_Char(char row,char col,char oledChar){ //row*2-2unsigned int i;Oled_Write_Cmd(0xb0+(row*2-2)); //page 0Oled_Write_Cmd(0x00+(col&0x0f)); //lowOled_Write_Cmd(0x10+(col>>4)); //high for(i=((oledChar-32)*16);i<((oledChar-32)*16+8);i++){Oled_Write_Data(F8X16[i]); //写数据oledTable1}Oled_Write_Cmd(0xb0+(row*2-1)); //page 1Oled_Write_Cmd(0x00+(col&0x0f)); //lowOled_Write_Cmd(0x10+(col>>4)); //highfor(i=((oledChar-32)*16+8);i<((oledChar-32)*16+8+8);i++){Oled_Write_Data(F8X16[i]); //写数据oledTable1}
}/******************************************************************************/
// 函数名称:Oled_Show_Char
// 输入参数:oledChar
// 输出参数:无
// 函数功能:OLED显示单个字符
/******************************************************************************/
//指定行列显示字符串
void Oled_Show_Str(char row,char col,char *str){while(*str!=0){Oled_Show_Char(row,col,*str);str++;col += 8; }
}
//清屏函数
void Oled_Screen_Clear(void){int i,n;Oled_Write_Cmd (0x20); //set memory addressing modeOled_Write_Cmd (0x02); //page addressing modefor(i=0;i<8;i++){Oled_Write_Cmd(0xb0+i); //éè??ò3μ??·£¨0~7£?Oled_Write_Cmd(0x00); //éè????ê??????aáDμíμ??·Oled_Write_Cmd(0x10); //éè????ê??????aáD??μ??·for(n=0;n<128;n++)Oled_Write_Data(0x00);}
}
四、效果实现
四、总结
最近也是断断续续的在学习freertos知识,有了STM32的HAL库开发经历及Linux的系统编程知识,对freertos的学习相对没有那么吃力,虽然对其中的任务、信号量、任务标志位的理解还是有点欠缺,但是以后会通过动手做项目来巩固加深理解。
对于该小项目、其实还有优化的空间、比如可以通过任务标志位来进行任务的通信或者信号量互斥量来进行同步互斥,也可以自行进行功能的拓展,比如检测数据加入烟雾传感器(ADC)、二氧化碳模块等传感器,通信模块换成wifi模块、4g模块,屏幕换成lcd屏幕、串口屏等,更高级就造个手机APP或者使用mqtt与云平台交互显示数据,妥妥的一个设计就出来了哈哈。