寫代碼,記筆記,防忘記,須牢記。 寫串口的Verilog代碼關鍵是要搞明白RS232串口的通信協議,它并不像單片機,直接讀寫SBUF就可實現串口的收發功能,收發整個字節。而FPGA要一位一位的收發,因此必須了解RS232的數據格式。 起始位:RS232約定一位起始位“0”。 停止位:約定停止位為“1”。可選一位或兩位停止位。 奇偶校驗位:可選。 通過串口發送數據時,要嚴格遵守RS232的數據格式,先發送起始位,然后是數據,最后是停止位(無奇偶校驗的情況)。 通過串口接收數據時,若接收端無數據輸入,會一直處于高電平,若開始接收數據,會首先收到來自串口的起始位“0”,然后是要接收的數據,最后為停止位(無奇偶校驗的情況)。所以對于接收模塊,可如此設計,FPGA一直檢測接收端是否有下降沿到來,直到檢測到下降沿,才開始接收數據。 波特率設置的重要性不言而喻,毋庸贅述。 此設計為最基礎的串口收發代碼,控制邏輯簡單,適合編寫第一次編寫串口代碼的朋友。 此設計收發的數據格式為1位起始位,1位停止位,無奇偶校驗位,8位數據位。波特率為19200,代碼中可隨意更改。 具體Verilog代碼如下: 頂層模塊 `timescale 1ns / 1ps //////////////////////////////////////////////////////////////////////////////// // Company : 杭州電子科技大學 // Engineer : 曉曉川 // Create Date : 2012.08.26 // Design Name : serial_test // Module Name : serial_test // Project Name: serial_test // Target Device: CycloneII EP2C5T144C8 // Tool versions: Quartus II 11.0 // Revision : V1.0 // Description : 一個極為簡單的串口收發工程,適于串口收發的入門。只能收發單個字節,沒有 // 奇偶校驗位。 // 工作流程為:串口發送數據給FPGA,以LED燈的亮滅直觀顯示接收到數據,按下 // 相應按鍵并彈起后,FPGA又將接收到的串口數據發送出去。 // Additional Comments : // //////////////////////////////////////////////////////////////////////////////// module serial_test(clk,rst_n,ena,txd,rxd,data); input clk; //系統輸入時鐘 input rst_n; //異步復位 input ena; //FPGA發送使能,即按鍵輸入端 input rxd; //FPGA接收端 output txd; //FPGA發送端 output [7:0] data; //至LED顯示的數據 wire [7:0] data; wire txd; wire clk2; //PLL輸出時鐘 wire clk_baud; //波特率時鐘 PLL u1(.inclk0(clk),.c0(clk2)); //PLL輸出低頻時鐘 clk_baud_gen u2(.clk(clk2),.rst_n(rst_n),.clk_baud(clk_baud)); //產生波特率時鐘 serial_txd u3(.clk(clk_baud),.rst_n(rst_n),.ena(ena),.data(data),.txd(txd)); //FPGA發送模塊 serial_rxd u4(.clk(clk_baud),.rst_n(rst_n),.rxd(rxd),.data(data)); //FPGA接收模塊 endmodule 接收模塊 //此模塊是FPGA控制模塊從串口接收數據,不接收起始位“0”和停止位“1” //在接收端,若串口沒有數據發出,則一直處于高電平,有數據發出時,先發送起始位“0”,即如果 //接收端出現由高到低的跳變,說明串口有數據發出,應開始接收 module serial_rxd(rst_n,clk,rxd,data); input rst_n; //全局復位 input clk; //接收時鐘 input rxd; //FPGA接收串口數據的接收端 output [7:0] data; //FPGA接收的來自串口的數據,輸出至LED顯示 reg [3:0] cnt; //接收數據計數器 reg rec_reg1; //起始位檢測寄存器1 reg rec_reg2; //起始位檢測寄存器2 reg [7:0] data; //FPGA接收的數據 always @(posedge clk or negedge rst_n) if(!rst_n) begin rec_reg1<=1'b1; //起始位檢測寄存器置1, rec_reg2<=1'b1; //處于等待接收狀態 data<=8'hzz; //輸出復位,LED全滅 end else if(rec_reg1&&rec_reg2) begin rec_reg1<=rxd; //rec_reg1寄存rxd當前周期的值 rec_reg2<=rec_reg1; //rec_reg2寄存rxd前一周期的值 end else if(!rec_reg1&&rec_reg2) //檢測rxd下降沿,也即是否有低電平到來 case (cnt) 4'd0:data[0]<=rxd; //接收第一位數據 4'd1:data[1]<=rxd; //接收第二位數據 4'd2:data[2]<=rxd; //接收第三位數據 4'd3:data[3]<=rxd; //接收第四位數據 4'd4:data[4]<=rxd; //接收第五位數據 4'd5:data[5]<=rxd; //接收第六位數據 4'd6:data[6]<=rxd; //接收第七位數據 4'd7:begin data[7]<=rxd; //接收第八位數據 rec_reg1<=1'b1;//數據接收完畢,起始位檢測寄存器復位, rec_reg2<=1'b1;//以準備下次接收 end default:begin data<=8'hzz; rec_reg1<=1'b1; rec_reg2<=1'b1; end endcase always @(posedge clk or negedge rst_n) if(!rst_n) cnt<=4'd0; //復位,接收數據技術器清零 else if(!rec_reg1&&rec_reg2) cnt<=(cnt<4'd7)?cnt+4'd1:4'd0; //檢測到起始位后,接收數據計數器啟動 endmodule 發送模塊//此模塊的作用是FPGA控制模塊向串口發送數據,起始位為“0”,停止位為“1” //延時電路的設計思想為按鍵按下彈起之后開始計時,時長為1010/11920秒 //延時去抖結束后給出發送標志位,直至FPGA向串口發送完畢 module serial_txd(rst_n,clk,ena,data,txd); input rst_n; //全局復位 input clk; //串口發送時鐘 input ena; //串口發送使能輸入端,即按鍵輸入端 input [7:0] data; //FPGA向串口發送的數據 output txd; //FPGA向串口發送數據的發送端 reg txd; reg [3:0] cnt; //發送數據計數器 reg [9:0] cnt_delay; //延時去抖計數器,延時時間為1010/11920秒 reg ena_reg1; //按鍵狀態寄存器1 reg ena_reg2; //按鍵狀態寄存器2 wire tx_flag; //發送標志位,高電平表示正在發送串口數據 always @(posedge clk or negedge rst_n) if(!rst_n) begin ena_reg1<=1'b1; ena_reg2<=1'b1; cnt_delay<=10'd0; end else if(ena_reg1&!ena_reg2) //檢測按鍵按下后彈起,即ena的上升沿(因為無動作時連接按鍵的pin處于高電平) case (cnt_delay) 10'd1011:begin cnt_delay<=10'd0; //延時去抖結束,計數器清零 ena_reg1<=1'b1; //按鍵狀態寄存器置1,等待下次ena上升沿的到來 ena_reg2<=1'b1; end default:cnt_delay<=cnt_delay+10'd1; //檢測到上升沿,延時去抖計數器啟動 endcase else begin ena_reg1<=ena; //ena_reg1寄存ena當前周期的狀態 ena_reg2<=ena_reg1; //ena_reg2寄存ena前一周期的狀態 end assign tx_flag=((cnt_delay>=10'd1000)&& //延時去抖結束后給出發送忙標志,持續10 (cnt_delay<=10'd1010)); //個周期,以等待FPGA向串口發送完畢 always @(posedge clk or negedge rst_n) if(!rst_n) cnt<=4'd0; //串口發送計數器復位 else if(!tx_flag) cnt<=4'd0; //若沒有檢測到串口發送標志位,則計數器等待 else cnt<=(cnt>=4'd10)?4'd11:cnt+4'd1; //檢測到串口發送標志位,啟動計數器 always @(posedge clk or negedge rst_n) if(!rst_n) txd<=1'bz; //發送端復位,高阻態 else case (cnt) 4'd0:txd<=1'bz; 4'd1:txd<=1'b0; //發送起始位 4'd2:txd<=data[0]; //發送第一位 4'd3:txd<=data[1]; //發送第二位 4'd4:txd<=data[2]; //發送第三位 4'd5:txd<=data[3]; //發送第四位 4'd6:txd<=data[4]; //發送第五位 4'd7:txd<=data[5]; //發送第六位 4'd8:txd<=data[6]; //發送第七位 4'd9:txd<=data[7]; //發送第八位 4'd10:txd<=1'b1; //發送停止位 default:txd<=1'bz; endcase endmodule 波特率產生模塊(此模塊的輸入時鐘來自PLL,頻率為12MHz,PLL模塊為宏功能函數) //此模塊為波特率生成模塊,修改BAUDRATE的值可改變波特率 //串口波特率時鐘的高電平僅僅持續一個clk周期 module clk_baud_gen(clk,rst_n,clk_baud); input clk; //波特率基準時鐘,此時鐘來自PLL input rst_n; //全局復位 output clk_baud; //串口波特率時鐘 wire clk_baud; reg [9:0] cnt; //波特率時鐘計數器 parameter BAUDRATE=10'd625; always @(posedge clk or negedge rst_n) if(!rst_n) cnt<=10'd0; else cnt<=(cnt==BAUDRATE-10'd2)?10'd0:cnt+1'b1; //波特率時鐘計數器啟動 assign clk_baud=(cnt==BAUDRATE-10'd2); endmodule |
|