MCU中如何利用串口通信,增加AT指令框架

  • 第一步,通过串口与PC端建立通信
  • 第二步,根据PC端发来的AT指令,MCU执行相应代码
    主要是解析PC端发来的字符串,也就是获取字符串、处理字符串、以及分析字符串。

1. 串口通信

用到的是DMA串口通信,收发字符串数据时,无需占用CPU资源。

首先在cubeMax配置好串口

  • 配置波特率、异步模式等,一般修改波特率即可

  • 添加DMA

  • 打开DMA中断

配置完成后,点击生成代码后,需要在两个位置添加代码。

  • 位置一:串口的初始化函数内
c 复制代码
    extern byte_t pcUartBufDMA[PC_UART_BUFFER_MAX];
	
	//使能串口接收DMA
    HAL_UART_Receive_DMA(&huart2, pcUartBufDMA, PC_UART_BUFFER_MAX);
    
    //使能IDLE中断
    __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
    
    //清除空闲标志位,防止中断误入
    __HAL_UART_CLEAR_IDLEFLAG(&huart2);
  • 位置二:串口的中断处理函数中

    经过以上的配置,此时MCU应该可以接收到PC发送的字符串,但PC端不会收到回复,因为MCU只接收了数据,没有处理和分析。

2. 指令解析

  • 首先做一些类型定义
c 复制代码
enum
{
    AT_ERR = -1,                //指令异常
    AT_OK = 0                   //指令正常
};

typedef enum
{
    QUERY_CMD   = 0x01,          //查询命令
    SET_CMD     = 0x02,          //设置命令
    EXECUTE_CMD = 0x03,          //执行命令
} cmd_t;

typedef int (*deal_func)(cmd_t opt, int argc, char *argv[]);    //回调函数

typedef struct
{
    char *atCmdStr;                 //AT指令
    deal_func atFunc;               //AT指令执行函数
} pcAtCmd_t;
  • 可扩展的AT指令表
c 复制代码
#define AT_TABLE_SIZE   (sizeof(pcAtTable) / sizeof(pcAtCmd_t))
#define ARGC_LIMIT      0x10    /* 参数限制个数 */

const pcAtCmd_t pcAtTable[] =
{
    {"AT+Z",                pcAtDevReset},
    {"AT+LOG",         		pcAtLogSw},
    ...
};
  • 指令解析函数
    传入指令字符串pdata和字符串长度size,首先将指令字符转成大写,而后判断指令类型,根据不同类型给函数指针传入相应的参数。
c 复制代码
//AT指令解析
int pcAtCmdParse(uint8_t * pdata, uint16_t size)
{
    int ret  = AT_ERR;
    uint16_t offset = 0;
    int index = 0;
    
    int argc = ARGC_LIMIT;
    char *argv[ARGC_LIMIT] = { (char *)0 };
    char *ptr = NULL;

    atStringToUpper((char *)pdata);

    if (strstr((const char *)pdata, "AT") == NULL)
    {
        ret  = AT_ERR;
        goto at_end;
    }

	//AT\r 测试指令回复
    if ((pdata[0] == 'A') && (pdata[1] == 'T') && (pdata[2] == '\r'))
    {
        ret  = AT_OK;
        goto at_end;
    }

    /* 查找匹配的执行指令 */
    ret = pcAtCmdSearch(pdata, size);
    if (AT_ERR == ret)
    {
        goto at_end;
    }

    index = ret;
    /* 定位到指令后内容,即W后面的字符
		AT+SW=1
		     |
	 */
    ptr = strstr((const char *)pdata, pcAtTable[index].atCmdStr) + strlen(pcAtTable[index].atCmdStr);

    /* AT+SW?\r 是查询命令 */
    if ((ptr[0] == '?') && (ptr[1] == '\r'))
    {
        if (NULL != pcAtTable[index].atFunc)
        {
            ret = pcAtTable[index].atFunc(QUERY_CMD, 0, NULL);
        }
    }
     /* AT+SW=1 是设置命令 */
    else if (ptr[0] == '=')    
    {
        //移动到'='后面,计算剩余字符串长度,并分割字符串
        ptr += 1;
        offset = ptr - (char *)pdata;
        argc = atStringSplit((char*)ptr, size - offset, ',', argv, argc);

        if (NULL != pcAtTable[index].atFunc)
        {
            ret = pcAtTable[index].atFunc(SET_CMD, argc, argv);
        }
    }
    /* AT+SW 是执行命令 */
    else if (ptr[0] == '\r')
    {
        if (NULL != pcAtTable[index].atFunc)
        {
            ret = pcAtTable[index].atFunc(EXECUTE_CMD, 0, NULL);
        }
    }
    else
    {
        ret = AT_ERR;
    }

at_end:
    if (AT_ERR == ret)
    {
        pcAtRespondError();
    }
    else
    {
        pcAtRespondOk();
    }
    return ret;
}
  • 指令解析函数中调用的三个函数
