本帖最后由 zhang062061 于 2021-1-24 17:00 编辑
1.写在前面 AT指令在各种WIFI模块、2G/4G模块以及一些无线通讯模块中应用广泛。但是用过的朋友都知道,这种方式对于单片机编程来说,并不友好……本篇文章将以ESP8266 WIFI模块为例介绍在单片机裸机环境下编写AT指令程序的一种方式。 2.程序设计 首先串口底层的收发程序不在这里详细介绍。接收程序一般采用中断方式,采用超时判断的方式判断帧结束。 先简单介绍一个概念:状态机,状态转移图。对于程序来说,就是将程序分为几个状态,不同状态执行不同程序,判断条件进行状态转移。具体到C语言程序中,就是switch-case语句。 以ESP8266 WIFI模块的AT指令程序为例,将它的状态分为以下几种: 1.准备发送AT指令 2.发送AT指令 3.等待接收回复数据 4.接收成功 5.接收超时 将几种状态定义成一个枚举类型数据: - typedef enum
- {
- ATCMD_START = 0x00U,
- ATCMD_SEND = 0x01U,
- ATCMD_WAIT_REV = 0x02U,
- ATCMD_REVOK = 0x03U,
- ATCMD_TIMEOUT = 0x04U
- } ATCMD_StatusTypeDef;
复制代码
接下来用switch-case语句编写各个状态的程序,如下所示。 - //------------------发送AT指令-----------------
- //参数1:huart 串口号
- //参数2:cmd AT指令内容
- //参数3:timeout 单次发送的超时时间
- //参数4:res 要判断的返回结果
- //参数5:count 尝试次数
- //返回值:ATCMD_REVOK 发送且收到回复 ATCMD_TIMEOUT 超时
- ATCMD_StatusTypeDef AT_CMD_ESP12(UART_HandleTypeDef *huart,uint8_t* cmd,uint16_t timeout,const char* res,uint8_t count)
- {
- static ATCMD_StatusTypeDef atcmd_status=ATCMD_START;
- static uint8_t cnt=1;
- uint16_t temp;
- switch(atcmd_status)
- {
- case ATCMD_START:
- cnt = count;
- atcmd_status=ATCMD_SEND;
- break;
- case ATCMD_SEND:
- Clr_RxBuf();//清除串口接收缓存
- HAL_UART_Transmit_IT(huart,cmd,strlen((const char*)cmd));
- atcmd_status=ATCMD_WAIT_REV;
- Esp12cmd_tick=0;
- break;
- case ATCMD_WAIT_REV:
- if(Esp12cmd_tick < timeout)
- {
- if(Hand((char*)res,&temp,0))//
- {
- atcmd_status=ATCMD_REVOK;
- }
- }
- else
- {
- if(cnt>0)//再次发送
- atcmd_status=ATCMD_SEND;
- else
- atcmd_status=ATCMD_TIMEOUT;
- cnt--;
- }
- break;
- case ATCMD_REVOK:
- case ATCMD_TIMEOUT:
- atcmd_status=ATCMD_START;
- default:
- break;
- }
- return atcmd_status;
- }
复制代码
程序逻辑很简单,其中中Esp12cmd_tick变量为毫秒计数器,在SysTick_Handler中断中进行+1操作。 Hand()为判断串口接收数据中是否包含指定字符串的函数,如下: - //判断串口接收缓存中是否包含substr
- //参数1 *substr 被判断的字符串
- //参数2 *index substr在串口缓存中的位置
- //参数3 start_index串口缓存起始判断位置
- //返回值 包含返回1 不包含返回0
- uint8_t Hand(char* substr,uint16_t *index,uint16_t start_index)
- {
- uint16_t i,flag=0;
- char *p;
- if(Uart5.RxFlag != 1)return 0;//一帧数据未接收完成,直接返回
- Uart5.RxFlag = 0;
- for(i=start_index; i<RX_LEN; i++)
- {
- if(Uart5.RxBuf[i]==*substr)
- {
- flag=1;
- p=substr;
- while(*p)
- {
- if(Uart5.RxBuf[i]==*p)
- {
- *p++;
- i++;
- }
- else
- {
- flag=0;
- break;
- }
- }
- if(flag==1)
- {
- *index = i;
- break;
- }
- }
- }
- return flag;
- }
复制代码
调用方式 一般设置时都需要多条AT指令,也采用状态机的方式进行设置。以设置WIFI模块为AP模式为例,程序如下: - //-------------------设置为AP模式------------
- //设置的SSID 密码 和本机IP
- //成功返回0 返回1表示错误 返回2正在设置
- uint8_t SetAPMode(UART_HandleTypeDef *huart)
- {
- static uint8_t status=0;//
- static ATCMD_StatusTypeDef res;
-
- switch(status)
- {
- case 0:
- res = AT_CMD_ESP12(huart,(uint8_t*)"AT+CWMODE_DEF=2\r\n",1000,"OK",3);//设置为AP模式
- if(res==ATCMD_REVOK)
- status=1;
- else if(res==ATCMD_TIMEOUT)
- {
- status=0;
- return 1;
- }
- break;
- case 1:
- res = AT_CMD_ESP12(huart,(uint8_t*)"AT+CWSAP_DEF="ESP8266","1234567890",5,3\r\n",1000,"OK",3);//设置AP
- if(res==ATCMD_REVOK)
- status=2;
- else if(res==ATCMD_TIMEOUT)
- {
- status=0;
- return 1;
- }
- break;
- case 2:
- res = AT_CMD_ESP12(huart,(uint8_t*)"AT+CIPAP_DEF="192.168.5.1","192.168.5.1","255.255.255.0"\r\n",1000,"OK",3);//设置IP
- if(res==ATCMD_REVOK)
- status=3;
- else if(res==ATCMD_TIMEOUT)
- {
- status=0;
- return 1;
- }
- break;
- case 3://设置UDP
- res = AT_CMD_ESP12(huart,(uint8_t*)"AT+CIPSTART="UDP","192.168.5.255",8899,8266,0\r\n",1000,"OK",3);
- if(res==ATCMD_REVOK)
- status=4;
- else if(res==ATCMD_TIMEOUT)
- {
- status=0;
- return 1;
- }
- break;
- case 4://设置完成
- status=0;
- return 0;
- default:
- break;
- }
- return 2;
- }
复制代码
上述设置程序中,AT指令接收错误后的操作是返回执行第一条指令,当然也可以进行一些其它操作,比如多次接收错误后模块重新复位等。上述设置程序也可以是WIFI模块主程序的一个状态,WIFI主程序如下。 - void ESP12_Task(UART_HandleTypeDef *huart)
- {
- switch(WIFI_Status)
- {
- case 0://设置为AP模式
- if(SetAPMode(huart) == 0)//设置成功,转到等待接收状态
- {
- WIFI_Status=1;
- }
- break;
- case 1://等待接收数据
- if(Uart5.RxFlag == 1)
- {
- Uart5.RxFlag = 0;
- ESP12_Rev(huart);
- WIFI_Status = 2;
- }
- break;
- case 2:
- if(Send_Data(huart,Uart5.TxBuf,Uart5.TxNum)<2)
- {
- Uart5.TxFlag = 0;
- WIFI_Status = 1;
- }
- break;
- default:
- break;
- }
- }
复制代码
该函数在程序主循环中周期循环调用即可。可以完成AT指令的发送,等待的操作,也不影响其它程序的执行。
3.总结 本篇文章其实主要介绍了状态机的概念,层层调用。理解起来并不困难,实际编程中非常实用。可以广泛应用于其它程序的编写
欢迎关注公众号"嵌入式技术开发",大家可以后台给我留言沟通交流。如果觉得该公众号对你有所帮助,也欢迎推荐分享给其他人。
|