串口发送&串口发送+接收

下面这个是我们的 USB 转串口的模块啊,这里有个跳线帽,上节也说过,要插在 VCC 和 3V3 这两个引脚上,选择通信的 TTL 电平为 3.3 伏。然后通信引脚 TXD 和 RXD 要接在 STM32 的 PA9 和 PA10 口,为什么是这两个口呢?我们看一下引脚定义表啊。这里看到 USART1 的 TX 是 PA9, RX 是 PA10。我们计划用 USART1 进行通信,所以就选这两个脚。如果你用 USART2 或 3 的话,就要在这里找一下,接在 USART2 或 3 的对应脚上。然后是 TX 和 RX 交叉连接,这边一定要注意哈,别接错了。

这里 PA9 是 STM32 的 TX 发送,在接线图这里,接的就是串口模块的 RXD 接收。

然后串口模块的 TXD 发送要接在 STM32 的 PA10 就是 RX 接收

然后两个设备之间要把负极接在一起进行共地啊。一般多个系统之间互联都要进行共地啊,这样电平才能有高低的参考。最后这个串口模块和 STLink 都要插在电脑上,这样 STM32 和串口模块都有独立供电,所以这里通信的电源正极就不需要接了,只接三根线就行。
当然我们第一个代码只有 STM32 发送的所以通信线只有这个发送的有用,另一根线第一个代码没有用到哈,暂时可以不接。在我们下一个串口发送加接收的代码,两根通信线就都需要接了。所以我们把这两根通信线一起都接上吧,这样两个代码的接线图是一模一样的。那我们看一下面包板接一下电路啊。
自己电脑串口没有驱动的需要加驱动:








初始化的流程,看一下这个基本结构图。
第一步,开启时钟,把需要用的 USART 和 GPIO 的时钟打开。
第二步, GPIO 初始化,把 TX 配置成复用输出, RX 配置成输入啊。
第三步,配置 USART, 直接使用一个结构体就可以把这里所有的参数都配置好了。
第四步,如果你只需要发送的功能,就直接开启 USART, 初始化就结束了。如果你需要接收的功能,可能还需要配置中断,那就在开启 USART 之前,再加上 ITConfig 和 NVIC 的代码。就行了。

整个数字化流程非常中规中矩啊,每个函数大家应该都已经很熟悉了吧。得益于库函数的分装,内部各种细节问题就不需要我们再关心了。那数字化完成之后,如果要发送数据,调用一个发送函数就行了。如果要接收数据,就调用接收的函数。如果要获取发送和接收的状态,就调用获取标志位的函数,这就是 USART 外设的使用思路啊。
先看一下库函数里面都有哪些库函数吧。我们找一下 USART 点 h 的文件,打开,拖到最后。这里函数一眼看上去还是挺多的是吧?但是这里面很多都是那些增强功能和兼容其他协议的函数哈,我们都不会用到,真正常用的其实很少。
我们来看一下,DeInit 、Init、StructInit,这些不用说哈。

clockInit 、和 clockStructInit ,这两个函数是用来配置同步时钟输出的哈,包括时钟是不是要输出,时钟的极性啊,相位啊。等参数,因为参数也比较多哈,所以也是用结构体这种方式来配置的。需要时钟输出的话可以了解一下这两函数。

然后 CMD、 IT Config ,这些也不用说哈。

USART_ DMA_ CMD 这个可以开启 USART 到 DMA 的触发通道哈,需要用 DMA 的话可以了解一下。

然后下面一段设置地址唤醒 LIN 这些函数啊我们都不用。

最后这两个函数比较重要, send data 发送数据, receive data 接收数据。这两个函数在我们发送和接收的时候会用到哈。 send data 就是写 DR 寄存器, receive data 就是读 DR 寄存器。 DR 寄存器内部有四个寄存器哈,控制发送与接收。执行细节我们上一小节已经分析过了,程序上就非常简单了,写 DR 就是发送,读 DR 就是接收。至于怎么产生波形怎么判断输入,软件一概不管哈。

然后继续,这里一大大段函数,什么智能卡啊、 IrDA 啊,我们也都不用哈,不用看了。

最后四个标志位相关函数,这个也不用说哈。

所以总结下来我们用的函数非常少哈,而且都是常见函数。那我们回到初始化这里来,写下代码。
第一步开启时钟, RCC APB2 外设时钟控制, RCC APB2 外设 USART1 Enable,开启 USART1 的时钟。这里 USART1 是 APB2 的,其他的都是 APB1 的外设,注意一下。

然后我们还需要开启一下 GPIO 的时钟。刚才看引脚定义表啊, USART1 的 TX 是 PA9, RX 是 PA10。

