卢骏 · 2020年07月07日

CORTEX-A8裸机系列:第十一章 串口

第十一章 串口

这部分代码,查看我的github网址:https://github.com/weiqi7777/...

1、210的UART控制器

210提供了4个UART,4个UART可以工作在中断或者DMA模式。串口能产生中断或者DMA请求数据传输信号。UART支持最大比特率3Mbps。每个UART包含了两个FIFO,用来接收和发送数据。

对于ch0,有256字节fifo,对于ch1,有64字节fifo,对于ch2和ch3,有16字节fifo。

UART包括一个波特率产生器,发送器,接收器和控制单元。
1.png

时钟源可以有两个选择,PCLK和SCLK_UART。

当数据写入到发送FIFO,硬件会自动的将数据拷贝到发送移位器中,然后通过发送管脚(TXDN)一位一位发送出去。

当从接收移位器从接收数据管脚(RXDN)中接收到数据后,会将该数据拷贝到接收FIFO中。

2、自动流控(AFC,auto flow control)

解决发送方和接收方之间数据传输不一致导致数据丢失的情况,因此加了几个信号,实现流控制。

对于发送方,要发送数据的时候,要将一根控制线拉高,接收方接收到该控制信号为高,表示要接收数据。

对于接收方,可以接收数据的时候,将一根控制线拉高,发送方接收到该控制信号为高,表示可以发送数据。

所以,流控的目的是使串口通信变得可靠,在发送方速率比接收方快的时候,流控可以保证发送和接收均不会漏掉数据。不过,现在已经不使用该功能了。

3、IrDA模式及其用法

1) IrDA其实就是红外,红外就是红外线通信(电视机,空调遥控器就是红外通信)。

2) 红外编码传输和串口传输很类似,按照一定时间,发送红外,有发送红外线,表示传输1,没有发送红外线,表示传输0。

3) 红外通信的原理:发送方固定间隔时间向接收方发送红外信号(表示1或0)或者不发送红外信号(表示0或1),接收方每隔固定时间去判断有无红外线信号来接收1和0。

4) 因此红外通信和串口通信很类似,都是每隔固定时间发送1或者0(判断1或0的物理方式不同)给接收方来通信。因此,210就利用串口通信来实现了红外发送和接收

5) 210的某个串口支持IrDA,开启红外模式后,我们只需要向串口写数据,这些数据就会以红外光的方式向外发射出去(当然是需要一些外部硬件支持),然后接收方接收这些红外数据即可解码得到我们的发送信息。

4、串口中断

串口中断,有三类

1) 接收中断,当接收到数据,并接收数据达到FIFO的触发数目,产生中断

2) 发送中断,方发送数据,FIFO中的数据小于或等于FIFO的触发数目,产生中断。

3) 错误中断,传输发生一些其他错误,产生中断
2.png

对于发送,有一个状态寄存器,其中有一位叫做发送缓冲器空标志,发送器发送完成((发送缓冲区空了)就会给这个标志位置位,CPU就是通过不断查询这个标志位为1还是0来知道发送是否已经完成。

对于接收,有一个状态寄存器,其中有一位叫做接收缓冲器满标志,CPU通过不断查询这个标志位判断是否接收到数据。但是这样,效率就很低了,因此,对于接收,要使用中断的模式。

5、波特率设置

串口,有一个很重要的东西,就是波特率的设置。波特率如果设置的不正确,那么串口通道是不能正常通信的。

波特率设置,有两个重要寄存器,UBRDIVn和UDIVSLOTn。其中UBRDIVn为主要波特率设置的寄存器,UDIVSLOTn为辅助的寄存器,目的是为了校准波特率。

计算公式如下:
3.png

关于UDIVSLOTn,推荐的值
4.png

我们写程序的时候,就使用手册上推荐的参数,设置波特率就行了。

6、串口初始化
6.1、将串口管脚设置为串口功能

设置GPA0CON寄存器,将GPIO设置为串口功能。
5.png
6.png

6.2、设置串口功能的关键寄存器

UCONx, ULCONx, UMCONx, UFCONx, UBRDIVx, UIDVSLOTOx。

对于UCONx(867页),设置串口的数据传输的格式。包括数据位,停止位,奇偶校验位,以及工作模式。
7.png

对于ULCONx,设置串口的发送和接收工作模式,以及时钟源选择和一些其他设置。
8.png
9.png

对于UFCONx,设置关于FIFO的功能。
10.png

对于UMCONx,设置关于流控和modem功能。
11.png

对于UBRDIVx, UIDVSLOTOx寄存器(878页),设置波特率。
12.png
13.png

6.3、读取串口发送接收状态

状态保存在UTRSTAT寄存器中。

发送的时候,有两个信号,一个是Transmitter empty ,一个是transmitter buffer empty。两个在没有使用FIFO的时候,效果是一样的,但是如果使用FIFO的话,就要使用transmitter buffer empty。

Transmitter empty是包括传输的FIFO和传输的移位寄存器,两个都为空,这一位的值才会为1。

Transmitter buffer empty只是指FIFO,当FIFO空的时候,该位才为1。所以当使用串口FIFO功能的时候,要检测这一位,判断发送是否完成。

当Receive buffer data ready为1,表示接收到数据。
14.png

6.4、发送数据

直接往寄存器UTXH写入值即可。但是前提是发送FIFO,没有满。因此在写入之前,需要判断FIFO是否满。

6.5、读取数据

直接读取寄存器URXH即可。但是前提是接收FIFO中有数据。因此在读取之前,需要判断FIFO是否为空。

7、移植stdio

printf函数和scanf函数可以和底层输入、输出函数绑定,然后这两个函数就可以和stdio绑定起来。也就是直接调用printf函数输出,内容就会从标准输出输出出去。

因此,这个时候标准输出就不是屏幕了,而是串口。标准输入也不是键盘,而是串口。

7.1、printf函数原理

printf函数工作时内部实际调用了2个关键函数:

一个是vsprintf函数(主要功能是格式化打印信息,最终得到纯字符串格式的打印信息等待输出)

另一个就是真正的输出函数(操控标准输出的硬件,将信息发送出去)

移植printf函数可以有3个途径获取printf的实现源码:

1) 最原始最原本的来源就是linux内核中的printk。难度较大而且麻烦。

2) 从uboot中移植

3) 直接使用别人移植好的

使用全局变量定义了两个缓冲区,一个是接收数据用,一个是发送数据用。
15.png

最终,printf函数调用putc函数,所以要实现putc,实现数据发送。scanf函数调用getc函数,所以要实现getc。

移植,修改顶层的makefile,增加一些变量,并将变量导出,让子文件夹的makefile使用这些变量。

然后再进入到子文件夹,运行makefile。
16.png
17.png

make –C lib,就是进入到lib文件夹,执行make。lib文件夹,放的就是实现printf,scanf函数的库函数代码。将库函数编译成,静态库,然后在顶层链接到我们写的程序当中去。

7.2、printf函数
printf

    调用vsprintf

        调用vsnprintf

           调用number

vsprintf函数的作用是按照我们的printf传进去的格式化文本,对变参进行处理,然后将之格式化后缓存在一个实现分配好的缓冲区中。

printf后半段调用putc函数将缓冲区中格式化后的字符串直接输出到标准输出(也就是串口)。

系列其他篇

原文首发于骏的世界博客
作者:卢骏.
更多Arm技术相关的文章请关注Arm技术博客极术专栏,每日更新。
推荐阅读
关注数
23565
内容数
1018
Arm相关的技术博客,提供最新Arm技术干货,欢迎关注
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息