- 第一步,通过串口与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;
}