1.通信涉及的几个基本概念
差分传输是一种信号传输的技术,区别于传统的一根信号线一根地线的做法,差分传输在这两根线上都传输信号,这两个信号的振幅相同,相位相反。在这两根线上的传输的信号就是差分信号。信号接收端比较这两个电压的差值来判断发送端发送的逻辑状态。优点是抗干扰能力强;缺点是需要两根线,导致电路板面积紧张。
2.串口通信的基本概念
(1)串口通信的特点:异步、电平信号、串行
异步:串口通信的发送方和接收方之间是没有统一的时钟信号的。
电平信号:串口通信出现的时间较早,速率较低,传输的距离较近,所以干扰还不太明显,因此当时使用了电平信号传输。后期出现的传输协议都改成差分信号传输了。
串行通信:串口通信每次同时只能传输1个二进制位。
(2)RS232电平和TTL电平
电平信号是用信号线电平减去参考线电平得到电压差,这个电压差决定了传输值是1还是0。取0还是1取决于电平标准,如RS232电平中-3V~-15V表示1;+3~+15V表示0;TTL电平则是+5V表示1,0V表示0。RS232的电平定义比较大,适合干扰大、距离远的情况;TTL电平电压范围小,适合距离近且干扰小的情况。台式电脑后面的串口插座就是RS232接口的,在工业上用串口时都用这个,传输距离小于15米;TTL电平一般用在电路板内部两个芯片之间。对编程来说,RS232电平传输还是TTL电平是没有差异的。所以电平标准对硬件工程师更有意义,而软件工程师只要略懂即可。(把TTL电平和RS232电平混接是不可以的)
(3)波特率(Band)
是串口通信时每秒传输的二进制位个数。如每秒种传输9600个二进制位,波特率就是9600。一般低端单片机如51常用9600,高端单片机和嵌入式SoC一般用115200。波特率不可以随便指定,因为如果发送方和接收方按照不同的波特率通信则根本收不到。
(4)起始位、数据位、奇偶校验位、停止位
串口通信时,每周期传输n个二进制位。包括起始位、数据位、奇偶校验位、停止位。
起始位表示开始发送,是串口通信标准事先指定的,是由通信线上的电平变化来反映的。数据位是发送的有效信息,一般是8位,因为一般发送的字符都是ASCII码编码的,而ASCII码为8位。奇偶校验位是用来给数据位进行奇偶校验(把待校验的有效数据逐个位的加起来,总和为奇数奇偶校验位就为1,总和为偶数奇偶校验位就为0)的,可以在一定程度上防止位反转。停止位是结束标志,停止位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的,一般停止位为1位。
串口通信时因为是异步通信,所以通信双方必须事先约定好通信参数,这些通信参数(帧)包括:波特率、数据位、奇偶校验位、停止位(串口通信中起始位定义是唯一的,所以一般不用选择)
3.串口通信的基本原理
(1)一般开发板都会引出SoC上串口引脚直接输出的TTL电平的串口(X210开发板没有),插座用插针式插座,每个串口引出的都有3个线(Tx、Rx、GND),可以用这些插座直接连接外部的TTL电平的串口设备。
(2)DB9接口是串口通信早期常用的一种规范化接口。DB9接口中有9根通信线,其中3根很重要,为GND、Tx、Rx,必不可少;剩余6根都是和流控有关的,现代我们使用串口都是用来做调试一般都禁用流控,所以这6根没用。现在一般使用串口时要记得把流控禁止掉,不然可能发生意想不到的问题。
4.S5PV210串行通信接口详解①
(1)串口UART:universal asynchronous reciver and transmitter,通用异步收发器
(2)串口控制器原理框图
①串口控制器包含transmitter和receiver两部分。
②总线角度来讲,串口控制器是接在APB总线上的,将来计算串口控制器的源时钟时是以APB总线来计算的。
③transmitter由发送缓冲区Transmit Buffer Register和发送移位器Transmit Shifter构成。我们要发送信息时,首先将信息进行编码(一般用ASCII码)成二进制流,然后将一帧数据(一般是8位)写入发送缓冲区(从这里以后程序就不用管了,剩下的发送部分是硬件自动的),发送移位器会自动从发送缓冲区中读取一帧数据,然后自动移位(移位的目的是将一帧数据的各个位分别拿出来)将其发送到Tx通信线上。
④receiver由接收缓冲区和接收移位器构成。信息通过Rx通信线进入接收移位器,然后接收移位器自动移位将该二进制位存入接收缓冲区,接收完一帧数据后receiver会产生一个中断给CPU,CPU收到中断后即可知道receiver接收满了一帧数据,就会来读取这帧数据。
总结:所以我们写串口的代码就是:首先初始化(实质是读写寄存器)串口控制器(包括发送控制器和接收控制器),然后要发送信息时直接写入发送缓冲区,要接收信息时直接去接收缓冲区读取。串口底层的工作(譬如怎么移位的、譬如起始位怎么定义的、譬如TTL电平还是RS232电平等)程序员不用去管。
⑤串口控制器中有一个波特率发生器Baud-rate Generator,作用是产生串口发送/接收的节拍时钟。波特率发生器其实就是个时钟分频器,它的工作需要源时钟(APB总线来),然后内部将源时钟进行分频(软件设置寄存器来配置)得到目标时钟,然后再用这个目标时钟产生波特率(硬件自动的)。
(3)自动流控(AFC:Auto flow control)
为什么需要流控?流控的目的是让串口通信非常可靠,在发送方速率比接收方快的时候流控可以保证发送和接收不会漏掉东西。
现在为什么不用流控?现在计算机之间有更好更高级(usb、internet)的通讯方式,串口已经基本被废弃了。现在串口的用途更多是SoC用来输出调试信息的。由于调试信息不是关键性信息、而且由于硬件发展串口本身速度已经相对慢的要死了,所以硬件都能协调发送和接收速率,因此流控已经失去意义了,所以现在基本都废弃了。
5.S5PV210串行通信接口详解②
本来串口的功能就是上节讲过的部分,但是后来的技术发展给串口叠加了一些高级功能,在像210这类的高级SoC的串口控制器中,都有这类高级功能。
(1)FIFO模式及其作用
典型的串口设计,发送/接收缓冲区只有1字节,每次发送/接收只能处理1帧数据。这样在单片机中没什么问题,但是到复杂SoC中(一般有操作系统的)就会有问题,会导致效率低下,因为CPU需要不断切换上下文(需要CPU频繁处理,进程切换频繁)。解决方案就是将发送/接收缓冲器设置为64字节,CPU一次给64字节的待发送数据,发完再找CPU再要。但是串口控制器本来的发送/接收缓冲区是固定的1字节长度的,所以做了个扩展,就是FIFO(64字节放这里,大缓冲区)。FIFO就是first in first out,先进先出。fifo其实是一种数据结构,这里这个大的缓冲区叫FIFO是因为这个缓冲区的工作方式类似于FIFO这种数据结构。(进来abc顺序,出去也是abc)
(2)DMA模式及其作用
DMA direct memory access,直接内存访问。DMA本来是DSP中的一种技术,DMA技术的核心就是在交换数据时不需要CPU参与,模块可以自己完成。DMA模式要解决的问题和上面FIFO模式是同一个问题,就是串口发送/接收要频繁的折腾CPU造成CPU反复切换上下文导致系统效率低下。传统的串口工作方式(无FIFO无DMA)效率是最低的,适合低端单片机;高端单片机上CPU事物繁忙所以都需要串口能够自己完成大量数据发送/接收。这时候就需要FIFO或者DMA模式。FIFO模式是一种轻量级的解决方案,DMA模式适合大量数据迸发式的发送/接收时。
(3)IrDA模式及其用法
IrDA其实就是红外,红外就是红外线通信(电视机、空调遥控器就是红外通信的)。红外通信的原理是发送方固定间隔时间向接收方发送红外信号(表示1或0)或者不发送红外信号(表示0或者1),接收方每隔固定时间去判断有无红外线信号来接收1和0。红外通信和串口通信非常像,因此210就利用串口通信来实现了红外发送和接收。210的某个串口支持IrDA模式,开启红外模式后,我们只需要向串口写数据,这些数据就会以红外光的方式向外发射出去(当然是需要一些外部硬件支持的),然后接收方接收这些红外数据即可解码得到我们的发送信息。
6.S5PV210串行通信接口详解③
(1)串行通信与中断的关系
串口通信分为发送/接收2部分。发送方一般不需要(也可以使用)中断即可完成发送,接收方必须(一般来说必须,也可以轮询方式接收)使用中断来接收。
发送方使用中断:发送方先设置好中断并绑定一个中断处理程序,然后发送方丢一帧数据给transmitter,transmitter发送耗费一段时间来发送这一帧数据,这段时间内发送方CPU可以去做别的事情,等transmitter发送完成后会产生一个TXD中断,该中断会导致事先绑定的中断处理程序执行,在中断处理程序中CPU会切换回来继续给transmitter放一帧数据,然后CPU切换离开。
发送方不使用中断:发送方事先禁止TXD中断,发送方CPU给一帧数据到transmitter,然后transmitter耗费一段时间来发送这帧数据,这段时间CPU在这等着,待发送完成后CPU再给它一帧数据继续发送直到发完。
CPU是怎么知道transmitter已经发送完了?原来是有个状态寄存器,状态寄存器中有一个位叫发送缓冲区空标志,transmitter发送完成(发送缓冲区空了)就会给这个标志位置位,CPU就是通过不断查询这个标志位为1还是0来指导发送是否已经完成的。
因为串口通信是异步的,发送方占主导权,随时想发就能发,但是接收方只有时刻等待才不会丢失数据(轮询的时候先前的数据在缓冲区没被接收可能会被后来的数据顶掉)。所以这个差异就导致发送方可以不用中断,而接收方不得不使用中断模式。
(2)210串行通信接口的时钟设计
因为串口通信需要一个固定的波特率,所以transmitter和receiver都需要一个时钟信号。源时钟信号是外部APB总线(PCLK_PSYS,66MHz)提供给串口模块的,然后进到串口在波特率发生器中进行分频,得到一个低频时钟。
串口通信中时钟的设置主要看寄存器设置。重点的有:寄存器源设置(为串口控制器选择源时钟,一般选择为PCLK_PSYS,也可以是SCLK_UART),还有波特率发生器的2个寄存器:UBRDIVn和UDIVSLOTn,其中UBRDIVn是主要的设置波特率的寄存器,UDIVSLOTn是用来辅助设置的,目的是为了校准波特率的。
7.S5PV210串行通信编程实战①
(1)整个程序流程分析
整个串口通信相关程序包含2部分:uart_init负责初始化串口,uart_putc负责发送一个字节。
(2)串口控制器初始化关键步骤
①初始化串口的Tx和Rx引脚所对应的GPIO(查原理图可知Rx和Tx分别对应GPA0_0和GPA0_1)。GPA0CON(0xE0200000)应设置bit[3:0] = 0b0010 bit[7:4] = 0b0010 。
②初始化这几个关键寄存器ULCON0、UCON0、UFCON0、UMCON0、UBRDIV0、UDIVSLOT0。
·ULCON0 = 0x3(无红外模式、0奇偶校验位、1停止位、8数据位)。
·UCON0 = 0x5(不使用DMA、时钟选用PCLK、暂时不考虑中断、不回环、发送接收都是轮询模式polling mode)。
·UFCON0 = 0x0(不用FIFO)
·UMCON0 = 0x0(不用流控)
·UBRDIV0和UDIVSLOT0和波特率有关,要公式去算
(3)在C源文件中定义访问寄存器的宏(这里多加了三个ULCON0里的宏)。定义好了访问寄存器的宏之后,将来写代码时直接使用即可。
8.S5PV210串行通信编程实战②
(1)串口Tx、Rx对应的GPIO的初始化。给GPA0CON的相应bit位赋值为上节相应值,用C语言位操作来完成。
(2)UCON、ULCON、UMCON、UFCON等主要控制寄存器。依据上节中分析的值进行依次设置即可。
(3)波特率的计算和设置
①用PCLK_PSYS和目标波特率去计算DIV_VAL: DIV_VAL = (PCLK / (bps x 16)) ?1
②rUBRDIV0寄存器中写入DIV_VAL的整数部分
③用小数部分*16得到1个个数,查表得rUDIVSLOT0寄存器的设置值
(4)因为串口控制器发送一个字节的速度远小于CPU,所以CPU发送一个字节前必须确认串口控制器UTRSTAT0当前的缓冲区是空的。如果缓冲区非空则位为0,此时应该while循环直到位为1再发出符号。没用FIFO模式时bit2和1是一样的?这里66MHz和66.7MHz是一样的。
(5)上述为uart.c的设置,还需设置makefile添加uart.o main.o(把先前的led改名为uart);start.S中改为bl main;main.c中设置如下,初始化、循环发送字符a(添加一定延时)。实验结果为从Securt CRT中看到有字符a不断输出。
9.uart stdio的移植①
(1)什么是stdio
①#include <stdio.h>
②stdio:standard input output,标准输入输出
③标准输入输出就是操作系统定义的默认的输入和输出通道。一般在PC机的情况下,标准输入指的是键盘,标准输出指的是屏幕。
④printf函数和scanf函数可以和底层输入/输出函数绑定,然后这两个函数就可以和stdio绑定起来。也就是说我们直接调用printf函数输出,内容就会被从标准输出输出出去。
⑤在我们这里,标准输出当然不是屏幕了,而是串口。标准输入也不是键盘,而是串口。
(2)printf工作原理
printf函数工作时内部实际调用了2个关键函数:一个是vsprintf函数(主要功能是格式化打印信息,最终得到纯字符串格式的打印信息等待输出),另一个就是真正的输出函数putc(操控标准输出的硬件,将信息发送出去)。
(3)移植printf函数的三种思路
我们希望在我们的开发板上使用printf函数进行(串口)输出,使用scanf函数进行(串口)输入,就像在PC机上用键盘和屏幕进行输入输出一样。因此需要移植printf函数/scanf函数。我们说的移植而不是编写,我们不希望自己完全从新重新编写而是想尽量借用也有的代码(叫移植)。一般移植printf函数可以有3个途径获取printf的实现源码:最原始最原本的来源就是linux内核中的printk,难度较大、关键是麻烦;稍微简单些的方法是从uboot中移植printf;更简单的方法就是直接使用别人移植好的(本次采用)。
10.uart stdio的移植②
(1)把别人一直好的printf解压到2.uart_c_printf中,分别是include和lib(我认为是表示头文件和库)。lib中也有一个Makefile。
(2)uart.c中,输入输出函数的名字要改成和lib中的printf.c中的函数名对应,即putc、getc。
(3)main.c中,main函数要调用printf.c,需要添加头文件#include “stdio.h”,这里的stdio.h不是编译器提供的,而是我们自己提供的,在include中(用双引号表示优先到指定的路径找文件)。还需要添加对初始化函数的声明。
(4)总Makefile中,由于子Makefile是靠总Makefile调用的,要使得两者相兼容,子Makefile中是用非常标准的方法来写的,定义了变量objs,所以这里也使用变量objs := start.o led.o clock.o uart.o main.o,引用变量用的是
(
o
b
j
s
)
。
这
里
用
了
比
较
正
式
的
写
法
,
即
C
C
替
换
编
译
器
,
L
D
替
换
链
接
器
,
O
B
J
C
O
P
Y
替
换
生
成
b
i
n
文
件
的
命
令
,
O
B
J
D
U
M
P
替
换
生
成
反
编
译
的
命
令
,
A
R
是
用
来
生
成
静
态
链
接
库
的
,
下
面
就
可
以
用
这
些
简
写
加
(objs)。这里用了比较正式的写法,即CC替换编译器,LD替换链接器,OBJCOPY替换生成bin文件的命令,OBJDUMP替换生成反编译的命令,AR是用来生成静态链接库的,下面就可以用这些简写加
(objs)。这里用了比较正式的写法,即CC替换编译器,LD替换链接器,OBJCOPY替换生成bin文件的命令,OBJDUMP替换生成反编译的命令,AR是用来生成静态链接库的,下面就可以用这些简写加{}来代替这些命令(
(
)
:
这
个
小
括
号
里
放
的
是
命
令
,
和
‘
‘
反
引
号
作
用
一
样
,
执
行
这
个
命
令
;
():这个小括号里放的是命令,和``反引号作用一样,执行这个命令;
():这个小括号里放的是命令,和‘‘反引号作用一样,执行这个命令;{}:这里面放的是变量,用来引用的)。然后用export把这些变量导出全局,给子文件夹Makefile中使用。-
nostdlib表示不用标准库,标准库中的printf是屏幕输出,这里需要串口输出,-nostdinc表示不用标准的头文件,而用自己提供的头文件。-Wall表示显示所有警告,-O2表示优化等级为2,-fno-builtin表示编译链接时全用我们项目里的东西。在linux中编译.c时使用了一个.h文件,则会在当前目录下去找,第二种是会在编译器设置了的指定了专门放头文件的目录中找,第三种是可以用-I来指定一个人为建立的路径,这里当前目录用
(
I
N
C
D
I
R
)
代
替
.
表
示
更
好
。
然
后
把
(INCDIR)代替.表示更好。然后把%.o中的nostdlib删掉,添加
(INCDIR)代替.表示更好。然后把(CPPFLAGS) $(CFLAGS)。
为了让子Makefile也得到编译,加上objs += lib/libc.a,使父Makefile的编译依赖于子Makefile的编译。但问题是没有找到这个文件,所以可以添加一个这样的规则lib/libc.a:,使其进入到lib后运行Makefile然后再出来。同样在clean的时候也加上一个类似的命令来清理子Makefile中的文件。
注:“=”和“:=”的区别:
(5)子Makefile,是靠总Makefile调用的。其中定义了一个变量objs :=div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.o;.a表示库文件(Windows中叫lib)。
(6)在移植后的uart stdio项目中添加link.lds链接脚本,指定链接地址到0xd0020010(因为移植的printf代码中可能不都是地址无关码,所以应该链接到运行地址)。Makefile修改如下,-Ttext 0x0改为-Tlink.lds。复制chapter5最后一个项目中的link.lds到该项目,修改链接地址为d0020010,删掉多余的sdram_init.o。最终成功输出结果如下。
11.uart stdio的移植③
(1)gcc可变参数及va_arg介绍
printf函数中首先使用了C语言的可变参数va_start/va_arg/va_end。具体使用可变参数可百度。
(2)vsprintf函数详解
printf
vsprintf
vsnprintf
number
vsprintf函数的作用是按照我们的printf传进去的格式化标本,对变参进行处理,然后将之格式化后缓存在一个事先分配好的缓冲区中。printf后半段调用putc函数将缓冲区中格式化好的字符串直接输出到标准输出。
12.串口实验烧录问题总结
bin文件大于16KB怎么办?
通过USB下载最多也只能下载96KB大小的bin,如果bin大于96KB肯定SRAM放不下会出错。如果用SD卡启动,那么mkv210_image.c决定了bin文件最大不能超过16KB。
超过了怎么办?2种解法:(uboot下载的两种方法)
第一,在USB下载时,可以先下载一个x210_usb.bin,然后再将裸机程序连接到0x23E00000,然后再修改dnw中下载地址,将裸机代码下载到0x23E00000运行。(这时不需要重定位了)
第二,在SD卡启动时,将整个裸机工程分为2部分;第一部分大小16KB以内,第二部分放剩下的(放在SD卡的后面的某个扇区开始的位置,譬如放在第50个扇区开始的位置),然后在裸机代码中进行重定位(SD卡中重定位)。这个暂时没讲,以后如果有用到就讲。