- 第一步,通过串口与PC端建立通信
- 第二步,根据PC端发来的AT指令,MCU执行相应代码
主要是解析PC端发来的字符串,也就是获取字符串、处理字符串、以及分析字符串。
1. 串口通信
用到的是DMA串口通信,收发字符串数据时,无需占用CPU资源。
首先在cubeMax配置好串口
-
配置波特率、异步模式等,一般修改波特率即可
-
添加DMA
-
打开DMA中断
配置完成后,点击生成代码后,需要在两个位置添加代码。
- 位置一:串口的初始化函数内
extern byte_t pcUartBufDMA[PC_UART_BUFFER_MAX];//使能串口接收DMAHAL_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. 指令解析
- 首先做一些类型定义
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指令表
#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
,首先将指令字符转成大写,而后判断指令类型,根据不同类型给函数指针传入相应的参数。
//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;
}
- 指令解析函数中调用的三个函数
//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指令表中,回调函数的实现了
//指令 设备重启
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;
}