FPGA筆記-串口發送模塊

串口發送模塊筆記

首先時完成以後的效果如圖:

在這裏插入圖片描述

一級目錄

二級目錄

三級目錄

一、分析

觀看了小梅哥的講解和設計思路以後我決定自己重新寫一下串口發送模塊;

串口發送模塊的原理比較簡單,只需要考慮波特率和發送的數據長度就可以了。

在這裏插入圖片描述

​ 如上圖所示,一般的串口模塊就是以8bit一個字節的長度爲單位發送數據的,每個數據的開頭都由低電平爲開始,高電平爲結束。那麼開始結束的兩個bit加上數據的8bit,每發送一次數據至少處理10bit信號。

​ 從整體上先設計邏輯電路,首先需要一個基本的分頻計數器,來產生9600Hz的數據脈衝,其次需要一個計數器,計數頻率9600Hz,計數每滿10就從新計數。

在這裏插入圖片描述

二、設計每個部分的代碼

1、5207計數器

在這裏插入圖片描述

always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			flag5207 <= 0;
		else if(conter == 13'd5207)
			flag5207 <= 1'b1;
		else
			flag5207 <= 1'b0;
	end

爲最基本的計數器分頻,產生9600Hz的信號提供給其他部分

2、數據比較產生使能信號

在這裏插入圖片描述

always@(posedge clk or negedge rst_n)
	begin
		if (!rst_n)
			conter <= 0;
		else if(conter == 13'd5207)
			conter <= 0;
		else
			conter <= conter +1'b1;
	end

爲了後面分頻給後面的邏輯電路,讓5207計數器每次循環計數到1時就產生一個高電平的使能信號給下面的電路。

3、11進制計數器

在這裏插入圖片描述

always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			con <= 0;
		else if(flag5207 == 1'b1)
		begin
			if(con == 4'd11)
				con <= 0;
			else
				con <= con +1'b1;
		end
		else
			con <= con;
	end

爲了發送一個字節數據需要循環發送起始信號、數據段、結束信號,加在一起一共需要10bit,最後1個bit需要保持高電平防止信號不穩定

4、數據選擇器

在這裏插入圖片描述

always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			TX <= 1'b0;
		else 
		begin
			case(con)
			0:TX <= 1'b0;
			1:TX <= 1'b1; //bit0
			2:TX <= 1'b0; //bit1
			3:TX <= 1'b1; //bit2
			4:TX <= 1'b0; //bit3
			5:TX <= 1'b1; //bit4
			6:TX <= 1'b0; //bit5
			7:TX <= 1'b1; //bit6
			8:TX <= 1'b0; //bit7
			default: TX <= 1'b1;
			endcase
		end
	end

來產生最後的輸出信號,需要接收11進制計數器的數據,選擇發送從0到11的數據。分別代表了數據起始位和數據位以及數結束位。

三、基礎部分的全部代碼:

​ 這裏我實現了基礎功能,下載到開發板裏以後只能一直循環發送數據“0x55”,轉換成ASIC就是“u”。也只以9600這一種波特率發送。

module main(
				//input
				clk,//模塊的時鐘 50MHz
				rst_n, //系統復位信號
				//output
				TX, //串口輸出信號
				conter, //內部計數器
				con, //內部計數器
				);
			
	input clk;
	input rst_n;
	
	output TX;
	output [13:0] conter;
	output [4:0]con;
	
	wire clk;
	wire	rst_n;	
	
	reg [13:0] conter;
	reg [4:0]con;
	reg TX;
	reg flag5207;
	
	//爲最基本的計數器分頻,產生9600Hz的信號提供給其他部分
	always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			flag5207 <= 0;
		else if(conter == 13'd5207)
			flag5207 <= 1'b1;
		else
			flag5207 <= 1'b0;
	end
	
	//爲了後面分頻給後面的邏輯電路,讓5207計數器每次循環計數到1時就產生一個高電平的使能信號給下面的電路。
	always@(posedge clk or negedge rst_n)
	begin
		if (!rst_n)
			conter <= 0;
		else if(conter == 13'd5207)
			conter <= 0;
		else
			conter <= conter +1'b1;
	end
	
	//11進制計數器爲了發送一個字節數據需要循環發送起始信號、數據段、結束信號,
	//加在一起一共需要10bit,最後1個bit需要保持高電平防止信號不穩定
	always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			con <= 0;
		else if(flag5207 == 1'b1)
		begin
			if(con == 4'd13)
				con <= 0;
			else
				con <= con +1'b1;
		end
		else
			con <= con;
	end
	
	//來產生最後的輸出信號,需要接收11進制計數器的數據,
	//選擇發送從0到11的數據。分別代表了數據起始位和數據位以及數結束位
	always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			TX <= 1'b0;
		else 
		begin
			case(con)
			0:TX <= 1'b0;
			1:TX <= 1'b1; //bit0
			2:TX <= 1'b0; //bit1
			3:TX <= 1'b1; //bit2
			4:TX <= 1'b0; //bit3
			5:TX <= 1'b1; //bit4
			6:TX <= 1'b0; //bit5
			7:TX <= 1'b1; //bit6
			8:TX <= 1'b0; //bit7
			default: TX <= 1'b1;
			endcase
		end
	end
	
endmodule

四、測試文件

`timescale 1ns/1ns

module main_testbench;

	reg clk;
	reg rst_n;

	wire TX;
	wire [13:0] conter;
	wire [4:0] con;
	
	main main(
				.clk(clk),
				.rst_n(rst_n),
				.TX(TX),
				.conter(conter),
				.con(con)
				);
				
	always #10 clk =~ clk;
	
	initial
	begin
		clk = 1; 
		rst_n = 1'b0;
		#100
		rst_n = 1'b1;
	end
	
endmodule

五、完善以後的發送模塊RTL

在這裏插入圖片描述

在原來的基礎上增加了可以選擇的波特率、模塊的控制使能信號、發送一個字節的完成信號。

下面是我改進以後的代碼,可以在115200波特率的情況下向串口發送“hello word!”

1、頂層文件:

main.v

module main(
				clk,
				rst_n,
				TX
				);

	input clk;
	input rst_n;
	
	output TX;

	
	wire clk;
	wire	rst_n;	
	
	wire TX;
	wire tx_down;
	
	reg [8:0]data;
	reg [3:0]flag;
	urat_tx urat_tx(
				//input
				.clk(clk),//模塊的時鐘 50MHz
				.rst_n(rst_n), //系統復位信號
				.set_Baud_rate(4'd4),//波特率大小設置
				.data(data), //要發送的一個字節數據
				.en(1'b1),
				
				//output
				.TX(TX), //串口輸出信號
				.tx_down(tx_down) //發送完成標誌,每完成一次發送就產生一段時間高電平
				);

	always@(posedge tx_down or negedge rst_n)
	begin
		if (!rst_n)
			flag <=0;
		else
			flag <= flag + 1'd1;
	end
	
	always@(posedge tx_down or negedge rst_n)
	begin
		if(!rst_n)
			data <= 0;
		else 
			case(flag)
				0: data <= 8'h68; //h
				1: data <= 8'h65; //e
				2: data <= 8'h6c; //l
				3: data <= 8'h6c; //l
				4: data <= 8'h6f; //o
				5: data <= 8'h20; //空格
				6: data <= 8'h77; //w
				7: data <= 8'h6f; //o
				8: data <= 8'h72; //r
				9: data <= 8'h64; //d
				10: data <= 8'h21; //!
				11: data <= 8'h0d; //回車 \r
				12: data <= 8'h0a; //換行 \n
				default : data <= 8'h00;
			endcase
	end
endmodule

2、串口發送文件:

urat_tx.v

module urat_tx(
				//input
				clk,//模塊的時鐘 50MHz
				rst_n, //系統復位信號
				set_Baud_rate,//波特率大小設置
				data, //要發送的一個字節數據
				en, //模塊工作的使能信號
				//output
				TX, //串口輸出信號
				tx_down //發送完成標誌,每完成一次發送就產生一段高脈衝
				);
			
	input clk; //模塊的時鐘 50MHz
	input rst_n; //系統復位信號
	input [8:0] data; //要發送的一個字節數據
	input [3:0] set_Baud_rate;  //波特率大小設置
	input en; //模塊工作的使能信號
	
	output TX; //串口輸出信號
	output tx_down; //發送完成標誌,每完成一次發送就產生一段高脈衝
	
	wire clk;
	wire	rst_n;	
	wire en;
	
	reg TX;
	reg tx_down;
	
	//模塊內部的寄存器
	reg flag5207;
	reg [8:0] rev_data;
	reg [13:0] conter;
	reg [4:0]con;
	reg [13:0] Baud_rate; //波特率
	//localparam Baud_rate=433;//設置波特率
	
	//接收設置的波特率大小
	always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			Baud_rate <= 5207;
		else 
			case (set_Baud_rate)
				0: Baud_rate <= 5207; //9600
				1: Baud_rate <= 2603;
				2: Baud_rate <= 1301;
				3: Baud_rate <= 867;
				4: Baud_rate <= 433; //115200
				default: Baud_rate <= 5207;
			endcase
	end
	
	//接收外來的要發送的數據;
	always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			rev_data <= 8'd0;
		else
			rev_data <= data;
	end
	
	//爲最基本的計數器分頻,產生例如9600Hz的信號提供給其他部分
	always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			flag5207 <= 0;
		else if(en)//增加使能信號控制
		begin
			if(conter == Baud_rate)
			flag5207 <= 1'b1;
			else
				flag5207 <= 1'b0;
		end
		else
			flag5207 <= 1'b0;//如果不使能相當於給復位了  使能:en=1;
	end
	
	//爲了後面分頻給後面的邏輯電路,讓5207計數器每次循環計數到1時就產生一個高電平的使能信號給下面的電路。
	always@(posedge clk or negedge rst_n)
	begin
		if (!rst_n)
			conter <= 0;
		else if(conter == Baud_rate)
			conter <= 0;
		else
			conter <= conter +1'b1;
	end
	
	//11進制計數器爲了發送一個字節數據需要循環發送起始信號、數據段、結束信號,
	//加在一起一共需要10bit,最後1個bit需要保持高電平防止信號不穩定
	always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			con <= 0;
		else if(flag5207 == 1'b1)
		begin
			if(con == 4'd13)
				con <= 0;
			else
				con <= con +1'b1;
		end
		else
			con <= con;
	end
	
	//來產生最後的輸出信號,需要接收11進制計數器的數據,
	//選擇發送從0到11的數據。分別代表了數據起始位和數據位以及數結束位
	always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			TX <= 1'b0;
		else 
		begin
			case(con)
			0:TX <= 1'b0;
			1:TX <= rev_data[0]; //bit0
			2:TX <= rev_data[1]; //bit1
			3:TX <= rev_data[2]; //bit2
			4:TX <= rev_data[3]; //bit3
			5:TX <= rev_data[4]; //bit4
			6:TX <= rev_data[5]; //bit5
			7:TX <= rev_data[6]; //bit6
			8:TX <= rev_data[7]; //bit7
			default: TX <= 1'b1;
			endcase
		end
	end
	
	//發送一個字節完成的標誌位,高電平
	always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			tx_down <=0;
		else if(con == 4'd13)
			tx_down <= 1'b1;
		else
			tx_down <= 1'b0;
	end
	
endmodule

注意

串口要發送換行符就必須發送\r\n兩個數據,並不是直接發送這四個字符就可以實現換行的目的,而是需要根據 ASCII碼 的值來發送,這裏我主要參考了這兩位大神的教程:

回車與換行的區別

ZZ 回車\r和換行\n的區別–ASCII碼錶(含二進制 十進制 十六進制 )

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章