一、UART帧格式详解
UART
Universal Asynchronous Receiver Transmitter 即 通用异步收发器,是一种通用的串行、异步通信总线 该总线有两条数据线,可以实现全双工的发送和接收,在嵌入式系统中常用于主机与辅助设备之间的通信。
通信基础 - 并行和串行
并行通信:总线在传送数据的时候,可以一次性发送多位数据。
串行通信:数据线只有一根,逐次传送各位数据
在同等条件下,并行比串行的通信速度更快,但并行使用的总线数量较多,会比较浪费资源,布线难度也比较大,不同总线在传输数据时,线和线之间都会有一些信号的干扰。做项目时,使用串行通信多一点
通信基础 - 单工和双工
单工通信:通信的双方区分为发送器和接收器,数据传输的方向是单向的
双工通信: 分为半双工和全双工,它们数据的传输都是双向的。半双工通信时,A和B可以互相发数据,但不能是同时的,而全双工可以同时进行。
如果总线的数据线只有一根,一般是半双工的,如果有多根,一般是全双工。
通信基础 - 波特率
波特率用于描述UART通信时的通信速度,其单位为bps(bit per second)即每秒钟传送的bit的数量
UART帧格式
起始位表示一次通信的开始,数据位就是通过串口发送的数据,校验位会校验(只能校验,不能修正)数据发送的正确性,停止位表示一次通信的结束。如果想发送多个数据,重复这个步骤就可以
注: 1、数据线在空闲时,数据线上的状态必须是高电平。
2、发送数据时,先发低位数据。
3、串口每次只能发送一个字节, 是为了避免产生累计误差(发送方与接收方的时间误差)。
串口一般为奇偶校验。奇偶校验(Parity Check)是一种校验代码传输正确性的方法。根据被传输的一组二进制代码的数位中“1”的个数是奇数或偶数来进行校验。采用奇数的称为奇校验,反之,称为偶校验。采用何种校验是事先规定好的。通常专门设置一个奇偶校验位,用它使这组代码中“1”的个数为奇数或偶数。若用奇校验,则当接收端收到这组代码时,校验“1”的个数是否为奇数,从而确定传输代码的正确性。
UART硬件连接
两个芯片通信时,要交叉接线,一方的TXD要与另一方的RXD连接,
UART控制器
一般情况下处理器中都会集成UART控制器 我们使用UART进行通信时候只需对其内部的相 关寄存器进行设置即可。
二、Exynos4412下的 UART控制器
引脚功能设置
注:设置引脚功能的实质是让引脚在芯片内部连接到某一个对应的控制器上
串口的高低电平信号较弱,极有可能收到干扰,通信距离较短。为了增强串口的信号,在中间加了一个U3芯片,把串口发出来的TTL信号转化为232信号。
设置引脚功能本质上就是让引脚连接对应的控制器
UART在4412内部提供了四个独立的通道,每个通道都包含输入输出端口,这4和通道时Ch0-3,另外还提供了一个专用通道Ch4,用于跟GPS通信(本次使用的是ch2)。所有的端口都可以运行中断和DMA模式。在CPU和串口控制器进行数据传送时,UART既可以产生中断,也可以产生DMA的一些请求。UART支持的波特率最大是4Mbps。每个串口通道都有两个FIFO用于接收和发送数据,
想要发送数据时,只需要将发送内容写入FIFO(队列)就会自动发送,接收时也只需要读取FIFO(队列)内容即可。
串口的波特率是可以编程的
支持红外传输,(无线)
1位或者两位停止位
数据位可以是5-8位,还可以有校验位
波特率发生器、发送器、接收器、控制单元
波特率的发生器使用SCLK_UART时钟,他的频率时100M,发送器和接收器都包含队列和移位器。要被发送的数据会先写到发送队列里,如何数据会被拷贝到发送的移位器。数据通过发送数据的引脚被移出去。接收的数据从接收数据的引脚移进来,被拷贝到接收的缓冲区里。
数据通过FIFO->移位器->引脚实现发送,通过引脚->移位器->FIFO实现接收。
三、UART寄存器详解
先用tar xvf命令解压出一个新工程
1、将GPA1_0和GPA1_1分别设置成UART2的接收引脚和发送引脚 GPA1CON[7:0]
2、设置UART2的帧格式 ULCON2 (8位数据位、1位停止位、无校验位、正常模式)
回环模式就是将发送端和用户端短接,一般用于测试。
中断模式:有消息通知CPU
轮询模式:CPU不断的读取缓冲区,查看有没有数据
DMA:自动传送给内存,解放CPU
AFC:自动流控制,这次实验就是普通的收发暂时不需要
3、设置UART2的接收和发送模式为轮询模式 UCON2[3:0]
只读,[1]表示发送的队列是空的,[0]表示接收的队列有数据
把要发送的数据写到UTXHn寄存器的[7:0]位
接收器接收到的数据会放入URXHn寄存器的[7:0]位
UBRDIVn寄存器的[15:0]位和UFRACVALn寄存器的[3:0]位是设置波特率的
4、设置UART2的波特率为115200 (UBRDIV2 / UFRACVAL2)
四、UART编程
#include "exynos_4412.h"
int main()
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收引脚和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 ULCON2 (8位数据位、1位停止位、无校验位、正常模式)*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:2]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 (UBRDIV2 / UFRACVAL2)*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
while(1)
{
/*将发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = 'A';
}
return 0;
}
#include "exynos_4412.h"
int main()
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收引脚和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 ULCON2 (8位数据位、1位停止位、无校验位、正常模式)*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:2]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 (UBRDIV2 / UFRACVAL2)*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
while(1)
{
/*将发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = 'A';
UART2.UTXH2 = 'B';
UART2.UTXH2 = 'C';
UART2.UTXH2 = 'D';
}
return 0;
}
并没有输出想要的结果,因为CPU的执行速度是1GHz,而发送器的速度是115200,两者的速度不一样,所以发送的字符是随机的。
#include "exynos_4412.h"
int main()
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收引脚和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 ULCON2 (8位数据位、1位停止位、无校验位、正常模式)*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:2]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 (UBRDIV2 / UFRACVAL2)*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
while(1)
{
/*将发送的数据写入发送寄存器 UTXH2*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
UART2.UTXH2 = 'A';
while(!(UART2.UTRSTAT2 & (1 << 1)));
UART2.UTXH2 = 'B';
while(!(UART2.UTRSTAT2 & (1 << 1)));
UART2.UTXH2 = 'C';
while(!(UART2.UTRSTAT2 & (1 << 1)));
UART2.UTXH2 = 'D';
}
return 0;
}
#include "exynos_4412.h"
void UART_Init(void)
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收引脚和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 ULCON2 (8位数据位、1位停止位、无校验位、正常模式)*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:2]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 (UBRDIV2 / UFRACVAL2)*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
}
void UART_Send_Byte(char Dat)
{
/*等待发送寄存器寄存器为空*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
/*将发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(void)
{
char Dat;
/*判断接收寄存器是否接收到了数据*/
if(UART2.UTRSTAT2 & 1)
{
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
int main()
{
char RecDat = 0;
UART_Init();
while(1)
{
RecDat = UART_Rec_Byte();
if(RecDat == 0)
{
}
else
{
RecDat = RecDat + 1;
UART_Send_Byte(RecDat);
}
}
return 0;
}
输入什么返回什么加一(以ASCII计算)
五、输入输出重定向
#include "exynos_4412.h"
void UART_Init(void)
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收引脚和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 ULCON2 (8位数据位、1位停止位、无校验位、正常模式)*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:2]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 (UBRDIV2 / UFRACVAL2)*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
}
void UART_Send_Byte(char Dat)
{
/*等待发送寄存器寄存器为空*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
/*将发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(void)
{
char Dat;
/*判断接收寄存器是否接收到了数据*/
if(UART2.UTRSTAT2 & 1)
{
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
void UART_Send_Str(char * pstr)
{
while(*pstr != '\0')
UART_Send_Byte(*pstr++);
}
int main()
{
char RecDat = 0;
UART_Init();
while(1)
{
UART_Send_Str("Hello World\n");
}
return 0;
}
#include "exynos_4412.h"
void UART_Init(void)
{
GPA1.CON = GPA1.CON & (~(0xFF)) | (0x22);
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F)) | (0x3);
UART2.UCON2 = UART2.UCON2 & (~(0xF)) | (0x5);
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
}
void uart_send_byte(char Dat)
{
while(!(UART2.UTRSTAT2 & (1 << 1)));
UART2.UTXH2 = Dat;
}
char uart_recv_byte(void)
{
char Dat = 0;
if(UART2.UTRSTAT2 & 1)
{
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
void uart_send_str(char * pstr)
{
while(*pstr != '\0')
uart_send_byte(*pstr++);
}
int main()
{
char RecDat = 0;
UART_Init();
while(1)
{
printf("Hello World\n");
}
return 0;
}
我们也可以把printf封装一下直接调用,但是这时的输出和以前的不同,以前的是Linux为我们提供的C库,它将输出重定向到显卡,所以我们能在屏幕上看到,而这个printf是我们自己写的,它重定向到了串口,所以我们使用串口软件连接单片机时能打印出来
作业
1.若使用UART协议发送一个字节的数据0x63,画出信号线上的时序图
注:8位数据位、无校验位、一位停止位
2.编程实现电脑远程控制LED状态
注:在终端上输入‘2’,LED2点亮,再次输入‘2’,LED2熄灭... ...
#include "exynos_4412.h"
void UART_Init(void)
{
/*1.将GPA1_0和GPA1_0_1置成ART2的接收引脚和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 ULCON2(8位数据位、1位停止位、无校验位、正常模式)*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:2]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 (UBRDIV2 / UFRACVAL2)*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
}
void GPIO_Init(void)
{
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
}
void UART_Send_Byte(char Dat)
{
/*等待发送寄存器寄存器为空*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
/*将发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(void)
{
char Dat;
/*判断接收寄存器是否接收了数据*/
if(UART2.UTRSTAT2 & 1)
{
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
void UART_Send_Str(char * pstr)
{
while(*pstr != '\0')
UART_Send_Byte(*pstr++);
}
void OnLED2(void)
{
GPX2.DAT = GPX2.DAT | (1 << 7);
}
void OffLED2(void)
{
GPX2.DAT = GPX2.DAT & (~(1 << 7));
}
int main()
{
char RecDat = 0;
UART_Init();
GPIO_Init();
while(1)
{
RecDat = UART_Rec_Byte();
if(RecDat == '2')
{
if(GPX2.DAT & (1 << 7))
{
OffLED2();
}
else
{
OnLED2();
}
}
else
{
}
}
return 0;
}