19.S5PV210串口基本操作


19.1.通信涉及的几个基础概念
(1)通信的发展历史;最早通信是烽火台狼烟,邮局信件;现代化通信是电子通信,譬如电报/电话/互联网。通信三要素=信息表示方法+信息解析方法+信息传输方法;通信双方事先需要约定好信息的表示方法和解析方法,否则信息不能有效传递;信号的传输方法是指经过编码后的通信信息如何在传输介质上传输的过程。通信的标准流程,首先发送方先按照信息编码方式对有效信息进行编码(编程成可以在通信线路上传输的信号形态);然后编码后的信息在传输介质上进行传输,输送给接收方;最后接收方接收到编码信息后进行解码,解码后得到可以理解的有效信息。
(2)同步通信和异步通信;发送方和接收方按照同一个时钟节拍工作就叫同步,发送方和接收方各自按照自己的时钟节拍工作就叫异步。同步通信中,通信双方按照统一节拍工作,一般需要发送方给接收方发送信息同时发送时钟信号,接收方根据发送方给它的时钟信号来安排自己的节奏,同步通信一般应用于通信双方信息交换频率固定场景或通信双方经常通信场景。异步通信中,接收方不必一直在意发送方,发送方发送信息时需要首先给接收方起始信号,接收方接收到起始信号后就认为后面紧跟有效信息,才会开始注意接收信息,直到收到发送方发过来的结束标志,异步通信一般应用于通信双方的通信频率不固定场景。
(3)电平信号和差分信号;电平信号和差分信号是用来描述通信线路传输方式,即如何在通信线路上表达1和0;电平信号的传输线中有一个参考电平线(一般是GND),然后信号线上的信号值是由信号线电平和参考电平线的电压差决定;差分信号的传输线中没有参考电平,所有都是信号线,然后1和0的表达靠信号线之间的电压差。电平信号的2根通信线之间的电平差异容易受到干扰,差分信号不容易受到干扰因此传输质量比较稳定,现代通信一般都使用差分信号,电平信号几乎没有了;看起来似乎相同根数的通信线下,电平信号要比差分信号要快,但是实际还是差分信号快,因为差分信号抗干扰能力强,因此1个发送周期更短。
(4)并行接口和串行接口;串行和并行主要是考虑通信线的根数,即发送方和接收方同时可以传递的信息量的多少;譬如在电平信号下,1根参考电平线+1根信号线可以传递1位二进制,如果我们有3根线(2根信号线+1根参考线)就可以同时发送2位二进制,如果想同时发送8位二进制就需要9根线;在差分信号下,2根线(彼此差分)可以同时发送1位二进制;如果需要同时发送8位二进制,需要16根线。听起来似乎并行接口比串行接口要快,但实际上串行接口才是王道,用的比较广,因为更省信号线,而且对传输线的要求更低,成本更低,而且串行时可以通过提高通信速度来提高总体通信性能,不一定非得要并行。
(5)单工通信和双工通信;单工就是单方向通信,双工就是双方同时收发,只能单方向通信但是方向可以改变叫半双工;如果只能A发B收则单工,A发B收或者B发A收(两个方向不能同时)叫半双工,A发B收同时B发A收叫全双工。


