灵动微电子 · 2022年08月19日 · 北京市

灵动微课堂 | 基于F0040串口实现AT指令解析

1 AT指令简介

AT命令作为主控芯片与通信模块的协议接口,通常使用串口作为通信协议的传输,因此在通信模块中硬件接口通常为串口,这样简化了主控设备的代码开发。

AT指令通常由前缀、主体、结束符构成,其中前缀为“AT”,主体由命令、参数以及数据组成;结束符一般为“\r\n”。

AT指令的发送内容最多为1056个字符的长度(不包括“AT”,但包括最后的空字符即回车符号)

使用AT指令进行设备的连接通信,AT client与AT server必须共同完成。

即:AT server 必须对接收的AT client的命令进行判断并发送响应给到AT client;AT client 等待响应,并对响应的数据进行解析处理。

当然AT server也可主动发送数据给AT client,AT client对接收的数据进行解析处理。一般是需要用户做出相应操作的情况,例如:WiFi的断开连接等。

因此,AT server发送的数据可以分为两种,一种是响应数据,另一种则是主动发送的数据(URC)。

AT指令集是用于实现设备(AT client)与服务器(AT Server)之前的连接与通信的方式。

image.png
由上图可以看出,AT Client和AT Server既是发送端也是接收端。

AT server需要接收AT Client的请求,对请求进行响应,解析。将响应和解析结果发送给AT client。

2 设计思路

通过串口助手发送AT指令给目标mcu,目标mcu对接收的数据进行解析和超时判断,并响应解析结果,执行对应的响应。

设计思路如图所示:

image.png

3 具体实现

超时设计

通过滴答定时器进行接收和发送的超时判断。

/* Timeout judgment. */
bool is_timeout(uint32_t timeout)
{
    return tick > timeout;
}

/* SysTick ISR entry. */
void SysTick_Handler(void)
{
    tick += 1;
}

解析器设计

判断接收到正确的AT指令是否超时,若超时则返回超时并发送[AT] TIMEOUT给客户端,清除计数值,同时清空将数据接收缓存区。

将目标mcu接收的数据和发送的AT指令进行比较,若匹配则返回匹配成功并发送“[AT] OK”给客户端,若失败则返回错误”[AT] ERROR“给客户端。

uint32_t at_parse(char *cmdstr, uint32_t timeout)
{
    uint32_t ret;
    tick = 0;
    while (! (is_timeout(timeout)))
    {
        if (REC_STA) /*  receive a complete line command. */
        {
            REC_STA = false;
            for (uint32_t i=0; i<strlen(cmdstr); i++)
            {
                tick = 0;
                if (rec_buff[i] == cmdstr[i])
                {
                    ret = AT_RETURN_OK;
                }
                else
                {
                    ret = AT_RETURN_ERROR;
                }
            }
            memset(rec_buff, 0, sizeof(rec_buff)); /* clear receiver buffer. */
            return ret;
        }
    }
    tick =0;
    ret = AT_RETURN_TIMEOUT;
    memset(rec_buff, 0, sizeof(rec_buff)); /* clear receiver buffer. */
    return ret;
}

AT 适配器配置

使用pokt-f0040的默认debug接口,UART1(PB6,和PB7),使用接收中断来接收串口助手发送的数据,具体实现如下:

实例化AT适配器

/* initialize the at adaptter. */
static AT_Adapter_Type at=
{
    .write = uart_putchar,
    .read = uart_getchar,
    .rec_buf = rec_buff,
    .buf_idx = 0u
};

AT接口初始化void app_at_port_init(void)

初始化UART需要配置:时钟频率、波特率、数据长度、停止位、传输模式及是否使用校验。

void app_at_port_init(void)
{
    UART_Init_Type uart_init;

    /* Setup the xfer engine. */
    uart_init.ClockFreqHz   = BOARD_AT_UART_FREQ; /* 48mhz, APB2. */
    uart_init.BaudRate      = BOARD_AT_UART_BAUDRATE;
    uart_init.WordLength    = UART_WordLength_8b;
    uart_init.StopBits      = UART_StopBits_1;
    uart_init.Parity        = UART_Parity_None;
    uart_init.XferMode      = UART_XferMode_RxTx;
    uart_init.HwFlowControl = UART_HwFlowControl_None;
    UART_Init(BOARD_AT_UART_PORT, &uart_init);

    /* Enable RX interrupt. */
    UART_EnableInterrupts(BOARD_AT_UART_PORT, UART_INT_RX_DONE, true);
    NVIC_EnableIRQ(BOARD_AT_UART_IRQn);

    /* Enable UART. */
    UART_Enable(BOARD_AT_UART_PORT, true);

    /* Enable UART. */
    UART_Enable(BOARD_AT_UART_PORT, true);
}