所以复制下这一句,外设改成 GPIOA,开启 GPIOA 的时钟,这样时钟就开好了。

接着第二步,初始化 GPIO 引脚,这个代码我们从 LED 点 C 这里复制一下哈,

放到这里,稍微修改一下。首先引脚模式, TX 引脚是 USART 外设控制的输出脚,所以要选复用推挽输出, RX 引脚是 USART 外设数据输入脚,所以要选择输入模式。输入模式并不分什么普通输入、复用输入啊,根线只能有一个输出但可以有多个输入,所以输入角外设和 GPIO 都可以同时用。一般 RX 配置是浮空输入或者上拉输入哈,因为串口波形空闲状态是高电平,所以不使用下拉输入。这个引脚模式如果不清楚的话还是看一下手册哈, GPIO 那一节有个推荐的配置表可以参考一下。
那我们这里第一个程序目前只需要数据发送,所以只初始化 TX 就行了。引脚模式这里选择 AFPP, GPIO 引脚选择 9,下面初始化 GPIOA, 这样就是把 PA9 配置成复用推挽输出,供 USART1 的 TX 使用,那引脚就初始化好了。

接下来我们就可以直接初始化 USART 了。
接下来我们就可以直接初始化 USART 了 USART 一 lit USART 一第二个是结构体哈在上面 USART 一 lit type define USART 一 lit structure 然后把结构体的参数都引出来最后把结构体的地址放到初始化函数的第二个参数啊

看一下参数有关这个参数的解释哈我就不再一一跳转定义带大家看了大家如果不清楚的话可以自己再转到定义看一下步骤我们之前已经演示过很多遍了是吧这个解释我直接讲一下哈省下时间我们还有更重要的内容要讲好
那我们看一下第一个参数波特率在这里我们可以直接写一个波特率的数值哈比如 9600 这样就行了写完之后这个已例的函数类步会自动算好 9600 对应的分频系数然后写到 BRR 寄存器计算步骤我们上一小节的最后也讲过哈,所以这里我们就非常方便了需要什么波特率直接写就行了

然后第二个参数是硬件流控制,它的取值列表我们可以直接复制参数的名称哈,放到这里然后按 ctrl alt 加空格联想下代码,这里就可以看到它的取值列表,因为这个参数取值啊一般都是以参数名称作为前缀的所以复制下参数名称再开启代码提示,就可以很方便的选出参数了,如果没提示到合适的参数那就再跳转定义看看解释咋说啊,可能这个参数是需要你自己写而不是选的哈,那我们看一下这个参数的取值可以是 none 不使用流控,只用 CTS, 只用 RTS 或者 CTS RTS 都使用,我们不使用流控哈所以选择 none


接下来串口模式复制放到这里 CTRL ALT 加空格提示一下,可以看到这里可以选择 TX 发送模式和 RX 接收模式如果你既需要发送又需要接收那就用或符号把 TX 和 RX 或起来,就跟 GPIO_Pin_1 或 GPIO_Pin_2 这一样的用法,那我们这个程序只需要发送功能哈,所以就选择 TX 这一个参数就行了


然后是校验位同样的操作这里校验位可以选择 none 无校验 odd 奇校验 even 偶校验,我们不需要校验啊所以选择 none


然后停止位操作一下这个参数可以选择 0.5 1 1.5 2 ,我们选择一位停止位


最后是字长操作一下这个参数可以选择 8 位或 9 位,我们不需要校验哈所以字长就选择 8 位这样就行了


那到这里我们结构体参数的初始化就完成了,目前我们串口的配置是 9600 波特率 ,8 位字长,无校验一位停止位,无流控,只有发送模式
最后我们再来个 USART cmd USART1 enable 这样整个 USART 就配置完成了

来写一个发送数据的函数啊,调用这个函数就可以从 TX 引脚发送一个字节数据,void serial send byte 参数给个 unit 8 杠 t byte 在这里面我们需要调用串口的 send data 函数对吧,到 usart 点 c 里去找一下,复制这个 usart send data,然后放到这里,参数第一个给 usart1,第二个给 byte,把形参传递过去,

然后转到定义,看一下函数内部哈,这里 byte 传递给 data 这个变量之后,data 与上 0xFF 就是把无关的高位清 0 哈,然后直接赋值给 DR 寄存器,因为这是写入 DR,所以数据最终通向 TDR,发送数据寄存器,TDR 再传递给发送移位寄存器,最后一位一位的把数据移出到 TX 引脚,完成数据的发送,在这里可以看出哈,单片机是一种软件和硬件高度配合的产品,要想学好单片机,程序思维和电路分析都是必不可少的,

那我们回到这里,调用这一个函数数 Byte 变量就写入到 TDR 的,
写完之后我们还需要等待一下,等 TDR 的数据转移到移位寄存器了,我们才能放心哈,要不然如果数据还在 TDR 进行等待,我们再写入数据就会产生数据覆盖了,所以在发送之后我们还需要等待一下标志位,
在这里调用 USART GetFlagStatus 函数,参数 USART1,第二个跳转定义看一下啊,我们需要使用这个 TXE 发送数据寄存器空标志位,复制放到这里,然后我们要等待 TXE 置一哈,所以还是套一个 while 循环,如果 TXE 标志位等于等于 RESET,就一直循环哈,直到 SET 结束等待。


然后是标志位是否需要手动清除的问题,这个可以看一下手册哈。在状态寄存器这里有对 TXE 这一位的描述,当 TDR 寄存器中的数据被硬件转移到移位寄存器的时候,该位被硬件置位。如果 USART CR1 寄存器中的 TXE I E 为一,就是允许中断哈,则产生中断。最后有一句对 USART DR 的写操作,将该位清 0。所以说这里标志位置一之后不需要手动清 0 哈,当我们下一次再 send data 时,这个标志位会自动清 0。

那这样我们这个 send byte 函数就完成了,现在我们可以进行发送部分的测试啊,那把这两个函数放到头文件声明一下。

没有错误。然后到 main 点 c,先 include 的 serial 点 h。然后在 main 函数主循环前,先调用 serial 一例的,初始化 usart。然后再调用 serial send byte,发送一个字节数据啊,参数先随便给一个 0x41。这样程序的逻辑就是,上电后初始化串口,再调用串口发送一个 0x41。调用这个函数之后, TX 引脚就会产生一个 0x41 对应的波形,这个波形可以发送给其他支持串口的模块哈,也可以通过 USB 转串口模块发送到电脑端哈。我们本节主要是和电脑通信,所以是在电脑端接收数据。

那我们先下载试试看,编译。下载看一下,我们按一下复位键。这时可以看到串口模块的接收指灯闪了一下哈,说明有波形发过来了。那在电脑端我们需要打开串口助手这个软件哈,来查看接收到的数据。在我提供资料的工具软件目录下,有我提供的一个串口助手软件哈。那我们打开串口助手这个软件。

串口号选择我们刚才这个设备管理器里这个 COM 号啊,两个 COM 号保持一致。这里必须要插上串口,且串口是空闲状态才能扫描到对应的串口号哈。如果没有显示到串口号,那需要你先检查一下设备管理器,看是不是有设备,然后再关掉其他串口软件哈,防止串口号被占用了。

好那之后下面的参数,这里需要和你 STM32 二初始化这里的配置保持一致,双方约定的参数必须一致哈,要不然接搜就会解题错误。所以这里波特率选择 9600 数据位 8 位停止位 1 位校验位无。然后打开串口,这样电脑的串口就准备就绪了。

然后我们再按一次复位键,可以看到这个数据 41 就接收到了,

复位一次接收到一个 41 啊,这就是发送一个字节数据的现象。

然后大家注意到下面这里有一个接收模式,目前选择的是 hex 模式,也就是以原始数据的形式显示,发送 41 显示就是 41 本身。如果我们想显示下字符串怎么办呢?那就可以选择文本模式,这样就是以字符的形式显示,再发送试一下。可以看到目前 41 这个数据就解析成了字符 A。

我们来看一下 PPT 啊,最后这里就是不同数据模式的具体解释。第一条, hex 模式,有的地方也称作十六进制模式啊,或者二进制模式,这些称呼都是一个意思,它表示的是以原始数据的形式显示,收到什么数据就把这个数据本身显示出来哈。在这种模式下只能显示一个个的十六进制数,比如 41 42 7A 8B,不能显示文本哈,比如 hello world 和各种符号,比如感叹号逗号点。如果想显示文本,那就要对一个个的数据进行编码了,这就是文本模式或者叫字符模式。它是以原始数据编码后的形式显示。在这种模式下,每一个字节数据通过查找字符集编码成一个字符哈,
比如下面这个表展示的就是ASCII字符集。在这里面就可以看到 0X41 这个数据对应的就是大写字母 A。如果想显示小写字母呢,可以看到右边这里 0X61 对应的就是小写字母 a。如果想显示字符形式的数字,那在这边可以看到 0X30 到 0X39 对应的就是字符形式的 0 到 9。然后这里还有各种各样的标点符号,背后其实都是对应的一个字节的数据哈。另外最前面这里还有一些不可见字符,比如退格、换行、换页等等,这些字符可以用于控制文本的大打印,最后字符集的第一个字符原始数据是 0X00,对应字符是空字符,也就是保留位哈,不映射任何字符。一般这个 0 经常作为字符串的结束标作位,字符串遇到数据 0X00 之后就代表字符串结束了。这就是阿斯克码字符集的大概分布啊,大家在做数据和字符串的相互转换时需要查一下这个表。

