小梅哥FPGA进阶教程第九章 基于串口猎人软件的串口示波器
Posted 小梅哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小梅哥FPGA进阶教程第九章 基于串口猎人软件的串口示波器相关的知识,希望对你有一定的参考价值。
九、基于串口猎人软件的串口示波器
1、实验介绍
本实验,为芯航线开发板的综合实验,该实验利用芯航线开发板上的ADC、独立按键、UART等外设,搭建了一个具备丰富功能的数据采集卡,芯航线开发板负责进行数据的采集并将数据通过串口发送到PC机上,PC端,利用强大的串口调试工具——串口猎人,来实现数据的接收分析,并将数据分别以波形、码表、柱状图的形式动态显示出来,以让使用者能够直观的看到ADC采集到的信号细节。同时,用户也可以使用串口猎人通过串口给下位机(FPGA)发送指令,下位机将对接收到的指令进行解码,然后依据解码结果来配置FPGA中各个子模块的控制寄存器,以实现通过串口控制FPGA中子模块工作状态的功能。
本实验中,涉及到的应用模块和知识点如下所示:
串口收发模块的设计和使用;
串口收发模块仿真模型的设计;
串口简单数据帧的解码;
串口帧转Memory Mapped总线的设计;
Memory Mapped Slave模块的设计;
线性序列机设计思想的应用(ADC驱动);
独立按键消抖的分析与实现;
直接数字频率合成(DDS)的设计与实现;
使能时钟对系统间模块协调工作的重要性;
串口猎人的详细使用;
完整系统的仿真验证设计;
头文件在设计中的运用;
Quartus II软件中可定制化存储器ROM的使用;
本实验不仅注重可综合的代码编写,同时更注重代码的仿真验证。通过仿真,我们能够寻找设计中可能存在的问题并修正。最终,在整个系统仿真无误的基础上,下载到开发板上一次性成功。
2、系统结构
下图为本设计的框架结构图:
系统采用模块化设计,在模块划分的过程中,重点考虑了系统的可扩展性,下表为对系统中各模块功能的简单介绍。
系统中各端口和信号的功能介绍如下:
本实验为综合性实验,代码量较大,因此这里只针对部分代码进行讲解。如果文档中没有讲到的内容,大家可以参看代码注释。
模块详解
3.1 Tx_Bps_Gen
Tx_Bps_Gen为发送波特率生成模块,每当有Byte_En信号到来时,即开始产生发送一个完整字节的数据需要的完整波特率时钟信号。
本设计,波特率支持9600bps到921600bps。例如,需要产生的波特率时钟为9600bps,即波特率时钟频率为9600Hz,周期为104.17us。生成9600Hz波特率时钟的核心思想就是对系统时钟进行计数,这里设定系统时钟为50MHz,则一个时钟的周期为20ns,我们只需要对系统时钟计数5208次,每计数5208次产生一个时钟周期的高电平脉冲,即可实现生成9600Hz波特率时钟的功能。相应代码如下所示:
018 parameter system_clk = 50_000_000; /*输入时钟频率设定,默认50M*/ 019 020 /*根据输入时钟频率计算生成各波特率时分频计数器的计数最大值*/ 021 localparam bps9600 = system_clk/9600 - 1; 022 localparam bps19200 = system_clk/19200 - 1; 023 localparam bps38400 = system_clk/38400 - 1; 024 localparam bps57600 = system_clk/57600 - 1; 025 localparam bps115200 = system_clk/115200 - 1; 026 localparam bps230400 = system_clk/230400 - 1; 027 localparam bps460800 = system_clk/460800 - 1; 028 localparam bps921600 = system_clk/921600 - 1; 029 030 reg [31:0]BPS_PARA;/*波特率分频计数器的计数最大值*/ 031 032 always@(posedge Clk or negedge Rst_n) 033 if(!Rst_n)begin 034 BPS_PARA <= bps9600;/*复位时波特率默认为9600bps*/ 035 end 036 else begin 037 case(Baud_Set)/*根据波特率控制信号选择不同的波特率计数器计数最大值*/ 038 3\'d0: BPS_PARA <= bps9600; 039 3\'d1: BPS_PARA <= bps19200; 040 3\'d2: BPS_PARA <= bps38400; 041 3\'d3: BPS_PARA <= bps57600; 042 3\'d4: BPS_PARA <= bps115200; 043 3\'d5: BPS_PARA <= bps230400; 044 3\'d6: BPS_PARA <= bps460800; 045 3\'d7: BPS_PARA <= bps921600; 046 default: BPS_PARA <= bps9600; 047 endcase 048 end 049 050 //========================================================= 051 reg[12:0]Count; 052 053 reg n_state; 054 localparam IDEL_1 = 1\'b0, 055 SEND = 1\'b1; 056 057 reg BPS_EN; 058 059 /*-------波特率时钟生成控制逻辑--------------*/ 060 always@(posedge Clk or negedge Rst_n) 061 if(!Rst_n)begin 062 BPS_EN <= 1\'b0; 063 n_state <= IDEL_1; 064 end 065 else begin 066 case(n_state) 067 IDEL_1: 068 if(Byte_En)begin/*检测到字节发送使能信号,则启动波特率生成进程,同时进入发送状态*/ 069 BPS_EN <= 1\'b1; 070 n_state <= SEND; 071 end 072 else begin 073 n_state <= IDEL_1; 074 BPS_EN <= 1\'b0; 075 end 076 SEND: 077 if(Tx_Done == 1)begin/*发送完成,关闭波特率生成进程,回到空闲状态*/ 078 BPS_EN <= 1\'b0; 079 n_state <= IDEL_1; 080 end 081 else begin 082 n_state <= SEND; 083 BPS_EN <= 1\'b1; 084 end 085 default:n_state <= IDEL_1; 086 endcase 087 end 088 089 /*-------波特率时钟生成定时器--------------*/ 090 always@(posedge Clk or negedge Rst_n) 091 if(!Rst_n) 092 Count <= 13\'d0; 093 else if(BPS_EN == 1\'b0) 094 Count <= 13\'d0; 095 else begin 096 if(Count == BPS_PARA) 097 Count <= 13\'d0; 098 else 099 Count <= Count + 1\'b1; 100 end 101 102 /*输出数据接收采样时钟*/ 103 //----------------------------------------------- 104 always @(posedge Clk or negedge Rst_n) 105 if(!Rst_n) 106 Bps_Clk <= 1\'b0; 107 else if(Count== 1) 108 Bps_Clk <= 1\'b1; 109 else 110 Bps_Clk <= 1\'b0;
第18行“parameter system_clk = 50_000_000;”,这里用一个全局参数定义了系统时钟,暂时设定为50M,可根据实际使用的板卡上的工作时钟进行修改。
所谓波特率生成,就是用一个定时器来定时,产生频率与对应波特率时钟频率相同的时钟信号。例如,我们使用波特率为115200bps,则我们需要产生一个频率为115200Hz的时钟信号。那么如何产生这样一个115200Hz的时钟信号呢?这里,我们首先将115200Hz时钟信号的周期计算出来,1秒钟为1000_000_000ns,因此波特率时钟的周期Tb= 1000000000/115200 =8680.6ns,即115200信号的一个周期为8680.6ns,那么,我们只需要设定我们的定时器定时时间为8680.6ns,每当定时时间到,产生一个系统时钟周期长度的高脉冲信号即可。系统时钟频率为50MHz,即周期为20ns,那么,我们只需要计数8680/20个系统时钟,就可获得8680ns的定时,即bps115200=Tb/Tclk - 1=Tb*fclk - 1=fclk/115200-1。相应的,其它波特率定时值的计算与此类似,这里小梅哥就不再一一分析。20行至28行为波特率定时器定时值的计算部分。
为了能够通过外部控制波特率,设计中使用了一个3位的波特率选择端口:Baud_Set。通过给此端口不同的值,就能选择不同的波特率,此端口控制不同波特率的原理很简单,就是一个多路选择器,第32行至第48行即为此多路选择器的控制代码, Baud_Set的值与各波特率的对应关系如下:
000 : 9600bps;
001 : 19200bps;
010 :38400bps;
011 :57600bps;
100 :115200bps;
101 :230400bps;
110 :460800bps;
111 :921600bps;
3.2 Uart_Byte_Tx
Uart_Byte_Tx为字节发送模块,该模块在波特率时钟的节拍下,依照UART通信协议发送一个完整的字节的数据。当一个字节发送完毕后,Tx_Done产生一个高脉冲信号,以告知其它模块或逻辑一个字节的数据已经传输完成,可以开始下一个字节的发送了。其发送一个字节数据的实现代码如下:
33 /*计数波特率时钟,11个波特率时钟为一次完整的数据发送过程*/ 34 always@(posedge Clk or negedge Rst_n) 35 if(!Rst_n) 36 Bps_Clk_Cnt <= 4\'b0; 37 else if(Bps_Clk_Cnt == 4\'d11) 38 Bps_Clk_Cnt <= 4\'b0; 39 else if(Bps_Clk) 40 Bps_Clk_Cnt <= Bps_Clk_Cnt + 1\'b1; 41 else 42 Bps_Clk_Cnt <= Bps_Clk_Cnt; 43 44 /*生成数据发送完成标志信号*/ 45 always@(posedge Clk or negedge Rst_n) 46 if(!Rst_n) 47 Tx_Done <= 1\'b0; 48 else if(Bps_Clk_Cnt == 4\'d11) 49 Tx_Done <= 1\'b1; 50 else 51 Tx_Done <= 1\'b0; 52 53 /*在开始发送起始位的时候就读取并寄存Data_Byte,以免Data_Byte变化导致数据的丢失*/ 54 always@(posedge Clk or negedge Rst_n) 55 if(!Rst_n) 56 Data = 8\'d0; 57 else if(Bps_Clk & Bps_Clk_Cnt == 4\'d1) 58 Data <= Data_Byte; 59 else 60 Data <= Data; 61 62 /*发送数据序列机*/ 63 always@(posedge Clk or negedge Rst_n) 64 if(!Rst_n) 65 Rs232_Tx <= 1\'b1; 66 else begin 67 case(Bps_Clk_Cnt) 68 4\'d1: Rs232_Tx <= 1\'b0; 69 4\'d2: Rs232_Tx <= Data[0]; 70 4\'d3: Rs232_Tx <= Data[1]; 71 4\'d4: Rs232_Tx <= Data[2]; 72 4\'d5: Rs232_Tx <= Data[3]; 73 4\'d6: Rs232_Tx <= Data[4]; 74 4\'d7: Rs232_Tx <= Data[5]; 75 4\'d8: Rs232_Tx <= Data[6]; 76 4\'d9: Rs232_Tx <= Data[7]; 77 4\'d10: Rs232_Tx <= 1\'b1; 78 default:Rs232_Tx <= 1\'b1; 79 endcase 80 end
在UART协议中,一个完整的字节包括一位起始位、8位数据位、一位停止位即总共十位数据,那么,要想完整的实现这十位数据的发送,就需要11个波特率时钟脉冲,如下所示:
BPS_CLK信号的第一个上升沿到来时,字节发送模块开始发送起始位,接下来的2到9个上升沿,发送8个数据位,第10个上升沿到第11个上升沿为停止位的发送。
3.3 Uart_Byte_Rx
单个串口接收模块中实现串口数据接收的主要代码如下所示:
025 always @ (posedge Clk or negedge Rst_n) 026 if(!Rst_n) begin 027 Rs232_Rx0 <= 1\'b0; 028 Rs232_Rx1 <= 1\'b0; 029 Rs232_Rx2 <= 1\'b0; 030 Rs232_Rx3 <= 1\'b0; 031 end 032 else begin 033 Rs232_Rx0 <= Rs232_Rx; 034 Rs232_Rx1 <= Rs232_Rx0; 035 Rs232_Rx2 <= Rs232_Rx1; 036 Rs232_Rx3 <= Rs232_Rx2; 037 end 038 039 wire neg_Rs232_Rx= Rs232_Rx3 & Rs232_Rx2 & ~Rs232_Rx1 & ~Rs232_Rx0; 040 041 assign Byte_En = neg_Rs232_Rx; 042 043 /*----------计数采样时钟--------------*/ 044 /*9倍波特率采样时钟,故一个完整的接收过程有90个波特率时钟*/ 045 reg[6:0]Sample_Clk_Cnt; 046 always @ (posedge Clk or negedge Rst_n) 047 if(!Rst_n) 048 Sample_Clk_Cnt <= 7\'d0; 049 else if(Sample_Clk)begin 050 if(Sample_Clk_Cnt == 7\'d89) 051 Sample_Clk_Cnt <= 7\'d0; 052 else 053 Sample_Clk_Cnt <= Sample_Clk_Cnt + 1\'b1; 054 end 055 else 056 Sample_Clk_Cnt <= Sample_Clk_Cnt; 057 058 reg [1:0]Start_Bit; /*起始位,这里虽然定义,但并未使用该位来判断接收数据的正确性,即默认接收都是成功的*/ 059 reg [1:0]Stop_Bit; /*停止位,这里虽然定义,但并未使用该位来判断接收数据的正确性,即默认接收都是成功的*/ 060 reg [1:0] Data_Tmp[7:0];/*此部分较为复杂,请参看说明文档中相关解释*/ 061 062 always @ (posedge Clk or negedge Rst_n) 063 if(!Rst_n)begin 064 Data_Tmp[0] <= 2\'d0; 065 Data_Tmp[1] <= 2\'d0; 066 Data_Tmp[2] <= 2\'d0; 067 Data_Tmp[3] <= 2\'d0; 068 Data_Tmp[4] <= 2\'d0; 069 Data_Tmp[5] <= 2\'d0; 070 Data_Tmp[6] <= 2\'d0; 071 Data_Tmp[7] <= 2\'d0; 072 Start_Bit <= 2\'d0; 073 Stop_Bit <= 2\'d0; 074 end 075 else if(Sample_Clk)begin 076 case(Sample_Clk_Cnt) 077 7\'d0: 078 begin 079 Data_Tmp[0] <= 2\'d0; 080 Data_Tmp[1] <= 2\'d0; 081 Data_Tmp[2] <= 2\'d0; 082 Data_Tmp[3] <= 2\'d0; 083 Data_Tmp[4] <= 2\'d0; 084 Data_Tmp[5] <= 2\'d0; 085 Data_Tmp[6] <= 2\'d0; 086 Data_Tmp[7] <= 2\'d0; 087 Start_Bit <= 2\'d0; 088 Stop_Bit <= 2\'d0; 089 end 090 7\'d3,7\'d4,7\'d5: Start_Bit <= Start_Bit + Rs232_Rx; 091 7\'d12,7\'d13,7\'d14:Data_Tmp[0] <= Data_Tmp[0] + Rs232_Rx; 092 7\'d21,7\'d22,7\'d23:Data_Tmp[1] <= Data_Tmp[1] + Rs232_Rx; 093 7\'d30,7\'d31,7\'d32:Data_Tmp[2] <= Data_Tmp[2] + Rs232_Rx; 094 7\'d39,7\'d40,7\'d41:Data_Tmp[3] <= Data_Tmp[3] + Rs232_Rx; 095 7\'d48,7\'d49,7\'d50:Data_Tmp[4] <= Data_Tmp[4] + Rs232_Rx; 096 7\'d57,7\'d58,7\'d59:Data_Tmp[5] <= Data_Tmp[5] + Rs232_Rx; 097 7\'d66,7\'d67,7\'d68:Data_Tmp[6] <= Data_Tmp[6] + Rs232_Rx; 098 7\'d75,7\'d76,7\'d77:Data_Tmp[7] <= Data_Tmp[7] + Rs232_Rx; 099 7\'d84,7\'d85,7\'d86:Stop_Bit <= Stop_Bit + Rs232_Rx; 100 default:; 101 endcase 102 end 103 else ;
根据串口发送协议,一个字节的数据传输是以一个波特率周期的低电平作为起始位的,因此,成功接收UART串口数据的核心就是准确检测起始位。由于外部串口发送过来的数据与接收系统不在同一个时钟域,因此不能直接使用该信号的下降沿来作为检测标志,我们需要在fpga中,采用专用的边沿检测电路来实现,第25行至37行通过四个移位寄存器,存储连续四个时钟上升沿时外部发送数据线的状态,第39行通过比较前两个时钟时数据线的状态与后两个时钟时数据线的状态,来得到该数据线的准确下降沿,以此保证起始位的准确检测。
在简单的串口接收中,我们通常选取一位数据的中间时刻进行采样,因为此时数据最稳定,但是在工业环境中,存在着各种干扰,在干扰存在的情况下,如果采用传统的中间时刻采样一次的方式,采样结果就有可能受到干扰而出错。为了滤除这种干扰,这里采用多次采样求概率的方式。如下图,将一位数据平均分成9个时间段,对位于中间的三个时间段进行采样。然后对三个采样结果进行统计判断,如果某种电平状态在三次采样结果中占到了两次及以上,则可以判定此电平状态即为正确的数据电平。例如4、5、6时刻采样结果分别为1、1、0,那么就取此位解码结果为1,否则,若三次采样结果为0、1、0,则解码结果就为0。
因为采样一位需要9个时钟上升沿,因此,采样一个完整的数据需要10*9,即90个时钟上升沿,这里,采样时钟为波特率时钟的9倍。产生采样时钟的部分代码如下所示:
089 /*-------波特率时钟生成定时器--------------*/ 090 always@(posedge Clk or negedge Rst_n) 091 if(!Rst_n) 092 Count <= 10\'d0; 093 else if(BPS_EN == 1\'b0) 094 Count <= 10\'d0; 095 else begin 096 if(Count == BPS_PARA) 097 Count <= 10\'d0; 098 else 099 Count <= Count + 1\'b1; 100 end 101 102 //===================================================== 103 /*输出数据接收采样时钟*/ 104 always @(posedge Clk or negedge Rst_n) 105 if(!Rst_n) 106 Sample_Clk <= 1\'b0; 107 else if(Count== 1) 108 Sample_Clk <= 1\'b1; 109 else 110 Sample_Clk <= 1\'b0;
这里,BPS_PARA的计算原理和前面Tx_Bps_Gen模块中的BPS_PARA的计算原理一致,不过这里,因为采样时钟为波特率时钟的9倍,所以,BPS_PARA为Tx_Bps_Gen模块中的BPS_PARA的1/9。计算BPS_PARA的相关代码如下:
018 parameter system_clk = 50_000_000; /*输入时钟频率设定,默认50M*/ 019 020 /*根据输入时钟频率计算生成各波特率时分频计数器的计数最大值*/ 021 localparam bps9600 = system_clk/9600/小梅哥FPGA进阶教程第十四章 TFT屏显示图片09B-独立按键消抖实验02——小梅哥FPGA设计思想与验证方法视频教程配套文档
02-FPGA设计流程介绍——小梅哥FPGA设计思想与验证方法视频教程配套文档
05-IP核应用之计数器——小梅哥FPGA设计思想与验证方法视频教程配套文档