stm32的printf重定向

来源:互联网 发布:oa办公系统java源代码 编辑:程序博客网 时间:2024/05/16 16:57

                                                STM32中printf重定向到串口

 

        学习STM32过程中,经常打交道的莫过于串口,你可以将任何信息,当然重要的是调试信息打印到串口中输出,总是用一个字节发送函数或者字符串发送函数

 

总是有些不放便,之前编程中熟悉的莫过于printf了,下面就给出了用printf打印到串口的方案,当然方案不止一个,仅供参考。

 

1、 添加printf的头文件   #include <stdio.h>

 

2、重写int fputc(int ch, FILE *f)函数

 

int fputc(int ch, FILE *f);

函数

int fputc(int ch, FILE *f)
{

USART_SendData(USART1, (uint8_t) ch);

while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);

return ch;
}

3、修改一下选中Use MicroLIB   Target——Code Generation——选中Use MicroLIB

 

这样就可以像以前那样使用printf了。




vsprintf()  printf()

     首先使用printf函数,代码如下:

#include "stdio.h"     

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;">#define <span style="color:#ff0000;">NOT_USE_MICROLIB</span> 0  
  2.   
  3.   
  4. #if <span style="color:#ff0000;">NOT_USE_MICROLIB</span>  
  5. //=========不用选择use MicroLIB的方法=====================  
  6. #pragma import(__use_no_semihosting)               
  7. //标准库需要的支持函数                   
  8. struct __FILE   
  9. {   
  10.     int handle;   
  11.   
  12. };   
  13.   
  14. FILE __stdout;         
  15. //定义_sys_exit()以避免使用半主机模式      
  16. _sys_exit(int x)   
  17. {   
  18.     x = x;   
  19. }   
  20. //重定义fputc函数   
  21. int fputc(int ch, FILE *f)  
  22. {        
  23.     while((USART1->SR&0X40)==0);//循环发送,直到发送完毕  <span style="font-family: Arial, Helvetica, sans-serif;">USART1</span><span style="font-family: Arial, Helvetica, sans-serif;"> 为STM32串口1的寄存器,后续需要根据实际MCU进行更改</span>  
  24.     USART1->DR = (u8) ch;        
  25.     return ch;  
  26. }  
  27. #else  
  28. //=========使用use MicroLIB的方法=====================  
  29.   
  30. int fputc(int ch, FILE *f)  
  31. {  
  32.     USART_SendData(USART1, (uint8_t) ch);  
  33.   
  34.     while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}    
  35.      
  36.     return ch;  
  37. }  
  38. int GetKey (void)  {   
  39.   
  40.     while (!(USART1->SR & USART_FLAG_RXNE));  
  41.   
  42.     return ((int)(USART1->DR & 0x1FF));  
  43. }  
  44. #endif</span>  

      解释下:

    想要在MDK中使用printf,需要同时重定义fputc函数和避免使用semihosting(半主机模式),标准库函数的默认输出设备是显示器,要实现在串口或LCD输出,必须重定义标准库函数里调用的与输出设备相关的函数。所以需要将printf需要调用的fput里面的输出指向串口(重定向)。

     另外printf之类的函数,使用了半主机模式。使员工标准库会导致程序无法运行,解决方法有2个:

     方法1.使用微库,因为使用微库的话,不会使用半主机模式。

    

    方法2.添加一段代码

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. #pragma import(__use_no_semihosting)               
  2. //标准库需要的支持函数                   
  3. struct __FILE   
  4. {   
  5.     int handle;   
  6.   
  7. };   
  8.   
  9. FILE __stdout;         
  10. //定义_sys_exit()以避免使用半主机模式      
  11. _sys_exit(int x)   
  12. {   
  13.     x = x;   
  14. }   

    semihosting的作用,介绍如下  
    Semihosting is a mechanism for ARM targets to communicate input/outputrequests from application code to a host computer running a debugger. This mechanismcould be used, for example, to allow functions in the C library, such as printf() andscanf(), to use the screen and keyboard of the host rather than having a screenand keyboard on the target system.  
    This is useful because development hardware often does not have all the inputand output facilities of the final system. Semihosting allows the host computer toprovide these facilities.  
    Semihosting is implemented by a set of defined software interrupt (SWI)operations.  
    The application invokes the appropriate SWI and the debug agent then handlesthe SWI exception. The debug agent provides the required communication with thehost.  
    In many cases, the semihosting SWI will be invoked by code within libraryfunctions. The application can also invoke the semihosting SWI directly. Referto the C library descriptions in the ADS Compilers and Libraries Guide for moreinformation on support for semihosting in the ARM C library.  
     

     按我的理解,这个模式是用来调试的,通过仿真器,使用主机的输入输出代替单片机自己的,也就是说即便单片机没有输出口也能printf到电脑上。反过来,由于这个模式更改了printf()等的实现方式,输入输出就不走单片机的外设了,所以只重定义fputc不起作用。 

用代码关闭此模式后,需要同时更新一下__stdout 和__stdin 的定义,所以有后面的语句。 

    以上仅为个人理解,如有错误请指正。  

    另外,勾选microlib之后,也许编译的时候就不把开启semihosting的文件包进去了,所以没事。 

    

     以上方法就成功的将printf引进过来直接调用了,但是个人觉得还是比较麻烦,比如在STM32和LPC环境下就必须更改底层串口接口寄存器。如此就引来了另外一个C标准库函数vsprintf()。

    函数名: vsprintf 
     功 能: 送格式化输出到串中 
     用 法: int vsprintf(char *string, char *format, va_list param); 

     代码如下:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h>  
  2. #include <stdarg.h>  
  3.   
  4. void  UART_PrintStr (const uint8_t * str)  
  5. {  
  6.   
  7.    while ((*str) != 0) {  
  8.       if (*str == '\n') {  
  9.          UART_PrintChar(*str++);  
  10.          UART_PrintChar('\r');  
  11.       } else {  
  12.          UART_PrintChar(*str++);  
  13.       }      
  14.    }  
  15. }  
  16.   
  17. /*********************************************************************//** 
  18.  * @brief     print formatted string. This function takes variable length arguments 
  19.  * @param[in]     variable length arguments 
  20.  * @return    None. 
  21.  **********************************************************************/  
  22. void  UART_Printf (const  void *format, ...)  
  23. {  
  24.     static  uint8_t  buffer[40 + 1];  
  25.             va_list     vArgs;  
  26.   
  27.     va_start(vArgs, format);  
  28.     vsprintf((char *)buffer, (char const *)format, vArgs);  
  29.     va_end(vArgs);  
  30.     UART_PrintStr((uint8_t *) buffer);  
  31. }  

    VA_LIST的用法( VA_LIST 是在C语言中解决变参问题的一组宏):

  (1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针
  (2)然后用VA_START宏初始化变量刚定义的VA_LIST变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数。(如在运行VA_START(ap,v)以后,ap指向第一个可变参数在堆栈的地址。)
  (3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型。
  (4)最后用VA_END宏结束可变参数的获取。然后你就可以在函数里使用第二个参数了。
    如果函数有多个可变参数的,依次调用VA_ARG获取各个参数。


     vsprintf的功能就是将为止大小的输入进行重组,之后使用串口输出,底层还是运用单片机的串口输出函数。

     综上所诉,个人更倾向于使用vsprintf进行修改,方便快捷。


0 0
原创粉丝点击