cstring转char*函数_STM32学习笔记(4):USART串口(可变参函数、打印函数实现)...

f03eaab86a56f2c9be7c0f94e6564207.png

前言

如果仅仅是用串口发送和接收数据的话,那操作特别简单——串口初始化后调用对应的库打印函数就行了。我个人认为可以借串口来提高C语言的水平。所以,今天这篇文章,先简略介绍一下设备之间的通信方式,再对C语言中可变参函数的实现做一个讲解,最后实现一个可打印格式化字符串的、类似C库函数printf的用户函数(只实现了比较常用的一些格式化类型)。

一、通信方式简述

1、并行通信

相信学习过微机原理的朋友对并行通信并不陌生。没错,微机原理教材里8255A那块经典的芯片就是并行通信芯片。并行通信是指每次传送数据时按字节为单位进行传送的。由于每次传送的数据量很大,这就使得并行通信方式传输速度很快;但是同时这也使得系统需要更多的数据线,导致系统成本增加;并且各数据线间有可能会互相干扰(类比一下多联装的战列舰舰炮)。考虑到当今技术的发展,串行通信方式的通信速度也很快了,并且成本低、线间干扰少,所以现在很多高速设备基本上采用的都是串行通信方式。

2、串行通信

串行通信是按位传输数据的——也就是说,每次只传送一个二进制位(bit)的数据。经典的串行通信芯片有8250、8251这些。串行通信需要的数据线数量少,并且每次只传送一位——这意味着线间干扰的减少(类比一下坦克火炮),所以串行通信的应用越来越广泛了。

而串口的含义就是“串行通信接口”。

3、串行通信接口的三种工作方式

根据发送端和接收端的能力不同,串行接口一共可分为单工、半双工、全双工的三种工作方式。现假设有A、B两个端口。单工是指信息只能由A端口至B端口,传输方向不可更改;半双工是指A、B均具有发送和接收的能力,也就是说信息可以双向传递了,但是任意一端不可以同时发送和接收数据;全双工是指A、B均具有发送和接收的能力,并且任意一端可以同时发送和接收数据。

4、串行通信的两种类别

按照发送端和接收端的时钟,可以把串行通信分为两个类型——串行同步通信和串行异步通信。同步通信是指发送端和接收端的时钟严格一致(频率和相位),它的通信效率很高,但是为了满足时钟的要求因此成本也较高。异步通信是指以起始位、校验位(可选)和停止位以及数据字节组成一帧信息,再进行收发的串行通信方式。串行同步通信和串行异步通信都可以使用奇偶校验;异步通信一般采用偶校验,同步通信一般采用奇校验或者CRC(循环冗余校验码)进行校验。

二、C语言可变参函数实现

1、C99风格

C99可以利用变参宏__VA_ARGS__来实现带变参的函数。例:

#define FUN(...) function(__VA_ARGS__) 

接下来就可以通过调用宏函数FUN()来实现可变参数的效果了。

2、K&R C以及ANSI C风格

这种风格的实现说起来还是蛮容易的。我们在声明这个可变参函数的时候只要把可变参部分写成三个省略号就行了,不过一定要先引用头文件stdarg.h哟。

#include 

那么我们下一步就是获取这些甚至连名字都没有的参数的值了。连名字都没有我们咋办?幸好C语言给我们提供了一些特定的变量类型和对应的函数。

首先,需要先声明一个va_list类型的变量,这个变量实际上是可变参数表的一个指针(简称“参数指针”);

va_list parameter;

其次,我们要调用va_start函数,这个函数有两个参数——参数指针变量名,最后一个非可变形参的形参名;

va_start(parameter, a);

然后,我们就可以获取参数值了。调用va_arg函数,这个函数有两个参数——参数指针变量名和当前可变参数类型;调用后将返回一个可变参数,并自动将参数指针指向后一个可变参数存放的区域;

int d = va_arg(parameter, int);

最后,在我们退出这个函数前,还需要调用va_end函数(仅有一个参数,为参数指针变量名)来释放变参的空间及进行其他清理工作。

va_end(parameter);

PS:上面的过程是不是有点像内存分配的过程哈哈哈

3、一些猜测

因为上述过程用到了“参数指针”,而学过x86汇编的朋友应该知道高级函数的参数在汇编层面是靠一个栈来进行操作的——基于这些,我对于上述获取变参值的原理做出一些猜测:参数指针会记录第一个可变参数的地址;当其他参数进入CPU专门用于保存函数参数的栈时,参数指针不动,栈顶指针自减;调用va_arg函数时将参数指针指向的栈存储单元的内容弹出,再根据指定的类型进行处理后返回,此时参数指针自减;当参数指针和栈顶指针相等时,所有可变参数均弹出栈,完成一次取出全部可变参数的过程。

三、STM32初始化USART串口的流程

这部分打字有点难,我传个图吧,清晰明了。

83e70ff2debf7e11979cdd9b1ef573cf.png

四、程序代码

1、USART初始化代码

这次只使用USART1,对应的Tx(发送端)为PA9,Rx(接收端为)PA10。该初始化函数接受串口的波特率为参数,具体初始化内容详见代码:

void 

2、十进制整数转字符串函数

这个函数主要是为了格式化字符串中的%d服务的。该函数接收一个十进制整数,返回一个字符指针(字符串首地址)。

进入函数后,大致的逻辑是:先判断整数是否为0,若为0则置返回“0”;再判断整数正负,若为负则先将整数取反,再置正负标志位flag为1,将两个数组首元素置为'-'(负号);接下来进入正整数转换环节。由于我们事先不知道整数共几位,因此采用变量length记录整数的位数;同时,我们每取出整数从右往左数的一位就存入temp数组中(按此顺序:个位、十位、百位、千位...),之后temp中存放了该整数的逆序字符串。最后,我们将该字符串进行反转,存入reserve中,再将s指向reserve即可。

在函数内部,声明了temp和reserve两个字符数组。其中,temp就是用于存放逆序字符串的;reserve是用于存放最后正确的字符串;MAX_LEN为宏定义,限制了最大可转换的整数的范围。

char

3、自定义串口打印函数

该变参函数采用的是ANSI C风格实现的。非可变参数为需打印的格式化字符串。在函数内部,随着base字符指针的增加,if-else if-else会判断是否出现特殊字符;如若出现特殊字符,如和%号,会继续判断该符号后的字符,再根据结果进行相应操作。

void 

五、实验结果

左边是测试用例,右边是串口助手上查看到的内容。

8e3224e520f61f18ec53b72b4b871ce5.png

六、结语

一开始想实现位数无限制的整数-字符串函数,所以全程采用字符指针;结果发现有时候结果对有时候不对,这太难受了呀。检查了一遍,发现我的指针没分配内存就解引用了,运气好的时候指针指在数据区,程序没问题;运气不好就指在一些奇怪的地方了。。。现在还没学到ARM Cortex-M3的内存管理,等以后学到了再改进~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值