FPGA学习笔记之UART

代码源于小梅哥FPGA视频教程,本人仅参考记录学习

串口发送原理

起始位+数据+停止位一共十位数据

Alt text

串口模块设计图

Alt text

波特率设置参考表

baud_set 波特率 波特率周期 波特率分频计数值 System_clk_period = 20计数值
0 9600 104167ns 104167/ System_clk_period 5208-1
1 19200 52083ns 52083/ System_clk_period 2604-1
2 38400 26041ns 26041/ System_clk_period 1302-1
3 57600 17361ns 17361/ System_clk_period 868-1
4 115200 8680ns 8680/ System_clk_period 434-1

总体框图

Alt text

(照图施工)代码解析

uart_state部分对应代码如下:

always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		uart_state=1'b0;
	else if(send_en)
		uart_state=1'b1;
	//else if(Tx_done)//用此句会导致Tx_done有两个时钟周期而不是一个
        else if(bps_cnt==4'd11)
		uart_state=1'b0;
	else
		uart_state<=uart_state;

将要发送的数据用寄存器寄存起来,以免发送过程中数据突然变化

always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		r_data_byte<=8'd0;
	else if(send_en)
		r_data_byte<=r_data_byte;
	else
		r_data_byte<=r_data_byte;

查找表模块,实现分频计数值的输出,送入分频模块产生bps_clk

always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		bps_DR<=16'd5207;
	else begin
		case(baud_set)
			0:bps_DR<=16'd5207;
			1:bps_DR<=16'd2603;
			2:bps_DR<=16'd1301;
			3:bps_DR<=16'd867;
			4:bps_DR<=16'd433;
//			5:bps_DR<=16'd5207
//			6:bps_DR<=16'd5207
//			7:bps_DR<=16'd5207
			default:bps_DR<=16'd5207;
		endcase
	end

分频计数器模块

//counter
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		div_cnt<=16'd0;
	else if(uart_state)begin
		if (div_cnt==bps_DR)
			div_cnt<=16'd0;
	else
		div_cnt<=div_cnt+1'b1;
	end
	else
		div_cnt<=16'd0;

根据分频计数器模块的输出产生bps_clk

always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		bps_clk<=1'b0;
	else if (div_cnt==16'd1)
		bps_clk<=1'b1;
	else
		bps_clk<=1'b0;

bps_cnt 计数,到11清零,控制节奏

always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		bps_cnt<=4'd0;
//else if(Tx_done)//用此句会导致Tx_done有两个时钟周期而不是一个
	else if(bps_cnt == 4'd11)
		bps_cnt<=4'd0;
	else if (bps_clk)
		bps_cnt<=bps_cnt+1'b1;
	else
		bps_cnt<=bps_cnt;

产生发送完成信号tx_done

always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		Tx_done<=1'b0;
	else if (bps_cnt==4'd11)
		Tx_done=1'b1;
	else
		Tx_done<=1'b0;

十选一多路器,控制输出哪一位

always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		Rs232_Tx<=1'b1;
	else begin
		case(bps_cnt)
			0:Rs232_Tx<=1'b1;
			1:Rs232_Tx<=START_BIT;
			2:Rs232_Tx<=r_data_byte[0];
			3:Rs232_Tx<=r_data_byte[1];
			4:Rs232_Tx<=r_data_byte[2];
			5:Rs232_Tx<=r_data_byte[3];
			6:Rs232_Tx<=r_data_byte[4];
			7:Rs232_Tx<=r_data_byte[5];
			8:Rs232_Tx<=r_data_byte[6];
			9:Rs232_Tx<=r_data_byte[7];
			10:Rs232_Tx<=STOP_BIT;
			default Rs232_Tx<=1'b1;
		endcase
	end

testbench文件仿真

`timescale 1ns/1ns
`define clk_period 20

module uart_byte_tx_tb;

	reg Clk;
	reg Rst_n;
	reg [7:0]data_byte;
	reg send_en;
	reg [2:0]baud_set;
	
	wire Rs232_Tx;
	wire Tx_Done;
	wire uart_state;
	
	uart_byte_tx uart_byte_tx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.data_byte(data_byte),
		.send_en(send_en),
		.baud_set(baud_set),
		
		.Rs232_Tx(Rs232_Tx),
		.Tx_Done(Tx_Done),
		.uart_state(uart_state)
	);
	
	initial Clk = 1;
	always#(`clk_period/2)Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		data_byte = 8'd0;
		send_en = 1'd0;
		baud_set = 3'd4;//115200波特率
		#(`clk_period*20 + 1 )
		Rst_n = 1'b1;
		#(`clk_period*50);
		data_byte = 8'haa;
		send_en = 1'd1;
		#`clk_period;
		send_en = 1'd0;
		
		@(posedge Tx_Done)
		
		#(`clk_period*5000);
		data_byte = 8'h55;
		send_en = 1'd1;
		#`clk_period;
		send_en = 1'd0;
		@(posedge Tx_Done)
		#(`clk_period*5000);
		$stop;	
	end

endmodule

仿真波形

Alt text

板级验证

//小梅哥AC620开发板

module uart_byte_tx_top(clk,rst_n,data_in,uart_tx,send_en,tx_done,uart_state);

    input [7:0]data_in;
    input clk;
    input rst_n;
    input send_en;
    
    output  uart_tx;
    output  tx_done;
    output uart_state;

    //例化
    uart_byte_tx myUART(//测试能否发送
	.Clk(clk),
	.Rst_n(rst_n),
	.data_byte(data_in),
	.send_en(~send_en),//开关按下为低电平所以取个反
	.baud_set(0),
	.Rs232_Tx(uart_tx),
	.Tx_Done(tx_done),
	.uart_state(uart_state)
);

endmodule

引脚分配

Alt text

串口数据显示

我使用的keysking的在线串口助手 Alt text


本文章使用limfx的vscode插件快速发布