一种基于ESP8266 AT指令集的通用协议框架
实际应用中,在采用单片机进行项目设计时,常常会出现这样一种场景。场景中包含主控和其它多个从设备,且主控与从设备之间需要定时进行数据交互,如:恒温箱中,单片机定时读取从设备温度传感器的数据,根据获取的温度值,控制加热模块的加热方式。
简单应用中,可根据外设的技术手册,以及相应的接口协议(如:IIC,SPI等)直接获取从设备数据;若从设备逻辑复杂、实时性等有一定的技术要求,此时,就不得不考虑将从设备和其它外设单独作为一个”小系统“,通过特定的接口和主控进行通讯。
主控需要通过协议(自定义或通用协议)和“小系统”进行通讯。相应地,主控和“小系统”都需要实现协议的封包和解包函数。
基于以上内容,本文将介绍一种协议的设计方法,此方法源于乐鑫提供的ESP8266 RTOS源码中的AT指令部分。事实上,此设计方法非常通用,在芯片设计行业几乎是“标准”。
1. 源码下载
笔者已将AT相关的例程放在此网站,可直接下载(点击下载附件)。
2. 架构逻辑
(1)入口函数
./esp8266_at/user/user_main.c -> at_init();
(2)函数处理逻辑
3. 指令介绍
(1)at.h
系统整个全局变量定义为枚举类型,增加了代码的可读性。
通过结构体+函数指针来实现所有指令的统一管理,后期维护将会很方便。
typedef enum { at_statIdle, at_statRecving, at_statProcess, at_statIpSending, at_statIpSended, at_statIpTraning } at_stateType; ...... typedef struct { char *at_cmdName; int8_t at_cmdLen; void (*at_testCmd)(uint8_t id); void (*at_queryCmd)(uint8_t id); void (*at_setupCmd)(uint8_t id, char *pPara); void (*at_exeCmd)(uint8_t id); } at_funcationType;
(2)at_cmd.h
定义了相关指令的具体名称、长度、所需执行的函数等。采用此种方法定义指令,可在其它文件中实现具体函数功能,方便扩展。
#define at_cmdNum 32 at_funcationType at_fun[at_cmdNum]= { {NULL, 0, NULL, NULL, NULL, at_exeCmdNull}, {"E", 1, NULL, NULL, at_setupCmdE, NULL}, {"+RST", 4, NULL, NULL, NULL, at_exeCmdRst}, {"+GMR", 4, NULL, NULL, NULL, at_exeCmdGmr}, {"+GSLP", 5, NULL, NULL, at_setupCmdGslp, NULL}, {"+IPR", 4, NULL, NULL, at_setupCmdIpr, NULL}, #ifdef ali {"+UPDATE", 7, NULL, NULL, NULL, at_exeCmdUpdate}, #endif {"+CWMODE", 7, at_testCmdCwmode, at_queryCmdCwmode, at_setupCmdCwmode, NULL}, {"+CWJAP", 6, NULL, at_queryCmdCwjap, at_setupCmdCwjap, NULL}, {"+CWLAP", 6, NULL, NULL, at_setupCmdCwlap, at_exeCmdCwlap}, {"+CWQAP", 6, at_testCmdCwqap, NULL, NULL, at_exeCmdCwqap}, {"+CWSAP", 6, NULL, at_queryCmdCwsap, at_setupCmdCwsap, NULL}, {"+CWLIF", 6, NULL, NULL, NULL, at_exeCmdCwlif}, {"+CWDHCP", 7, NULL, at_queryCmdCwdhcp, at_setupCmdCwdhcp, NULL}, {"+CIFSR", 6, at_testCmdCifsr, NULL, at_setupCmdCifsr, at_exeCmdCifsr}, {"+CIPSTAMAC", 10, NULL, at_queryCmdCipstamac, at_setupCmdCipstamac, NULL}, {"+CIPAPMAC", 9, NULL, at_queryCmdCipapmac, at_setupCmdCipapmac, NULL}, {"+CIPSTA", 7, NULL, at_queryCmdCipsta, at_setupCmdCipsta, NULL}, {"+CIPAP", 6, NULL, at_queryCmdCipap, at_setupCmdCipap, NULL}, {"+CIPSTATUS", 10, at_testCmdCipstatus, NULL, NULL, at_exeCmdCipstatus}, {"+CIPSTART", 9, at_testCmdCipstart, NULL, at_setupCmdCipstart, NULL}, {"+CIPCLOSE", 9, at_testCmdCipclose, NULL, at_setupCmdCipclose, at_exeCmdCipclose}, {"+CIPSEND", 8, at_testCmdCipsend, NULL, at_setupCmdCipsend, at_exeCmdCipsend}, {"+CIPMUX", 7, NULL, at_queryCmdCipmux, at_setupCmdCipmux, NULL}, {"+CIPSERVER", 10, NULL, NULL,at_setupCmdCipserver, NULL}, {"+CIPMODE", 8, NULL, at_queryCmdCipmode, at_setupCmdCipmode, NULL}, {"+CIPSTO", 7, NULL, at_queryCmdCipsto, at_setupCmdCipsto, NULL}, {"+CIUPDATE", 9, NULL, NULL, NULL, at_exeCmdCiupdate}, {"+CIPING", 7, NULL, NULL, NULL, at_exeCmdCiping}, {"+CIPAPPUP", 9, NULL, NULL, NULL, at_exeCmdCipappup}, #ifdef ali {"+MPINFO", 7, NULL, NULL, at_setupCmdMpinfo, NULL} #endif };
4. 解析和处理
(1)at_recvTask
接收串口数据,并对相应的AT指令进行解析,然后交给at_procTask函数进行处理。
/** * @brief Uart receive task. * @param events: contain the uart receive data * @retval None */ static void ICACHE_FLASH_ATTR at_recvTask(os_event_t *events) { static uint8_t atHead[2]; static uint8_t *pCmdLine; uint8_t temp; //add transparent determine while(READ_PERI_REG(UART_STATUS(UART0)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S)) { WRITE_PERI_REG(0X60000914, 0x73); //WTD if(at_state != at_statIpTraning) { temp = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF; if((temp != '\n') && (echoFlag)) { uart_tx_one_char(temp); //display back } } switch(at_state) { case at_statIdle: //serch "AT" head atHead[0] = atHead[1]; atHead[1] = temp; if((os_memcmp(atHead, "AT", 2) == 0) || (os_memcmp(atHead, "at", 2) == 0)) { at_state = at_statRecving; pCmdLine = at_cmdLine; atHead[1] = 0x00; } else if(temp == '\n') //only get enter { uart0_sendStr("\r\nERROR\r\n"); } break; case at_statRecving: //push receive data to cmd line *pCmdLine = temp; if(temp == '\n') { system_os_post(at_procTaskPrio, 0, 0); pCmdLine++; *pCmdLine = '\0'; at_state = at_statProcess; if(echoFlag) { uart0_sendStr("\r\n"); } } else if(pCmdLine >= &at_cmdLine[at_cmdLenMax - 1]) { at_state = at_statIdle; } pCmdLine++; break; case at_statProcess: //process data if(temp == '\n') { uart0_sendStr("\r\nbusy p...\r\n"); } break; case at_statIpSending: *pDataLine = temp; if((pDataLine >= &at_dataLine[at_sendLen - 1]) || (pDataLine >= &at_dataLine[at_dataLenMax - 1])) { system_os_post(at_procTaskPrio, 0, 0); at_state = at_statIpSended; } else { pDataLine++; } break; case at_statIpSended: //send data if(temp == '\n') { uart0_sendStr("busy s...\r\n"); } break; case at_statIpTraning: os_timer_disarm(&at_delayCheck); if(pDataLine > &at_dataLine[at_dataLenMax - 1]) { os_timer_arm(&at_delayCheck, 0, 0); os_printf("exceed\r\n"); return; } else if(pDataLine == &at_dataLine[at_dataLenMax - 1]) { temp = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF; *pDataLine = temp; pDataLine++; at_tranLen++; os_timer_arm(&at_delayCheck, 0, 0); return; } else { temp = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF; *pDataLine = temp; pDataLine++; at_tranLen++; os_timer_arm(&at_delayCheck, 20, 0); } break; default: if(temp == '\n') { } break; } } if(UART_RXFIFO_FULL_INT_ST == (READ_PERI_REG(UART_INT_ST(UART0)) & UART_RXFIFO_FULL_INT_ST)) { WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR); } else if(UART_RXFIFO_TOUT_INT_ST == (READ_PERI_REG(UART_INT_ST(UART0)) & UART_RXFIFO_TOUT_INT_ST)) { WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_TOUT_INT_CLR); } ETS_UART_INTR_ENABLE(); }
(2)at_procTask
对指令进行处理,具体函数在at_baseCmd.c、at_wifiCmd.c、at_ipCmd.c三个文件中实现,处理完成后并返回相应的数据。
/** * @brief Task of process command or txdata. * @param events: no used * @retval None */ static void ICACHE_FLASH_ATTR at_procTask(os_event_t *events) { if(at_state == at_statProcess) { at_cmdProcess(at_cmdLine); if(specialAtState) { at_state = at_statIdle; } } else if(at_state == at_statIpSended) { at_ipDataSending(at_dataLine); if(specialAtState) { at_state = at_statIdle; } } else if(at_state == at_statIpTraning) { at_ipDataSendNow(); } }
5. 指令分类
ESP8266 AT指令集(点击下载附件)可分为三大类:基础AT指令 、wifi功能AT指令、TCP/IP相关指令。
基础AT指令,在文件at_baseCmd.c中实现;wifi功能AT指令,在文件at_wifiCmd.c中实现;TCP/IP相关指令,在文件at_ipCmd.c中实现。
笔者说:
在设计协议框架时,考虑此例程的设计思路,将大大提高协议的灵活性。另外,建议开发者在进行嵌入式项目开发时,尽量在不同项目中复用之前已验证的协议框架,这样既可以缩短开发时间,又能保证框架的稳定性。