FPGA笔记-串口发送模块

串口发送模块笔记

首先时完成以后的效果如图:

FPGA笔记-串口发送模块_第1张图片

目录:

  • 串口发送模块笔记
  • 一级目录
    • 二级目录
      • 三级目录
    • 一、分析
    • 二、设计每个部分的代码
      • 1、5207计数器
      • 2、数据比较产生使能信号
      • 3、11进制计数器
      • 4、数据选择器
    • 三、基础部分的全部代码:
    • 四、测试文件
    • 五、完善以后的发送模块RTL
      • 1、顶层文件:
      • 2、串口发送文件:
      • 注意

一级目录

二级目录

三级目录

一、分析

观看了小梅哥的讲解和设计思路以后我决定自己重新写一下串口发送模块;

串口发送模块的原理比较简单,只需要考虑波特率和发送的数据长度就可以了。

在这里插入图片描述

​ 如上图所示,一般的串口模块就是以8bit一个字节的长度为单位发送数据的,每个数据的开头都由低电平为开始,高电平为结束。那么开始结束的两个bit加上数据的8bit,每发送一次数据至少处理10bit信号。

​ 从整体上先设计逻辑电路,首先需要一个基本的分频计数器,来产生9600Hz的数据脉冲,其次需要一个计数器,计数频率9600Hz,计数每满10就从新计数。

FPGA笔记-串口发送模块_第2张图片

二、设计每个部分的代码

1、5207计数器

FPGA笔记-串口发送模块_第3张图片

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、数据比较产生使能信号

FPGA笔记-串口发送模块_第4张图片

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进制计数器

FPGA笔记-串口发送模块_第5张图片

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、数据选择器

FPGA笔记-串口发送模块_第6张图片

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

FPGA笔记-串口发送模块_第7张图片

在原来的基础上增加了可以选择的波特率、模块的控制使能信号、发送一个字节的完成信号。

下面是我改进以后的代码,可以在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码表(含二进制 十进制 十六进制 )

你可能感兴趣的:(单片机)