阿斯克码是一种最简单最常用的字符集啊,如果想显示和存储汉字的话也得制定汉字的字符集,由于汉字比较多哈,所以这需要多个字节才能编码一个汉字。常用的汉字字符集有 GB 2312、 GBK、 GB 18030 等等。对于外国也有相应的字符集,比如欧洲的 ASCII 扩展码、日本的编码、韩国的编码等等。随着计算机的发展啊,全球互相通信,为了防止不同国家编码的不兼容现象,我们可以把所有国家的字符全部收入到一个统一的字符集,这就是 Unicode 的字符集。 Unicode 的最常用的传输形式是 UTF-8。有关字符编码的内容哈,大家可以自己再去网上搜一搜。如果编码不匹配,就会出现非常烦的乱码,这个得注意一下哈。
然后我们看一下右边这个模式图,这个图描述的是字符和数据在发送和接收的转换关系哈。比如最上面,发送 0X41 数据,发送的线路传输的就是 0X41。接收方如果以原始数据形式显示,就是 0X41。如果以字符显示,就是走下面这一路,通过字符集译码。这个译码的字符集就是我们刚才说的阿斯克码、 GBK、 UTF8 等等。通过字符集译码找到字符,然后显示字符 A。
在发送方也可以直接发送字符哈,比如发送字符 A, 这时它就会先从字符集找到 A 的数据进行编码,发现 A 对应的数据是0X 41,最终在线路中传输的必须是十六进制数哈,0X41。然后接收方可以选择查看原始数据0X 41,也可进行译码得到字符 A。 这就是字符和数据在发送接收过程中经历的变化。

那我们回到程序这边来。这里 send bat 也可以这样写。
这里 send byte 也可以这样写,单引号引一个字符 A,这样就是以字符的方式发送,它先会对 A 进行编码,找到数据 0x41,最终发送的其实还是 0x41 哈,

我们编一下下载看一下,这个现象其实和刚才是一样的,因为在线路中传输的数据本身 0x41 并没有改变哈,数据还是字符,只是数据的一种表现形式而已。好好,这就是发送一个字节数据或者发送一个字符的程序现象,

那接下来我们再封装一些函数模块啊,这些函数大家之后用串口肯定会经常用到,光有一个发送字节函数功能太简单了哈,满足不了需求,接下来我写的函数其实都是对 send byte 的封装,是纯软件的内容哈,
那首先是发送一个数组,比如我们有一个很大的数组,需要通串口发送到电脑,那就需要一个发送数组的函数,void Serial SendArray 参数第一个 UINT8 杠 T 星 array,这是一个 UINT8 杠 T 的指针类型哈,指向待发送数组的首地址,传递数组需要使用指针,这个我在指针教程里有讲哈。然后第二个参数 UINT16 杠 T length,由于数组无法判断是否结束,所以需要再传递一个 len 进来,len 类型我觉得 16 位的就够了哈,如果你觉得不够,可以定义为 32 位的,然后在函数里面先定义一个变量 INT16 杠 7I,然后来个 for i 等于 0,i 小于 len,i 加加,这样的话 for 循环就会执行 n 次,可以对 array 数据进行遍历啊,那在循环里就不断调用 Serial SendByte 发送 array i,这样就是依次取出数组 array 的每一项,然后通过 SendByte 进行发送了。这就是发送一个数组,

我们测试一下,放在头文件声明一下啊,

编一下。然后 main 函数这里先定义一个数组,UNIT8 杠 7,my array 里面的数据有 0x42,0x43,0x44,0x45,总共四个数据啊,实际用的话想几个就可以几个,注意别超出 unit 16 杠七数据的范围就行。之后 serial send array 第一个参数把数组名传进去,也就是数组的首地址啊,第二个参数指定传出四个字节,这样就可以把 my array 数组一次性发送过去了,非常方便啊。然后上面这里先注释掉啊,

好,我们编译一下,看一下现象。这里接收模式选择 hex 模式,send array 一般应用在 hex 模式下啊,清空一下,复位,目前收到了四个数据,就是我们 send array 发过来的,四二到四五没问题啊,那这里 send array 就测试完成了。

