在嵌入式开发中,与通信模块(如蓝牙、ESP8266、4G模块、NB-IoT模块等)交互通常采用AT指令集。为了提高代码的可维护性和复用性,我们需要构建一个既能处理单条指令,又能方便地进行批量指令发送的框架。本方案采用“结构体数组定义+通用发送函数+中断接收(也可以用轮询)”的模式,实现高效、稳定的AT指令交互。

为了实现批量处理和统一管理,我们首先定义AT指令的存储结构和接收缓冲区(串口中断的接收缓冲区)。使用结构体可以将指令字符串、期望的响应关键字以及超时时间封装在一起,便于索引。

  • 指令信息结构体:用于存储AT指令表。包含指令本身(如AT\r\n)、预期的成功响应(如OK)以及建议的等待时间。
  • 接收缓冲区结构体:用于在中断中暂存串口接收到的数据。采用双缓冲机制,Receive_Buffer用于中断实时接收,AT_Buffer用于主程序处理,防止数据竞争。
  • 指令枚举:定义指令的索引,方便在代码中通过名称调用,避免硬编码数字。
/*指令结构体*/
typedef struct
{
   const  char *cmd;            //AT指令
   const  char *response;       //AT指令的响应
   int delay_ms;                //AT指令的等待时间
} at_cmd_info;


/*指令集*/
const at_cmd_info AT_CMD[]={
    "AT\r\n",           "OK\r\n",       1000,
    "AT+RESET\r\n",     "OK\r\n",       1000,
    "AT+MAX\r\n",       "OK\r\n",       1000
};


/*指令枚举*/
typedef enum __enumAT_CMD
{
    AT_CMD_AT=0,
    AT_CMD_RESET,
    AT_CMD_MAX
} enumAT_CMD;

/*  接收返回的数据(DMA+中断/轮询/中断),并比对是否是自己想要的返回值*/
typedef struct
{
    char buffer[100];   //接收缓冲区
    int length;         //接收数据长度
    int rev_flag;       //接收完成标志
} reve_info;
reve_info Receive_Buffer;   //全局变量,接收数据的缓冲区,示例,明白这是串口中断接收缓冲区就可以
reve_info AT_Buffer;        //全局变量,如果需要进一步对返回的指令进行分析,将串口缓冲区的数据复制到AT_Buffer
通用发送与接收函数

这是整个框架的核心。该函数负责清空旧数据、发送新指令、并在超时范围内轮询检查接收缓冲区。一旦在缓冲区中发现预期的响应字符串,即判定为成功,并将数据拷贝至处理缓冲区。

关键设计点

  • 防干扰:函数入口处必须使用memset清空接收缓冲区,防止上一条指令的残留数据导致误判。
  • 安全性:数据拷贝时使用strncpy而非strcpy,并手动添加\0,防止缓冲区溢出。
  • 时间片:循环中使用HAL_Delay(1),既保证了超时计时的准确性,又让出了CPU时间片给中断服务程序去搬运数据。
    /* 发送AT指令并接收返回数据 */
    int AT_Send_Reve_Delay(uint8_t *cmd ,uint8_t *regsponse, uint32_t delay_ms)
    {
        memset(Receive_Buffer.buffer, 0, sizeof(Receive_Buffer.buffer));
        Receive_Buffer.length = 0;
        /*发送函数:串口,AT指令,指令长度,超时时间(和AT指令的等待时间不同),根据发送函数不同,可以删除串口和超时时间,只保留发送指令和指令长度 */
        HAL_UART_Transmit(&huart2, cmd, strlen((const char*)cmd), 100);
        int timeout = delay_ms; //根据实际情况调整超时时间的单位
        while (timeout--)
        {
            if (strstr(Receive_Buffer.buffer, regsponse) != NULL)
            {   //将接收到的数据复制到AT_Buffer中,如果你需要对数据进行进一步处理,可以在这里复制接收缓冲区数据,如果不用进一步处理,可以删除此段代码,直接返回1
                strncpy(AT_Buffer.buffer, Receive_Buffer.buffer,sizeof(AT_Buffer.buffer)-1); 
                AT_Buffer.buffer[sizeof(AT_Buffer.buffer) - 1] = '\0';
                return 1; //接收到正确的响应,退出循环
            }
            HAL_Delay(1); //根据实际情况调整延迟时间的单位1ms
        }
        return 0; //超时,未接收到正确的响应
    }
    处理策略:
    单条处理:适用于需要严格顺序或根据上一条结果决定下一条指令的场景。
    批量循环:适用于初始化配置,通过for循环遍历AT_CMD数组。
    int main (void)
    {
        int ret=AT_Send_Reve_Delay(AT_CMD[AT_CMD_AT].cmd, AT_CMD[AT_CMD_AT].response, AT_CMD[AT_CMD_AT].delay_ms);
        if (ret)//命令成功
        {
            printf("Received response: %s\n", AT_Buffer.buffer);
        }else  //命令失败
        {
            printf("Command failed\n");
        } 
        while(1)
        {
    
        }
    }
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