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

Posted 没落骑士

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了串口完整项目之串口收发字符串相关的知识,希望对你有一定的参考价值。

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

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

CC2530 串口收发字符串

使用Windows API实现一个简单的串口助手

QT5 串口收发实例代码

C语言变成实现串口收发数据

做了了简易的串口收发数据界面,用LabVIEW做的,每次打开程序就运行了,但是第一次发