19.2.串口通信的基本概念
(1)串口通信为异步+电平信号+串行;异步=串口通信的发送方和接收方之间是没有统一的时钟信号的;电平信号=串口通信使用了电平信号传输;串行通信=串口通信每次同时只能传输1个bit位。RS232电平和TTL电平;电平信号是用信号线电平减去参考线电平得到电压差,该电压差决定了传输值是1还是0;在电平信号时多少V代表1,多少V代表0不是固定的,取决于电平标准;譬如RS232电平中-3V~-15V表示1,+3~+15V表示0;TTL电平则是+5V表示1,0V表示0。不管哪种电平都是为了在传输线上表示1和0,区别在于适用的环境和条件不同;RS232的电平定义比较大,适合干扰大+距离远的情况;TTL电平电压范围小,适合距离近+干扰小的情况。我们台式电脑后面的串口插座就是RS232接口的,在工业上用串口时都用这个,传输距离小于15米;TTL电平一般用在电路板内部两个芯片之间进行通信。
(2)波特率(baudrate);指的是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位,譬如每秒种可以传输9600个二进制位(传输1个二进制位需要的时间是1/9600秒,也就是104us),波特率就是9600。最常见的串口波特率是9600或者115200(低端单片机如51常用9600,高端单片机和嵌入式SoC一般用115200)。波特率不可以随便指定的原因,首先通信双方必须事先设定相同的波特率这样才能成功通信,如果发送方和接收方按照不同的波特率通信则根本收不到,因此波特率最好是大家熟知的而不是随意指定的,其次常用的波特率经过长久发展,就形成了共识,大家常用就是9600或者115200。
(3)起始位+数据位+奇偶校验位+停止位;串口通信时的数据收发是以周期为单位进行传输的,每个周期传输n个二进制位,该周期就叫做1个通信单元,1个通信单元=起始位+数据位+奇偶校验位+停止位。起始位表示发送方要开始发送1个通信单元;数据位是1个通信单元中发送的有效信息位;奇偶校验位是用来校验数据位,以防止数据位出错的;停止位是发送方用来表示本通信单元结束标志的。
(4)起始位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。数据位是本次通信真正要发送的有效数据,串口通信每次发送多少位有效数据是可以设定的,有6/7/8/9可选,99%情况下我们都是选择8位数据位,因为我们一般通过串口发送的文字信息都是ASCII码编码的,而ASCII码中1个字符刚好编码为8位。奇偶校验位是用来给数据位进行奇偶校验;把待校验的有效数据逐个位的加起来,总和为奇数奇偶校验位就为1,总和为偶数奇偶校验位就为0;可以在一定程度上防止位反转。停止位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的,常见的停止位有1位/1.5位/2位等,99%情况下都是用1位停止位。
(5)串口通信时因为是异步通信,所以通信双方必须事先约定好通信参数,这些通信参数=波特率+数据位+奇偶校验位+停止位,串口通信中起始位定义是唯一的,所以一般不用选择。


19.3.串口通信的基本原理
(1)三根通信线Rx+Tx+GND;任何通信都要有信息传输载体,或者是有线的或者是无线的;串口通信是有线通信,是通过串口线来通信的;串口通信线最少需要2根(GND和信号线),可以实现单工通信,也可以使用3根通信线Tx+Rx+GND来实现全双工;一般开发板都会引出SoC上串口引脚直接输出的TTL电平的串口,插座用插针式插座,每个串口引出的都有3个线Tx+Rx+GND,可以用这些插座直接连接外部的TTL电平的串口设备。
(2)收发双方事先规定好通信参数=波特率+数据位+奇偶校验位+停止位等;串口通信属于基层基本性的通信规约,它自己本身不会去协商通信参数,需要通信前通信双方事先约定好通信参数;串口通信的任何一个关键参数设置错误,都会导致通信失败,譬如波特率调错了,发送方发送没问题,接收方也能接收,但是接收到全是乱码。
(3)信息以二进制流的方式在信道上传输;串口通信的发送方每隔一定时间(时间固定为1/波特率,单位是秒)将有效信息(1或者0)放到通信线上去,逐个二进制位的进行发送;接收方通过定时(起始时间由读到起始位标志开始,间隔时间由波特率决定)读取通信线上的电平高低来区分发送给我的是1还是0,依次读取数据位+奇偶校验位+停止位,停止位就表示这个通信单元(帧)结束,然后中间是不定长短的非通信时间,发送方有可能紧接着就发送第二帧,也可能半天都不发第二帧。
(4)波特率非常重要,波特率错了整个通信就乱套了;数据位+奇偶校验位+停止位也很重要,否则可能认不清数据;通过串口不管发数字/文本/命令,都要先对发送内容进行编码,编码成二进制再进行逐个位的发送;串口发送的一般都是字符,一般都是ASCII码编码后的字符,所以一般设置数据位都是8,方便刚好一帧发送1个字符。
(5)DB9接口介绍;DB9接口是串口通信早期比较常用的一种规范化接口;串行通信在早期是计算机与外界通信的主要手段,那时候的计算机都有标准配置的串口以实现和外部通信,那时候就定义了一套标准的串口规约,DB9接口就是标准接口;DB9接口中有9根通信线,其中3根很重要GND+Tx+Rx,剩余6根都是和流控有关的,现代我们使用串口都是用来做调试一般都禁用流控;现在一般使用串口时要记得把流控禁止掉,不然可能发生意想不到的问题。


19.4.S5PV210串行通信接口
(1)串口名称为通用异步收发器,英文缩写是uart,中文简称串口;整个串口控制器包含transmitter和receiver两部分,两部分功能彼此独立,transmitter负责210向外部发送信息,receiver负责从外部接收信息到210内部;串口控制器是接在APB总线上的,将来计算串口控制器的源时钟时是以APB总线来计算的(见图1)。
(2)transmitter由发送缓冲区和发送移位器构成,我们要发送信息时,首先将信息进行编码(一般用ASCII码)成二进制流,然后将一帧数据(一般是8位)写入发送缓冲区(从这里以后程序就不用管了,剩下的发送部分是硬件自动的),发送移位器会自动从发送缓冲区中读取一帧数据,然后自动移位(移位的目的是将一帧数据的各个位分别拿出来)将其发送到Tx通信线上。
(3)receiver由接收缓冲区和接收移位器构成,当有人通过串口线向我发送信息时,信息通过Rx通信线进入我的接收移位器,然后接收移位器自动移位将该二进制位保存入我的接收缓冲区,接收完一帧数据后receiver会产生一个中断给CPU,CPU收到中断后即可知道receiver接收满了一帧数据,就会来读取这帧数据。
(4)发送缓冲区和接收缓冲区是关键,发送移位器和接收移位器的工作都是自动的,不用编程控制的,则我们首先初始化串口控制器(包括发送控制器和接收控制器),然后要发送信息时直接写入发送缓冲区,要接收信息时直接去接收缓冲区读取即可;串口底层的工作对程序员是隐藏的,软件工程师对串口操作的接口就是发送/接收缓冲区(实质就是寄存器,操作方式就是读写内存)。
(5)串口控制器中有一个波特率发生器,作用是产生串口发送/接收的节拍时钟;波特率发生器其实就是个时钟分频器,它的工作需要源时钟(APB总线来),然后内部将源时钟进行分频(软件设置寄存器来配置)得到目标时钟,然后再用这个目标时钟产生波特率(硬件自动的)。
(6)自动流控AFC;流控的目的是让串口通信非常可靠,在发送方速率比接收方快的时候流控可以保证发送和接收不会漏帧;现在计算机之间有更好更高级的通讯方式,串口已经基本被废弃了;现在串口的用途更多是SoC用来输出调试信息的,由于调试信息不是关键性信息,而且由于硬件的发展串口本身的速度相对来说非常慢,所以硬件自己都能协调发送和接收速率,因此流控已经失去意义了,所以现在基本都废弃了。