那这里 send array 就测试完成了接下来我们继续分装啊写一个 send string 函数写下 void serial send string 参数给一个 char 星 string 这里类型给 unsigned 8 杠 t 星也可以哈都是一样的效果然后由于字符串自带一个结束标志位所以就不需要再传递长度参数了,在里面执行逻辑和 send array 是非常类似的哈,先定义变量 INT8 杠 T I 再 for i 等于 0 这里循环条件就可以用结束标志位来判断了 string i 不等于 0 ,这里数据 0 刚才说过了哈对应空字符是字符串结束标志位,如果不等于 0 就是还没结束进行循环,如果等于 0 就是结束了停止循环,这里数据 0 也可以写成字符的形式哈就是单引号引起来反斜杠 0, 这也是空字符的转义字符表示形式和直接写 0 最终效果是一样的哈这里就以转义字符的形式来写吧最后 i 加加,然后在循环里还是一样 Serial send byte string i 将 string 字符串一个个取出来通过 send byte 发送这样就是发送字符串,

头文件声明一下

编译然后试一下在 main 函数这里可以写 Serial send string 字符串用双引号引起来哈然后给一个 hello world 感叹号,在写完这个字符串之后编译器会自动补上结束标志位啊,所以字符串的存储空间会比字符的个数大一,我们看一下使用现象啊,上面先注释掉

编译下载看一下这里 send string 一般应用在文本模式啊,那我们说一下可以看到这里显示的 hello world

这里如果要进行换行可以在这里用转义字符反斜杠 r 反斜杠 n 来执行换行的命令,这里注意需要用反斜杠 r 反斜杠 n 两个转义字符才能执行换行,这两个转义字符在 ASCII码表里也可以查到,反斜杠 r 就是 0D ,反斜杠 n 就是 0A ,都是不可见的控制字符啊

那我们下载看一下这样在每次打印之后就会执行一次换行命令

这就是 send string 函数接下来我们继续封装 send number 函数调用 send number 发送一个数字最终能在电脑显示字符串形式的数字哈
那写一下 void serial send number INT32 杠 T number INT8 杠 T nance number 类型给 32 位哈稍微大一些 ,Length类型 8 位就行,然后在函数里面我们需要把 number 的个位十位百位等等以十进制拆分开然后转换成字符数字对应的数据哈依次发送出去,怎么以十进制拆开呢可以这样来做比如有个数字是一二三四五取万位就是一二三四五除以一万对十取余等于一,取千位就是一二三四五除以一千等于十二哈再对十取余等于二,取百位一二三四五除以一百等于一百二十三再对十取余等于三,取十位就是除以十对十取余,取个位就是除以一对十取余,
总结下来取某一位就是数字除以十的 x 次方再对十取余,除以十的 x 次方就是把这一位的右边去掉哈对十取余就是把左边去掉,这就拆分数字的思路

所以我们先需要写个次方函数在这里写啊 unit32_t serial power 的 unit32_t T X ,unit32_t Y 这个函数的返回值等于 x 的 y 次方啊,内部逻辑就是unit32_t RESULT 等于一 ,while y 减减这就是循环 y 次,然后 result 乘等于 x ,result 累乘 y 次 x 就是 x 的 y 次方哈最后 return result 这,样就行了

那回到 send number 也是依次发送数字的每一位这个逻辑,哈首先定义 unit 八杠 t i 然后放循环 i 等于零, i 小于Length i 加加 循环 Length 次在里面发送字节哈 serial send byte 按照之前的公式取出 number 的每一位 number 除以 serial power 10 的 i 次方再对十取余,然后 i 这里遍历是从 0 开始的哈 10 的 0 次方是个位,第一个数据肯定不是个位了

所以方向要反过来 Length 减 i 再减一哈这里要再减一个一,

试一下假设 Length为 2 第一次循环 2-0-1 10 的一次方就是发送十位哈,第二次循环 2-1-1=0 10 的 0 次方就是发送个位,这样逻辑才是正确的,目前循环参数就会以十进制字从高位到低位依次发送当然最终我们要以字符的形式显示啊所以这里还要再加个偏移,可以看一下阿斯克码表啊字符 0 对应的数据是 0x30

所以这里还得加上一个 0x30 或者以字符的形式写就是单引号 0 这就是发送数字的逻辑

其实这两个函数 send string 和 send number 啊和 oled 的秀 string 和秀 number 是一样的逻辑哈可以对照 oled 的代码看看都是一样的
那我们测试一下先函数声明哈编一下

然后主函数这里上面注释掉,写一个 serial send number 12345 长度为 5

下载试一下复位可以看到目前显示文本形式的 12345

send string 配合 send number 就可以显示各种字符和数字的
当然这里 send number 默认是十进制显示啊,其他进制的可以参考一下 oled 的代码这里就不再演示了啊,那这些就是串口常用的模块函数啊发送一个字节,发送一个数组,发送字符串,发送字符形式的数字,
那最后再给大家介绍一下 printf 函数的移植方法是
使用 printf 之前我们需要打开工程选项把这个 use MicroLIB 勾上 ,MicroLIB 是 Keil 为嵌入式平台优化的一个精简库,我们等会要用的 printf 函数就可以用这个 MicroLIB 所以先勾上这个,

