分享

两种在keil中实现printf重定向到串口的方法

 myallmy 2023-02-22 发布于北京

1 系统环境
系统:win10
ide:keil5
测试芯片:GD32F450
2 说明
在我们使用mcu的时候,printf重定向最常用的几种种方案

利用mcu的外设,进行重定向,如printf重定向到串口等外设上。
利用mcu内核支持的某种指令来进行跟踪调试,如cortex-m3、m4、m7支持利用指令跟踪宏单元(ITM)重定向到调试接口。
利用半主机模式,这个要由工具链支持半主机特性,需要在可执行镜像种添加额外的运行时代码库。
基于J-Link的RTT Viewer,它结合了SWO和半主机( semihosting)的优点,具有很高的性能。
利用diag调试。
利用Tracealyzer。
第一种需要有外设支持,第二种需要内核支持和调试器支持,第三种和第四种需要调试器支持在添加一些额外的代码库,嵌到mcu的某个特定中断(这个中断所有ARM都是带的)上来完成。

这些重定向的关键是重新实现printf,而这个函数又和工具链和工具链使用的c库类型有关。

本文介绍两种嵌入式实现printf重定向到外设串口的方法,一种利用标准c库,一种利用轻量型MicroLIB库。

至于其他方案,我会在后面的文章讲解。

两种情况各有利弊,使用标准库跨平台会好些。使用MicroLIB库简单、运行效率高。

所以本文介绍标准库和MicroLIB库两种方案,用者自行选择。

3 用标准c库实现printf重映射到串口
3.1 c库的简介
虽然c语言是非常高效精简的语言,但是c语言的一些功能也需要c库进行支持的,有关于c库的使用请参考菜鸟教程。c库解决了我们很多问题,比如若进行字符串比较需要#include <string.h>.当调用printf和scanf就需要#include <stdio.h>.

但是我们要知道,广义上我们所说的标准c库指的是符合c国际标准的库(如:c89,c99,c11),标准只是指明了库的框架和特点,而库则是厂家会根据标准写出来的,这也就说明每种c库开发商都不一样。如微软的c库叫VC库,linux的c库叫GNU C库,MinGW是仿照GNU C库做出来的可以在win上运行的库。而ARM用的c库叫ARM C库,这些库多少都会有一些区别的,他们都会在c国际标准上扩展一些自己的函数或特性,这导致c库和c库也存在一定的不兼容(但是绝大部分功能是兼容的)。

而在arm中我所说的标准c库指的是ARM C标准库。

3.2 用标准c库实现printf重映射到串口
在使用 printf 和 scanf,你需要重写 fputc和 fgetc 函数。但是嵌入式不推荐使用scanf(scanf 不会对放数据的空间进行检测,可能导致输入的数据空间大于原有的数据空间,出现报错,所以scanf不安全),而是用其他方式代替,那不是本文重点。所以下面以printf 为例。

在标准库下,printf 定义在 <stdio.h> 头文件中,如下:

1
int printf(const char *format, ...)


printf 函数根据 format 字符串给出的格式打印输出到 stdout(标准输出)中,当然,printf 函数是不会一个字符一个字符去输出,而是通过 for循环,反复调用更底层的 I/O 函数:fputc去逐个字符打印。fputc 也定义于头文件 <stdio.h>中,如下:

1
int fputc(int ch, FILE *stream)


fputc 函数的作用是将写入字符 ch 输出到输出流 stream中,printf函数在调用该函数时,会向stream参数传入stdout(输出接口)从而打印数据到标准输出。

那么,要实现printf打印到串口就变得非常简单了,只需要重新定义fputc函数,在fputc的函数中将数据通过串口发送,称之为:fputc重定向或者printf重定向。

但是对于ARM来讲,有一个非常严重的问题,那就是ARM支持一种叫半主机模式,在M3、M4的权威指南中对于它的描述很少,只是说这个要由工具链支持半主机特性,需要在可执行镜像种添加额外的运行时代码库,然后利用ARM的某个特定中断,将printf传给调试器,从而实现在pc和ARM的交互,我们可以利用这种方式调试我们的程序。注意这种方式不是我们说的指令跟踪宏单元(ITM)调试(虽然效果很像但是原理不同),它俩是有区别的,尤其对于M0处理器来说没有ITM,但是可以支持半主机,我会用另一篇会讲半主机调试,那篇文章还在整理中。

按照我的理解keil的自带工具链支持半主机特性,使用标准库默认会启动半主机模式,半主机模式库里面有fputc和 fgetc 弱实现(会处理某个中断),而当您重新定义fputc和 fgetc,系统就会按半主机流程调用您的fputc,会导致那个中断没被处理最终程序卡住,无法运行,因此在用标准库重定向fputc的时候,一定主动声明禁用ARM芯片的半主机模式,然后使用自己的一套逻辑来输出,不采用半主机的流程来输出,所以要利用目标 ARM器件的输入输出设备完成fputc,首先要关掉半主机。然后再将输入输出重定向到 ARM 器件上,如 printf 和 scanf,你不止需要重写 fputc和 fgetc 函数。还需要重写一些因为关闭半主机而被屏蔽的跟半主机密切相关的其他函数,下面就是将 scanf 和 printf 重定向到 uart 的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#pragma import(__use_no_semihosting) //确保没有从 C 库链接使用半主机的函数      
//解决HAL库使用时,某些情况可能报错的bug
//因为禁止了半主机模式,需要重写一个半主机模式下的接口,如下
int _ttywrch(int ch)   
{
    ch=ch;
    return ch;
}
//标准库需要的支持函数                
struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};
FILE __stdout;    
//定义_sys_exit()以避免使用半主机模式   
void _sys_exit(int x)
{
    x = x;
}
int fputc(int ch, FILE *f){     
    usart_data_transmit(USART0, (uint8_t)ch);  //调用串口发送函数
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));//等待发送完成
    return ch;
}
   
