一、UART相关介绍
UART是我们常用的全双工异步串行总线,常用TTL电平标准,由TXD和RXD两根收发数据线组成。
那么,利用硬件描述语言实现UART对应的电路和51单片机内部配合寄存器实现的电路到底有何区别呢?接下来我们对照看一下。
二、FPGA实现UART异步串行收发方式
对于FPGA而言,利用verilog实现这个过程,其实最重要的就是安排好对应的计数器并配合移位寄存器串并转换即可完成。
FPGA发送端对应的代码如下:
module uart_tx(
input wire clk ,
input wire rst_n ,
input wire [7:0] pi_data ,
input wire pi_flag ,
output reg tx
);
reg [7:0] pi_data_reg;//用于寄存传进来的数据
reg tx_flag ;
reg [12:0] cnt_baud ;
reg bit_flag1;
reg [3:0] bit_cnt ;
//在接收到pi_flag为高电平的时候寄存输入数?
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
pi_data_reg <= 'd0;
else if(pi_flag == 1'b1)
pi_data_reg <= pi_data ;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_flag <= 1'b0;
else if(bit_flag1 == 1'b1 && bit_cnt == 'd8)
tx_flag <= 1'b0;
else if(pi_flag == 1'b1)
tx_flag <= 1'b1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_baud <= 'd0;
else if(tx_flag == 1'b0)
cnt_baud <= 'd0;
else if(cnt_baud == 'd5207 && tx_flag == 1'b1)
cnt_baud <= 'd0;
else if(tx_flag == 1'b1)
cnt_baud <= cnt_baud + 1'b1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
bit_flag1 <= 1'b0;
else if(cnt_baud == 5206)
bit_flag1 <= 1'b1;
else
bit_flag1 <= 1'b0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
bit_cnt <= 'd0;
else if(bit_cnt == 'd8 && bit_flag1 == 1'b1)
bit_cnt <= 'd0;
else if(bit_flag1 == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
tx <= 1'b1;//空闲状态
else if(pi_flag == 1'b1)
tx <= 1'b0;//起始?
else if(bit_flag1 == 1'b1 && bit_cnt <= 'd7)
tx <= pi_data_reg[bit_cnt];//8位数?
else if(bit_cnt == 'd8 && bit_flag1 == 1'b1)
tx <= 1'b1;//空闲?
end
endmodule
FPGA接收端对应的代码如下:
module uart_rx(
input wire clk ,
input wire rst_n ,
input wire rx ,
output reg [7:0] po_data ,
output reg po_flag
);
//用于打拍的寄存器
reg rx1;
reg rx2;
reg rx2_reg ;
reg rx_flag ;
reg [12:0] cnt_baud ;
reg bit_flag ;
reg [3:0] bit_cnt ;
//对接收到的外部信号进行打拍寄存
always@(posedge clk)begin
rx1 <= rx ;
rx2 <= rx1 ;
rx2_reg <= rx2 ;
end
//检测下降沿,拉高rx_flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_flag <= 1'b0;
end
else if (rx2 == 1'b0 && rx2_reg == 1'b1) begin
rx_flag <= 1'b1;
end
else if(bit_flag == 1'b1 && bit_cnt == 'd8)
rx_flag <= 1'b0;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_baud <= 'd0;
else if(rx_flag == 1'b0)
cnt_baud <= 'd0;
else if(cnt_baud == 'd5207 && rx_flag == 1'b1)//5207为波特率是9600时对应的计数值
cnt_baud <= 'd0;
else if(rx_flag == 1'b1)
cnt_baud <= cnt_baud + 1'b1;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
bit_flag <= 1'b0;
else if(cnt_baud == 'd2603 && rx_flag == 1'b1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
bit_cnt <= 'd0;
else if(bit_cnt == 'd8 && bit_flag == 1'b1)
bit_cnt <= 'd0;
else if(bit_flag == 1'b1 && rx_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
po_data <= 'd0;
else if(bit_flag == 1'b1 && bit_cnt >= 'd1)
po_data <= {rx2_reg,po_data[7:1]};
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
po_flag <= 1'b0 ;
else if(bit_flag == 1'b1 && bit_cnt == 'd8)
po_flag <= 1'b1 ;
else
po_flag <= 1'b0;
end
endmodule
顶层代码如下:
module top_uart(
input wire clk ,
input wire rst_n ,
input wire rx ,
output wire tx
);
wire [7:0] po_data ;
wire po_flag ;
uart_rx inst_uart_rx (
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.po_data(po_data),
.po_flag(po_flag)
);
uart_tx inst_uart_tx (
.clk(clk),
.rst_n(rst_n),
.pi_data(po_data),
.pi_flag(po_flag),
.tx(tx));
endmodule
经过综合后,生成的对应原理图如下:
顶层:
tx:可以发现,这里其实每一位寄存器都对应着一个D触发器,剩余的逻辑由LUT实现。
rx:接收端同理。
三、单片机内部UART异步串行收发实现方式
对于51单片机而言,它内部是CPU控制一些在物理层面已经连接好寄存器,通过对寄存器的控制配合电路的改变完成整个任务。
对于串口通信这个任务而言,它的整个实现过程总结如下:
整个实现可分为三大部分,定时器、中断系统及对应的缓冲寄存器完成,三大部分配合内部CPU便可完成收发任务,具体示意图如下:
红色区域是定时器部分,它外部连接着晶振,通过对TH1和TL1寄存器写入初始值可实现具体溢出频率的控制,假设晶振频率位12MHz(12T模式下1us计数一次),TH1和TL1填入0xF3(对应十进制243),一共256,则每隔256-243=13个数就会溢出一次,则溢出率为1/13us=0.07692MHz,当SMOD=1时,频率只经过➗16也就对应4800Hz,就会对应波特率4800Baud。配合控制器即可完成对波特率的控制。
蓝色部分是用于发送和接收数据的缓冲区;
黄色部分是串口中断器,这个中断器在接收数据的时候需要使用,TI和RI是中断标志。
在使用方式上,对涉及的寄存器进行初始化后,将数据写入SBUF或通过中断读取SBUF内的数据即可完成发送接收任务。
对应配置代码如下:
#include <REGX51.H>
/**
* @brief 串口初始化,4800bps@11.0592MHz
* @param 无
* @retval 无
*/
void UART_Init()
{
//对串行寄存器配置
SCON=0x50; //串行控制寄存器,涉及模式选择
PCON &= 0x7F; //电源控制寄存器(包括波特率)
//对定时器0进行配置
#11.0592M
TMOD &= 0x0F; //设置定时器模式,低4位保持不变
TMOD |= 0x20; //设置定时器模式,串口对应的模式需要是双8位模式
TL1 = 0xFA; //设定定时初值
TH1 = 0xFA; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定定时器1
//串口中断配置
EA=1;//启动所有中断
ES=1;//启动串口中断
}
/**
* @brief 串口发送1个字节数据,4800bps@11.0592MHz
* @param Byte 要发送的一个字节数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;//将数据写入发送缓冲器即可
while(TI==0);//是否有中断请求位
TI=0;//软件置0
}
//串口中断函数的模板,需要用的时候复制到主函数中
/*
//注意:一个函数不能既在主函数中出现,又在中断函数中出现
void UART_Routine() interrupt 4 //串口中断对应的中断号
{
if(RI==1)//接收到了串口中断
{
P2=~SBUF;
UART_SendByte(SBUF);
RI=0;//需要由软件清0
}
}
*/
主函数代码如下:
#include <REGX51.H>
#include "Delay.h"
#include "UART.h"
void main()
{
UART_Init();
while(1)
{
}
}
//注意:一个函数不能既在主函数中出现,又在中断函数中出现
void UART_Routine() interrupt 4 //串口中断对应的中断号
{
if(RI==1)//接收到了串口中断
{
P2=~SBUF;
UART_SendByte(SBUF);
RI=0;//需要由软件清0
}
}
四、总结
均由寄存器加逻辑电路组成,只不过二者工作模式及灵活性差别很大,单片机依赖冯诺依曼架构需要控制器控制且电路固定(使得定时器、寄存器等资源有限),FPGA的并行及灵活性在这里体现的淋漓尽致。