最近用STM32CubeMX创建了一个demo工程,在调试过程中,printf打印功能一直不能正常打印,检查工程中也已将fputc函数进行了实现。奇怪的是用JTAG进行调试时打印恢复了正常。最后发现问题的原因是没有勾选MDK使用微库的配置,使用微库的话 ,不会使用半主机模式。
printf之类的函数,使用了半主机模式,MDK上开启半主机模式-需要SWO线(换言之,需要使用JTAG接线)当目标板脱离仿真器(jlink/ulink)单独运行时,不能使用半主机模式。
半主机是ARM的一种目标机制,它使得在ARM目标上跑的代码,如果主机电脑运行了调试器,那么该代码可以使用该主机电脑的输入输出设备。
这点非常重要,因为开发初期,可能开发者根本不知道该 ARM 器件上有什么输入输出设备,而半主基机制使得你不用知道ARM器件的外设,利用主机电脑的外设就可以实现输入输出调试。
一般单片机需要独立运行,使用时应去掉仿真器,把printf函数通过单片机的外设来实现,例如通过开发板的串口,lcd或者sd卡。首先要关掉半主机机制。然后再将输入输出重定向到ARM器件上,如 printf 和 scanf,需要重写fputc和fgetc函数。
printf 定义在
头文件中,如下:
int printf(const char *format, ...);
printf 函数根据 format
字符串给出的格式打印输出到 stdout
(标准输出)中,当然,printf 函数是不会一个字符一个字符去输出,它会调用更底层的 I/O 函数:fputc
去逐个字符打印。
fputc 也定义于头文件
中,如下:
int fputc(int ch, FILE *stream);
fputc 函数写入字符 ch 到给定输出流 stream,printf函数在调用该函数时,会向stream参数传入stdout
从而打印数据到标准输出。
那么,要实现printf打印到串口就变得非常简单了,只需要重新定义fputc函数,在fputc的函数中将数据通过串口发送,称之为:fputc重定向或者printf重定向。
在MDK中使用重定向的方式:
使用微库
#include
int fputc(int ch, FILE *stream)
{
while((USART1->ISR & UART_FLAG_TC) == 0);
USART1->TDR = (uint8_t) ch;
return ch;
}
使用标准库
系统 IO 一般指的是 Linux/Unix 系统调用中关于 I/O 操作的统称,其中包括 open、read、write、close 等操作。
与系统 IO 对应还有标准 IO,标准 IO 是 ISO 标准中 C 语言标准定义的 IO 访问接口,例如 fprintf/fgets 等 C 语言标准中定义的文件访问接口。
在 Linux 系统中 open/read/write 等函数的底层实现是通过系统调用访问的,在 STM32 的裸机中没有操作系统,更没有这些系统调用。
但是我们可以用一种其他的方式去实现这些系统 IO,而不需要操作系统。
/* 为确保没有从 C 库链接使用半主机的函数,如果仍然链接了使用半主机的函数,则链接器会报告错误 */
#pragma import(__use_no_semihosting)
/* 标准库需要的支持函数 */
struct __FILE {
int handle;
};
/* FILE 在stdio.h文件 */
FILE __stdout;
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE *stream)
{
while((USART1->ISR & UART_FLAG_TC) == 0);
USART1->TDR = (uint8_t) ch;
return ch;
}
microlib 进行了高度优化以使代码变得很小。 它的功能比缺省 C 库少,并且根本不具备某些 ISO C 特性。某些库函数的运行速度也比较慢,例如,memcpy()。
不同的编译器对于C库的底层实现机制是不同的,所以上面两种在MDK中的实现方法,在使用Gcc编译器的时候是不可行的。
GCC使用标准库重定向
#include
int _write(int fd, char *ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 0xFFFF);
return len;
}
与microlib 方式不同的是,GCC重定向是一次写入多个字符,而fputc 中是一次写入一个字符。
在需要使用printf 频繁写入大量字符时,建议使用半主机模式或者使用GCC 编译器。GCC使用打印时要注意行缓冲。