S3C2440—6.串口的printf实现
文章目录
一.框架
在之前STM32的学习中,我在串口输出调试信息的时候,经常采用printf()函数作为串口输出函数,这样不仅方便调试而且代码易读。
在S3C2440的学习中,对于UART同样需要对串口输出信息进行调试,那么在这里可不可以使用printf函数呢?
当然是可以的,不过相比于STM32中简单的配置,S3C2440中对printf的使用要深入到printf函数的原理,将printf函数进行彻头彻尾的刨析,然后结合底层的UART输出,使用printf打印调试信息,其实现的前提是:
- UART底层的putchar函数
- 了解printf函数中,固定参数和可变参数
- 会利用固定参数以及格式字符推出可变参数
- 根据不同的格式字符输出可变参数
其中,UART底层的putchar函数在上一篇博客中以及实现了,这里主要介绍对printf函数的刨析,以及对可变参数的推导。
对printf函数的刨析,由my_printf.c实现,由my_printf.c调用UART底层的putchar函数即可实现UART的printf输出。
二.printf函数原理
2.1 printf的声明
在标准库<stdio.h>中定义了printf函数,其声明如下:
*int printf(const char format, …);
2.2 参数解读
可以看出printf的参数为:(const char *format, …)
其中:format代表固定参数,…代表可变参数,固定参数中含有格式字符
(听起来有点懵逼,下面解读一下参数的内容)
顾名思义,固定参数就是一串固定的字符串, *format就是字符串的首地址,根据首地址我们可以将字符串的全部内容输出。欸,可是固定参数中也有特殊的字符啊,那就是格式字符,格式字符用来说明可变参数的数据类型和个数,根据固定参数中的格式字符,可以将可变参数按照格式打印出来。**所以利用printf打印信息的主要步骤就是解析固定参数以及固定参数中的格式字符,将可变参数在格式字符位置输出,直到固定参数解析完毕!**这样就可以将参数中像表达的内容完整地打印出来。
格式字符:
例如:
printf("I'm %s, my ID is %d ,my score is %.2f !!!\n","Bob",25,98.2);
运行结果如下:
printf是从固定参数”I’m %s, my ID is %d ,my score is %.2f !!!\n”的首地址开始解析的,逐个输出字符,直到第一个格式字符%s,对应了后面可变参数的“Bob”字符串,在格式字符的位置上打印可变参数,然后继续解析固定参数继续打印字符,直到第二个格式字符%d,在%d的位置打印可变参数25,然后继续解析直到第三个格式字符%.2f,根据格式字符打印出98.20,然后继续解析,直到最后一个转义字符“\n”,结束了固定参数的解析,也完成了数据的打印输出。
没遇到%就直接输出,遇到%根据格式符前导码分情况处理。
2.3 如何得到可变参数的值
我们了解了参数组成,以及printf打印输出的流程,那么问题来了,固定参数的首地址是直接传进来的,那可变参数的地址怎么得到呢???
实际上,参数都存储在栈上的,而且固定参数和可变参数的地址是连续的,对!!!因为是连续的,所以我们就可以通过指针的移动和取值,由固定参数得到可变参数的地址,从而打印出可变参数。
下面有一段代码来解释获取可变参数的过程:
(参考自百问科技代码)
#include <stdio.h>
struct person{
char *name;
int age;
char score;
int id;
};
/*
*int printf(const char *format, ...);
*依据:x86平台,函数调用时参数传递是使用堆栈来实现的
*目的:将所有传入的参数全部打印出来
*/
int push_test(const char *format, ...)
{
char *p = (char *)&format;
int i;
struct person per;
char c;
double d;
printf("arg1 : %s\n",format);
p = p + sizeof(char *);
/*指针对连续空间操作时: 1) 取值 2)移动指针*/
i = *((int *)p);
p = p + sizeof(int);
printf("arg2 : %d\n",i);
/*指针对连续空间操作时: 1) 取值 2)移动指针*/
per = *((struct person *)p);
p = p + sizeof(struct person);
printf("arg3: .name = %s, .age = %d, .socre=%c .id=%d\n",\
per.name, per.age, per.score, per.id);
/*指针对连续空间操作时: 1) 取值 2)移动指针*/
c = *((char *)p);
p = p + ((sizeof(char) + 3) & ~3);
printf("arg4: %c\n",c);
/*指针对连续空间操作时: 1) 取值 2)移动指针*/
d = *((double *)p);
p = p + sizeof(double);
printf("arg5: %f\n",d);
return 0;
}
int main(void)
{
push_test("abcd",123,per,'c',2.79);
return 0;
}
这是已知可变参数情况下的输出,目的就是理解如何通过固定参数得到可变参数,通过栈空间的移动取值就可以!
2.4 解决变参的宏定义
在stdarg.h中,有解决变参问题的一些宏定义,改进后作注释如下:
/* 参数指针 */
typedef char * va_list;
/* 考虑到地址对齐,对齐大小为sizeof(int) */
/* 比如,如果sizeof(n)在1-4之间,那么_INTSIZEOF(n)=4;如果sizeof(n)在5-8之间,那么 _INTSIZEOF(n)=8。 */
/* & ~(sizeof(int) - 1)相当于将0~3掩盖掉 sizeof(int) - 1 相当于增加0~3 如此,就只取决于sizeof(n)的大小 */
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
/* 得到第一个可变参数的起始地址,传给ap */
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
/* 移动指针到下一个参数,取值 */
#define va_arg(ap,t) (*(t *)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t)))
/* 结束,防止ap成为野指针 */
#define va_end(ap) ( ap = (va_list)0 )
2.5 完成printf函数的封装
解决了变参问题后,剩下的就是对printf函数的封装了,封装引出的接口就是:outc()输出一个字符、outs()输出字符串,到时候只要将这俩个接口和UART的putchar()函数、puts()函数对应起来,就可以实现UART使用printf了。
printf输出打印的核心部分:输出固定参数及可变参数的函数
/*reference : int vprintf(const char *format, va_list ap); */
/* 输出固定参数及可变参数的函数 */
static int my_vprintf(const char *fmt, va_list ap)
{
char lead=' ';
int maxwidth=0;
for(; *fmt != '\0'; fmt++)
{
if (*fmt != '%')
{
outc(*fmt); //outc()就是输出一个字符
continue;
}
//format : %08d, %8d,%d,%u,%x,%f,%c,%s
fmt++;
if(*fmt == '0')
{
lead = '0';
fmt++;
}
lead=' ';
maxwidth=0;
while(*fmt >= '0' && *fmt <= '9')
{
maxwidth *=10;
maxwidth += (*fmt - '0');
fmt++;
}
switch (*fmt)
{
case 'd': out_num(va_arg(ap, int), 10,lead,maxwidth); break;
case 'o': out_num(va_arg(ap, unsigned int), 8,lead,maxwidth); break;
case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;
case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;
case 'c': outc(va_arg(ap, int )); break;
case 's': outs(va_arg(ap, char *)); break;
default:
outc(*fmt);
break;
}
}
return 0;
}
里面的out_num()函数是根据格式字符输出不同形式的数字,其函数定义如下:
static int out_num(long n, int base,char lead,int maxwidth)
{
unsigned long m=0;
char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
int count=0,i=0;
*--s = '\0';
if (n < 0)
m = -n;
else
m = n;
do
{
*--s = hex_tab[m%base];
count++;
}
while ((m /= base) != 0);
if( maxwidth && count < maxwidth)
{
for (i=maxwidth - count; i; i--)
*--s = lead;
}
if (n < 0)
*--s = '-';
return outs(s);
}
所以,printf函数的封装就如下:
int printf(const char *fmt, ...)
{
/* 参数指针 */
va_list ap;
/* 有固定参数得到起始地址 */
va_start(ap, fmt);
/* 输出打印 */
my_vprintf(fmt, ap);
/* 结束 */
va_end(ap);
return 0;
}
三.结合UART实现
上面我们知道,对printf函数的刨析,由my_printf.c实现,由my_printf.c调用UART底层的putchar函数即可实现UART的printf输出,调用接口就是outc()、outs()函数,只要如下设置就ok:
static int outc(int c)
{
putchar(c);
return 0;
}
static int outs (const char *s)
{
while (*s != '\0')
putchar(*s++);
return 0;
}
对UART的配置已经在之前的博客中有详细介绍了。
有问题可以q我,一起学习:2723808286
S3C2440—6.串口的printf实现的更多相关文章
- 通过串口利用printf函数输出数据
一.printf函数格式 printf函数具有强大的输出功能 %表示格式化字符串输出 目前printf支持以下格式的输出,例如: printf("%c",a);输出单个字符. pr ...
- 使用 VSCode 给STM32配置一个串口 printf 工程
使用 VSCode 给STM32配置一个串口 printf 工程 gcc 重定向 printf 和 keil 不一样. 文件准备 先从以前的工程中拷过一份串口的代码来,然后在 main 函数中初始化串 ...
- Smart210学习记录------linux串口驱动
转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=27025492&id=327609 一.核心数据结构 串口驱动有 ...
- keil中的串口调试:
keil中串口的虚拟调试信息在通过View-serial windows-#usart1/2/3/4/debug(printf)可以看到.当然也可以通过虚拟串口VSPD+串口调试助手在外部实现,方法如 ...
- 基于STM32F10x的串口(USART)输入输出编程
1 前言 STM32有强大的固件库,绝大部分函数都可以有库里面的函数组合编写.固件库可以到ST官网(www.st.com)上下载,也可以搜索“STM32 固件库 v3.5”下载到固件库.本文章就是基于 ...
- 第20章 USART—串口通讯
本章参考资料:<STM32F76xxx参考手册>USART章节. 学习本章时,配合<STM32F76xxx参考手册>USART章节一起阅读,效果会更佳,特别是涉及到寄存器说明的 ...
- printf行缓冲区的分析总结
最近在客户那调试串口的时候,read串口然后printf打印,单字符printf,发现没有输出,后来想起来printf这些标准输入输出函数也是属于标准C库glibc的, 这里就要区分一下标准库函数和系 ...
- STM32Cube IDE配置串口发送与接收
此项目源码下载地址:https://github.com/lizhiqiang0204/STM32CubeIDE_Uart 串口与中断配置如下 在生成的main函数中,添加开启串口接收中断 HAL_I ...
- linux内核调试技术之printk
原创博客:欢迎转载,转载请注明出处https://i.cnblogs.com/EditPosts.aspx?postid=6218383 1.简介(基于s3c2440 linux) 在内核调试技术之中 ...
随机推荐
- Python网页正文转换语音文件的操作方法
天气真的是越来越冷啦,有时候我们想翻看网页新闻,但是又冷的不想把手拿出来,移动鼠标翻看.这时候,是不是特别想电脑像讲故事一样,给我们念出来呢?人生苦短,我有python啊,试试用 Python 来朗读 ...
- 修改vcenter的Administrator@vsphere.local密码
vsphere 忘记vcenter的Administrator@vsphere.local密码的解决办法 1.cd c:\Program Files\VMware\Infrastructure\VM ...
- c语言:结果不理解
#include <stdio.h> int main() { int a;float b; scanf("a=%d,b=%f",&a,&b); pri ...
- Redis 6.0 新特性:带你 100% 掌握多线程模型
Redis 官方在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新特性,所以备受关注. 码老湿,提供了啥特性呀?知道了我能加薪么? 主要特性如下: 多线程处理网络 IO: 客户端缓存 ...
- 基于FPGA的图像镜像
图像镜像,一种较为常见的图像处理操作,分为水平镜像.垂直镜像.对角镜像.水平镜像即处理后的图像与原图像关于垂直线对称,垂直镜像为处理后的图像与 原图像关于水平线对称,对角镜像则关于对角线对称. 关于低 ...
- Grafana、Prometheus、mtail-日志监控
一:日志如何监控 在上一篇博客Grafana.Prometheus-监控平台中,简单了解了Grafana与Prometheus对项目做特定的监控打点,可视化的配置操作. 但是对于没有设置监控或者不容易 ...
- 打造一个window桌面应用:在线聊天对话机器人
大家好,我是辰哥~~~ 本文目标:打造一个window桌面应用:在线聊天对话机器人. 今天辰哥教大家做一个在线聊天对话机器人桌面应用,已经打包成exe可执行文件,读者可以直接拿来使用, 先上演示图 聊 ...
- ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁
前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...
- EasyUI学后总结第一集
1,创建Easyui组件-使用html还是使用js方式? 如果在创建Easyui组件的时候,组件再被更改,那么属于静态组件,对于静态组件不要使用js方式创建--会增加js代码量. 如果创建的Easyu ...
- 简单配置nginx反向代理,实现跨域请求
简单配置nginx去做反向代理,实现跨域请求 简单介绍nginx的nginx.conf最核心的配置,去做反向代理,实现跨域请求. 更多详细配置,参考nginx官方文档 先介绍几个nginx命令 打开n ...