使用STM32实现 WIFI插座

硬件介绍

WIFI模块ESP-01S

使用AT指令控制,之前也写过代码,这次是将代码移植到STM32并新增一些功能。

继电器模块

(VCC 3.3V)当左侧IN输入低电平时,右侧的ON 和 COM会导通,左上的绿灯会亮,此处充当插座的角色 

项目需求

通过ESP-01SWIFI模块控制继电器

串口2用于与ESP8266通讯,串口1连接PC,用于打印log,查看系统状态。

项目接线

继电器 IN 接入单片机的PB5;(电源接3.3V)

WIFI模块TXRX分别接到单片机的RX2和TX2

CH340TXDRXD分别接到单片机的RX1和TX1

  

 

项目注意事项 

1. 在这个项目里,烧写完单片机之后,不光要点击复位,还要重启单片机电源,以确保WIFI模块重新启动

2. 在以后的工作中一般不直接在中断服务函数里处理数据,而是在收到数据后直接丢给队列,再处理数据

3. 在中断服务函数里尽量减少使用延时函数及打印函数

4. 2,3条在本项目中并没有实现,因为还没有学到Linux的队列概念,只是先留个印象

5. 由于之前的WIFI模块波特率改成了9600,所以现在连接CH340修改回 115200

 

 

AP模式下控制的实现

CubeMX

1. 惯例配置

2. 打开两个串口,并在WIFI模块对应的串口2打开中断

 

 

 3. 由于在中断处理函数中使用了Delay,因此不得不再次调整优先级

4. 配置GPIO,并初始化为高电平

再配置一个LED用于指示状态

 

 5. 惯例配置,导出工程

Keil

1. 打开MiroLIB,使得可以重写printf

2. 写代码

#include "stdio.h"
#include "string.h"

#define UART2_REC_LEN 200 //定义最大接收字节数 200,可根据需求调整

uint8_t buf=0; //串口接收缓存(1字节)
uint8_t UART2_RX_Buffer[UART2_REC_LEN];//接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint16_t UART2_RX_STA=0; //bit15,接收完成标志 //bit14,接收到0x0d //bit13~0,接收到的有效字节数目

char conn_wifi[] = "AT+CWJAP=\"BELL846\",\"541E5FE2C1E5\"\r\n"; //在"号前加上\是转义,使得"成为一个单纯的符号
char conn_server[] = "AT+CIPSTART=\"TCP\",\"192.168.2.15\",8880\r\n"; 
char touchuan[] = "AT+CIPMODE=1\r\n";
char init[] = "AT+RST\r\n";
char send[] = "AT+CIPSEND\r\n";

