FPGA串口逻辑编程学习的总结

本文介绍了串口通信的基础知识,包括帧格式的组成(起始位、数据位、校验位等),波特率的计算,以及如何根据波特率进行系统时钟分频。在数据接收方面,详细阐述了状态机的运用,从起始位检测到数据采集和校验,最后解析并处理接收到的数据帧。
摘要由CSDN通过智能技术生成

对于串口收发辑编程学习的总结

基础准备

串口的帧格式

请添加图片描述
请添加图片描述

  • 串口的帧格式 {.mindmap}
    • 格式组成:起始位,帧头,数据位,循环码,校验位,帧尾
    • 起始位低电平有效
    • 数据采集的clk为串口clk的8倍,16倍,推荐8倍即可。
      • 数据抓取在中间位置
      • 定义采集计数器cnt=0
      • 起始位来时计数,每来一次clk计数器就+1
      • 经过4个clk,cnt=4时取到第一个数
      • 后面的0<i=i+1<9个数就应该在cnt=4+i*8的时候取
      • 取完8位数后计数器归零
    • 取完一帧数后将起始拉高,等待下一帧数据

串口的波特率

波特率是指一个位的周期,即一个位所逗留的时间,波特率115200bps表示一秒可以传输115200位。那么一位所需时间为t秒:
$$t=\frac1{115200}$$

如果1帧10位数据
在115200bps情况下
那么1秒内可传输的帧数1/T
1bit所需时间t
一帧所需时间t*10
T=10 * 1/115200
115200/10

系统时钟和波特率的关系
$$分频系数p=\frac{系统时钟clk}{波特率bps}$$
最大分频计数器 n = 分频系数 p 2 − 1 ∣ 最大分频计数器n=\frac{分频系数p}2-1| 最大分频计数器n=2分频系数p1∣

clk_p = (cnt==n) ? ~clk_p:0 ;

数据抓取

对系统时钟要根据串口波特率进行分频处理,发送数据时的分频频率和波特率一致,采集数据时用8倍采样率采集数据的中间位置可保证数据的准确性。

请添加图片描述

串口数据收模块的解析

各端口的接收模块由uart_rx例化而来,由于各波特率和数据的字节数不同所以各uart_rx略有不同

状态机图

[rxd]
起始位
帧头
数据
校验位
帧尾
[re_data]

有关逻辑(state)

初始化(Init): 初始化
Init:
    begin    
    state <= Idle;
    tally <= 8'd15;  
    trig_r <= 1'b0;		
    trig_v <= 1'b0;	
    rx0<=1'b1; rx1<=1'b1; rx2<=1'b1;rx3<=1'b1;
    cnt  <= 32	         
    data_r <=8'h00;
    rxd_r  <=9'b0000_0000_0;
    end				
状态1(Idle) :起始位判断
Idle:begin
    rx0<=rxd;rx1<=rx0;rx2<=rx1;rx3<=rx2;//输入打4拍,取反做与处理判断为真开始采集
    tirg_r<=0;  //字节数收完标记
    trig_v<=0;  //数据正确校验码ok标记
    cnt<=0;     //取一个字节数计数
    tall<=0;    //取所有字节数计数
    if (~rx3 & ~rx2 & ~rx1 & ~rx0)  
        state <= Syn_First;
    else 
        state <= Idle;
end
状态2(Syn_Firs) :取8bit数及帧头判断
Syn_Firs:begin 
    cnt <= cnt+1;
    case(cnt) 
    4+0*8:
        rxd_r(0) <= rxd;  //串行数据rxd保存在并行变量rxd_r[9:0]
        state <= Syn_Firs;
             ...
    4+7*8:
        rxd_r(7) <= rxd; //第8位数值
         state <= Syn_Firs;
    4+57:
        data_r <= rxd_r[7:0] //保存该字节数在变量data_r[7:0]
        state <= Syn_Firs;
    4+8*8:begin   //起始位拉高判断该字节数是否是帧头是就进取值状态不是返回重来
        rxd_r(8) <= rxd;
        rx0<=1'b1; rx1<=1'b1; rx2<=1'b1; rx3<=1'b1;
        if(data_r==帧头) begin  //帧头判断逻辑
            state <= Start_rec; //开始进入采集逻辑
        end else begin
            state <= Idle;  //返回继续判断起始位
        end
    end