OK 然后我们还需要对 printf 进行重定向将 printf 函数打印的东西输出到串口上因为 printf 函数默认是输出到屏幕,我们单片机没有屏幕,所以要进行重定向,步骤就是在串口模块里最开始加上井号 INCLUDE STDIO.H

之后在这后面重写 f putchar 函数 int f put c int c 去 file 新 f 这是 f putchar 函数的原型哈,这个参数什么的按照我这样写就行哈不需要管那么,多然后在里面我们要把 f putchar 重定向到串口,就是 serial send byte c 去最后 return c 去这样就行了,那重定向 f putchar 跟 printf 有什么关系呢这是因为这个 f putchar 是 printf 函数的底层 ,printf 函数在打印的时候就是不断调用 f putchar 函数一个个打印的我们把 f putchar 函数重定向到了串口,那 printf 自然就输出到串口了哈, 好这样 printf 就移植好了

我们到主函数试一下上面注释掉哈这里直接写 print f 比如打印一个 number 等于百分号 d 杠 r 杠 n 逗号六六六

看一下编译这里有个警告啊说的是 print f 函数隐私声明,这一般是没包含头文件哈我们这里使用了 print F 但是这个文件之上没有定义或声明 Printf, 那我们可以在串口的头文件哈加上井号 include 的 s t d i o 点 h

串口头文件包含 s t d i o ,s t d i o 里面有 printf 的声明, main 点 c 又包含串口头文件,所以相当于 main 里面也有 printf 的声明了,我们再试一下没问题了哈
下载,可以看到 number 等于 666 print f

接下来我再介绍两种 printf 函数的移植方法,刚才这一种方法,printf 只能有一个哈,你重定向到串口一了,那串口二再用就没有了,如果多个串口都想用 printf 怎么办呢?这时就可以用 s printf,s printf 可以把格式化字符输出到一个字符串里,
所以这里可以先定义一个字符串 char string,一百长度给够哈,然后 s printf 第一个参数是打印输出的位置,我们指定打印到 string 之后就跟 printf 一样哈,复制一下目前这个格式化的字符串在 string 里,最后需要再来一个 siri send string,把字符串 string 通过串口发送出去,这样就完成了,因为 s printf 可以指定打印位置,不涉及重定向的东西,所以每个串口都可以使用 s printf 进行格式化打印。那看一下现象上面的注释掉哈,

编译下载看一下,这个现象和刚才是一样的。

那最后再介绍一种方法,就是你看 s printf 每次都得先定义字符串,再打印到字符串,再发送字符串,太麻烦了啊,我们要是能分装一下这个过程就再好不过了。所以第三种方法就是分装 s printf,由于 printf 这类函数比较特殊啊,它支持可变的参数,像我们之前写的函数,参数的个数都是固定的可变参数,这个执行起来比较复杂,如果想深入学习的话,可以百度搜索啊 C 语言可变参数学习一下,这里时间关系我就只能讲分装的步骤了啊,
首先在串口模块先添加头文件 include 的 stdarg,点 h,

然后在最后这里对 s printf 函数进行分装,void serial printf 参数给 char 星 format,这个参数用来接收格式化字符串啊,之后逗号再加三个点,这部分用来接收后面的可变参数列表,在函数里面首先定义输出的字符串 char string 100 ,注意接下来的部分就比较难了哈,
VA 杠 list A R G 第一个参数列表变量哈,VA 杠 list 是一个类型名,A R G 是变量名哈,然后 VA 杠 start A R G format,从 format 位置开始接收参数表哈,放在 A R G 里面,之后 S print f 打印位置是 string,格式化字符串是 format,参数表是 A R G 在这里 s printf 要改成 vs printf 啊,因为 s printf 只能接收直接写的参数,对这种分装格式要用 vs printf,之后 v a 杠 end arg 释放参数表,最后就是串口 send string 把 string 发送出去,这样就把 printf 这种可变参数的格式分装好了啊,

这里面出现了很多没见过的函数,如果没学过这种用法可能比较难理解啊,这个也没关系,知道这样移植就行了,如果学过了那其实是基本操作啊,大家也可以学学这种可变参数的用法,自己写其他函数的时候也可以用啊,还是非常高级的好,我们来测试一下,把这个函数放到头文件声明一下

编译看看,没问题哈,然后主函数这里调用 serial printf,里面的东西和上面这是一样的哈,这个注释掉试一下下载可以看到,这样也可以实现 printf 的功能哈。


好,那以上就是 printf 函数的移植方法了,最常见的是第一种哈,如果你有多个 printf 的需求,可以了解一下后两种方法。

