你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32串口通信中使用printf发送数据配置方法

[复制链接]
Savy1314 发布时间:2017-7-26 13:24
STM32串口通信中使用printf发送数据配置方法(开发环境 Keil RVMDK)


在STM32串口通信程序中使用printf发送数据,非常的方便。可在刚开始使用的时候总是遇到问题,常见的是硬件访真时无法进入main主函数,其实只要简单的配置一下就可以了。

下面就说一下使用printf需要做哪些配置。

有两种配置方法:

一、对工程属性进行配置,详细步骤如下

1、首先要在你的main 文件中 包含“stdio.h” (标准输入输出头文件)。

2、在main文件中重定义函数   
如下:
  1. // 发送数据
  2.    int fputc(int ch, FILE *f)
  3.    {
  4.       USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成 USART2 等
  5.       while (!(USART1->SR & USART_FLAG_TXE));
  6.       return (ch);
  7.    }
  8.    // 接收数据
  9.    int GetKey (void)  

  10.   {
  11.       while (!(USART1->SR & USART_FLAG_RXNE));
  12.       return ((int)(USART1->DR & 0x1FF));
  13.    }
复制代码
这样在使用printf时就会调用自定义的fputc函数,来发送字符。

3、在工程属性的 “Target" -> "Code Generation" 选项中勾选 "Use MicroLIB"
   MicroLIB 是缺省C的备份库,关于它可以到网上查找详细资料。

二、第二种方法是在工程中添加“Regtarge.c”文件
1、在main文件中包含 “stdio.h” 文件

2、在工程中创建一个文件保存为 Regtarge.c , 然后将其添加工程中在文件中输入如下内容(直接复制即可)
  1. #include
  2. #include
  3. #pragma import(__use_no_semihosting_swi)
  4. extern int  SendChar(int ch); // 声明外部函数,在main文件中定义
  5. extern int  GetKey(void);
  6. struct __FILE {
  7.   int handle;                 // Add whatever you need here
  8. };
  9. FILE __stdout;
  10. FILE __stdin;
  11. int fputc(int ch, FILE *f) {
  12.   return (SendChar(ch));
  13. }
  14. int fgetc(FILE *f) {
  15.   return (SendChar(GetKey()));
  16. }
  17. void _ttywrch(int ch) {
  18. SendChar (ch);
  19. }
  20. int ferror(FILE *f) {                            // Your implementation of ferror
  21.   return EOF;
  22. }
  23. void _sys_exit(int return_code) {
  24. label:  goto label;           // endless loop
  25. }
复制代码
3、在main文件中添加定义以下两个函数
  1. int SendChar (int ch)  {
  2.   while (!(USART1->SR & USART_FLAG_TXE)); // USART1 可换成你程序中通信的串口
  3.   USART1->DR = (ch & 0x1FF);
  4.   return (ch);
  5. }
  6. int GetKey (void)  {
  7.   while (!(USART1->SR & USART_FLAG_RXNE));
  8.   return ((int)(USART1->DR & 0x1FF));
  9. }
复制代码
至此完成配置,可以在main文件中随意使用 printf 。


STM32程序添加printf函数后无法运行的解决方法(串口实验)

标准库函数的默认输出设备是显示器,要实现在串口或LCD输出,必须重定义标准库函数里调用的与输出设备相关的函数.

例如:printf输出到串口,需要将fputc里面的输出指向串口(重定向),方法如下:

只要自己添加一个int fputc(int ch, FILE *f)函数,能够输出字符就可以了
  1. #ifdef __GNUC__

  2. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
  3. #else
  4. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
  5. #endif

  6. PUTCHAR_PROTOTYPE
  7. {
  8.   
  9.   
  10.   USART_SendData(USART1, (uint8_t) ch);
  11.   
  12.   while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
  13.   return ch;
  14. }
复制代码
因printf()之类的函数,使用了半主机模式。使用标准库会导致程序无法运行,以下是解决方法:


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


方法2.仍然使用标准库,在主程序添加下面代码:
  1. #pragma import(__use_no_semihosting)

  2. _sys_exit(int x)
  3. {
  4.   x = x;
  5. }

  6. struct __FILE
  7. {
  8.   int handle;
  9. };

  10. FILE __stdout;
