STM32
直播中

张昂笙

7年用户 971经验值
私信 关注
[问答]

怎样去使用printf函数往控制台打印信息呢

怎样去使用printf函数往控制台打印信息呢?有哪几种方法呢?

回帖(1)

赵阳

2021-11-30 11:13:33
        在《STM32 Uart 接收变长数据》的结尾,我们觉得每次使用这样的形式来输出信息感觉好麻烦,也不方便调试。

    HAL_UART_Transmit(huart, &RxLenHi, 1, 1000); // 发送长度高位

    HAL_UART_Transmit(huart, &RxLenlo, 1, 1000); // 发送长度低位

    HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000); // 发送接收到的数据
       所以我们就想想办法,看可不可以像标准C语言一样,使用printf往控制台打印信息。
          在这一篇,我们提供了三种方法。
          方法1:简单粗暴,直接写三个函数,一个负责输出字符串,名myPrintfChar;一个负责输出十进制数,名myPrintfDec;一个输出十六进制数,名myPrintfHex;

void myPrintfChar(uint8_t* buf)
{
        uint8_t * pDebBuf;
        uint16_t debugLen = 0;

        pDebBuf = debugBuffer;

        if(buf == NULL)
                return;

        // Put string to debugBuffer;
        while(1)
        {
                if(*buf==''||debugLen>=MAX_DEBBUF)
                {
                        break;
                }

                *pDebBuf = *buf;
                pDebBuf++;
                buf++;
                debugLen++;
        }

        // Output the debugBuffer to uart;
        if(debugLen)
        {
                HAL_UART_Transmit(printfUart, debugBuffer, debugLen, 1000);
        }
}

void myPrintfHex(uint32_t num)
{
        uint16_t debugLen = 0;

        // Translate num to DEC string;
        debugLen = itoa(num, debugBuffer, 16);

        // Output the string to uart
        if(debugLen)
        {
                HAL_UART_Transmit(printfUart, debugBuffer, debugLen, 1000);
        }
}

void myPrintfDec(uint32_t num)
{
        uint16_t debugLen = 0;

        // Translate num to HEX string, ignore 0x;
        debugLen = itoa(num, debugBuffer, 10);

        // Output the string to uart
        if(debugLen)
        {
                HAL_UART_Transmit(printfUart, debugBuffer, debugLen, 1000);
        }
}      
里面有个函数,itoa,作用是把数字转成字符串,参数有三个,1.数字;2.字符串地址;3.控制,10就是十进制,16就是16进制,返回值是字符串的长度。函数的实现如下:

static uint8_t itoa(uint32_t num, uint8_t *str, uint8_t format)
{
        uint8_t i,j;
        uint8_t strLen = 0;

        switch (format){
                case 10:{
                        i = 0;
                        do{
                                str = num%10+'0';
                                i++;
                                num/=10;
                        }while(num>0);

                        strLen = i;

                        // Exchange Str abcdef... to ...fedcba;
                        if(strLen>=2){
                                j = 0; i--;
                                do{
                                        str = str^str[j];
                                        str[j] = str^str[j];
                                        str = str^str[j];
                                        j++; i--;
                                }while(i/2);
                        }
                }
                        break;

                case 16:{
                        i = 0;
                        do{
                                str =  ((num&0xf)>=10)?((num&0xf)%10+'A'):((num&0xf)+'0');
                                i++;
                                num>>=4;
                        }while(num>0);
                       
                        strLen = i;

                        // Exchange Str abcdef... to ...fedcba;
                        if(strLen>=2){
                                j = 0; i--;
                                do{
                                        str = str^str[j];
                                        str[j] = str^str[j];
                                        str = str^str[j];
                                        j++; i--;
                                }while(i/2);
                        }
                }
                        break;

                default:
                        break;
        }

        str[strLen] = '';

        return strLen;
}
      
          方法2:写一个不定参数的函数,类似于printf(char *,...),名为 myPrintf(char* str, ...)。
          如何实现不定参数的函数呢?
          首先,在#include 里面,记得有两个非常重要的宏定义va_start和va_arg,这是实现不定参数函数的关键。
          va_start(ap, fmt):作用是指向参数中第一个可变参数,va_arg(ap, xxx):作用是返回这个参数并指向下一个可变参数。
          myPrintf(char* str, ...)函数的实现如下:

