ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] > # 说明 由于fpga的系统时钟可能会存在差异,之前搜索的一些串口模块都是要经过修改才能更好的适配当前的项目,为了之后可以更快的实现项目串口的添加,在这里首先写了一个**通用串口发送模块**,根据系统的时钟和需要的波特率,可自行设置。 > # 实现如下 ```verilog `timescale 1ns / 1ps // ******************************************************************** // FileName : uart_tx.v // Author :hpy // Email :yuan_hp@qq.com // Date :2020年12月06日 // Description :串口发送模块,该模块使用时必须先复位一次,否则无法工作 // 这是因为设计的时候使用了独热码,状态没有全零的。 // -------------------------------------------------------------------- /*------------------------------------------- uart_tx #( .CLK_FREQ(12000000), //时钟频率 .BSP(9600) // 波特率 ) u1 ( .clk(clk), .rst_n(), .start() , // 发送触发标志,上升沿有效 .sdata(), //要发送的数据 .tx(), //串口tx .busy(), //忙标志 .interrupt() //发送一个字节完毕的中断信号,数据发送完毕后会产生一个clk的上升沿脉冲 ); --------------------------------------------*/ module uart_tx#( parameter CLK_FREQ = 12000000, //时钟频率 BSP = 9600 // 波特率 )( input clk, input rst_n, input start , // 发送触发标志,上升沿有效 input [7:0]sdata, //要发送的数据 output reg tx, output reg busy, //忙标志 output interrupt //中断信号 ); localparam CNT_MAX = CLK_FREQ / BSP; //根据时钟频率和波特率计算分频值 localparam CNT_HALF = (CNT_MAX>>1) ; // //状态机状态 localparam IDEL = 6'b000001, PRE = 6'b000010, START = 6'b000100, TX_DATA = 6'b001000, TX_CHECK = 6'b010000, STOP = 6'b100000; //assign busy = (nst != IDEL ) ; always @(posedge clk) begin if( !rst_n ) busy <= 1'b0; else begin if(start_p && (cst==IDEL)) busy <= 1'b1; else if(nst == IDEL && bsp_clk) busy <= 1'b0; else busy <= busy; end end reg [5:0] cst,nst; /***************************************************** * 产生数据接发送完毕的中断信号 *****************************************************/ reg busy_f; assign interrupt = (~busy) & busy_f; always@(posedge clk) begin if( !rst_n ) begin busy_f <= 1'b0; end else begin busy_f <= busy; end end function integer clog2 (input integer din); for( clog2 = 0; din; clog2=clog2 +1) din = din >>1; endfunction /***************************************************** * 检测触发信号上升沿 *****************************************************/ reg start_f; wire start_p; assign start_p = start & (~start_f); always@(posedge clk) begin if ( !rst_n ) begin start_f <= 1'b0; //start_p <= 1'b0; end else begin start_f <= start; //start_p <= start & (~start_f); end end reg [ clog2(CNT_MAX) - 1 : 0 ] cnt ; //计数器 /***************************************************** * 计数产生bps *****************************************************/ wire bsp_clk; assign bsp_clk = (cnt == CNT_HALF)? 1'B1 : 1'B0; always@(posedge clk ) begin if(!rst_n)begin cnt <= 'd0; end else begin if( cnt >= CNT_MAX - 1'b1 || ((cst == IDEL) && (start_p))) cnt <= 1'b0; else if(busy) cnt <= cnt + 1'b1; else cnt <= 'd0; end end /***************************************************** * 状态机到下一状态 *****************************************************/ always@(posedge clk) begin if(rst_n == 1'b0 )begin cst <= IDEL; end else begin if(cst == IDEL ) cst <= nst; else cst <= (bsp_clk) ? nst : cst; end end /***************************************************** * 状态机下一状态选择 *****************************************************/ reg [ 3 : 0 ] tx_cnt; always@(*) begin nst = IDEL; case(cst) IDEL : nst = (start_p)? PRE : IDEL; PRE : nst = START; START : nst = TX_DATA; TX_DATA : nst = (tx_cnt >= 4'd8)? TX_CHECK : TX_DATA; TX_CHECK :nst = STOP; STOP: nst = IDEL; default:nst = IDEL; endcase end /***************************************************** * 状态机输出 *****************************************************/ reg [7:0]tx_buf; always@(posedge clk) begin if(rst_n == 1'b0 )begin tx <= 1'b1; tx_cnt <= 4'd0; end else begin case(nst) IDEL: begin tx <= 1'b1; end PRE: begin //由于时钟的原因,计算得到的分频值可能是小数,因此每次发数据之前都回复初始状态,不让误差累积,等一拍可实现 tx <= 1'b1; tx_buf <= sdata; end START: if(bsp_clk) begin tx <= 1'b0; //tx_buf <= sdata; tx_cnt <= 4'd0 ; end TX_DATA: if(bsp_clk) begin tx_cnt <= tx_cnt + 1'b1; tx <= tx_buf[0]; tx_buf <= tx_buf >> 1 ; end TX_CHECK : if(bsp_clk)begin //奇偶校验 tx <= 1'b1; tx_cnt <= 4'b0; end STOP: if(bsp_clk) begin //停止位 tx <= 1'b1; end default: tx <= 1'b1; endcase end end endmodule ``` > # 使用示例 ```verilog module top ( input clk, //输入系统12MHz时钟 output tx ); wire tflag; wire rst_n; //产生复位信号 Rst_sys #( .N(9000000) // 计数器最大值 ,取值要大于2 ) rst_u1( .clk(clk), .rst_n(rst_n) ); divide #( .N(12000000) ) u2 ( .clk(clk), .rst_n(rst_n), .clkout(tflag) ); reg [7:0] tx_data; wire tx_inte; always@(posedge clk) begin if(rst_n == 0) begin tx_data <=0; start <= 1'b0; end else if(!tx_busy && (start==1'b0))begin tx_data <= tx_data + 1; start <= 1'b1; end else begin start <= 1'b0; end end wire tx_busy; reg start; uart_tx #( .CLK_FREQ(12000000), //时钟频率 .BSP(9600) // 波特率 ) u1 ( .clk(clk), .rst_n(rst_n), .start(start) , // 发送触发标志,上升沿有效 .sdata(tx_data), //要发送的数据 .tx(tx), //串口tx .interrupt(tx_inte), .busy(tx_busy) //忙标志 ); endmodule ``` > # 仿真波形 ![](https://img.kancloud.cn/10/b4/10b40988ebda875f1d9831779e86ae6d_1915x593.png)