串口发送模块——1字节数据发送

摘要:
有了这些初步知识,我们开始设计串行传输模块。每个模块应具有必要的时钟和复位输入。此外,串行端口传输模块需要确保数据不会重复发送,因此应该有传输启用信号。发送侧应提供数据串行输出端口和发送完成指示输出。至于传输完成指示信号,它只需要根据比特计数器的值而改变。

  设计思想与代码规范均借鉴明德扬至简设计法,有不足之处希望大家多提建议,真正做到至简设计。本篇着重提出FPGA通用设计思想,以计数器为核心的代码规范以及VIVADO debug操作流程。

  此次试验旨在通过串口试验,讲述FPGA的硬件设计思想和通用设计流程。串口是电子设计中非常常见,可以说掌握了串口数据收发,就明白了最基本的时序操作。串口的数据收发过程有其固定的数据格式。下面是本次实验使用的数据格式,在满足串口格式规范前提下是可变的:

串口发送模块——1字节数据发送第1张

  空闲状态下为高电平,当发送数据时,先发送低电平起始位,后从低位开始逐位发送有效数据比特,数据位位数由双方约定,此处设定为8位。可在数据位后添加数据校验位,但这不是必须的。发送完后发送高电平停止位并持续空闲状态直至下一次发送。虽然本次实验没有用到,但这里简要讲一下奇偶校验的原理:

  奇偶校验是一种非常简单常用的数据校验方式,分为奇校验和偶校验。奇校验需要保证传输的数据总共有奇数个逻辑高电平,若是偶校验则要保证传输的数据有偶数个逻辑高电平。即“奇偶”的意思就是数据中(包括该校验位)中1的个数。例如:传输的数据位是0100_0011。如果是奇校验,校验位是0,偶校验校验位是1。

  在串口通信中,波特率是一个非常重要的概念。串口通信中常用的波特率是9600、19200、38400、57600、115200。波特率是每个码元传输的速率,在二进制数据传输中,和比特率相同,都是每个比特数据传输的速率,其倒数为1bit数据的位宽,也就是1bit数据持续的时间。有了这一时间段,就可用FPGA构造计数器实现比特周期的延时,从而实现特定的数据传输波特率。

  有了这些预备知识,我们开始设计串口发送模块。第一步要明确设计目的:要设计的模块功能当一个时钟周期使能信号有效时,将输入数据通过串口发送给PC机。后续可以通过FIFO缓存数据,实现多个数据的发送。知道设计目的后,通常要开始根据大体功能进行模块划分,模块之间的接口定义以及各模块内部的硬件设计。本次实验只有一个模块,所以直接从模块接口定义开始。每个模块都要有必要的时钟和复位输入,另外串口发送模块需要确保数据不重复发送,因此要有发送使能信号。为了满足不同速率需求,需要波特率设定输入信号来选通不同的波特率。最重要的是待发送数据输入端口。发送侧要有数据串行输出端口和发送完成指示输出。综上,串口发送模块接口示意图如下:

 串口发送模块——1字节数据发送第2张

  现在开始模块内部功能的硬件实现。首先需要一个参数可变的分频计数器满足不同波特率要求。为此需要一个查找表结构对输入的波特率设定指令进行译码,改变计数器参数。然后要将数据进行并串转换可以通过一个比特位计数器控制数据选择器实现,这样可以将发送比特位数与待发送数据位数相对应。至于发送完成指示信号只需根据比特计数器的数值改变即可。在设计代码前先画出主要信号的时序波形图有助于理清思路:(此处假设比特计数器每个时钟周期计数一次便于画图)

串口发送模块——1字节数据发送第3张

  到目前为止最重要的设计工作已经做完了,接下来的代码编写也就没有任何难度可言。