void myPrintf(char* str, ...)
{
        va_list ap;
        uint8_t * pDebBuf;
        uint16_t debugLen = 0;
        uint16_t valLen = 0;

        char *p, *sval;
        uint32_t ival;

        pDebBuf = debugBuffer;

        va_start(ap, str);
        for(p=str; *p; p++)
        {
                if(*p!='%'){
                        *pDebBuf = *p; pDebBuf++;
                        debugLen++;
                        continue;
                }

                switch(*++p){
                        case 'd':{
                                ival = va_arg(ap, uint32_t);
                                valLen = itoa(ival, pDebBuf, 10);
                                pDebBuf += valLen; debugLen += valLen;
                        }
                                break;

                        case 'x':{
                                ival = va_arg(ap, uint32_t);
                                valLen = itoa(ival, pDebBuf, 16);
                                pDebBuf += valLen; debugLen += valLen;
                        }
                                break;

                        case 's':{
                                for(sval = va_arg(ap, char *); *sval; sval++){
                                        *pDebBuf = *sval;
                                        pDebBuf++; debugLen++;
                                }
                        }
                                break;

                        default:{
                                *pDebBuf = *p; pDebBuf++;
                                debugLen++;       
                        }
                                break;
                }
        }
        va_end(ap);

        if(debugLen)
        {
                HAL_UART_Transmit(printfUart, debugBuffer, debugLen, 1000);
        }
       
}      
          方法3:printf重定向,只需要重写fputc便可,简单实用,以后,我们都用这种方式吧,对了,记得检查一下微库有没有钩上,请看图:
  
  

  

          重写的fputc函数实现如下:

int fputc(int ch, FILE *f)
{
        HAL_UART_Transmit(printfUart, (uint8_t *)&ch, 1, 1000);
        return ch;
}      
有没有发现,不管哪种方式,调用的HAL_UART_Transmit(printfUart, ..........)函数,都带有printfUart这个参数,记得,把这个参数换成你板子上对应的Uart:
  UART_HandleTypeDef *printfUart = &huart4;   
          最后,我们测试一下这三种方法,如《STM32 Uart 接收不定长数据》里所写,在idle中断的回调函数里打印字符串,以及收到的字符长度十进制显示和十六进制显示,测试代码如下:

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);

        myPrintfChar("Test myPrintfChar/myPrintfDec/myPrintfHexrn");
        myPrintfChar("Receive ");
        myPrintfDec(uart4RxLength);
        myPrintfChar("(0x");
        myPrintfHex(uart4RxLength);
        myPrintfChar(") Bytes Wowrn");
       
        myPrintfChar("Test myPrintfrn");
        myPrintf("Receive %d(0x%x) %s Wow Wow. rn", uart4RxLength, uart4RxLength, "Bytes");

        printf("Test printfrn");
        printf("Receive %d(0x%x) %s Wow Wow Wow. rn", uart4RxLength, uart4RxLength, "Bytes");
       
        __HAL_DMA_DISABLE(&hdma_uart4_rx);
#endif
}      
其实,我们不建议在中断里面打印信息,在中断里面,最好只接收数据,做一些简单的判断,尽量让中断程序简短,执行时间短,一般情况下,我们会把处理数据放在主循环里面,下一篇的《STM32 Uart @调试命令的实现》,就把处理数据放在主循环里面处理,以后,我们都这么做吧。
          老套路,编译,烧录,重启开发板,打开串口调试工具,发送一串数据看一看:
  
  

  

          我们已经实现了打印,实现在从STM32串口发送信息到PC机的功能,但要调试还是不够的,我们还要从PC往STM32传送各种命令,各种参数啊,咋办?请看下一篇《STM32 Uart @调试命令的实现》。
   
     
举报

更多回帖

发帖
×
20
完善资料,
赚取积分