FPGA|FPGA UART发送与接收

背景 最近刚开始接触FPGA,在大概看了基本教材学习了Verilog后就买了一块黑金的开发板,开始正儿八经的撸代码。然后就到了UART通信这一节,基本上例程都是顶层模块例化UART收发模块,作者觉得这样不能很好的理解UART的底层协议,UART发送具体是怎么发送,又怎么接收,也不想直接ctrl+ch和ctrl+v,然后作者在参考正点原子的代码后,通过状态机实现500ms发送一个字节的数据和UART接收,具体请看下文,还有请正点原子支付下广告费。
协议 如下图所示,UART通信需要三根线,一根发送TX,一根接收RX,还有一根地。总线(TX和RX)空闲时处于高电平,发送方发送数据时,先发送起始位(逻辑0),然后以地为在前、高位在后的顺序将一个字节的每一个比特位发送出去(这一字节可以是7位或者8/9位),数据发送完成后,再发送1位校验位和停止位(可以是1位或者2位,逻辑1)。接收方的RX是与发送方的TX连接在一起的,因此,接收方检测RX上是否存在下降沿,是,则准备开始接收,因为UART通信没有时钟,因此只能规定多少时间发送一个比特位来保证数据收发不会出错,这就是比特率(或者说是波特率),单位是比特每秒(bit/s),一般情况下 ,比特率使用9600和115200,。本次作者的UART通信是采用8位数据位,1位停止位,没有奇偶检验位,共10位。
FPGA|FPGA UART发送与接收
文章图片

代码编写 协议清楚后开始撸代码。
UART发送 作者的思路是用状态机进行数据发送,初始化、发送、结束,发送周期是500ms,波特率是9600,系统频率是50MHz,所以需要对时钟脉冲计数50_000_000/9600=5208.33个,取整5208。下面是代码。