char flag_ok = 0;
char flag_wifi = 0;
char flag_fail = 0;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) // 接收完成回调函数,每收到一个字符(字节)后,就会在这里处理
{
	int i = 0;
	
	if(huart->Instance == USART2){ // 判断中断是由哪个串口触发的
		if((UART2_RX_STA & 0x8000) == 0){ // 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
			if(UART2_RX_STA & 0x4000){ // 如果已经收到了 0x0d (回车)
				// 则接着判断是否收到 0x0a (换行)
				if(buf == 0x0a){ // 如果 0x0a 和 0x0d 都收到,
					UART2_RX_STA |= 0x8000; //则将 bit15 位 置为1
					
					//程序运行到此处,代表数据已经完整接收,可以开始判断//
					///
					
					if(!strcmp((const char *)UART2_RX_Buffer, "WIFI GOT IP")){ // 查看是否收到 WIFI GOT IP
						flag_wifi = 1;
					}
					if(!strcmp((const char *)UART2_RX_Buffer, "OK")){ // 查看是否收到 OK
						flag_ok = 1;
						HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); //LED灭提示状态正常
					}
					if(!strcmp((const char *)UART2_RX_Buffer, "FAIL")){ // 查看是否收到 FAIL
						for(i=0; i<5; i++){
							HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); //LED闪烁提示用户连接失败
							HAL_Delay(1000);
						}
						HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); //LED亮表示准备开始重启
						printf(init);//重启WIFI模块
					}
					if(!strcmp((const char *)UART2_RX_Buffer,"open")){ //如果strcmp返回0,则说明指令是open 
						HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); //导通继电器
					}
					if(!strcmp((const char *)UART2_RX_Buffer,"close")){ //如果strcmp返回0,则说明指令是close
						HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); //断开继电器
					}
					
					memset(UART2_RX_Buffer, 0, UART2_REC_LEN); //重要!
					UART2_RX_STA = 0;//重要!

					///
					
				}else{ // 否则认为接收错误,重新开始
					UART2_RX_STA = 0;
				}
			}else{ // 如果没有收到 0x0d (回车)
				//则先判断收到的这个字符是否是 0x0d (回车)
				if(buf == 0x0d){ // 是的话则将 bit14 位置为1
					UART2_RX_STA |= 0x4000;
				}else{ // 否则将接收到的数据保存在缓存数组里
					UART2_RX_Buffer[UART2_RX_STA & 0X3FFF] = buf; //因为UART1_RX_STA只有前14位为有效数据,所以缓存数组UART1_RX_Buffer[X]中的X作为16位的二进制数,最高两位的判断应该写在前面代码的判断中,在此处不用
          UART2_RX_STA++;
					if(UART2_RX_STA > UART2_REC_LEN - 1){ //如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
							UART2_RX_STA = 0;
					}
				}
			 }
			}
	HAL_UART_Receive_IT(&huart2, &buf, 1); // 重新开启中断
			
	}
}
 
 
 
int fputc(int a, FILE *f) //一个字符一个字符发送 
{
	unsigned char temp[1] = {a};
	HAL_UART_Transmit(&huart2, temp, 1, 0xffff); //注意,printf的重写是针对串口2的,也就是wifi的,对于串口1的数据发送不能用printf
	return a;
}


int main(void)
{
	HAL_NVIC_SetPriority(SysTick_IRQn,0,0);
	HAL_UART_Receive_IT(&huart2, &buf, 1);//开启中断,并把数据存到buf里
	
	HAL_UART_Transmit(&huart1, "log ready\r\n", strlen("log ready\r\n"), 100); //注意,printf的重写是针对串口2的,也就是wifi的,对于串口1的数据发送不能用printf
	
	printf(conn_wifi); //发送联网AT指令并等待成功
	while(!flag_ok){
		HAL_Delay(50); //此处这个delay必须得加,不然程序会卡死
	}
	flag_ok = 0;
	HAL_UART_Transmit(&huart1, "wifi connect success\r\n", strlen("wifi connect success\r\n"), 100);

	printf(conn_server); //发送连服务器指令并等待成功
	while(!flag_ok){
		HAL_Delay(50);
	}
	flag_ok = 0;
	HAL_UART_Transmit(&huart1, "server connect success\r\n", strlen("server connect success\r\n"), 100);

	printf(touchuan); //发送透传模式指令并等待成功
	while(!flag_ok){
		HAL_Delay(50);
	}
	flag_ok = 0;
	HAL_UART_Transmit(&huart1, "touchuan mode success\r\n", strlen("touchuan mode success\r\n"), 100);

	printf(send); //发送数据传输指令并等待成功
	while(!flag_ok){
		HAL_Delay(50);
	}
	flag_ok = 0;
	HAL_UART_Transmit(&huart1, "send ready\r\n", strlen("send ready\r\n"), 100);

  while (1)
  {
		printf("pop and pop\r\n");//发给串口2的
		HAL_UART_Transmit(&huart1, "pop pop\r\n", strlen("pop pop\r\n"), 100);//发给串口1的
		HAL_Delay(3000);

  }
}

实现效果

在串口助手中:

在网络助手中:

可见,在电脑串口和网络调试助手可以看到不断发来的心跳包。

