UART串口发送逻辑设计

**初学者学习FPGA的一些小心得和笔记,以免之后忘记。教程是跟着小梅哥AC620教学视频学的

视频地址:【0基础学FPGA 2023全新版本——小梅哥AC620 FPGA开发板教学视频】 https://www.bilibili.com/video/BV1Ga4y1f7hy/?p=28&share_source=copy_web&vd_source=079f387420bab0d1dc43fb9c7ea5a62d


目录

设计一个串口发送模块,发送用户输入的数据给电脑。

要求如下:

verilog代码展示:

testbench代码:

仿真波形情况:

要求解读

1.“波特率为9600”

2.“8位数据位,1位停止位”

3.“每1s发送一次当前8位拨码开关的值”

4.“每次发送完后将LED0状态反转”

代码详解

1.输入输出部分

2.波特率计数器部分

3.位计数器

4.延时计数器

5.位发送逻辑


 

设计一个串口发送模块,发送用户输入的数据给电脑。

要求如下:

verilog代码展示:

module uart_byte_tx(
input Clk,
input Reset_n,
input [7:0] Data,
output reg uart_tx,
output reg Led
);

reg [12:0] band_div_cnt;
reg en_band_cnt;
reg [7:0] r_Data;
reg [25:0] delay_cnt;
reg [3:0] bit_cnt;

parameter MCNT_DLY=50_000_000-1;
parameter MCNT_BIT=10-1;
parameter MCNT_BAND= 5208-1;

//波特率计数器 1/960*1000 000 000 /20 -1
    always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
    en_band_cnt <=0;
    else if (delay_cnt == MCNT_DLY)
        en_band_cnt <= 1;
    else if ((bit_cnt == MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))
        en_band_cnt <=0;//关闭

    always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
    band_div_cnt <= 0;
    else if( en_band_cnt ==1) begin
        if(band_div_cnt ==MCNT_BAND)
            band_div_cnt <=0;
            else
            band_div_cnt <= band_div_cnt +1'b1;
        end
        else
        band_div_cnt <=0;

//位计数器
    
    always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        bit_cnt<=0;
    else if(band_div_cnt ==MCNT_BAND) begin
        if(bit_cnt==MCNT_BIT)
            bit_cnt<=0;
        else
            bit_cnt <= bit_cnt +1'd1;
        end

//延时计数器
    always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        delay_cnt <=0;
    else if(delay_cnt == MCNT_DLY)
            delay_cnt <=0;
        else 
            delay_cnt <=delay_cnt +1'd1;
    
//位发送逻辑
    always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        r_Data <=0;
    else if(delay_cnt == MCNT_DLY)
        r_Data <= Data;

    always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        uart_tx <= 1;
    else if( en_band_cnt == 0)
        uart_tx <=1;
    else begin
        case (bit_cnt)
        0: uart_tx <=0;
        1: uart_tx <=r_Data[0];
        2: uart_tx <=r_Data[1];
        3: uart_tx <=r_Data[2];
        4: uart_tx <=r_Data[3];
        5: uart_tx <=r_Data[4];
        6: uart_tx <=r_Data[5];
        7: uart_tx <=r_Data[6];
        8: uart_tx <=r_Data[7];
        9: uart_tx <= 1;
        default : uart_tx <=uart_tx;
        endcase
    end
    
//Led翻转逻辑
    always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        Led<=0;
    else if((bit_cnt==MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))
    Led<=!Led;

endmodule

testbench代码:

`timescale 1ns / 1ps

module uart_byte_tx_tb();

   reg 			Clk;
   reg 			Reset_n;
   reg [7:0]	Data;
   wire			uart_tx;
   wire			Led;

	uart_byte_tx uart_byte_tx(
		.Clk		(Clk		),
		.Reset_n	(Reset_n	),
		.Data		(Data		),
		.uart_tx	(uart_tx	),
		.Led		(Led		)
	);
	
	defparam uart_byte_tx.MCNT_DLY = 500_000 - 1;//10ms

	initial Clk = 1;
	always #10 Clk = !Clk;

	initial begin
		Reset_n = 0;
		#201;
		Reset_n = 1;
		Data = 8'b0101_0101;	
		#30_000_000;
		Data = 8'b1010_1010;
		#30_000_000;
		$stop;
	end

endmodule 

仿真波形情况:

要求解读

1.“波特率为9600”

        波特率为单位时间内传输符号的个数(串口通信的速率),即每秒钟要传输的数量【可以用速度 =路程/时间】来简单理解

        要求波特率为9600,意思是传输一个符号,所耗费的时间为

1➗9600=1.04166✖10^{-4}(s)

        使用的AC620上带有50MHz的时钟信号(1s有50M个Clk方波信号),即每个Clk信号所占时

1➗50_000_000=2✖10^{-8}(s)

        两者相结合,则可以用Clk的数量来代替传输一个符号的时间

Clk数量:1.04166✖10^{-4}(s)➗2✖10^{-8}=5208.333...

        取整即为5208个Clk信号(波特率在2.5%的误差范围下,可以保证可靠传输)

2.“8位数据位,1位停止位”

START位为低电平,STOP位为高电平

        要发送八位数据,那么就需要十(8+1+1)位的bit_cnt,单个时长为1/9600(s)。

3.“每1s发送一次当前8位拨码开关的值”

        串口发送时间为1/960s,那么剩余空闲时间就是1-1/960s,可以用en_band_cnt来控制

4.“每次发送完后将LED0状态反转”

        串口发送完成之后,可以给LED进行一个if语句的判断,详细内容后面结合代码讲解

代码详解

1.输入输出部分

module uart_byte_tx(
input Clk,
input Reset_n,
input [7:0] Data,
output reg uart_tx,
output reg Led
);

        输入端口Clk(时钟),Reset_n,Data(8位数据位);输出端口uart_tx,Led(控制翻转)

        因为在板子上,复位信号按下时是低电平,所以后面判断是否按下复位信号时候,都是用的

if(!Reset_n)

2.波特率计数器部分

        引入了

    always @ (posedge Clk or negedge Reset_n)//Clk上升沿或者复位信号的下降沿
    if(!Reset_n)
    en_band_cnt <=0;//波特率分频器使能信号为0,关闭
    else if (delay_cnt == MCNT_DLY)//MCNT_DLY为50_000_000-1,因为计算机从0开始数,数到第        
                                   //50_000_000-1时候,就数了50_000_000个数
        en_band_cnt <= 1;//波特率分频器使能信号为1,开启
    else if ((bit_cnt == MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))
    //当位计数器等于最大位(10-1)时,且波特率分频计数器计数到最大位(5208-1)时候
        en_band_cnt <=0;//波特率分频器使能信号为0,关闭

        这一段具象理解就是“做了个减法” ,上面的红线就是en_band_cnt的信号情况

        先用

else if (delay_cnt == MCNT_DLY)
        en_band_cnt <= 1;

·        让1S内的 en_band_cnt均赋值为1

        然后再用

else if ((bit_cnt == MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))
        en_band_cnt <=0;

        这里的((bit_cnt == MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))就是为了确保STOP后的时间点,所以看着比较长,理解了也还好。

        因为波特率计数器每计数满一轮(MCNT_BAND 5208-1)后,就相当于时间上走了1/9600s

 always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
    band_div_cnt <= 0;//位计数器清0
    else if( en_band_cnt ==1) begin//当波特率计数器使能信号为1时候,启动波特率计数器计数
        if(band_div_cnt ==MCNT_BAND)//波特率计数器数到5208-1时候
            band_div_cnt <=0;//清0
            else
            band_div_cnt <= band_div_cnt +1'b1;//否则加一
        end
        else
        band_div_cnt <=0;

3.位计数器

        当波特率计数器记满5208-1时候,要让位计数器+1

        位计数器经历0->1->2->3->4->5->6->7->8->9,共10个数,依次对应如下部分

always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        bit_cnt<=0;
    else if(band_div_cnt ==MCNT_BAND) begin//当波特率计数器记满5208-1时候,进入位计数器中
        if(bit_cnt==MCNT_BIT)//当位计数器计数满10-1时候(bit_cnt)
            bit_cnt<=0;//位计数器清0
        else
            bit_cnt <= bit_cnt +1'd1;//否则位计数器+1
        end

4.延时计数器

        这个计数器是为了确保1s的时长,和波特率计数器上有点关联

always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        delay_cnt <=0;//复位时候,延时计数器值0
    else if(delay_cnt == MCNT_DLY)//如果Clk上升沿来到了50_000_000-1时
            delay_cnt <=0;//清0
        else 
            delay_cnt <=delay_cnt +1'd1;//否则+1

5.位发送逻辑

        要保证在串口发送过程中(也就是1/960s之中),不会因为Data数据的变化,而让原本打算发送的数据错乱,所以需要一个“寄存器”(r_Data)来暂时储存Data数据

always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        r_Data <=0;
    else if(delay_cnt == MCNT_DLY)
        r_Data <= Data;//数据的寄存

        在数据寄存好之后,因为在不发送数据的时候,uart_tx应该处于一个高电平状态,且uart_tx的发送时为低电平(0),结束位为高电平(1),想知道为什么是这样的话,可以看一下UART通信协议及其工作原理(图文并茂+超详细)-CSDN博客

        这里运用了case语句(记得endcase结尾)

always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        uart_tx <= 1;//复位状态下, uart_tx应该处于高电平
    else if( en_band_cnt == 0)
        uart_tx <=1;//非传输状态下,uart_tx应该处于高电平
    else begin
        case (bit_cnt)
        0: uart_tx <=0;//START
        1: uart_tx <=r_Data[0];
        2: uart_tx <=r_Data[1];
        3: uart_tx <=r_Data[2];
        4: uart_tx <=r_Data[3];
        5: uart_tx <=r_Data[4];
        6: uart_tx <=r_Data[5];
        7: uart_tx <=r_Data[6];
        8: uart_tx <=r_Data[7];
        9: uart_tx <= 1;//STOP
        default : uart_tx <=uart_tx;//需要使用default语句来确保 未填写时候的情况
        endcase
    end

6.LED的翻转逻辑

        主要还是用了 else if((bit_cnt==MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))来确保翻转时候的时间情况

always @ (posedge Clk or negedge Reset_n)
    if(!Reset_n)
        Led<=0;//复位状态下,Led处于低电平
    else if((bit_cnt==MCNT_BIT)&&(band_div_cnt ==MCNT_BAND))//在波特率计数器中有见到
    Led<=!Led;//用!来取反
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值