历史上的今天
返回首页

历史上的今天

今天是:2024年09月10日(星期二)

正在发生

2019年09月10日 | 基于STM32从零写操作系统系列---将printf指向串口输出

2019-09-10 来源:eefocus

为什么需要printf?

首先,这个printf不是标准C中的printf,这个printf是自己参考标准库实现的。只是简单地完成了打印输出int,long long int, unsigned int, unsigned long long int, float, double和十六进制数等功能。主要用于在以后的学习中,输出变量、寄存器等的数据,便于调试程序。


1.函数调用中的参数传递

根据《Procedure Call Standard for the ARM ® Architecture》(文章结尾有下载分享)这个文档可知,标准规定在寄存器(r0-r3)和堆栈中传递参数。对于采用少量参数的子程序,仅使用寄存器,大大减少了调用的开销。还有就是,char,short,int这些类型的数据入栈时,会占用4字节的空间;long long int,double等的8字节数据入栈时,只会放置到8字节对齐的地址上。下面通过反汇编查看参数传递的过程:


C语言调用过程:

反汇编:

 先了解test_func1函数的参数(long long int a1, ...)中的“...”省略号,它表示这是一个可变参数列表。用于表示将来调用该函数时,可能会传递除参数a1以外的一个、两个或多个的参数给test_func1函数。那么如何获取可变参数列表中的参数呢?经过上面的标准文档说明和反汇编代码的分析,然后参照网上的一些分享,用以下的方法获取可变参数列表:


自定义va_list类型,typedef char *va_list。其实就是一个指向char类型的指针,void类型的指针void *应该更合理(没试过)。va_list指针用于指向可变参数列表中的不同类型的参数。


定义宏va_start(ap,v),ap就是va_list类型的变量,v就是靠近可变参数列表左边的第一个参数(这里是a1参数);这个宏的目的就是用a1变量在栈中的地址初始化va_list类型的ap指针,让它指向可变参数列表中的第一个参数(这里是a2)。


定义宏va_arg(ap,t),ap就是va_list类型的变量,已通过va_start(ap,v)初始化;t就是要获取的参数的类型,如在这里要获取a2参数,就是va_arg(ap,int);这个宏的作用是首先用sizeof(t)判断要获取的参数的类型t的大小,如果是小于等于4字节,就按4字节大小在栈中取值,如果大于4字节(在这里就默认为8字节),就需要判断ap指针是否在8字节对齐的地址上,如果是就直接在当前位置取8字节数据,ap=ap+8指向下一个数据,如果不是,ap就需要ap=ap+4加4到达8字节对齐的地址上取8字节数据,ap=ap+8再加8指向下一个数据。


定义宏va_end(ap),ap就是va_list类型的变量,这个宏用于销毁ap指针,就是出于安全让指针指向0地址处(相当于NULL指针)。


定义宏_INTSIZEOF(n),n是数据类型,源于计算4字节对齐。


通过定义了上面的宏,我们就可以在test_func1函数中使用这些宏去获取可变参数列表中的参数了。用法如下:


 2.printf实现

printf的实现就是需要用到可变参数列表,定义好上面的宏后,就需要开始写如何格式化输出信息了。所谓的格式化,可以简单理解为在一串字符串中使用占位符表示将要输出的数据,如“a = %drn”,%d就相当于占位符,表示这个位置将用一个十进制有符号整型数据(int)来代替。


怎么实现呢?其实就是通过读取格式化字符串中的每个字符,当读取到%百分号时,再读取%百分号的下一个字符,判断是什么字符,如‘d’这个字符表示将数据转换为十进制后输出。本次实验的printf只实现了一下几种格式输出:


 1.十进制整型输出(包括d,u,ld,lu)

 

首先就是就是计算这个十进制数有几位,如123,很明显有3位,代码实现如下:

“/”斜杠表示求除法中的商,如123除以10的商为12,余数为3