最后我再讲一个显示汉字的操作方法哈。之前有人经常反映 printf 打印函数经常会乱码哈,这里给大家提供几种解决方案。目前我们这个汉字编码格式选的是 UTF 8,所以最终发送的串口汉字会以 UTF 8 的方式编码,最终串口助手也得选择 UTF 8 才能解码正确。

先说下 UTF 8 乱码的方案啊。
比如我这里写了个字符串你好世界,不过这样直接写汉字编译器有时候会报错啊,我在网上搜索了一下这里需要打开工程选项 C C 加加这里杂项控制栏写上杠杠 N O 杠 M U L T I B Y T E 杠 C H A R S 需要给编译器输入一个这样的参数啊,

注意别写错了然后 OK 这样编译没问题下载看看,在串口助手这里目前是乱码哈编码方式要选择 UFT8 复位一下这样汉字就没问题了

这是 UTF 八的解决方案,但是 UTF 八可能有些软件兼容性不好啊,所以第二种方式就是切换为 GB 二三幺二编码,在这里打开配置 Encoding 选择 GB 二三幺二这是汉字的编码方式啊,

OK 目前这个文件编码格式其实还是 UTF 八我们需要把汉字删掉,再把文件关掉再打开等字体变为这种宋体了编码格式才上改过来,

然后再写你好世界这样编码就是 GB2312 ,

然后下载试一下在串口助手这里选择 GBK 编码一般 Windows 软件默认就是 GBK 的编码哈 GBK 和 GB2312 一样都是中文的编码基本都是兼容的再复位可以看到这样也是没问题的

所以总结一下就是要么 Keil 和串口助手都选择 UTF8 且 keil 加上杠杠 no 杠 multi byte 杠 char s 参数
要么都使用 gb 开头的中文编码格式参数不用加的哈,
如果你已经有很多选好编码格式的工程了这里资料里我也提供了一个转码软件哈可以批量进行转码记得关闭文件的只读哈这个了解一下这就是汉字乱码的解决方案
对英文来说任何编码都是兼容的所以英文怎么选都不会乱码,好有关串口发送的代码到这里就结束了
串口接收:
接下来我们来学习一下串口的接收哈
这个工程先关掉接线这里呢和串口发送的是一样的哈我们已经把 TX 和 RX 的线都接好了然后回到工程目录复制一下串口发送的代码

我们在这个工程的基础上加上接收的代码,之前的这些先删掉哈,

然后初始化,这里加上接收的部分,首先是 GPIO 口,我们要使用 RX 的引脚,在引脚定义表里,我们知道 USART1 的 RX 复用在了 PA10 引脚,所以这里需要再初始化一下 PA10,

我们复制一下下面这部分啊,结构体定义这行不用复制啊,然后改一下引脚,改成 GPIOPin10 引脚模式可以选择浮空输入或上拉输入,那我就选择 GPIOMode IPU 上拉输入模式啊,下面初始化 GPIOA,这样 PA10 就初始化好了,

接着继续下面这些参数,大部分不用改,只需要改一下模式,这个参数后面加上一个或串口模式 IX,这样就是同时开启发送和接收的部分,如果你只需要接收,那就把前面这个 TX 去掉就行了,目前我们就同时开启啊,然后呢,串口接收的代码其实就配置差不多了哈,

对于串口接收来说,可以使用查询和中断两种方法啊,如果使用那初始化就结束了,如果使用中断,那还需要在这里开启中断配置 NVIC,像我们最开始演示的那个现象啊,使用查询的方法就可以完成,这里我就先演示一下查询,再演示试一下中断,
查询的流程是在主函数里不断判断 RXNE 标志位,如果置一了就说明收到数据了,那再调用 receive data 读取 DR 寄存器,这样就行了,我就在主函数直接演示一下哈,就不再分装了,
在 while 循环里直接 if USART1 get flag status USART1 一 USART1 flag RXNE 等于等于 SET,IF 成立就说明收到数据了,上面我们定义一个数据哈,INT8 杠 7 RXDATA,然后这里 RXDATA 等于 USART RECEIVE DATA USART1 目前接收到的一个字节数据就已经在 USART1 里了,接下来我们可以进行显示哈,OLED ShowHexNumber 一行一列 USART1Data 长度为 2。

然后还有一个清标志位的问题啊,我们看一下手册,这里有 RXNE 标志位的说明,当 RDR 移位,寄存器中的数据被转移到 USART DR 寄存器中,该位被硬件置位,如果 USART CR1 寄存器中的 RXNEIE 为一,则产生中断对 USART DR 的读操作,可以将该位清 0,这里读 DR 可以自动清 0 标志位,所以这里读完 DR 就不需要我们再清除标志位了,

