09B 串口通信发送的verilog设计与调试

1、题目

设计一个串口模块,以一个字节为单位发送数据

2、代码实现

`timescale 1ns / 1ns
module uart_tx(
clk,
reset_n,
data,
send_en,
baud_set,
uart_tx,
tx_done
    );
 input clk;
 input reset_n;
 input [7:0]data;
 input send_en;
 input [2:0]baud_set;
 output reg uart_tx;//串口发送信号输出
 output reg tx_done; //串口模块8bit发送完毕 再发送一个发送完成信号通知对方
// baud_set=0 就让波特率=9600   
//baud_set=1 就让波特率=19200  
//baud_set=2 就让波特率=38400  
//baud_set=3 就让波特率=115200    5  6  7 位 先留着   
 
 
 //波特率设置
 reg [17:0]bps_DR;//一个时钟周期在某波特率下计时器应该计的数 
 //9600对应5208 最少需要13位 这里用了18位是因为要和div_cnt作比较
 always@(*)begin
 if(!reset_n)
 bps_DR <= 1000000000/9600/20;
 else begin
    case(baud_set)//fpga保留整数 无需考虑除不尽的情况
    0:bps_DR = 1000000000/9600/20;
    1:bps_DR = 1000000000/19200/20;
    2:bps_DR = 1000000000/38400/20;
    3:bps_DR = 1000000000/57600/20;  
    4:bps_DR = 1000000000/115200/20;
    default: bps_DR = 1000000000/9600/20; //baud_set其他的情况还是为9600的波特率
    endcase
  end 
  end 
  
//每一个时钟周期计数  send_en 为控制信号 为1则开始计数
reg [17:0]div_cnt;//每一个时钟周期计数器 
 always@(posedge clk or negedge reset_n)begin
 if (!reset_n)
    div_cnt <= 0;
 else if(send_en)begin
    if(div_cnt == bps_DR-1)
      div_cnt <= 0;
    else
      div_cnt <= div_cnt + 1'b1;
    end
 else
 div_cnt <= 0;
 end
 
//数据输出模块设计    
reg [3:0]bps_cnt;// 时钟信号个数计数器
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
   bps_cnt <= 0;
 else if (div_cnt == bps_DR-1)begin//当到达一个时钟信号  bps_cnt+1
       if(bps_cnt == 11)
          bps_cnt <= 0;
      else
         bps_cnt <= bps_cnt + 1'b1;
     end  
  else
  bps_cnt <= 0;
  end
 
 //数据传输模块设计   
always@(posedge clk or negedge reset_n)
if(!reset_n)begin
   uart_tx <= 1'b1;//空闲时刻为高电平
   tx_done <= 0;
   end
   else begin
         case(bps_cnt)
         0:begin uart_tx <= 0;tx_done <= 1'b0;end//发送开始信号并让tx_done为0
         1:uart_tx <= data[0];
         2:uart_tx <= data[1];
         3:uart_tx <= data[2];
         4:uart_tx <= data[3];
         5:uart_tx <= data[4];
         6:uart_tx <= data[5];
         7:uart_tx <= data[6];
         8:uart_tx <= data[7];
         9:uart_tx <= 1;//stop信号
         10: begin uart_tx <= 1; tx_done <= 1'b1;end//发送结束信号并让tx_done为1
         default:  uart_tx <= 1;
    endcase
    end
endmodule

testbench如下

`timescale 1ns / 1ns

module uart_tx_tb( );
reg clk;
reg reset_n;
reg [7:0]data;
reg send_en;
reg [2:0]baud_set;
wire uart_tx;
wire tx_done;
//例化
uart_tx uart_tx_inst0(
.clk(clk),
.reset_n(reset_n),
.data(data),
.send_en(send_en),
.baud_set(baud_set),
.uart_tx(uart_tx),
.tx_done(tx_done)
   );
//产生周期为20ns的时钟
initial clk=1;
always #10 clk =~clk;

//对输入进行赋值
initial begin
reset_n = 0;
data = 0;//刚开始要发的数据也让它等于0
send_en = 0;
baud_set = 4; //初始化波特率为115200 速度快11个时钟脉冲下来时间短
#201;

reset_n = 1;
#100
send_en = 1;
data = 8'h57; //0101 0111

