FPGA-串口通信的原理和发送模块

简介: FPGA-串口通信的原理和发送模块

了解串口通信首先要了解串口通信的原理:


串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。典型地,串口用于ASCII码字符的传输。通信使用3根线完成:(1)地线,(2)发送,(3)接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但是不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配:  

a,波特率:这是一个衡量通信速度的参数。它表示每秒钟传送的bit的个数。例如300波特表示每秒钟发送300个bit。当我们提到时钟周期时,我们就是指波特率例如如果协议需要4800波特率,那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。    

 b,数据位:这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。  

 c,停止位:用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。   

 d,奇偶校验位:在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位为1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。

发送模块的设计:

串口发送,通过按键控制指令类型。可在PC端显示相应指令。

指令一:I like FPGA.

指令二:I like Verilog.

指令三:I like 107.

后来加了指令切换功能通过按键切换(这里没有画出来)

image.png

模块设计:


div.v

moduleuart_div(clk,rst_n,bps_sel,bps_clk    );
inputclk      ;//输入系统时钟inputrst_n    ;//复位信号input     [1:0] bps_sel  ;//波特率选择outputbps_clk  ;//输出波特率时钟信号regbps_clk  ;
parameterBPS_4800=13'd5208    ,BPS_9600=13'd2604    ,BPS_19200=13'd1302    ,BPS_115200=13'd217     ;reg      [12:0] div_cnt  ;
reg      [12:0] time_div ;
//波特率选择模块always@(*)beginif(rst_n==1'b0)begintime_div=BPS_9600;
endelsebegincase(bps_sel)
2'b00: time_div = BPS_4800;2'b01: time_div = BPS_9600;2'b10: time_div = BPS_19200;2'b11: time_div = BPS_115200;default:time_div=BPS_9600;
endcaseendend//波特率时钟计数模块always@(posedgeclkornegedgerst_n)beginif(rst_n==1'b0)begindiv_cnt<=1'b0;endelseif(div_cnt==time_div-1'b1)begindiv_cnt<=0;
endelsebegindiv_cnt<=div_cnt+1'b1;endend//波特率时钟输出模块always@(posedgeclkornegedgerst_n)beginif(rst_n==1'b0)beginbps_clk<=1'b0;endelseif(div_cnt<(time_div-1'b1))beginbps_clk<=1'b1;endelsebeginbps_clk<=1'b0;endendendmodule

串口通信发送模块:


moduleuart_txd(clk,rst_n,Date_byte,send_en,txd,txd_finish,uart_state    );
inputclk        ;//输入时钟inputrst_n      ;//复位信号input    [7:0] Date_byte  ;//发送数据inputsend_en    ;//发送使能outputtxd        ;//串口发送outputtxd_finish ;//发送完成位outputuart_state ;//串口状态位regtxd        ;
regtxd_finish ;
reguart_state ;
reg      [7:0] date_buf   ;
//数据寄存模块always@(posedgeclkornegedgerst_n)beginif(rst_n==1'b0)begindate_buf<=8'd0;endelseif(send_en==1'b1)begindate_buf<=Date_byte;
endelsebegindate_buf<=date_buf;
endend//串口状态模块always@(posedgeclkornegedgerst_n)beginif(rst_n==1'b0)beginuart_state<=1'b0;endelseif(send_en==1'b1)beginuart_state<=1'b1;endelseif(txd_finish==1'b1)beginuart_state<=1'b0;endelsebeginuart_state<=uart_state;
endendreg      [3:0] dat_cnt    ;
//数据计数模块always@(posedgeclkornegedgerst_n)beginif(rst_n==1'b0)begindat_cnt<=4'd0;endelseif(txd_finish==1'b1)begindat_cnt<=4'd0;endelseif(send_en==1'b1)begindat_cnt<=dat_cnt+1'b1;endelsebegindat_cnt<=4'd0;endend//串口发送完成标志模块always@(posedgeclkornegedgerst_n)beginif(rst_n==1'b0)begintxd_finish<=1'b0;endelseif(dat_cnt==4'd10)begintxd_finish<=1'b1;endelsebegintxd_finish<=1'b0;endend//数据发送模块always@(posedgeclkornegedgerst_n)beginif(rst_n==1'b0)begintxd<=1'b1;endelseif(send_en==1'b0)begintxd<=1'b1;endelsebegincase(dat_cnt)
4'd0 :txd<=1'b1;
4'd1 :txd<=1'b0;//发送位4'd2 :txd<=date_buf[0];4'd3 :txd<=date_buf[1];4'd4 :txd<=date_buf[2];4'd5 :txd<=date_buf[3];4'd6 :txd<=date_buf[4];4'd7 :txd<=date_buf[5];4'd8 :txd<=date_buf[6];4'd9 :txd<=date_buf[7];4'd10:txd<=1'b1;//停止位default:txd<=1'b1;endcaseendendendmodule