串口发送模块代码:

 1 `timescale 1ns / 1ps
 2 
 3 module uart_tx(
 4     input clk,
 5     input rst_n,
 6     input [2:0] baud_set,
 7     input send_en,
 8     input [7:0] data_in,
 9     
10     output reg data_out,
11     output tx_done
12     );
13     
14     reg [15:0] CYC;
15     reg [15:0] cnt_div;
16     (*mark_debug = "true"*)reg [3:0] cnt_bit;
17     reg add_flag;
18     
19     wire add_cnt_div;
20     (*mark_debug = "true"*)wire end_cnt_div;
21     wire add_cnt_bit,end_cnt_bit;
22     
23     //分频计数器
24     always@(posedge clk or negedge rst_n)begin
25         if(!rst_n)
26             cnt_div <= 0;
27         else if(add_cnt_div)begin
28             if(end_cnt_div)
29                 cnt_div <= 0;
30             else 
31                 cnt_div <= cnt_div + 1'b1;
32         end
33     end
34     
35     assign add_cnt_div = add_flag;
36     assign end_cnt_div = add_cnt_div && cnt_div == CYC - 1;
37     
38     //比特位数计数器
39     always@(posedge clk or negedge rst_n)begin
40         if(!rst_n)
41             cnt_bit <= 0;
42         else if(add_cnt_bit)begin
43             if(end_cnt_bit)
44                 cnt_bit <= 0;
45             else     
46                 cnt_bit <= cnt_bit + 1'b1;
47         end
48     end
49     
50     assign add_cnt_bit = end_cnt_div;
51     assign end_cnt_bit = add_cnt_bit && cnt_bit == 10 - 1;
52     
53     //发送使能后分频计数器开始计数,直到将起始位、数据位、停止位发送完成为止
54     always@(posedge clk or negedge rst_n)begin
55         if(!rst_n)
56             add_flag <= 0;
57         else if(send_en)
58             add_flag <= 1;
59         else if(end_cnt_bit)
60             add_flag <= 0;
61     end
62     //波特率查找表
63     always@(*)begin
64         case(baud_set)
65             3'b000:CYC  <= 20833;//9600
66             3'b001:CYC  <= 10417;//19200
67             3'b010:CYC  <= 5208;//38400
68             3'b011:CYC  <= 3472;//57600
69             3'b100:CYC  <= 1736;//115200
70             default:CYC <= 20833;//9600
71         endcase
72     end
73     //根据比特计数器得到对应比特位
74     always@(posedge clk or negedge rst_n)begin
75         if(!rst_n)
76             data_out <= 1;
77         else if(send_en)
78             data_out <= 0;
79         else if(add_cnt_bit && cnt_bit >= 0 && cnt_bit < 8)
80             data_out <= data_in[cnt_bit];
81         else if((add_cnt_bit && cnt_bit == 8) || end_cnt_bit)
82             data_out <= 1;//结束位或者空闲状态均为高电平
83     end
84     
85     assign tx_done = end_cnt_bit;
86     
87 endmodule

现编写测试激励,观察仿真波形是否与预期一致:

 1 `timescale 1ns / 1ps
 2 
 3 module uart_tx_tb;
 4 
 5     reg clk,rst_n;
 6     reg [2:0] baud_set;
 7     reg send_en;
 8     reg [7:0] data_in;
 9     
10     wire data_out;
11     wire tx_done;
12     
13     uart_tx uart_tx(
14     .clk(clk),
15     .rst_n(rst_n),
16     .baud_set(baud_set),//[2:0]
17     .send_en(send_en),
18     .data_in(data_in),//[7:0] 
19     
20     .data_out(data_out),
21     .tx_done(tx_done)
22     );
23     
24     parameter CYCLE = 5,
25               RST_TIME = 2;
26     
27     initial begin
28         clk = 0;
29         forever #(CYCLE / 2) clk = ~clk;
30     end
31     
32     initial begin
33         rst_n = 1;
34         #1;
35         rst_n = 0;
36         #(CYCLE * RST_TIME);
37         rst_n = 1;
38     end
39     
40     initial begin
41         baud_set = 3'b000;
42         send_en = 0;
43         data_in = 0;
44         #1;
45         #(CYCLE * RST_TIME);
46         #(CYCLE * 10);
47         send_en = 1;
48         data_in = 8'b0101_0110;
49         #(CYCLE * 1);
50         send_en = 0;
51         #2_000_000;
52         $stop;
53     end
54     
55 endmodule

