5 UART续
1、CPU发送数据的流程:
1)CPU软件上以地址指针的形式将数据放到发送缓冲区
2)发送缓冲区硬件上自动将数据拷贝到发送移位器中
3)发送移位器根据波特率产生器给定的波特率(速率)然后将数据一位一位的放到TX数据先上。
4)问题:CPU软件上以指针的形式将数据放到发送缓冲区的速度要远远快于发送移位器将数据放到TX数据线的速度,如果CPU仅仅是发一字节数据,没问题,但是如果CPU发送多个字节,势必带来发送数据覆盖丢失问题(发送移位器慢慢发送第一字节数据时,CPU已经将第三个字节的数据放到发送缓冲区而覆盖第二个字节)
2、CPU接收数据的流程:
1)首先接收移位器从RX数据线上硬件上自动接收数据,接收数据的速率由波特率产生器来决定
2)然后接收移位器将接收到的数据拷贝到接收缓冲区中
3)最后CPU软件上以指针的形式从接收缓冲区中获取数据
4)问题:CPU软件上以指针的形式访问接收缓冲区的速度要远远快于接收移位器从RX数据线上接收数据的速度
如何保证CPU从接收缓冲区中获取的数据是有效数据呢?
3、发送数据和接收数据都依赖波特率,而波特率依赖时钟源
问:时钟源如何影响波特率呢?
1)RTS/CTS两个信号线
如果使用,UART瞬间数据同步由异步变成同步,将来数据同步靠RTS和CTS来实现,此时改名为USART
目前:X6818下位机RTS和CTS没有连接
2)波特率换算公式P969:
UBRDIVn+UFRACVALn/16=(SCLK_UART/(bps*16))-1
bps:就是要指定的波特率,例如115200
SCLK_UART:UART控制器的时钟源,例如40MHz
UBRDIVn+UFRACVALn/16=(40000000/(115200*16))-1=20.7
UBRDIVn=20(取20.7的整数)
UFRACVALn=0.7*16=11(取整)
结论是:
如果UART的时钟源是40MHz&&UBRDIVn=20&&UFRACVALn=11
最终UART的波特率就是为115200
如果将来修改了UART的时钟源,那么UBRDIVn的值和UFRACVALn的值也会随着改变
问:UBRDIVn和UFRACVALn到底是什么东西?
SCLK_UART时钟源为40MHz如何得到的?
3)涉及的相关重要寄存器如下P974:
ULCON0:控制寄存器
基地址:0xC00A1000
BIT[1:0]=11 //设置数据位的有效位数为8位
BIT[2]=0 //设置停止位有效位数为1位,采用默认值
BIT[5:3]=000//设置不校验,采用默认值
BIT[6]=0 //设置普通的TX/RX模式,采用默认值
UCON0:控制寄存器
基地址:0xC00A1004
BIT[1:0]=01 //设置CPU从接收缓冲区读取数据的方式采用轮询方式
//也就是当CPU发现接收缓冲区的数据无效,CPU原地死等
//直到接收缓冲区的数据有效为止
说明:
中断=interrupt=IRQ=INT
轮询=polling
disable=禁止
enable=使能
loop-back:回环模式,将TX和RX短接,自发自收,用于测试场合
valid:有效
invalid:无效
CLK=clock=时钟
i=input=输入
o=output=输出
SRC=source=源
SEL=select=选择
INV=invert=反转
明确:外设的数据处理速度远远慢于CPU的数据处理速度。计算机中为了能够保证CPU和外设操作数据正常,一般对数据的操作方式有三种:轮询/中断/DMA。这里采用轮询方式,后面两种方式慢慢讲。
“轮询”:就是原地打转,原地死等,如果等待时间长,相当浪费CPU资源
BIT[3:2]=01 //设置CPU将数据放到发送缓冲区的方式采用轮询方式
//将发送缓冲区中数据满了,CPU就原地死等不能再向发送缓冲区写入数据,直到发送缓冲区中空间了
BIT[4]=0 //设置正常传输模式,采用默认值
BIT[5]=0 //设置正常传输模式,采用默认值
BIT[31:6]=无需关注,都是用来配置中断和DMA相关,跟轮询无关系
UTRSTAT0:状态寄存器
基地址:0xC00A1010
BIT[0]=0:表示接收缓冲区没有数据
=1:表示接收缓冲区有有效数据,此BIT自动置1。C语言实现判断接收缓冲区是否有有效数据的代码:
while(!(UTRSTAT0 & 0x01)); //轮询代码
//开始从接收缓冲区读取数据
BIT[1]=0:表示发送缓冲区有数据
=1:表示发送缓冲区为空,此BIT自动置1
C语言实现判断CPU发送数据的代码:
while(!(UTRSTAT0 & 0x2));
//CPU开始向发送缓冲区写入要发送的数
BIT[2]=0:表示发送缓冲区和发送移位器有数据
=1:表示发送缓冲区和发送移位器无数据
C语言实现判断CPU发送数据的代码:
while(!(UTRSTAT0 & 0x4));
//CPU开始向发送缓冲区写入要发送的数
BIT[3:31]都是跟DMA相关,无需关注
UTXH0:发送缓冲区寄存器
基地址:0xC00A1020
BIT[7:0]=要发送的数据
C语言实现将数据放到发送缓冲区中:
UTXH0='A'; //注意:写入之前要判断发送缓冲区是否为空
URXH0:接收缓冲区寄存器
基地址:0xC00A1024
BIT[7:0]=接受到的数据
C语言实现从接收缓冲区获取有效数据:
unsigned char data = URXH0; //注意:从缓冲区获取数据之前判断缓冲区的数据是否有效
UBRDIV0:波特率配置寄存器
基地址:0xC00A1028
BIT[15:0]=值,此值通过第8个知识点的公式来运算
UFRACVAL0:波特率配置寄存器
基地址:0xC00A102C
BIT[3:0]=值,此值通过第8个知识点的公式来运算
由于UARTTXD0和UARTRXD0两个引脚具有复用功能,还需配置复用功能选择寄存器:
GPIODALTFN1:复用功能选择寄存器
基地址:0xC001D024
BIT[5:4]=01 //配置为UARTTXD0功能,作为UART的发送数据引脚功能
GPIODALTFN0:复用功能选择寄存器
基地址:0xC001D020
BIT[29:28]=01 //配置为UARTRXD0功能,作为UART的接收数据引脚功能
问:换算波特率时,使用的40MHz的时钟怎么来的?
答:答案在P305
结论:UART时钟源40MHz=系统输入时钟源/n
问:系统输入时钟源为多少呢?
答:通过UART时钟配置相关的寄存器来获取答案P313
UARTCLKENB:UART时钟使能寄存器
基地址:0xC00A9000
BIT[2]=0:关闭UART时钟
=1:使能UART时钟
UARTCLKGEN0L:UART时钟配置寄存器
基地址:0xC00A9004
BIT[4:2]=00,选择PLL[0]为UART的系统输入时钟源,PLL[0]=800MHz
然后根据以上公式得到:n=800MHz/40MHz=20
问:n如何生效呢?
BIT[12:5]=CLKDIV0
n=CLKDIV0+1=20=>CLKDIV0=19
也就是如果系统输入的时钟源为800MHz,并且BIT[12:5]=19
那么将来UART的时钟源就等于40MHz=800MHz/(19+1)
BIT[15]:依赖BIT[4:2]:注意:BIT位之前还会进行依赖!
至此:UART硬件信息掌握完毕!
4、硬件信息掌握完毕,编辑交叉编译UART裸板程序
1)内心明确裸板程序的编程框架
void xxx(void)
{
//硬件初始化
xxx_init();
while(1)
{
//根据用户需求完成硬件操作
}
}
uart.h
#ifndef __UART_H
#define __UART_H
/*寄存器定义*/
#define ULCON0 (*(volatile unsigned long *)0xC00A1000)
#define UCON0 (*(volatile unsigned long *)0xC00A1004)
#define UTRSTAT0 (*(volatile unsigned long *)0xC00A1010)
#define UTXH0 (*(volatile unsigned long *)0xC00A1020)
#define URXH0 (*(volatile unsigned long *)0xC00A1024)
#define UBRDIV0 (*(volatile unsigned long *)0xC00A1028)
#define UFRACVAL0 (*(volatile unsigned long *)0xC00A102c)
#define GPIODALTFN1 (*(volatile unsigned long *)0xC001D024)
#define GPIODALTFN0 (*(volatile unsigned long *)0xC001D020)
#define UARTCLKENB (*(volatile unsigned long *)0xC00A9000)
#define UARTCLKGEN0L (*(volatile unsigned long *)0xC00A9004)
/*声明操作函数*/
void uart_init(void);
void uart_putc(char c);
void uart_puts(char *str);
#endif
uart.c
#include "uart.h"
/*初始化函数定义*/
void uart_init(void)
{
//1.将TX和RX的引脚配置为UARTRXD0和UARTTXD0功能
GPIODALTFN1 &= ~(3 << 4);
GPIODALTFN1 |= (1 << 4);
GPIODALTFN0 &= ~(3 << 28);
GPIODALTFN0 |= (1 << 28);
//3.指定UART的时钟为50MHz
//CLKDIV0=n-1(n=800MHz/50MHz)=15=0xF
UARTCLKGEN0L &= ~(0xFF << 5);
UARTCLKGEN0L |= (0xF << 5);
//4.配置UART的波特率为115200
//UBRDIV0+UFRACVAL0/16=(50000000/(115200*16))-1
//UBRDIV0+UFRACVAL0/16=26.126
//UBRDIV0=26
//UFRACVAL0=0.126*16=2
UBRDIV0 = 26;
UFRACVAL0 = 2;
//5.配置UART工作模式为轮循模式
UCON0 = 5;
//6.配置UART的数据位为8,停止位1位,不采用校验
ULCON0 = 3;
//2.使能UART时钟
UARTCLKENB |= (1 << 2);
}
/*发送字符一字节函数定义*/
void uart_putc(char c)
{
//1.首先判断发送缓冲区收否为空
//判断UTRSTAT0[1]=0(有数)/1(空)
while(!(UTRSTAT0 & 0x2));
//2.将发送的数据放到发送缓冲区
UTXH0 = c;
//3.判断是否需要发送'\r'
if(c == '\n')
uart_putc('\r');
}
/*发送字符串函数定义*/
void uart_puts(char *str)
{
//1.循环发送一个一个字符
while(*str) {
uart_putc(*str);
str++;
}
}
main.c
#include "uart.h"
void main(void)
{
//初始化UART
uart_init();
//根据用户需求完成硬件操作
while(1) {
uart_puts("hello,PC\n"); //下位机重复给上位机发送字符串
}
}
2)裸板程序的特点
单文件
单任务
不基于操作系统
不能用标准C库的函数
3)上位机编辑交叉编译UART裸板程序
mkdir /opt/arm/day04/1.0 -p
cd /opt/arm/day04/1.0
vim uart.h //声明
vim uart.c //定义
vim main.c //调用
arm-cortex_a9-linux-gnueabi-gcc -nostdlib -c -o uart.o uart.c
arm-cortex_a9-linux-gnueabi-gcc -nostdlib -c -o main.o main.c\
arm-cortex_a9-linux-gnueabi-ld -nostartfiles -nostdlib
-Ttext=0x48000000 -emain -o uart.elf main.o uart.o
arm-cortex_a9-linux-gnueabi-objcopy -O binary uart.elf uar.bin
arm-cortex_a9-linux-gnueabi-objdump -D uart.elf > uart.dis
vim uart.dis //确保0x48000000地址对应的函数为入口函数
cp uart.bin /tftpboot
下位机测试:
重启下位机,进入uboot命令行执行:
tftp 0x48000000 uart.bin
go 0x48000000 //此时此刻观察上位机SecureCRT的打印信息
小问题:
arm...ld .... uart.o main.o //能够将uart.o放置在main.o的前面呢
通过反汇编确认内心的答案!
'\n':换行
'\r':回车
思考:如何实现下位机通过UART获取到上位机发送的数据,并且下位机将上位机发送的数据在返回给上位机打印