复制代码
IAR EWARM

General Options -- Library Configuration -- Library : Full < file descriptor support >

  1. #include

  2. #ifdef __GNUC__

  3. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
  4. #else
  5. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
  6. #endif
  7. PUTCHAR_PROTOTYPE
  8. {
  9.   
  10.   
  11.   USART_SendData(USART1, (uint8_t) ch);
  12.   
  13.   while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
  14.   return ch;
  15. }
复制代码

General Options -- Library Configuration -- Library : Normal < NO file descriptor support >
  1. #include

  2. size_t __write(int handle, const unsigned char * buffer, size_t size)
  3. {
  4.   // byte by byte write
  5. }

  6. size_t __dwrite(int handle, const unsigned char * buffer, size_t size)
  7. {
  8.   // buffer[ 0x50 ]
  9. }
复制代码

Buffered Terminal Output : Enabled

xxwritebuffered.c

  1. #define STORE_SIZE 80

  2. static size_t storeLen = 0;
  3. static unsigned char store[STORE_SIZE];
复制代码

uint8_t store[ 0x50 ];

uint32_t storelen;

printf() --> __dwrite() : buffer[0x50]

Buffered Terminal Output : Disabled

printf() --> __write(), byte by byte


自定义输出缓冲区
  1. #define LOG_MAX_STR_LEN  512
  2. void log_printf( const char * fmt, ... )
  3. {
  4.   char log_buf[ LOG_MAX_STR_LEN ];
  5.   va_list args;

  6.   va_start( args, fmt );
  7.   int count = vsnprintf( log_buf, LOG_MAX_STR_LEN, fmt, args );
  8.   va_end( args );

  9.   // If an output error is encountered, a negative value is returned.
  10.   if ( count < 0 )
  11.     return;

  12.   // "123456"    [123456][0X] : count = 6, n = 8
  13.   // "1234567"   [1234567][0] : count = 7, n = 8
  14.   // "12345678"  [1234567][0] : count = 8, n = 8
  15.   // "123456789" [1234567][0] : count = 9, n = 8
  16.   if ( count >= LOG_MAX_STR_LEN )
  17.     count = LOG_MAX_STR_LEN - 1;

  18.   // now log_buf is C string with the terminating null character
  19.   __write(0, log_buf, count );
  20. }
复制代码
log_printf --> __write(), bufferred

stm32系列单片机之printf重定向

在程序的调试过程中,除了那些高大上的调试手段外,printf无疑是我们最熟悉最顺手的调试方法。

通过使用printf,我们可以很方便很直观的获取当前程序的运行状态。

printf()函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息。

但是在单片机开发中,一般情况下并不存在标准输出设备,因此我们需要将printf的输出信息重定向,也就是输出到其他输出设备中去。

在stm32平台上实现重定向的方式有两种,重定向至UART,或者通过JTAG的SW模式将printf重定向至SWO引脚输出。

首先介绍第一种,重定向至UART,这种方式我们比较熟悉,ST官方提供的固件库中也是使用的这种方法。

代码如下:在对UART进行初始化后,通过如下代码对printf进行重定向
  1. int fputc(int ch, FILE *f)
  2. {
  3.   USART_SendData(USART1, (uint8_t) ch);
  4.   
  5.   while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
  6.   {}
  7.   return ch;
  8. }
复制代码
通过JTAG的SW模式将printf重定向至SWO引脚输出

1.在源码中添加对ITM端口寄存器的定义
  1. #define ITM_Port8(n)    (*((volatile unsigned char *)(0xE0000000+4*n)))
  2. #define ITM_Port16(n)   (*((volatile unsigned short*)(0xE0000000+4*n)))
  3. #define ITM_Port32(n)   (*((volatile unsigned long *)(0xE0000000+4*n)))

  4. #define DEMCR           (*((volatile unsigned long *)(0xE000EDFC)))
  5. #define TRCENA          0x01000000
复制代码
2.通过如下代码将printf的输出重定向至ITM的Port 0
  1. int fputc(int ch, FILE *f)
  2. {
  3.   if (DEMCR & TRCENA) {
  4.     while (ITM_Port32(0) == 0);
  5.     ITM_Port8(0) = ch;
  6.   }
  7.   return(ch);
  8. }