仿真波形如下:

串口发送模块——1字节数据发送第4张

  可以看出该模块真确将待发送数据8'b0101_0110 按照串口数据格式发送了出去,分频计数器计数完成后分别发送了0_0110_1010_1.此刻,串口发送模块逻辑功能验证完毕。为了在开发板中运行,添加按键消抖模块,将按键有效输出信号作为发送模块的发送使能,并建立顶层模块。按键消抖模块在上一篇博文中已详细讲述,仅稍作改动调用。下面是顶层模块:

 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     output dout,
 9     output tx_done_out
10     );
11     (*mark_debug = "true"*)wire tx_done;
12     (*mark_debug = "true"*)wire key_en;
13     // 差分时钟转单端时钟
14     // IBUFGDS是IBUFG差分形式,当信号从一对差分全局时钟引脚输入时,必须使用IBUFGDS作为全局时钟输入缓冲
15     wire sys_clk_ibufg;
16     IBUFGDS #
17     (
18     .DIFF_TERM ("FALSE"),
19     .IBUF_LOW_PWR ("FALSE")
20     )
21     u_ibufg_sys_clk
22     (
23     .I (sys_clk_p), //差分时钟的正端输入,需要和顶层模块的端口直接连接
24     .IB (sys_clk_n), // 差分时钟的负端输入,需要和顶层模块的端口直接连接
25     .O (sys_clk_ibufg) //时钟缓冲输出
26     );
27     
28     key_jitter key_jitter(
29     .clk(sys_clk_ibufg),
30     .rst_n(rst_n),
31     .key_i(key),
32     .key_vld(key_en)
33     );
34     
35     uart_tx uart_tx(
36     .clk(sys_clk_ibufg),
37     .rst_n(rst_n),
38     .baud_set(3'b000),//[2:0]
39     .send_en(key_en),
40     .data_in(8'h32),//[7:0] 
41     
42     .data_out(dout),
43     .tx_done(tx_done));
44     
45     assign tx_done_out = ~tx_done;
46     
47     
48 endmodule

  打开分析后的设计原理图,方便地观察设计整体结构:

串口发送模块——1字节数据发送第5张

  HDL代码设计完毕,后需添加约束文件,这里只需为每个端口添加对应的端口号和电平标准即可。注意:当某个信号为多个位时,在后边的方括号内需要用大括号把每一位信号括起来,如:set_property PACKAGE A5 [{led[0]}] 

串口发送模块——1字节数据发送第6张

  仿真只是通过软件来模拟硬件的场景,尤其在只做了最理想情况下的行为仿真时,并不能完全的体现出所有硬件特性,这时就要进行“在线调试”,也就是使用嵌入式逻辑分析仪,直接抓取芯片内部真实运行的信号数值。它的基本原理是通过IP核的形式嵌入到FPGA芯片内部,不断将要观测数据存入RAM中,当触发条件有效时,停止检测并将信号数据以类似仿真波形的形式显示出来。那么如何选择所要观测的信号呢?观察上面的HDL代码会发现,某些信号定义之前有(*mark_debug = "true"*)。这就是“抓取信号”的方式,在信号定义之前加上这条语句之后,点击Run synthesis,并打开综合后的设计。打开调试界面,点击Set Up Debug 执行ILA调试IP核的生成向导。之前被标注的信号已经自动添加了进来,当然,你可以添加更多的需要观测的信号。

串口发送模块——1字节数据发送第7张

串口发送模块——1字节数据发送第8张

  Run implementation并生成比特流后,打开硬件管理器,并自动连接开发板下载比特流。此时debug probles file也同时被加载进来:

串口发送模块——1字节数据发送第9张

  下载完毕后debug界面自动打开:

串口发送模块——1字节数据发送第10张

  按照图中数字的顺序依次完成抓取模式设置,设置触发条件,启动触发,观测波形。2中设置key_en为高电平时启动触发,观察核心信号数据。

串口发送模块——1字节数据发送第11张

  可以看出key_en高电平后发送“0”。由于设置RAM深度太小,导致没有观察到串口数据完整格式。再次将触发条件改为tx_done高电平触发,并修改触发条件所在观测窗口的位置:

串口发送模块——1字节数据发送第12张

  tx_done高电平之前比特计数器正确计数到9,tx_done高电平之后一个时钟周期计数值变为0,证明内部逻辑功能正常运行。也可以自行回到综合后界面,再次打开Set Up Debug界面修改数据采样深度观察完整波形:

串口发送模块——1字节数据发送第13张

  此时观察串口调试助手,设置好波特率和数据格式,将显示方式设定为16进制。打开串口后,按下按键并松手后,串口调试助手接收到一个8位数据,这里固定让其发送数字8'h32,以下是按两次按键收到的数据

串口发送模块——1字节数据发送第14张

  到此,串口发送模块已设计完毕,将ILA IP核的标注和相关约束去掉可节省逻辑资源。

免责声明:文章转载自《串口发送模块——1字节数据发送》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇MySQL 百万级分页优化(Mysql千万级快速分页)Selenium示例集锦--常见元素识别方法、下拉框、文本域及富文本框、鼠标操作、一组元素定位、弹窗、多窗口处理、JS、frame、文件上传和下载下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

SpringBoot 2.x 整合Lombok

Lombok的官方介绍 Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Lombok以简单的注解形式来简化java代码,提高开发人员的开发效率 lombok是一个编译级别的插件,...

Intel Core Microarchitecture Pipeline

Intel微处理器近20年从Pentium发展到Skylake,得益于制作工艺上的巨大发展,处理器的性能得到了非常大的增强,功能模块增多,不过其指令处理pipeline的主干部分算不上有特别大的变化,更多的是为了提高指令的处理速度添加一些模块以及各模块的增强与优化。 本文会以Intel Core微处理器架构为例去了解Intel微处理器pipeline的各个...

STM32H743 | FDCAN 波特率问题

STM32H743 | FDCAN 波特率问题 直奔主题,最近项目上接触了FDCAN,主控为STM32H743。在开发过程中存在疑点,特此记录。 疑点:芯片手册上,波特率的相关寄存器位标明硬件将该值解析为编程值加1,但是实际上通过STM32 HAL库的HAL_FDCAN_Init()函数来初始化FDCAN时,我们给FDCAN初始化结构体的成员变量所赋的值...

ubuntu中minicom安装和使用

想要对嵌入式开发板进行开发和操作,都需要进行文件传输或者是控制,这时基本都是需要通过串口线或者是网线进行连接的,在Windows下是使用超级终端通过串口对开发板进行操作的,而在Linux下,最后最常见的串口调试工具就是minicom。minicom的安装过程还是比较简单的,对于操作过程也就是一个熟悉的过程,在其中的操作都是字符界面下的,只要知道熟悉,就能熟...

Arduino库函数中文说明

#define 常量名 常量值% 取模运算符String abc/char abc[n]定义字符串pinMode(pin,mode);用于引脚的初始化mode包括 INPUT/OUTPUT/INPUT_PULLUPArduino 数模转换器有 10位精度,可以将0-5V转换为 0-1023,仅用于analogRead(pin) analogWrite(n)...

蓝牙进阶之路 (001)

USB转串口的有线转接方式,实在太难看了,尤其是寻接头,那是相当的不方便。其它电器厂商都想把是接头做小,做精致,唯独串口接头还是那么庞大,感觉应该换一换了,都已经完全不符合这个时代的审美观了。 于是,某宝上买了两套HC-05蓝牙无线模块,HC-05是主从一体的蓝牙模块,所以比只能当从机的HC-6要贵一点。下面讲述HC-05配置过程。 1、引脚说明 1 1...