19.5.S5PV210串口高级功能
(1)FIFO模式及其作用;在典型的串口设计中,发送/接收缓冲区只有1字节,每次发送/接收只能处理1帧数据,这样在单片机中没什么问题,但是到有操作系统的复杂SoC中会导致效率低下,因为CPU需要不断切换上下文;解决方案就是想办法扩展串口控制器的发送/接收缓冲区,譬如将发送/接收缓冲器设置为64字节,CPU一次过来直接给发送缓冲区64字节的待发送数据,然后transmitter慢慢发,发完再找CPU再要64字节;但是串口控制器本来的发送/接收缓冲区是固定的1字节长度的,所以做了个变相的扩展,就是FIFO,因为该缓冲区的工作方式类似于FIFO这种先进先出的数据结构。
(2)DMA模式及其作用;DMA直接内存访问,DMA本来是DSP中的一种技术,DMA技术的核心就是在交换数据时不需要CPU参与,模块可以自己完成;DMA模式要解决的问题是串口发送/接收要频繁的折腾CPU造成CPU反复切换上下文导致系统效率低下;传统的串口工作方式(无FIFO无DMA)效率是最低的,适合低端单片机;高端单片机上CPU事物繁忙所以都需要串口能够自己完成大量数据发送/接收,这时候就需要FIFO或者DMA模式;FIFO模式是一种轻量级的解决方案,DMA模式适合大量数据迸发式的发送/接收时。
(3)IrDA模式及其用法;IrDA其实就是红外即红外线通信,红外通信的原理是发送方固定间隔时间向接收方发送红外信号(表示1或0)或者不发送红外信号(表示0或者1),接收方每隔固定时间去判断有无红外线信号来接收1和0;红外通信和串口通信非常像,都是每隔固定时间发送1或者0(判断1或0的物理方式不同)给接收方来通信,因此210就利用串口通信来实现了红外发送和接收;210的某个串口支持IrDA模式,开启红外模式后,我们只需要向串口写数据,这些数据就会以红外光的方式向外发射出去(需要红外发射和接收管的外部硬件支持),然后接收方接收这些红外数据即可解码得到我们的发送信息。


19.6.S5PV210串口中断和时钟源
(1)串行通信和中断;串口通信分为发送/接收2部分,发送方一般不需要(也可以使用)中断即可完成发送,接收方必须(也可以轮询方式接收)使用中断来接收数据。因为串口通信是异步的,异步的意思就是说发送方占主导权,即发送方随时想发就能发,但是接收方只有时刻等待才不会丢失数据,所以该差异就导致发送方可以不用中断,而接收方不得不使用中断模式。
(2)发送方使用中断的工作情景,发送方先设置好中断并绑定中断处理程序,然后发送方发送1帧数据给transmitter,然后transmitter发送耗费时间来发送该帧数据,该段时间内发送方CPU可以去做别的事情,等transmitter发送完成后会产生TXD中断,该中断会导致事先绑定的中断处理程序执行,在中断处理程序中CPU会切换回来继续给transmitter发送1帧数据,然后CPU切换离开。
(3)不使用中断的工作情景,发送方事先禁止TXD中断,发送方CPU给1帧数据到transmitter,然后transmitter耗费时间来发送该帧数据,该段时间CPU在这等着,CPU没有切换去做别的事情,发送方发送完成后CPU再给它1帧数据继续发送直到所有数据发完,CPU通过不断查询状态寄存器中的发送缓冲区空标志为1还是0来指导发送是否已经完成,该状态寄存器若transmitter发送完成(发送缓冲区空了)硬件会自动给该标志位置位。
(4)S5PV210串行通信接口的时钟设计;串口通信需要1个固定的波特率,所以transmitter和receiver都需要1个时钟信号;串口的源时钟信号是外部APB总线(PCLK_PSYS=66MHz)提供的,源时钟进入到串口控制器内部后给波特率发生器,在波特率发生器中进行分频,分频后得到1个低频时钟,该时钟就是给transmitter和receiver使用的。
(5)串口通信中时钟的设置主要看寄存器设置;重点有串口时钟源寄存器设置(为串口控制器选择源时钟,一般选择为PCLK_PSYS,也可以是SCLK_UART);其次还有波特率发生器的2个寄存器,波特率发生器有2个重要寄存器UBRDIVn和UDIVSLOTn,其中UBRDIVn是主要的设置波特率的寄存器,UDIVSLOTn是用来辅助设置的,目的是为了校准波特率。


