目录
一、通信的基本概念
1.1 同步通信和异步通信
1.2 电平信号和差分信号
1.3 并行接口和串行接口
1.4 单工通信和双工通信
二、串口通信
2.1 基本概念
2.2 起始位、数据位、奇偶校验位、停止位
2.3 RS232电平和TTL电平
2.4 波特率
2.5 串口通信基本原理
2.5.1 三根通信线:Rx Tx GND
2.5.2 通信参数的约定
2.5.3 信息的传输
2.5.4 DB9接口介绍
三、S5PV210的串口
3.1 串口控制器框图
3.1.1 transmitter
3.1.2 receiver
3.1.3 Buad-rate Generator
3.2 自动流控(AFC,Auto flow control)
3.3 FIF0模式及其作用
3.4 DMA模式及其作用
3.5 IrDA模式及其用法
3.6 串行通信与中断的关系
四、编程准备
4.1 程序流程分析
4.2 相关寄存器
4.2.1 GPA0
4.2.2 UCON0
4.2.3 ULCON0
4.2.4 UFCON0
4.2.5 UMCON0
4.2.6 UBRDIV0、UDIVSLOT0
4.2.7 UTXH0和URXH0
4.2.8 UTRSTAT0
五、编程
5.1 uart.c文件
5.2 printf导入
一、通信的基本概念
通信过程分为3个步骤:
1.发送方按照信息编码方式对有效信息进行编码(编程成可以在通信线路上传输的信号形态)
2.编码后的信息在传输介质上进行传输,输送给接收方
3.接收方接收到编码信息后进行解码,解码后得到可以理解的有效信息。
最常用的:异步、串行、差分,譬如USB和网络通信
1.1 同步通信和异步通信
同步通信:通信双方按照统一节拍工作,一般需要发送方给接收方发送信息同时发送时钟信号,接收方根据发送方给它的时钟信号来安排自己的节奏。同步通信用在通信双方信息交换频率固定,或者经常通信时。
异步通信:又叫异步通知。在双方通信的频率不固定时(有时3ms收发一次,有时3天才收发一次)不适合使用同步通信,而适合异步通信。异步通信时接收方不必一直在意发送方,发送方需要发送信息时会首先给接收方一个信息开始的起始信号,接收方接收到起始信号后就认为后面紧跟着的就是有效信息,才会开始注意接收信息,直到收到发送方发过来的结束标志。
同步和异步的区别:发送方和接收方按照同一个时钟节拍工作就叫同步,发送方和接收方没有统一的时钟节拍、而各自按照自己的节拍工作就叫异步。
1.2 电平信号和差分信号
电平信号:传输线中有一个参考电平线(一般是GND),然后信号线上的信号值是由信号线电平和参考电平线的电压差决定。
差分信号:传输线中没有参考电平,所有都是信号线。然后1和0的表达靠信号线之间的电压差。
电平信号和差分信号是用来描述通信线路传输方式的。也就是说如何在通信线路上表达1和0。电平信号的2根通信线之间的电平差异容易受到干扰,传输容易失败;差分信号不容易受到干扰因此传输质量比较稳定,现代通信一般都使用差分信号,电平信号几乎没有了。
电平信号同时传输两个信息需要3条线(一条GND,两条信号线),差分信号同时传输两个信息需要4条线,看起来似乎相同根数的通信线下,电平信号要比差分信号要快;但是实际还是差分信号快,因为差分信号抗干扰能力强,因此1个发送周期更短。
1.3 并行接口和串行接口
串行、并行主要是考虑通信线的根数,就是发送方和接收方同时可以传递的信息量的多少。听起来似乎并行接口比串行接口要快(串行接口一次只能发送1位二进制,而并行接口一次可以发送多位二进制)要更优秀;但是实际上串行接口更好,用的比较广。因为更省信号线,而且对传输线的要求更低、成本更低;而且串行时可以通过提高通信速度来提高总体通信性能,不一定非得要并行。
1.4 单工通信和双工通信
单工:只能单方向进行数据传输
双工:双方同时可以收发数据
半双工:能够进行双向数据接收发送,但在同一时间只能进行单方面传输
二、串口通信
2.1 基本概念
串口通信的特点:异步、电平信号、串行。串口在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位。
异步:串口通信的发送方和接收方之间是没有统一的时钟信号的。
电平信号:串口通信出现的时间较早,速率较低,传输的距离较近,所以干扰还不太明显,因此当时使用了电平信号传输。后期出现的传输协议都改成差分信号传输了。
串行通信:串口通信每次同时只能传输1个二进制位。
2.2 起始位、数据位、奇偶校验位、停止位
起始位:数据发送标志,起始位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。
数据位:一个通信单元中发送的有效信息位,是本次通信真正要发送的有效数据,串口通信一次发送多少位有效数据是可以设定的(一般可选的有6、7、8、9,99%情况下我们都是选择8位数据位。因为我们一般通过串口发送的文字信息都是ASCII码编码的,而ASCII码中一个字符刚好编码为8位。)
奇偶校验位:用来校验数据位,以防止数据位出错,给数据位进行奇偶校验(把待校验的有效数据逐个位的加起来,总和为奇数奇偶校验位就为1,总和为偶数奇偶校验位就为0)的,可以在一定程度上防止位反转。
停止位:是发送方用来表示本通信单元结束标志的。停止位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。常见的有1位停止位,1.5位停止位,2位停止位等。99%情况下都是用1位停止位。
总结:串口通信时因为是异步通信,所以通信双方必须事先约定好通信参数,这些通信参数包括:波特率、数据位、奇偶校验位、停止位(串口通信中起始位定义是唯一的,所以一般不用选择)。
2.3 RS232电平和TTL电平
电平信号:信号线电平减去参考线电平得到电压差,这个电压差决定了传输值是1还是0。在电平信号时多少V代表1,多少V代表0不是固定的,取决于电平标准。RS232电平中-3V~-15V表示1;+3~+15V表示0;TTL电平则是+5V表示1,0V表示0。
总结:RS232的电平定义比较大,适合干扰大、距离远的情况;TTL电平电压范围小,适合距离近且干扰小的情况。电脑的串口插座就是RS232接口的,在工业上使用,传输距离小于15米;TTL电平一般用在电路板内部两个芯片之间
2.4 波特率
波特率(bandrate):指的是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位。譬如每秒种可以传输9600个二进制位(传输一个二进制位需要的时间是1/9600秒,也就是104us),波特率就是9600。
串口通信的波特率不能随意设定,而应该在一些值中去选择。一般最常见的波特率是9600或者115200(低端单片机如51常用9600,高端单片机和嵌入式SoC一般用115200).
为什么波特率不可以随便指定?
第一,通信双方必须事先设定相同的波特率这样才能成功通信,如果发送方和接收方按照不同的波特率通信则根本收不到,因此波特率最好是大家熟知的而不是随意指定的。
第二,常用的波特率经过长久发展,就形成了共识,大家常用就是9600或者115200。
2.5 串口通信基本原理
2.5.1 三根通信线:Rx Tx GND
任何通信都要有信息传输载体,或者是有线的或者是无线的。串口通信是有线通信,是通过串口线来通信的。串口通信线最少需要2根(GND和信号线),可以实现单工通信,也可以使用3根通信线(Tx、Rx、GND)来实现全双工。
2.5.2 通信参数的约定
串口通信属于基层基本性的通信规约,它自己本身不会去协商通信参数,需要通信前通信双方事先约定好通信参数(一般4个最重要的波特率、数据位、奇偶校验位、停止位)。串口通信的任何一个关键参数设置错误,都会导致通信失败。
2.5.3 信息的传输
串口通信的发送是将有效信息(1或者0)放到通信线上去,逐个二进制位的进行发送。接收方通过定时(起始时间由读到起始位标志开始,间隔时间由波特率决定)读取通信线上的电平高低来区分发送给我的是1还是0。
通过串口不管发数字、还是文本还是命令还是什么,都要先对发送内容进行编码,编码成二进制再进行逐个位的发送。串口发送的一般都是字符,一般都是ASCII码编码后的字符,所以一般设置数据位都是8,方便刚好一帧发送1个字符。
2.5.4 DB9接口介绍
串行通信在早期是计算机与外界通信的主要手段,那时候的计算机都有标准配置的串口以实现和外部通信。那时候就定义了一套标准的串口规约,DB9接口就是标准接口。
DB9接口中有9根通信线,其中3根很重要,为GND、Tx、Rx,必不可少;剩余6根都是和流控有关的,现代我们使用串口都是用来做调试一般都禁用流控,所以这6根没用。现在一般使用串口时要记得把流控禁止掉,不然可能发生意想不到的问题。
三、S5PV210的串口
3.1 串口控制器框图
整个串口控制器包含transmitter和receiver两部分,两部分功能彼此独立,transmitter负责210向外部发送信息,receiver负责从外部接收信息到210内部。串口控制器接在APB总线。
3.1.1 transmitter
transmitter由发送缓冲区和发送移位器构成。发送信息时,首先将信息进行编码(一般用ASCII码)成二进制流,然后将一帧数据(一般是8位)写入发送缓冲区,发送移位器会自动从发送缓冲区中读取一帧数据(这一步是硬件自动处理),自动移位将其发送到Tx通信线上。
3.1.2 receiver
receiver由接收缓冲区和接收移位器构成。当有人通过串口线发送信息时,信息通过Rx通信线进入接收移位器,然后接收移位器自动移位将该二进制位保存入我的接收缓冲区(硬件自动处理),接收完一帧数据后receiver会产生一个中断给CPU,CPU收到中断后即可知道receiver接收满了一帧数据,就会来读取这帧数据。
3.1.3 Buad-rate Generator
串口控制器中有一个波特率发生器,作用是产生串口发送/接收的节拍时钟。波特率发生器其实就是个时钟分频器,它的工作需要源时钟(APB总线来),然后内部将源时钟进行分频(软件设置寄存器来配置)得到目标时钟,然后再用这个目标时钟产生波特率(硬件自动的)。
3.2 自动流控(AFC,Auto flow control)
流控的目的是让串口通信非常可靠,在发送方速率比接收方快的时候流控可以保证发送和接收不会漏掉东西。现在计算机之间有更好更高级(usb、internet)的通讯方式,串口已经基本被废弃了。现在串口的用途更多是SoC用来输出调试信息的。由于调试信息不是关键性信息、而且由于硬件发展串口本身速度太慢了,硬件都能协调发送和接收速率,因此流控已经失去意义了,所以现在基本都废弃了。
3.3 FIF0模式及其作用
FIFO就是first in first out,先进先出。fifo其实是一种数据结构,这里这个大的缓冲区叫FIFO是因为这个缓冲区的工作方式类似于FIFO这种数据结构。
典型的串口设计在发送/接收缓冲区只有1字节,每次发送/接收只能处理1帧数据。这样在单片机中没什么问题,但是到复杂SoC中(一般有操作系统的)就会有问题,会导致效率低下,因为CPU需要不断切换上下文。
解决方案:扩展串口控制器的发送/接收缓冲区,譬如将发送/接收缓冲器设置为64字节,CPU一次过来直接给发送缓冲区64字节的待发送数据,然后transmitter慢慢发,发完再找CPU再要64字节。但是串口控制器本来的发送/接收缓冲区是固定的1字节长度的,所以做了个变相的扩展,就是FIFO。
3.4 DMA模式及其作用
DMA direct memory access,直接内存访问。DMA本来是DSP中的一种技术,DMA技术的核心就是在交换数据时不需要CPU参与,模块可以自己完成。
DMA模式要解决的问题和上面FIFO模式是同一个问题,就是串口发送/接收要频繁的折腾CPU造成CPU反复切换上下文导致系统效率低下。
FIFO模式是一种轻量级的解决方案,DMA模式适合大量数据迸发式的发送/接收时。
3.5 IrDA模式及其用法
IrDA就是红外线通信,红外通信的原理是发送方固定间隔时间向接收方发送红外信号(表示1或0)或者不发送红外信号(表示0或者1),接收方每隔固定时间去判断有无红外线信号。
红外通信和串口通信非常像,都是每隔固定时间发送1或者0(判断1或0的物理方式不同)给接收方来通信。因此210就利用串口通信来实现了红外发送和接收。
210的某个串口支持IrDA模式,开启红外模式后,只需要向串口写数据,这些数据就会以红外光的方式向外发射出去(当然是需要一些外部硬件支持的),然后接收方接收这些红外数据即可解码得到我们的发送信息。
3.6 串行通信与中断的关系
串口通信分为发送/接收2部分。发送方一般不需要(也可以使用)中断即可完成发送,接收方必须(一般来说必须,也可以轮询方式接收)使用中断来接收。发送方可以选择使用中断,也可以选择不使用中断。
CPU通过状态寄存器中的发送缓冲区标志判断缓存区是否为空,当缓存区为空时会被置1。
四、编程准备
4.1 程序流程分析
(1)串口需要和外部进行连接,需要将相应的端口使能,打开uart,在核心板上可以找到uart模块,找到相应的RXD0与TXD0,对应端口为GPA0_0与GPA0_1。
(2)串口初始化,设置寄存器为相应的值
(3)设置串口时钟,transmitter和receiver都需要一个时钟信号,uart时钟来自PCLK_PSYS。还需要对波特率进行设置。
(4)进行配置,让串口可以接收/发送数据
4.2 相关寄存器
根据4.1中分析,将需要的寄存器按照步骤及功能进行分类。
(1)使能串口
GPA0CON(0xE020_0000):GPA0控制寄存器
(2)串口配置寄存器
ULCON0(0xE290_0000):线路控制寄存器
UCON0(0xE290_0004):uart控制寄存器
UFCON0(0xE290_0008):FIFO模式控制寄存器
UMCON0(0xE290_000C):MODEM控制寄存器
(3)时钟及波特率设置寄存器
UBRDIV0(0xE290_0028)
UDIVSLOT0(0xE290_002C)
(4)串口发送接收寄存器
UTXH0(0xE290_0020):uart发送缓存寄存器
URXH0(0xE290_0024):uart接受缓存寄存器
UTRSTAT0(0xE290_0010):缓存区状态寄存器
4.2.1 GPA0
设置GPA0CON[0]、GPA0CON[1]为uart模式
4.2.2 UCON0
设置接收输出模式为轮询,其他功能关闭
4.2.3 ULCON0
数据为设置8-bit,停止位设置为1位,奇偶校验位关闭,infrared mode关闭
4.2.4 UFCON0
关闭FIFO模式
4.2.5 UMCON0
关闭MODEM模式
4.2.6 UBRDIV0、UDIVSLOT0
根据公式计算寄存器对应的值, 对应的PLCK为PLCK_PSYS,相应频率为66MHz;
66000000/(115200*16)-1 = 34.80
0.8*16 = 12.8;UDIVSLOT0中有12/13个1,取13
UBRDIV0 = 34;
查询表格,UDIVSLOT0 = 0xDFDD
4.2.7 UTXH0和URXH0
两个缓存区,不用配置。
4.2.8 UTRSTAT0
当0位置1时,接受缓存区装满;1位置1时,发送缓存区为空。
五、编程
5.1 uart.c文件
其他文件略做小调整,添加了uart.c文件,串口发送与接收可在初始化后用uart_putc与uart_getc函数。
uart.c
#define GPA0CON 0xE0200000#define ULCON0 0xE2900000
#define UCON0 0xE2900004
#define UFCON0 0xE2900008
#define UMCON0 0xE290000C
#define UBRDIV0 0xE2900028
#define UDIVSLOT0 0xE290002C
#define UTRSTAT0 0xE2900010
#define UTXH0 0xE2900020
#define URXH0 0xE2900024#define rGPA0CON *((volatile unsigned int*)GPA0CON)
#define rULCON0 *((volatile unsigned int*)ULCON0)
#define rUCON0 *((volatile unsigned int*)UCON0)
#define rUFCON0 *((volatile unsigned int*)UFCON0)
#define rUMCON0 *((volatile unsigned int*)UMCON0)
#define rUBRDIV0 *((volatile unsigned int*)UBRDIV0)
#define rUDIVSLOT0 *((volatile unsigned int*)UDIVSLOT0)
#define rUTRSTAT0 *((volatile unsigned int*)UTRSTAT0)
#define rUTXH0 *((volatile unsigned int*)UTXH0)
#define rURXH0 *((volatile unsigned int*)URXH0)// 串口初始化程序
void uart_init(void)
{// 将GPA0设置为 UART模式rGPA0CON &= ~(0xff<<0); // 把寄存器的bit0~7清零rGPA0CON |= 0x00000022;// 几个关键寄存器设置rULCON0 = 0x3; // 0校验位,8数据位,1停止位rUCON0 = 0x5; // 关闭中断,设置轮询模式rUFCON0 = 0x0; // 禁止FIFOrUMCON0 = 0x0; // 禁止modem,afc// DIV_VAL = (PCLK / (bps x 16)) ?1 = (66_000_000/(115200*16))-1// PCLK = 66M, DIV_VAL = 35.80// rUBRDIV0 和 rUDIVSLOT0 查公式和推荐表 // rUBRDIV0 = 0x34;// rUDIVSLOT0 = 0xDFDD;// PCLK = 66.7M, DIV_VAL = 36.18rUBRDIV0 = 35; // 这个35是十进制,不能写0x35rUDIVSLOT0 = 0x0888;}// 串口发送程序,发送一个字节
void uart_putc(char c)
{// 串口发送一个字符,其实就是将一个字节丢到发送缓冲区// 当发送缓存区为空时,发送数据// 设置一个循环,若缓存区不为空,则一直循环, rUTRSTAT0第二位为1时为空while( !(rUTRSTAT0 & (1<<1)));rUTXH0 = c;
}// 串口接收程序,轮询方式,接收一个字节
// void uart_getc(void)
char uart_getc(void)
{// 状态位为1时退出循环while(!(rUTRSTAT0 & (1<<0)));return (rURXH0 & 0x0f);}
5.2 printf导入
将include与lib文件复制入文件夹,将uart.c中的uart_putc与uart_getc函数更名为putc与getc,对照include中Makefile对主文件Makefile进行更改。
Makefile
CC = arm-linux-gcc
LD = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR = arm-linux-arINCDIR := $(shell pwd)
# c预处理器的flag,flag就是编译器可选的选项,-nostdlib:不用标准库,-nostdinc: 不用标准头文件, I:指定头文件目录
CPPFLAGS := -nostdlib -nostdinc -I$(INCDIR)/include
# c编译器器的flag,-Wall:给警告信息,-O2:给优化级别,
CFLAGS := -Wall -O2 -fno-builtin# 导出变量到全局,给子文件下的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGSobjs := start.o led.o clock.o uart.o main.o
objs += lib/libc.aled.bin: $(objs)$(LD) -Tlink.lds -o led.elf $^$(OBJCOPY) -O binary led.elf led.bin$(OBJDUMP) -D led.elf > led_elf.disgcc mkv210_image.c -o mkx210./mkx210 led.bin 210.bin#lib/libc:
# cd lib; make; cd ..lib/libc.a:cd lib; make; cd ..%.o : %.S$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c %.o : %.c$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -cclean:rm *.o *.elf *.bin *.dis mkx210 -fcd lib; make clean; cd ..