基于STM32从零写操作系统系列---将printf指向串口输出

发布者:MagicGarden最新更新时间:2019-09-10 来源: eefocus关键字:STM32  操作系统系列  printf  串口输出 手机看文章 扫描二维码
随时随地手机看文章

为什么需要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 

关键字:STM32  操作系统系列  printf  串口输出 引用地址:基于STM32从零写操作系统系列---将printf指向串口输出

上一篇:基于STM32从零写操作系统系列---熟悉win+linux交叉编译环境
下一篇:嵌入式固件开发之二——直接操作STM32寄存器的LED点灯

推荐阅读最新更新时间:2024-11-12 10:24

STM32 PWM输出控制步进电机-3000转每分钟串口显示+电位器调速
用电位器控制步进电机转速,可以控制正反转,在电位器中点停止;越往左翻转越快,越往右正转越快。速度均匀,包含滤波。付完整代码。全部测试正常。 单片机源程序: #include led.h #include delay.h #include key.h #include sys.h #include usart.h #include timer.h #include adc.h int main(void) { u16 adcx; float temp; int a=3999; //int b=0 u16 led0pwmval=200; u8
[单片机]
STM32成长记之USART--232串口通信
此处介绍最简单的USART使用。 USART基本特性: ● 全双工的,异步通信 ● 分数波特率发生器系统 ─ 发送和接收共用的可编程波特率,最高达4.5Mbits/s ● 可编程数据字长度(8位或9位) ● 可配置的停止位-支持1或2个停止位 ● 检测标志 ─ 接收缓冲器满 ─ 发送缓冲器空 ─ 传输结束标志 ● 校验控制 ─ 发送校验位 ─ 对接收数据进行校验 ● 四个错误检测标志 ─ 溢出错误 ─ 噪音错误 ─ 帧错误 ─ 校验错误 ● 10个带标志的中断源 ─ CTS改变 ─ LIN断开符检测 ─ 发送数据寄存器空 ─ 发送完成 ─ 接收数据寄存器满 ─ 检测到总线为空闲 ─ 溢出错误 ─ 帧错误 ─ 噪
[单片机]
<font color='red'>STM32</font>成长记之USART--232<font color='red'>串口</font>通信
STM32单片机ADC的模拟看门狗的测试
ADC的模拟看门狗用于检查电压是否越界。他又上下两个边界,可分别在寄存器ADC_HTR和ADC_LTR中设置。库函数是使用ADC_AnalogWatchdogThresholdsConfig设置的,无论是常规通道还是注入通道,都非常简单 当模拟看门狗检测到电压高于上限或者低于下限时将会产生看门狗中断。捕获这个中断,可以做出一些应对措施。 数据手册上特别之处的一个东西:模拟看门狗说使用的比较数据与ADC_CR2寄存器中设置的数据对齐方式无关。看门狗比较是在数据对齐之前完成的。先进行看门狗比较,再将数据放入ADC_DR数据寄存器。 在ST的库中,只有简单的三个与看门狗相关的函数: void ADC_AnalogWatchdogCm
[单片机]
<font color='red'>STM32</font>单片机ADC的模拟看门狗的测试
STM32中FATFS文件系统常用API函数的理解
首先,第一次在CSDN上面开通博客,这也算是我的第一篇博文吧,写的不好的地方还请大家不吝赐教 ,笔者现大二在校学生,之所以会选择在这里使用博客,是在一位嵌入式大虾的渲染下,和小伙伴一起分享学习的话,我相信可以学的更多。 废话不多说,进入主题。 首先,FATFS 是一个完全免费开源的 FAT 文件系统模块,专门为小型的嵌入式系统而设计的,使用标准的C语言编写,具有很强的独立性,可以轻松的移植到8位,16位,及其我正在用的32位ARM系列的STM32上面。即FATFS是可裁剪的文件系统。这点,尤为重要。 FATFS模块的层次结构图如下,这里,参考了部分正点原子的资料,感谢原子大大 其中底层接口,包括存储媒介读/写接口(dis
[单片机]
对<font color='red'>STM32</font>中FATFS文件系统常用API函数的理解
stm32--独立看门狗使用
1、基本介绍 包含有两个看门狗,独立看门狗:IWDG 窗口看门狗:WWDG 用来检测由于软件错误导致的故障。 看门狗由VDD电压与供电,在停止和待机模式下仍能工作 2、看门狗的原理 我们可以键看门狗理解为一个递减计数器,在看门狗已经激活的状态下,如果,计数器递减到0,那么系统就会产生复位。 如果,计数器在递减到零之前,重新刷新了数值(称为“喂狗”),此时,系统就不会产生复位。 3、结构框图 LSI:内部时钟30k-60k , 适合于精度低的情况 计数器的最大值:0xfff - 重载寄存器的数值:装着我们的递减的初值 40k/(4*2**pre),pre=0~6 都可以 4、独立看门
[单片机]
stm32--独立看门狗使用
2.1、STM32怎么创建工程:如何创建第一个工程模板(基于固件库)
一、 首先在电脑的某个目录下创建一个空文件夹用来存放工程文件。创建好后在该文件目录下分别创建CORE 、FWLIB、OBJ、SYSTEM、USER等五个子文件夹。至于这些文件夹名字,实际上是可以任取的,我们这样取名只是为了行业规范。 二、打开keil软件,点击 Keil 的菜单: Project – New Uvision Project,保存时将目录定位到自己创建的文件下的USER子目录下: 三、接下来会出现一个选择 Device 的界面,就是选择我们的芯片型号,我们选择STM232F407ZE(如果使用的是其他系列的芯片,选择相应的型号就可以了)特别注意: 一定要安装对应的器件支持包才会显示这些内容)。 这里是
[单片机]
2.1、<font color='red'>STM32</font>怎么创建工程:如何创建第一个工程模板(基于固件库)
STM32零基础入门详解-时钟篇
  STM32中使用任何一个外设都必须打开相应的时钟。在STM32中有5个时钟源可供用户选择:   1.HSI高速内部时钟,RC震荡器,频率为8MHz。   2.HSE高速外部时钟,右英/陶瓷谐振器,或着外部时钟源,4MHz-16MHz.   3.LSI内部低速时钟,RC震荡器频率为40Hz。   4.LSE外部低速时钟,接频率为32.768KHz的石英晶体。   5.PLL锁相环频输出,时钟源可选为HIS/2、HSE或HSE/2。倍频可选2-16倍,但其输出频率最大不能超过72MHz。   系统时钟SYSCLK,它是供STM32中绝大部分器件工作的时钟源,系统时钟可选择为PLL输出、HSI或者HSE。系统时钟的做大频率为72MH
[单片机]
STM32的ISP方式和IAP方式下载程序有什么区别
都可以通过串口来下载啊 ISP方式:需要将BOOT管脚配置成上电从System Memory启动,System Memory中有一段BOOTLOADER来接收串口来的数据,把它们烧写到FLASH中。 IAP方式:是用户自己写一段烧写程序,通过ISP或者仿真器事先烧写到用户FLASH中,使用时通过某种方式触发这段程序,再来从串口接收数据,然后烧到相应的FLASH中,不需要改变BOOT管脚配置。
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved