讀這篇文章前,最好先閱讀這篇索引文章
如果你的系統是通過單片機去控制小風扇,也可以參考此文章。
如果一點單片機基礎都沒有的,麻煩請先去入門一下單片機基礎知識。
本系統的單片機在該系統中起着最重要的作用, 是數據管理和處理的核心。其主要實現的功能有:
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的內置函數佔用了,串口二串口三也被其他功能使用了,剩下串口四和串口五,但是它倆是同步的,沒學過,只好硬着頭皮去用咯…
單片機的所有代碼在這裏