zoukankan      html  css  js  c++  java
  • 串口完整项目之串口收发字符串

      本篇博文设计思想及代码规范均借鉴明德扬至简设计法,加上些自己的理解和灵活应用,希望对自己和大家都有所帮助。核心要素依然是计数器和状态标志位逻辑相配合的设计方式。在最简单的串口收发一字节数据功能基础上,实现字符串收发。

      上一篇博文中详细设计了串口发送模块,串口接收模块设计思想基本相同,只不过将总线的下降沿作为数据接收的开始条件。需要注意有两点:其一,串口接收中读取每一位bit数据时,最好在每一位的中间点取值,这样数据较为准确。第二,串口接收的比特数据属于异步数据,因此需要打两拍做同步处理,避免亚稳态的出现。关于串口接收的设计细节这里不再赘述,不明之处请参考串口发送模块设计思路。串口接收代码如下:

      1 `timescale 1ns / 1ps
      2 
      3 module uart_rx(
      4     input clk,
      5     input rst_n,
      6     input [2:0] baud_set,
      7     input din_bit,
      8     
      9     output reg [7:0] data_byte,
     10     output reg dout_vld
     11     );
     12     
     13     reg din_bit_sa,din_bit_sb;
     14     reg din_bit_tmp;
     15     reg add_flag;
     16     reg [15:0] div_cnt;
     17     reg [3:0] bit_cnt;
     18     reg [15:0] CYC;
     19     
     20     wire data_neg;
     21     wire add_div_cnt,end_div_cnt;
     22     wire add_bit_cnt,end_bit_cnt;
     23     wire prob;
     24     
     25     //分频计数器
     26     always@(posedge clk or negedge rst_n)begin
     27         if(!rst_n)
     28             div_cnt <= 0;
     29         else if(add_div_cnt)begin
     30             if(end_div_cnt)
     31                 div_cnt <= 0;
     32             else 
     33                 div_cnt <= div_cnt + 1'b1;
     34         end
     35     end
     36     
     37     assign add_div_cnt = add_flag;
     38     assign end_div_cnt = add_div_cnt && div_cnt == CYC - 1;
     39     
     40     //bit计数器
     41     always@(posedge clk or negedge rst_n)begin
     42         if(!rst_n)
     43             bit_cnt <= 0;
     44         else if(add_bit_cnt)begin
     45             if(end_bit_cnt)
     46                 bit_cnt <= 0;
     47             else 
     48                 bit_cnt <= bit_cnt + 1'b1;
     49         end
     50     end
     51     
     52     assign add_bit_cnt = end_div_cnt;
     53     assign end_bit_cnt = add_bit_cnt && bit_cnt == 9 - 1;
     54     
     55     //波特率查找表
     56     always@(*)begin
     57         case(baud_set)
     58             3'b000: CYC  <= 20833;//9600
     59             3'b001: CYC  <= 10417;//19200
     60             3'b010: CYC  <= 5208;//38400
     61             3'b011: CYC  <= 3472;//57600
     62             3'b100: CYC  <= 1736;//115200
     63             default:CYC  <= 20833;//9600
     64         endcase
     65     end
     66     
     67     //同步处理
     68     always@(posedge clk or negedge rst_n)begin
     69         if(!rst_n)begin
     70             din_bit_sa <= 1;
     71             din_bit_sb <= 1;
     72         end
     73         else begin
     74             din_bit_sa <= din_bit;
     75             din_bit_sb <= din_bit_sa;
     76         end
     77     end
     78     
     79     //下降沿检测
     80     always@(posedge clk or negedge rst_n)begin
     81         if(!rst_n)
     82             din_bit_tmp <= 1;
     83         else 
     84             din_bit_tmp <= din_bit_sb;
     85     end
     86     
     87     assign data_neg = din_bit_tmp == 1 && din_bit_sb == 0;
     88     
     89     //检测到下降沿说明有数据起始位有效,计数标志位拉高
     90     always@(posedge clk or negedge rst_n)begin
     91         if(!rst_n)
     92             add_flag <= 0;
     93         else if(data_neg)
     94             add_flag <= 1;
     95         else if(end_bit_cnt)
     96             add_flag <= 0;
     97     end
     98     
     99     //bit位中点采样数据
    100     always@(posedge clk or negedge rst_n)begin
    101         if(!rst_n)
    102             data_byte <= 0;
    103         else if(prob)
    104             data_byte[bit_cnt - 1] <= din_bit_sb;
    105     end
    106     
    107     assign prob = bit_cnt !=0 && add_div_cnt && div_cnt == CYC / 2 - 1;
    108     
    109     
    110     //输出数据设置在接收完成是有效
    111     always@(posedge clk or negedge rst_n)begin
    112         if(!rst_n)
    113             dout_vld <= 0;
    114         else if(end_bit_cnt)
    115             dout_vld <= 1;
    116         else 
    117             dout_vld <= 0;
    118     end
    119     
    120 endmodule

       由于思路代码与串口发送非常详尽,这里省去仿真,单独在线调试的过程,将验证工作放在总体设计中。到目前为止,串口的一字节数据发送和接收功能已经实现。下面我们在此基础上做一个完整的小项目。功能定为:FPGA每隔3s向PC发送一个准备就绪(等待)指令“wait”,再等待区间内PC端可以发送一个由#号结尾且长度小于等于10个字符的字符串,当FPGA在等待区间内收到了全部字符串,即收到#号,则等待时间到达后转而发送收到的字符串实现环回功能。之后如果没有再收到字符串再次发送“wait”字符串,循环往复。

      现在串口发送接收8位数据的功能已经实现,而一个字符即为8位数据(详见ASCII码表),那么现在的工作重心已将从发送接收字符转到如何实现字符串的收发和切换上。很明显,需要一个控制模块完成上述逻辑,合理调配它的部下:串口接收模块和串口发送模块。我们来一起分析控制模块的实现细节:

      先来说发送固定字符串的功能,字符串即是多个字符的集合,所以这里需要一个字符发送计数器,在每次串口发送模块发送完一个字符后加1,从而索引存储在FPGA内部的字符串。说到存储字符串,我们需要一个存储结构,它能将多个比特作为一个整体进行索引,这样才能通过计数器找到一整个字符,所以要用到存储器的结构。上面说要每隔一段时间发送一个字符串,很明显需要等待时间计数器和相应的标志位来区分等待区间和发送区间。至于字符串的接收,其实是一个道理:当然也需要对接收数据计数,这样才能知道接收到字符串的长度。等待区间内若收到结束符#号,则在等待结束后由发送固定字符转而将接收的字符发送出去。其关键也是在于通过接收计数器对接收缓存进行索引。至此,控制模块已设计完毕。你会发现,上述功能仅仅需要几个计数器和一些标志位之间的逻辑即可完成,如此简单的流程不需要使用的状态机。之前的按键检测模块等下也用这种设计思想加以化简。废话不多说,上代码:

      1 `timescale 1ns / 1ps
      2 
      3 module uart_ctrl(
      4     input clk,
      5     input rst_n,
      6     input key_in,
      7     
      8     input [7:0] data_in,
      9     input data_in_vld,
     10     input tx_finish,
     11     output reg [2:0] baud,
     12     output reg [7:0] data_out,
     13     output reg tx_en
     14     );
     15     
     16     parameter WAIT_TIME = 600_000_000;//3s
     17     integer i;
     18     
     19     reg [7:0] store [4:0];//发送存储
     20     reg [7:0] str_cnt;
     21     reg [7:0] N;
     22     reg [7:0] rx_cnt;
     23     reg [7:0] rx_cnt_tmp;
     24     reg [7:0] rx_num;
     25     reg [31:0] wait_cnt;
     26     (*mark_debug = "true"*)reg wait_flag;
     27     reg rec_flag;
     28     reg [7:0] rx_buf [9:0];
     29     
     30     wire add_str_cnt,end_str_cnt;
     31     wire add_wait_cnt,end_wait_cnt;
     32     wire add_rx_cnt,end_rx_cnt;
     33     wire end_signal;
     34     wire din_vld;
     35     
     36     //按键实现波特率的切换
     37     always@(posedge clk or negedge rst_n)begin
     38         if(!rst_n)
     39             baud <= 3'b000;
     40         else if(key_in)begin
     41             if(baud == 3'b100)
     42                 baud <= 3'b000;
     43             else 
     44                 baud <= baud + 1'b1;
     45         end
     46     end
     47     
     48     always@(posedge clk or negedge rst_n)begin
     49         if(!rst_n)begin
     50             store[0]  <= 0;
     51             store[1]  <= 0;   
     52             store[2]  <= 0;
     53             store[3]  <= 0;  
     54             store[4]  <= 0;  
     55         end
     56         else begin
     57             store[0]  <= "w";//8'd119;//w  
     58             store[1]  <= "a";//8'd97;//a   
     59             store[2]  <= "i";//8'd105;//i  
     60             store[3]  <= "t";//8'd116;//t  
     61             store[4]  <= " ";//8'd32;//空格 
     62         end
     63     end
     64     
     65     //发送计数器区分发送哪一个字符
     66     always@(posedge clk or negedge rst_n)begin
     67         if(!rst_n)
     68             str_cnt <= 0;
     69         else if(add_str_cnt)begin
     70             if(end_str_cnt)
     71                 str_cnt <= 0;
     72             else 
     73                 str_cnt <= str_cnt + 1'b1;
     74         end
     75     end
     76     
     77     assign add_str_cnt = tx_finish;
     78     assign end_str_cnt = add_str_cnt && str_cnt == N - 1;
     79     
     80     //接收计数器
     81     always@(posedge clk or negedge rst_n)begin
     82         if(!rst_n)
     83             rx_cnt <= 0;
     84         else if(add_rx_cnt)begin
     85             if(end_rx_cnt)
     86                 rx_cnt <= 0;
     87             else 
     88                 rx_cnt <= rx_cnt + 1'b1;
     89         end
     90     end
     91     
     92     assign add_rx_cnt = din_vld;
     93     assign end_rx_cnt = add_rx_cnt && ((rx_cnt == 10 - 1) || data_in == "#");//接收到的字符串最长为10个
     94     
     95     
     96     assign din_vld = data_in_vld && wait_flag;
     97     
     98     //计数器计时等待时间1s
     99     always@(posedge clk or negedge rst_n)begin
    100         if(!rst_n)
    101             wait_cnt <= 0;
    102         else if(add_wait_cnt)begin
    103             if(end_wait_cnt)
    104                 wait_cnt <= 0;
    105             else 
    106                 wait_cnt <= wait_cnt + 1'b1;
    107         end
    108     end
    109     
    110     assign add_wait_cnt = wait_flag;
    111     assign end_wait_cnt = add_wait_cnt && wait_cnt == WAIT_TIME - 1;
    112     
    113     //等待标志位
    114     always@(posedge clk or negedge rst_n)begin
    115         if(!rst_n)
    116             wait_flag <= 1;
    117         else if(end_wait_cnt)
    118             wait_flag <= 0;
    119         else if(end_str_cnt)
    120             wait_flag <= 1;
    121     end
    122     
    123     always@(posedge clk or negedge rst_n)begin
    124         if(!rst_n)
    125             rx_num <= 0;
    126         else if(end_signal)
    127             rx_num <= rx_cnt + 1'b1;
    128     end
    129     
    130     assign end_signal = add_rx_cnt && data_in == "#";
    131     
    132     //接收缓存
    133     always@(posedge clk or negedge rst_n)begin
    134         if(!rst_n)
    135             for(i = 0;i < 10;i = i + 1)begin
    136                 rx_buf[i] <= 0;
    137             end
    138         else if(din_vld && !end_signal)
    139             rx_buf[rx_cnt] <= data_in;
    140         else if(end_wait_cnt)
    141             rx_buf[rx_num - 1] <= " ";
    142         else if(end_str_cnt)
    143         for(i = 0;i < 10;i = i + 1)begin
    144                 rx_buf[i] <= 0;
    145             end
    146     end
    147     
    148     //检测有效数据
    149     always@(posedge clk or negedge rst_n)begin
    150         if(!rst_n)
    151             rec_flag <= 0;
    152         else if(end_signal)
    153             rec_flag <= 1;
    154         else if(end_str_cnt)
    155             rec_flag <= 0;
    156     end
    157     
    158     always@(*)begin
    159         if(rec_flag)
    160             N <= rx_num;
    161         else 
    162             N <= 5;
    163     end
    164     
    165     //发送数据给串口发送模块
    166     always@(*)begin
    167         if(rec_flag)
    168             data_out <= rx_buf[str_cnt];
    169         else 
    170             data_out <= store[str_cnt];
    171     end
    172     
    173     //等待结束后发送使能有效
    174     always@(posedge clk or negedge rst_n)begin
    175         if(!rst_n)
    176             tx_en <= 0;
    177         else if(end_wait_cnt || (add_str_cnt && str_cnt < N - 1 && !wait_flag))
    178             tx_en <= 1;
    179         else 
    180             tx_en <= 0;
    181     end
    182     
    183 endmodule

      控制模块设计结束,我们通过仿真验证预期功能是否实现。这里仅测试最重要的控制模块,由于需要用到发送模块的tx_finish信号,在测试文件中同时例化控制模块和串口发送模块。需要注意在仿真前将控制模块设为顶层。测试文件:

     1 `timescale 1ns / 1ps
     2 
     3 module uart_ctrl_tb;
     4     
     5     reg clk,rst_n;
     6     reg key_in;
     7     reg [7:0] data_in;
     8     reg data_in_vld;
     9     
    10     wire tx_finish;
    11     wire [2:0] baud;
    12     wire [7:0] data_tx;
    13     wire tx_en;
    14     
    15     uart_ctrl uart_ctrl(
    16     .clk(clk),
    17     .rst_n(rst_n),
    18     .key_in(key_in),
    19     
    20     .data_in(data_in),
    21     .data_in_vld(data_in_vld),
    22     .tx_finish(tx_finish),
    23     .baud(baud),
    24     .data_out(data_tx),
    25     .tx_en(tx_en)
    26     );
    27     
    28     uart_tx_module uart_tx_module( 
    29     .clk(clk),
    30     .rst_n(rst_n),
    31     .baud_set(baud),
    32     .send_en(tx_en),
    33     .data_in(data_tx),
    34     
    35     .data_out(),
    36     .tx_done(tx_finish)
    37     );
    38     
    39     
    40     integer i;
    41     
    42     parameter CYC = 5,
    43               RST_TIME = 2;
    44               
    45     defparam uart_ctrl.WAIT_TIME = 2000_000;
    46     
    47     initial begin
    48         clk = 0;
    49         forever #(CYC / 2.0) clk = ~clk;
    50     end
    51     
    52     initial begin
    53         rst_n = 1;
    54         #1;
    55         rst_n = 0;
    56         #(CYC * RST_TIME);
    57         rst_n = 1;
    58     end
    59     
    60     
    61     initial begin
    62         #1;
    63         key_in = 0;
    64         data_in = 0;
    65         data_in_vld = 0;
    66         #(CYC * RST_TIME);
    67         #10_000;
    68         #5_000_000;
    69         data_in = 8'h80;
    70         repeat(4)begin
    71             data_in_vld = 1;
    72             data_in = data_in + 1;
    73             #(CYC * 1);
    74             data_in_vld = 0;
    75         end
    76         data_in_vld = 1;
    77         data_in = 8'd32;
    78         #(CYC * 1);
    79         data_in_vld = 0;
    80         #10_000;
    81         $stop;
    82     end
    83     
    84 endmodule

      本次设计先采用VIVADO自带仿真工具Vivado Simulator。虽然速度有些慢,不过对简单的设计来说体验区别不明显,而且用起来很方便简单,适合新手。观察行为仿真波形:

      可以看到波形符合预期功能,成功将串口接收到的129 130 131 132 32五个数据通过串口环回,在没有收到有效字符串时发送“wait”字符串对应的ASCII码十进制数值。如代码有问题修改代码并保存后只需按下仿真界面上方仿真工具栏中重新Relaunch Simulation按钮,开发工具将自动将修改后的代码更新到仿真环境中并重新开始运行仿真:

      在上述控制模块中,我加入了根据按键按下次数调整常用波特率的功能,因此需要例化按键消抖模块。剩下的工作只需建立顶层文件,把各个模块之间信号连接起来。好像没什么可说的了,相信大家都能看懂,以下是顶层模块

     1 `timescale 1ns / 1ps
     2 
     3 module send_data_top(
     4     input sys_clk_p,
     5     input sys_clk_n,
     6     input rst_n,
     7     input key,
     8     
     9     output bit_tx,
    10     output tx_finish_led,
    11     
    12     input bit_rx,
    13     output rx_finish_led
    14     );
    15     
    16     wire tx_done,rx_done;
    17     (*mark_debug = "true"*)wire data_rx_vld;
    18     (*mark_debug = "true"*)wire [7:0] data_rx_byte;
    19     wire key_signal;
    20     wire [2:0] baud;
    21     wire [7:0] data_tx;
    22     (*mark_debug = "true"*)wire send_start;
    23     
    24     // 差分时钟转单端时钟
    25     // IBUFGDS是IBUFG差分形式,当信号从一对差分全局时钟引脚输入时,必须使用IBUFGDS作为全局时钟输入缓冲
    26     wire sys_clk_ibufg;
    27     IBUFGDS #
    28     (
    29     .DIFF_TERM ("FALSE"),
    30     .IBUF_LOW_PWR ("FALSE")
    31     )
    32     u_ibufg_sys_clk
    33     (
    34     .I (sys_clk_p), //差分时钟的正端输入,需要和顶层模块的端口直接连接
    35     .IB (sys_clk_n), // 差分时钟的负端输入,需要和顶层模块的端口直接连接
    36     .O (sys_clk_ibufg) //时钟缓冲输出
    37     );
    38     
    39     key_jitter key_jitter
    40     (
    41     .clk(sys_clk_ibufg),
    42     .rst_n(rst_n),
    43     
    44     .key_i(key),
    45     .key_vld(key_signal)
    46     );
    47     
    48     uart_ctrl uart_ctrl(
    49     .clk(sys_clk_ibufg),
    50     .rst_n(rst_n),
    51     .key_in(key_signal),
    52     
    53     .data_in(data_rx_byte),
    54     .data_in_vld(data_rx_vld),
    55     .tx_finish(tx_done),
    56     .baud(baud),
    57     .data_out(data_tx),
    58     .tx_en(send_start)
    59     );
    60     
    61     
    62     uart_tx uart_tx(
    63     .clk(sys_clk_ibufg),
    64     .rst_n(rst_n),
    65     .baud_set(baud),//[2:0]
    66     .send_en(send_start),
    67     .data_in(data_tx),//[7:0] 
    68     
    69     .data_out(bit_tx),
    70     .tx_done(tx_done));
    71     
    72     assign tx_finish_led = !tx_done;
    73     
    74     uart_rx uart_rx(
    75     .clk(sys_clk_ibufg),
    76     .rst_n(rst_n),
    77     .baud_set(baud),
    78     .din_bit(bit_rx),
    79     
    80     .data_byte(data_rx_byte),
    81     .dout_vld(data_rx_vld)
    82     );
    83     
    84     assign rx_finish_led = !data_rx_vld;
    85     
    86 endmodule

      看下整体结构图吧,很清晰,也确认信号连接没有犯低级错误

      确认功能没有问题之后添加约束文件:

       然后步骤同上一篇博文,添加调试IP核,综合、布局布线、生成bit流。打开硬件管理器下载bit流,使用调试界面观察芯片内部波形数据,先来看看接收有没有问题,串口调试助手发送“good#”,观察接收有效指示信号和接收数据:

      成功接收到了good字符串,并且串口调试助手收到了发送的字符,在没有发送字符时每隔3s收到一个“wait”字符串:

      串口收到数据的工程到这里告一段落,以后可以进一步改进和做些更具应用性的工程。经过三篇博文,提高了VIVADO开发环境的基本操作熟练度,对串口协议有了深层次的认识。最重要的是时序设计能力有了一定的提升。

  • 相关阅读:
    P1158 导弹拦截
    麦基数(p1045)
    Django之路由层
    web应用与http协议
    Django之简介
    Mysql之表的查询
    Mysql之完整性约束
    Mysql之常用操作
    Mysql之数据类型
    Mysql之数据库简介
  • 原文地址:https://www.cnblogs.com/moluoqishi/p/7280191.html
Copyright © 2011-2022 走看看