本设计共分为单片机部分和Android端APP部分,本文给大家介绍一下环境检测系统的单片机部分是如何实现的。文末会给出keil工程和AD工程,有需要的可以参考一下。首先声明一下:本系统硬件部分采用的是市面上常见的电路模块,包含STM32F103C8T6最小系统板模块,DHT11温湿度模块,2.4寸带触摸的TFT屏幕模块,ESP8266_01 Wifi模块和GY-30 数字光模块,故大家对模块购买和电路连接就应该没啥问题。程序有一部分是经过网上收集并加工而成,我只是简单的对各个模块代码拼接了一哈,程序大部分都很简单,基本上都有注释哈。
环境检测系统设计的整体方案是:环境检测设备以STM32F103C8T6最小系统板作为主控模块,控制数据采集模块完成对环境参数的采集,其中数据采集模块包括:DHT11温湿度数据采集模块,GY-30 数字光模块。主控模块使用2.4寸带触摸的TFT屏幕模块作为输入输出设备,使用Wifi模块实现百度云Iot平台的数据的收发。同时配套开发的手机软件可以实现实时显示环境信息,也可以发送指令控制环境检测设备的工作状态。
1.1 模块实物图和电路图
1.2 模块规格概述和引脚说明
规格概述:STM32F103C8T6基于ARMCortex-M3内核,最高工作频率为72MHz,内置64K字节的闪存和20K字节的SRAM,拥有丰富的I/O端口和联接到两条APB总线的外设:包含2个12位的ADC、3个通用16位定时器和1个PWM定时器,还包含标准和先进的通信接口:多达2个I2C接口和SPI接口、3个USART接口、一个USB接口和一个CAN接口。
供电电压为2.0V至3.6V,包含-40°C至+85°C温度范围和-40°C至+105°C的扩展温度范围。
BT0/BT1启动选择端口用于选择最小系统板复位后的启动模式,它们是通过跳帽来选择连接VCC(高电平)还是GND(低电平)。相关的启动模式如表所示。
STM32F103C8T6最小系统板模块引脚说明如下所示。
2.1 模块实物图和电路图
2.2 模块规格概述和引脚说明
规格概述:DHT11数字温湿度模块是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比高等优点。单线制串行接口,使系统集成变得简易快捷[3]。测量范围:20-90%RH±5%RH,0-50℃±2℃。
3.1 模块实物图和电路图
3.2 模块规格概述和引脚说明
规格概述:GY-30 数字光模块是一款采用I2C总线接口通信的照度数字转换器。其光谱的范围与人眼相近,对光源的依赖性不大,50Hz/60Hz光噪声,可调的光学窗口使其具有宽范围和高分解(1-65535 勒克斯)的特性。模块使用1.8V逻辑输入接口,具有低电流关机功能,无需任何外部零件。
GY-30 数字光模块引脚说明如表所示。
4.1 模块实物图和电路图
4.2 模块规格概述和引脚说明
规格概述:该模块包含有LCD显示屏,背光控制电路以及触摸屏控制电路。其中LCD控制芯片采用ILI9341,ILI9341支持的分辨率为240*320,拥有一个172800字节大小的GRAM。同时支持8位、9位、16位、18位并口数据总线,还支持3线制和4线制SPI串口。ILI9341还支持65K、262K RGB颜色显示,显示色彩很丰富,同时支持旋转显示和滚动显示以及视频播放,显示方式多样。ILI9341控制器使用16bit (RGB565)来控制一个像素点显示,因此可以每个像素点显示颜色多达65K种。像素点地址设置按照行列的顺序进行,递增递减方向由扫描方式决定。ILI9341显示方法按照先设置地址再设置颜色值进行。采用SPI串行总线,只需几个I0即可点亮显示。
5.1 模块实物图和电路图
5.2 模块规格概述和引脚使用
规格概述:ESP8266是一款超低功耗的UART-WiFi 透传模块,拥有业内极富竞争力的封装尺寸和超低能耗技术,专为移动设备和物联网应用设计,可将用户的物理设备连接到Wi-Fi 无线网络上,进行互联网或局域网通信,实现联网功能[5]。ESP8266封装方式多样,天线可支持板载PCB天线,IPEX接口和邮票孔接口三种形式;ESP8266可广泛应用于智能电网、智能交通、智能家具、手持设备、工业控制等领域。支持无线802.11 b/g/n 标准。支持STA/AP/STA+AP三种工作模式。内置TCP/IP协议栈,支持多路TCP Client连接 。支持丰富的Socket AT指令。支持UART/GPIO数据通信接口。支持Smart Link 智能联网功能。支持远程固件升级(OTA)。内置32位MCU,可兼作应用处理器 。超低能耗,适合电池供电应用。
ESP8266模块支持STA/AP/STA+AP 三种工作模式。
(1)STA 模式:ESP8266模块通过路由器连接互联网,手机或电脑通过互联网实现对设备的远程控制。
(2)AP 模式:ESP8266模块作为热点,实现手机或电脑直接与模块通信,实现局域网无线控制。
(3)STA+AP 模式:两种模式的共存模式,即可以通过互联网控制可实现无缝切换,方便操作。
单片机上电之后,首先初始化系统滴答计时器SysTick,配置时钟源,设置工作频率为72MHz。接着初始化串口1和2的GPIO并配置其波特率为115200bps。然后初始化屏幕模块的GPIO,配置LCD的控制芯片的寄存器,初始化GRAM使其进入显示状态。然后读取STM32内部FLASH中数据检测触摸屏是否已经被校准,如果没有在内部FLASH中检测到触摸校准系数则进入触摸屏校准程序:按照LCD屏幕上的提示依次点击四个顶点后,校准程序完成,保存触摸校准参数在内部FLASH中。接着配置timer1每100ms运行触摸检测状态机。如果触摸屏已经校准完成,就在LCD屏幕上显示信息界面。接着初始化DHT11温湿度模块,GY-30数字光模块的GPIO,完成模块对应的寄存器的设置,然后配置timer2,每10s采集环境信息。初始化ESP8266Wiif模块的GPIO,配置Wifi无线网络的账户和MQTT账户信息,先连接指定的Wifi无线网络,再使用MQTT协议连接百度云Iot平台。配置timer3,每隔20s定时回送PING报文。配置timer1,每50ms处理通过串口2接收到的Wifi模块的数据。最后,进入指令的逻辑处理程序,监听收到的指令并回调功能函数。
ESP8266Wifi模块上电之后,由单片机初始化串口2的GPIO和配置波特率。单片机首先通过串口发送设置模块寄存器的指令,模块于是被配置为STA 模式,透传模式。模块接收带有Wifi名称和密码的数据,使用此数据可以连接指定的Wifi无线网络。在此过程中,模块可以通过串口发送实时连接状态给单片机。模块连接指定的Wifi无线网络成功之后,单片机发送百度云Iot平台的相关参数和MQTT账户信息,模块通过Wifi使用MQTT协议登录到百度云Iot平台指定的账户中。模块收到云端的数据将通过串口发送给单片机,同时将单片机需要发送的数据转发给云端。
屏幕模块上电之后,通过SPI通信接收到LCD控制器ITI9341的寄存器设置指令,配置LCD相关的寄存器。单片机检测内部FLASH是否含有校准参数,若没有校准参数,则开始触摸校准,通过使用模拟SPI与触摸屏控制芯片XPT2046通信,校准触摸屏并保存触摸参数,接着启动timer1,每100ms运行一次触摸检测状态机。最后,进入信息显示界面。
使用状态机加定时器的模式,可以大大提高单片机运行速度,更容易组织各模块代码,降低程序编写难度。不管是按键还是触摸屏,其检测状态机都差不多,由于按键的状态机前面的文章已有了,故这里就再给出触摸屏的检测状态机。
头文件首先声明:
//触屏信号有效电平
#define XPT2046_PENIRQ_ActiveLevel 0
#define XPT2046_PENIRQ_Read() PEN
/******触摸状态机相关******/
typedef enum
{
XPT2046_STATE_RELEASE = 0, //触摸释放
XPT2046_STATE_WAITING, //触摸按下
XPT2046_STATE_PRESSED, //触摸按下
}enumTouchState ;
//触摸状态
#define TOUCH_PRESSED 1
#define TOUCH_NOT_PRESSED 0
//触摸消抖阈值
#define DURIATION_TIME 2
u8 XPT2046_TouchDetect(void);//触摸屏检测状态机
函数实现:
/**
* @brief 触摸屏检测状态机
* @retval 触摸状态
* 该返回值为以下值之一:
* @arg TOUCH_PRESSED :触摸按下
* @arg TOUCH_NOT_PRESSED :无触摸
*/
u8 XPT2046_TouchDetect(void)
{
static enumTouchState touch_state = XPT2046_STATE_RELEASE;
static u32 i;
u8 detectResult = TOUCH_NOT_PRESSED;
switch(touch_state)
{
case XPT2046_STATE_RELEASE:
if(XPT2046_PENIRQ_Read()== XPT2046_PENIRQ_ActiveLevel) //第一次出现触摸信号
{
touch_state = XPT2046_STATE_WAITING;
detectResult =TOUCH_NOT_PRESSED;
}
else //无触摸
{
touch_state = XPT2046_STATE_RELEASE;
detectResult =TOUCH_NOT_PRESSED;
}
break;
case XPT2046_STATE_WAITING:
if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel)
{
i++;
//等待时间大于阈值则认为触摸被按下
//消抖时间 = DURIATION_TIME * 本函数被调用的时间间隔
//如在定时器中调用,每10ms调用一次,则消抖时间为:DURIATION_TIME*10ms
if(i > DURIATION_TIME)
{
i=0;
touch_state = XPT2046_STATE_PRESSED;
detectResult = TOUCH_PRESSED;
}
else //等待时间累加
{
touch_state = XPT2046_STATE_WAITING;
detectResult = TOUCH_NOT_PRESSED;
}
}
else //等待时间值未达到阈值就为无效电平,当成抖动处理
{
i = 0;
touch_state = XPT2046_STATE_RELEASE;
detectResult = TOUCH_NOT_PRESSED;
}
break;
case XPT2046_STATE_PRESSED:
if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel) //触摸持续按下
{
touch_state = XPT2046_STATE_PRESSED;
detectResult = TOUCH_PRESSED;
}
else //触摸释放
{
touch_state = XPT2046_STATE_RELEASE;
detectResult = TOUCH_NOT_PRESSED;
}
break;
default:
touch_state = XPT2046_STATE_RELEASE;
detectResult = TOUCH_NOT_PRESSED;
break;
}
return detectResult;
}
限于篇幅,上文虽是程序片段,但意思不难理解。通过定时器不断更新状态,故不会对其他模块造成运行堵塞问题。
对于习惯GUI编程的大家而言,使用GUI组件是毫不费力的,但估计还是有些人对单片机的裸机的绘制方法不太了解,这里给出了一种参考方法。
头文件声明:
/******屏幕页面组件相关******/
/******按钮******/
typedef struct
{
u16 start_x; //按钮的x起始坐标
u16 start_y; //按钮的y起始坐标
u16 end_x; //按钮的x结束坐标
u16 end_y; //按钮的y结束坐标
u8 str[20]; //按钮显示的文本
u16 fontColor; //按钮里面的文本颜色
u16 borderColor; //按钮边框的颜色
u16 backColor;//按钮背景颜色
u8 touchFlag; //按钮按下的标志,0:没按下,1:按下
void (*draw_btn)(void * btn); //按钮描绘函数
void (*btn_command)(void * btn); //按钮功能执行函数
}Button;
//按钮数量
#define BUTTON_NUM 2
extern Button *button;
void Button_Init(void);//初始化按钮参数(位置,外观,内容等)
//按钮的回调函数
void Button_Down(u16 x,u16 y);
void Button_Up(u16 x,u16 y);
/******文本视图******/
typedef struct
{
u16 start_x; //文本框的x起始坐标
u16 start_y; //文本框的y起始坐标
u16 end_x; //文本框的x结束坐标
u16 end_y; //文本框的y结束坐标
u8 str[40]; //文本框显示的文本
u16 fontColor; //文本框里面的文本颜色
u16 borderColor; //文本框边框的颜色
u16 backColor;//文本框背景颜色
void (*draw_tv)(void * textView); //文本框描绘函数
}TextView;
extern TextView *tv;
void TextView_Init(void);//初始化文本视图参数(位置,外观,内容等)
//文本视图数量
#define TEXTVIEW_NUM 12
/******页面视图管理器******/
typedef struct
{
u8 btnNum;
u8 tvNum;
void (*widgetInit)(void); //初始化页面组件的参数(位置,外观,内容等)
void (*drawWidget)(void); //页面视图描绘函数
void (*freeWidget)(void); //释放页面中不再使用的视图
}widgetManager;
extern widgetManager *wManager;//页面视图管理器
void widgetManagerInit(void);//初始化页面视图管理器
显然,大部分代码都能见名知意,其中widgetManager 是自定义GUI组件的管理器,用于所有组件的各种参数初始化并且绘制所有组件。
限于篇幅,感兴趣的可以下载工程去看一下具体实现,我知道自己写的不算太完美,欢迎大家的指正和建议!!
使用单片机通过不同的通信协议控制模块采集数据的程序大同小异,这里给出一种可行的程序模板,在一定程度上可以降低数据管理难度。
限于篇幅,,这里只给出DHT11温湿度模块的采集方案:
头文件首先声明:
//注意更改寄存器,引脚低8位用CRL,高8位CRH
#define DHT11_IO_IN() {GPIOA->CRL&=0XFFFFFF0F;GPIOA->CRL|=8<<(4*1);} //PA1控制DHT11,设置为输出
#define DHT11_IO_OUT() {GPIOA->CRL&=0XFFFFFF0F;GPIOA->CRL|=3<<(4*1);} //PA1控制DHT11,设置为输入
#define DHT11_OUT(x) GPIO_WriteBit(GPIOA, GPIO_Pin_1, (BitAction)x) //PA1控制DHT11
#define DHT11_DQ_IN GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) //PA1控制DHT11
void DHT11_Rst(void);//复位DHT11
char DHT11_Check(void);//等待DHT11的回应
char DHT11_Read_Bit(void);//读取一个位
char DHT11_Read_Byte(void);//读取一个字节
char DHT11_Read_Data(char *temp, char *humi);//读取一次数据温湿度
char DHT11_Init(void);//初始化DHT11
void DHT11_RefreshData(void);//刷新内存中采集的温湿度数据
//DHT11模块管理器
typedef struct
{
char humidity; //定义一个变量,保存湿度值
char temperature; //定义一个变量,保存温度值
char (*DHT11_Init)(void); //初始化DHT11
void (*DHT11_RefreshData)(void);//刷新内存中采集的温湿度数据
}DHT11Manager;
extern DHT11Manager* DHTManager; //DHT11模块管理器
void DHT11ManagerInit(void); //DHT11模块管理器初始化
DHT11Manager是DHT11的管理器,可以初始化模块,保存采集的数据,刷新保存的数据。其主要实现方法如下:
/*-------------------------------------------------*/
/*函数名:初始化DHT11 */
/*参 数:无 */
/*返回值:1错误 0正确 */
/*-------------------------------------------------*/
char DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义一个IO端口参数结构体
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA , ENABLE); //使能PA端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //准备设置PA8
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速率50Mhz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推免输出方式
GPIO_Init(GPIOA, &GPIO_InitStructure); //设置PA8
DHT11_Rst(); //复位DHT11
return DHT11_Check(); //返回DHT11的回复状态
}
/**
* @brief 刷新内存中采集的温湿度数据
* @param void
* @retval void
*/
void DHT11_RefreshData(void)
{
DHT11_Read_Data(&(DHTManager->temperature),&(DHTManager->humidity)); //读取温湿度值
}
DHT11Manager* DHTManager; //DHT11模块管理器
/**
* @brief DHT11模块管理器初始化
* @param void
* @retval void
*/
void DHT11ManagerInit(void)
{
DHTManager=(DHT11Manager *)malloc(sizeof(DHT11Manager));
DHTManager->DHT11_Init=DHT11_Init;
DHTManager->DHT11_RefreshData=DHT11_RefreshData;
}
要使用DHT11模块只需调用:
DHT11ManagerInit(); //DHT11模块管理器初始化
DHTManager->DHT11_Init();//DHT11模块初始化
DHTManager->DHT11_RefreshData();//刷新内存中采集的温湿度数据
在定时器的中断里只需重复调用 DHTManager->DHT11_RefreshData(); 即可刷新数据。
(1)首先将各个功能模块焊接组装完成之后,检查其电气连接性能确认无误。将程序编译后,通过串口1下载到单片机。
(2)单片机下载程序之后,显示屏出现触摸屏校准界面,按照提示依次点击屏幕上出现的点,进行触摸校准。
(3)触摸校准完成之后,进入信息显示初始界面,在此界面可看到两栏的内容:环境参数栏目,网络信息栏目,点击屏幕下方的两个按钮都可以立即刷新相应栏目的内容,具有很好的实时性。
(4)等待环境参数采集模块工作,完成第一次采集。等待Wifi模块工作,建立网络连接。
(5)将板子处于不同的环境下,可看见屏幕上温度,湿度,光照数据的变化。
keil工程和AD工程资源链接
“忆往昔峥嵘岁月稠。 恰同学少年,风华正茂;书生意气,挥斥方遒“。 转眼距离本科毕业还有12天了,回首大学四年,,,算了没啥回首的━┳━ ━┳━。最后,祝所有已经工作的老板们百尺竿头,更进一步!祝所有还在读书的同学们学业有成,前程似锦! 行