STM32 Uart @调试命令的实现

      这篇,我们讲一讲@命令的实现,我们就以 @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 <string.h> 喔;
    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; cnt<dig; cnt++)
        {
            *num += (numTmp[cnt]*power(16, (dig-cnt-1)));
        }

        dig += 2;
    }
    else
    {
        do
        {
            numTmp[dig] = *str - '0';
            str++; dig++;        

            if(dig>MAX_DIG-1)
                break;
        }while(*str>='0'&&*str<='9');

        for(cnt=0; cnt<dig; cnt++)
        {
            *num += (numTmp[cnt]*power(10, (dig-cnt-1)));
        }
    }

    return dig;
    
#undef MAX_DIG
}

        其中,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<exponent; cnt++)
    {
        value *= base;
    }

    return value;
}

        将字符串转化成数字参数,接下来,就是处理数字参数了。

        想怎么处理就处理,这里,第一个参数放在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函数

下一篇:还没想好写啥?I2C?SPI?

回目录:《目录

猜你喜欢

转载自blog.csdn.net/ForeverIT/article/details/82533762