码迷,mamicode.com
首页 > 其他好文 > 详细

串口完整项目之串口收发字符串

时间:2017-08-03 16:56:14      阅读:253      评论:0      收藏:0      [点我收藏+]

标签:它的   操作   mod   文件   比特   tmp   time   存储结构   同步   

  上一篇博文中详细设计了串口发送模块,串口接收模块设计思想基本相同,只不过将总线的下降沿作为数据接收的开始条件。需要注意有两点:其一,串口接收中读取每一位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 + 1b1;
 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 + 1b1;
 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             3b000: CYC  <= 20833;//9600
 59             3b001: CYC  <= 10417;//19200
 60             3b010: CYC  <= 5208;//38400
 61             3b011: CYC  <= 3472;//57600
 62             3b100: 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 <= 3b000;
 40         else if(key_in)begin
 41             if(baud == 3b100)
 42                 baud <= 3b000;
 43             else 
 44                 baud <= baud + 1b1;
 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 + 1b1;
 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 + 1b1;
 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 + 1b1;
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 + 1b1;
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

  在上述控制模块中,我加入了根据按键按下次数调整常用波特率的功能,现在拿出使用计数器和标志位逻辑实现的简化版按键消抖模块代码:

 1 `timescale 1ns / 1ps
 2 
 3 module key_filter
 4 #(parameter DATA_W    = 20,
 5             KEY_W     = 1,
 6             TIME_20MS = 1_000_000)
 7 (
 8    input clk    ,
 9    input rst_n  ,
10    input [KEY_W-1 :0] key_in ,    //按键 按下为低电平
11    output reg [KEY_W-1 :0] key_vld 
12 );
13 
14     reg [DATA_W-1:0] cnt;
15     reg flag;
16     reg [KEY_W-1 :0] key_in_ff1;
17     reg [KEY_W-1 :0] key_in_ff0;
18 
19     wire add_cnt,end_cnt;
20     
21     //延时计数器
22     always  @(posedge clk or negedge rst_n)begin
23         if(rst_n==1b0)begin
24             cnt <= 20b0;
25         end
26         else if(add_cnt)begin
27             if(end_cnt)
28                 cnt <= 20b0;
29             else
30                 cnt <= cnt + 1b1;
31         end
32         else begin
33             cnt <= 0;
34         end
35     end
36     //按下状态才计数,松手清零
37     assign add_cnt = flag == 1b0 && key_in_ff1 == 0; 
38     assign end_cnt = add_cnt && cnt == TIME_20MS - 1;
39     
40     //计数标志位,0有效
41     always  @(posedge clk or negedge rst_n)begin 
42         if(rst_n==1b0)begin
43             flag <= 1b0;
44         end
45         else if(end_cnt)begin
46             flag <= 1b1;
47         end
48         else if(key_in_ff1==0)begin
49             flag <= 1b0;
50         end
51     end
52     
53     //同步处理
54     always  @(posedge clk or negedge rst_n)begin 
55         if(rst_n==1b0)begin
56             key_in_ff0 <= 0;
57             key_in_ff1 <= 0;
58         end
59         else begin
60             key_in_ff0 <= key_in    ;
61             key_in_ff1 <= key_in_ff0;
62         end
63     end
64 
65     //输出有效
66     always  @(posedge clk or negedge rst_n)begin 
67         if(rst_n==1b0)begin
68             key_vld <= 0;
69         end
70         else if(end_cnt)begin
71             key_vld <= ~key_in_ff1;
72         end
73         else begin
74             key_vld <= 0;
75         end
76     end
77     
78 endmodule

   剩下的工作只需建立顶层文件,把各个模块之间信号连接起来,并添加XDC约束文件。好像没什么可说的了,相信大家都能看懂,以下是顶层模块和XDC文件:

 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开发环境的基本操作熟练度,对串口协议有了深层次的认识。最重要的是时序设计能力有了一定的提升。

串口完整项目之串口收发字符串

标签:它的   操作   mod   文件   比特   tmp   time   存储结构   同步   

原文地址:http://www.cnblogs.com/moluoqishi/p/7280191.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!