复制代码
3.通过printf输出调试信息
  1. printf("AD value = 0xX\r\n", AD_value);
复制代码
4.将Jtag设置为SW模式,并设置ITM的Port 0 获取信息。

STM32中重定向printf到SWO口

printf在命令行编程的时候是非常常用的,虽然是个老函数,但是功能强大,经久不衰

51等8位单片机由于RAM比较小,栈就比较小,跑printf比较吃力,

但是STM32这种32位单片机跑printf就很容易了,而作为一种调试手段,printf十分方便、直观。

比较常见的方法是把printf重定向到串口,不过这需要外接一个串口线,比较麻烦。

其实STM32自带的SWO口是能够异步输出数据的,而且不需要外接什么设备,

ST-LINK/J-Link等带SWO口的调试器都支持。

下面以STM32F4Discovery开发板+GCC为例说明。

根据这里的方法,也可以把printf定位到其他外设。

PS:IAR在编译选项里自带了printf via SWO的功能,就不需要外加设置了。

首先来说说怎么把信息输出到SWO口,一句话搞定。

ITM_SendChar(ch);

这是在core_cm4.h(如果是F1系列的那就是core_cm3.h)中定义的内联函数。

不过不需要特意去include这个头文件,通过#include "stm32f4xx.h"就间接地将core_cm4.h包含进来。

不过说起来,ITM这个东西其实严格来说是Cortex-M提供的一个特性,而不是STM32。

利用这个函数把信息输出到SWO口之后再打开St-Link Utility,

在菜单里找到ST-LINK→Printf via SWO Viewer就会弹出一个窗口,

设置System Clock为单片机内核频率,点Start就能看到输出的信息了。

接下来就是把printf函数输出的字符串重定向过去了。

由于单片机的外设功能是根据需求变化的,编译器不可能确定printf需要用到的外设资源,

于是乎它就干脆留了个接口,也就是_write函数,

当然除了_write函数之外还有_read等其他函数,不过这里我们用不到。

其声明为 int _write(int fd, char* ptr, int len);

关于_write函数,说简单点,就是所有涉及到输出字符串的函数,

比如printf和putchar(),最终都会跑到_write函数,这里fd是文件标识符,说开来就比较复杂了,

这里我们用得到的就只有STDOUT_FILENO跟STDERR_FILENO,

其中前一个是标准输出的文件标识符的预定义变量,后一个是错误输出的文件标识符预定义变量。

第二个变量ptr是需要输出的字符串首地址,len就是输出长度。

当我们调用printf函数后,C运行库会把输入变量转换为最终需要输出的字符串,

然后调用_write函数,将结果输出。我们的工作就是实现一个_write函数。

新建一个_write.c文件,内容如下:
  1. #include
  2. #include

  3. #include "stm32f10x.h"

  4. #ifdef _DEBUG

  5. int _write(int fd, char* ptr, int len)
  6. {
  7. if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
  8. {
  9. int i = 0;
  10. while(i<<span style="font-family: 'Courier New' !important; line-height: 1.8; margin: 0px; padding: 0px;">len)
  11. ITM_SendChar(ptr[i++]);
  12. }
  13. return len;
  14. }

  15. #endif
复制代码
加了个#ifdef _DEBUG 的效果是未加 _DEBUG 定义的时候就忽略下面的东西,

因为这东西主要是用在调试阶段,RELEASE版本里面都用不到了,而且多少还是会影响速度。

其他东西就很简单了- -不需要多说明了吧。

直接编译能通过,但是链接会报错,提示无法找到_read之类的一堆函数。

在链接脚本的下面libgcc.a ( * )后面加上libnosys.a ( * ),就不会报错了。

具体原因涉及到Cortex-M3使用的newlib库的实现,就不具体展开了。

好吧好吧,其实我也不知道。

如果想把信息定位到串口,可以直接把ITM_SendChar改成相应的串口函数,

也可以利用DMA,先把数据拷贝到DMA缓冲区,让DMA自动传数据,提高响应速度。


STM32片内外设--DBG之Keil SWO输出

