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


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


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

    CSDN

    遇见未来

    一、效果演示

    🥝发送Hello:
    hello

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

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

    二、代码编写

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

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

    代码:uart_string_tx_top.v

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    RTL视图:

    RTL1

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

    🔸代码详解:
    用于判断串口发送模块已发送完1byte数据:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试激励文件:

    `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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    其中,仿真模型key_model:

    `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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

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

    仿真结果:

    仿真结果

    三、封装为模块

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

    Str_Hello.v:

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    module Str_Hello的RTL视图:

    RTLstr

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

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    顶层模块的RTL视图:

    RTLstr2

    四、其余项目

    以使用串口发送模块发送字符串的思路,编写一个模块:
    按下一次按键,串口发送字符“0”;再按下一次按键,串口发送字符“1”;… ;再按下一次按键,串口发送字符“9”。且每一个数字字符为一行。

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

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

    module Num_Adder.v:

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    module uart_NumAdder_tx_top.v:

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    五、后记

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

    在设计串口发送字符串的逻辑框架时,发现仿真一直出不来结果,直到我看了一眼RTL视图:
    123
    看了之后才发现是例化模块的时候,引脚绑定的大小写不一致导致的。

    csdn

    🧸结尾


  • 相关阅读:
    【双指针-简单】977. 有序数组的平方
    初识数据结构
    MQTT 持久会话与 Clean Session 详解
    DPDK的几种buffer分配方式
    【云原生 二】 Docker架构演进过程
    [剑指Offer] 三种方法求解找出数组中出现次数超过一半的数字
    three.js学习-智慧城市
    Azure 机器学习:使用 Azure 机器学习 CLI、SDK 和 REST API 训练模型
    物联网系统
    前后端分离不可忽视的陷阱,深入剖析挑战,分享解决方案,助你顺利实施分离开发。
  • 原文地址:https://blog.csdn.net/m0_56262476/article/details/133621122