c 复制代码
//at小写转大写
static void atStringToUpper(char *strp)
{
    while ( *strp != '\0')
    {
        if (*strp >= 'a' && *strp <= 'z')
        {
            *strp -= ('a' - 'A');
        }
        strp++;
    }
}

//查找指令表中对应的指令
static int16_t pcAtCmdSearch(uint8_t *pStr, int16_t len)
{
    int ret = AT_ERR;
    int16_t index = 0;
    int16_t n = 0;

    for (index = 0; index < AT_TABLE_SIZE; index++)
    {
        n = strlen(pcAtTable[index].atCmdStr);
        if (!strncmp((char *)pStr, pcAtTable[index].atCmdStr, n))
        {
            ret = index;
            break;
        }
    }
    return ret;
}

//at分割字符串  0,1,2
static int atStringSplit(char *strp, uint32_t strsize, char ch, char *argv[], uint32_t argcMax )
{
    int ch_index = 0;
    int argc_index = 0;
    uint8_t splitflag = 0;

    if ((!strsize) || (!argcMax))
    {
        return 0;
    }
    //取第一个数据
    argv[argc_index++] = &strp[ch_index];

    for (ch_index = 0; ch_index < strsize; ch_index++)
    {
        if (strp[ch_index] == '\r')
        {
            break;
        }
        else if (strp[ch_index] == ch)
        {
            strp[ch_index] = '\0';
            splitflag = 1;
        }
        else if (splitflag == 1)
        {
            splitflag = 0;
            argv[argc_index++] = &strp[ch_index];
            if (argc_index >= argcMax)
            {
                break;
            }
        }
        else
        {
            splitflag = 0;
        }
    }
    return argc_index;
}
  • 还有就是AT指令表中,回调函数的实现了
c 复制代码
//指令 设备重启
int pcAtDevReset(cmd_t opt, int argc, char *argv[])
{
    int ret  = AT_ERR;
    if (QUERY_CMD == opt)       //查询变量
    {

    }
    else if (SET_CMD == opt)    //设置变量
    {

    }
    else if (EXECUTE_CMD == opt) //执行功能
    {
        mcuSoftReset();
        ret = AT_OK;
    }
    return ret;
}
相关推荐
yutian06066 小时前
Keil MDK下载程序后MCU自动重启设置
单片机·嵌入式硬件·keil
析木不会编程9 小时前
【小白51单片机专用教程】protues仿真独立按键控制LED
单片机·嵌入式硬件·51单片机
枯无穷肉13 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
不过四级不改名67713 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式大圣14 小时前
单片机UDP数据透传
单片机·嵌入式硬件·udp
云山工作室14 小时前
基于单片机的视力保护及身姿矫正器设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
嵌入式-老费14 小时前
基于海思soc的智能产品开发(mcu读保护的设置)
单片机·嵌入式硬件
qq_3975623116 小时前
MPU6050 , 设置内部低通滤波器,对于输出数据的影响。(简单实验)
单片机
liyinuo201716 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范
艺术家天选16 小时前
STM32点亮LED灯
stm32·单片机·嵌入式硬件