STM32
直播中

王浩

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

有哪几种办法可实现单片机像在pc终端一样打印log呢

有哪几种办法可实现单片机像在pc终端一样打印log呢?

STM32实现printf打印log的办法有哪些呢?

回帖(1)

王蓓

2021-12-1 14:24:01
在stm32单片机下,改一些bug的时候,光靠调试还不行,有时候需要打印log来查看某些变量在一段时间内的变化趋势,但是板卡又没有接串口,没办法重定向到串口打印,上网查资料研究了一下,发现以下几种办法可实现单片机像在pc终端一样打印log:
方法1:使用串口重定向,将printf打印的信息输出到串口,再将串口连接pc端串口接收终端,在终端上查看log.主要2个步骤:修改printf函数底层调用到的fputc函数和避免使用semihos TIng(半主机模式)。
a.如果使用mdk作为编译工具,在Target选项框里选Use MicroLib 选项,即为使用微库模式,不会使用半主机模式;
b.工程中添加串口配置代码,方便后续使用串口发送数据



void UART1_Configuration(void)
{
        USART_InitTypeDef USART_InitStructure;
        USART_ClockInitTypeDef  USART_ClockInitStructure;
       
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 |RCC_APB2Periph_USART1, ENABLE  );
       
        USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;   // 时钟低电平活动
        USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;     // 时钟低电平
        USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;  // 时钟第二个边沿进行数据捕获
        USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;  // 最后一位数据的时钟脉冲不从SCLK输出
        /* Configure the USART1 synchronous paramters */
        USART_ClockInit(USART1, &USART_ClockInitStructure);  // 时钟参数初始化设置

        USART_InitStructure.USART_BaudRate = 115200;    // 波特率为:115200
        USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据
        USART_InitStructure.USART_StopBits = USART_StopBits_1;  // 在帧结尾传输1个停止位
        USART_InitStructure.USART_Parity = USART_Parity_No ;   // 奇偶失能
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件流控制失能
       
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 发送使能+接收使能
        /* Configure USART1 basic and asynchronous paramters */
        USART_Init(USART1, &USART_InitStructure);
               
          /* Enable USART1 */
        USART_ClearFlag(USART1, USART_IT_RXNE); //清中断,以免一启用中断后立即产生中断
        USART_ITConfig(USART1,USART_IT_RXNE, ENABLE); //使能USART1中断源
        USART_Cmd(USART1, ENABLE); //USART1总开关:开启
}
c.添加串口中断程序


#define TxBufferSize   (countof(TxBuffer) - 1)
#define RxBufferSize   0x20

/* Private macro -------------------------------------------------------------*/
#define countof(a)   (sizeof(a) / sizeof(*(a)))

/* Private variables ---------------------------------------------------------*/
u8 TxBuffer[] = "nrUSART Hyperterminal Interrupts Example: USART-Hyperterminal
communication using Interruptnr";
u8 RxBuffer[RxBufferSize];
u8 NbrOfDataToTransfer = TxBufferSize;
u8 NbrOfDataToRead = RxBufferSize;
u8 TxCounter = 0;
u16 RxCounter = 0;


/*******************************************************************************
* Function Name  : USART1_IRQHandler
* Description    : This function handles USART1 global interrupt request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void USART1_IRQHandler(void)
{
  if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
  {
    /* Read one byte from the receive data register */
    RxBuffer[RxCounter++] = (USART_ReceiveData(USART1) & 0x7F);
   if(RxCounter == NbrOfDataToRead)
    {
      /* Disable the USART Receive interrupt */
          RxCounter = 0;
      //USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
    }
  }

  if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
  {   
    /* Write one byte to the transmit data register */
    USART_SendData(USART1, TxBuffer[TxCounter++]);                    

   if(TxCounter == NbrOfDataToTransfer)
    {
      /* Disable the USART1 Transmit interrupt */
      USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
    }   
  }           
}
d.修改fputc和fgetc函数


int fputc(int ch, FILE *f)
{
        /* 发送一个字节数据到USART1 */
        USART_SendData(USART1, (u8) ch);
       
        /* 等待发送完毕 */
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);       
       
        return (ch);
}

// 重定向c库函数scanf到USART1
int fgetc(FILE *f)
{
                /* 等待串口1输入数据 */
        while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);

        return (int)USART_ReceiveData(USART1);
}


d.现在就可以直接在代码中使用printf就可以在串口连接的pc端看到打印信息了
方法2:利用mdk自带的ITM功能查看log打印,这种办法优点是不占用串口,速度快,但还是依赖mdk来显示,在keil官网有相关的详细介绍
方法3:使用Jlink自带的RTT工具打印。相比前2种,速度更快,也不依赖编译工具,只要有JLink即可。步骤如下:
a.需要使用新版的JLINK软件,最低要V4.90版本以上,旧版的jlink没有该功能
b.从官网下载的JLINK程序安装后,再找到包里以下4个个文件添加到工程中





c.这就可以直接使用SEGGER_RTT_printf函数打印,函数使用格式如下:
SEGGER_RTT_printf((0,"hello test %d timesrn",++u32Counter);
d.先启动调试程序,然后可以在安装的Jlink目录找到J-link RTT Client.exe(还有其他查看log工具),打开此程序,打印的信息即可显示在该窗口





e.查看RTT输出的工具有三个:



  • RTTViewer:不支持中文。至少要进入一次Debugger才能正常显示输出。建议进入Debugger之后再打开,否则经常不能正常显示输出
  • RTTLogger:支持中文,并且可以保存为log文件。使用具体的正确使用方法不清楚。根据手册说明,log只接收RTT通道1的输出,即SEGGER_RTT_printf(1,"字符串",输出格式)。但是实测,只能输出RTT通道0的信息,并且要求代码中要有使用到通道1的语句。否则收不到数据。
  • RTTClient:必须配合RTTLogger或者keil的Debugger来使用,而RTTLogger也必须配合Debugger使用。Client、Logger和Debugger三个窗口都打开的时候,Client和Logger只有其中一个能正常显示,另外一个会严重丢失数据。

方法4:使用图形化显示数据的调试法宝—JScope。
J-Scope是SEGGER公司推出的,可以在目标MCU运行时,实时分析数据并图形化显示的软件。它不需要像SWO那样需要MCU上面额外的引脚,而是使用标准的调试接口。J-Link驱动4.90之后的版本都有这个软件哦。
举报

更多回帖

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