end

状态3(Start_rec) :采集及校验码判断
Start_rec: begin
    case(cnt)
       ... 
    4+57:  data_r <= rxd_r[7:0] //取一个字节数
    4+8*8:begin
        rxd_r(8) <= rxd;
        rx0<=1'b1; rx1<=1'b1; rx2<=1'b1; rx3<=1'b1;
        tally<=tally+1; //通过tally将一帧所有字节数据取完
        if(tall<8‘d17)  //数据为16个字节
        case(tally)
            0: re_data0 <= data_r;
                trig_r <= 0;                    
            1: re_data1 <= data_r;
                trig_r <= 0;   
                    ...
            16: re_data[i]<= data_r;
                 trig_r <= 1;
        default: trig_r <=0;
        sata <= Start_rec;
    end
    4+65:begin
    if(trig_r) begin //全部字节是否完整
        if(re_data[i]==前字节和) //比较校验位
            data_buf <={帧头,re_data0,...re_datai}; 有效数据
            trig_v <= 1;        //数据有效标记
            state   <= Receive_over;
    


        
状态4(Receive_over) :解析结束
 Receive_over:begin
    rx0<=1'b1; rx1<=1'b1; rx2<=1'b1; rx3<=1'b1;
    trig_r <= 0;
    trig_v <= 0;
    state <= Init;
end					
完成解析赋值
always @(negedge trig_v)
begin
   re_data <= data_buf;
end
采集完成标记信号的处理

为了能准确的采集到采集完成标记信号,要将trig_v做拉长处理,拉长时间宽度80us。示意图如下:80us的判断方法依然是用采用计数器,trig_v信号来开始计数,clock计数=299拉低trig_vv信号。
请添加图片描述

串口数据的发送

触发信号的判断。用下面代码可以用trig_spor_Re抓取strig_spot的上升沿.依此来判断触发信号是否有效。当触发信号是上升沿有效时开始发送一帧数据

   always @ (posedge clk460k)
	begin
	    trig_spot_d <= trig_spot;
	end
   assign trig_spot_Re = (!trig_spot_d) & trig_spot;

顶层逻辑

由于对方给与的整个逻辑需求不明确,导致实际写逻辑的时候疑问太多,直接导致了工作量的不必要增加。今后对于类似项目的处理就要有前车之鉴。

数据传输与赋值

  1. 直接将需要的数据assign就行
  2. 数字运算时将除法换算为乘法
  3. {}里可以直接调用函数
    assign data = {rx1,rx2,dov(rx3[5:0]),rx0}
  4. 访达

调试中的注意事项

  1. 帧头位置是一帧数据的最高位
  2. 有校验位的必须算正确,否则认为命令无效

调试

模块的优化

          preamble[0] <= recdata[303:296];
          preamble[1] <= recdata[295:288];
         。。。
          preamble[12] <= recdata[207:200];
          preamble[13] <= recdata[199:192];
          。。。

如上代码可用for循环代替矢量做为索引的时候:“+:” 相当于"+=" (DW-1)-ii*8选定了起始,然后从起始开始每次减8,8个取为一组 in[i +: 4]; 特别的,起始和终点都随着i变化,这是不允许的,verilog中只允许起始或终点发生变化,其他保持常量,这样做是为了保证宽度不发生改变

              for ( ii=0; ii<10; ii=ii+1) begin
                preamble[ii] <= recdata[(DW-1)-ii*8-:8];
              end

直接用参数代替,