`timescale 1ns/1psmodule uart_tx( inputclk, inputrst_n,output regtx_pin); parameterCLK_FREQ=50000000, BPS=9600, BPS_CNT=CLK_FREQ/BPS; parameterIDLE=4'd0, SEND=4'd1, END=4'd2; reg[7:0]data_buf; reg[24:0]cnt1; //500ms 计数寄存器 regtx_start; //计时到了置1,并开始发送 //计时500ms always@(posedge clk,negedge rst_n) if(!rst_n) begin cnt1<=0; tx_start<=0; end elseif(cnt1==25000000) begin cnt1<=0; tx_start<=~tx_start; end else cnt1<=cnt1+1; reg[3:0] tx_cnt; reg[24:0] clk_cnt; reg[3:0] state; always@(posedge clk,negedge rst_n) if(!rst_n) begin state<=IDLE; data_buf<=8'h5A; //发送的数据 end else case(state) IDLE: begin tx_cnt<=0; clk_cnt<=0; if(tx_start==1)//500ms发送一次数据 state<=SEND; endSEND:begin if(tx_cnt==4'd9&&clk_cnt==BPS_CNT/2) begin tx_cnt<=10; //data_buf<=0; state<=END; end else if(clk_cnt==BPS_CNT-1) begin clk_cnt<=0; tx_cnt<=tx_cnt+1; end else begin clk_cnt<=clk_cnt+1; tx_cnt<=tx_cnt; end endEND:begin if(tx_start==1) begin //tx_pin<=1'b1; state<=END; end else state<=IDLE; end default:state<=IDLE; endcasealways@(posedge clk,negedge rst_n) if(!rst_n) tx_pin<=1'b1; else if(tx_start==1) case(tx_cnt) 4'd0:tx_pin<=1'b0; 4'd1:tx_pin<=data_buf[0]; 4'd2:tx_pin<=data_buf[1]; 4'd3:tx_pin<=data_buf[2]; 4'd4:tx_pin<=data_buf[3]; 4'd5:tx_pin<=data_buf[4]; 4'd6:tx_pin<=data_buf[5]; 4'd7:tx_pin<=data_buf[6]; 4'd8:tx_pin<=data_buf[7]; 4'd9:tx_pin<=1'b1; default:tx_pin<=1'b1; endcase endmodule

UART接收 【FPGA|FPGA UART发送与接收】当前作者的思路是检测RX上的降沿,检测到则将将接收标志位置1,因为总线上有数据那么就存在高低电平,然后根据波特率对时钟计数,最后缓存。
module uart_rx( inputclk,//系统时钟 inputrst_n,//系统复位,低电平有效inputuart_rxd,//UART接收端口 outputreguart_done,//接收一帧数据完成标志信号 outputreg [7:0] uart_data//接收的数据 ); //parameter define parameterCLK_FREQ = 50000000; //系统时钟频率 parameterBPS= 9600; //串口波特率 parameterBPS_CNT= CLK_FREQ/BPS; //为得到指定波特率, //需要对系统时钟计数BPS_CNT次 //reg define reg [1:0]rxcheck; reg [15:0] clk_cnt; //系统时钟计数器 reg [ 3:0] rx_cnt; //接收数据计数器 regrx_flag; //接收过程标志信号 reg [ 7:0] rxdata; //接收数据寄存器//wire define wirestart_flag; //捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号 assignstart_flag = rxcheck[1]& (~rxcheck[0] ); always @(posedge clk or negedge rst_n) begin if (!rst_n) begin rxcheck<=0; end else begin rxcheck[0]<= uart_rxd; rxcheck[1]<= rxcheck[0] ; end end//当脉冲信号start_flag到达时,进入接收过程 always @(posedge clk or negedge rst_n) begin if (!rst_n) rx_flag <= 1'b0; else begin if(start_flag)//检测到起始位 rx_flag <= 1'b1; //进入接收过程,标志位rx_flag拉高 else if((rx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2)) rx_flag <= 1'b0; //计数到停止位中间时,停止接收过程 else rx_flag <= rx_flag; end end//进入接收过程后,启动系统时钟计数器与接收数据计数器 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin clk_cnt <= 16'd0; rx_cnt<= 4'd0; end else if ( rx_flag ) begin//处于接收过程 if (clk_cnt < BPS_CNT - 1) begin clk_cnt <= clk_cnt + 1'b1; rx_cnt<= rx_cnt; end else begin clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零 rx_cnt<= rx_cnt + 1'b1; //此时接收数据计数器加1 end end else begin//接收过程结束,计数器清零 clk_cnt <= 16'd0; rx_cnt<= 4'd0; end end//根据接收数据计数器来寄存uart接收端口数据 always @(posedge clk or negedge rst_n) begin if ( !rst_n) rxdata <= 8'd0; else if(rx_flag)//系统处于接收过程 if (clk_cnt == BPS_CNT/2) begin//判断系统时钟计数器计数到数据位中间 case ( rx_cnt ) 4'd1 : rxdata[0] <= uart_rxd_d1; //寄存数据位最低位 4'd2 : rxdata[1] <= uart_rxd_d1; 4'd3 : rxdata[2] <= uart_rxd_d1; 4'd4 : rxdata[3] <= uart_rxd_d1; 4'd5 : rxdata[4] <= uart_rxd_d1; 4'd6 : rxdata[5] <= uart_rxd_d1; 4'd7 : rxdata[6] <= uart_rxd_d1; 4'd8 : rxdata[7] <= uart_rxd_d1; //寄存数据位最高位 default:; endcase end else rxdata <= rxdata; else rxdata <= 8'd0; end//数据接收完毕后给出标志信号并寄存输出接收到的数据 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin uart_data <= 8'd0; uart_done <= 1'b0; end else if(rx_cnt == 4'd9) begin//接收数据计数器计数到停止位时 uart_data <= rxdata; //寄存输出接收到的数据 uart_done <= 1'b1; //并将接收完成标志位拉高 end else begin uart_data <= 8'd0; uart_done <= 1'b0; end endendmodule

    推荐阅读