11. 通用异步收发器 UART
11.1 UART原理,部件使用方法
11.1.1UART原理
UART是Universal Asynchronous Receiver Transmitter的缩写,即通用异步收发器
UART用来传输串行数据:
- 发送时,CPU将并行数据写入UART,UART按照一定格式在一根电线上串行发出
- 接收时,UART检测另一根电线上的信号,收集串行数据存放在缓冲区,供CPU读取
连线图:
图中收发各有一条线,有的设备比如SIM卡,只有一条线,收发共用。
- 2条线可以实现全双工
- 1条线可以实现半双工
电平逻辑
- TTL/CMOS
- 5V,3.3V,2.5V/1.8V 表示1
- 0V,表示0
- RS-232
- 3~12V 表示1
- -3~-12V表示0
传输结构
- 位,最小单元
- 帧,不可分割的若干位
- 开始位
- 数据位
- 校验位(可选)
- 停止位
波特率
每一位所需时间的倒数,即每秒可以传输的位数。
波形
上图是一个7位数据位的帧波形。
- 空闲状态,高电平
- 拉低1位,表示帧开始
- 然后按照次序,按照约定的每帧数据位数,传输数据
- 数据位全部传输完成后,传输校验位,校验方式提前约定,可以是奇校验,也可以是偶校验
- 下一位是停止位,拉高电平
- 停止位长度可以设置,1,1.5,2,为了保护各个帧,防止错乱
11.1.2 2440的UART
3个通道
- UART0
- UART1
- UART2
工作模式
- 中断模式
- DMA模式
收发过程
发数据
- 2440的UART由深度64的FIFO控制
- CPU写入数据到FIFO
- UART将FIFO的数据复制到“发送移位寄存器”Transmit Shifter
- Shifter将数据发送的TxDn数据线上
收数据
- 数据线RxDn上的数据进入接收移位器,Receive Shifter
- Shifter复制数据到FIFO中
- CPU从FIFO中读取数据
11.1.3 2440的UART使用
使用前的设置
- 波特率
- 传输格式
- 数据位宽度
- 是否使用校验位
- 奇校验还是偶校验
- 多少个停止位
- 是否使用流量控制
- 设置管脚
- 对应管脚设置位UART管脚
- UART通道工作模式
- 中断模式
- DMA模式
设置好以后的使用
- 往某寄存器写入数据即发送
- 读取某个寄存器即获得接收到的数据
- 发送完毕,接收到数据的信息获取
- 查询状态寄存器
- 设置中断
1. UART管脚设置
- UART通道0
- TxD0 GPH2
- RxD0 GPH3
所以GPH2和GPH3需要设置成TxD0和RxD0管脚。
2. 波特率 UBRDIVn寄存器
根据芯片手册,时钟频率,波特率和UBVRDIVn寄存器的数学关系如下
UBRDIVn = int(UART clock/(baud rate x 16)) - 1
那么如果给定时钟频率是40MHz,波特率要求是115200,那么可以计算出寄存器UBRDIVn的设置值应该是
UBRDIVn = int(40000000/(115200 x 16)) - 1
= int(21.7) - 1 /*取最近接的整数*/
= 22 - 1
= 21
3. 传输格式 ULCONn寄存器
传输格式包括这几个方面的设置
- 红外模式开关
- 校验位设置
- 停止位设置
- 数据位宽度
其他几项的含义非常直观,不赘述。
红外模式的含义这里按照芯片手册复述一下,来龙去脉并不清楚。
下图是正常的串口波形,高电平是1,低电平是0。
下图是红外模式的波形:
从图中可以看出,在红外模式中:
- 发送时,通过宽度是3/16位的脉冲,判断当前位是0,如果没有脉冲,当前位是1
- 接收时,通过宽度是3/16位的脉冲,发出信号0,如果是1,不发出脉冲
4. UART控制寄存器 UCONn
从低位往高位分析:
接收/接收模式
- 00 关闭,禁止接收
- 01 中断或者查询模式
- 10 ,11 DMA模式
break信号
自环模式
接收错误状态中断
- 0 正常模式
- 1 打开/启用
接收超时中断
- 0 关闭
- 1 打开
发送/接收中断类型
- 0 脉冲
- 1 电平
时钟选择
- 00 10 PCLK
- 01 UEXTCLK
- 11 FCLK/n
PCLK是用于串口等速度较慢的外设的时钟(用于APB 总线设备),FCLK是内核晶振的频率(用于CPU),HCLK用于液晶,内存等高速设备(用于AHB总线设备),UEXTCLK是外接的时钟,用于UART
在2440中,这几个时钟的频率如下
- FCLK <= 400MHz
- HCLK <= 136MHz
- PCLK <= 68MHz
最后如果选择了FCLK/n,那么这个n由FCLK Divider确定。
规则比较复杂:
- UCON2[15] 是使能位,决定是否允许FCLK/n作为时钟源
- n的确定
- n = 7 ~ 21
- n = divider + 6, divider = UCON0[15:12]
- n = 22 ~ 36
- n = divider + 21, divider = UCON1[15:12]
- n = 37 ~ 43
- n = divider + 36, divider = UCON2[14:12]
- n = 44
- UCON0[15:12], UCON1[15:12], UCON2[14:12]都是0
- n = 7 ~ 21
5. FIFO配置(UFCONn寄存器),FIFO状态(UFSTATn)
见下图,含义比较明显,不赘述。可以使用FIFIO队列,也可以不使用。本篇的实例就没有使用FIFO
6. 流量控制(UMCONn),流量状态(UMSTATn)
本篇不涉及。
7. 发送/接收状态(UTRSTATn)
记录这3个状态信息
- 接收缓冲数据就绪,接收到数据时,自动被设置为1
- 0 空
- 1 接收到数据
- 发送缓冲为空, 发送缓冲区内没有数据时,自动设为1
- 0 缓冲不为空
- 1 空
- 发送器空, 发送缓冲区中没有数据,并且最后一个数据也发送出去了,自动设为1
- 0 非空
- 1 空
8. 错误状态(UERSTATn)
4种错误,见表格。
读取这个寄存器时,会自动清0。
9. 发送缓冲寄存器(UTXHn)
CPU将数据写入这个寄存器,UART会立即将它保存到缓冲区中,并自动发送。
10. 接收缓冲寄存器(URXHn)
UART接收到数据时,CPU读取这个寄存器,就可以获得数据。
11.2 UART操作实例
在串口上接收一个字符,然后ASCII + 1,从串口输出
1. UART初始化
#include "s3c24xx.h"
#include "serial.h"
#define TXD0READY (1<<2)
#define RXD0READY (1)
#define PCLK 50000000 // init.c中的clock_init函数设置PCLK为50MHz
#define UART_CLK PCLK // UART0的时钟源设为PCLK
#define UART_BAUD_RATE 115200 // 波特率
#define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
/*
* 初始化UART0
* 115200,8N1,无流控
*/
void uart0_init(void)
{
GPHCON |= 0xa0; // GPH2,GPH3用作TXD0,RXD0
GPHUP = 0x0c; // GPH2,GPH3内部上拉
ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)
UCON0 = 0x05; // 查询方式,UART时钟源为PCLK
UFCON0 = 0x00; // 不使用FIFO
UMCON0 = 0x00; // 不使用流控
UBRDIV0 = UART_BRD; // 波特率为115200
}
2. 发送字符函数
/*
* 发送一个字符
*/
void putc(unsigned char c)
{
/* 等待,直到发送缓冲区中的数据已经全部发送出去 */
while (!(UTRSTAT0 & TXD0READY));
/* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */
UTXH0 = c;
}
3. 接收字符函数
/*
* 接收字符
*/
unsigned char getc(void)
{
/* 等待,直到接收缓冲区中的有数据 */
while (!(UTRSTAT0 & RXD0READY));
/* 直接读取URXH0寄存器,即可获得接收到的数据 */
return URXH0;
}
4. 主函数
#include "serial.h"
int main()
{
unsigned char c;
uart0_init(); // 波特率115200,8N1(8个数据位,无校验位,1个停止位)
while(1)
{
// 从串口接收数据后,判断其是否数字或子母,若是则加1后输出
c = getc();
if (isDigit(c) || isLetter(c))
putc(c+1);
}
return 0;
}