在物联网无限通讯技术中,有着例如lora、ook、WIFI无线通讯等技术。他们有着各自的优缺点,本次实验中,采用的是WIFI无线通讯,连接单片机和手机。能够通过手机上的微信小程序来控制单片机上LED灯的亮灭,在单片机上按下按键,也能实现门铃报警功能。
1:单片机是韦东山的STM32F103MINI开发板
2:无线WIFI模块是乐鑫ESP8266
本次项目,使用cubeMX和MDK两个软件完成。
cubeMX是意法半导体推荐的单片机图形化配置软件,通过这个软件开发项目,能够大大节省开发过程中浪费在配置过程中的时间,减少项目的开发周期,提高效率。而cubeMX所生成的代码是通过HAL库实现的,HAL库对比标准库而言,封装性更高,可移植性更高。这也是现在意法半导体主推的开发方式。
打开cubeMX,根据硬件原理图进行配置。
1配置PA0,上升沿触发中断。可以在GPIO→User Label中写上标签KEY,这样在配置完成后,PA0的会被重命名为KEY,方便使用,这里忘了写。
2配置PA1推挽输出,标签为LED。
3配置PD1和PD0为连接外部高速晶振HSE的引脚,提供时钟。
4配置PA14和PA13为调试引脚,连接ST-LINK。
5配置PA9和PA10为USART1_RX和USART1_TX,这里主要用来串口输出信息。
6配置PA2和PA3为USART2_TX和USART2_RX,配置中断,这里用来连接WIFI模块。
当所有需要的引脚配置完毕后,进入Clock Configuration配置界面,可以看到HSE默认为8MHz,不用做修改。点击HCLK,输入72之后回车,软件会自动配置前面的分频因子,得到结果。将挂载在APB1和总线配置为36MHz,APB2总线上的外设配置为72MHz的系统时钟。
在Code Generator中勾选Generate peripheral initialization sa apair of c/h files per peripheral 这个选项,对不同外设生成多个源文件和头文件。
选择MDK-ARM,点击生成,之后可以用MDK打开,开始写之后的代码实现功能。
在usart.c文件中,将printf函数和scanf函数进行重定义,以便在串口工具中输入输出。这里必须要记住的是,所有的代码必须在注释中/* USER CODEBEGIN*/ 和/* USER CODE END 1 */之间的地方进行编写,之后如果还想要对项目修改,再次用cubemx生产配置之后,写在其他地方的代码会被删除,只有在规定地方写的才会保留。
/*****************************************************
*function: 写字符文件函数
*param1: 输出的字符
*param2: 文件指针
*return: 输出字符的 ASCII 码
******************************************************/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 10);
return ch;
}
/*****************************************************
*function: 读字符文件函数
*param1: 文件指针
*return: 读取字符的 ASCII 码
******************************************************/
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, (uint8_t*)&ch, 1, 10);
return (int)ch;
}
#pragma import(__use_no_semihosting)
struct __FILE
{
int a;
};
FILE __stdout;
FILE __stdin;
void _sys_exit(int x)
{
}
static uint8_t Rx_Data=0;
static uint8_t buff[200];
static uint8_t newbuff[200];
//static uint8_t tx_buff[200]={0};
static uint8_t len=0;
在HAL库中,hsart2的中断回调函数是一个虚函数,意思是说,如果用户没有定义则会运行原本的函数,但如果用户重新定义了该函数,则会运行用户写的。这里要把他复制过来重新定义,这样触发中断后会运行重新写的函数。
static uint8_t Rx_Data=0;
static uint8_t buff[200];
static uint8_t newbuff[200];
//static uint8_t tx_buff[200]={0};
static uint8_t len=0;
//开启接受中断
void rx_start(void)
{
HAL_UART_Receive_IT(&huart2,(uint8_t*)&Rx_Data,1);
}
//中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
buff[len%200]=Rx_Data;
len++;
HAL_UART_Receive_IT(&huart2,(uint8_t*)&Rx_Data,1);
}
//清空buf和len
void clearbuffer()
{
memset(buff,0,sizeof(buff));
len=0;
}
//将接受数据赋值
uint8_t huart2_Rx(uint8_t *data)
{
memcpy(data,buff,len);
return len;
}
// 串口2发送数据
void usart2_Transmit(uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
clearbuffer();
HAL_UART_Transmit(&huart2, pData, Size, Timeout);
}
命令发送函数中,对输入的代码进行处理,利用strstr给他加上\r\n,然后发送出去再接收。接收的时候,利用sys进行延时,防止接受不全的情况出现。在查看ESP8266的数据手册的时候,能看到收到命令之后他会返回OK,判断单片机收到的数据中有没有OK,如果有就说明发送成功,没有就是失败。
uint8_t Cmd_Transmit(char *cmd, char *judge, uint32_t Timeout)
{
//输入AT命令后面加入\r\n
char buf[256]={0};
strcat(buf,cmd);
if(strstr(buf,"\r\n")==0)
{
strcat(buf,"\r\n");
}
usart2_Transmit(buf, strlen(buf), 500);
memset(buf, 0, 256);
while(Timeout != 0)
{
if(huart2_Rx((uint8_t *)buf))
{
if (strstr(buf, judge))
{
// 发送成功
printf("%s Send ok!\r\n", cmd);
if (strstr(buf, "CIFSR"))
{
printf("%s\r\n", buf); // 打印IP地址
}
return 0;
}
else
{
Timeout--;
HAL_Delay(1);
}
}
}
printf("%s Send error!\r\n", cmd); // 发送失败
return 1;
}
uint8_t Data_Transmit(char *data)
{
// 1.准备发送的指令
char buf[256] = {0};
uint8_t len = strlen(data);
sprintf(buf, "AT+CIPSEND=0,%d\r\n", len);
// 2.发送指令
if (Cmd_Transmit(buf, "OK", 500) == 0)
{
// 3.发送数据
Cmd_Transmit(data, "OK", 1000);
return 0;
}
return 1;
}
ESP8266可以使用TCP、UDP或者透传模式进行无线通讯,这里使用的是TCP,当然也可以用UDP。
主函数中,首先要调用一次 HAL_UART_Receive_IT函数,否则不会进入中断。然后开始发送命令。
之后打开开发板商提供的微信小程序,输入在串口工具上显示的ip地址,和端口9999,进行绑定。注意手机要连在同一个wifi信号下。
rx_start();
Cmd_Transmit("AT+RST\r\n", "OK",1500);//重启模块
HAL_Delay(1000);
Cmd_Transmit("AT+CWMODE=1", "OK", 500);//设置sta模式
Cmd_Transmit("AT+CWJAP=\"dadada\",\"1941025788\"", "OK", 5000);//输入wifi账号密码
Cmd_Transmit("AT+CIPMUX=1","OK", 500);//设置多连接模式
Cmd_Transmit("AT+CIPSERVER=1,9999", "OK", 500);//设置端口
Cmd_Transmit("AT+CIFSR", "OK", 500);//查询模块IP
在gpio.c中,写下有案件的中断回调函数和获取按键状态的函数。
在main.c中判断按键状态,如果按键按下,就发送一段数据。注意的是在主函数中写在while(1)的循环中,这样可以不断轮询按键状态。
这时候按下按键,就会发现,微信小程序上的门铃按钮变成红色,代表受到了报警。
uint8_t Key_GetFlag(void)
{
if(key_flag)
{
key_flag = 0;
return 1;
}
else
return 0;
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin ==GPIO_PIN_0)
{
key_flag = 1;
//HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, key_flag?GPIO_PIN_RESET:GPIO_PIN_SET);
}
}
if (Key_GetFlag() == 1)
{
Data_Transmit("{\"data\":\"doorbell\",\"status\":\"1\"}");
}
加入这段代码之后,在手机上点击台灯按钮,就会发现单片机上的led亮灭了。
if (huart2_Rx(rx_data)) //接收到数据
{
if (strstr((char *)rx_data, "\"dev\":\"led\",\"status\":\"0\""))
{
printf("led off\n\r");
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
clearbuffer();
memset(rx_data, 0, 200);
}
else if (strstr((char *)rx_data, "\"dev\":\"led\",\"status\":\"1\""))
{
printf("led on\n\r");
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
clearbuffer();
memset(rx_data, 0, 200);
}
}
根据开发板提供的资料来说,还可以使用UDP和透传的方法进行无线通讯,实现起来都是大同小异的。这个小项目中可以修改的地方还有很多,比如说增加wifi账号和密码修改,否则换一个wifi就要重新写代码了。