文章目录
- 前言
- 一、串行通信
- 1、分类
- 1、同步串行通信
- 2、异步串行通信
- 2、UART串口通信
- 1、UART通信原理
- 2、串口通信时序图
- 二、系统设计
- 1、系统框图
- 2.RTL视图
- 三、源码
- 1、串口发送模块
- 2、接收模块
- 3、串口回环模块
- 4、顶层模块
- 四、测试效果
- 五、总结
- 六、参考资料
前言
环境:
1、Quartus18.0
2、vscode
3、板子型号:原子哥开拓者2(EP4CE10F17C8)
要求:
上位机通过串口调试助手发送数据给 FPGA,FPGA 通过 USB 串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。(这里我们以波特率115200、数据位为8位、无校验位、停止位为1位作为案例)。
一、串行通信
1、分类
1、同步串行通信
同步串行通信需要通信双方在同一时钟的控制下进行同步传输数据。
2、异步串行通信
异步串行通信指通信双方使用各自的时钟控制数据的发送和接收。
2、UART串口通信
1、UART通信原理
UART串口通信是一种采用异步串行通信方式的通用异步收发传输器,在收发数据过程中通过串并转换实现数据的发送、接收。UART串行通信一般需要两根信号线实现,一根用于串口发送,一根用于串口接收。下面是其通信的数据格式:
从通信格式看,我们可知收发的一帧数据涵盖了4部分:起始位、数据位、校验位、以及停止位。校验位一般是奇检验、偶检验,就是发送方应使数据位中 1 的个数与校验位中 1 的个数之和为奇数或偶数。数据位有5、6、7、8位,一般选择8位。串口通信的速率用波特率进行表示,表示每秒传输二进制的位数,常用9600、115200等。
- 注意:
UART 通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。
2、串口通信时序图
二、系统设计
1、系统框图
2.RTL视图
uart_recv 为串口接收模块:从串口接收端口 uart_rxd 来接收上位机发送的串行数据,并在一帧数据接收结束后给出通知信号 uart_done。
uart_send 为串口发送模块:以 uart_en 为发送使能信号。uart_en 的上升沿将启动一次串口发送过程,
将 uart_din 接口上的数据通过串口发送端口 uart_txd 发送出去。
uart_loop 模块负责完成串口数据的环回功能:它在 uart_recv 模块接收完成后,将接收到的串口数据发送到 uart_send 模块,并通过 send_en 接口给出一个上升沿,以启动发送过程。
三、源码
1、串口发送模块
module uart_send(input sys_clk ,input sys_rst_n ,input uart_en ,//发送使能input [7:0] uart_din ,//待发送的数据output uart_tx_busy ,//发送忙output en_flag ,output reg tx_flag ,//发送过程标志output reg [7:0] tx_data ,//寄存发送数据output reg [3:0] tx_cnt ,//发送数据计数器output reg uart_txd //发送端口
);parameter CLK_FREQ = 50_000_000;
parameter UART_BPS =9600;
localparam BPS_CNT = CLK_FREQ/UART_BPS;reg uart_en_d0;//打拍信号
reg uart_en_d1;
reg [15:0] clk_cnt;assign uart_tx_busy = tx_flag;//捕获上升沿
assign en_flag = (~uart_en_d1) & uart_en_d0;//打拍
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)beginuart_en_d0 <= 1'b0;uart_en_d1 <= 1'b0;endelse beginuart_en_d0 <= uart_en;uart_en_d1 <= uart_en_d0;endend//当上升沿来时,寄存待发送的数据,进入发送过程
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begintx_flag <= 1'b0;tx_data <= 8'd0;endelse if(en_flag) begin//上升沿tx_flag <= 1'b1;tx_data <= uart_din;endelse if((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT - (BPS_CNT/16)))begintx_flag <= 1'b0;tx_data <= 8'd0;endelse begintx_flag <= tx_flag;tx_data <= tx_data;end
end//开启时钟计数器
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)beginclk_cnt <= 16'd0;endelse if(tx_flag) begin//发送过程if(clk_cnt < BPS_CNT - 1)clk_cnt <= clk_cnt + 1'b1;elseclk_cnt <= 16'd0;endelseclk_cnt <= 16'd0;
end
//开启发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begintx_cnt <= 4'd0;endelse if(tx_flag) beginif(clk_cnt == BPS_CNT - 1)tx_cnt <= tx_cnt + 1'b1;elsetx_cnt <= tx_cnt;endelsetx_cnt <= 4'd0;
end
//并转串
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)beginuart_txd <= 1'b1;endelse if(tx_flag)case(tx_cnt)4'd0: uart_txd <= 1'b0;//起始位4'd1: uart_txd <= tx_data[0];//低位4'd2: uart_txd <= tx_data[1];4'd3: uart_txd <= tx_data[2];4'd4: uart_txd <= tx_data[3];4'd5: uart_txd <= tx_data[4];4'd6: uart_txd <= tx_data[5];4'd7: uart_txd <= tx_data[6];4'd8: uart_txd <= tx_data[7];4'd9: uart_txd <= 1'b1;default: ;endcaseelseuart_txd <= 1'b1;
end
endmodule
2、接收模块
module uart_recv(input sys_clk ,input sys_rst_n ,input uart_rxd ,//接收端口output reg uart_done ,//接收一帧数据结束标志output reg rx_flag ,//接收过程标志信号output reg [3:0] rx_cnt ,//接收数据计数器output reg [7:0] rxdata ,output reg [7:0] uart_data //接收的数据
);parameter CLK_FREQ = 50_000_000;
parameter UART_BPS = 9600;//波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS;//为得到指定波特率需要计数的值reg uart_rxd_d0;//用于打拍判定下降沿决定开始信号
reg uart_rxd_d1;reg [15:0] clk_cnt;
wire start_flag;assign start_flag = uart_rxd_d1 & (~uart_rxd_d0);//打拍
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n) beginuart_rxd_d0 <= 1'b0;uart_rxd_d1 <= 1'b0;endelse beginuart_rxd_d0 <= uart_rxd;uart_rxd_d1 <= uart_rxd_d0;end
end//下降沿到来时,开始接收
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)rx_flag <= 1'b0;else beginif(start_flag)//下降沿到来rx_flag <= 1'b1;else if((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))//计数到第10位,且在第十位中间时接收结束rx_flag <= 1'b0;elserx_flag <= rx_flag;end
end//进入接收过程后开启时钟计数器,为了满足特定波特率
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)clk_cnt <= 16'd0;else if(rx_flag) beginif(clk_cnt < BPS_CNT - 1)clk_cnt <= clk_cnt + 1'b1;elseclk_cnt <= 16'd0;endelseclk_cnt <= 16'd0;
end//进入接收过程,开启接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)rx_cnt <= 4'd0;else if(rx_flag)beginif(clk_cnt == BPS_CNT - 1)rx_cnt <= rx_cnt + 1'b1;elserx_cnt <= rx_cnt;endelserx_cnt <= 4'd0;
end//根据接收数据计数器寄存uart接收端的数据
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_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:;endcaseendelserxdata <= rxdata;elserxdata <= 8'd0;
end//数据接收完毕给出结束标志并寄存输出接收的数据
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)beginuart_data <= 8'd0;uart_done <= 1'b0;endelse if(rx_cnt == 4'd9)beginuart_data <= rxdata;uart_done <= 1'b1;endelse beginuart_data <= 8'd0;uart_done <= 1'b0;end
end
endmodule
3、串口回环模块
module uart_loop(input sys_clk ,input sys_rst_n ,input recv_done ,//接收一帧数据结束标志input [7:0] recv_data ,//接收的数据input tx_busy ,//发送忙output reg send_en ,//发送使能output reg [7:0] send_data //待发送数据
);//打拍信号
reg recv_done_d0;
reg recv_done_d1;reg tx_ready;
wire recv_done_flag;//捕获上升沿,一帧结束
assign recv_done_flag = (~recv_done_d1) & recv_done_d0;//打拍
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginrecv_done_d0 <= 1'b0;recv_done_d1 <= 1'b0;endelse beginrecv_done_d0 <= recv_done;recv_done_d1 <= recv_done_d0;end
end//一帧接收结束后进入准备阶段,寄存接收的数据用于发送
//当准备阶段且发送空闲时,使能发送信号always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n) begintx_ready <= 1'b0;send_en <= 1'b0;send_data <= 8'd0;endelse beginif(recv_done_flag)begintx_ready <= 1'b1;send_en <= 1'b0;send_data <= recv_data;endelse if(tx_ready && (~tx_busy))begintx_ready <= 1'b0;send_en <= 1'b1;endend
end
endmodule
4、顶层模块
module uart_loopback_top(input sys_clk ,input sys_rst_n ,input uart_rxd ,output uart_txd
);parameter CLK_FREQ = 50_000_000;
parameter UART_BPS = 115200;wire uart_recv_done ;//接收完成标志
wire [7:0] uart_recv_data ;//接收数据
wire uart_send_en ;//发送使能
wire [7:0] uart_send_data ;//发送数据
wire uart_tx_busy ;//发送忙状态标志//串口接收模块
uart_recv #(.CLK_FREQ(CLK_FREQ),.UART_BPS(UART_BPS)) uart_recv_inst(/*input */ .sys_clk (sys_clk ) ,/*input */ .sys_rst_n (sys_rst_n) ,/*input */ .uart_rxd (uart_rxd ) ,//接收端口/*output reg */ .uart_done (uart_recv_done) ,//接收一帧数据结束标志/*output reg [7:0]*/ .uart_data (uart_recv_data)//接收的数据
);//串口发送模块
uart_send #(.CLK_FREQ(CLK_FREQ),.UART_BPS(UART_BPS)) uart_send_inst(/*input */ .sys_clk (sys_clk ) ,/*input */ .sys_rst_n (sys_rst_n) ,/*input */ .uart_en (uart_send_en ) ,//发送使能/*input [7:0] */ .uart_din (uart_send_data) ,//待发送的数据/*output */ .uart_tx_busy (uart_tx_busy) ,//发送忙/*output reg */ .uart_txd (uart_txd) //发送端口
);//串口回环模块
uart_loop uart_loop_inst(/*input */ .sys_clk (sys_clk ),/*input */ .sys_rst_n (sys_rst_n),/*input */ .recv_done (uart_recv_done),//接收一帧数据结束标志/*input [7:0] */ .recv_data (uart_recv_data),//接收的数据/*input */ .tx_busy (uart_tx_busy),//发送忙/*output reg */ .send_en (uart_send_en ),//发送使能/*output reg [7:0]*/ .send_data (uart_send_data) //待发送数据
);
endmodule
四、测试效果
五、总结
串口通信其实算是几种通信协议里较简单的一种了,看着通信时序图就已经对UART串行通信理解了许多,但是里面还是有许多细节需要注意,所以为了巩固在UART串口通信的知识,打算在最近通过FPGA实现UART串口通信实现一个测量温湿度并自动传回到上位机的学习。还有这里请允许我偷个懒,没有进行波形的仿真和SignalTap II在线抓波形,今天没什么心情。
六、参考资料
以上资料均来自正点原子的教学视频或开拓者2开发教程:原子官方