原标题:RISC-V单片机快速入门05-玩转ESP8266 WIFI模块② 来源:知乎
前言:
本文引用地址:http://www.eepw.com.cn/article/202006/414785.htm
上一节,我们使用串口工具发送AT指令操作ESP-01S,本节,使用GD32VF103代替传偶工具完成和ESP-01S模块的交互过程。
一、基础知识
1.交互流程简介
(1)设备上电,先控制8266的复位引脚为低电平,让模块复位
(2)发送指令:ATE0,取消回显
(3)发送指令:AT+CWMODE=2,设置ESP01S为AP模式
(4)发送指令:AT+CIPMUX=1,设置多路连接,AP模式最多支持5个设备连接
(5)发送指令:AT+CWSAP="ESP01S_test","12345678",1,3,启动一个WIFI热点
(6)发送指令:AT+CIPSERVER=1,8089,启动TCP Server
(7)发送指令:AT+CIPSERVER=1,8089,启动TCP Server
(8)大循环中检测是否收到ESP01S数据,收到数据后立刻返回。
2.程序框架简介
程序主要包括如下4个功能模块:ESP01S初始化、串口处理、Event回调函数、事件处理;串口处理模块包括串口接收和定时器判断一帧数据是否接收完成功能,Event回调函数主要用来通知应用层系统的状态,方便应用层做出相应,比如设备检测到其他TCP Client客户端接入模块,可以控制LED状态,事件处理模块主要包含应用程序大循环,大循环中检测系统事件状态,根据事件状态再大循环中做出响应。
二、系统功能模块详述
1.Event回调函数
本程序使用了函数指针,应用层将事件处理函数传到hal_common.c中int hal_sys_contex_init(sys_status_fun fun, void *user_data)函数
void system_status_callback(int sock, int event){ system_context->sock_id = sock; system_context->event = event; switch (event) { case STA_CONNECTED: rt_kprintf("Sock %d connected!rn", sock); break; case STA_CLOSED: rt_kprintf("Sock %d closed!rn", sock); break; case STA_DATA_ARRIVED: rt_kprintf("Sock %d data arrived!rn", sock); break; default: break; }}typedef enum { STA_CONNECTED, STA_CLOSED, STA_DATA_ARRIVED, // clients send data to wifi STA_EVENT_MAX,}sys_event_e;typedef void (*sys_status_fun)(int sock, int event);typedef struct sys_ctx{ int sock_id; sys_event_e event; char data_buf[SYS_CTX_UART_RECV_SIZE]; sys_status_fun sys_status_cb; void *user_data;}sys_ctx_t;int hal_sys_contex_init(sys_status_fun fun, void *user_data){ sys_contex.sys_status_cb = fun; sys_contex.user_data = user_data; return 0;}int main(void){ hal_sys_contex_init(system_status_callback, RT_NULL); while(1) { }}
2.串口处理
串口处理模块包括串口接收和定时器判断一帧数据是否接收完成功能,串口接收函数代码如下:
#define RX_BUF_MAX_LEN 1024 //最大接收缓存字节数struct STRUCT_USART_Fram_S //串口数据帧的处理结构体{ char Data_RX_BUF [ RX_BUF_MAX_LEN ]; uint16_t FramLength; struct { uint8_t FramStartFlag; uint8_t FramFinishFlag; } InfBit;} ;struct STRUCT_USART_Fram_S Esp8266_Frame_Record;void USART2_IRQHandler(){ uint8_t ch = -1; if(RESET != usart_interrupt_flag_get(EVAL_COM2, USART_INT_FLAG_RBNE)) { ch = usart_data_receive(EVAL_COM2);// if ( Esp8266_Frame_Record.FramLength < ( RX_BUF_MAX_LEN - 1 ) ) //预留1个字节写结束符// { Esp8266_Frame_Record .Data_RX_BUF [ Esp8266_Frame_Record.FramLength ] = ch;// } Esp8266_Frame_Record.FramLength ++; if (Esp8266_Frame_Record.FramLength >= 1024) { Esp8266_Frame_Record.FramLength = 0; } cnt = Esp8266_Frame_Record.FramLength;// rt_kprintf(".......uart recv : %c, count is %drn", ch, cnt); Esp8266_Frame_Record.InfBit.FramStartFlag = 1; }}
中断处理函数中,将接收的数据放到Esp8266_Frame_Record .Data_RX_BUF中,然后将
Esp8266_Frame_Record.InfBit.FramStartFlag置1,这个标志位再定时器中会用到,可以用来判断接收一帧数据是否完成。
一帧数据接收是否完成的判断逻辑是:定时器会定期检测,如果FramStartFlag为1,说明串口正在接收数据,没接收一个数据,FramLength加1,因此,当进入定时器中断函数,判断FramStartFlag为1情况下FrameLength如果不再增加,说明一帧数据接收完成。
static void timeout1(void *parameter){ int sock_id = -1; char buff[128] = { 0x00 }; int len = 0; sys_event_e event = STA_EVENT_MAX; // rt_kprintf("timer's cnt is %d, FrameLength is %drn", cnt, Esp8266_Frame_Record.FramLength); if (1 == Esp8266_Frame_Record.InfBit.FramStartFlag) { if (cnt == Esp8266_Frame_Record.FramLength && cnt != 0) { cnt = 0; Esp8266_Frame_Record .Data_RX_BUF [ Esp8266_Frame_Record.FramLength ] = 0x00; rt_kprintf("timer --------> data %srn", Esp8266_Frame_Record.Data_RX_BUF); if (rt_strstr(Esp8266_Frame_Record.Data_RX_BUF, "CONNECT")) { sscanf(Esp8266_Frame_Record.Data_RX_BUF, "%d,%s", &sock_id, buff); event = STA_CONNECTED; }else if (rt_strstr(Esp8266_Frame_Record.Data_RX_BUF, "CLOSED")) { sscanf(Esp8266_Frame_Record.Data_RX_BUF, "%d,%s", &sock_id, buff); event = STA_CLOSED; }else if (rt_strstr(Esp8266_Frame_Record.Data_RX_BUF, "+IPD")) { rt_memset(hal_sys_contex_get()->data_buf, 0x00, SYS_CTX_UART_RECV_SIZE); sscanf(Esp8266_Frame_Record.Data_RX_BUF, "%*[^+]+IPD,%d,%d:%[^r]", &sock_id, &len, hal_sys_contex_get()->data_buf); event = STA_DATA_ARRIVED; rt_kprintf("parsed +IPD :%srn", hal_sys_contex_get()->data_buf); } // call sys_status_cb if (hal_sys_contex_get()->sys_status_cb) { hal_sys_contex_get()->sys_status_cb(sock_id, event); } Esp8266_Frame_Record.InfBit.FramFinishFlag = 1; Esp8266_Frame_Record.InfBit.FramStartFlag = 0; }else { cnt = Esp8266_Frame_Record.FramLength; } }else { cnt = 0; Esp8266_Frame_Record.FramLength = 0; }}
注意:事件处理本质上是在此调用hal_sys_contex_get()->sys_status_cb(sock_id, event)映射到应用层的void system_status_callback(int sock, int event)函数。
3.事件处理
事件处理的核心再while(1)中,根据系统当前事件状态做出响应,本节是检测到事件为数据类型时候,将数据原路返回。
int main(void){ /* enable the LED clock */ rcu_periph_clock_enable(RCU_GPIOA); /* configure LED GPIO port */ gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1); gpio_bit_reset(GPIOA, GPIO_PIN_1); // create iwdt_thread dynamic_thread = rt_thread_create("led_thread", led_process_thread_entry, RT_NULL, 512, 2, 10); rt_thread_startup(dynamic_thread); // init sys_ctx hal_sys_contex_init(system_status_callback, RT_NULL); system_context = hal_sys_contex_get(); hal_timer_init(); ESP8266_Init(); rt_thread_mdelay(1000); ESP8266_Ate0(); tcp_server_init(); tcp_server_start(); while(1) { if (STA_DATA_ARRIVED == system_context->event) { // send back ESP8266_SendString ( DISABLE, system_context->data_buf, rt_strlen(system_context->data_buf), system_context->sock_id ); } rt_thread_mdelay(10); } return 0;}
三、运行
下载程序完毕后,重启设备,ESP01S启动一个WIFI热点,并启动TCP Server,log如下:
电脑连接热点,使用网络助手连接192.168.4.1:8089
网络助手发送数据给ESP01S
关闭网络助手,应用程序也可以检测到,如下Log所示