同时,串口2作为单片机和WIFI的通信,而串口1作为日志发回电脑的串口助手,串口2每发送并成功接收了回应,串口1就会返回电脑成功的信息,这样用户就可以可视化的看到连接进行到了哪一步,这算是用两路串口实现了以前白盒测试和黑盒测试的效果。

 当在网络助手发送“open”加上回车时,继电器会打开;反之输入“close”加上回车,继电器会闭合

 

 

STA模式下控制的实现

在这种模式下,ESP作为路由器,由电脑连接ESP的WIFI,作为Client来接收和发送。

以下是之前学习WIFI模块时总结的,关于ESP作为STA模式下的相关命令

 1. 配置成双模 AT+CWMODE=3

 

2. 查网段 AT+CIFSR

 之前就做过这步,已经知道,这个WIFI就是AI_Thinker那个

3. 让电脑连接上WIFI模块作为路由的WIFI

 4. 使能多连接 AT+CIPMUX=1 

5. 建立TCPServer AT+CIPSERVER=1 //default port = 333

 

6. 打开网络调试助手,选择TCP Client 和对应的网络信息,并点击连接,可以在串口助手中看到连接信息

7. 发送数据 AT+CIPSEND=0,8 //发送8个字节在连接0通道上 

此时可以发送信息(不用加回车!)并在网络助手中收到!

 

8. 断开连接 AT+CIPCLOSE=0

其实 这个实现和刚刚的思路是一样的。

CubeMX

同上

Keil

另外,通过白盒测试可知:在ESP作为STA的模式下,WIFI助手发送的数据在串口中显示的是有特定格式的:

如果发送“open”加回车,那么显示的是:

如果发送“close”加回车,那么显示的是:

因此,在KEIL中的代码中,字符串的比较不能直接和open close比较,而是和以上两个字符串进行比较!!!

1. 同上,打开MICRO-LIB

2. 代码:

#include "stdio.h"
#include "string.h"

#define UART2_REC_LEN 200 //定义最大接收字节数 200,可根据需求调整

uint8_t buf=0; //串口接收缓存(1字节)
uint8_t UART2_RX_Buffer[UART2_REC_LEN];//接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint16_t UART2_RX_STA=0; //bit15,接收完成标志 //bit14,接收到0x0d //bit13~0,接收到的有效字节数目

char mode[] = "AT+CWMODE=3\r\n";
char mul_con[] = "AT+CIPMUX=1\r\n";
char server[] = "AT+CIPSERVER=1\r\n";
char send[] = "AT+CIPSEND=0,8\r\n";
char init[] = "AT+RST\r\n";

char flag_ok = 0;
char flag_fail = 0;
char flag_con = 0;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) // 接收完成回调函数,每收到一个字符(字节)后,就会在这里处理
{
	int i = 0;
	
	if(huart->Instance == USART2){ // 判断中断是由哪个串口触发的
		if((UART2_RX_STA & 0x8000) == 0){ // 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
			if(UART2_RX_STA & 0x4000){ // 如果已经收到了 0x0d (回车)
				// 则接着判断是否收到 0x0a (换行)
				if(buf == 0x0a){ // 如果 0x0a 和 0x0d 都收到,
					UART2_RX_STA |= 0x8000; //则将 bit15 位 置为1
					
					//程序运行到此处,代表数据已经完整接收,可以开始判断//
					///
					
					if(!strcmp((const char *)UART2_RX_Buffer, "0,CONNECT")){ // 查看是否收到 0,CONNECT
						flag_con = 1;
					}
					if(!strcmp((const char *)UART2_RX_Buffer, "OK")){ // 查看是否收到 OK
						flag_ok = 1;
						HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); //LED灭提示状态正常
					}
					if(!strcmp((const char *)UART2_RX_Buffer, "FAIL")){ // 查看是否收到 FAIL
						for(i=0; i<5; i++){
							HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); //LED闪烁提示用户连接失败
							HAL_Delay(1000);
						}
						HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); //LED亮表示准备开始重启
						printf(init);//重启WIFI模块
					}
					if(!strcmp((const char *)UART2_RX_Buffer,"+IPD,0,6:open")){ //如果strcmp返回0,则说明指令是open 
						HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); //导通继电器
					}
					if(!strcmp((const char *)UART2_RX_Buffer,"+IPD,0,7:close")){ //如果strcmp返回0,则说明指令是close
						HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); //断开继电器
					}
					
					memset(UART2_RX_Buffer, 0, UART2_REC_LEN); //重要!
					UART2_RX_STA = 0;//重要!

					///
					
				}else{ // 否则认为接收错误,重新开始
					UART2_RX_STA = 0;
				}
			}else{ // 如果没有收到 0x0d (回车)
				//则先判断收到的这个字符是否是 0x0d (回车)
				if(buf == 0x0d){ // 是的话则将 bit14 位置为1
					UART2_RX_STA |= 0x4000;
				}else{ // 否则将接收到的数据保存在缓存数组里
					UART2_RX_Buffer[UART2_RX_STA & 0X3FFF] = buf; //因为UART1_RX_STA只有前14位为有效数据,所以缓存数组UART1_RX_Buffer[X]中的X作为16位的二进制数,最高两位的判断应该写在前面代码的判断中,在此处不用
          UART2_RX_STA++;
					if(UART2_RX_STA > UART2_REC_LEN - 1){ //如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
							UART2_RX_STA = 0;
					}
				}
			 }
			}
	HAL_UART_Receive_IT(&huart2, &buf, 1); // 重新开启中断
			
	}
}
 
 
 