module uart_rx #(parameter
  integer DW     = 224  ,
          FH1    = 8'h7e,
          FH2    = 8'ha0,
          FF     = 8'he7,
          N_DATA = 25     //去帧头节数从零开始
) 。。。
         (value + 32'd65): begin
            if (yxyyjb == 1'b1) begin
              if ((recdat_r[N_DATA] == FF) && (recdat_r[N_DATA-1] == sum_p)) begin
                for (kk=0; kk<DW/8-2; kk=kk+1) begin
                  yk_buf[N_DATA-kk] <= recdat_r[kk];
                end
                yk_buf[DW/8-1:DW/8-2] <= {FH1,FH2};
                yk_trig               <= 1'b1;
                state                 <= Receive_over;
              end else begin
                state   <= Idle;
                yk_trig <= 1'b0;
              end

对于向case语句里面的重复代码用

                8'd1 :
                  begin
                    Txd   <= 1'b0;        //Start_bit 0
                    state <= Start_Tx;
                  end

                8'd2,8'd3,8'd4,8'd5,8'd6,8'd7,8'd8,8'd9:begin
                  Txd   <= tx_data[Tx_cnt-2];
                  state <= Start_Tx;
                end

功能的修改

bug调试

pp值修改用assign代码如下情况时pp1和pp2的值不受条件1和条件2的控制,直接表现为一荣俱荣一损俱损。使用ila功能编译时提示这种组合逻辑环有问题,应该用寄存器变量定义中间变量。

assign pp1 = (条件1)?pp:pp1;
assign pp2 = (条件2)?pp:pp2:

其它

函数定义的中间变量用reg写法和modele类似。
function [31:0] outdata ;
input [31:0] a;
reg [31:0] temp1;
begin
...
end
16位数转换成32位浮点数
  • 转换依据编码器的原始数据角度转换不同编码器设置不同
  • 编码器位偏移量输出7fff时,角度是0°;输出ffff时,角度是90°,输出0000时,角度是-90°
    即目的就是将x转换成至少小数点后4位的angle。
方法一首先想到如下计算函数实现,但仿真结果误差较大。
  function [31:0] conv_dat32;
    input [15:0] data_i;
    reg [15:0] conv_dat1;
    begin
      conv_dat1 = (data_i / 364 - 90);
      if (data_i >= 16'h7fff) begin
        conv_dat32 = {16'h0,conv_dat1};
      end else begin
        conv_dat32 = {16'hffff, conv_dat1};
      end
    end
  endfunction
方法二 将16位定点数的转换成32位定点数,然后就行浮点数的A*B-C

B=180/65535的浮点数,C为90的浮点数

//16位定点转32位定点
  assign data32 = {16'h0, data16};
//32位定点数转成32位浮点数
floating_point_0 floating_point_0_dut (
      .aclk(clk8xuart),
      .s_axis_a_tvalid(1'b1),
      .s_axis_a_tready(),
      .s_axis_a_tdata(data32),
      .m_axis_result_tvalid(),
      .m_axis_result_tdata(float_data_o) 
  );
//A*B-C,B和C为转换成浮点数的常数
  floating_point_2 floating_point_2_dut (
      .aclk(clk8xuart),
      .s_axis_a_tvalid(1'b1),
      .s_axis_a_tready(),
      .s_axis_a_tdata(float_dataA),
      .s_axis_b_tvalid(1'b1),
      .s_axis_b_tready(),
      .s_axis_b_tdata(32'h3B340B41),  //0.00274658203125 32'h3B340B41
      .s_axis_c_tvalid(1'b1),
      .s_axis_c_tready(),
      .s_axis_c_tdata(32'h42B40000),
      .m_axis_result_tvalid(),
      .m_axis_result_tdata(float_data_o)
  );
//浮点数的加法
floating_point_1 floating_point_1_dut3 (
      .aclk(clk8xuart),
      .s_axis_a_tvalid(1'b1),
      .s_axis_a_tready(),
      .s_axis_a_tdata(float_a),
      .s_axis_b_tvalid(1'b1),
      .s_axis_b_tready(),
      .s_axis_b_tdata(float_b),
      .m_axis_result_tvalid(),
      .m_axis_result_tdata(float_data_o)
  );

仿真后的数据截图
请添加图片描述

仿真命令

写了do文件后 在quesat—>file—>change director …指定do文件所在路径
.do文件写法

if [file exists work] {vdel -all}

vlib work
vmap work work

vlog -f list.f
vopt +acc float_top_tb -o float_top_tb_opt -debugdb

vsim -vopt float_top_tb_opt 
log -r /*
add wave /*
run -all

.f文件写法

./src/float_top_tb.sv
./src/float_top.v
./src/fix2float.v
./src/floatingadder.v
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值