一、UART基本概念
????????通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART。它将要传输的资料在串行通信与并行通信之间加以转换,可用于单片机之间、单片机与PC之间数据通信,也就是我们常说的串口。
二、串口协议
? ? ? ? 这里所说的串口协议按照我的理解,是针对“帧”这个单位来说的,其结构如下图所示。
?????????在一帧中,其基本单位是“位”,也就是bit。一帧数据包含起始位、数据位、校验位(可有可无)和停止位。因为串口通信是异步通信,没有同步时钟协调,所以我们在接收数据时,要知道一帧的帧头是从哪开始的,所以需要一个起始位,在数据线空闲状态下,通信线电平始终为高,当一帧数据发来时,其起始位为‘0’,所以会产生一个下降沿,我们通过判断这个下降沿就可以知道一帧数据到来。
数据位为8位,也就是一个字节,发送顺序为从低位到高位依次发送。
然后是校验位(可有可无)。最后是停止位,停止位将重新拉回高电平,然后处于空闲状态,等待下一帧数据到来。
三、FPGA&Verilog实战
????????1、思路整理
? ? ? ? ? ? ? ? 整个模块可以分为两部分,接收部分和发送部分,如图所示。
?????????分为两部分后,后续使用灵活,每部分的功能又可以细分为波特率产生、发送/接收数据、产生标志位(方便仿真分析)。
?
????????2、代码编写
? ? ? ? ? ? ? ? 两个模块功能很相似,我们先写接收模块。
????????(1)接收模块
module uart_rx #( ?? ?parameter BAT = 'd9600? ? ? ? //波特率9600 ) ( ?? ?input wire ?? ??? ??? ?clk?? ??? ?,? ? ?//时钟50MHZ ?? ? ?? ?input wire ?? ??? ??? ?rst?? ??? ?,? ? ? //复位信号,低有效 ?? ? ?? ?input wire ?? ??? ? ?? ?rx?? ??? ?,? ? ? //接收端 ?? ? ?? ?output reg [9:0]?? ?rx_data,? ? ? //接收缓存 ?? ??? ??? ??? ? ?? ?output reg ?? ??? ??? ?rx_flag? ? ? ?//接收完成标志 );
parameter?? ?BAT_CNT = 'd50_000_000 / BAT;? ? ? ? //根据波特率计算计数值 parameter?? ?IDLE? ? ?= 4'b0001, ?? ??? ??? ??? ?????START? = 4'b0010, ? ? ? ? ? ? ? ? ? ? DATA? ? = 4'b0100, ? ? ? ? ? ? ? ? ? ? STOP? ?= 4'b1000; ?? ??? ??? ??? ? reg [3:0] ?? ?state,next_state; reg [3:0] ?? ?rx_cnt; reg ?? ??? ??? ?bat_flag; reg [12:0] ??cnt; reg ?? ??? ??? ?rx_dely1, ? ? ? ? ? ? ? ? ? rx_dely2;
//rx_dely1:rx打一拍 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?rx_dely1 <= 0; ?? ?else ?? ??? ?rx_dely1 <= rx; ?? ??? ? //rx_dely2:rx打两拍 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?rx_dely2 <= 0; ?? ?else ?? ??? ?rx_dely2 <= rx_dely1;
//cnt:波特率频率计数 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?cnt <= 0; ?? ?else if(cnt == BAT_CNT-1 || state == IDLE) ?? ??? ?cnt <= 0; ?? ?else ?? ??? ?cnt <= cnt + 1'b1;
//bat_flag:波特率计数标志 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?bat_flag <= 0; ?? ?else if(cnt == BAT_CNT/2-1'b1)? ? ? ? //在每bit周期中间位置产生标志信号 ?? ??? ?bat_flag <= 1'b1;? ? ? ? ? ? ? ? ? ? ? ? ? ?//此时采集的数据可靠性高 ?? ?else ?? ??? ?bat_flag <= 0;
//rx_cnt:输入数据计数?? ??? ? always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?rx_cnt <= 0; ?? ?else if((rx_cnt == 'd9 && cnt == BAT_CNT/2) || state == IDLE) ?? ??? ?rx_cnt <= 0; ?? ?else if(cnt == BAT_CNT-1'b1)? ? ? ? ? ? ? ?? ??? ?rx_cnt <= rx_cnt + 1'b1; ?? ??? ? //state:状态改变 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?state <= IDLE; ?? ?else ?? ??? ?state <= next_state; ?? ??? ? //next_state:次态改变 always@(*) ?? ?case(state) ?? ??? ?IDLE ?: next_state <= (~rx_dely1 & rx_dely2)?START:IDLE; ?? ??? ?START : next_state <= DATA; ?? ??? ?DATA ?: next_state <= (rx_cnt == 'd8 && cnt == BAT_CNT-1'b1)?STOP:DATA; ?? ??? ?STOP ?: next_state <= (cnt == BAT_CNT/2)?IDLE:STOP; ?? ??? ?default : next_state <= IDLE; ?? ?endcase ?? ? //rx,rx_data:输入数据 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?rx_data <= 0; ?? ?else if(bat_flag == 1'b1) ?? ??? ?rx_data[rx_cnt] <= rx_dely2;? ? ? ??//保存起始位、数据位、停止位共10bit数据 ?? ??? ? //rx_flag:输出完成标志 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?rx_flag <= 0; ?? ?else if(state == STOP && cnt == BAT_CNT/2) ?? ??? ?rx_flag <= 1'b1; ?? ?else ?? ??? ?rx_flag <= 0;? ? ? ?? ?? ??? ? ?? ??? ? endmodule
????????(2)发送模块? ? ? ??
module uart_tx #( ?? ?parameter BAT = 'd9600 ) ( ?? ?input wire ?? ??? ??? ?clk?? ??? ?, ?? ? ?? ?input wire ?? ??? ??? ?rst?? ??? ?, ?? ? ?? ?input wire?? ??? ??? ?rx_flag?? ?, ?? ? ?? ?input wire [9:0] ?? ?rx_data?? ?, ?? ? ?? ?output reg ?? ??? ??? ?tx?? ??? ??? ?, ?? ??? ??? ??? ? ?? ?output reg ?? ??? ??? ?tx_flag );
parameter?? ?BAT_CNT = 'd50_000_000 / BAT; parameter?? ?IDLE? ? ?= 4'b0001, ? ? ? ? ? ? ? ? ? ? START? = 4'b0010, ? ? ? ? ? ? ? ? ? ? DATA? ? = 4'b0100, ? ? ? ? ? ? ? ? ? ? STOP? ?= 4'b1000; ?? ??? ??? ??? ? reg [3:0] ?? ?state,next_state; reg [3:0] ?? ?in_cnt; reg ?? ??? ??? ?bat_flag; reg [12:0]? ?cnt; reg [9:0]? ? ?data;
//data:数据缓存 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?data <= 0; ?? ?else if(rx_flag == 1'b1)? ? ? ? //接收到rx模块发来的接收完成标志 ?? ??? ?data <= rx_data;? ? ? ? ? ? ?//将数据从数据缓存读出 ?? ?else ?? ??? ?data <= data; ?? ??? ? //cnt:波特率频率计数 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?cnt <= 0; ?? ?else if(cnt == BAT_CNT-1 || state == IDLE) ?? ??? ?cnt <= 0; ?? ?else ?? ??? ?cnt <= cnt + 1'b1;
//bat_flag:波特率计数标志 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?bat_flag <= 0; ?? ?else if(cnt == BAT_CNT-2) ?? ??? ?bat_flag <= 1'b1; ?? ?else ?? ??? ?bat_flag <= 0;
//in_cnt:输出数据计数?? ??? ? always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?in_cnt <= 0; ?? ?else if((in_cnt == 'd9 && cnt == BAT_CNT - 1'b1) || state == IDLE) ?? ??? ?in_cnt <= 0; ?? ?else if(bat_flag == 1'b1) ?? ??? ?in_cnt <= in_cnt + 1'b1; ?? ??? ? //state:状态改变 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?state <= IDLE; ?? ?else ?? ??? ?state <= next_state; ?? ??? ? //next_state:次态改变 always@(*) ?? ?case(state) ?? ??? ?IDLE ?: next_state <= (rx_flag)?START:IDLE; ?? ??? ?START : next_state <= DATA; ?? ??? ?DATA ?: next_state <= (in_cnt == 'd8 && cnt == BAT_CNT-1'b1)?STOP:DATA; ?? ??? ?STOP ?: next_state <= (cnt == BAT_CNT/2)?IDLE:STOP; ?? ??? ?default : next_state <= IDLE; ?? ?endcase ?? ? //tx,rx_data:输出数据 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?tx <= 1'b1; ?? ?else if(state == STOP && cnt == BAT_CNT/2) ?? ??? ?tx <= 1'b1; ?? ?else if(state != IDLE) ?? ??? ?tx <= data[in_cnt];? ? ? ? //发送数据,将采集到的数据原封不动返回 ?? ??? ? //tx_flag:输出完成标志 always@(posedge clk or negedge rst) ?? ?if(!rst) ?? ??? ?tx_flag <= 0; ?? ?else if(state == STOP && cnt == BAT_CNT/2) ?? ??? ?tx_flag <= 1'b1; ?? ?else ?? ??? ?tx_flag <= 0;
endmodule ?
????????(3)顶层模块
module uart ( ?? ?input wire ?? ?clk?? ??? ?, ?? ? ?? ?input wire ?? ?rst?? ??? ?, ?? ? ?? ?input wire ?? ?rx?? ??? ??, ?? ? ?? ?output? ? ? ? ? ?tx? ? ? ? ?, ?? ? ?? ?output? ? ? ? ? ?rx_flag?,? ? ? ? //放到端口里是为了方便仿真 ?? ? ?? ?output? ? ? ? ? ?tx_flag );
wire [9:0] ?? ?rx_data;
uart_rx? #(9600) uart_rx_inst ( ?? ?.clk? ? ? ? ?(clk? ? ? ? ?), ?? ?.rst? ? ? ? ?(rst? ? ? ? ? ), ?? ?.rx? ? ? ? ? (rx? ? ? ? ? ?),?? ? ?? ? ?? ?.rx_data??(rx_data?), ?? ?.rx_flag?? (rx_flag?? ) ); ?? ?? uart_tx? #(9600) uart_tx_inst ( ?? ?.clk? ? ? ? ? ?(clk? ? ? ? ), ?? ?.rst? ? ? ? ? ?(rst? ? ? ? ?), ?? ?.rx_flag? ? (rx_flag??), ?? ?.rx_data?? (rx_data?), ?? ? ?? ?.tx? ? ? ? ? ? (tx? ? ? ? ? ?), ?? ?.tx_flag?? ?(tx_flag???) );
endmodule
????????3、仿真分析
????????(1)编写Testbench文件进行仿真
`timescale 1ns/1ns
module tb_uart();
reg ?? ?clk; reg ?? ?rst; reg ?? ?rx?? ?;
wire ?? ?tx; wire ?? ?rx_flag; wire ?? ?tx_flag;
initial? ?? ?begin ?? ??? ?clk <= 0; ?? ??? ?rst <= 0; ?? ??? ?rx <= 1'b1; ?? ??? ?#20 ?? ??? ?rst <= 1;?? ? ?? ?end
always #10 clk <= ~clk;
initial ?? ?begin ?? ??? ?#80 ?? ??? ??? ?begin ?? ??? ??? ??? ?rx <= 0;? ? ? ? ? ?//起始位 ?? ??? ??? ??? ?#104166;? ? ? ? //对应波特率9600的单bit周期时间 ?? ??? ??? ??? ?rx <= 0; ?? ??? ??? ??? ?#104166; ?? ??? ??? ??? ?rx <= 1; ?? ??? ??? ??? ?#104166; ?? ??? ??? ??? ?rx <= 1; ?? ??? ??? ??? ?#104166; ?? ??? ??? ??? ?rx <= 0; ?? ??? ??? ??? ?#104166; ?? ??? ??? ??? ?rx <= 0; ?? ??? ??? ??? ?#104166; ?? ??? ??? ??? ?rx <= 1; ?? ??? ??? ??? ?#104166; ?? ??? ??? ??? ?rx <= 1; ?? ??? ??? ??? ?#104166; ?? ??? ??? ??? ?rx <= 0; ?? ??? ??? ??? ?#104166; ?? ??? ??? ??? ?rx <= 1;? ? ? ? ? ?//停止位 ?? ??? ??? ??? ?#105000;?? ? ?? ??? ??? ?end ?? ?end uart uart_inst ( ?? ?.clk? ? ? ? ?(clk?? ??? ?), ?? ?.rst? ? ? ? ?(rst?? ??? ?),?? ? ?? ?.rx? ? ? ? ? (rx? ? ? ? ?), ?? ? ?? ?.tx? ? ? ? ? ?(tx? ? ? ? ?), ?? ?.rx_flag?? (rx_flag?), ?? ?.tx_flag?? (tx_flag??) );
endmodule ?
????????(2)Modelsim仿真
????????可以看到,仿真结果和预期的效果相同,实验成功。
?四、总结
? ? ? ? 总体来说,这个项目不是很难,只要理清楚其逻辑和时序,写程序就很方便了,万变不离其宗。需要注意的是,在接收模块执行时,接收到停止位后不必等待停止位周期完全结束就可以停止接收了,直接返回空闲状态等待下一帧数据到来,这样在多字节连续发送时可以避免数据遗漏,读到错码。
? ? ? ? PS:作者是FPGA初学者,水平有限,写的不是很详细,仅作为一个简单的总结,有问题的地方还请大家多多指出,谢谢~
|