【FPGA零基础学习之旅#14】串口发送字符串

🎉欢迎来到FPGA专栏~串口发送字符串


  • ☆* o(≧▽≦)o *☆ ~我是小夏与酒🍹
  • 博客主页: 小夏与酒的博客
  • 🎈该系列文章专栏: FPGA学习之旅
  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
  • 📜 欢迎大家关注! ❤️

🎉 目录-串口发送字符串

一、效果演示

🥝发送Hello:

🥝发送数字字符并自增1:

🥝发送数字字符复位后从1开始发送:

二、代码编写

✨注:本篇文章需要使用到按键消抖模块串口发送模块(1byte)
按键消抖模块: 【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现)
串口发送模块: 【FPGA零基础学习之旅#13】串口发送模块设计与验证

首先展示整体代码和RTL视图。

代码:uart_string_tx_top.v

c 复制代码
module uart_string_tx_top(
	input	Clk,
	input 	Rst_n,
	input 	key_in,
	output 	uart_tx,
	output 	led
);
	reg 	send_en;
	reg 	[7:0]data_byte;
	reg 	[2:0]cnt;
	wire 	Tx_Done;
	wire 	key_flag;
	wire 	key_state;
	
	localparam
		byte1 = "H",
		byte2 = "E",
		byte3 = "L",
		byte4 = "L",
		byte5 = "O",
		byte6 = "\n";
		
	KeyFilter KeyFilter(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(key_flag),
		.key_state(key_state)
	);

	uart_byte_tx uart_byte_tx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.data_byte(data_byte),
		.send_en(send_en),
		.baud_set(3'd0),
		.uart_tx(uart_tx),
		.Tx_Done(Tx_Done),
		.uart_state(led)
	);

	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			cnt <= 1'b0;
		else if(Tx_Done)
			cnt <= cnt + 1'b1;
		else if(key_flag & !key_state)
			cnt <= 1'b0;
		else
			;
	end

	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			send_en <= 1'b0;
		else if(key_flag & !key_state)
			send_en <= 1'b1;
		else if(Tx_Done & (cnt < 3'd5))
			send_en <= 1'b1;
		else
			send_en <= 1'b0;
	end

	always@(*)begin
		case(cnt)
			3'd0:data_byte = byte1;
			3'd1:data_byte = byte2;
			3'd2:data_byte = byte3;
			3'd3:data_byte = byte4;
			3'd4:data_byte = byte5;
			3'd5:data_byte = byte6;
			default:data_byte = 0;
		endcase
	end

endmodule

RTL视图:

🔸设计思路:

使用前文的串口发送模块(FPGA零基础学习之旅#13】串口发送模块设计与验证)一次只能发送1byte的数据,为了发送多比特的字符串数据,我们将字符串按照byte流发送出去即可。

🔸代码详解:

用于判断串口发送模块已发送完1byte数据:

c 复制代码
always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		cnt <= 1'b0;
	else if(Tx_Done)
		cnt <= cnt + 1'b1;
	else if(key_flag & !key_state)
		cnt <= 1'b0;
	else
		;
end

用于开启或关闭串口发送模块的使能信号:

c 复制代码
always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		send_en <= 1'b0;
	else if(key_flag & !key_state)
		send_en <= 1'b1;
	else if(Tx_Done & (cnt < 3'd5))
		send_en <= 1'b1;
	else
		send_en <= 1'b0;
end

通过查找表的方式将byte流发送出去:

c 复制代码
always@(*)begin
	case(cnt)
		3'd0:data_byte = byte1;
		3'd1:data_byte = byte2;
		3'd2:data_byte = byte3;
		3'd3:data_byte = byte4;
		3'd4:data_byte = byte5;
		3'd5:data_byte = byte6;
		default:data_byte = 0;
	endcase
end

测试激励文件:

c 复制代码
`timescale 1ns/1ns
`define clock_period 20

module uart_string_tx_top_tb;

	reg Clk;
	reg Rst_n;
	reg press;
	wire key_in;
	wire uart_tx;
	wire led;

	uart_string_tx_top uart_string_tx_top0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.uart_tx(uart_tx),
		.led(led)
	);
	
	key_model key_model(
		.press(press),
		.key(key_in)
	);
	
	initial Clk = 1;
	always#(`clock_period / 2) Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		press = 0;
		#(`clock_period*20 + 1);
		Rst_n = 1'b1;
		#(`clock_period*20 + 1);
		press = 1;
		#(`clock_period*20 + 1);
		press = 0;
		
		wait(uart_string_tx_top0.Tx_Done &(uart_string_tx_top0.cnt == 3'd5));
		#(`clock_period*2000000 + 1);
		
		#(`clock_period*20 + 1);
		press = 1;
		#(`clock_period*20 + 1);
		press = 0;
		
		wait(uart_string_tx_top0.Tx_Done &(uart_string_tx_top0.cnt == 3'd5));
		#(`clock_period*2000000 + 1);
		
		$stop;
	end

endmodule

其中,仿真模型key_model:

c 复制代码
`timescale 1ns/1ns

module key_model(press,key);
	
	input press;
	output reg key;
	
	reg [15:0]myrand;
	
	initial begin
		key = 1'b1;
	end
	
	always@(posedge press)
		press_key;
	
	task press_key;
		begin
			//50次随机时间按下抖动
			repeat(50)begin
				myrand = {$random}%65536;//0~65535
				#myrand key = ~key;
			end
			key = 0;
			#25_000_000;//按下稳定
			
			//50次随机时间释放抖动
			repeat(50)begin
				myrand = {$random}%65536;//0~65535
				#myrand key = ~key;
			end
			key = 1;
			#25_000_000;//释放稳定
		end
	endtask	

endmodule

关于上述仿真模型的基础讲解,见文章:【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现)

仿真结果:

三、封装为模块

将uart_string_tx_top中发送字符串"Hello"代码的部分封装为一个模块,只需要把设计部分中使用到的输入输出信号整理好即可。

Str_Hello.v:

c 复制代码
module Str_Hello(
	input 				Clk,
	input 				Rst_n,
	input 				Tx_Done,
	input 				key_flag,
	input 				key_state,
	output reg 			send_en,
	output reg [7:0]	data_byte
);

	reg 	[2:0]cnt;
	
	localparam
		byte1 = "H",
		byte2 = "E",
		byte3 = "L",
		byte4 = "L",
		byte5 = "O",
		byte6 = "\n";
	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			cnt <= 1'b0;
		else if(Tx_Done)
			cnt <= cnt + 1'b1;
		else if(key_flag & !key_state)
			cnt <= 1'b0;
		else
			;
	end

	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			send_en <= 1'b0;
		else if(key_flag & !key_state)
			send_en <= 1'b1;
		else if(Tx_Done & (cnt < 3'd5))
			send_en <= 1'b1;
		else
			send_en <= 1'b0;
	end

	always@(*)begin
		case(cnt)
			3'd0:data_byte = byte1;
			3'd1:data_byte = byte2;
			3'd2:data_byte = byte3;
			3'd3:data_byte = byte4;
			3'd4:data_byte = byte5;
			3'd5:data_byte = byte6;
			default:data_byte = 0;
		endcase
	end

endmodule

module Str_Hello的RTL视图:

将该模块例化到顶层模块中:

c 复制代码
module uart_string_tx_top(
	input	Clk,
	input 	Rst_n,
	input 	key_in,
	output 	uart_tx,
	output 	led
);
	wire 	send_en;
	wire 	[7:0]data_byte;
	wire 	Tx_Done;
	wire 	key_flag;
	wire 	key_state;
		
	KeyFilter KeyFilter(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(key_flag),
		.key_state(key_state)
	);

	uart_byte_tx uart_byte_tx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.data_byte(data_byte),
		.send_en(send_en),
		.baud_set(3'd0),
		.uart_tx(uart_tx),
		.Tx_Done(Tx_Done),
		.uart_state(led)
	);
	
	Str_Hello Str_Hello0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.Tx_Done(Tx_Done),
		.send_en(send_en),
		.key_flag(key_flag),
		.key_state(key_state),
		.data_byte(data_byte)
	);

endmodule

顶层模块的RTL视图:

四、其余项目

以使用串口发送模块发送字符串的思路,编写一个模块:

按下一次按键,串口发送字符"0";再按下一次按键,串口发送字符"1";... ;再按下一次按键,串口发送字符"9"。且每一个数字字符为一行。

🔸实现思路:

当接收到一次按键信号之后,串口发送模块依次发送数字字符和一个换行符;同时,再接收到一次按键信号的同时,内部的计数器开始计数,一次按键信号获取后计数器增加1,用于判断发送的数字字符的大小。

🔸实现效果:

🔸先看RTL视图 来理解思路:

通过RTL视图,可以看到串口发送模块的Tx_Done信号是作为反馈信号输入给Num_Adder模块的,该信号即用于判断一个byte数据发送的完成。当一个数字字符发送完成并返回Tx_Done信号之后,需要继续发送一个换行符。

module Num_Adder.v:

c 复制代码
module Num_Adder(
	input 				Clk,
	input 				Rst_n,
	input 				key_flag,
	input 				key_state,
	input 				Tx_Done,
	output reg 			send_en,
	output reg [7:0]	Num_byte
);
	reg [3:0]cnt;
	reg [1:0]cnt_N;
	reg [7:0]Num_byte_r;

//--------<发送数据的增加模块>--------		
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			cnt <= 4'd0;
		else if(key_flag & !key_state)
			cnt <= cnt + 1'd1;
		else if(cnt == 4'd10)
			cnt <= 4'd0;
		else
			cnt <= cnt;
	end

//--------<数据查找表>--------		
	always@(*)begin
		case(cnt)
			4'd1:Num_byte_r = "0";
			4'd2:Num_byte_r = "1";
			4'd3:Num_byte_r = "2";
			4'd4:Num_byte_r = "3";
			4'd5:Num_byte_r = "4";
			4'd6:Num_byte_r = "5";
			4'd7:Num_byte_r = "6";
			4'd8:Num_byte_r = "7";
			4'd9:Num_byte_r = "8";
			4'd10:Num_byte_r = "9";
			default:Num_byte_r = 0;
		endcase
	end

//--------<发送"\n"的计数器>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			cnt_N <= 2'd0;
		else if(Tx_Done)
			cnt_N <= cnt_N + 1'b1;
		else if(key_flag & !key_state)
			cnt_N <= 2'd0;
		else
			;
	end

//--------<发送"\n">--------	
	always@(*)begin
		case(cnt_N)
			2'd0:Num_byte = Num_byte_r;
			2'd1:Num_byte = "\n";
			default:Num_byte = 0;
		endcase
	end

//--------<发送模块使能信号的处理>--------		
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			send_en <= 1'b0;
		else if(key_flag & !key_state)
			send_en <= 1'b1;
		else if(Tx_Done & (cnt_N < 2'd2))
			send_en <= 1'b1;
		else
			send_en <= 1'b0;
	end	
	
endmodule

module uart_NumAdder_tx_top.v:

c 复制代码
module uart_NumAdder_tx_top(
	input	Clk,
	input 	Rst_n,
	input 	key_in,
	output 	uart_tx,
	output 	led
);
	
	wire [7:0]data_byte;
	wire 	key_flag;
	wire 	key_state;
	wire	Tx_Done;
	wire  send_en;
			
	KeyFilter KeyFilter(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(key_flag),
		.key_state(key_state)
	);
	
	Num_Adder Num_Adder0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_flag(key_flag),
		.key_state(key_state),
		.Tx_Done(Tx_Done),
		.send_en(send_en),
		.Num_byte(data_byte)	
	);
	
	uart_byte_tx uart_byte_tx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.data_byte(data_byte),
		.send_en(send_en),
		.baud_set(3'd0),
		.uart_tx(uart_tx),
		.Tx_Done(Tx_Done),
		.uart_state(led)
	);

endmodule

五、后记

我在我的每篇文章中都几乎放上设计的RTL视图,因为观察RTL视图,也是一种简单的debug方法。

在设计串口发送字符串的逻辑框架时,发现仿真一直出不来结果,直到我看了一眼RTL视图:

看了之后才发现是例化模块的时候,引脚绑定的大小写不一致导致的。

🧸结尾


相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
ZPC82105 天前
docker 镜像备份
人工智能·算法·fpga开发·机器人
ZPC82105 天前
docker 使用GUI ROS2
人工智能·算法·fpga开发·机器人
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs