一种基于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中实现。
笔者说:
在设计协议框架时,考虑此例程的设计思路,将大大提高协议的灵活性。另外,建议开发者在进行嵌入式项目开发时,尽量在不同项目中复用之前已验证的协议框架,这样既可以缩短开发时间,又能保证框架的稳定性。