int fputc(int a, FILE *f) //一个字符一个字符发送 
{
	unsigned char temp[1] = {a};
	HAL_UART_Transmit(&huart2, temp, 1, 0xffff); //注意,printf的重写是针对串口2的,也就是wifi的,对于串口1的数据发送不能用printf
	return a;
}

int main(void)
{
	HAL_NVIC_SetPriority(SysTick_IRQn,0,0);
	HAL_UART_Receive_IT(&huart2, &buf, 1);//开启中断,并把数据存到buf里
	
	HAL_UART_Transmit(&huart1, "log ready\r\n", strlen("log ready\r\n"), 100); //注意,printf的重写是针对串口2的,也就是wifi的,对于串口1的数据发送不能用printf
	
	printf(mode); 
	while(!flag_ok){
		HAL_Delay(50); //此处这个delay必须得加,不然程序会卡死
	}
	flag_ok = 0;
	HAL_UART_Transmit(&huart1, "mode set success\r\n", strlen("mode set success\r\n"), 100);

	printf(mul_con); 
	while(!flag_ok){
		HAL_Delay(50);
	}
	flag_ok = 0;
	HAL_UART_Transmit(&huart1, "enable success\r\n", strlen("enable success\r\n"), 100);

	printf(server); 
	while(!flag_con){
		HAL_Delay(50);
	}
	flag_ok = 0;
	flag_con = 0;
	HAL_UART_Transmit(&huart1, "server connect success\r\n", strlen("server connect success\r\n"), 100);


	HAL_UART_Transmit(&huart1, "send ready\r\n", strlen("send ready\r\n"), 100);

  while (1)
  {
		printf(send);//ESP作为STA时不能开启透传模式,也就是每次都必须调用AT+CIPSEND=0,8,才能每次发送8个字节
		HAL_Delay(1000);
		printf("pop an\r\n");//发给串口2的,共8个字节
		HAL_UART_Transmit(&huart1, "pop pop\r\n", strlen("pop pop\r\n"), 100);//发给串口1的
		HAL_Delay(3000);

  }
}

实现效果

打开单片机点击复位,使得电脑脸上”AI-Thinker“的WIFI,然后在网络助手中输入相关信息之后点击连接:

串口助手中:

网络助手中:

可见,在电脑串口和网络调试助手可以看到不断发来的心跳包。

 当在网络助手发送“open”加上回车时,继电器会打开;反之输入“close”加上回车,继电器会闭合

 

所以,实现了和ESP作为AP所实现的同样效果! 

 

 

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值