那我们看一下这个代码的现象,编译没问题下载看一下,这里串口助手,我们需要在发送区里写入数据哈,发送模式可以选择 hex 模式或文本模式,hex 就是原始数据。文本模式首先要过一遍字符编码哈,我们选择 hex 模式,然后在这里写比如 41,在 hex 模式下这里只能写十六进制数哈,也就是 09 A F,不用写 0X 的哈,两个数为一组非法字符都将会被忽略哈,那我们点发送,在面包板上可以看到就收到了数据 41,

然后我们改一个,比如 AF 发送,这里显示 AF,

这就是查询方法的串口接收程序现象,如果你程序比较简单,查询方法是可以考虑的哈,那接下来我们再演示一下中断方法的程序。
如何使用中断呢?首先初始化这里我们要加上开启中断的代码,流程是第一步 USART ITConfig USART1 第二个参数看一下解释啊,

这里选择 RXNE 的中断复制一下,然后放到这里第三个参数 enable 开启 RXNE 标志位到 NVIC 的输出之后就是配置 NVIC 了,先分组 NVIC PriorityGroupConfig ,NVIC PriorityGroup 2 再初始化 NVIC 的 USART1 通道, NVIC InitTypeDef NVIC InitStruct 把结构体参数引出来啊,最后 NVIC ITConfig 把结构体地址放进去,参数第一个中断通道我们跳转一下搜索这个这里找一下啊,我们需要用这个 USART1 IRQn

复制放到这里第二个给以 level 之后优先级啊,先随便给个都给一,

好到这里 RXNE 标志位一旦置一了就会向 NVIC申请中断,之后我们可以在中断函数里接收数据啊,中断函数的名字我们看一下启动文件找一下复制这个 usart1 irq handler

,然后在这最下面写 void 粘贴弹出 void 在这里面老样子啊,先判断标志位 if USART GetITStatus USART1 USART IT RXNE 等于等于 SET,如果 RXNE 确实置一了就进 if 这里 if 最后要不要清除标志位呢,如果你读取了 DR 就可以自动清除,如果没读取 DR 就需要手动清除啊,我们这里直接给清一下吧,这个也不影响 USART1 ClearITPendingBit 参数和这一样啊,之后在这里面你可以直接读取 dr 执行一些操作啊,当然由于这个代码是在模块里,不太适合加入过多其他的代码啊,我是比较喜欢弄个分装,大家可以根据自己的需求来啊,那我就在最上面定义两个变量,一个是 unit 87 serial rx data,一个是 uin87 serial x flag 在这个函数里面,

我们先读取到模块的变量里,serial x data 等于 usart receive data usart1,读完之后置个自己的标志位哈,serial x flag 等于一,

然后对这个变量也分装一个 get 函数啊,大家也可以把这两个变量 extern 出去,都是可以的,
那这里 uin8 杠 t serial get x flag void 的,我们也想实现一个读后自动清除的功能啊,所以这里写 if serial x flag 等于等于一,就 serial x flag 等于零,再 return 一,否则 return 零,这就是 Get X Flag 函数,下面再来一个哈,丢进它八杠七 serial get X data 为的里面就直接 return serial X data。

好,到这里中断接收和变量的分装我们就完成了,其实这里我就是在中断里把数据进行了一次转存哈,最终还是要扫描查询这个 X Flag 来接收数据的,对于这种单字节接收来说可能转存一下意义不大哈,这里这样写主要是给大家演示一下中断接收的操作方法,另外也是为我们下一小节多字节数据包接收做一个铺垫哈,
那我们测试一下,先把这两个 get 函数放在头文件声明一下,

然后主函数这里就改成我们自己的函数,这里改成 if serial get flag 等于等于一,里面改成 x data 等于 serial get x data,这样代码的功能和之前演示的也是一样哈,我们就把最终的现象也完成一下,上面显示先来个 o l d show string 一行一列 x data 冒号下面位置改成八列。然后我们再加一个数据回传功能哈,这里加上 serial send byte x data,把接收到的这一字节数据回传到电脑,这就是目前程序的全部功能了哈,

我们编译下载试一下,发送接收都设置成 hex 模式,这里先给个 41 发送,屏幕显示 41 哈,串口接收回传的数据 41

改成 42 发送屏幕显示 42,串口接收回传的数据 42,

这就是这个程序的现象哈。本节课的内容也差不多结束了,
目前这里只支持一个字节的接收哈,这功能比较简单,但现在很多模块都需要回传大量数据,这就需要用数据包的形式进行传输,接收部分也需要按照数据包的格式来接收啊,这样才能接收多字节数据包。数据包的发送和接收也是比较常见和重要的内容,有关串口这部分进阶的内容,我们下小节还会继续来为大家讲解哈