计算机中CPU和外设进行硬件通信的方式有以下几种:
UART串口定义:通用串行异步收发器
通用: UART串口应用非常广泛
串行: CPU和外设进行数据通信时,只需一根信号线即可,此信号线又称数据线,也就是说CPU和外设进行数据通信时,是一个bit位一个bit位的传输:
高->低->高->低->高->低->低->高
1 0 1 0 1 0 0 1
并行: CPU和外设数据传输时,需要多根信号线(数据线),8/16/32根,那么也就是一次数据传输可以同时传输8bit/16bit/32bit。
异步: CPU的数据处理速度要远远快于外设,所以CPU和外设进行数据传输时,务必要考虑数据同步。双方在数据正式传输时,只要保证数据同步即可,传输前和传输以后,无需考虑数据同步。
收发器: 接收和发送数据的硬件单元 。
单工: 数据传输永远只能一个方向。
半双工: 数据传输可以两个方向(可以收也可以发,但不能同时进行)
全双工: 数据传输可以同时双向进行,CPU和外设数据传输需要两根数据线,一根用于发送(TX),一根用于接收(RX),一定要记得连接GND。
如何利用异步实现UART串口的数据同步?
空闲位: CPU和外设不再进行数据传输时,数据线上一直发送空闲位,高电平(1)有效。
起始位: CPU和外设进行数据传输时,首先在数据线上要发送一个起始位信号,有效位数:1个bit位,低电平有效。
数据位: 指定有效的数据位数,选择如下:5 / 6 / 7 / 8一般选择为8位。
奇偶校验位: 指示数据传输时是否发生错误,有效的校验位数:1个bit位。对数据的校验方式有三种:奇校验、偶校验、不校验:无需发送校验位。这里以CPU向BT发送0X95数据为例,采用奇校验方式检查数据传世是否发生错误。
注意:CPU和BT必须都是奇校验方式。
停止位: 指示数据传输结束,有效位数 :1 bit或者2bit,有效电平为高电平。
波特率: 指示双方数据传输的速率。本质就是CPU和外设数据传输指定一个步调,波特率由外设来指定。例如:波特率为115200bps表示一秒钟CPU和外设传输115200个bit位。
发送端 BT接收数据并且判断数据是否正确
首先查看原理图,在其中找到UART0对应的连接线。UART0其实就是我们在板子上使用的调试串口,所以UART0从CPU发送数据给PC上位机,上位机通过CRT串口调试能够正常接收到发送的数据。连接图如下:
此时,说明通过CPU核访问操作UARTTXD0和UARTRXD0来实现UART操作。具体如下图:
每一个UART内部都有一个接收缓冲区和发送缓冲区,大小为64字节, 如果UART控制器工作在FIFO模式,缓冲区最大为64字节, 如果UART控制器工作在非FIFO模式,缓冲区为1字节, 此时此刻采用非FIFO模式!
而发送器内部又集成了发送缓冲区寄存器和发送移位器:
CPU核发送数据的流程:
而接收器内部又集成了接收移位器和接收缓冲区
CPU核接收数据的流程:
UBRDIVn和UFRACVALn两个寄存器的值共同决定这波特率的值;
公式:
UBRDIVn+UFRACVALn/16=(SCLK_UART/(bps*16))-1
假设SCLK_UART=40MHz=40000000,将来波特率为115200得到:
UBRDIVn+UFRACVALn/16=(4000000/115200*16)-1
UBRDIVn+UFRACVALn/16=20.7
UBRDIVn=20(取整)
UFRACVALn=0.7*16(取整)=11
也就是说:如果将来SCLK_UART=40MHz,并且设置的波特率为115200,那么只需向寄存器UBRDIVn写20,UFRACVALn写11即可。
ULCON0:配置寄存器
基地址:0xC00A1000
BIT[1:0]=11 //设置数据位为8位
BIT[2]=0 //停止位为1位
BIT[5:3]=000 //不校验
BIT[6]=0 //普通的UART
UCON0:控制寄存器
基地址:0xC00A1004
注意:CPU访问外设的数据三种操作方式:
1.轮训方式(polling)
当CPU访问外设数据时,如果发现外设数据没有
准备就绪,那么CPU就会原地忙等待(死等),
直到外设准备好数据
2.中断方式(interrupt=IRQ=INT)
暂不解释,后面驱动章节会说明
3.DMA方式
暂不解释,后面驱动章节会说明
此时此刻,对UART的数据操作采用轮训方式!
BIT[3:0]=0101 //采用轮训方式
BIT[4]=0 //正常传输数据
BIT[5]=0 //正常传输
#回环模式:自发自收,测试时常用
UTRSTAT0:状态寄存器
基地址:0xC00A1010
切记:CPU向发送缓冲区放数和从接受缓冲区读数的
速度要远远快于发送移位器将数据放到TX
和接受移位器从RX接收数据的速度!
BIT[0]=0:接收缓冲区为空
=1:接受缓冲区有有效数据
CPU判断是否准备就绪的代码:
while(!(UTRSTAT0 & 0x1));
//开始读取数据
BIT[1]=0:发送缓冲区中有数据
=1: 发送缓冲区为空
CPU判断数据是否发送完毕的代码:
while(!(UTRSTAT0 & 0x2));
//开始发下一个数据
UTXH0:发送缓冲区寄存器
基地址:0xC00A1020
BIT[7:0]=保存要发送的数
URXH0:接受缓冲区寄存器
基地址:0xC00A1024
BIT[7:0]=保存接受到的数据
UBRDIV0:波特率配置寄存器你
基地址:0xC00A1028
根据公式进行换算
UFRACVAL0:波特率配置寄存器
基地址:0xC00A102C
根究公式进行换算
UARTCLKENB:UART时钟使能寄存器
基地址:0xC00A9000
BIT[2]=1 //使能UART时钟
UARTCLKGEN0L:UART时钟配置寄存器
基地址:0xC00A9004
BIT[4:2]=000 UART时钟源为CPU的PLL[0]=800MHZ
#include "uart.h"
//UART初始化函数
void uart_init(void)
{
//0.禁止UART时钟
//UARTCLKENB[2]=0
UARTCLKENB &= ~(1 << 2);
//1.配置UART0使用的两个管脚的功能
//UARTRXD0
//GPIOD_ALTFN0[29:28]=00
GPIOD_ALTFN0 &= ~(0x3 << 28);
//GPIOD_ALTFN0[29:28]=01
GPIOD_ALTFN0 |= (1 << 28);
//为UARTTXD0
//GPIOD_ALTFN1[5:4]=00
GPIOD_ALTFN1 &= ~(0x3 << 4);
//GPIOD_ALTFN1[5:4]=01
GPIOD_ALTFN1 |= (1 << 4);
//2.配置UART的工作时钟为50MHz
//PLL1(800MHz)/(0xF + 1) = 50MHz
//UARTCLKGEN0L[4:2]=000
//UARTCLKGEN0L[12:5]=00000000
UARTCLKGEN0L &= ~((7 << 2) | (0xFF << 5));
//UARTCLKGEN0L[4:2]=001
//UARTCLKGEN0L[12:5]=00001111
UARTCLKGEN0L |= ((1 << 2) | (0xF << 5));
//3.配置UART的工作参数
//波特率115200:根据公式进行配置即可
//数据位8,奇偶校验:None,停止位1
ULCON0 = 3;//115200,8,N,1
UCON0 = 5; //轮循方式
UBRDIV0=26;//50000000/115200*16-1取整
UFRACDIV0=2;//(26.12-26)*16=2.02取整
//4.打开UART时钟,即可使用UART
//UARTCLKENB[2]=1
UARTCLKENB |= (1 << 2);
}
//发送字符函数
void uart_putc(char c)
{
//1.由于CPU的处理速度远远快与UART
//控制器发送数据的速度,发送之前先
//判断发送缓冲区是否为空
//UTRSTAT0[1]=0:有数
//UTRSTAT0[1]=1:空
while(!(UTRSTAT0 & 0x2));
//2.正式发送数据
UTXH0 = c; //将数据塞到发送缓冲区
//3.判断是否需要发回车字符
if (c == '\n')
uart_putc('\r');
}
//发送字符串函数
//"hello,world"
void uart_puts(char *str)
{
while(*str) {
uart_putc(*str);
str++;
}
}
#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 UFRACDIV0 (*(volatile unsigned long *)0xC00A102C)
#define GPIOD_ALTFN0 (*(volatile unsigned long *)0xC001D020)
#define GPIOD_ALTFN1 (*(volatile unsigned long *)0xC001D024)
/*UART时钟配置相关寄存器*/
#define UARTCLKENB (*(volatile unsigned long *)0xC000A900)
#define UARTCLKGEN0L (*(volatile unsigned long *)0xC000A904)
/*声明相关函数*/
//初始化UART函数
extern void uart_init(void);
//发送单个字节数据
extern void uart_putc(char c);
//发送字符串函数
extern void uart_puts(char *str);
#endif
#include "uart.h"
void main(void)
{
//初始化UART
uart_init();
while(1) {
uart_puts("hello,world\n");
}
}
#定义变量
NAME=uart_demo
BIN=$(NAME).bin
ELF=$(NAME).elf
OBJ=main.o uart.o
CROSS_COMPILE=arm-cortex_a9-linux-gnueabi-
CC=$(CROSS_COMPILE)gcc
LD=$(CROSS_COMPILE)ld
OBJCOPY=$(CROSS_COMPILE)objcopy
CP=cp
RM=rm
INSTALLPATH=/tftpboot
#链接选项
LDFLAGS=-nostdlib -nostartfiles -emain
#编译选项
CFLAGS=-nostdlib
#定义编译规则
#shell.bin:shell.elf
# arm...objcopy -O binary shell.elf shell.bin
$(BIN):$(ELF)
$(OBJCOPY) -O binary $(ELF) $(BIN)
$(CP) $(BIN) $(INSTALLPATH)
#shell.elf:main.o uart.o led.o strcmp.o
# arm...ld -nostartfiles -nostdlib -... -o shell.elf main.o ...
$(ELF):$(OBJ)
$(LD) $(LDFLAGS) -o $(ELF) $(OBJ)
#各种.o:各种.c
# 各种编译
%.o:%.c
$(CC) $(CFLAGS) -c -o $@ $<
#伪目标
#当执行make clean时,仅仅执行clean伪目标对应的命令
clean:
$(RM) $(BIN) $(ELF) $(OBJ)