预警系统最小例程构建
引言
为了更直观, 我们使用最小例程来实现这个预警流程, 环境温湿度读取,然后判断阈值, 超标则触发小灯警报。
最小例程工程备份链接:
https://ww0.lanzoul.com/iz4wd261k21i
仿真文件工程备份链接:
https://ww0.lanzoul.com/i8vTn261syyb
文章目录
- 预警系统最小例程构建
- 引言
- 用到的单片机工具
- 实物:
- 仿真:
- keil软件开发安装视频:
- 工程构建:
- 最小例程实操:
- 构建思路
- 代码实现
- (1)引入stm32头文件,相关变量定义以及初始化
- (2)然后在while循环里面, 我们就做三件事情
- (3)小灯初始化, 用小灯代替仪器应急处理
- (4) 上面的源代码
- 烧录调试:
- (1)配置烧录设置
- (2)人工调整变量, 查看程序能否正常运行
- (3)观察现象
- 总结反思:
- 读取到的数值
- 应急处理措施
- led灯状态优化
用到的单片机工具
实物:
下面是用到的材料:
stm32最小系统板,面包板,面包线,杜邦线,led灯,dht11,按键
手把手购买视频:
【手把手器件购买视频-哔哩哔哩】 https://b23.tv/nLOVso3
手把手安装视频:
【最小例程插线方法-哔哩哔哩】 https://b23.tv/k3CciWL
仿真:
也可以不购买器件,仿真安装视频:
【仿真软件安装教程】 https://b23.tv/z6qWs1K
仿真文件以及烧录视频:
仿真工程:https://ww0.lanzoul.com/i8vTn261syyb
【仿真烧录步骤-哔哩哔哩】 https://b23.tv/WojZSWY
烧录仿真配置:
输出仿真文件:
仿真图:
烧录步骤:
(1)双击单片机
(2)点击那个文件夹, 然后选中刚才的Output里面的hex文件, 然后确定
(3)然后点击左下角的运行按钮
keil软件开发安装视频:
【开发软件keil5安装-哔哩哔哩】 https://b23.tv/9pYqego
工程构建:
1.桌面新建文件夹
2.打开keil5软件
-
新建项目
4.选中刚才在桌面新建的文件夹
5.随便输入英文工程名字 ,保存
6.选中STM32F103C8 , 然后点击OK
7.然后就会跳出选择配置文件的页面, 如果没跳出,就点击这个绿色菱形
8.勾选下面的对钩, 只选最基本的,然后点击ok
- 下面配置一下编译器和环境语言
(1)点锤子
(2)选择version5 , 点击ok
(3)点击Edit->configuration
(4)选择 chinese GB2312 (Simplified), 点击ok (方便输入中文)
(5)启动文件这里变成800, 后面Onenet服务器要增加栈内存
10.工程源码文件夹创建
(1)点品字,下面的 这几块的功能介绍, 下一步直接开建
(2)Project Targets:是工程名字, 我们起zuixiao
Groups: 是组名字,我们先起 user
然后在右侧的Add Files里面,添加mina函数
(3)点击此处文件夹, 新建Source文件夹, 用来存放源码:
(4)点进去Source文件夹, 然后新建User文件夹,用来存放user组的文件
(5)点进去user文件夹, 然后点文件类型, 选择All files(这样就可以看到所有文件了)
(6),然后右键空白处新建main.c文件( 如果没有看到后缀,则百度搜索:如何看到文件后缀名), 可以新建一个txt文件, 然后右键重命名, 选中所有,然后改成main.c
(7)选中main.c, 然后点击add
(8)此处就可以看到main.c了,点击ok,
(9)双击左侧的 main.c文件, 就可以进行编辑了
最小例程实操:
构建思路
我们之前的思路就是, 读取环境信息-> 判断环境与阈值关系->是否启动预警
由于读取环境信息器件众多, 所以我们直接用变量来代替温湿度, 我们通过手动输入变量,来模拟器件读取
int temp; //读取的环境温度
int humi; //读取的环境湿度
由于此时条件有限, 我们也通过手动控制 温湿度阈值, 我们其实主要想实现, 判断环境信息与阈值对比,如果能报警,则行的通
int temp_th; //设置的温度阈值
int humi_th; //设置的湿度阈值
此时我们手动进行赋值,来模拟读取环境信息
temp = 28; //温度是28度
humi = 60; //湿度是60%
此时我们再设置一个阈值
temp_th = 30; //温度阈值是30度
humi = 60; //湿度阈值是60%
预警信息触发条件:
温湿度,超过阈值时候,触发
温度 >= 温度阈值 : 红灯亮
湿度 >= 湿度阈值 : 蓝灯亮
if(temp >= temp_th)
{
//红灯亮
}
else
{
//红灯灭
}
if(humi >= humi_th)
{
//蓝灯亮
}
else
{
//蓝灯灭
}
代码实现
(1)引入stm32头文件,相关变量定义以及初始化
#include "stm32f10x.h" // Device header
int temp; //读取的环境温度
int humi; //读取的环境湿度
int temp_th; //设置的温度阈值
int humi_th; //设置的湿度阈值
_Bool red_led; //(温度指示灯)
_Bool blue_led; //(湿度指示灯)
int main()
{
//手动设置环境温度(模拟上帝)
temp = 28; //温度是28度
humi = 60; //湿度是60%
//手动设置阈值(代替远程)
temp_th = 30; //温度阈值是30度
humi_th = 60; //湿度阈值是60%
//小灯初始化
red_led = 0;
blue_led = 0;
while(1)
{
}
return 0;
}
(2)然后在while循环里面, 我们就做三件事情
① 设置环境信息阈值(手动输入代替远程控制)
潜台词: (1)当需要设置的时候, 才设置阈值
引申: 所以写代码的时候, 就不能重复赋值了, 当管理员发设置阈值信息的时候, 我们才能 设置阈值
② 读取环境信息 (手动输入代替器件读取)
潜台词: 保证实时性,所以需要一直读取 , 其实也可以隔一段时间(例如100ms)读取一次
③ 每时每刻判断环境信息是否异常
潜台词; (1)为了保证实时性, 拿到阈值和环境信息,就直接判断了
引申风险: 但是如果环境正常还好, 直接跳出了, 但是如果环境异常,
我们是不是就需要启动仪器了. 如果 为了保证实时性, 一直判断异常了就报警, 异常就启动仪器, 那我们是不是就会一直陷入 启动仪器的循环之中?
解决方案: 增加读取仪器状态的信息, 当环境异常的时候. 在启动仪器前, 先看看仪器是否正在运行, 如果在运行, 就不必循环启动了.
if(环境异常) { if(仪器不运行) { 启动应急仪器(开灯演示) } else if(仪器已经在运行了) { 跳过(不操作) } }
Bool set_limit; //管理员是否设置阈值
set_limit = 1;
while(1)
{
//(1)设置环境信息阈值(手动输入代替远程控制)
if(set_limit == 1)
{
temp_th = 20;
humi_th = 30;
//设置完阈值就清零,下次不设置了, 直到set_limit被管理员置1
//然后再次进入此功能函数里,进行设置阈值
set_limit = 0;
}
//(2)读取环境信息 (手动输入代替器件读取)
//if(过了100毫秒) 每隔100毫秒,读取一次, 保证实时性,减轻负担
if(++timeCount >= 100)
{
temp = 26; //人手输入,代替器件,忽略底层,重视逻辑
humi = 40;
}
//(3)每时每刻判断环境信息是否异常
if(temp >= temp_th)
{
if(red_led == 0) //如果是关闭状态则启动,开启则无操作
{
red_led = 0;
}
}
else //同理,环境正常时候
{
if(red_led == 1) //如果是开启状态则启动,关闭则无操作
{
red_led = 0;
}
}
//判断湿度是否报警
//每时每刻判断环境信息是否异常
if(humi >= humi_th)
{
if(blue_led == 0) //如果是关闭状态则启动,开启则无操作
{
blue_led = 0;
}
}
else //同理,环境正常时候
{
if(blue_led == 1) //如果是开启状态则启动,关闭则无操作
{
blue_led = 0;
}
}
}
(3)小灯初始化, 用小灯代替仪器应急处理
① 先包含 膨胀位处理操作, 方便我们操作led:
右键user, 然后点击Add New item to Group ‘user’…
② 选择Source-> user的目录
③ 点击这个文件夹, 然后确认
④ 然后输入 sys.h , 然后点击Add
⑤ 把下面代码, 直接粘贴上去, 然后ctrl + s保存
#ifndef _SYS_H
#define _SYS_H
//操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第5章 92页 在 C 语言中使用位带操作 或<<CM3与M4权威指南>>第6章6.7.4 P216
//addr&0xF0000000取出最高的四位,其实就是用于区别SRAM(0x20000000)还是片上外设(0x40000000)的。
//+0x2000000对于SRAM位带区则得到 0x22000000,对于片上外设位带区则得到0x42000000?
//(addr&0xFFFFF)等效于(addr&0x000FFFFF),就是屏蔽掉高12位,地址范围是0x20000000-0x200FFFFF和0x40000000-0x400FFFFF
//<<5就等效于乘以32 ,同样<<2等效于乘以4 。
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//GPIO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//GPIO口操作,只对单一的GPIO口!确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
#endif
⑥ 我们初始化led的函数, 分配PB3 -> 温度报警小灯, 分配 PB4 湿度报警小灯, 分配 PB5 烟雾浓度报警小灯, 我们先用温湿度小灯, 后续再教大家如何演示浓度小灯, 正好方便后续演示拓展用.
大家直接复制黏贴即可, 如需了解,请看stm32基础教程,其实我们这里配置, 也仅仅是为了让大家看现象, 请不要害怕, 请直接复制黏贴
void led_init(void)
{
//初始化小灯 , PB3(温度) PB4(湿度) PB5(烟雾浓度)
GPIO_InitTypeDef GPIO_InitStructure;
//开启硬件时钟 PB5
/* GPIOD Periph clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//开启AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//禁用JTAGD端口
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
//配置 pB 3 4 5 GPIO工作模式(推挽输出)
/* Configure PD0 and PD2 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure); //配置端口
//控制端口寄存器GPIO输出电平
PBout(3) = 0;//temp_led
PBout(4) = 0; //humi_led
// PBout(5) = 0; //mq2_led
}
(4) 上面的源代码
在main.c里面的main函数, 也要包含一下第(3)步的 #include “sys.h” , 然后 小灯初始化, 也需要调用一下, 其次就是 我们现在比较简单, 所以操作小灯报警的时候, 就直接用PBout膨胀位了, 下面是综合上面的代码后的main.c代码。
main.c
代码:
#include "stm32f10x.h" // Device header
#include "sys.h"
int temp; //读取的环境温度
int humi; //读取的环境湿度
int temp_th; //设置的温度阈值
int humi_th; //设置的湿度阈值
_Bool red_led; //(温度指示灯)
_Bool blue_led; //(湿度指示灯)
_Bool set_limit; //管理员是否设置阈值
//led小灯初始化
void led_init(void);
int main()
{
unsigned short timeCount = 0; //发送间隔变量
//手动设置环境温度(模拟上帝)
temp = 28; //温度是28度
humi = 60; //湿度是60%
//手动设置阈值(代替远程)
temp_th = 30; //温度阈值是30度
humi_th = 60; //湿度阈值是60%
//小灯状态初始化
red_led = 0;
blue_led = 0;
//测试变量: 管理设置阈值符号位
set_limit = 1;
//小灯初始化
led_init();
while(1)
{
//(1)设置环境信息阈值(手动输入代替远程控制)
if(set_limit == 1)
{
temp_th = 50;
humi_th = 2;
//设置完阈值就清零,下次不设置了, 直到set_limit被管理员置1
//然后再次进入此功能函数里,进行设置阈值
set_limit = 0;
}
//(2)读取环境信息 (手动输入代替器件读取)
//if(过了100毫秒) 每隔100毫秒,读取一次, 保证实时性,减轻负担
if(++timeCount >= 100)
{
temp = 20; //人手输入,代替器件,忽略底层,重视逻辑
humi = 20;
timeCount = 0; //从新计时
}
//后面跟 1ms的延时, 我们一切从简,先留着
//(3)每时每刻判断环境信息是否异常
if(temp >= temp_th)
{
if(red_led == 0) //如果是关闭状态则启动,开启则无操作
{
red_led = 1;
PBout(3) = 1;
}
}
else //同理,环境正常时候
{
if(red_led == 1) //如果是开启状态则启动,关闭则无操作
{
red_led = 0;
PBout(3) = 0;
}
}
//判断湿度是否报警
//每时每刻判断环境信息是否异常
if(humi >= humi_th)
{
if(blue_led == 0) //如果是关闭状态则启动,开启则无操作
{
blue_led = 1;
PBout(4) = 1;
}
}
else //同理,环境正常时候
{
if(blue_led == 1) //如果是开启状态则启动,关闭则无操作
{
blue_led = 0;
PBout(4) = 0;
}
}
}
}
/**************************************************
函数名: led_init
功 能: 温湿度,烟雾浓度小灯初始化
参 数: 无
配置参数: PB3(温度) PB4(湿度) PB5(烟雾浓度)
推挽输出
小灯正极接io口, 负极接地
返回值: 无
**************************************************/
void led_init(void)
{
//初始化小灯 , PB3(温度) PB4(湿度) PB5(烟雾浓度)
GPIO_InitTypeDef GPIO_InitStructure;
//开启硬件时钟 PB5
/* GPIOD Periph clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//开启AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//禁用JTAGD端口
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
//配置 pB 3 4 5 GPIO工作模式(推挽输出)
/* Configure PD0 and PD2 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure); //配置端口
//控制端口寄存器GPIO输出电平
PBout(3) = 0;//temp_led
PBout(4) = 0; //humi_led
// PBout(5) = 0; //mq2_led
}
sys.h
代码:
#ifndef _SYS_H
#define _SYS_H
//操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第5章 92页 在 C 语言中使用位带操作 或<<CM3与M4权威指南>>第6章6.7.4 P216
//addr&0xF0000000取出最高的四位,其实就是用于区别SRAM(0x20000000)还是片上外设(0x40000000)的。
//+0x2000000对于SRAM位带区则得到 0x22000000,对于片上外设位带区则得到0x42000000?
//(addr&0xFFFFF)等效于(addr&0x000FFFFF),就是屏蔽掉高12位,地址范围是0x20000000-0x200FFFFF和0x40000000-0x400FFFFF
//<<5就等效于乘以32 ,同样<<2等效于乘以4 。
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//GPIO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//GPIO口操作,只对单一的GPIO口!确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
#endif
烧录调试:
(1)配置烧录设置
① 插上stlink , 然后按照如下方式连线
连线文字说明
实物说明(最小板和上述图引脚顺序一样,但stLink可能不一样,请对准,并且是下面那一排):
(2)stlink配置方法
①驱动:https://ww0.lanzoul.com/ipWDD261eamb
下载后直接安装
② 然后去小锤子里面进行调整烧录工具, 选择ST-Link Debugger
③ 这样就选中了stLink工具
④ 烧录插上stLink , 然后依次点击 即可烧录
⑤ 如果出错, 请再次检查 上述配置, 或者查看线是否插对插紧
(2)人工调整变量, 查看程序能否正常运行
通过调整 temp与 temp_th, 当temp > temp_th ,查看
红灯是否亮, temp <= temp_th , 红灯是否灭
调整humi与humi_th , 当humi > humi_th, 查看蓝灯是否亮, humi <= humi_th, 蓝灯是否灭
(3)观察现象
红灯报警现象:
温度 > 温度阈值
蓝灯报警现象:
总结反思:
到这里, 其实我们已经算实现了一个小案例了, 其实还有完善的空间,那就是
读取到的数值
是需要真实的器件, 每隔一段时间100ms,然后读取一次环境信息。判断预警的时候, 我们只是简单的判断了是否超过阈值,并且这个阈值还是我们手动输入的, 后续需要优化到wifi.
应急处理措施
比如小灯, 我们通过查看代码,就可以看出,我们写的太多了,
我们完全可以另外建立一个文件, 把温湿度以及温湿度阈值传进去, 然后通过判断, 再进行应急处理措施.
led灯状态优化
比如led灯, 其实它有两个变量, 一个是代表此时灯的状态, 另一个是
控制灯的亮灭, 我们可以通过结构体,来把他们进行组成一个函数, 我们只需要通过传进去小灯的状态即可, 至于小灯后续, 是亮还是灭, 需要更多的优先级判断, 才能决定, 我们不用考虑太多,请看如下示例,后面会进行优化.
传入状态, 至于启不启动器件, 再次在此函数内进行判断, 然后传回状态, 把他们深度绑定, 并且自我消化, 可以减少不必要的麻烦.