#if 0
int fgetc(FILE *f) 
{
    int ch;
    //数据接收
    while (usart_flag_get(USART0, USART_FLAG_RBNE) == RESET);
    ch = usart_data_receive(USART0); //获取字符
     
    return ch;
}
#endif

  解释:在 C 模块中,使用 #pragma 指令:

1
#pragma import(__use_no_semihosting)

  在汇编语言模块中,使用 IMPORT 指令:

1
IMPORT __use_no_semihosting

  

通过加上面这句话,就是声明不使用半主机模式,如果仍然链接了使用半主机的函数,则链接器会报错。这个时候就需要看一下报错的内容,重新实现一下这个报错函数,如:若出现如下编译错误:Error: L6915E: Library reports error: __use_no_semihosting_swi was requested, but _ttywrch was referenced,此时你只需重写_ttywrch 函数即可

1
2
3
4
5
int _ttywrch(int ch)   
{
    ch=ch;
    return ch;
}

  

现在就可以在工程代码中使用printf()函数了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main(void)
{
    USART0_INIT();
     
    printf("\r\nGD32F450 printf output\r\n");
     
    while (1){
     
    };     
    return 0;
}

  

4 MicroLIB库简介
4.1 MicroLIB库由来
上面我已经讲了标准库的由来,对于性能比较好的设备,我们直接用标准c库的源码来编就好了,但是对于单片机来讲,标准的c库太笨重了,显然在单片机使用标准c库不是最合理的选择。那么一个轻量型MicroLIB库就诞生了。

MicroLIB是keil专门为单片机准备的简单的c库。其实这种c库有很多(毕竟不是所有的人都用keil写程序),也有开源的,比如libc和newlib .

在keil中使用MicroLIB库替代标准库简单高效,同时使用MicroLIB库 ,会默认不开启RAM的半主机模式。而且实现起来也超简单。

4.2 MicroLIB库的特点
既然MicroLIB库不是标准的c库,而是精简过的c库,那么它相对标准的c库肯定是区别的:

MicroLib是精简c库的备选库(因为这类精简库有很多,这只是其中一种,因为可以在keil中直接配置,所以再keil环境下添加简单,因此在keil环境下,大家都使用MicroLib是精简c库)。

它可装入少量内存中,与嵌入式应用程序配合使用,且这些应用程序不在操作系统中运行。
MicroLib进行了高度优化以使代码变得很小,功能比标准c库少,不具备某些ISO c特性,部分库函数的运行速度也比较慢,如内存拷贝函数memcpy()。
MicroLib与标准c库之间的主要差异在网上的摘抄:

它的库函数的命名规则基本会仿照标准c库,只是修改了部分内部逻辑,使之在嵌入式中更高效。
MicroLib 不符合 ISO C 库标准。 不支持某些 ISO 特性,并且其他特性具有的功能也较少。
MicroLib 不符合 IEEE 754 二进制浮点算法标准。
MicroLib 进行了高度优化以使代码变得很小。
无法对区域设置进行配置。 标准 C 区域设置是唯一可用的区域设置。
不能将 main() 声明为使用参数,并且不能返回内容。
不支持 stdio,但未缓冲的 stdin、stdout 和 stderr 除外。
MicroLib对 C99 函数提供有限的支持。
MicroLib不支持操作系统函数。
MicroLib不支持与位置无关的代码。
MicroLib不提供互斥锁来防止非线程安全的代码。
MicroLib不支持宽字符或多字节字符串。
与stdlib不同,MicroLib不支持可选择的单或双区内存模型。MicroLib只提供双区内存模型,即单独的堆栈和堆区。
4.3 用MicroLIB库实现printf重映射到串口
当我们在keil中要使用MicroLib库的时候,就需要进行下面操作:

1.勾选Use MicroLIB选项

 

2.初始化串口,将printf要映射的那个串口进行初始化

1
USART0_INIT();

3.重定向fputc函数,在工程的任意.c文件中,

在MicroLib的stdio.h中,fputc()函数的原型为

1
int fputc(int ch, FILE* stream)

此函数原本是将字符ch打印到文件指针stream所指向的文件流去的,现在我们不需要打印到文件流,而是打印到串口0。基于前面的代码编写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//需要包含头文件stdio.h
#include <stdio.h>
/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
    usart_data_transmit(USART0, (uint8_t)ch);  //调用串口发送函数
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));//等待发送完成
    return ch;
}
#if 0
 int fgetc(FILE *f) 
 {
    int ch;
    //数据接收
    while (usart_flag_get(USART0, USART_FLAG_RBNE) == RESET);
    ch = usart_data_receive(USART0); //获取字符
      
     return ch;
 }
#endif

  4.现在就可以在工程代码中使用printf()函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main(void)
{
    USART0_INIT();
     
    printf("\r\nGD32F450 printf output\r\n");
     
    while (1){
     
    };     
    return 0;
}

  通过程序可知,使用MicroLIB库只需要实现fputc()函数,更为简单。

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多