1.几个电子通信的基本概念
-> 同步通信和异步通信:
同步通信中,通信双方按照统一节拍工作,所以配合很好;一般需要发送方给接收方发送信息同时发送时钟信号.
异步通信又叫异步通知。在双方通信的频率不固定时(有时3ms收发一次,有时3天才收发一次)不适合使用同步通信,而适合异步通信。
-> 电平信号和差分信号:
电平信号的传输线中有一个参考电平线(一般是GND),然后信号线上的信号值是由信号线电平和参考电平线的电压差决定。
差分信号的传输线中没有参考电平,所有都是信号线。然后1和0的表达靠信号线之间的电压差。
-> 并行接口和串行接口:
串行、并行主要是考虑通信线的根数,就是发送方和接收方同时可以传递的信息量的多少。
譬如在电平信号下,1根参考电平线+1根信号线可以传递1位二进制;如果我们有3根线(2根信号线+1根参考线)就可以同时发送2位二进制;如果想同时发送8位二进制就需要9根线。
在差分信号下,2根线(彼此差分)可以同时发送1位二进制;如果需要同时发送8位二进制,需要16根线。
2.几个串口通信的基本概念
-> RS232电平和TTL电平:
RS232电平中-3V~-15V表示1;+3~+15V表示0,台式电脑后面的串口插座就是RS232接口的,在工业上用串口时都用这个,传输距离小于15米。
TTL电平则是+5V表示1,0V表示0,TTL电平一般用在电路板内部两个芯片之间。
-> 波特率:
指的是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位。譬如每秒种可以传输9600个二进制位(传输一个二进制位需要的时间是1/9600秒,也就是104us),波特率就是9600。
通信双方必须事先设定相同的波特率这样才能成功通信,常用就是9600或者115200。
-> 单工通信和双工通信:
单工就是单方向,双工就是双方同时收发,同时只能单方向但是方向可以改变叫半双工
如果只能A发B收则单工,A发B收或者B发A收(两个方向不能同时)叫半双工,A发B收同时B发A收叫全双工。
-> 三根通信线:Rx Tx GND:
串口通信线最少需要2根(GND和信号线),可以实现单工通信,也可以使用3根通信线(Tx、Rx、GND)来实现全双工。
-> 起始位、数据位、奇偶校验位、停止位:
串口通信时,收发是一个周期一个周期进行的,每周期传输n个二进制位。这一个周期就叫做一个通信单元,一个通信单元是由:起始位+数据位+奇偶校验位+停止位组成的。
起始位表示发送方要开始发送一个通信单元;数据位是一个通信单元中发送的有效信息位;
奇偶校验位是用来校验数据位,以防止数据位出错的;停止位是发送方用来表示本通信单元结束标志的。
-> FIFO模式:
发送/接收缓冲区只有1字节,每次发送/接收只能处理1帧数据。这样在单片机中没什么问题,但是到复杂SoC中(一般有操作系统的)就会有问题,会导致效率低下,因为CPU需要不断切换上下文。
3.串口通信的基本原理
整个串口控制器包含发送缓冲区transmitter和接收缓冲区receiver两部分,两部分功能彼此独立,transmitter负责210向外部发送信息,receiver负责从外部接收信息到210内部。
transmitter由发送缓冲区和发送移位器构成,我们要发送信息时,首先将信息进行编码(一般用ASCII码)成二进制流,然后将一帧数据(一般是8位)写入发送缓冲区(从这里以后程序就不用管了,剩下的发送部分是硬件自动的),发送移位器会自动从发送缓冲区中读取一帧数据,然后自动移位(移位的目的是将一帧数据的各个位分别拿出来)将其发送到Tx通信线上。
receiver由接收缓冲区和接收移位器构成。当有人通过串口线向我发送信息时,信息通过Rx通信线进入我的接收移位器,然后接收移位器自动移位将该二进制位保存入我的接收缓冲区,接收完一帧数据后receiver会产生一个中断给CPU,CPU收到中断后即可知道receiver接收满了一帧数据,就会来读取这帧数据。
串口控制器中有一个波特率发生器,作用是产生串口发送/接收的节拍时钟。
4.S5PV210串行通信编程实战
硬件原理图:Rx和Rx分别对应GPA0_1和GPA0_0
几个重要的寄存器:
ULCON0 = 0x3 // 0校验位、8数据位、1停止位
UCON = 0x5 // 发送和接收都是polling mode
UMCON0 = 0x0 // 禁止modem、afc
UFCON0 = 0x0 // 禁止FIFO模式
UBRDIV0和UDIVSLOT0和波特率有关,要根据公式去算的
整个串口通信相关程序包含2部分:
uart_init负责初始化串口,uart_putc负责发送一个字节,char uart_getc负责接收一个字节。
uart.h:
#define GPA0CON 0xE0200000
#define UCON0 0xE2900004
#define ULCON0 0xE2900000
#define UMCON0 0xE290000C
#define UFCON0 0xE2900008
#define UBRDIV0 0xE2900028
#define UDIVSLOT0 0xE290002C
#define UTRSTAT0 0xE2900010
#define UTXH0 0xE2900020
#define URXH0 0xE2900024
#define rGPA0CON (*(volatile unsigned int *)GPA0CON)
#define rUCON0 (*(volatile unsigned int *)UCON0)
#define rULCON0 (*(volatile unsigned int *)ULCON0)
#define rUMCON0 (*(volatile unsigned int *)UMCON0)
#define rUFCON0 (*(volatile unsigned int *)UFCON0)
#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)
uart.c:
实现了串口的初始化,串口的发送函数,串口的接收函数。
// 串口初始化程序
void uart_init(void)
{
// 初始化Tx Rx对应的GPIO引脚
rGPA0CON &= ~(0xff<<0); // 把寄存器的bit0~7全部清零
rGPA0CON |= 0x00000022; // 0b0010, Rx Tx
// 几个关键寄存器的设置
rULCON0 = 0x3;
rUCON0 = 0x5;
rUMCON0 = 0;
rUFCON0 = 0;
//波特率的计算和设置
//用PCLK_PSYS和目标波特率去计算DIV_VAL: DIV_VAL = (PCLK / (bps x 16)) -1
//UBRDIV0寄存器中写入DIV_VAL的整数部分
//用小数部分*16得到1个个数,查表得uBDIVSLOT0寄存器的设置值
// 波特率设置 DIV_VAL = (PCLK / (bps x 16))-1
// PCLK_PSYS用66MHz算 余数0.8
//rUBRDIV0 = 34;
//rUDIVSLOT0 = 0xdfdd;
// PCLK_PSYS用66.7MHz算 余数0.18
// DIV_VAL = (66700000/(115200*16)-1) = 35.18
rUBRDIV0 = 35;
// (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18
// (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3
rUDIVSLOT0 = 0x0888; // 3个1,查官方推荐表得到这个数字
}
// 串口发送程序,发送一个字节
void uart_putc(char c)
{
// 串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去
// 因为串口控制器发送1个字节的速度远远低于CPU的速度,所以CPU发送1个字节前必须
// 确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)
// 如果缓冲区非空则位为0,此时应该循环,直到位为1
while (!(rUTRSTAT0 & (1<<1)));
rUTXH0 = c;
}
// 串口接收程序,轮询方式,接收一个字节
char uart_getc(void)
{
while (!(rUTRSTAT0 & (1<<0)));
return (rURXH0 & 0x0f);
}
main.c:
void main(void)
{
uart_init();
while(1)
{
uart_putc('a');
delay();
}
}