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位。
文章图片
代码编写 协议清楚后开始撸代码。
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
推荐阅读
- 宽容谁
- 讲述,美丽聪明的海欧!
- 夜游宫|夜游宫 心语
- 画画吗()
- 深入理解Go之generate
- 图书集合完毕
- 【故障公告】周五下午的一次突发故障
- 即将到手三百万
- 7.9号工作总结~司硕
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售