赞
踩
前面文章已经介绍了AT命令的组成,以及通讯过程。《AT命令使用和简单介绍》。
现在写代码实现AT命令的发送,以及响应数据、URC数据的解析。
代码框架主要是有两个线程。一个线程负责命令发送,并阻塞等待命令响应结果和响应数据;还有一个是数据解析线程,主要是解析AT命令的响应数据已经URC数据,解析的结果和数据会传递给命令发送线程,然后唤醒命令发送线程。大致流程如下:
数据解析线程会调用一个读取一行数据的函数,这个函数会去读取串口的数据,如果读取不到串口数据,那么这个线程就会一直阻塞,直到等到有串口数据的时候,才会发送信号量唤醒这个线程。
然后下面的代码把接收到的数据进行解析。解析完成之后,会发送信号量唤醒AT命令的发送线程。
主要核心代码就是这两个线程的代码,以及所调用的函数。
void at_recv_parser(void *parameter) { char recv_line_buff[128] = {0}; int read_len = 0; at_resp_t tmp_resp = {{0}, 0, 0}; const at_urc_t *urc = NULL; while (1) { read_len = at_recv_readln(recv_line_buff, sizeof(recv_line_buff)); if ( read_len > 0) { if ((urc = at_get_urc(recv_line_buff, read_len)) != NULL) { /* URC数据处理 */ if (urc->func != NULL) { urc->func(recv_line_buff, read_len); } } else { /* 命令响应数据处理 */ if (tmp_resp.buf_len < AT_MAX_RESP_LEN) { recv_line_buff[++read_len] = '\0'; memcpy(tmp_resp.buf + tmp_resp.buf_len, recv_line_buff, read_len); tmp_resp.buf_len += read_len; tmp_resp.line_counts++; } else { at_set_resp_status(AT_RESP_BUFF_FULL); } if (strstr(recv_line_buff, "OK")) { memset(&gs_resp, 0, sizeof(gs_resp)); memcpy(&gs_resp, &tmp_resp, sizeof(gs_resp)); at_set_resp_status(AT_RESP_OK); } else if (strstr(recv_line_buff, "ERROR")) { memset(&gs_resp, 0, sizeof(gs_resp)); memcpy(&gs_resp, &tmp_resp, sizeof(gs_resp)); at_set_resp_status(AT_RESP_ERROR); } else { continue; } memset(&tmp_resp, 0, sizeof(tmp_resp)); xSemaphoreGive(at_resp_sem); } } } }
1、在这个线程函数中,有一个 at_recv_readln
函数,会一直去读取串口环形缓冲区的数据,如果没有数据那么就会阻塞等待,直到串口接收数据中断释放的信号量去唤醒它。这个函数实现如下,这个函数读取到一行数据或者有匹配的URC数据,那么就返回给数据解析线程。
static int at_recv_readln(char *buff, unsigned int buff_len) { char ch = 0, last_ch = 0; unsigned int read_len = 0; memset(buff, 0, buff_len); while (1) { at_client_getchar(&ch, portMAX_DELAY); if (read_len < buff_len) { buff[read_len++] = ch; } else {/* buff溢出错误 */ memset(buff, 0x00, buff_len); return -1; } /* 读到一行数据,或者接收到URC数据 */ if ((ch == '\n' && last_ch == '\r') || at_get_urc(buff, read_len)) { break; } last_ch = ch; /* 暂存前一个字符 */ } return read_len; }
2、数据解析线程主要分为两个部分的情况进行解析。一个是URC数据解析,一个是命令响应数据的解析。
其中URC数据处理,我定义了一个URC的数据结构体:
typedef struct _at_urc_t
{
const char *cmd_prefix; /* 前缀 */
const char *cmd_suffix; /* 后缀 */
void (*func)(const char *data, unsigned int size); /* 对应执行函数 */
} at_urc_t;
然后定义一个URC数据表格:
static at_urc_t esp8266_urc_table[] =
{
{"SEND OK", "\r\n", urc_send_func},
{"SEND FAIL", "\r\n", urc_send_func},
{"+IPD", ":", urc_recv_func},
};
我这里暂时只实现数据收发的URC数据的处理函数。在这个 at_recv_parser
函数中,会调用 at_get_urc
函数去匹配判断是否有接收到和我们表格中定义的URC数据,如果匹配上了,就会调用对应的函数去处理,处理完成之后,就会释放信号量唤醒AT命令发送线程。
at_get_urc
函数实现如下:
static const at_urc_t *at_get_urc(const char *recv_line_buf, unsigned int recv_line_len) { unsigned char prefix_len = 0, suffix_len = 0; for (int i = 0; i < sizeof(esp8266_urc_table) / sizeof(esp8266_urc_table[0]); i++) { prefix_len = strlen(esp8266_urc_table[i].cmd_prefix); suffix_len = strlen(esp8266_urc_table[i].cmd_suffix); if ((prefix_len ? !strncmp(recv_line_buf, esp8266_urc_table[i].cmd_prefix, prefix_len) : 1) && (suffix_len ? !strncmp(recv_line_buf + recv_line_len - suffix_len, esp8266_urc_table[i].cmd_suffix, suffix_len) : 1)) { return &esp8266_urc_table[i]; } } return NULL; }
3、命令响应数据解析
对于命令响应数据,因为都会有响应的状态回复,要么回复 “OK” ,要么是 “ERROR” ,所以我们只需要判断匹配这两个字符串即可。判断完成之后,把响应的数据和状态通过全局变量传递给AT命令发送线程,然后再发送信号量去唤醒这个线程。
主要是实现了 at_exce_cmd
这个发送AT命令的函数,并返回命令响应的状态和响应数据。
/** * 发送命令给AT服务器,和等待回应 * * @param resp : 输出AT服务器回应的数据 * * @return 0 : success * -1 : response status error * -2 : wait timeout */ int at_exce_cmd(const char *cmd, at_resp_t *resp, unsigned int timeout) { /* 发送命令给AT服务器 */ at_client_sendcmd(cmd); /* 获取解析AT服务器回应数据的任务释放的信号量 */ if (xSemaphoreTake(at_resp_sem, pdMS_TO_TICKS(timeout)) == pdTRUE) { if (resp != NULL) { memcpy(resp, &gs_resp, sizeof(at_resp_t)); } return at_get_resp_status(); } else { return AT_RESP_TIMEOUT; } }
其中调用的 at_client_sendcmd
函数,就是通过串口发送数据给AT Server(即ESP8266模块)的。发送命令之后,就阻塞等待数据解析线程的回应,如果解析成功或者超时,都会唤醒这个线程。
以上就是主要代码的介绍,完整的工程代码可以去下面的链接下载。
https://download.csdn.net/download/luobeihai/86403607?spm=1001.2014.3001.5503
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。