19.7.S5PV210简单的串口使用
(1)整个串口通信程序=uart_init负责初始化串口+uart_putc负责发送1个字节;我们操作GEC210的串口0;首先初始化串口的Tx(对应原理图上的GPA0_1引脚)和Rx(对应原理图上的GPA0_0引脚)引脚所对应的GPIO(GPA0CON(0xE0200000)的bit[3:0]=0b0010;bit[7:4]=0b0010)。
(2)初始化这几个关键寄存器=ULCON0(0xE290_0000;0x03=普通模式+0校验位+1停止位+8数据位)+UCON0(0xE290_0004;0x05=选择PCLK_PSYS做为串口时钟源+不使用回环模式+发送和接收都是中断/轮询模式(此处均使用轮询模式))+UMCON0(0xE290_000C;0x00=禁止流控的一切配置)+UFCON0(0xE290_0008;0x00=禁止FIFO模式)+UBRDIV0(0xE290_0028;设置波特率)+UDIVSLOT0(0xE290_002C;设置波特率)。
(3)在C源文件中定义访问寄存器的宏,定义好了访问寄存器的宏之后,将来写代码时直接使用即可;UTRSTAT0(0xE290_0010;发送和接收缓冲区空或满状态寄存器)+UTXH0(0xE290_0020;数据位接收缓冲区寄存器)+URXH0(0xE290_0024;数据位发送缓冲区寄存器)。
(4)波特率的计算和设置;用PCLK_PSYS(66MHz或66.7MHz)和目标波特率去计算DIV_VAL的值(DIV_VAL=(PCLK/(bps*16))-1);DIV_VAL的整数部分写入UBRDIV0寄存器;DIV_VAL的小数部分*16得到”1的个数”的值,查表得到uBDIVSLOT0寄存器的设置值(见图2)。


19.8.串口输入输出的移植
(1)标准输入输出就是操作系统定义的默认的输入和输出通道;一般在PC机的情况下,标准输入指的是键盘,标准输出指的是屏幕;printf函数和scanf函数可以和210串口底层输入/输出函数绑定,即我们直接调用printf函数输出,内容就会被从串口输入输出。
(2)printf函数的工作原理;printf函数内部实际调用了2个关键函数=vsprintf函数(格式化打印信息,最终得到纯字符串格式的打印信息等待输出)+输出函数putc(操控标准输出的硬件,将信息发送出去)。
(3)我们希望在我们的开发板上使用printf函数进行(串口)输出,使用scanf函数进行(串口)输入,则需要移植printf函数/scanf函数;移植printf函数可以有3个途径获取printf的实现源码=最原始最原本的来源就是linux内核中的printk(难度较大,关键是麻烦)+稍微简单些的方法是从uboot中移植printf+更简单的方法就是直接使用别人移植好的;此处别人移植好的printf函数来自于友善之臂的Tiny210的裸机教程中提供的。
(4)gcc可变参数及va_arg;printf函数中首先使用了C语言的可变参数va_start/va_arg/va_end(此处和C语言的可变参数有关);vsprintf函数(printf->vsprintf->vsnprintf->number);vsprintf函数的作用是按照我们的printf传进去的格式化标本,对变参进行处理,然后将之格式化后缓存在一个事先分配好的缓冲区中;printf后半段调用putc函数将缓冲区中格式化好的字符串直接输出到标准输出。
(5)bin文件若大于16KB的解决办法;通过USB下载最多也只能下载96KB大小的bin,如果bin大于96KB肯定SRAM放不下会出错;如果用SD卡启动,那么mkv210_image.c决定了bin文件最大不能超过16KB;解决方法1->在USB下载时,可以先下载1个x210_usb.bin,然后再将裸机程序连接到0x23E00000,然后再修改dnw中下载地址,将裸机代码下载到0x23E00000运行;解决方法2->在SD卡启动时,将整个裸机工程分为2部分,第1部分大小16KB以内,第2部分放剩下的(放在SD卡的后面的某个扇区开始的位置,譬如放在第50个扇区开始的位置),然后在裸机代码中进行重定位(SD卡中重定位)。


