赞
踩
有了AT指令,使用PC操作模组是远远不可能的,我们更想使用MCU来独立操作模组与服务器或PC进行通信。
在使用AT指令的时候,直接发送AT指令的一端称为客户端(AT Client),接收AT指令并返回响应的一端称为服务端(AT Server)。
ESP8266、M26、BC35-G这些通信模组都是接收我们发送的AT指令,所以称为AT命令服务端,MCU 需要向模组主动发送AT指令,称为AT客户端,它们之间的通信架构如下:
上图有三个数据流:
发送AT指令:可以直接调用HAL库提供的API发送,AT框架并无太大作用;
等待接收返回结果:可以直接调用HAL库的API使用中断方式接收;
接收服务端主动发送的数据:可以直接调用HAL库的API使用中断方式接收;
三条数据流都可以调用HAL库的API直接实现,另外设计一层AT框架的意义在于:
1.在直接调用HAL库实现的时候,无法保证每次模组向 MCU 发送的数据都能完整的被接收,所以,我们需要设计一层串口驱动以保证数据在任何时候都可以被完整的接收进缓冲区。
2.在接收数据之后,难点在于对数据的处理,判断AT指令发送的数据是不是正常的返回结果,从返回结果中提取有效信息等等。这些如果每条指令接收之后,都去写代码依次判断,代码量陡增暂且不说,编程的难度也是直接上升。
所以,我们需要基于串口驱动,在保证数据被完整接收的前提之上,再根据AT命令通信的特点,设计一层AT框架,专门负责解析数据,提取有效信息。
MCU想要和模组通信,首先要使用IPC通信协议的一种进行命令收发,一般采用串口来实现。串口驱动直接使用LiteOS提供的驱动框架实现,由于其特殊性,最底层的驱动框架实现文件放在了工程目录中,调用HAL库提供的API实现:
uart_at.c
文件中,主要完成了两个功能:
串口初始化函数的调用架构如图:
默认初始化LPUART1为AT指令的串口,可更改为其他串口
UART_HandleTypeDef uart_at;
static USART_TypeDef* s_pUSART = LPUART1;
static uint32_t s_uwIRQn = LPUART1_IRQn;
中断服务函数中USART_IT_IDLE
和USART_IT_RXNE
区别
USART_IT_RXNE
中断USART_IT_IDLE
中断ring_buffer
是专门实现的用户存放接收数据的缓冲区,用户只需要调用read和write操作缓冲区即可,其实现文件在iot-link SDK的IoT_LINK\iot_link\link_misc
路径下:
ring_buffer在串口初始化函数中被调用初始化:
/*******************************************************************************
function :use this function to initialize the uart
parameters :
instruction :
*******************************************************************************/
bool_t uart_at_init(void *pri)
{
//initialize the at controller
(void) memset(&g_atio_cb,0,sizeof(g_atio_cb));
if(false == osal_semp_create(&g_atio_cb.rcvsync,CN_RCVMEM_LEN,0))
{
printf("%s:semp create error\n\r",__FUNCTION__);
goto EXIT_SEMP;
}
ring_buffer_init(&g_atio_cb.rcvring,g_atio_cb.rcvringmem,CN_RCVMEM_LEN,0,0);
缓冲区大小在配置文件宏定义中声明:
CONFIG_UARTAT_RCVMAX=2048
CONFIG_UARTAT_BAUDRATE=115200
CONFIG_UARTAT_DEVNAME="atdev"
初始化之后,向LiteOS注册的中断服务函数只需要调用ring_buffer_write
向缓冲区不停的写入接收到的数据,即可保证串口数据被完整的接收。
因为数据全部保存在了ring_buffer
中,所以串口驱动的read API实现用缓冲区提供的读取函数实现即可。
实现read和write两个函数之后,调用如下的宏定义,即可将设备和驱动注册到系统中:
OSDRIV_EXPORT(uart_at_driv,CONFIG_UARTAT_DEVNAME,(los_driv_op_t *)&s_at_op,NULL,O_RDWR);
AT客户端框架的实现源码在SDK的IoT_LINK\iot_link\at文件夹下。
其框架结构如下:
如图,因为串口设备已经注册到了系统中,所以AT框架的底层发送和接收函数直接调用LiteOS设备驱动框架提供的API_at_read,_at_write
实现,除了上述图中的这些,还涉及到大量的使用信号量、互斥锁、字符串比较等函数进行AT指令匹配处理,提取结果的代码,这些不是理解AT框架的重点,所以图中未给出。
在实现了AT框架之后,最终留给用户使用的接口只要三个,即可完成AT指令的交互,非常简洁:
AT客户端完整框架包括MCU与通信模组的串口通信驱动,用户编程接口AT客户端框架。所以需要在.config
文件中将此两项使能。
CONFIG_AT_ENABLE=y
CONFIG_DRIVER_ENABLE=y
使能之后,不仅驱动框架的源码和AT框架的源码会被加入工程,还会在link_main
函数进行自动初始化。
///< install the driver framework
#ifdef CONFIG_DRIVER_ENABLE
#include <driver.h>
///< install the driver framework for the link
(void)los_driv_init();
#endif
///< install the at framework
#ifdef CONFIG_AT_ENABLE
#include <at.h>
(void)at_init();
#endif
函数声明:
/**
* @brief:use this function to register a function that monitor the URC message
* @param[in]:cmd, the command to send
* @param[in]:cmdlen, the command length
* @param[in]:index, the command index, if you don't need the response, set it to NULL; this must be a string
* @param[in]:respbuf, if you need the response, you should supply the buffer
* @param[in]:respbuflen,the respbuf length
* @param[in]:timeout, the time you may wait for the response;and the unit is ms
*
* @return:>=0 success (return the received data length) while -1 failed
* */
int at_command(const void *cmd, size_t cmdlen,const char *index,\
void *respbuf,size_t respbuflen,uint32_t timeout);
例程实现函数
static bool_t esp8266_atcmd(const char *cmd,const char *index){
int ret = 0;
ret = at_command((unsigned char *)cmd,strlen(cmd),index,NULL,0,5000);
if(ret >= 0)
{
return true;
}
else
{
return false;
}
}
static int at_esp8266_demo_entry(){
char cmd[64];
int ret;
/* 测试AT是否OK,超时时间5S */
memset(cmd,0,64);
snprintf(cmd,64,"AT\r\n");
while(false == esp8266_atcmd(cmd, "OK"))
{
printf("AT Test fail, repeat.\r\n");
}
printf("AT test ok.\r\n");
/* 关闭回显 */
memset(cmd,0,64);
snprintf(cmd,64,"ATE0\r\n");
ret = esp8266_atcmd(cmd, "OK");
if(ret == false)
{
printf("ATE0 test fail.\r\n");
}
else
{
printf("ATE0 test ok.\r\n");
}
/* 设置模式为AP+STA */
memset(cmd,0,64);
snprintf(cmd,64,"AT+CWMODE=3\r\n");
ret = esp8266_atcmd(cmd, "OK");
if(ret == false)
{
printf("AT+CWMODE=3 test fail.\r\n");
}
else
{
printf("AT+CWMODE=3 test ok.\r\n");
}
/* 连接路由器 */
memset(cmd,0,64);
snprintf(cmd,64,"AT+CWJAP=\"%s\",\"%s\"\r\n", SSID, PASSWD);
while(false == esp8266_atcmd(cmd, "OK"))
{
printf("try to join AP:%s fail, repeat.\r\n", SSID);
}
printf("AT+CWMODE=3 test ok.\r\n");
return 0;
}
对于AT命令返回的结果,如果其中存放有效信息,我们可以在调用at_command时传入一个缓冲区,如下,发送查询模组的ip地址,并从中提取出ip地址:
/* 获取ip地址 */
const char cs_cmd[] = "AT+CIFSR\r\n";
char buffer[150];
char *str;
uint8_t ip[4];
memset(buffer,0,150);
ret = at_command(cs_cmd,strlen(cs_cmd),"OK", buffer, 150, 5000);
if(ret < 0)
{
printf("AT+CIFSR test fail.\r\n");
}
else
{
printf("AT+CIFSR test ok.\r\n");
/* 提取ip地址 */
str = strstr(buffer,"STAIP");
str = str + 7;
sscanf(str,"%d.%d.%d.%d",&ip[0],&ip[1],&ip[2],&ip[3]);
printf("ip: %d.%d.%d.%d\r\n", ip[0], ip[1], ip[2], ip[3] );
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。