#20;
@(posedge tx_done);//死循环 一直等待tx_done上升沿的到来 来了就跳出循环 没有来就持续等待
send_en = 0;
#2000;
$stop;
end


endmodule

仿真结果如下:

 send_en还是低电平的时候,串口发送的信号已经变成了低电平,不合常理 

发送的第二个信号在send_en为1之后要先以1为发送对象,等待div_cnt到一个新的时钟脉冲,bps_cnt变成11,在bps_cnt为11时,发送对象还是1,所以继续以1为发送对象,等到div_cnt到一个新的时钟脉冲,bps_cnt从1变成0,才开始发送低电平起始位。所以一共用了两个周期才走到低电平的位置。

所以提出解决方法:让bps_cnt也受send_en的控制

reg [3:0]bps_cnt;// 时钟信号个数计数器
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
   bps_cnt <= 0;
 else if (send_en)
 begin
     if(div_cnt == bps_DR-1)
     begin                  //当到达一个时钟信号  bps_cnt+1
       if(bps_cnt == 11)
          bps_cnt <= 0;
      else
         bps_cnt <= bps_cnt + 1'b1;
      end 
 end
 else
 bps_cnt <= 0;
 end

在stop信号传输中,div_cnt计数到了新的时钟脉冲,div_cnt = 0,bps_cnt=10,于是开始传输高电平同时让tx_done变成高电平。刚一变成高电平,测试平台感应到就立马把send_en赋值为0,send_en=0了,div_cnt还是为0(因为这些操作都是一瞬间的事情,姑且认为div_cnt还没来得及开始计数)且bps_cnt由10变为0,所以在下面的仿真图中可以看见,uart_tx和tx_done立马就变为低电平了,随后就以低电平为传输对象一直传,直到20ns后send_en再次被赋值为1,div_cnt立马重新开始计时,到一个新的时钟脉冲时(div_cnt从0开始到这个新的时钟脉冲这段时间依旧是以0为传输对象,所以这段时间就相当于开始信号),bps_cnt由零变为1,随后就开始传输data[0]

 还是有问题:比如20ns这段空闲状态tx是以低电平为传输对象的,不符合常理

原因是bps_cnt被置零,直接就对应发送start信号,所以把代码做如下修改:(记得把11个脉冲那里改为12个)

always@(posedge clk or negedge reset_n)
if(!reset_n)begin
   uart_tx <= 1'b1;//空闲时刻为高电平
   tx_done <= 0;
   end
   else begin
         case(bps_cnt)
         1:begin uart_tx <= 0;tx_done <= 1'b0;end//发送开始信号并让tx_done为0
         2:uart_tx <= data[0];
         3:uart_tx <= data[1];
         4:uart_tx <= data[2];
         5:uart_tx <= data[3];
         6:uart_tx <= data[4];
         7:uart_tx <= data[5];
         8:uart_tx <= data[6];
         9:uart_tx <= data[7];
         10:uart_tx <= 1;//stop信号
         11: begin uart_tx <= 1; tx_done <= 1'b1;end//发送结束信号并让tx_done为1
         default:  uart_tx <= 1;//其他就为1 高电平
    endcase
    end

但是又出现了之前的问题:要多一个周期后才会开始传输start信号 

 

观察代码可知,计满才加一次(才让bps_cnt变为1)才开始发送start信号,浪费了一位的时间

div_cnt计数到bps_dr需要很长的时间,但是计数到1的时间是可以忽略的(这里的意思是说:原来代码里的div_cnt需要计数一个完整的周期才能将bps_cnt加一,而每一段计数过程都会经过1这个数,所以直接让它跑到1的时候就让bps——cnt加1,节约了很多时间。可能你会想,那这样的话每一个位的时间不就改变了吗?其实并没有,因为div_cnt是每次到bps_dr-1才归零的这个条件没有变。只是之前把div_cnt到了bps_dr-1时当作bps_cnt加1的条件,现在把div_cnt到1时当作bps_cnt加1的条件,同样是一段div_cnt的计数周期仅对应一个数值1.代码改变如下:

仿真结果如下

 接下来是波特率验证,从上图仿真结果来看,发一位需要8.68us 符合115200的波特率

另外:将程序修改为bps_clk来控制的思路

 仿真结果里有一个bps_clk 11个脉冲把tx分为10位

结束!!

后记:没有天生的适合和不适合 事在人为 只要你想 

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值