ARM裸板开发——UART通信方式及使用


UART相关描述及概念

计算机中常见的几种硬件通信方式

计算机中CPU和外设进行硬件通信的方式有以下几种:

  • GPIO通信方式,如:LED等、蜂鸣器等。
  • UART串口通信方式,如:GPS、GPRS等等。
  • I2C总线通信方式,如:重力传感器、加速度传感器、触摸屏等。
  • SPI总线通信方式,如:norflash闪存、触摸屏等、
  • 1-wire(一线式)总线通信方式,例如:温度传感器、EEPROM存储器等。

UART串口的定义

UART串口定义:通用串行异步收发器
通用: UART串口应用非常广泛
串行: CPU和外设进行数据通信时,只需一根信号线即可,此信号线又称数据线,也就是说CPU和外设进行数据通信时,是一个bit位一个bit位的传输:

  • 切记:UART数据传输从低位开始!例如:CPU向BT发送一个0x95这个数据
    • 数据线的操作如下:
  高->低->高->低->高->低->低->高
  1   0   1   0   1   0    0  1

在这里插入图片描述

并行: CPU和外设数据传输时,需要多根信号线(数据线),8/16/32根,那么也就是一次数据传输可以同时传输8bit/16bit/32bit。
异步: CPU的数据处理速度要远远快于外设,所以CPU和外设进行数据传输时,务必要考虑数据同步。双方在数据正式传输时,只要保证数据同步即可,传输前和传输以后,无需考虑数据同步。

  • 数据同步: CPU向设备发送数据以后,要确保外设能够 正常的将数据接收到,接收完整! 计算机中数据同步的方法有两种:异步和同步。
    • 异步定义: 双方在数据正式传输时,只要保证数据同步即可,传输前和传输以后,无需考虑数据同步。
    • 同步定义: CPU和外设进行数据传输时,如果采用同步方式保证数据同步,那么CPU和外设之间不仅仅有数据线,还需要一根时钟控制信号线,此信号线就是用来实现双方的数据同步。
      在这里插入图片描述

收发器: 接收和发送数据的硬件单元 。

UART串口的三种工作方式

单工: 数据传输永远只能一个方向。
半双工: 数据传输可以两个方向(可以收也可以发,但不能同时进行)
全双工: 数据传输可以同时双向进行,CPU和外设数据传输需要两根数据线,一根用于发送(TX),一根用于接收(RX),一定要记得连接GND。

UART协议相关概念

如何利用异步实现UART串口的数据同步?

  • 在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位。

在这里插入图片描述发送端

  • CPU首先将0x95通过TX数据线发送给BT。
  • CPU计算数据0x95中1的个数(4个,4为偶数)
  • 由于4是一个偶数,而数据校验采用奇校验,所以CPU最后在发送一个校验位(高电平1)这样加起来高电平个数为5个(奇数),满足了奇校验。
  • 结论:校验位为高电平。

发送端 BT接收数据并且判断数据是否正确

  • BT从TX上接收数据0x95
  • BT再从TX上接收CPU发送来的校验位(高电平1)
  • BT先算接收的数据0x95中的1的个数为4,并且采用奇校验来验证,1的个数4和校验位高电平相加符合奇校验。
  • BT发现CPU发送的校验位满足,BT确认CPU发送的数据没问题。

ARM裸板操作UART串口进行数据输出

首先查看原理图,在其中找到UART0对应的连接线。UART0其实就是我们在板子上使用的调试串口,所以UART0从CPU发送数据给PC上位机,上位机通过CRT串口调试能够正常接收到发送的数据。连接图如下:

在这里插入图片描述此时,说明通过CPU核访问操作UARTTXD0和UARTRXD0来实现UART操作。具体如下图:

在这里插入图片描述

  • CPU核通过地址指针的形式访问UART控制器内部的一大堆寄存器,也就是CPU核通过软件给UART控制器发送命令。
  • UART控制器根据命令,硬件上自动操作UARTTXD0和UARTRXD0两个引脚。
  • 最终影响外设。
  • 具体如何配置UART控制器和内部寄存器需要查看芯片手册了。

查看芯片手册UART相关内容

支持6路UART

每一个UART内部都有一个接收缓冲区和发送缓冲区,大小为64字节, 如果UART控制器工作在FIFO模式,缓冲区最大为64字节, 如果UART控制器工作在非FIFO模式,缓冲区为1字节, 此时此刻采用非FIFO模式!

UART控制器内部集成了发送器

而发送器内部又集成了发送缓冲区寄存器和发送移位器:
CPU核发送数据的流程:

  • 1.CPU核以指针的形式将数据放到发送缓冲区寄存器中进行缓存。
  • 2.然后移位器硬件上自动从发送缓冲区中获取数据,然后将数据一位一位的放到TX数据线上。
  • 3.移位器发送数据的频率由波特率产生器决定

UART控制器内部还集成了接收器

而接收器内部又集成了接收移位器和接收缓冲区
CPU核接收数据的流程:

  • 1.接收移位器硬件上自动从RX数据线上一位一位的将数据收集起来接收数据的频率又波特率产生器决定。
  • 2.然后接收移位器硬件上自动将接收的数据放到接收缓冲区中。
  • 3.CPU核只需以地址指针的形式访问接收缓冲区寄存器即可,获取接收到的数据 。

波特率配置P969

UBRDIVnUFRACVALn两个寄存器的值共同决定这波特率的值;
公式:
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即可。

  • SCLK_UART是什么?
    • 答:SCLK_UART为UART控制器的时钟源

UART控制器性格的寄存器

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

  • 这里需要设置UART_CLK=50MHz
  • UART_CLK=50MHZ=800MHZ/n
    =>n=16=CLKDIV0+1=>CLKDIV0=15=0xF

代码示例:

  • uart.c
#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++;
    }
}

  • 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 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

  • main.c
#include "uart.h"

void main(void)
{
    //初始化UART
    uart_init();

    while(1) {
        uart_puts("hello,world\n");
    }
}

  • Makefile
#定义变量
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)   


发布了78 篇原创文章 · 获赞 25 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_37596943/article/details/103848365
今日推荐