例如,s32_tmp = 123;第一次计算,123除以10的商为12,即s32_tmp = 12,count = 1;第二次计算,12除以10的商为1,即s32_tmp = 1,count = 2;第三次计算,1除以10的商为0,即s32_tmp = 0,count = 3。此时s32_tmp=0,退出while循环。求得count=3,即表示123这个数有3位。


然后,从高位到低位输出十进制数,代码如下:


 “%”百分号表示除法中求余数,如123除以10的商为12,余数为3


pow_10()这个函数用于求10的n次方,如pow_10(2)返回10的2次方100的值。这里需要注意pow_10()的返回值定义为long long int类型,否则在格式化长整型(ld,lu)时会出错。


myputc(),用于串口输出一个字符。


例如,s32_tmp = 123;输出第一个字符,123除以10的2次方,商为1,余数23,即c = 1,s32_tmp = 23,c + ‘0’表示1加0的ascii码0x30,就是1的ascii码0x31,然后串口输出0x31,这会在串口调试助手中显示字符1。以后的输出也是相似的,直到count为0,退出while循环。


注意,如果s32_tmp = -123,求得的c的值也是负的,在myputc()中就需要用‘0’-c,才能输出正确的字符。


2.十六进制输出


输出十六进制与输出十进制差不多,只是一个除以16,一个除以10。代码如下:

3.浮点输出


将浮点数分成整数部分和小数部分,整数部分的处理如上面说明的;小数部分通过将小数乘以10,再强制类型转换为long long int类型(这里需要小心强制类型转换后,数据的变化;由于float(例外,转换后为64位)和double都是64位,刚开始是转换为char类型的,后来成就出错了,应该是符号位在转换时改变了,导致出错),如,-0.1234乘以10得-1.234,转换后为-1。负浮点数输出代码实现如下:


 4.回车,换行


3.串口字符输出函数

如何初始化串口,请看基于STM32从零写操作系统系列---基于寄存器写串口驱动,这里有详细的步骤。或参考文章结尾分享的源代码,会有所不同,但原理一样。


字符输出函数代码实现如下:


4.效果

 

5.代码编译

这里解释一些自己定义的编译指令:


printf功能,我是通过编译成库来提供的,所以首先要编译库命令,在项目根目录printf_proj输入make mylib编译库

清除库的.o文件,make clean_libobj

make编译项目

make all_clean,用于清除所有编译后得到的文件,包括库

make clean,清除所有编译后得到的文件,除lib文件夹下的文件

6.小结 

printf的功能基本实现了,代码比较粗糙,还可以进行修改;实现的方法,我就想到这种,如有其它好方法请介绍给我!!下面有源代码分享和arm文档分享,以及串口调试工具。


源代码包文件名:printf_proj.zip


百度云分享:


链接:https://pan.baidu.com/s/1DlzYMo8oZsnF9ammJuuZoQ 

提取码:dc5h 

推荐阅读

史海拾趣

ENSIGN公司的发展小趣事

ENSIGN不仅局限于照明领域,还积极向能源服务领域拓展。近年来,ENSIGN通过技术创新和战略合作,为客户提供定制化的电源解决方案。例如,在2009年,ENSIGN建成了新的工厂,并与多家本地供应、装配和测试组织建立了合作关系,以提供更高质量、更可靠的产品和服务。

Adaptive Interconnect Electronics, Inc. [AIE]公司的发展小趣事

随着电子技术的飞速发展,AIE公司不断投入研发力量,推出了一系列具有创新性的测试配件产品。其中,AIE金属探头以其高精度、高稳定性的特点,受到了市场的广泛认可。此外,AIE还针对汽车行业推出了ADK汽车诊断试剂盒,为汽车维修和诊断提供了便捷、高效的解决方案。这些技术创新和产品升级不仅提升了AIE的市场地位,也推动了电子测试技术的不断进步。

General Cable公司的发展小趣事
由于时钟频率是载波频率的2倍,因此需要根据选定的载波频率计算时钟频率。例如,若载波频率为38kHz,则时钟频率应为76kHz。
Elite Semiconductor Products Inc公司的发展小趣事

