一、项目简介
利用 STM32F103C8T6 开发板和 SG90 舵机、ESP8266模块、TFLCD 显示屏、2个按键、蜂鸣器、DHT11 温湿度传感器以及光敏传感器,研发了一款厨房预警监控系统。该系统的主要功能如下:
1.自动开关灯功能:当光照强度较弱时,自动打开厨房灯,并且此时在LCD屏上显示当前光照强度。而光照强度较强时,自动关闭厨房灯。另外在晚上需要熄灯时,手动关闭厨房灯,并且不可再自动开灯,同时不想影响其他功能继续执行。
2.光照采集功能:采用光敏传感器采集光照强度并显示在LCD屏上。
3.DHT11温湿度采集并预警功能:首先采集当前温湿度信息并且显示在LCD屏上,设置温湿度阈值,超过温度阈值,蜂鸣器报警。超过湿度阈值,舵机转动。而低于温度阈值,舵机反转。另外,当蜂鸣器报警时,需要手动关闭蜂鸣器,此时蜂鸣器不再自动触发并且不影响其他功能继续执行。
4.APP监测温湿度并进行预警功能:通过串口将DHT11所采集到的温湿度信息上传到ESP8266模块,再通过WIFI上传到手机APP图形化实时显示。并且监测到厨房内温度使蜂鸣器报警时,手机APP通过微信发送报警信息到用户手机上。
二、项目框架
三、ucosii实时操作系统简介
uC/OS-II是一种实时多任务操作系统,由美国嵌入式软件工程师Jean Labrosse开发。它是通过源代码方式提供的,并且具有高度可移植性。uC/OS-II被广泛用于嵌入式系统、物联网设备和其他实时应用中。它提供了任务管理、内存管理、时间管理、中断管理和通信机制等功能,通过这些功能可以实现多个任务的并发执行,并确保任务之间的实时性和可靠性。uC/OS-II还具有较小的内存占用和运行效率高的特点,适用于资源受限的嵌入式系统。
uC/OS-II中的每个任务都是一个循环。每个任务都处于以下五种状态之一:睡眠状态、就绪状态、运行状态、等待状态(等待事件发生)、中断服务状态。
睡眠状态: 是指没有被分配任务控制块或被剥夺了任务控制块的任务状态。
就绪状态: 是指系统为任务分配了任务控制块,并在任务就绪表中进行了登记,任务已准备好,但由于任务的优先级低于当前正在运行的任务,所以暂时无法运行,此时任务的状态为就绪状态。
运行状态: 是指任务获得CPU使用权限,并正在运行。
等待状态: 是指当前正在运行的任务需要等待一段时间或等待某个事件发生后再继续运行,此时任务会将CPU的使用权让给其他任务,进入等待状态。
中断服务状态: 是指正在运行的任务一旦响应中断请求,就会中止当前运行并执行中断服务程序,此时任务的状态为中断服务状态。
ucosii操作系统就简单介绍这么多,详情还请参考正点原子官方资料以及《嵌入式实时操作系统 UCOSII 原理及应用》一书。
四、APP的设计
我是利用点灯blinker来进行开发的,论坛里有好多相关的文章,我这里就不详细介绍了。当然,我更推荐去看点灯科技官网,里边有更详细的介绍。
五,代码
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "timer.h"
#include "includes.h"
#include "adc.h"
#include "exti.h"
#include "lcd.h"
#include "gui.h"
#include "dht11.h"
#include "beep.h"
/UCOSII任务设置///
//START 任务
//设置任务优先级
#define START_TASK_PRIO 10 //开始任务的优先级设置为最低
//设置任务堆栈大小
#define START_STK_SIZE 64
//任务堆栈
OS_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *pdata);
//LED任务
//设置任务优先级
#define LED0_TASK_PRIO 6
//设置任务堆栈大小
#define LED0_STK_SIZE 512
//任务堆栈
OS_STK LED0_TASK_STK[LED0_STK_SIZE];
//任务函数
void led0_task(void *pdata);
//智能窗任务
//设置任务优先级
#define PWM_TASK_PRIO 7
//设置任务堆栈大小
#define PWM_STK_SIZE 64
//任务堆栈
OS_STK PWM_TASK_STK[PWM_STK_SIZE];
//任务函数
void pwm_task(void *pdata);
//光敏电阻任务
#define ADC_TASK_PRIO 9
#define ADC_STK_SIZE 512
OS_STK ADC_TASK_STK[ADC_STK_SIZE];
void adc_task(void *pdata);
//dht11任务
#define dht11_task_prio 8
#define dht11_stk_size 512
OS_STK dht11_task_stk[dht11_stk_size];
void dht11_task(void *pdata);
//蜂鸣器开启任务
#define beep_task_prio 5
#define beep_stk_size 64
OS_STK beep_task_stk[beep_stk_size];
void beep_task(void *pdata);
//OS_EVENT *Bsem;
OS_EVENT *email;
OS_EVENT *sig1;
OS_EVENT *sig2;
led_d led1;
led_d p14;
led_d bep;
static u8 flag=0;
static u8 mark=0;
static u8 biao=0;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
delay_init(); //延时函数初始化
LED_Init(&led1,GPIOC,GPIO_Pin_13); //初始化与LED连接的硬件接口
Beep_Init(&bep,GPIOB,GPIO_Pin_15);
TIM3_PWM_Init(99,14399);
Adc_Init();
EXTIX_Init();
LCD_Init();
uart_init(9600);
OSInit();
OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//创建起始任务
OSStart();
}
//开始任务
void start_task(void *pdata)
{
OS_CPU_SR cpu_sr=0;
pdata = pdata;
//Bsem=OSSemCreate(0);
sig1=OSSemCreate(0);
sig2=OSSemCreate(0);
email=OSMboxCreate((void *)0);
OS_ENTER_CRITICAL(); //进入临界区(无法被中断打断)
OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],LED0_TASK_PRIO);
OSTaskCreate(pwm_task,(void *)0,(OS_STK*)&PWM_TASK_STK[PWM_STK_SIZE-1],PWM_TASK_PRIO);
OSTaskCreate(adc_task,(void *)0,(OS_STK*)&ADC_TASK_STK[ADC_STK_SIZE-1],ADC_TASK_PRIO);
OSTaskCreate(dht11_task,(void *)0,(OS_STK*)&dht11_task_stk[dht11_stk_size-1],dht11_task_prio);
OSTaskCreate(beep_task,(void *)0,(OS_STK*)&beep_task_stk[beep_stk_size-1],beep_task_prio);
OSTaskSuspend(START_TASK_PRIO); //挂起起始任务.
OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)
}
//LED任务,当光线过暗,自动开灯同时显示当前光照电阻值
void led0_task(void *pdata)
{
u8 err;
u16 *x;
while(1)
{
x=(u16 *)OSMboxPend(email,0,&err);
// Show_Str(20,30,BLUE,WHITE,"Light: C",16,0);
// LCD_ShowNum(20+6*8,30,*x,2,16);
// printf("此时的光敏电阻值为:");
// printf("%d\n",*x);
// printf("Ω");
// printf("\r\n");
led_on(&led1);
Show_Str(0,20,RED,WHITE,"Light:00.00K",16,0);
LCD_ShowNum(0+6*8,20,(*x)/1000,2,16);
LCD_ShowNum(0+9*8,20,(*x)%1000,2,16);
delay_ms(1000);
}
}
//智能窗任务,如果湿度过高,开窗
void pwm_task(void *pdata)
{ u8 err;
while(1)
{
OSSemPend(sig1,0,&err);
biao+=1;
TIM_SetCompare3(TIM3,13);
delay_ms(1000);
}
}
//光照强度采集任务,显示当前光照电阻值
void adc_task(void *pdata)
{
u16 adcx;
u16 RL;
while(1){
adcx=Get_Adc_Average(ADC_Channel_1,10);
RL=(adcx*10000)/(4096-adcx);
// printf("采集到的光敏电阻为:\n");
// printf("%d\n",RL);
// printf("Ω");
// printf("\r\n");
Show_Str(0,40,BLUE,WHITE,"Light:00.00K",16,0);
LCD_ShowNum(0+6*8,40,RL/1000,2,16);
LCD_ShowNum(0+9*8,40,RL%1000,2,16);
if(RL>=10000) OSMboxPost(email,(void *)&RL);
else{
led_off(&led1);
LCD_Fill(0,0,128,35,WHITE);
}
delay_ms(1000);
}
}
//dht11任务
void dht11_task(void *pdata)
{
u8 wendu;
u8 shidu;
while(1)
{
DHT_Read_Data(&wendu,&shidu,GPIOC,GPIO_Pin_14,&p14);
Show_Str(0,60,BLUE,WHITE,"Temp:00C",16,0);
LCD_ShowNum(0+5*8,60,wendu,2,16);
Show_Str(0,80,BLUE,WHITE,"Humi:00RH",16,0);
LCD_ShowNum(0+5*8,80,shidu,2,16);
printf("%d,%d",shidu,wendu);
if(wendu>=30) OSSemPost(sig2);
else if(wendu<=24) TIM_SetCompare3(TIM3,3);
if(shidu>=70)
{
OSSemPost(sig1);
if(biao==1)
{
biao=0;
OSTaskSuspend(PWM_TASK_PRIO);
}
}
else if(shidu<=50) OSTaskResume(PWM_TASK_PRIO);
delay_ms(1000);
}}
//蜂鸣器开启任务
void beep_task(void *pdata)
{
u8 err;
while(1)
{
OSSemPend(sig2,0,&err);
Beep_on(&bep);
delay_ms(5000);
}
}
void EXTI1_IRQHandler(void)//外部中断1,按第一下挂起蜂鸣器任务,按第二下恢复任务
{
OSIntEnter();
delay_ms(50);
if(KEY0==0)
{
flag+=1;
if(flag%2==1)
{
Beep_off(&bep);
OSTaskSuspend(beep_task_prio);
}
else if(flag%2==0)
{
OSTaskResume(beep_task_prio);
flag=0;
}
Show_Str(0,100,BLUE,WHITE,"Flag:",16,0);
LCD_ShowNum(0+5*8,100,flag,2,16);
}
EXTI_ClearITPendingBit(EXTI_Line1);
OSIntExit();
}
void EXTI15_10_IRQHandler(void)//外部中断12,当光照强度最小时,按下按键,关灯,再按一下开灯
{
OSIntEnter();
delay_ms(50);
if(KEY1==0)
{
mark+=1;
if(mark%2==1)
{
led_off(&led1);
OSTaskSuspend(LED0_TASK_PRIO);
}
else if(mark%2==0)
{
OSTaskResume(LED0_TASK_PRIO);
mark=0;
}
Show_Str(0,120,BLUE,WHITE,"Mark:",16,0);
LCD_ShowNum(0+5*8,120,mark,2,16);
}
EXTI_ClearITPendingBit(EXTI_Line12);
OSIntExit();
}
六、APP代码
#define BLINKER_WIFI
#include <Blinker.h>
char auth[] = "8c25d5d6472d";
char ssid[] = "Magic3";
char pswd[] = "00000000";
// 新建组件对象
BlinkerNumber Humi("humi");
BlinkerNumber Tem("tem");
BlinkerText Text("baojing");
int shidu;
int wendu;
int mark=0;
String comdata = ""; //定义一个comdata字符串变量,赋初值为空值
int numdata[4] = {0};
void heartbeat()
{
if(mark==1){
mark=0;
Blinker.wechat("温度过高!");
}
Humi.print(shidu);
Tem.print(wendu);
}
void dataStorage()
{
Blinker.dataStorage("tem", wendu);
Blinker.dataStorage("humi", shidu);
}
void BEEP()
{
if(wendu>=30){
Blinker.delay(1000);
Text.print("警报!");
//Blinker.vibrate(1000);//关于微信消息和app推送,我解决方案是抓住一分钟一次重点以及串口报错信息MQTTnot alive or MSG limit,这表示发送消息频繁导致http连接失败,因此我把消息写在心跳包里同时去除掉振动函数就可以了
//Blinker.delay(60000);
mark=1;
}
else{
Text.print("正常");
}
}
void UartRead(void)
{
int j = 0; //j是分拆之后数字数组的位置记数
while (Serial.available() > 0) //不断循环检测串口缓存,一个个读入字符串,
{
comdata += char(Serial.read()); //读入之后将字符串,串接到comdata上面。
Blinker.delay(2); //延时一会,让串口缓存准备好下一个数字,不延时会导致数据丢失,
// Serial.println(comdata);
mark = 1; //标记串口读过数据,如果没有数据的话,直接不执行这个while了。
}
if(mark == 1) //如果接收到数据则执行comdata分析操作,否则什么都不做。
{
for(int i = 0; i < comdata.length() ; i++) //以串口读取字符串长度循环,
{
//逐个分析comdata[i]字符串的文字,如果碰到文字是分隔符(这里选择逗号分割)则将结果数组位置下移一位
//即比如11,22,33,55开始的11记到numdata[0];碰到逗号就j等于1了,
//再转换就转换到numdata[1];再碰到逗号就记到numdata[2];以此类推,直到字符串结束
if(comdata[i] == ',')
{
j++;
}
else
{
//如果没有逗号的话,就将读到的数字*10加上以前读入的数字,
//并且(comdata[i] - '0')就是将字符'0'的ASCII码转换成数字0(下面不再叙述此问题,直接视作数字0)。
//比如输入数字是12345,有5次没有碰到逗号的机会,就会执行5次此语句。
//因为左边的数字先获取到,并且numdata[0]等于0,
//所以第一次循环是numdata[0] = 0*10+1 = 1
//第二次numdata[0]等于1,循环是numdata[0] = 1*10+2 = 12
//第三次是numdata[0]等于12,循环是numdata[0] = 12*10+3 = 123
//第四次是numdata[0]等于123,循环是numdata[0] = 123*10+4 = 1234
//如此类推,字符串将被变成数字0。
numdata[j] = numdata[j] * 10 + (comdata[i] - '0');
}
}
comdata = String(""); //comdata的字符串已经全部转换到numdata了,清空comdata以便下一次使用,如果不请空的话,本次结果极有可能干扰下一次。
for(int i = 0; i <1; i++) //循环输出numdata的内容
{
int a = numdata[i];
shidu=a;
Serial.print(shidu);
numdata[i] = 0;
}
for(int i = 1; i <2; i++)
{
//Serial.println(numdata[i]);
int b = numdata[i];
wendu=b;
Serial.print(wendu);
numdata[i] = 0;
}
mark = 0; //输出之后必须将读到数据的mark置0,不置0下次循环就不能使用了。
}
}
void setup() {
Serial.begin(9600);
//BLINKER_DEBUG.stream(Serial);
//BLINKER_DEBUG.debugAll();
// 初始化blinker
Blinker.begin(auth, ssid, pswd);
Blinker.attachHeartbeat(heartbeat);
Blinker.attachDataStorage(dataStorage);
}
void loop() {
Blinker.run();
BEEP();
UartRead();
}
七、实物及APP图
谢谢观看!