在大家使用keil或是iar开发stm32等arm芯片的时候,想来最不陌生的就是使用print通过串口输出一些数据,用来调试或是其他作用。但是要明确的是由于keil iar gcc 他们使用的标准C语言库虽然都遵循一个标准,但他们底层的函数实现方式都是不同的,那么在GCC中我们能否像在keil中一样重映射print的输出流到串口上呢?答案是肯定的。
/*
* libc_printf.c
*
* Created on: Dec 26, 2015
* Author: Yang
*
* 使用标准C库时,重映射printf等输出函数的文件
* 添加在工程内即可生效(切勿选择semihost功能)
*/
#include
//include "stm32f10x.h"
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重映射fputc函数,此函数为多个输出函数的基础函数
int fputc(int ch, FILE *f)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_SendData(USART1, (uint8_t) ch);
return ch;
}
在keil中的C库中,printf、scanf等输入输出流函数是通过fputc、fgetc来实现最底层操作的,所以我们只需要在我们的工程中重定义这两个函数的功能就可以实现printf、scanf等流函数的重映射。
我们来看看前几篇中提供的样例工程中的usart_stdio例程中的代码片段:
#include
#include
#include
/*
* To implement the STDIO functions you need to create
* the _read and _write functions and hook them to the
* USART you are using. This example also has a buffered
* read function for basic line editing.
*/
int _write(int fd, char *ptr, int len);
int _read(int fd, char *ptr, int len);
void get_buffered_line(void);
/*
* This is a pretty classic ring buffer for characters
*/
#define BUFLEN 127
static uint16_t start_ndx;
static uint16_t end_ndx;
static char buf[BUFLEN + 1];
#define buf_len ((end_ndx - start_ndx) % BUFLEN)
static inline int inc_ndx(int n) { return ((n + 1) % BUFLEN); }
static inline int dec_ndx(int n) { return (((n + BUFLEN) - 1) % BUFLEN); }
/* back up the cursor one space */
static inline void back_up(void)
{
end_ndx = dec_ndx(end_ndx);
usart_send_blocking(USART1, '\010');
usart_send_blocking(USART1, ' ');
usart_send_blocking(USART1, '\010');
}
/*
* A buffered line editing function.
*/
void get_buffered_line(void)
{
char c;
if (start_ndx != end_ndx)
{
return;
}
while (1)
{
c = usart_recv_blocking(USART1);
if (c == '\r')
{
buf[end_ndx] = '\n';
end_ndx = inc_ndx(end_ndx);
buf[end_ndx] = '\0';
usart_send_blocking(USART1, '\r');
usart_send_blocking(USART1, '\n');
return;
}
/* or DEL erase a character */
if ((c == '\010') || (c == '\177'))
{
if (buf_len == 0)
{
usart_send_blocking(USART1, '\a');
}
else
{
back_up();
}
/* erases a word */
}
else if (c == 0x17)
{
while ((buf_len > 0) &&
(!(isspace((int) buf[end_ndx]))))
{
back_up();
}
/* erases the line */
}
else if (c == 0x15)
{
while (buf_len > 0)
{
back_up();
}
/* Non-editing character so insert it */
}
else
{
if (buf_len == (BUFLEN - 1))
{
usart_send_blocking(USART1, '\a');
}
else
{
buf[end_ndx] = c;
end_ndx = inc_ndx(end_ndx);
usart_send_blocking(USART1, c);
}
}
}
}
/*
* Called by libc stdio fwrite functions
*/
int _write(int fd, char *ptr, int len)
{
int i = 0;
/*
* write "len" of char from "ptr" to file id "fd"
* Return number of char written.
*
* Only work for STDOUT, STDIN, and STDERR
*/
if (fd > 2)
{
return -1;
}
while (*ptr && (i < len))
{
usart_send_blocking(USART1, *ptr);
if (*ptr == '\n')
{
usart_send_blocking(USART1, '\r');
}
i++;
ptr++;
}
return i;
}
/*
* Called by the libc stdio fread fucntions
*
* Implements a buffered read with line editing.
*/
int _read(int fd, char *ptr, int len)
{
int my_len;
if (fd > 2)
{
return -1;
}
get_buffered_line();
my_len = 0;
while ((buf_len > 0) && (len > 0))
{
*ptr++ = buf[start_ndx];
start_ndx = inc_ndx(start_ndx);
my_len++;
len--;
}
return my_len; /* return the length we got */
}
这个文件因为实现了scanf的功能同时还带有在串口上终端回显并支持backspace键所以显得有些长,我们来将其中的实现printf重映射的片段取出:
#include
#include
int _write(int fd, char *ptr, int len)
{
int i = 0;
/*
* write "len" of char from "ptr" to file id "fd"
* Return number of char written.
*
* Only work for STDOUT, STDIN, and STDERR
*/
if (fd > 2)
{
return -1;
}
while (*ptr && (i < len))
{
usart_send_blocking(USART1, *ptr);
if (*ptr == '\n')
{
usart_send_blocking(USART1, '\r');
}
i++;
ptr++;
}
return i;
}
与keil C库类似GNU C库下的流函数底层是通过_read、_write函数实现的,我们只要在工程中将他们重新定义就可以实现重映射的功能了。
差点忘了最重要的。我们在使用GNU的printf时,一定要记住在发送的内容后添加 \n或者在printf后使用fflush(stdout),来立即刷新输出流。否则printf不会输出任何数据,而且会被后来的正确发送的printf数据覆盖。这是由于printf的数据流在扫描到 \n以前会被保存在缓存中,直到 \n出现或是fflush(stdout)强制刷新才会输出数据,如果我们在printf数据的末尾不加入\n或fflush(stdout),这个printf数据就不会被发送出去,而且在新的printf语句也会重写printf的缓存内容,使得新的printf语句不会附带之前的内容一起输出,从而造成上一条错误的printf内容不显示且丢失。
/*methord1*/
printf("Enter the delay(ms) constant for blink : ");
fflush(stdout);
/*methord2*/
printf("Error: expected a delay > 0\n");
这里需要大家明白的是,GNU C 与 KEIL C 下的标准库函数实际上都是各个不同的机构组织编写的,虽然他们符合不同时期的C标准,如C89、C99等,那也只是用户层的API相同(同时要明白他们这些标准库是属于编译器的一部分的,就储存在编译器路径下的lib文件夹中)。虽然上层被调用的标准C函数相同,但是他们各有各的实现方式,他们在底层实现是可能完全不同的样子。所以在我们更换工具链后,一定要注意自己工程中的代码不一定会适应新的工具链开发环境。