嵌入式Linux应用开发完全手册(四)UART

2019-07-12 16:08发布

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
传输结构
  • 位,最小单元
  • 帧,不可分割的若干位
    • 开始位
    • 数据位
    • 校验位(可选)
    • 停止位
波特率
每一位所需时间的倒数,即每秒可以传输的位数。 波形
Markdown
上图是一个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中读取数据
    Markdown

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寄存器
Markdown
传输格式包括这几个方面的设置
  • 红外模式开关
  • 校验位设置
  • 停止位设置
  • 数据位宽度
其他几项的含义非常直观,不赘述。 红外模式的含义这里按照芯片手册复述一下,来龙去脉并不清楚。
下图是正常的串口波形,高电平是1,低电平是0。
Markdown 下图是红外模式的波形:
Markdown
从图中可以看出,在红外模式中:
  • 发送时,通过宽度是3/16位的脉冲,判断当前位是0,如果没有脉冲,当前位是1
  • 接收时,通过宽度是3/16位的脉冲,发出信号0,如果是1,不发出脉冲
4. UART控制寄存器 UCONn
Markdown 从低位往高位分析:
Markdown
接收/接收模式
  • 00 关闭,禁止接收
  • 01 中断或者查询模式
  • 10 ,11 DMA模式
Markdown
break信号
自环模式
接收错误状态中断
  • 0 正常模式
  • 1 打开/启用
Markdown
接收超时中断
  • 0 关闭
  • 1 打开
发送/接收中断类型
  • 0 脉冲
  • 1 电平
Markdown
时钟选择
  • 00 10 PCLK
  • 01 UEXTCLK
  • 11 FCLK/n
PCLK是用于串口等速度较慢的外设的时钟(用于APB 总线设备),FCLK是内核晶振的频率(用于CPU),HCLK用于液晶,内存等高速设备(用于AHB总线设备),UEXTCLK是外接的时钟,用于UART
在2440中,这几个时钟的频率如下
  • FCLK <= 400MHz
  • HCLK <= 136MHz
  • PCLK <= 68MHz
Markdown
最后如果选择了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
5. FIFO配置(UFCONn寄存器),FIFO状态(UFSTATn)
见下图,含义比较明显,不赘述。可以使用FIFIO队列,也可以不使用。本篇的实例就没有使用FIFO
Markdown
Markdown 6. 流量控制(UMCONn),流量状态(UMSTATn)
本篇不涉及。 7. 发送/接收状态(UTRSTATn)
Markdown
记录这3个状态信息
  • 接收缓冲数据就绪,接收到数据时,自动被设置为1
    • 0 空
    • 1 接收到数据
  • 发送缓冲为空, 发送缓冲区内没有数据时,自动设为1
    • 0 缓冲不为空
    • 1 空
  • 发送器空, 发送缓冲区中没有数据,并且最后一个数据也发送出去了,自动设为1
    • 0 非空
    • 1 空
8. 错误状态(UERSTATn)
Markdown
4种错误,见表格。
读取这个寄存器时,会自动清0。 9. 发送缓冲寄存器(UTXHn)
Markdown
CPU将数据写入这个寄存器,UART会立即将它保存到缓冲区中,并自动发送。 10. 接收缓冲寄存器(URXHn)
Markdown
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; }