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,通信