Verilog写一个简单的UART发送模块
johncheapon 于 2017年07月23日 发表在 FPGA 开发
输入时钟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核。
注意:本站所有文章除特别说明外,均为原创,转载请务必以超链接方式并注明作者出处。
标签:VerilogHDL,FPGA,通信
