读这篇文章前,最好先阅读这篇索引文章
如果你的系统是通过单片机去控制小风扇,也可以参考此文章。
如果一点单片机基础都没有的,麻烦请先去入门一下单片机基础知识。
本系统的单片机在该系统中起着最重要的作用, 是数据管理和处理的核心。其主要实现的功能有:
1.除湿、通风;
2.与APP通过串口通信以及配置WiFi;
3.与CC2530串口通信;
4.智能照明;
5.与语音模块通过串口通信以及语音模块的设置。
1.智能除湿、智能通风
单片机(我的单片机型号是stm32f103系列)通过串口二接收来自协调器节点(CC2530,这个在目录3会说明)的温湿度、气体浓度数据,并根据适宜衣柜储存的温湿度、气体浓度来判断是否执行抽湿机、通风机。串口二对应的接口是PA2(TXD)、PA3(RXD),单片机串口二接收协调器节点数据处理函数关键代码(usart.c)如下:
提示一下,以下代码主要实现的是:判断是否接收到来自串口二发送过来的数据,如果接收到了,就根据温湿度和气体浓度的理想值判断是否开关抽湿模块、通风模块。
#include "sys.h"
#include "usart2.h"
#include "usart3.h"
#include "delay.h"
#include "stdarg.h"
#include "stdio.h"
#include "string.h"
#include "timer.h"
#include "common.h"
#include "lcd.h"
#include "GUI.h"
#if EN_USART2_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART2_RX_BUF[USART2_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART2_RX_STA=0; //接收状态标记
u8 pi2;
void uart2_recdeal(void)
{
static u16 len,t;
if(USART2_RX_STA&0x8000)
{
len=USART2_RX_STA&0x3fff;//得到此次接收到的数据长度
//printf("\r\n老米:\r\n\r\n");
if( USART2_RX_BUF[0]=='1') //温湿度,1T&H:
{
printf("%s\r\n",USART2_RX_BUF); //发送到串口
if( USART2_RX_BUF[8]>'6')
{
LED0=0;
}
else if(USART2_RX_BUF[8]<'3')
{
LED0=1;
}
// printf("%c\r\n",USART2_RX_BUF[5]); //发送到串口
// sprintf((char*)pi,"Rec %d byte ",rlen);//接收到的字节数
// Show_Str(0,102,BLUE,WHITE,pi,12,0); //显示接收到的数据长度
// Show_Str(0,102,BLUE,WHITE,USART3_RX_BUF,12,0); //显示接收到的数据长度
}
else if(USART2_RX_BUF[0]=='2') //气体浓度 2MQ2:
{
//printf("%s\r\n",USART2_RX_BUF); //发送到串口
//printf("%c\r\n",USART2_RX_BUF[5]); //发送到串口
//printf("%c\r\n",USART2_RX_BUF[6]); //发送到串口
static int k;
for(k=0;k<8;k++)
{
printf("%c",USART2_RX_BUF[k]);
}
if( USART2_RX_BUF[5]>='2')
{
LED1=0;
}
else if(USART2_RX_BUF[5]<'1')
{
LED1=1;
}
printf("\r\n");//插入换行
}
for(t=0;t<len;t++)
{
// USART_SendData(USART2, USART2_RX_BUF[t]);//向串口2发送数据
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待发送结束
}
//printf("\r\n\r\n");//插入换行
USART2_RX_STA=0;
//delay_ms(10);
}
}
void uart2_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能USART1,GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOA.2
//USART1_RX GPIOA.3初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOA.3
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART2, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART2, ENABLE); //使能串口1
}
void USART2_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART2); //读取接收到的数据
if((USART2_RX_STA&0x8000)==0)//接收未完成
{
if(USART2_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART2_RX_STA=0;//接收错误,重新开始
else USART2_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART2_RX_STA|=0x4000;
else
{
USART2_RX_BUF[USART2_RX_STA&0X3FFF]=Res ;
USART2_RX_STA++;
if(USART2_RX_STA>(USART2_REC_LEN-1))USART2_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
}
#endif
usart.h代码如下:
#ifndef __USART2_H
#define __USART2_H
#include "stdio.h"
#include "sys.h"
#define USART2_REC_LEN 200 //定义最大接收字节数 200
#define EN_USART2_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART2_RX_BUF[USART2_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART2_RX_STA; //接收状态标记
//如果想串口中断接收,请不要注释以下宏定义
void uart2_init(u32 bound);
void uart2_recdeal(void);
#endif
有不懂的可以问。
2.与APP通过串口通信以及配置WiFi
该系统的单片机通过串口三配置Wi-Fi模块,实现与手机APP通信。
ESP8266WiFi模块一共有三种工作模式,分别是AP模式、STA模式、STA+AP模式,该系统选用的是STA模式,如果使用该模式,单片机就能够连接路由器,同时会得到一个IP地址,只要我们用手机去连接这个IP地址,就可以实现手机和单片机通信。此时ESP8266模块是终端,可连接室内路由器、手机的热点或者AP热点:
①模块连接AP热点,可与其进行双向数据通信。
②模块连接手机的热点,可与网络调试助手通信。
③模块连接路由器,可以在同一路由器的局域网络下的手机或电脑网络调试助手软件进行通信。
④模块可以通过路由器接入互联网,实现手机通过外网去控制智能衣柜。
因此,手机APP就可以通过广域网实现与单片机通信。
由于该系统把单片机作为TCP服务器,所以我在此介绍一下串口无线STA模式下的TCP服务器的配置。
了解上述配置原理之后,即可编写单片机代码,在此,我引用了Wi-Fi的标准例子来修改,Wi-Fi模块连接单片机的串口三(怎么接线在我的另一篇文章有,详情请点击链接),即PB11(RXD)、PB10(TXD),以下是单片机配置和处理接收来自手机APP数据的代码(wifista.c):
#include "common.h"
#include "stdlib.h"
#include "lcd.h"
#include "GUI.h"
#include "usart2.h"
#include "timer3.h"
//ATK-ESP8266 WIFI STA测试
//用于测试TCP/UDP连接
//返回值:0,正常
// 其他,错误代码
char *Data1="@PlayFlashText#001$";//这是语音播报的命令
char *Data2="@PlayFlashText#002$";
char *Data3="@PlayFlashText#003$";
char *Data4="@PlayFlashText#004$";
u8 atk_8266_wifista_test(void)
{
u8 ipbuf[16]; //IP缓存
u8 res=0;
u8 *p;
p=mymalloc(SRAMIN,32); //申请32字节内存
atk_8266_send_cmd("AT+CWMODE=1","OK",50); //设置WIFI STA模式
atk_8266_send_cmd("AT+RST","OK",20); //DHCP服务器关闭(仅AP模式有效)
delay_ms(1000); //延时3S等待重启成功
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
//设置连接到的WIFI网络名称/加密方式/密码,这几个参数需要根据您自己的路由器设置进行修改!!
sprintf((char*)p,"AT+CWJAP=\"%s\",\"%s\"",wifista_ssid,wifista_password);//设置无线参数:ssid,密码
while(atk_8266_send_cmd(p,"WIFI GOT IP",300)); //连接目标路由器,并且获得IP
delay_ms(1000);
delay_ms(1000);
//TCP Server
{
LCD_Clear(WHITE);
POINT_COLOR=RED;
Show_Str(0,0,RED,WHITE,"Config ATK-ESP success!",12,0);
atk_8266_send_cmd("AT+CIPMUX=1","OK",20); //0:单连接,1:多连接
sprintf((char*)p,"AT+CIPSERVER=1,%s",(u8*)portnum);
atk_8266_send_cmd(p,"OK",20); //开启Server模式,端口号为8086
}
LCD_Clear(WHITE);
delay_ms(200);
atk_8266_get_wanip(ipbuf);//服务器模式,获取WAN IP
printf("ip:%s\r\n",ipbuf);
sprintf((char*)p,"IP:%s",ipbuf);
Show_Str(0,0,RED,WHITE,p,12,0); //显示IP地址
sprintf((char*)p,"port:%s",(u8*)portnum);
Show_Str(0,13,RED,WHITE,p,12,0); //显示端口
Show_Str(0,26,RED,WHITE,"static:",12,0); //连接状态
Show_Str(0,65,RED,WHITE,"send data:",12,0); //发送数据
Show_Str(0,90,RED,WHITE,"receive data:",12,0); //发送数据
atk_8266_wificonf_show(0,64," ",(u8*)wifista_ssid,(u8*)wifista_encryption,(u8*)wifista_password);
USART3_RX_STA=0;
myfree(SRAMIN,p); //释放内存
return res;
}
u8 key;
u16 t=999; //加速第一次获取链接状态
u8 *pi;
u16 rlen=0;
u8 constate=0; //连接状态
void atk_8266_wifista_server_test(void)
{
extern int b;
u8 i=0;
pi=mymalloc(SRAMIN,100);//申请100字节内存
key=KEY_Scan(0);
if(key==WKUP_PRES) //WK_UP
{
//LED0=1;
}
else if(send_sta ==1) //定时发送数据
{ //TCP Server
send_sta = 0;
// sprintf((char*)pi,"ATK-8266 TCP Server ");//测试数据
sprintf((char*)pi,"%s",USART2_RX_BUF);
Show_Str(3,78,BLUE,WHITE,pi,20,0);
atk_8266_send_cmd("AT+CIPSEND=0,20","OK",200); //发送指定长度的数据
delay_ms(1000);
atk_8266_send_data(pi,"OK",100); //发送指定长度的数据
USART3_RX_STA=0;
}
t++;
delay_ms(10);
if(USART3_RX_STA&0X8000) //接收到一次数据了
{
// printf("%s",USART3_RX_BUF); //发送到串口
if( USART3_RX_BUF[3]=='I') //USART3_RX_BUF[2]=='+'&&
{
if( USART3_RX_BUF[11]=='t')
{
if( USART3_RX_BUF[12]=='1')
{
LED0=0;
// sprintf((char*)pi,"%s",Data);
for(i=0;i<strlen((const char*)Data1);i++)
{
while(USART_GetFlagStatus(UART4,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕
USART_SendData(UART4, Data1[i]);
}
//@Retransmit#*PlayFlashText^001&$
}
else if(USART3_RX_BUF[12]=='0')
{
LED0=1;
for(i=0;i<strlen((const char*)Data2);i++)
{
while(USART_GetFlagStatus(UART4,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕
USART_SendData(UART4, Data2[i]);
}
}
}
else if(USART3_RX_BUF[11]=='c')
{
if( USART3_RX_BUF[12]=='1')
{
LED1=0;
for(i=0;i<strlen((const char*)Data3);i++)
{
while(USART_GetFlagStatus(UART4,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕
USART_SendData(UART4, Data3[i]);
}
}
else if(USART3_RX_BUF[12]=='0')
{
LED1=1;
for(i=0;i<strlen((const char*)Data4);i++)
{
while(USART_GetFlagStatus(UART4,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕
USART_SendData(UART4, Data4[i]);
}
}
}
rlen=USART3_RX_STA&0X7FFF; //得到本次接收到的数据长度
USART3_RX_BUF[rlen]=0; //添加结束符
printf("%s",USART3_RX_BUF); //发送到串口
sprintf((char*)pi,"Rec %d byte ",rlen);//接收到的字节数
Show_Str(0,102,BLUE,WHITE,pi,12,0); //显示接收到的数据长度
Show_Str(0,102,BLUE,WHITE,USART3_RX_BUF,12,0); //显示接收到的数据长度
}
USART3_RX_STA=0;
if(constate!='+')t=1000; //状态为还未连接,立即更新连接状态
else t=0; //状态为已经连接了,10秒后再检查
}
if(t==1000)//连续10秒钟没有收到任何数据,检查连接是不是还存在.
{
constate=atk_8266_consta_check();//得到连接状态
if(constate=='+')Show_Str(45,26,BLUE,WHITE,"success ",12,0); //连接状态
else Show_Str(45,26,BLUE,WHITE,"fail ",12,0);
USART3_RX_STA=0;
t=0;
}
// if((t%30)==0)LED0=!LED0;
myfree(SRAMIN,pi); //释放内存
}
不知道代码想要实现什么的,我可以提示一下,以上给出的代码,首先是判断是否接收到来自串口二(也就是感知层发送过来的数据),如果接收到了,就定时发送给wifi模块,也就是发给串口三,然后WiFi模块接收到之后,会转发给APP。不用管WiFi模块是怎么发给APP的,只需要知道的是,WiFi模块内置了tcp/ip协议,只要APP是用tcp/ip协议实现网络通信,就可以了。
3.智能照明
我为什么要把智能照明也要放到单片机功能实现呢?因为智能照明的供电来源就是单片机呀!图里没有说明连接哪里的GND其实就是要连接单片机的GND引脚。
4.与语音模块通过串口通信以及语音模块的设置
单片机通过串口四实现语音控制和语音播报反馈。
首先介绍一下WEGASUN-M6语音识别模块,要想实现语音控制智能衣柜,得先设置好识别词条和反馈词条。
将USB设置器(USB转TTL)与语音识别模块连接,然后将USB设置器与电脑连接起来,注意TXD是连接RXD。
连接好硬件之后,就可以对语音模块进行配置了。
首先安装串口驱动,这个我会放到资料包里的。
然后通过WEGASUN-M6提供的语音识别软件(我也放资料包了),设置识别和反馈词条。我设置的识别词条是:
@WriteKeywords#打开抽湿机 001|关闭抽湿机 002|通风 003|关闭通风机 004|$
我设置的反馈词条是:@WriteFlashText#|001马上给您打开抽湿机|002马上给您关闭抽湿机|003马上给您通风|004马上给您关闭通风机$
所以只要说其中的一句识别词条,就会有相对应的语音播报反馈,并返回一个数字给串口。
温馨提示:词条不要乱加什么空格,直接复制我的就行了,它格式是固定的。
对于具体怎么设置词条,它有个说明书的,我放资料包里了。
至此语音模块配置完了,由于语音模块在识别后做出反馈的时候会返回数字给串口,识别的是001的词条,则返回的是01数字,识别的是002的词条,则返回的数字是02 …以此类推。
该系统的语音功能通过单片机串口四实现,串口四的接口分别是:PC10(TXD)、PC11(RXD),以下是单片机主要实现代码(uart4.c):
#include "sys.h"
#include "uart4.h"
#include "led.h"
#include "delay.h"
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 UART4_RX_BUF[UART4_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 UART4_RX_STA=0; //接收状态标记
u8 voice_cmd = 0;
void uart4_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE); //??UART4??
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能UART4,GPIOC时钟
USART_DeInit(UART4); //????4
//UART4_TX PC10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PC.10
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化GPIOC.9
//UART4_RX PC.11
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化GPIOC.10
//UART ?????
USART_InitStructure.USART_BaudRate = bound; //波特率9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(UART4, &USART_InitStructure); //初始化串口4
//Usart4 NVIC ??
NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;//子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
USART_ITConfig(UART4, USART_IT_RXNE, ENABLE); //开启串口接受中断
USART_Cmd(UART4, ENABLE); //使能串口4
}
void UART4_IRQHandler(void) //串口4中断服务程序 语音控制模块
{
u8 Res;
if(USART_GetITStatus(UART4, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(UART4); //读取接收到的数据
printf("Res:%c",Res);
switch(Res)
{
case 01: voice_cmd = 1;
break;
case 02: voice_cmd = 2;
break;
case 03: voice_cmd = 3;
break;
case 04: voice_cmd = 4;
break;
case 05: //后续功能
break;
case 06:
break;
}
TIM_Cmd(TIM4, ENABLE); //关闭TIM4
}
}
还有uart.h的代码:
#ifndef __UART4_H
#define __UART4_H
#include "stdio.h"
#include "sys.h"
#define UART4_REC_LEN 200 //定义最大接收字节数 200
extern u8 voice_cmd;
extern u8 UART4_RX_BUF[UART4_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 UART4_RX_STA; //接收状态标记
//如果想串口中断接收,请不要注释以下宏定义
void uart4_init(u32 bound);
#endif
想说明一下,为什么我要使用同步串口通信呢?因为串口1 被WiFi的内置函数占用了,串口二串口三也被其他功能使用了,剩下串口四和串口五,但是它俩是同步的,没学过,只好硬着头皮去用咯…
单片机的所有代码在这里