数据缓冲模块:


module txd_data_buf(clk,rst_n,txd_finish,sel_dat,dat_buf_en,data_out
    );
  input           clk       ;//输入时钟
  input           rst_n     ;//复位信号
  input           txd_finish;//一位数据发送完成标志
  input     [1:0] sel_dat   ;
  output    [7:0] data_out  ;
  reg       [7:0] data_out  ;
  output          dat_buf_en;//数据发送使能位
  reg             dat_buf_en;
  reg       [4:0] dat_cnt   ;
  parameter       DAT_FULL_1 =    4'd12    ,
                    DAT_FULL_2 =    4'd15    ,
          DAT_FULL_3 =    4'd11    ;
  //数据发送使能模块
  always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      dat_buf_en<=1'b0;
    end
    else if(sel_dat==2'b01)begin
      if(dat_cnt==dat_ful)begin
        dat_buf_en<=1'b0;
      end
      else begin
        dat_buf_en<=1'b1;
      end
    end
    else if(sel_dat==2'b10)begin
      if(dat_cnt==dat_ful)begin
        dat_buf_en<=1'b0;
      end
      else begin
        dat_buf_en<=1'b1;
      end
    end
    else if(sel_dat==2'b11)begin
      if(dat_cnt==dat_ful)begin
        dat_buf_en<=1'b0;
      end
      else begin
        dat_buf_en<=1'b1;
      end
    end
    else begin
      dat_buf_en<=dat_buf_en;
    end
  end
  //数据计数模块
  always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      dat_cnt<=1'b0;
    end
    else if(dat_cnt==dat_ful)begin
      dat_cnt<=dat_cnt;
    end
    else if(dat_buf_en==1'b0)begin
      dat_cnt<=1'b0;
    end
    else if(txd_finish==1'b1)begin
      dat_cnt<=dat_cnt+1'b1;
    end
    else begin
      dat_cnt<=dat_cnt;
    end
  end
  //数据选择模块
  always@(*)begin
    if(rst_n==1'b0)begin
      data_out=0;
    end
    else if(dat_buf_en==1'b0)begin
      data_out=0;
    end
    else if(sel_dat==2'b01)begin
      case(dat_cnt)
        4'd0 :data_out="I";
        4'd1 :data_out=" ";
        4'd2 :data_out="L";
        4'd3 :data_out="i";
        4'd4 :data_out="k";
        4'd5 :data_out="e";
        4'd6 :data_out=" ";
        4'd7 :data_out="F";
        4'd8 :data_out="P";
        4'd9 :data_out="G";
        4'd10:data_out="A";
        4'd11:data_out=".";
        default:data_out=0;
      endcase
    end
    else if(sel_dat==2'b10)begin
      case(dat_cnt)
        4'd0 :data_out="I";
        4'd1 :data_out=" ";
        4'd2 :data_out="L";
        4'd3 :data_out="i";
        4'd4 :data_out="k";
        4'd5 :data_out="e";
        4'd6 :data_out=" ";
        4'd7 :data_out="V";
        4'd8 :data_out="e";
        4'd9 :data_out="r";
        4'd10:data_out="i";
        4'd11:data_out="l";
        4'd12:data_out="o";
        4'd13:data_out="g";
        4'd14:data_out=".";
        default:data_out=0;
      endcase
    end
    else if(sel_dat==2'b11)begin
      case(dat_cnt)
        4'd0 :data_out="I";
        4'd1 :data_out=" ";
        4'd2 :data_out="L";
        4'd3 :data_out="i";
        4'd4 :data_out="k";
        4'd5 :data_out="e";
        4'd6 :data_out=" ";
        4'd7 :data_out="1";
        4'd8 :data_out="0";
        4'd9 :data_out="7";
        4'd10:data_out=".";
        default:data_out=0;
      endcase
    end
  end
  reg [3:0] dat_ful;
  always@(*)begin
    if(rst_n==1'b0)begin
      dat_ful=4'd15;
    end
    else if(sel_dat==2'b01)begin
      dat_ful=DAT_FULL_1;
    end
    else if(sel_dat==2'b10)begin
      dat_ful=DAT_FULL_2;
    end
    else if(sel_dat==2'b11)begin
      dat_ful=DAT_FULL_3;
    end
    else begin
      dat_ful=dat_ful;
    end
  end
endmodule

控制电路,控制指令发送:


module control(clk,rst_n,key_entr,key_down,sel_dat,bps_sel
    );
  input clk;
  input rst_n;
  input key_entr,key_down;
  output reg [1:0]sel_dat;
  output reg [1:0]bps_sel;
  //按键抖动判断逻辑
  wire key;  //所有的按键相与的结果,用于按键触发判断
  assign key =key_entr;
  reg[3:0]keyr ; //按键值key的缓冲寄存器
  always@(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
      keyr <=4'b1111;
    else
      keyr <={keyr[2:0],key};
  end
  wire key_neg =~keyr[2] &keyr [3];//有按键被按下
  wire key_pos =keyr[2] &~keyr [3];// 有按键被释放
  //定时器计数逻辑,用于对按键的消抖的判断
  reg [19:0] cnt;
  always@(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
      cnt <=20'd0;
    else if(key_pos||key_neg)
      cnt <=20'd0;
    else if(cnt <20'd999_999)
      cnt <= cnt +1'b1;
    else
      cnt <=20'd0;
  end
  reg [1:0] key_value_0;
  reg [1:0] key_value_1;
  //定时采取按键值
  always@(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
    begin
      key_value_0 <=2'b11;
      key_value_1 <=2'b11;
    end
    else begin
      if(cnt ==20'd999_999)
        key_value_0 <={key_entr,key_down};
      else 
        key_value_1 <=key_value_0;
    end 
  end
  wire [1:0] key_press=key_value_1 & ~key_value_0;
  //切换指令控制
  always@(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
      sel_dat<=1'b0;
    else if(key_press[1]==1'b1)begin
      if(sel_dat==2'b11)begin
        sel_dat<=2'b01;
      end
      else begin
        sel_dat<=sel_dat+1'b1;
      end
    end
    else begin
      sel_dat<=sel_dat;
    end
  end
  //切换波特率控制
  always@(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
      bps_sel<=2'b01;
    else if(key_press[0]==1'b1)begin
      if(bps_sel==2'b11)begin
        bps_sel<=2'b00;
      end
      else begin
        bps_sel<=bps_sel+1'b1;
      end
    end
    else begin
      bps_sel<=bps_sel;
    end
  end
endmodule

顶层文件


module top(ext_clk_25m,ext_rst_n,uart_tx,key_down,key_entr,led
    );
  input       ext_clk_25m ;//系统时钟
  input       ext_rst_n   ;//复位信号
  input       key_down    ;//波特率选择
  input       key_entr    ;//指令选择
  output[1:0] led         ;//指示bps
  output      uart_tx     ;//串口发送
  assign led[1:0]=bps_sel[1:0];
  wire        bps_clk     ;
  wire        txd_finish  ;
  wire        uart_state  ;
  wire  [7:0] data_out    ;
  wire        dat_buf_en  ;
  wire  [1:0] bps_sel     ;
  wire  [1:0] sel_dat     ;
  //数据缓冲区
  txd_data_buf u_buf(
  .clk(bps_clk),
  .rst_n(ext_rst_n),
  .txd_finish(txd_finish),
  .sel_dat(sel_dat),
  .dat_buf_en(dat_buf_en),
  .data_out(data_out)
    );
  //波特率选择模块
  uart_div u_div(
   .clk(ext_clk_25m),
   .rst_n(ext_rst_n),
   .bps_sel(bps_sel),
   .bps_clk(bps_clk)
    );
  //串口发送模块
  uart_txd u_txd(
  .clk(bps_clk),
  .rst_n(ext_rst_n),
  .Date_byte(data_out),
  .send_en(dat_buf_en),
  .txd(uart_tx),
  .txd_finish(txd_finish),
  .uart_state(uart_state)
    );
  //按键控制模块
  control u_con(
  .clk(ext_clk_25m),
  .rst_n(ext_rst_n),
  .key_entr(key_entr),
  .key_down(key_down),
  .sel_dat(sel_dat),
  .bps_sel(bps_sel)
    );
endmodule

串口助手验证:


image.png

目录
相关文章
|
数据采集 算法 数据安全/隐私保护
【硬件测试】基于FPGA的MSK调制解调系统系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
本文基于FPGA实现MSK调制解调系统,采用Verilog开发,包含同步模块、高斯信道模拟、误码率统计等功能。相比仿真版本,新增ILA数据采集与VIO在线SNR设置模块。通过硬件测试验证,展示不同SNR(如10dB和16dB)下的性能表现。研究聚焦软件无线电领域,优化算法复杂度以适应硬件限制,利用MSK恒定包络、相位连续等特性提升频谱效率。核心代码实现信号生成、调制解调、滤波及误码统计,提供完整的硬件设计与分析方案。
529 19
|
数据采集 移动开发 算法
【硬件测试】基于FPGA的QPSK调制+软解调系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
本文基于FPGA实现QPSK调制与软解调系统,包含Testbench、高斯信道、误码率统计模块,并支持不同SNR设置。硬件版本新增ILA在线数据采集和VIO在线SNR设置功能,提供无水印完整代码及测试结果。通过VIO分别设置SNR为6dB和12dB,验证系统性能。配套操作视频便于用户快速上手。 理论部分详细解析QPSK调制原理及其软解调实现过程,涵盖信号采样、相位估计、判决与解调等关键步骤。软解调通过概率估计(如最大似然法)提高抗噪能力,核心公式为*d = d_hat / P(d_hat|r[n])*,需考虑噪声对信号点分布的影响。 附Verilog核心程序代码及注释,助力理解与开发。
440 5
|
数据采集 算法 数据安全/隐私保护
【硬件测试】基于FPGA的4ASK调制解调通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
本文介绍了基于FPGA的4ASK调制解调系统的硬件测试版本,该系统包括testbench、高斯信道模块和误码率统计模块,并新增了ILA在线数据采集和VIO在线SNR设置功能。通过VIO设置不同SNR(如15dB和25dB),实现了对系统性能的实时监测与调整。4ASK是一种通过改变载波幅度表示数据的数字调制方式,适用于多种通信场景。FPGA平台的高效性和灵活性使其成为构建高性能通信系统的理想选择。
386 17
|
数据采集 算法 数据安全/隐私保护
【硬件测试】基于FPGA的16QAM调制+软解调系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
本文基于之前开发的16QAM调制与软解调系统,增加了硬件测试功能。该系统包含FPGA实现的16QAM调制、软解调、高斯信道、误码率统计模块,并新增了ILA在线数据采集和VIO在线SNR设置模块。通过硬件测试,验证了不同SNR条件下的系统性能。16QAM软解调通过比较接收信号采样值与16个调制点的距离,选择最近的调制点来恢复原始数据。核心Verilog代码实现了整个系统的功能,包括SNR设置、信号处理及误码率统计。硬件测试结果表明系统在不同SNR下表现良好,详细操作步骤可参考配套视频。
367 13
|
数据采集 算法 数据安全/隐私保护
【硬件测试】基于FPGA的4FSK调制解调通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
本文基于之前的文章《基于FPGA的4FSK调制解调系统》,增加了ILA在线数据采集模块和VIO在线SNR设置模块,实现了硬件测试版本。通过VIO设置不同SNR(如10dB和20dB),并展示了ILA采集的数据结果。四频移键控(4FSK)是一种数字调制方法,利用四个不同频率传输二进制数据,具有较高的频带利用率和抗干扰性能。输入的二进制数据分为两组,每组两个比特,对应四个频率f1、f2、f3、f4,分别代表二进制组合00、01、10、11。调制过程中选择相应频率输出,并进行幅度调制以增强抗干扰能力。接收端通过带通滤波器提取信号并还原为原始二进制数据。
373 7
|
算法 数据安全/隐私保护 异构计算
基于FPGA的变步长LMS自适应滤波器verilog实现,包括testbench
### 自适应滤波器仿真与实现简介 本项目基于Vivado2022a实现了变步长LMS自适应滤波器的FPGA设计。通过动态调整步长因子,该滤波器在收敛速度和稳态误差之间取得良好平衡,适用于信道均衡、噪声消除等信号处理应用。Verilog代码展示了关键模块如延迟单元和LMS更新逻辑。仿真结果验证了算法的有效性,具体操作可参考配套视频。
621 74
|
机器学习/深度学习 算法 数据安全/隐私保护
基于FPGA的SNN脉冲神经网络之LIF神经元verilog实现,包含testbench
本项目展示了 LIF(Leaky Integrate-and-Fire)神经元算法的实现与应用,含无水印运行效果预览。基于 Vivado2019.2 开发,完整代码配有中文注释及操作视频。LIF 模型模拟生物神经元特性,通过积分输入信号并判断膜电位是否达阈值产生脉冲,相较于 Hodgkin-Huxley 模型更简化,适合大规模神经网络模拟。核心程序片段示例,助您快速上手。
|
算法 数据安全/隐私保护 异构计算
基于FPGA的16QAM调制+软解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本项目基于FPGA实现了16QAM基带通信系统,包括调制、信道仿真、解调及误码率统计模块。通过Vivado2019.2仿真,设置不同SNR(如8dB、12dB),验证了软解调相较于传统16QAM系统的优越性,误码率显著降低。系统采用Verilog语言编写,详细介绍了16QAM软解调的原理及实现步骤,适用于高性能数据传输场景。
740 69
|
算法 数据安全/隐私保护 异构计算
基于LSB最低有效位的音频水印嵌入提取算法FPGA实现,包含testbench和MATLAB对比
本项目展示了一种基于FPGA的音频水印算法,采用LSB(最低有效位)技术实现版权保护与数据追踪功能。使用Vivado2019.2和Matlab2022a开发,完整代码含中文注释及操作视频。算法通过修改音频采样点的最低有效位嵌入水印,人耳难以察觉变化。然而,面对滤波或压缩等攻击时,水印提取可能受影响。该项目运行效果无水印干扰,适合实时应用场景,核心逻辑简单高效,时间复杂度低。
|
算法 数据安全/隐私保护 异构计算
基于FPGA的2ASK+帧同步系统verilog开发,包含testbench,高斯信道,误码统计,可设置SNR
本内容展示了基于Vivado2019.2的算法仿真效果,包括设置不同信噪比(SNR=8db和20db)下的结果及整体波形。同时,详细介绍了2ASK调制解调技术的原理与实现,即通过改变载波振幅传输二进制信号,并提供数学公式支持。此外,还涉及帧同步理论,用于确定数据帧起始位置。最后,给出了Verilog核心程序代码,实现了2ASK解调与帧同步功能,结合DDS模块生成载波信号,完成信号处理流程。
296 0

热门文章

最新文章