1) 加入stdio.h,这样你就可以调用printf函数了
2) 使能SWO输出

使能SWO输出。最简单的办法就是将如下的函数拷贝到你的工程里面,并且在mian函数初始化之后调用该函数。
  1. void setupSWO(void)
  2. {
  3. uint32_t *dwt_ctrl = (uint32_t *) 0xE0001000;
  4. uint32_t *tpiu_prescaler = (uint32_t *) 0xE0040010;
  5. uint32_t *tpiu_protocol = (uint32_t *) 0xE00400F0;

  6. CMU->HFPERCLKEN0 |= CMU_HFPERCLKEN0_GPIO;

  7. GPIO->ROUTE |= GPIO_ROUTE_SWOPEN;
  8. #if defined(_EFM32_GIANT_FAMILY)

  9. GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC0;

  10. GPIO->P[5].MODEL &= ~(_GPIO_P_MODEL_MODE2_MASK);
  11. GPIO->P[5].MODEL |= GPIO_P_MODEL_MODE2_PUSHPULL;
  12. #else

  13. GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC1;

  14. GPIO->P[2].MODEH &= ~(_GPIO_P_MODEH_MODE15_MASK);
  15. GPIO->P[2].MODEH |= GPIO_P_MODEH_MODE15_PUSHPULL;
  16. #endif

  17. CMU->OSCENCMD = CMU_OSCENCMD_AUXHFRCOEN;

  18. while(!(CMU->STATUS & CMU_STATUS_AUXHFRCORDY));


  19. CoreDebug->DHCSR |= 1;
  20. CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;


  21. *dwt_ctrl = 0x400113FF;

  22. *tpiu_prescaler = 0xf;

  23. *tpiu_protocol = 2;

  24. ITM->LAR = 0xC5ACCE55;
  25. ITM->TCR = 0x10009;
  26. }
复制代码
3) 配置Keil的工程选项

打开Keil的工程配置,选择Debug页面,选择仿真器为Cortex-M/R J-Link/J-Trace, 并点击仿真器选项边上的setting选项,打开具体的设置窗口。

在打开的窗口中,切换到Trace页面,选中Enable,并且设置Core Clock为14MHz,分频选项为Core Clock/16。详情如下:
4) 在初始化SWO函数之后的地方,使用printf函数进行输出。例如printf("Hello world")。

5) 在你的工程里面,需要添加如下的函数:
  1. struct __FILE

  2. {

  3.   int handle;

  4. };

  5. FILE __stdout;
  6. FILE __stdin;
  7. int fputc(int ch, FILE *f)

  8. {
  9.   ITM_SendChar(ch);
  10.   return(ch);
  11. }
复制代码
6) 编译你的代码,并且进入Debug状态

7) 打开Keil的printf-view窗口, 通过 View -> Serial Windows -> Debug(printf) View

8) 点击运行之后,在Debug (printf) View里即可查看

收藏 4 评论4 发布时间:2017-7-26 13:24

举报

4个回答
张亚飞 回答时间:2017-7-26 14:05:24
先收藏,需要的时候拿出来弄一下printf
MrJiu 回答时间:2017-7-27 09:20:20
支持一个!!
any012 回答时间:2017-7-27 14:53:07
本帖最后由 any012 于 2017-7-27 15:04 编辑

在群里看到朋友共享的GCC下printf函数实现的方法:
  1. int _write(int fd, char *ptr, int len)
  2. {
  3.     HAL_UART_Transmit(&huart1, (uint8_t*) ptr, len, 0xFFFF);
  4.     return len;
  5. }

  6. int _read(int fd, char *ptr, int len)
  7. {
  8.     *ptr = 0x00;    //Flush the character buffer
  9.     HAL_UART_Receive(&huart1, (uint8_t*) ptr, 1, 0xFFFF);
  10.     return 1;
  11. }
复制代码
一开始使用时总觉得有问题,有些数没被发出来。
后来查询得知,printf()语句里面最后加上\r\n后才输出数据,我之前习惯在前边加\r\n。

淡定的H羊 回答时间:2017-7-29 00:10:28
这个太厉害了!谢谢分享。以前看正点原子的串口代码看得云里雾里

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版