随着技术的不断进步和市场的不断变化,Elite意识到单一市场已经无法满足公司的发展需求。因此,公司开始积极拓展国际市场,寻求更广阔的发展空间。在海外市场拓展过程中,Elite注重了解当地市场的需求和文化特点,制定针对性的市场策略。同时,公司还积极与当地企业建立合作关系,共同开拓市场。这些努力让Elite在国际市场上取得了不俗的成绩,也为公司的持续发展注入了新的动力。

广州奥松公司的发展小趣事

随着公司的不断发展,奥松电子陆续获得了多项荣誉资质,如“国家专精特新‘小巨人’企业”、“国家高新技术企业”等。这些荣誉的获得,不仅证明了公司在电子行业中的实力与地位,也为公司的品牌建设提供了有力的支撑。同时,这些荣誉也为公司的市场拓展和业务发展提供了更多的机遇和可能。

FutureWafer公司的发展小趣事

奥松电子拥有一支近200名工程师组成的专职研发团队,并配备了超过7000㎡的研发实验室。实验室中配置了步进式投影光刻机、双面光刻机等先进设备,为公司的产品研发提供了有力的支持。这些设备不仅满足了产品研发、小试以及中试各个阶段的试验条件,也为公司的技术创新提供了坚实的基础。

问答坊 | AI 解惑

硅技术引领汽车设计时代

摘  要:随着科技的不断向前发展,汽车电子化程度也越来越高,半导体技术也随之崛起。本文详尽的描述了硅技术的进步,微控制器在汽车应用上的发展以及硅产品在汽车网络所发挥的巨大潜力。最后作者希望汽车制造商和半导体生产商能够密切合作为 ...…

查看全部问答>

FOCS在煤调自动化系统中的应用

湘潭钢铁集团公司(以下简称湘钢)煤气调度系统在改造前使用的都是 型淘汰仪表,截至改造前安装的 /0 块仪表因!电缆等原因已全部瘫痪。“六五”以来湘钢经过几次大的改造煤气用户大量增加,煤气测量点由原来的 12 多点已增至近/22 点,显然现有的煤 ...…

查看全部问答>

建议一点

建议把资料共享区和技术交流区分开,通常下资料的都只是灌水,和技术交流混在一起感觉不便于聚集人气!…

查看全部问答>

支持移动硬盘需要加什么组件

usb host已经ok,u盘可以识别并挂载,但移动硬盘插上去没反应。 请问要支持移动硬盘,系统需要添加什么组件吗?…

查看全部问答>

usb键盘老是要拔掉重插

最近买了个usb接口键盘,老是要重插才能用,按照网上所说把设备管理-》usb room hub-》电源管理-》允许计算机关闭设别以节约电源停掉了。好像也不是qq冲突问题,在qq目录中找不到网上所说的的那两个文件。如果我把液晶显示其关掉,让机器开着, ...…

查看全部问答>

vxworks辅助时钟定时问题,急急急急

sysAuxClkRateSet(int rate)函数中,rate只能设成(2,4,8,16,32,64,128,..,1024等等),我想精确定时到1ms或5ms、10ms该怎么办,或者有其它方法吗,请大家帮忙!…

查看全部问答>

关于UCOS移植C51上的RAM分配的问题。

大家好。本人对于UCOS还是新手。想找UCOS的系统移植到C51上,遇到一些问题。希望大家来帮忙解决一下。 (在网上下载了一个移植实例有些看不太明白) 问题一:实例代码如下          ;定义重定位段     ...…

查看全部问答>

MC3486/MC3487使用问题

近日小弟准备用两组MC3486/MC3487实现数据通讯,但不知道MC3486/3487该怎么使用,接口电路怎么画?是否需要进行阻抗匹配?是否需要光藕隔离? 还望各位大哥小弟们赐教.谢谢~~~~~…

查看全部问答>

期待9B96

支持论坛 提供9B96板子  做USB通讯    天地华杰科技有限公司 [ 本帖最后由 bjmonsoon 于 2011-10-31 11:41 编辑 ]…

查看全部问答>