Verilog写一个简单的UART发送模块
johncheapon 于 2017年07月23日 发表在 电路设计与调试

输入时钟50MHz

波特率设置为115200,起始位1bit,停止位1bit,数据8bits,奇偶校验无。硬件流控无。

主时钟需要经过分频得到UART的波特率时钟,分频系数为:50000000/115200=434。

RTL代码分为两个module,分别是core和demo

Core部分是协议的核心,用来实现完整的1帧UART数据发送。模块名称为uart_core。

module uart_core(clk,rst_n,
				 tx_data,tx_req,
				 tx_done,clk_baud,
				 txd);
input clk,rst_n;//输入的50MHz时钟和异步复位
input tx_req;//发送请求,高有效
input[7:0] tx_data;//来自上层模块的数据
output txd;//TXD,和USB转TTL线的RXD相连
output clk_baud;//波特率时钟,此处设置为115200
output tx_done;//发送完成一帧的提示信号,高有效

reg txd_r;

assign txd=txd_r;

/**
Div Factor:434.therefor,toogle value is (434/2)-1=216
**/
//主时钟分频得到波特率时钟
reg clk_baud;
reg[8:0] baud_div_cnt;//Baud Rate Divider
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
	begin
		clk_baud<='b0;//Low Polarity
		baud_div_cnt<='h0;
	end
	else if(baud_div_cnt<216)
	begin
		baud_div_cnt<=baud_div_cnt+1;
		clk_baud<=clk_baud;
	end
	else
	begin
		baud_div_cnt<=0;
		clk_baud<=~clk_baud;
	end
end

//通信格式:起始位1bit,停止位1bit,数据8bits,奇偶校验无。硬件流控无。
//start(0)-b0-b1-b2-b3-b4-b5-b6-b7-stop(1)-idle(1)
wire clk_baud_posedge;
assign clk_baud_posedge = ((baud_div_cnt==216) && (clk_baud==0));//判断波特率时钟的上升沿
reg[3:0] frame_bit_cnt;
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		frame_bit_cnt<='h0;
	else
	begin
		if((frame_bit_cnt<12) && (tx_req))//只有tx_req有效且比特数小于12时,比特计数器才会加
		begin
			if(clk_baud_posedge)
				frame_bit_cnt<=frame_bit_cnt+'h1;
			else
				frame_bit_cnt<=frame_bit_cnt;
		end
		else
			frame_bit_cnt<='h0;
	end
end
/**
    组合逻辑:输入为比特计数
    根据比特计数器来决定发送哪一位
**/
always @(frame_bit_cnt)
begin
	case(frame_bit_cnt)
	1:txd_r=0;//Start bit
	2:txd_r=tx_data[0];//LSB
	3:txd_r=tx_data[1];
	4:txd_r=tx_data[2];
	5:txd_r=tx_data[3];
	6:txd_r=tx_data[4];
	7:txd_r=tx_data[5];
	8:txd_r=tx_data[6];
	9:txd_r=tx_data[7];//MSB
	10,11,0:txd_r=1;//Stop bit,IDLE,Reset
	default:txd_r=1;
	endcase
end
assign tx_done=(frame_bit_cnt==11);//在发送空闲为期间,tx_done都置高
endmodule

顶层模块为uart_demo,将预先定义好的数据装载给Core模块使之发送出去。

由于笔者的FPGA板外部晶振是25MHz的,需要调用一个锁相环IP核将输入的25MHz倍频到50MHz以满足波特率分频的需求。

module uart_demo(clk25_in,rst_n,
		 txd);
input clk25_in,rst_n;//25MHz时钟输入,异步复位
output txd;//UART的TXD
wire tx_done;
wire tx_req;
wire clk;
reg[7:0] tx_data;发送数据寄存器
wire locked_sig;
parameter tDLY=5000;
wire pll_reset;
assign pll_reset=~rst_n;//由于PLL是高电平复位,需要先将系统复位信号取反再连到pll_reset
//例化锁相环IP核
mypll	mypll_inst1 (
	.areset ( pll_reset ),
	.inclk0 ( clk25_in ),
	.c0 ( clk ),
	.locked ( locked_sig )
	);
//例化UART核心模块
uart_core u1(clk,rst_n,tx_data,tx_req,tx_done,clk_baud,txd);


reg[3:0] frame_cnt;//帧计数器

reg tx_done_pos_r1;
reg tx_done_pos_r2;
wire tx_done_posedge;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
	{tx_done_pos_r1,tx_done_pos_r2}='b00;
else
begin
	tx_done_pos_r1<=tx_done;
	tx_done_pos_r2<=tx_done_pos_r1;
end
end
assign tx_done_posedge=((tx_done_pos_r1)&&(!tx_done_pos_r2));//捕获发送一帧完成信号的上升沿,并用该边沿驱动帧计数器增加
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		frame_cnt<='h0;
	else
	begin
		if((frame_cnt<8))//
		begin
			if(tx_done_posedge)
				frame_cnt<=frame_cnt+'h1;
			else
				frame_cnt<=frame_cnt;
		end
 		else
			frame_cnt<='h0; 
	end
end

//组合逻辑决定每一帧发什么字符
always @(frame_cnt)
begin
	case(frame_cnt)
	1:tx_data=8'h48;//'H'
	2:tx_data=8'h45;//'E'
	3:tx_data=8'h4C;//'L'
	4:tx_data=8'h4C;//'L'
	5:tx_data=8'h4F;//'O'
	6:tx_data=8'h0d;//'\r'
	7:tx_data=8'h0a;//'\n'
	0:tx_data=8'h00;//NULL
	default:tx_data=8'hff;
	endcase
end

reg[23:0] pwr_on_dly_cnt;//上电延时计数器
wire pwr_on_dly_done;//延时完成
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
	pwr_on_dly_cnt<='h0;
else
begin
	if(pwr_on_dly_cnt<tDLY)
		pwr_on_dly_cnt<=pwr_on_dly_cnt+1;
end
end
assign pwr_on_dly_done=(pwr_on_dly_cnt==tDLY);
assign tx_req=(pwr_on_dly_done)&&(frame_cnt<8);//只有在上电稳定且还没发完8帧时,发送请求才有效
endmodule

这个功能在MCU的C编程中只要一句printf("HELLO\r\n");即可

用verilog实现可以加深对数字通信协议的理解。毕竟在MCU中,printf最终还是要把数据传入MCU的UART IP核。