#今天也是鹹魚的一天~
UART是一個很基礎的串口通信協議,有人打趣說只要有單片機的地方,就一定有uart。這話不假,不僅很多模塊和單片機的通信有uart,單片機和上位機之間的通信,uart也是最常用的。(寒假的時候,就用C#寫了一個上位機,用的就是串口)
Uart 的原理可以參考這個博客:FPGA的串口通訊(UART)
這幾天用Robei EDA 給我的一個感想就是,在FPGA的使用設計上,如果是從原有的程序上移植,比較需要注意的就是一份代碼,需要由多少個運算模塊組成,每個模塊的IO分配,變量類型和數據位的設置,還有對於testbech的設置。
而如果是獨立的Verilog設計,設計方式是自上而下還是自下而上,每個模塊的IO分配,一個模塊內部變量的使用,parameter的設置。尤其是parameter,不適合每個模塊都對parameter設置爲常數,比較好的應該是把局部模塊的parameter設置爲一個“變量”,然後在頂層模塊把這個“變量”設置爲parameter。
回到正題……
uart 在robei EDA的庫裏是有封裝好模塊,可以直接調用。唯一的壞處就是,這個模塊已經封裝好了,不能進行任何修改。(當然,也沒什麼好修改的)
代碼可以在Code裏面看到。
如果不用這個模塊,自己寫一個,其實還挺麻煩的……
要分別寫一個發送uart_tx
模塊和接收uart_rx
模塊,激勵文件的編寫也很麻煩……
分別是發送和接收的模塊。代碼我是參考正點原子的uart收發測試,上位機像FPGA發送一串數據,FPGA再把數據返回給上位機。
UART_TX
- 延時
//對發送使能信號uart_en延遲兩個時鐘週期
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else
begin
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
- 取上升沿
assign flag = ~d0 & d1;
//捕獲uart_en上升沿,得到一個時鐘週期的脈衝信號
- 數據寄存
parameter BPS_CNT= CLK_FRE/UART_BPS;
//當脈衝信號en_flag到達時,寄存待發送的數據,並進入發送過程
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
tx_flag <= 1'b0;
tx_data <= 8'b0;
end
else if(flag )
begin
tx_flag <= 1'b1;
tx_data <= uart_din;
end
else if((tx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else
begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end
- 啓動計數器
//進入發送過程後,啓動系統時鐘計數器與發送數據計數器
parameter BPS_CNT= CLK_FRE/UART_BPS;
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
clk_cnt <= 16'b0;
tx_cnt <= 4'd0;
end
else if(tx_flag)
begin
if(clk_cnt < BPS_CNT - 1)
begin
clk_cnt <= clk_cnt + 1'b1;
tx_cnt <= tx_cnt;
end
else
begin
clk_cnt <= 16'd0;
tx_cnt <= tx_cnt + 1'b1;
end
end
else
begin
clk_cnt <= 16'b0;
tx_cnt <= 4'd0;
end
end
- 數據配置
//根據發送數據計數器來給uart發送端口賦值
always@(posedge clk or negedge rst)
begin
if(!rst)
uart_txd <= 1'b1;
else 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 : ;
endcase
else
uart_txd <= 1'b1;
end
UART_RX
- 數據延時
//對UART接收端口的數據延遲兩個時鐘週期
always @(posedge clk or negedge rst) begin
if (!rst) begin
uart_rx_d0 <= 1'b0;
uart_rx_d1 <= 1'b0;
end
else begin
uart_rx_d0 <= uart_rx;
uart_rx_d1 <= uart_rx_d0;
end
end
- 獲取下降沿
//捕獲接收端口下降沿(起始位),得到一個時鐘週期的脈衝信號
assign start_flag = uart_rxd_d1 & (~uart_rxd_d0);
- 進入接收過程
parameter BPS_CNT = CLK_FREQ/UART_BPS;
//當脈衝信號start_flag到達時,進入接收過程
always @(posedge clk or negedge rst) begin
if (!rst)
rx_flag <= 1'b0;
else begin
if(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
- 啓動計數器
parameter BPS_CNT = CLK_FREQ/UART_BPS;
always @(posedge clk or negedge rst) begin
if (!rst) 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
- 數據傳輸
parameter BPS_CNT = CLK_FREQ/UART_BPS;
always @(posedge clk or negedge rst) begin
if ( !rst)
rxdata <= 8'd0;
else if(rx_flag) //系統處於接收過程
if (clk_cnt == BPS_CNT/2) begin //判斷系統時鐘計數器計數到數據位中間
case ( rx_cnt )
4'd1 : rxdata[0] <= uart_rx_d1; //寄存數據位最低位
4'd2 : rxdata[1] <= uart_rx_d1;
4'd3 : rxdata[2] <= uart_rx_d1;
4'd4 : rxdata[3] <= uart_rx_d1;
4'd5 : rxdata[4] <= uart_rx_d1;
4'd6 : rxdata[5] <= uart_rx_d1;
4'd7 : rxdata[6] <= uart_rx_d1;
4'd8 : rxdata[7] <= uart_rx_d1; //寄存數據位最高位
default:;
endcase
end
else
rxdata <= rxdata;
else
rxdata <= 8'd0;
end
- 輸出數據
always @(posedge clk or negedge rst) begin
if (!rst) 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
end
這次沒有建立激勵文件,因爲我仿真了一下,效果並不是很好,我感覺放出來有點奇怪,所以……激勵文件就不放了。uart更適合系統的仿真。