发送函数uart_putchar(uint8_t c)

/* sned data.*/
void uart_putchar(uint8_t c)
{
    while ( 0u == ( UART_STATUS_TX_EMPTY & UART_GetStatus(BOARD_AT_UART_PORT) ) )  /* Waiting tx buffer empty. */
    {}
    UART_PutData(BOARD_AT_UART_PORT, c);
}

接收函数uint8_t uart_getchar(void)

uint8_t uart_getchar(void)
{
    while ( 0u == ( UART_STATUS_RX_DONE & UART_GetStatus(BOARD_AT_UART_PORT) ) )  /* Waiting rx buffer receives a complete byte of data. */
    {}
    return UART_GetStatus(BOARD_AT_UART_PORT);
}

发送字符串函数void uart_putbuffer(uint8_t *str)

/* send string. */
void uart_putbuffer(uint8_t *str)
{
    while ((*str) != '\0')
    {
        uart_putchar(*str);
        str++;
    }
}

中断处理函数

在中断中进行接收数据的处理,判断是否接收到完整的一行命令.当接收到回车换行符时,即表示接收到了一行完整的命令。

/* receiver handler*/
void app_at_port_rx_isr_hook(void)
{
    tick = 0;
    if (   (0u != (UART_INT_RX_DONE & UART_GetEnabledInterrupts(BOARD_AT_UART_PORT)))
    && (0u != (UART_INT_RX_DONE & UART_GetInterruptStatus(BOARD_AT_UART_PORT))) )
    {
        rec_buff[at.buf_idx] = UART_GetData(BOARD_AT_UART_PORT); /* read data to clear rx interrupt bits. */
        uart_putchar(rec_buff[at.buf_idx]);
        if ((rec_buff[at.buf_idx]=='\n' )&& (rec_buff[at.buf_idx-1] == '\r')) /* recieve done.*/
           {
                REC_STA = true;
                at.buf_idx = 0;
           }
        at.buf_idx = (at.buf_idx+1)%AT_CMD_LEN;
    }
}
/* BOARD_AT_UART_IRQHandler ISR entry. */
void BOARD_AT_UART_IRQHandler(void)
{
    app_at_port_rx_isr_hook();
}

main() 函数

main()函数结合上述操作,不断执行用户自定义的任务task()

int main(void)
{
    BOARD_Init();

    while (1)
    {
        task();
    }
}

用户自定义的任务task()

用户设定接收完整的一行AT指令的时间,调用AT指令解析函数,根据响应结果执行自定义任务。

当接收的命令和发送命令匹配时,串口助手显示[AT]READY。

当匹配失败时,串口助手显示[AT]ERROR,小灯长亮。

当指定时间内(本实验设置为5s)没有接收到完整的一行指令时,串口助手显示[AT]TIMEOUT,小灯以1s间隔闪烁。

void task(void)
{
    while(AT_Parse(&at, cmdlib[0], 5000))
    {}
    AT_SendBuf(&at, "\r\n[AT] READY\r\n");
    while(AT_Parse(&at, cmdlib[1], 5000))
    {}
    GPIO_WriteBit(BOARD_LED0_GPIO_PORT, BOARD_LED0_GPIO_PIN, 0u);
}

代码中的“cmdlib”为用户自定义的AT指令库,本此实验中定义的AT指令库为:

/* custom AT command set. */
char *cmdlib[command_len] = {"AT+RST", "AT+LED=1"};

task任务解读:

当通过串口发送“AT+RST\r\n”时,mcu响应指令,并反馈响应结果给串口助手,若接收正确指令则执行预设任务,发送[AT] READY给串口助手。

当通过串口发送“AT+LED=1\r\n”时,mcu响应指令,并反馈响应结果给串口助手,若接收正确指令则执行预设任务,点亮小灯。

4 实验结果

image.png

5 测试环境

•   KEIL 5.37为程序下载调试环境
•   Tera Term作为串口数据的发送和显示的客户端
•   测试板为POKT-F0040
作者:灵动MM32
文章来源:灵动MM32MCU

推荐阅读

更多MM32F5系列资料请关注灵动MM32 MCU专栏。如想进行MM32相关芯片技术交流,请添加极术小姐姐微信(id:aijishu20)加入微信群。
推荐阅读
关注数
6151
内容数
276
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息