这篇,我们讲一讲@命令的实现,我们就以 @cmd 参数1 参数2 参数3 ... 为例,实现在STM32上使用@cmd 进行调试。这样,我们就把基本的调试平台搭建起来了,可以使用串口调试工具进行调试了,类似于linux的控制台。
在《STM32 Uart 实现printf函数》的代码里面,已经配置好串口,并设置好串口DMA接收,且实现在串口打印功能,我们就是《STM32 Uart 实现printf函数》代码上增加对应的代码,实现@调试命令。
要实现@cmd 参数1 参数2 参数3 ... 来调试,首先要获取@cmd ... ... ...字符串,接着要把字符串参数提取出来,最后根据不同的参数,执行不同的代码段。
比如,@cmd 0 0xaa 0xbb,我们一眼就看出 参数1 = 0, 参数2 = 0xaa,参数3 = 0xbb,如何让单片机也一眼看出来呢?一眼看不出来,看多几眼也行!
就让我们开始吧!
1. 先声明一个东东,来存储@cmd 参数1 参数2 参数3 这一串字符串。
volatile static stDebugCmd debugCmd;
这个东东是个结构体。
typedef struct{
uint8_t valid; // 命令是否有效,有效才处理
uint8_t cmd[MAX_DEBBUF]; // 命令存放的地方,这一串就是 @cmd 参数1 参数2 参数3
uint16_t cmdLen; // 命令长度
}stDebugCmd;
2. 接下来,获取命令,将收到的命令,放在 debugCmd.cmd[...] 里面
void SERDEB_PushCmd(uint8_t *cmd, uint16_t len)
{
memcpy((uint8_t *)(debugCmd.cmd), cmd, len); // 要调用这个函数,记得在文件里 #include 喔;
debugCmd.cmd[len] = '\0'; // end of string;
debugCmd.valid = TRUE; // 收到命令了,value置TRUE,表示可以处理,处理完了,会把它置 FALSE。
debugCmd.cmdLen = len;
}
这个函数,在 void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart) 调用,在《STM32 Uart中断接收》《STM32 Uart DMA方式接收数据 》已经讲明白这个回调函数了,就不多讲了。
整个函数如下:中断里收到的数据,要以 @cmd 开头的,才会获取命令字符串。
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
#ifdef RCV_DMAIDLE_PROCESS
uart4RxLength = UART4_BUF_MAX-__HAL_DMA_GET_COUNTER(&hdma_uart4_rx);
// 看这里,收到的东西,要以 @cmd 开头的,我们才认,其它的,都不认。
if(uart4Rx[0]=='@'
&&uart4Rx[1]=='c'
&&uart4Rx[2]=='m'
&&uart4Rx[3]=='d')
{
if(!SERDEB_CmdValid()) // 一条命令处理完成了,才会获取下一次命令;
{
SERDEB_PushCmd(uart4Rx, uart4RxLength);
}
}
__HAL_DMA_DISABLE(&hdma_uart4_rx);
#endif
}
3. 获取字符串放在 debugCmd.cmd[...] 里面,我们就开始处理了。
void SERDEB_Handler(void)
{
if(debugCmd.valid) // 必须命令有效,才会处理
{
//printf("CMD(%d): %s", debugCmd.cmdLen, debugCmd.cmd);
if(GetDebugCmd((uint8_t *)(debugCmd.cmd), debugCmd.cmdLen))
DebugCmdProceed();
debugCmd.valid = FALSE;
}
}
这个函数呢,在main的循环while(1)里面调用。
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
USR_LedHandler();
SERDEB_Handler();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
整个过程呢,就是:1.收到命令 2.获取命令置于debugCmd.cmd[...] 里面,并置 debugCmd.valid为TRUE; 3.在main主循环while(1)里面处理命令。
获取命令置于debugCmd.cmd[...] 里面,并置 debugCmd.valid为TRUE,这个动作是在中断服务例程回调函数里做的;
处理命令是在 main 主循环里面做的;
为什么要这么做呢?直接全部在中断服务例程里面做完不就行了?
记住中断服务例程设计原则其中之一:请不要在中断服务例程里执行比较耗费时间的事,比如,printf,延时等需要大量时间的事。
所以,我们在中断里面,只是置一些标志位,而处理,则是放在主循环里面。
接下来,说一下,怎么处理接收到的 @cmd 参数1 参数2 参数3 的。
先去虾头,再去虾线,啊,不!先去命令头"@cmd ",接下来取参数,将取得的参数存入debArray[]里面,debArray[0]是第一个参数,debugArray[1]是第二个参数。
static uint8_t GetDebugCmd(uint8_t *cmd, uint16_t len)
{
uint8_t *pCmd;
uint8_t index;
uint8_t u8Temp = 0;
pCmd = cmd;
if(cmd[0]!='@'
&&cmd[1] != 'c'
&&cmd[2] != 'm'
&&cmd[3] != 'd'
&&cmd[4] != ' ')
{
printf("GetDebugCmd: error cmd format. It must be @cmd n1 n2 n3... \r\n");
return FALSE;
}
pCmd += 5; // the first num // 去除头部@cmd ,从第6个字节起,是属于参数的;
index = 0; // index of debArray, ==0 the first num ==1 the second num ==2... ...
while(1)
{
// 命令串结束
if(*pCmd=='\r'
||*pCmd=='\n'
||*pCmd=='\0'
||len==0)
{
break;
}
// 将字符串转成数字
u8Temp = atoi(pCmd, (uint16_t *)(debArray+index));
pCmd+=u8Temp; len-=u8Temp;
pCmd+=1; len-=1;
index++;
}
return TRUE;
}
其中,atoi()将字符串转化成数字,存入debArray[]中。
atoi的实现思路,主要是检测字符串。
若有0x或者0X时,以16进制数处理,检测0~9,a~f,A~F,将其变为数字。
若无,则以10进制数处理,检测0~9,将其变为数字。
遇到其它的,便结束,将转换的数字,放入 *num 这个地址内。
static uint8_t atoi(uint8_t *str, uint16_t *num)
{
#define MAX_DIG 10
uint8_t dig = 0, cnt;
uint8_t numTmp[MAX_DIG];
memset((uint8_t *)numTmp, ' ', MAX_DIG);
*num = 0;
if(*str=='0'&&(*(str+1)=='x'||*(str+1)=='X'))
{
str+=2;
do
{
if(*str>='0'&&*str<='9')
{
numTmp[dig] = *str - '0';
}
else if((*str>='a'&&*str<='f'))
{
numTmp[dig] = *str - 'a' + 10;
}
else if((*str>='A'&&*str<='F'))
{
numTmp[dig] = *str - 'A' + 10;
}
str++; dig++;
if(dig>MAX_DIG-2)
{
break;
}
}
while(
(*str>='0'&&*str<='9')
||(*str>='a'&&*str<='f')
||(*str>='A'&&*str<='F')
);
for(cnt=0; cntMAX_DIG-1)
break;
}while(*str>='0'&&*str<='9');
for(cnt=0; cnt
其中,power是平方,power(10, 2),就是10的2次方。power()函数实现如下。
static uint32_t power(uint8_t base, uint8_t exponent)
{
uint32_t value;
uint8_t cnt;
if(exponent==0)
return 1;
value = 1;
for(cnt=0; cnt
将字符串转化成数字参数,接下来,就是处理数字参数了。
想怎么处理就处理,这里,第一个参数放在debArray[0]里,就当作一个命令吧,如果是0,就执行DEBCMD_TEST,如果是1,就执行DEBCMD_EEPROM,如果是2,... 3 ... 4 ...自已实现吧。
enum
{
DEBCMD_TEST = 0,
DEBCMD_EEPROM,
DEBCMD_FLASH,
DEBCMD_RESVERT1,
DEBCMD_RESVERT2,
DEBCMD_RESVERT3,
DEBCMD_MAX,
};
static void DebugCmdProceed(void)
{
switch(debArray[0])
{
case DEBCMD_TEST:
printf("DEBCMD_TEST: Parm1: %d / parm2: %d \r\n", debArray[1], debArray[2]);
break;
case DEBCMD_EEPROM:
printf("DEBCMD_EEPROM: Parm1: %d / parm2: %d / parm3: 0x%x. \r\n", debArray[1], debArray[2], debArray[3]);
break;
}
}
好咯,基本就这样了,编译,烧录,运行,测试。
我们输入两条命令:@cmd 0 100 0xFF 和 @cmd 1 128 256 512,看一下运行结果。
@cmd 0 100 0xFF
@cmd 1 128 256 512
至此,我们基本的调试平台搭建好了,可以使用串口调试工具,输入不同的命令,调试不同的器件,也可以输出打印信息。下一篇,我们来讲一讲某一种芯片的通信协议。
整个工程及代码呢,请上百度网盘上下载:
链接:https://pan.baidu.com/s/19usUcgZPX8cCRTKt_NPcfg
密码:07on
文件夹:\Stm32CubeMx\Code\[email protected]
代码还可以优化优化,比如,加一些判断条件,字符串溢出怎么处理,参数buffer大于128怎么处理,有兴趣的同志可以优化优化。
上一篇:《STM32 Uart 实现printf函数》
下一篇:《STM32 基本定时器》
回目录:《目录》