STM32串口通信中使用printf发送数据配置方法(开发环境 Keil RVMDK)
在STM32串口通信程序中使用printf发送数据,非常的方便。可在刚开始使用的时候总是遇到问题,常见的是硬件访真时无法进入main主函数,其实只要简单的配置一下就可以了。
下面就说一下使用printf需要做哪些配置。
有两种配置方法:
一、对工程属性进行配置,详细步骤如下
1、首先要在你的main 文件中 包含“stdio.h” (标准输入输出头文件)。
2、在main文件中重定义函数
如下:
- // 发送数据
- int fputc(int ch, FILE *f)
- {
- USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成 USART2 等
- while (!(USART1->SR & USART_FLAG_TXE));
- return (ch);
- }
- // 接收数据
- int GetKey (void)
- {
- while (!(USART1->SR & USART_FLAG_RXNE));
- return ((int)(USART1->DR & 0x1FF));
- }
复制代码 这样在使用printf时就会调用自定义的fputc函数,来发送字符。
3、在工程属性的 “Target" -> "Code Generation" 选项中勾选 "Use MicroLIB"
MicroLIB 是缺省C的备份库,关于它可以到网上查找详细资料。
二、第二种方法是在工程中添加“Regtarge.c”文件
1、在main文件中包含 “stdio.h” 文件
2、在工程中创建一个文件保存为 Regtarge.c , 然后将其添加工程中在文件中输入如下内容(直接复制即可)
- #include
- #include
- #pragma import(__use_no_semihosting_swi)
- extern int SendChar(int ch); // 声明外部函数,在main文件中定义
- extern int GetKey(void);
- struct __FILE {
- int handle; // Add whatever you need here
- };
- FILE __stdout;
- FILE __stdin;
- int fputc(int ch, FILE *f) {
- return (SendChar(ch));
- }
- int fgetc(FILE *f) {
- return (SendChar(GetKey()));
- }
- void _ttywrch(int ch) {
- SendChar (ch);
- }
- int ferror(FILE *f) { // Your implementation of ferror
- return EOF;
- }
- void _sys_exit(int return_code) {
- label: goto label; // endless loop
- }
复制代码 3、在main文件中添加定义以下两个函数
- int SendChar (int ch) {
- while (!(USART1->SR & USART_FLAG_TXE)); // USART1 可换成你程序中通信的串口
- USART1->DR = (ch & 0x1FF);
- return (ch);
- }
- int GetKey (void) {
- while (!(USART1->SR & USART_FLAG_RXNE));
- return ((int)(USART1->DR & 0x1FF));
- }
复制代码 至此完成配置,可以在main文件中随意使用 printf 。
STM32程序添加printf函数后无法运行的解决方法(串口实验)
标准库函数的默认输出设备是显示器,要实现在串口或LCD输出,必须重定义标准库函数里调用的与输出设备相关的函数.
例如:printf输出到串口,需要将fputc里面的输出指向串口(重定向),方法如下:
只要自己添加一个int fputc(int ch, FILE *f)函数,能够输出字符就可以了
- #ifdef __GNUC__
- #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
- #else
- #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
- #endif
- PUTCHAR_PROTOTYPE
- {
-
-
- USART_SendData(USART1, (uint8_t) ch);
-
- while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
- return ch;
- }
复制代码 因printf()之类的函数,使用了半主机模式。使用标准库会导致程序无法运行,以下是解决方法:
方法1.使用微库,因为使用微库的话,不会使用半主机模式.
方法2.仍然使用标准库,在主程序添加下面代码:
- #pragma import(__use_no_semihosting)
- _sys_exit(int x)
- {
- x = x;
- }
-
- struct __FILE
- {
- int handle;
- };
- FILE __stdout;
复制代码 IAR EWARM
General Options -- Library Configuration -- Library : Full < file descriptor support >
- #include
- #ifdef __GNUC__
- #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
- #else
- #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
- #endif
- PUTCHAR_PROTOTYPE
- {
-
-
- USART_SendData(USART1, (uint8_t) ch);
-
- while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
- return ch;
- }
复制代码
General Options -- Library Configuration -- Library : Normal < NO file descriptor support >
- #include
- size_t __write(int handle, const unsigned char * buffer, size_t size)
- {
- // byte by byte write
- }
- size_t __dwrite(int handle, const unsigned char * buffer, size_t size)
- {
- // buffer[ 0x50 ]
- }
复制代码
Buffered Terminal Output : Enabled
xxwritebuffered.c
- #define STORE_SIZE 80
- static size_t storeLen = 0;
- 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
自定义输出缓冲区
- #define LOG_MAX_STR_LEN 512
- void log_printf( const char * fmt, ... )
- {
- char log_buf[ LOG_MAX_STR_LEN ];
- va_list args;
- va_start( args, fmt );
- int count = vsnprintf( log_buf, LOG_MAX_STR_LEN, fmt, args );
- va_end( args );
- // If an output error is encountered, a negative value is returned.
- if ( count < 0 )
- return;
- // "123456" [123456][0X] : count = 6, n = 8
- // "1234567" [1234567][0] : count = 7, n = 8
- // "12345678" [1234567][0] : count = 8, n = 8
- // "123456789" [1234567][0] : count = 9, n = 8
- if ( count >= LOG_MAX_STR_LEN )
- count = LOG_MAX_STR_LEN - 1;
- // now log_buf is C string with the terminating null character
- __write(0, log_buf, count );
- }
复制代码 log_printf --> __write(), bufferred
stm32系列单片机之printf重定向
在程序的调试过程中,除了那些高大上的调试手段外,printf无疑是我们最熟悉最顺手的调试方法。
通过使用printf,我们可以很方便很直观的获取当前程序的运行状态。
printf()函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息。
但是在单片机开发中,一般情况下并不存在标准输出设备,因此我们需要将printf的输出信息重定向,也就是输出到其他输出设备中去。
在stm32平台上实现重定向的方式有两种,重定向至UART,或者通过JTAG的SW模式将printf重定向至SWO引脚输出。
首先介绍第一种,重定向至UART,这种方式我们比较熟悉,ST官方提供的固件库中也是使用的这种方法。
代码如下:在对UART进行初始化后,通过如下代码对printf进行重定向
- int fputc(int ch, FILE *f)
- {
- USART_SendData(USART1, (uint8_t) ch);
-
- while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
- {}
- return ch;
- }
复制代码 通过JTAG的SW模式将printf重定向至SWO引脚输出
1.在源码中添加对ITM端口寄存器的定义
- #define ITM_Port8(n) (*((volatile unsigned char *)(0xE0000000+4*n)))
- #define ITM_Port16(n) (*((volatile unsigned short*)(0xE0000000+4*n)))
- #define ITM_Port32(n) (*((volatile unsigned long *)(0xE0000000+4*n)))
- #define DEMCR (*((volatile unsigned long *)(0xE000EDFC)))
- #define TRCENA 0x01000000
复制代码 2.通过如下代码将printf的输出重定向至ITM的Port 0
- int fputc(int ch, FILE *f)
- {
- if (DEMCR & TRCENA) {
- while (ITM_Port32(0) == 0);
- ITM_Port8(0) = ch;
- }
- return(ch);
- }
复制代码 3.通过printf输出调试信息
- 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文件,内容如下:
- #include
- #include
- #include "stm32f10x.h"
- #ifdef _DEBUG
- int _write(int fd, char* ptr, int len)
- {
- if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
- {
- int i = 0;
- while(i<<span style="font-family: 'Courier New' !important; line-height: 1.8; margin: 0px; padding: 0px;">len)
- ITM_SendChar(ptr[i++]);
- }
- return len;
- }
- #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函数初始化之后调用该函数。
- void setupSWO(void)
- {
- uint32_t *dwt_ctrl = (uint32_t *) 0xE0001000;
- uint32_t *tpiu_prescaler = (uint32_t *) 0xE0040010;
- uint32_t *tpiu_protocol = (uint32_t *) 0xE00400F0;
-
- CMU->HFPERCLKEN0 |= CMU_HFPERCLKEN0_GPIO;
-
- GPIO->ROUTE |= GPIO_ROUTE_SWOPEN;
- #if defined(_EFM32_GIANT_FAMILY)
-
- GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC0;
-
- GPIO->P[5].MODEL &= ~(_GPIO_P_MODEL_MODE2_MASK);
- GPIO->P[5].MODEL |= GPIO_P_MODEL_MODE2_PUSHPULL;
- #else
-
- GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC1;
-
- GPIO->P[2].MODEH &= ~(_GPIO_P_MODEH_MODE15_MASK);
- GPIO->P[2].MODEH |= GPIO_P_MODEH_MODE15_PUSHPULL;
- #endif
-
- CMU->OSCENCMD = CMU_OSCENCMD_AUXHFRCOEN;
-
- while(!(CMU->STATUS & CMU_STATUS_AUXHFRCORDY));
-
-
- CoreDebug->DHCSR |= 1;
- CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
-
-
- *dwt_ctrl = 0x400113FF;
-
- *tpiu_prescaler = 0xf;
-
- *tpiu_protocol = 2;
-
- ITM->LAR = 0xC5ACCE55;
- ITM->TCR = 0x10009;
- }
复制代码 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) 在你的工程里面,需要添加如下的函数:
- struct __FILE
- {
- int handle;
- };
- FILE __stdout;
- FILE __stdin;
- int fputc(int ch, FILE *f)
- {
- ITM_SendChar(ch);
- return(ch);
- }
复制代码 6) 编译你的代码,并且进入Debug状态
7) 打开Keil的printf-view窗口, 通过 View -> Serial Windows -> Debug(printf) View
8) 点击运行之后,在Debug (printf) View里即可查看
|
在群里看到朋友共享的GCC下printf函数实现的方法:
后来查询得知,printf()语句里面最后加上\r\n后才输出数据,我之前习惯在前边加\r\n。