19.S5PV210串口基本操作_第1张图片


19.S5PV210串口基本操作_第2张图片


19.uart_printf/Makefile
# 210裸机项目中多个Makefile协同工作的模板
CC      = arm-linux-gcc
LD      = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR      = arm-linux-ar

INCDIR  := $(shell pwd)
# C预处理器的flag,flag就是编译器可选的选项
# -I指定头文件的的目录
CPPFLAGS    := -nostdlib -nostdinc -I$(INCDIR)/include
# C编译器的flag
CFLAGS      := -Wall -O2 -fno-builtin

#导出这些变量到全局,其实就是给子文件夹下面的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGS

name:=uart_printf
objs := start.o clock.o sdram_init.o  main.o uart.o
objs += lib/libc.a

$(name).bin:$(objs)
    $(LD) -Tlink.lds -o $(name).elf $^
    $(OBJCOPY) -O binary $(name).elf $(name)_usb.bin
    $(OBJDUMP) -D $(name).elf > $(name)_elf.dis
    gcc mkv210_image.c -o mkgec210
    ./mkgec210 $(name)_usb.bin $(name)_sd.bin

lib/libc.a:
    cd lib; make;   cd ..

%.o : %.S
    $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c 

%.o : %.c
    $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c

clean:
    rm *.o *.elf *.bin *.dis mkgec210 -f
    cd lib; make clean; cd ..
19.uart_printf/lib/Makefile
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.o

libc.a: $(objs)
    ${AR} -r -o $@ $^

%.o:%.c
    ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

%.o:%.S
    ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

clean:
    rm -f libc.a *.o    
19.uart_printf/uart.c
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:串口移植输出函数
 * 功能:串口移植printf函数功能
 */
#include "uart.h"

// 初始化串口
void uart_init(void)
{
    // 初始化RXD和TXD的GPIO引脚
    rGPA0CON &= ~(0xff<<0);            // 清零寄存器
    rGPA0CON |= 0x00000022;         // bit[3:0]=0b0010;bit[7:4]=0b0010

    // 初始化关键寄存器
    rULCON0 = 0x03;                 // 普通模式+0校验位+1停止位+8数据位
    rUCON0  = 0x05;                 // 选择PCLK_PSYS做为串口时钟源+不使用回环模式+发送和接收都是中断/轮询模式
    rUMCON0 = 0x00;                 // 禁止流控的一切配置
    rUFCON0 = 0x00;                 // 禁止FIFO模式

    // 设置串口波特率  
#if 1                               // 使用66.7MHz计算
    rUBRDIV0 = 35;                  // DIV_VAL = (66700000/(115200*16)-1) = 35.18
    rUDIVSLOT0 = 0x0888;            // 1的个数 = 16*0.18= 2.88 = 3个1,查官方推荐表
#endif

#if 0                               // 使用66MHz计算
    rUBRDIV0 = 34;                  // DIV_VAL = (66000000/(115200*16)-1) = 34.08
    rUDIVSLOT0 = 0xDFDD;            // 1的个数 = 16*0.8= 11 = 11个1,查官方推荐表
#endif
}

// 串口发送1个字符
void putc(char c)
{
    while (!(rUTRSTAT0 & (1<<1)));  // 若发送缓冲区为空,则发送数据
    rUTXH0 = c;
}

// 串口接收1个字符
char getc(void)
{
    while (!(rUTRSTAT0 & (1<<0)));  // 若接收到数据,则返回接收到的数据
    return (rURXH0 & 0x0f);
}

你可能感兴趣的:(arm裸机)