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

  上一篇博文中详细设计了串口发送模块,串口接收模块设计思想基本相同,只不过将总线的下降沿作为数据接收的开始条件。需要注意有两点:其一,串口接收中读取每一位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

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

 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==1‘b0)begin
24             cnt <= 20‘b0;
25         end
26         else if(add_cnt)begin
27             if(end_cnt)
28                 cnt <= 20‘b0;
29             else
30                 cnt <= cnt + 1‘b1;
31         end
32         else begin
33             cnt <= 0;
34         end
35     end
36     //按下状态才计数,松手清零
37     assign add_cnt = flag == 1‘b0 && 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==1‘b0)begin
43             flag <= 1‘b0;
44         end
45         else if(end_cnt)begin
46             flag <= 1‘b1;
47         end
48         else if(key_in_ff1==0)begin
49             flag <= 1‘b0;
50         end
51     end
52
53     //同步处理
54     always  @(posedge clk or negedge rst_n)begin
55         if(rst_n==1‘b0)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==1‘b0)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开发环境的基本操作熟练度,对串口协议有了深层次的认识。最重要的是时序设计能力有了一定的提升。

时间: 2024-10-22 18:59:26

串口完整项目之串口收发字符串的相关文章

【小梅哥FPGA进阶教程】第九章 基于串口猎人软件的串口示波器

九.基于串口猎人软件的串口示波器 1.实验介绍 本实验,为芯航线开发板的综合实验,该实验利用芯航线开发板上的ADC.独立按键.UART等外设,搭建了一个具备丰富功能的数据采集卡,芯航线开发板负责进行数据的采集并将数据通过串口发送到PC机上,PC端,利用强大的串口调试工具--串口猎人,来实现数据的接收分析,并将数据分别以波形.码表.柱状图的形式动态显示出来,以让使用者能够直观的看到ADC采集到的信号细节.同时,用户也可以使用串口猎人通过串口给下位机(FPGA)发送指令,下位机将对接收到的指令进行解

[RK_2014_0910]串口编程中,串口数据接收不全的可能原因

[情形1] [ubuntu14.04-->Win8] [环境描述] PC1:Win8,运行串口调试工具:USR-TCP232-Test.exe:串口COM1的参数设置为“9600,8,1,N". PC2:Ubuntu14.04:串口/dev/ttyUSB0的参数设置为“9600,8,1,N". [代码描述] // 串口传输线程 处理函数 void* CTestEth::ThreadFuncHandleSerialPortTransfer(IN void* arg) { if ((

C# 串口操作系列(2) -- 入门篇,为什么我的串口程序在关闭串口时候会死锁 ?

C# 串口操作系列(2) -- 入门篇,为什么我的串口程序在关闭串口时候会死锁 ? 标签: c#objectuibyte通讯.net 2010-05-19 08:43 55212人阅读 评论(188) 收藏 举报  分类: 通讯类库设计(4)  版权声明:本文为博主原创文章,未经博主允许不得转载. 第一篇文章我相信很多人不看都能做的出来,但是,用过微软SerialPort类的人,都遇到过这个尴尬,关闭串口的时候会让软件死锁.天哪,我可不是武断,算了.不要太绝对了.99.9%的人吧,都遇到过这个问

Delphi 使用串口模拟工具进行串口程序开发调试

版权声明:本文为博主原创文章,如需转载请注明出处及作者. 本文由小李专栏原创,转载需注明出处:[http://blog.csdn.net/softwave/article/details/8907599] 最近在做一个项目,要求使用Java对磅房的小磅数据进行读取,这就要求使用Java与串口进行通信.串口的Java代码已经开发完毕,但又不能总在生产现场调试程序,所以如何在自己的笔记本电脑上模拟串口数据就成了当务之急.托Google的福,找到了两个小工具:VSPD和串口调试助手.下面简单介绍一下如

iOS开发——完整项目实战OC篇&amp;百思不得姐第四天

iOS开发——完整项目实战OC篇&百思不得姐第四天 上午 一:自定义按钮使用九宫格布局 二:控件不能点击 三:获取用户点击了那个按钮 四:调整按钮内部控件的位置:主流->上下 五:不能直接使用self.navigationController中或者View中获取导航控制器 方法一: 方法二: 六:布局取整 1 // 总行数 2 3 // NSUInteger rows = sqaures.count / maxCols; 4 5 // if (sqaures.count % maxCols)

JavaWeb网上图书商城完整项目--day02-4.regist页面提交表单时对所有输入框进行校验

1.现在我们要将table表中的输入的参数全部提交到后台进行校验,我们提交我们是按照表单的形式提交,所以我们首先需要在table表外面添加一个表单 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core&

phpstorm在项目中查找某个字符串

如果项目过大,想在整个项目中找某个字符串,又不知道该字符串所在文件的路径,这时候就可以使用全局搜索了,ctrl+shift+F.在mac下,快捷键是command+shift+F:

纪录Swift完整项目打包Framework,嵌入OC项目使用

场景说明: -之前做的App,使用Swift框架语言,混合编程,内涵少部分OC代码. -需要App整体功能打包成静态库,完整移植到另一个App使用,该App使用OC语言,基于ReactiveCocoa框架. -----------------------------------------打包篇----------------------------------------- 实现步骤: 一.新建 Project - Framework&Library - Cocoa Touch Framewo

GankApp 侧滑和title修改颜色的完整项目app

GankApp 侧滑和title修改颜色的完整项目app GankApp 侧滑和title修改颜色的完整项目app,本项目主要由侧滑框架和4.4以及以上的头部title颜色调整和,首页viewpager滑动切换,以及处理侧滑和viewpager事件冲突问题,和每一个item点击进入图片预览页面的效果,以及图片预览页面点击头部显示隐藏动画效果.本项目来源:https://github.com/xiongwei-git/GankApp本项目主要代码如下: 通过如下加载viewpager