重定向STM32串口输出,C语言实现过程!

2 篇文章 0 订阅
1 篇文章 0 订阅


前言

提示:以前在学stm8的时候非常不解用printf串口打印为什么要加上重定义,它定义在了什么地方!为什么函数可以有不确定的参数(学了这么久,这也从没有见过啊!)在函数的参数中,今天来说明一下!


提示:文末有个连接,是我写的几个函数(类似于printf那个函数,不过不是改写标准流写的),是以正点原子的407的板子的demo。

结果一览:
在这里插入图片描述

一、printf是怎么实现的?

printf 我们虽然可以用,也会用!但是我们的编译器并没有给出源代码我们参考,怎么实现的我们平时也看不到!

那他低层到底做了什么呢!
可以参考一下这个文章

printf可变参数原理

具体原理是一样的,但是在keil编译器定义的方式略有所不同!
我去看了一下,发现keil有注释,但具体是怎么样定义的,封装了起来!

static char sprint_buf[1024];
int printf(char *fmt, ...)
{
        va_list args;
        int n;
        va_start(args, fmt);
        n = vsprintf(sprint_buf, fmt, args);
        va_end(args);
        write(stdout, sprint_buf, n);
        return n;
}

补充:我们C语言的参数地址传进来是有规律的,参数地址之间就是隔了他们类型的位数!这个点非常重要!我们要实现可变参数就是按照这个规律来的!

va_list >> 就是一个char *类型的指针,它用来存放临时的指向传进来参数的地址!
va_start >>就是获取首参数的地址!

va_end >> 按我的理解就是想防止野指针

这里面复杂的就是 vsprintf 函数了,我们在这里且先不谈!文末会有完整的源码的,我们现在知道最终的格式化输出在这里面即可!

在这里面的write是C语言中的一个函数,平时在Windows下,是直接输出到屏幕上的(称之为标准流)里面使用了标准流的文件指针(可以从write参数中可以看出(stdout))

而我们在单片机的时候,显然标准流不是我们的目标。我们在用它来输出到串口!所以我们对得重定向一下它的输出方向!

在这里插入图片描述
#pragma import…?

其实就是为了确保没有从 C 库链接使用半主机的函数,因为不使用半主机,标准 C 库 stdio.h 中有些使用半主机的函数要重新写 ,您必须为这些函数提供自己的实现 !(这种东西keil官方有解析的)
参看 STM32 串口 #pragma import(__use_no_semihosting)解析

#pragma import(__use_no_semihosting)  // 确保没有从 C 库链接使用半主机的函数
_sys_exit(int  x) //定义 _sys_exit() 以避免使用半主机模式
{
x = x;
}
struct __FILE  // 标准库需要的支持函数
{
int handle;
};
/* FILE is typedef ’ d in stdio.h. */
FILE __stdout;

而fput是printf最终输出的一个函数,在我们keil编译器库中应该就是弱类型的,我们对它进行我们的改写即可!

//重定义fputc函数
int fputc(int ch, FILE *f)
{
    while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
    USART1->DR = (u8) ch;
    return ch;
}

原理我们基本懂了那怎么自己实现一个类似于printf带可变参数的函数呢!

二、keil里面怎么配置?

1.引入库(头文件)

代码如下(示例):

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

2.功能的实现

代码如下(示例):

void u1_printf(char* fmt,...)  
{  
	u16 i,j;
	va_list ap;//定义了一个空指针
	memset(USART1_TX_BUF,'\0',USART1_MAX_SEND_LEN);
	va_start(ap,fmt);//
	vsprintf((char*)USART1_TX_BUF,fmt,ap);//格式化传进来的参数
	va_end(ap);
	i=strlen((const char*)USART1_TX_BUF);//此次发送数据的长度
	
	for(j=0;j<i;j++)//循环发送数据
	{
		//等待上次传输完成 
		while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
		//发送数据到串口1
		USART_SendData(USART1,(uint8_t)USART1_TX_BUF[j]);
	}
}

我们使用的是keil stdarg.h 里面定义好的宏!具体的原理跟上面一样的,只是定义的方式不同。

还是用的vsprintf 格式化!

memset 的作用是每次调用都会格式化数据,防止数据缓存的现象!

strlen 则是求数组的长度的,用来循环输出!需要注意的是strlen函数遇到 ‘\0’ 就会结束求长度!

还是很简单的,程序已经告诉我们了。


总结

按照这里所讲的方法,我们就可以定义出,不依赖于使用标准流来输出的方式,重新书写类似于printf这样强大的函数!

git仓库

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值