发 帖  
张飞软硬开源基于STM32 BLDC直流无刷电机驱动器开发视频套件, 👉戳此立抢👈
基于ZX-2型实现通过串口控制FPGA中子模块工作状态的功能
2891 FPGA adc
分享

  
本实验,为ZX-2开发板的综合实验,该实验利用ZX-2开发板上的ADC、独立按键、UART等外设,搭建了一个具备丰富功能的数据采集卡,ZX-2开发板负责进行数据的采集并将数据通过串口发送到PC机上,PC端,利用强大的串口调试工具——串口猎人,来实现数据的接收分析,并将数据分别以波形、码表、柱状图的形式动态显示出来,以让使用者能够直观的看到ADC采集到的信号细节。同时,用户也可以使用串口猎人通过串口给下位机(FPGA)发送指令,下位机将对接收到的指令进行解码,然后依据解码结果来配置FPGA中各个子模块的控制寄存器,以实现通过串口控制FPGA中子模块工作状态的功能。
  
本实验中,涉及到的应用模块和知识点如下所示:
串口收发模块的设计和使用;
串口收发模块仿真模型的设计;
串口简单数据帧的解码;
串口帧转Memory Mapped总线的设计;
Memory MappedSlave模块的设计;
线性序列机设计思想的应用(ADC驱动);
独立按键消抖的分析与实现;
直接数字频率合成(DDS)的设计与实现;
使能时钟对系统间模块协调工作的重要性;
串口猎人的详细使用;
完整系统的仿真验证设计;
头文件在设计中的运用;
Quartus II软件中可定制化存储器ROM的使用;
  
本实验不仅注重可综合的代码编写,同时更注重代码的仿真验证。通过仿真,我们能够寻找设计中可能存在的问题并修正。最终,在整个系统仿真无误的基础上,下载到开发板上一次性成功。
下图为本设计的框架结构图:





系统采用模块化设计,在模块划分的过程中,重点考虑了系统的可扩展性,下表为对系统中各模块功能的简单介绍。


                                                                                                     基于串口的虚拟示波器模块功能介绍                  
  模块名
  模块功能
  Uart_Byte_Rx
  串口字节接收模块,负责进行PC机发送指令数据的接收工作;
  CMD
  串口指令解析与控制模块,该模块通过解码PC机发送的指令数据序列,获取控制命令,转换为Memory Mapped Master总线,以实现上位机通过串口控制下位机(FPGA)中各模块寄存器的功能;
  Sample_Ctrl
  ADC采样速率控制模块,该模块通过控制使能ADC采数的速率来调整ADC的采样率;
  UART_Byte_Tx
  串口字节发送模块,该模块负责将需要发送的字节数据通过UART协议发送出去(发送到上位机)
  UART_Tx_Ctrl
  串口发送控制模块,根据数据有效标志信号来控制串口发送模块将数据出去
  tlc549_Driver
  TLC549模数转换(ADC)芯片驱动模块,负责驱动TLC549进行模数转换,并将转换结果以字节格式输出。
  DDS
  DDS信号发生器模块,该模块生成一个固定频率的正弦波,在没有外部信号发生器的情况下,可通过该模块生成正弦波,并将输出数据接入到采样部分,以模拟采集正弦波信号并在上位机显示
  Mux1
  AD采集结果和DDS数据结果多路选择模块,在测试时,用户可以选择需要采样的数据为内部信号发生器生成的正弦波数据(供演示用)或者ADC采集到的电压结果(实际测试)
  Mux2
  AD采集结果和DDS数据结果有效标志信号选择多路器,在ADC和DDS都处于工作的状态下,通过该多路器来实现数据有效标志信号的选择
  normal_keys_detect
  独立按键消抖模块,通过该模块来实现AD数据和DDS数据的切换,即在演示和实际测试中进行切换。
   

系统中各端口和信号的功能介绍如下:
  

                                                                                                                                                           基于串口的虚拟示波器端口和信号介绍
  端口
  位宽
  端口功能描述
  Clk
  1
  系统时钟,50M
  Rst_n
  1
  全局复位,低电平复位
  Rs232_Rx
  1
  串口接收引脚
  Rs232_Tx
  1
  串口发送引脚
  Key_in
  3
  按键输入
  ADC_Din
  1
  ADC芯片数据引脚
  ADC_Clk
  1
  ADC接口时钟
  ADC_Cs_n
  1
  ADC芯片片选信号
  内部信号
  位宽
  信号功能描述
  Baud_Set
  3
  波特率选择信号,对应波特率如下:
000: 9600bps
001: 19200bps
010: 38400bps
011: 57600bps
100: 115200bps
101: 230400bps
110: 460800bps
111: 921600bps
  Rx_Byte
  8
  串口接收到的字节数据
  Rx_Int
  1
  串口接收字节成功标志信号,每次接收成功,此信号产生一个时钟周期的高脉冲
  Byte_En
  1
  串口字节数据发送使能信号,每一次一个时钟周期的高脉冲使能一次串口字节发送。
  Tx_Done
  1
  串口发送字节数据完成标志,每个字节的数据发送完成,此信号产生一个时钟周期的高脉冲
  ADC_En
  1
  ADC单次转换使能信号,每一次一个时钟周期的高脉冲使能一次AD转换
  ADC_Data
  8
  ADC采样结果
  ADC_Flag
  1
  ADC转换结果有效标志,每次转换结果有效后,此信号产生一个时钟周期的高脉冲
  ADC_Busy
  1
  ADC工作忙标志,高电平表明ADC正处于转换状态,新的转换命令将被忽略,只有当该信号为低电平时,外部控制逻辑才可触发新的转换
  m_wr
  1
  主机写数据的请求,为1表明有写请求
  m_addr
  8
  主机写数据的地址
  m_wrdata
  16
  主机写数据
  DDS_Data
  8
  DDS生成的波形数据
  DDS_Flag
  1
  DDS采样使能标志,该信号由DDS采样速率控制进程产生
  Data_Flag
  1
  数据有效标志(根据用户按键进行选择DDS_Flag 或ADC_Flag)
  Data_Byte
  8
  串口发送字节数据
  Data_Sel
  1
  数据选择信号(选择串口发送DDS_Data 或 ADC_Data),为1选择DDS_Data,为0选择ADC_Data
  Flag_Sel
  1
  数据有效标志信号选择信号(选择DDS_Flag 或ADC_Flag)为1选择DDS_ Flag,为0选择ADC_ Flag
  Key_Flag
  1
  按键检测成功标志信号,每次按键检测成功该信号产生一个时钟周期的高脉冲信号
  Key_Value
  3
  按键检测结果
   
本实验为综合性实验,代码量较大,因此这里只针对部分代码进行讲解。如果文档中没有讲到的内容,大家可以参看代码注释。
  
1.1Tx_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;



1.2Uart_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个上升沿为停止位的发送。
  
小梅哥和你一起深入学习FPGA
0
2019-5-9 17:57:42   评论 分享淘帖 邀请回答
24个回答
单个串口接收模块中实现串口数据接收的主要代码如下所示:




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/9 - 1;
022     localparam bps19200 = system_clk/19200/9 - 1;
023     localparam bps38400 = system_clk/38400/9 - 1;
024     localparam bps57600 = system_clk/57600/9 - 1;
025     localparam bps115200 = system_clk/115200/9 - 1;
026     localparam bps230400 = system_clk/230400/9 - 1;
027     localparam bps460800 = system_clk/460800/9 - 1;
028     localparam bps921600 = system_clk/921600/9 - 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;/*异常情况,恢复到9600的波特率*/
047         endcase
048     end
 
2019-5-10 04:56:24 评论

举报

 
CMD

CMD模块为串口数据帧接收与解析模块,该模块负责对串口接收到的每一帧的数据进行解码判断,并从数据帧中提取出地址字节和数据字节。最后将地址字节和数据字节转换为类似于Avalon-MM形式的总线,以实现对其它模块的控制寄存器的读写,从而实现通过串口控制FPGA中各个模块工作的目的。
在工业应用中,串口指令大多以数据帧的格式出现,包含帧头、帧长、帧命令、帧内容、校验和以及帧尾,不会只是单纯的传输数据。在这个实验中,小梅哥也使用了数据帧的形式来通过上位机向FPGA发送命令,不过这里我使用的帧格式非常简单,帧格式以帧头、帧长、帧内容以及帧尾组成,忽略了校验部分内容,帧头、帧长以及帧尾内容都是固定的,不固定的只是帧内容,以下为小梅哥的设计中一帧数据的格式:




帧头
  帧长
  地址
  数据
  数据
  帧尾
  0xAA
  0x03
  0xXX
  0xXX
  0xXX
  0x88




由于数据帧本身结构简单,因此数据帧的解析过程也相对简洁,以下为小梅哥的数据帧解析状态机设计,该状态机分为帧头解析、帧长解析、数据接收以及帧尾解析。默认时,状态机处于帧头解析状态,一旦出现帧头数据,则跳转到帧长接收状态,若下一个字节为帧长数据(这里严格意义上并不能算作帧长,因为长度固定,充其量只能算作帧头,读者不须过分纠结),则开始连续接收三个字节的数据,若非指定的帧长内容,则表明这是一次无关传输,状态机将返回到帧头解析状态继续等待新的数据帧到来。在帧尾解析状态,若解析到的数据并非指定的帧尾数据,则表明此次数据帧非有效帧,则将此帧已解析到的数据舍弃。若为帧尾数据,则解析成功,产生命令有效标志信号(CMD_Valid),Memory Mapped 总线进程在检测到此命令有效信号后,即产生写外设寄存器操作。











命令解析的状态机实现代码如下所示:




   017     localparam
018         Header = 8'hAA, /*帧头*/
019         Length = 8'd3,      /*帧长*/
020         Tail   = 8'h88; /*帧尾*/
021
022 /*----------状态定义-----------------*/    
023     localparam
024         CMD_HEADER = 6'b00_0001,
025         CMD_LENGTH = 6'b00_0010,
026         CMD_DATAA  = 6'b00_0100,
027         CMD_DATAB  = 6'b00_1000,
028         CMD_DATAC  = 6'b01_0000,
029         CMD_TAIL   = 6'b10_0000;
030    
031    
032     always@(posedge Clk or negedge Rst_n)
033     if(!Rst_n)begin
034         reg_CMD_DATA <= 24'd0;
035         CMD_Valid <= 1'b0;
036         state <= CMD_HEADER;
037     end
038     else if(Rx_Int)begin
039         case(state)
040             CMD_HEADER: /*解码帧头数据*/
041                 if(Rx_Byte == Header)
042                     state <= CMD_LENGTH;
043                 else
044                     state <= CMD_HEADER;
045            
046             CMD_LENGTH: /*解码帧长数据*/
047                 if(Rx_Byte == Length)
048                     state <= CMD_DATAA;
049                 else
050                     state <= CMD_HEADER;
051            
052             CMD_DATAA:  /*解码数据A*/
053                 begin
054                     reg_CMD_DATA[23:16] <= Rx_Byte;
055                     state <= CMD_DATAB;
056                 end
057                
058             CMD_DATAB:  /*解码数据B*/
059                 begin
060                     reg_CMD_DATA[15:8] <= Rx_Byte;
061                     state <= CMD_DATAC;            
062                 end
063                
064             CMD_DATAC:  /*解码数据C*/
065                 begin
066                     reg_CMD_DATA[7:0] <= Rx_Byte;
067                     state <= CMD_TAIL;             
068                 end
069
070             CMD_TAIL:   /*解码帧尾数据*/
071                 if(Rx_Byte == Tail)begin
072                     CMD_Valid <= 1'b1;  /*解码成功,发送解码数据有效标志*/
073                     state <= CMD_HEADER;
074                 end
075                 else begin
076                     CMD_Valid <= 1'b0;
077                     state <= CMD_HEADER;
078                 end
079             default:;
080         endcase
081     end
082     else begin
083         CMD_Valid <= 1'b0;
084         reg_CMD_DATA <= reg_CMD_DATA;
085     end
   



第23行到第29行为状态机编码,这里采用独热码的编码方式。状态机的编码方式有很多种,包括二进制编码、独热码、格雷码等,二进制编码最接近我们的常规思维,但是在FPGA内部,其译码电路较为复杂,且容易出现竞争冒险,导致使用二进制编码的状态机最高运行速度相对较低。独热码的译码电路最简单,因此采用独热码方式编码的状态机运行速度较二进制编码方式高很多,但是编码会占用较多的数据位宽。格雷码以其独特的编码特性,能够非常完美的解决竞争冒险的问题,使状态机综合出来的电路能够运行在很高的时钟频率,但是格雷码编码较为复杂,尤其对于位宽超过4位的格雷码,编码实现较二进制编码和独热码编码要复杂的多。这里,详细的关于状态机的编码问题,小梅哥不做过多的讨论,更加细致的内容,请大家参看夏宇闻老师经典书籍《Verilog数字系统设计教程》中第12章相关内容。


Memory Mapped 总线进程根据命令有效标志信号产生写外设寄存器操作的相关代码如下所示:




   087 /*------驱动总线写外设寄存器--------*/    
088     always@(posedge Clk or negedge Rst_n)
089     if(!Rst_n)begin
090         m_wr <= 1'b0;
091         m_addr <= 8'd0;
092         m_wrdata <= 16'd0;
093     end
094     else if(CMD_Valid)begin
095         m_wr <= 1'b1;
096         m_addr <= reg_CMD_DATA[23:16];
097         m_wrdata <= reg_CMD_DATA[15:0];
098     end
099     else begin
100         m_wr <= 1'b0;
101         m_addr <= m_addr;
102         m_wrdata <= m_wrdata;  
103     end
   



在本系统中,需要通过该MemoryMapped 总线配置的寄存器总共有12个,分别位于ADC采样速率控制模块(Sample_Ctrl)、串口发送控制模块(UART_Tx_Ctrl)、直接数字频率合成信号发生器模块(DDS)中,各寄存器地址分配及物理意义如下所示:




   地址
   寄存器名称
   寄存器宽度
   寄存器功能
     0x01
   ADC_Sample_Cnt_Max_L
   16
   ADC采样率设置分频计数器计数最大值的低16位
     0x02
   ADC_Sample_Cnt_Max_H
   16
   ADC采样率设置分频计数器计数最大值的高16位
     0x03
   ADC_Sample_En
   1
   ADC采样使能寄存器
     0x04
   En_Tx
   1
   串口发送使能寄存器
     0x05
   reg_Baud_Set
   2
   串口发送波特率设置寄存器
     0x06
   DDS_En
   1
   DDS使能寄存器
     0x07
   reg_Fword_H
   16
   DDS频率控制字高16位
     0x08
   reg_Fword_L
   16
   DDS频率控制字低16位
     0x09
   reg_Pword
   12
   DDS相位控制字
     0x0a
   DDS_Sample_Cnt_Max_L
   16
   DDS采样率设置分频计数器计数最大值的低16位
     0x0b
   DDS_Sample_Cnt_Max_H
   16
   DDS采样率设置分频计数器计数最大值的高16位
     0x0c
   DDS_Sample_En
   1
   DDS采样使能寄存器
   



指令使用说明:




   操作
   指令
     使能DDS生成数据
   AA 03 06 00 01 88
     停止DDS生成数据
   AA 03 06 00 00 88
     使能采样DDS数据
   AA 03 0C 00 01 88
     停止采样DDS数据
   AA 03 0C 00 00 88
     使能串口发送
   AA 03 04 00 01 88
     停止串口发送
   AA 03 04 00 00 88
     使能ADC采样
   AA 03 03 00 01 88
     停止ADC采样
   AA 03 03 00 00 88
     写DDS频率控制字高16位
   AA 03 07 XX  XX 88
     写DDS频率控制字低16位
   AA 03 08 XX  XX 88
XX_XX_XX_XX = 232*Fout/50_000_000
 
     写DDS相位控制字
   AA 03 09 0X  XX 88
     采样DDS输出数据的采样速率控制高16位
   AA 03 0B XX  XX 88
     采样DDS输出数据的采样速率控制低16位
   AA 03 0A XX  XX 88
XX_XX_XX_XX = 50_000_000/Fs - 1
 
     ADC采样速率控制高16位
   AA 03 02 XX  XX 88
     ADC采样速率控制低16位
   AA 03 01 XX XX 88
XX_XX_XX_XX = 50_000_000/Fs - 1
 
     设置串口波特率
   AA 03 05 00  0X 88
X=
4’d0:9600bps;
4’d1:19200bps;
4’d2:38400bps;
4’d3:57600bps;
4’d4:115200bps;
4’d5:230400bps;
4’d6:460800bps;
4’d7:921600bps;
   



例如,系统在上电后,各个模块默认是没有工作的,要想在上位机上看到数据,就必须先通过上位机发送控制命令。因为系统上电后默认选择的数据通道为DDS生成的数据,为了以最快的方式在串口猎人上看到波形,一种可行的控制顺序如下所示:


使能DDS生成数据(AA 03 06 00 01 88) —> 使能采样DDS数据(AA 03 0C 00 01 88) —>使能串口发送数据(AA03 04 00 01 88),


这里,为了演示方便,因此在系统中对数据采样速率和DDS生成的信号的频率初始值都做了设置,因此不设置采样率和输出频率控制字这几个寄存器也能在串口猎人上接收到数据。


经过此操作后,串口猎人的接收窗口中就会不断的接收到数据了。当然,这离我们最终显示波形还有一段距离,这部分内容我将放到文档最后,以一次具体的使用为例,来step by step的介绍给大家。


 


关于Memory Mapped 总线如何实现各模块寄存器的配置,这里小梅哥以ADC采样控制模块Sample_Ctrl中三个寄存器的配置来进行介绍。Sample_Ctrl中三个寄存器的定义及配置代码如下所示:




   14      reg [15:0]ADC_Sample_Cnt_Max_L;/*采样分频计数器计数最大值的低16位,ADDR = 8'd1*/
15      reg [15:0]ADC_Sample_Cnt_Max_H;/*采样分频计数器计数最大值的高16位,ADDR = 8'd2*/
16      reg ADC_Sample_En;/*采样使能寄存器,ADDR = 8'd3*/
17
18  /*-------设置采样分频计数器计数最大值---------*/ 
19      always@(posedge Clk or negedge Rst_n)
20      if(!Rst_n)begin
21          ADC_Sample_Cnt_Max_H <= 16'd0;
22          ADC_Sample_Cnt_Max_L <= 16'd49999;/*默认设置采样率为1K*/
23      end
24      else if(m_wr && (m_addr == `ADC_S_Cnt_Max_L))//写采样分频计数器计数最大值的低16位
25          ADC_Sample_Cnt_Max_L <= m_wrdata;
26      else if(m_wr && (m_addr == `ADC_S_Cnt_Max_H))//写采样分频计数器计数最大值的高16位
27          ADC_Sample_Cnt_Max_H <= m_wrdata;
28      else begin
29          ADC_Sample_Cnt_Max_H <= ADC_Sample_Cnt_Max_H;
30          ADC_Sample_Cnt_Max_L <= ADC_Sample_Cnt_Max_L;
31      end
32     
33  /*---------写采样使能寄存器-------------*/
34      always@(posedge Clk or negedge Rst_n)
35      if(!Rst_n)
36          ADC_Sample_En <= 1'b0;
37      else if(m_wr && (m_addr == `ADC_Sample_En))
38          ADC_Sample_En <= m_wrdata[0];
39      else
40          ADC_Sample_En <= ADC_Sample_En;
 
   



采样率的控制采用定时器的方式实现。使用一个计数器持续对系统时钟进行计数,一旦计数满设定时间,则产生一个时钟周期的高脉冲信号,作为ADC采样使能信号。这里,系统时钟周期为20ns,因此,如果要实现采样1K的采样率(采样周期为1ms),则需对系统时钟计数50000次;若实现20K的采样率(采样周期为50us),则需要对系统时钟计数2500次。以此类推,可知改变采样率的实质就是改变计数器的计数最大值,因此,我们要想改变采样速率,也只需要改变采样率控制计数器的计数最大值即可。所以这里,我们设计了两个16位的寄存器,分别存储采样率控制计数器的计数最大值的低16位和高16位,如第14、15行所示。当我们需要修改ADC的采样率时,直接通过串口发送指令,修改这两个寄存器中的内容即可。


 


这里,小梅哥使用自己设计的一个山寨版MemoryMapped 总线来配置各个寄存器,该总线包含三组信号,分别为:


写使能信号:m_wr;


写地址信号:m_addr;


写数据信号:m_wrdata;
那么,这三组信号是如何配合工作的呢?我们以配置ADC_Sample_Cnt_Max_H和ADC_Sample_Cnt_Max_L这两个寄存器来进行介绍,这里再贴上这部分代码:


   18  /*-------设置采样分频计数器计数最大值---------*/ 
19      always@(posedge Clk or negedge Rst_n)
20      if(!Rst_n)begin
21          ADC_Sample_Cnt_Max_H <= 16'd0;
22          ADC_Sample_Cnt_Max_L <= 16'd49999;/*默认设置采样率为1K*/
23      end
24      else if(m_wr && (m_addr == `ADC_S_Cnt_Max_L))//写采样分频计数器计数最大值的低16位
25          ADC_Sample_Cnt_Max_L <= m_wrdata;
26      else if(m_wr && (m_addr == `ADC_S_Cnt_Max_H))//写采样分频计数器计数最大值的高16位
27          ADC_Sample_Cnt_Max_H <= m_wrdata;
28      else begin
29          ADC_Sample_Cnt_Max_H <= ADC_Sample_Cnt_Max_H;
30          ADC_Sample_Cnt_Max_L <= ADC_Sample_Cnt_Max_L;
31      end
 
   



复位时,让{ ADC_Sample_Cnt_Max_H,ADC_Sample_Cnt_Max_L }为49999,即设置默认采样率为1K,每当m_wr为高且m_addr等于ADC_Sample_Cnt_Max_H寄存器的地址时,就将m_wrdata的数据更新到ADC_Sample_Cnt_Max_H寄存器中,同理,若当m_wr为高且m_addr等于ADC_Sample_Cnt_Max_L寄存器的地址时,就将m_wrdata的数据更新到ADC_Sample_Cnt_Max_L寄存器中。其他寄存器的配置原理与此相同,因此不再做阐述,相信大家举一反三,便可理解了。
2019-5-10 05:11:54 评论

举报

DDS基本原理

注:本文内容摘抄自周立功编写的教材《EDA实验与实践》196~197页。
 
DDS(Direct Digital Synthesizer)即数字合成器,是一种新型的频率合成技术,具有相对带宽大,频率转换时间短、分辨率高和相位连续性好等优点,很容易实现频率,相位,和幅度的数控调制,广泛应用于通信领域。
DDS的基本结构图如图1所示:





图1  DDS的基本结构图


主要由相位累加器,相位调制器,正弦数据表,和D/A转换器构成,相位累加器由N位加法器与N位寄存器构成。每来一个时钟,加法器就将频率控制字,与累加寄存器输出的相位数据相加,相加的结果又反馈至累加寄存器的数据输入端,以使加法器在下一个时钟脉冲的作用下继续与频率控制字相加,这样,相位累加器在时钟作用下,不断对频率控制字进行线性相位累加。由此可以看出,在每一个时钟脉冲输入时,相位累加器便把频率控制字累加一次。相位累加器输出的数据就是合成信号的相位,相位累加器的溢出频率,就是DDS输出的信号频率,用相位累加器输出的数据,作为波形存储器的相位采样地址,这样就可以把存储在波形存储器里的波形采样值经查表找出,完成相位到幅度的转换,波形存储器的付出送到D/A转换器,由D/A转换器将数字信号转换成模拟信号输出,DDS信号流程示意图如图4.51所示。







图2  DDS信号流程示意图


 


由于相位累加器为N位,相当于把正弦信号在相位上的精度定义为N位,(N的取值范围一般为24~32),所以其分辨率为1/2N,若系统时钟频率为Fclk,频率控制字fword为1,则输出频率为Fout=Fclk/2N,这个频率相当于“基频”,若fword为B,则输出频率为

当系统输入时钟频率,Fclk不变时,输出信号频率由频率控制字M所决定,由上式可得:



其中B为频率字,注意B要取整,有时会有误差,在本设计中,N取32位,系统时钟频率Fclk为120兆,


选取ROM的地址(即相位累加器的输出数据)时,可以间隔选通,相位寄存器输出的位数一般取10~16位,这种截取方法称为截断式用法,以减少ROM的容量,M太大会导致ROM容量的成倍上升,而输出精度受D/A位数的限制未有很大改善,在本设计中M取12位。


以上为周立功《EDA实验与实践》一书中对DDS原理的介绍


 


DDS原理再解释


上面的对DDS原理的解释,还是有部分同学反映不够直观,读完之后还是不明白DDS究竟是怎么控制频率和相位的,那么,这里小梅哥再用更加通俗的方式给大家讲解一下。


如图3,为一个完整周期的正弦信号的波形,总共有33个采样点,其中第1点和第33点的


值相同,第33点为下一个周期的起始点,因此,实际一个周期为32个采样点(1~32)。因为是在matlab中生成的,因此起始点为1,而不是我们常见的0,这里对我们理解DDS的原理没有任何影响,因此不必过多纠结。








图3  32个采样点的正弦信号波形



图4  16个采样点的正弦信号波形


 


我们要使用FPGA控制DAC来输出这样一个周期的正弦信号,每1ms输出一个数值。如果每个点都输出,则总共输出这一个完整的周期信号需要输出32个点,因此输出一个完整的信号需要32ms,则输出信号的频率为1000/32Hz。


假如,我们现在用这一组数据来输出一个2*(1000/32)Hz的正弦信号,因为输出信号频率为2*(1000/32)Hz,那么输出一个完整的周期的正弦波所需要的时间为32/2,即16ms,为了保证输出信号的周期为16ms,那么,我们就需要对我们的输出策略进行更改,上面输出周期为32ms的信号时,我们采用的为逐点输出的方式,以32个点来输出一个完整的正弦信号,而我们FPGA控制DAC输出信号的频率固定为1ms,因此,我们要输出周期为16ms的信号,只能输出16个点来表示一个完整的周期。我们这里选择以每隔一个点输出一个数据的方式,例如,我们可以选择输出(1、3、5、7……29、31)这些点,因为采用这些点,我们还是能够组成一个完整的周期的正弦信号,而输出时间缩短为一半,则频率提高了一倍。最终结果如上图4所示。


如果我们需要输出频率为(1/2)*(1000/32)Hz,即周期为64ms,则只需要以此组数据为基础,每2ms输出一个数据即可,例如第1ms和第2ms输出第一个点,第3ms和第4ms输出第二个点,以此类推,第63ms和第64ms输出第32个点,即可实现周期加倍,即频率减半的效果。


对于相位的调整,则更加简单,我们只需要在每个取样点的序号上加上一个偏移量,便可实现相位的控制。例如,上面默认的是第1ms时输出第一个点的数据,假如我们现在在第1ms时从第9个点开始输出,则将相位左移了90度,这就是控制相位的原理。


实现DDS输出时,将横坐标上的数据作为ROM的地址,纵坐标上的数据作为ROM的输出,那么指定不同的地址就可实现对应值的输出。而我们DDS输出控制频率和相位,归结到底就是控制ROM的地址。
了解了以上原理之后,再来设计DDS系统就很容易了,以下为DDS信号发生器的代码:


   01  module DDS_Module(
02          Clk,
03          Rst_n,
04          EN,
05          Fword,
06          Pword,
07          DA_Clk,
08          DA_Data
09      );
10
11      input Clk;/*系统时钟*/
12      input Rst_n;/*系统复位*/
13      input EN;/*DDS模块使能*/
14      input [31:0]Fword;/*频率控制字*/
15      input [11:0]Pword;/*相位控制字*/
16     
17      output DA_Clk;/*DA数据输出时钟*/
18      output [9:0]DA_Data;/*D输出输出A*/
19     
20      reg [31:0]Fre_acc; 
21      reg [11:0]Rom_Addr;
22
23  /*---------------相位累加器------------------*/   
24      always @(posedge Clk or negedge Rst_n)
25      if(!Rst_n)
26          Fre_acc <= 32'd0;
27      else if(!EN)
28          Fre_acc <= 32'd0;  
29      else
30          Fre_acc <= Fre_acc + Fword;
31
32  /*----------生成查找表地址---------------------*/       
33      always @(posedge Clk or negedge Rst_n)
34      if(!Rst_n)
35          Rom_Addr <= 12'd0;
36      else if(!EN)
37          Rom_Addr <= 12'd0;
38      else
39          Rom_Addr <= Fre_acc[31:20] + Pword;
40
41  /*----------例化查找表ROM-------*/    
42      ddsrom ddsrom(
43          .address(Rom_Addr),
44          .clock(Clk),
45          .q(DA_Data)
46      );
47
48  /*----------输出DA时钟----------*/ 
49      assign DA_Clk = (EN)?Clk:1'b1;
50
51  endmodule
 
2019-5-10 05:20:18 评论

举报

仿真验证:

  以上分部分介绍了系统的各个关键模块的设计。接下来,我们来对该设计进行仿真验证。因为该实验是基于串口的,为了实现仿真验证,这里小梅哥分别编写了一个串口发送的仿真模型(Uart_Tx_Model)和一个串口接收的仿真模型(Uart_Rx_Model),两个仿真模型的设计都较为简单,但是我们却可以通过该模型模拟对我们的设计进行串口数据的发送和接收,并实时打印仿真模型发送的数据与接收到的数据。关于仿真模型的代码,这里只贴上代码,不做具体解释。(此贴回复超过50条我就专门开文讲解testbench的编写技巧)
   
  以下为串口接收仿真模型的代码




  001   `timescale 1ns/1ps
  002
  003   module Uart_RX_Model(Baud_Set,uart_rx);
  004         
  005        input [2:0]Baud_Set;/*波特率选择信号*/
  006        input uart_rx;/*仿真模型串口接收引脚*/
  007         
  008        reg Clk;/*仿真模型内部时钟,50M*/
  009        reg Rst_n;/*仿真模型内部复位信号*/
  010         
  011        wire Mid_Flag_Receive;/*数据中点(采样点)标志信号*/
  012         
  013        reg Receive_Baud_Start;/*接收波特率生成使能信号*/
  014        reg [7:0]rx_data;/*接收数据移位寄存器*/
  015         
  016        reg [7:0]Rx_Byte;/*最终接收结果*/
  017               
  018        initial Clk = 1;
  019        always#10 Clk = ~Clk;
  020         
  021   /*例化波特率设置模块*/   
  022        baud_select baud_select_Receive(
  023              .Clk(Clk),
  024              .Rst_n(Rst_n),
  025              .Baud_Set(Baud_Set),
  026              .Baud_Start(Receive_Baud_Start),
  027              .Mid_Flag(Mid_Flag_Receive)
  028        );
  029         
  030        initial begin
  031              Rst_n = 0;
  032              Rx_Byte = 0;
  033              rx_data = 0;
  034              #100 Rst_n = 1;
  035        end
  036
  037   /*接收一个字节的数据*/
  038        initial begin
  039        forever begin
  040              @(negedge uart_rx)
  041                   begin
  042                         Receive_Baud_Start = 1;
  043                         @(posedge Mid_Flag_Receive);
  044                         @(posedge Mid_Flag_Receive)rx_data[0] = uart_rx;
  045                         @(posedge Mid_Flag_Receive)rx_data[1] = uart_rx;   
  046                         @(posedge Mid_Flag_Receive)rx_data[2] = uart_rx;   
  047                         @(posedge Mid_Flag_Receive)rx_data[3] = uart_rx;
  048                         @(posedge Mid_Flag_Receive)rx_data[4] = uart_rx;   
  049                         @(posedge Mid_Flag_Receive)rx_data[5] = uart_rx;
  050                         @(posedge Mid_Flag_Receive)rx_data[6] = uart_rx;
  051                         @(posedge Mid_Flag_Receive)rx_data[7] = uart_rx;
  052                         @(posedge Mid_Flag_Receive)begin Receive_Baud_Start = 0;Rx_Byte = rx_data;end
  053                         $display("Master_receive  Data = %0h",Rx_Byte);  
  054                   end
  055              end
  056        end
  057
  058   endmodule
  

  

以下为串口发送仿真模型的设计代码






  001   `timescale 1ns/1ps
  002
  003   module Uart_Tx_Model(Baud_Set,Tx_Data,Tx_En,uart_tx,Tx_Done);
  004         
  005        input [2:0]Baud_Set;  /*波特率选择信号*/
  006        input [7:0]Tx_Data;   /*待发送数据字节*/
  007        input Tx_En;                /*数据字节发送使能信号*/
  008        output reg uart_tx;   /*仿真串口发送模型发送信号*/
  009        output reg Tx_Done;   /*发送完成信号*/
  010         
  011        reg Clk;   /*仿真模型内部工作时钟*/
  012        reg Rst_n; /*仿真模型内部复位信号*/
  013         
  014        wire Bps_Clk;    /*发送波特率时钟波特率*/
  015        reg Bps_En; /*发送波特率使能信号*/
  016               
  017        initial Clk = 1;
  018        always#10 Clk = ~Clk;
  019
  020   /*----例化发送波特率时钟生成模块-----*/    
  021        TxModel_Bps_Gen TxModel_Bps_Gen_send(
  022              .Clk(Clk),
  023              .Rst_n(Rst_n),
  024              .Baud_Set(Baud_Set),
  025              .Tx_Done(Tx_Done),
  026              .Bps_Clk(Bps_Clk),
  027              .Byte_En(Bps_En)
  028        );
  029               
  030        initial begin
  031              Tx_Done = 0;
  032              uart_tx = 1;
  033              Rst_n = 0;
  034              Bps_En = 0;
  035              #100;
  036              Rst_n = 1;
  037              forever@(posedge Tx_En)/*每来一个发送使能信号即执行一次发送过程*/
  038                   Uart_Send(Tx_Data);    
  039        end
  040
  041   /*执行一次字节数据的发送*/     
  042        task Uart_Send;
  043              input [7:0]Data;
  044              begin
  045                   Bps_En = 1;
  046                   Tx_Done = 0;
  047                   $display("Uart_Send  Data = %0h",Data);/*打印发送的数据*/
  048                   @(posedge Bps_Clk) #0.1 uart_tx = 0;
  049                   @(posedge Bps_Clk) #0.1 uart_tx = Data[0];
  050                   @(posedge Bps_Clk) #0.1 uart_tx = Data[1];
  051                   @(posedge Bps_Clk) #0.1 uart_tx = Data[2];
  052                   @(posedge Bps_Clk) #0.1 uart_tx = Data[3];
  053                   @(posedge Bps_Clk) #0.1 uart_tx = Data[4];
  054                   @(posedge Bps_Clk) #0.1 uart_tx = Data[5];
  055                   @(posedge Bps_Clk) #0.1 uart_tx = Data[6];
  056                   @(posedge Bps_Clk) #0.1 uart_tx = Data[7];
  057                   @(posedge Bps_Clk) #0.1 uart_tx = 1;
  058                   @(posedge Bps_Clk) #0.1 ;
  059                   Tx_Done = 1;
  060                   Bps_En = 0;
  061                   #20 Tx_Done = 0;
  062              end
  063        endtask
  064         
  065   endmodule
  

  

以下为仿真顶层模块的设计




  001   `timescale 1ns/1ns
  002   `include "../rtl/header.v"
  003   module uart_scope_tb;
  004        localparam  KEY_WIDTH = 3;
  005         
  006        reg Clk;
  007        reg Rst_n;
  008        reg [KEY_WIDTH - 1:0]Key_in;
  009         
  010        reg ADC_Din;
  011        wire ADC_Clk;
  012        wire ADC_Cs_n;
  013         
  014   /*波特率设置总线,此处默认为9600bps,仿真不做波特率修改测试*/       
  015        wire [2:0]Baud_Set;
  016        reg [7:0]Tx_Data;/*串口发送仿真模型待发送数据字节*/
  017        reg Tx_En; /*串口发送仿真模型发送使能信号*/
  018        wire Rs232_MTSR; /*串口“主机(PC)发送-从机(FPGA)接收”信号*/
  019        wire Rs232_MRST; /*串口“主机(PC)接收-从机(FPGA)发送”信号*/
  020        wire Tx_Done;    /*串口字节发送完成信号*/
  021         
  022        assign Baud_Set = 3'd0;/*设置波特率为固定的9600bps*/
  023         
  024        localparam  
  025              Header = 8'hAA,  /*帧头*/
  026              Length = 8'd3,        /*帧长*/
  027              Tail   = 8'h88;  /*帧尾*/
  028
  029   /*------例化串口示波器顶层模块------*/
  030        uart_scope uart_scope(
  031              .Clk(Clk),
  032              .Rst_n(Rst_n),
  033              .Rs232_Rx(Rs232_MTSR),
  034              .Rs232_Tx(Rs232_MRST),
  035              .Key_in(Key_in),
  036              .ADC_Din(ADC_Din),
  037              .ADC_Clk(ADC_Clk),
  038              .ADC_Cs_n(ADC_Cs_n)
  039        );
  040         
  041   /*------例化串口发送仿真模型------*/
  042        Uart_Tx_Model Uart_Tx_Model(
  043              .Baud_Set(Baud_Set),
  044              .Tx_Data(Tx_Data),
  045              .Tx_En(Tx_En),
  046              .uart_tx(Rs232_MTSR),
  047              .Tx_Done(Tx_Done)
  048        );
  049         
  050   /*------例化串口接收仿真模型------*/
  051   //该模型接收FPGA发送出来的数据并打印在modelsim的transcript窗口中   
  052        Uart_RX_Model Uart_RX_Model(
  053              .Baud_Set(Baud_Set),
  054              .uart_rx(Rs232_MRST)
  055        );
  056
  057   /*-------生成50M时钟信号--------*/  
  058        initial Clk = 0;
  059        always #10 Clk = ~Clk;
  060
  061   /*-------生成ADC_Din数据-------*/  
  062   /*此处不对ADC的采样结果多做计较,只要求保
  063     证ADC_Din上有数据即可,有兴趣者可自己编写仿真模型*/
  064        initial ADC_Din = 1;
  065        always #1315 ADC_Din = ~ADC_Din;
  066         
  067        initial begin
  068              Rst_n = 1'b0;
  069              Tx_En = 1'b0;
  070              Tx_Data = 8'd0;
  071              Key_in = 4'b1111;
  072              #200;
  073              Rst_n = 1'b1;    /*释放复位信号,系统即进入正常工作状态*/
  074              #1000;
  075              En_DDS_Run; /*使能DDS信号发生器生成信号数据*/
  076              #10000;
  077              En_S_DDS;  /*使能采样ADC数据*/
  078              En_S_ADC;  /*使能采样DDS数据*/
  079              #10000;
  080              En_UART_Send;/*使能串口发送,此时串口猎人软件上将会开始持续接收到数据*/   
  081        end
  082         
  083        initial begin
  084        #200_000_000;press_key(0);
  085        #200_000_000;press_key(1);
  086        #200_000_000;
  087        $stop;
  088        end
  089         
  090         
  091
  092   /*---发送命令帧数据任务-----*/      
  093        task Send_CMD;
  094              input [7:0]DATAA,DATAB,DATAC;/*用户数据(地址、数据高字节,数据低字节)*/
  095              begin
  096                   Tx_Data = Header;/*需发送数据为帧头*/
  097                   Tx_En = 1; /*启动发送*/
  098                   #20 Tx_En = 0;   /*一个时钟周期后,清零发送启动信号*/
  099                   @(posedge Tx_Done)/*等待发送完成信号*/
  100                   #1000;
  101                    
  102                   Tx_Data = Length;/*需发送数据为帧长,此处帧长只是数据内容的长度*/
  103                   Tx_En = 1; /*启动发送*/
  104                   #20 Tx_En = 0;   /*一个时钟周期后,清零发送启动信号*/
  105                   @(posedge Tx_Done)/*等待发送完成信号*/
  106                   #1000;
  107                    
  108                   Tx_Data = DATAA;/*需发送数据第一个字节,此数据代表外设寄存器的地址*/
  109                   Tx_En = 1; /*启动发送*/
  110                   #20 Tx_En = 0;   /*一个时钟周期后,清零发送启动信号*/
  111                   @(posedge Tx_Done)/*等待发送完成信号*/
  112                   #1000;
  113                    
  114                   Tx_Data = DATAB;/*需发送数据第二个字节,此数据代表写入外设寄存器的内容高8位*/
  115                   Tx_En = 1; /*启动发送*/
  116                   #20 Tx_En = 0;   /*一个时钟周期后,清零发送启动信号*/
  117                   @(posedge Tx_Done)/*等待发送完成信号*/
  118                   #1000;
  119                    
  120                   Tx_Data = DATAC;/*需发送数据第三个字节,此数据代表写入外设寄存器的内容低8位*/
  121                   Tx_En = 1; /*启动发送*/
  122                   #20 Tx_En = 0;   /*一个时钟周期后,清零发送启动信号*/
  123                   @(posedge Tx_Done)/*等待发送完成信号*/
  124                   #1000;
  125                    
  126                   Tx_Data = Tail;/*需发送数据为帧尾*/
  127                   Tx_En = 1; /*启动发送*/
  128                   #20 Tx_En = 0;   /*一个时钟周期后,清零发送启动信号*/
  129                   @(posedge Tx_Done)/*等待发送完成信号*/
  130                   #1000;
  131                   #10000;     
  132              end
  133        endtask     
  134         
  135        task En_DDS_Run;/*使能DDS生成数据*/
  136              begin
  137                   Send_CMD(`DDS_En, 8'h00, 8'h01);
  138                   $display("En  DDS Run");
  139              end
  140        endtask
  141         
  142        task Stop_DDS_Run;/*停止DDS生成数据*/
  143              begin
  144                   Send_CMD(`DDS_En, 8'h00, 8'h00);
  145                   $display("Stop  DDS Run");
  146              end
  147        endtask
  148         
  149        task En_S_DDS;/*使能采样DDS数据*/
  150              begin
  151                   Send_CMD(`DDS_Sample_En, 8'h00, 8'h01);
  152                   $display("En  Sample DDS data");
  153              end
  154        endtask
  155         
  156        task Stop_S_DDS;/*停止采样DDS数据*/
  157              begin
  158                   Send_CMD(`DDS_Sample_En, 8'h00, 8'h00);
  159                   $display("Stop  Sample DDS data");
  160              end
  161        endtask
  162         
  163        task En_UART_Send;/*使能串口发送*/
  164              begin
  165                   Send_CMD(`UART_En_Tx, 8'h00, 8'h01);
  166                   $display("En  UART Send");
  167              end
  168        endtask
  169         
  170        task Stop_UART_Send;/*停止串口发送*/
  171              begin
  172                   Send_CMD(`UART_En_Tx, 8'h00, 8'h00);
  173                   $display("Stop  UART Send");
  174              end
  175        endtask
  176         
  177              task En_S_ADC;/*使能采集ADC数据*/
  178              begin
  179                   Send_CMD(`ADC_Sample_En, 8'h00, 8'h01);
  180                   $display("En  Sample ADC data");
  181              end
  182        endtask
  183         
  184        task Stop_S_ADC;/*停止采集ADC数据*/
  185              begin
  186                   Send_CMD(`ADC_Sample_En, 8'h00, 8'h00);
  187                   $display("Stop  Sample ADC data");
  188              end
  189        endtask
  190
  191        task Set_ADC_Sample_Speed;/*设置ADC采样率*/
  192              input[25:0] Fs;/*采样率实际频率*/
  193              reg [31:0] S_cnt_top;/*分频计数器计数最大值*/
  194              begin
  195              /*由采样实际频率值换算出采样分频计数器计数最大值*/
  196                   S_cnt_top = 50000000/Fs - 1;
  197              /*写采样分频计数器计数最大值低16位*/
  198                   Send_CMD(`ADC_S_Cnt_Max_L,S_cnt_top[15:8],S_cnt_top[7:0]);
  199              /*写采样分频计数器计数最大值高16位*/
  200                   Send_CMD(`ADC_S_Cnt_Max_H,S_cnt_top[31:24],S_cnt_top[23:16]);
  201                   $display("Set  ADC Sample Speed as  = %0d" ,Fs);
  202              end
  203        endtask
  204         
  205        task Set_DDS_Sample_Speed;/*设置DDS数据的采样率*/
  206              input[25:0] Fs;/*采样率实际频率*/
  207              reg [31:0] S_cnt_top;/*分频计数器计数最大值*/
  208              begin
  209              /*由采样实际频率值换算出采样分频计数器计数最大值*/
  210                   S_cnt_top = 50000000/Fs - 1;
  211              /*写采样分频计数器计数最大值低16位*/  
  212                   Send_CMD(`DDS_S_Cnt_Max_L,S_cnt_top[15:8],S_cnt_top[7:0]);
  213              /*写采样分频计数器计数最大值高16位*/
  214                   Send_CMD(`DDS_S_Cnt_Max_H,S_cnt_top[31:24],S_cnt_top[23:16]);
  215                   $display("Set  DDS Sample Speed as  = %0d" ,Fs);
  216              end
  217        endtask
  218         
  219        task Set_DDS_Fout_Speed;/*设置DDS输出信号频率*/
  220              input[25:0] Fs;/*输出信号实际频率*/
  221              reg [31:0] r_fword;/*DDS频率控制字*/
  222              begin
  223              /*由实际要求输出频率数据换算出频率控制字*/
  224                   r_fword = Fs*65536*65536/50000000;
  225                   Send_CMD(`DDS_Fword_L,r_fword[15:8],r_fword[7:0]);
  226                   Send_CMD(`DDS_Fword_H,r_fword[31:24],r_fword[23:16]);
  227                   $display("Set  DDS Fout as = %0d" ,Fs);
  228              end
  229        endtask
  230         
  231         
  232        task press_key;
  233              input [KEY_WIDTH/2:0]Key;
  234              reg [15:0]myrand;
  235              begin
  236                   Key_in = {KEY_WIDTH{1'b1}};
  237                   /*按下抖动*/
  238                   repeat(20)begin
  239                         myrand = {$random} % 65536;
  240                         #myrand  Key_in[Key] = ~Key_in[Key];
  241                   end
  242                   Key_in[Key] = 1'b0;
  243                                     
  244                   #22000000;/*稳定期*/
  245                    
  246                   /*释放抖动*/
  247                   repeat(20)begin
  248                         myrand = {$random} % 65536;
  249                         #myrand  Key_in[Key] = ~Key_in[Key];
  250                   end
  251                   Key_in[Key] = 1'b1;
  252                   #22000000;/*稳定期*/
  253              end
  254        endtask
  255               
  256   endmodule
  

 
2019-5-10 05:29:24 评论

举报

下图为系统仿真架构图:





这里,在我们提供的工程中,已经设置好了Nativelink,用户只需要在QuartusII中点击tools—run rtl simulation tool—rtl simulation即可自动调用modelsim-altera并执行仿真,因为这里完全模拟真实时序进行仿真,因此运行完整个仿真大约需要5—10分钟。
仿真完成后,结果如图所示:





其中,Rx_Byte为串口接收仿真模型接收到的数据,这里以波形的方式展示。ADC_Data为ADC采样结果,DDS_Data为DDS输出的数据最下方为按键标志和按键结果,当按下按键1时,数据通道切换为ADC的采样结果,当按下按键2时,数据通道切换为DDS的输出数据。


(如果用户在进行仿真的过程中发现仿真无法运行,在modelsim中提示错误的话,请删除simulation—>modelsim文件夹下除wave.do和mydo.do文件外的其他所有文件,然后在quartus 中重新启动仿真)
2019-5-10 05:35:41 评论

举报

基于串口猎人的板级验证:

这里,我们使用一款功能非常强大的串口调试软件——串口猎人来调试我们的设计。串口猎人的安装这里不做过多的讲述。首先,我们将FPGA系统的sof文件配置到fpga中,然后运行串口猎人软件,串口猎人打开后界面如下所示:



我们点击图中的动画即可让该动画消失。
接下来我们载入预先设置好的配置文件,如下图所示:



我们点击右下角的“载入”按钮,在弹出的界面中,定位到我们本实验的根目录,选择“serialhunter.ini”文件,



点击打开。
切换到高级发码选项卡,显示如下所示:



点击启动自动发码。
回到基本功能选项卡,可以看到,窗口中开始连续不断的接收到数据,如下图所示:

此时,我们切换到波形显示选项卡,可看到如下所示的效果:



表明我们已经正确的接收到了波形数据。
切换到码表选项卡,效果如下图所示:





然后,我们切换到柱状显示选项卡,效果如下所示:





然后,我们回到高级发码选项卡,将0~3组发码列表前的勾选取消,勾选上第4组,然后点击启动自动发码。此时,我们就已经将fpga系统的接收和发送波特率速率切换到了115200,如下图所示:





因为波特率不对,所以接下来接收到的数据就全部是错误的了。我们回到基本功能选项卡,将波特率切换为115200bps,如下图所示:



然后我们再回到波形显示选项卡,结果如下所示:



这时,我们再回到高级发码选项卡,取消第4组发码的勾选,勾选上第5组发码,然后点击自动发码,再回到波形显示选项卡,结果如下所示:





此时,我们的DDS输出信号频率便更改为50Hz了。其他更多指令内容,这里就不一一介绍了,欢迎各位积极探索。当然,这个系统的最终目标是教会大家在fpga中使用串口进行简单的数据收发,离真正的虚拟示波器还相差甚远。此串口猎人显示的波形频率并不能严格的和实际信号的频率对应上,这一点望各位悉知。也欢迎有上位机开发基础的同学来根据本系统开发独立的上位机软件。另外,在使用中,我们只需要按下按键2,就能将数据通道切换到ADC的采样结果上来,此时,用刀口的螺丝刀拧动开发板上的电位器,在码表选项卡上就能明显的看到数值的变化,可作为电压表之用。按下按键1则切换到内部DDS通道。需要说明的是,本实验中使用的ADC驱动原本是为TLC548设计,在这里使用时,发现也能够正确的控制TLC549进行数据转换,因此就暂未做修改。需要注意的是,TLC549的IO时钟最高支持1.1M,而TLC548的则最高支持2.048M,因此,从严谨性的角度上来说,该驱动是无法很好的驱动TLC549的。需要我们对驱动进行一些小小的修改,具体的修改内容,小梅哥稍后实现。
  
 
由于本系统涉及到的功能模块和代码较多,无法一一为各位讲解,希望各位能够仔细阅读代码,代码中小梅哥都做了详细的注释,希望大家通过代码,能进一步学习verilog语法,增强对系统级仿真的意识。


小梅哥
2015年4月8日于至芯科技
2019-5-10 05:52:25 评论

举报

回复帖子即可索取源码工程哦
2019-5-10 06:00:45 评论

举报

哈哈哈哈 真的更新了终于更新了~~~感谢小梅哥
2019-5-10 06:13:32 评论

举报

不错啊,小梅哥直接放代码吧。。。。^_^
2019-5-10 06:25:49 评论

举报

一直在关注着,终于更新了。上代码学习下
2019-5-10 06:45:36 评论

举报

小梅哥您好 你这个串口我有个问题 您在前一篇《小梅哥和你一起深入学习FPGA之串口调试(一)》说道num《=12改成10是因为一共有10bit数据要发送和接收,那么为什么在这个程序里num《=11了? 还是说对于上个程序来说 其实它的停止位是0.5个?(仿真过后确实停止位只有半个)
2019-5-10 07:04:56 评论

举报

是的,上个帖子确实存在这个问题,所以我后来在自己设计代码的时候就注意到了这一点,然后更正了,你很细心。不过上个帖子的实际应用意义不大。这个帖子里的串口收发模块则比较好用
2019-5-10 07:16:38 评论

举报

小梅哥,上传一下代码学习下嘛
2019-5-10 07:30:53 评论

举报

不,你这个新代码的tx模块和之前是一样的 ,虽然因为起始位是改成了cnt=1赋值但最后算下来停止位还是1。5个高电平(和上个程序num=11一样)。收发之所以能成功 是因为的rx模块没有对停止位个数进行计数,默认1bit停止位 相当于数据之间有半个延迟,也就是说只要停止位大于等于1,rx模块都能正确接收。特权这个程序的tx是无法做到停止位正好是1个或者2个bit的
2019-5-10 07:48:47 评论

举报

`timescale 1ns/1nsmodule Uart_Byte_Tx_tb;reg Clk;reg Rst_n;reg [2:0]Baud_Set;reg Byte_En;wire Tx_Done;reg [7:0]Data_Byte;wire Bps_Clk;wire Rs232_Tx;/*-----------例化串口字节发送模块-------*/UART_Byte_Tx UART_Byte_Tx(.Clk(Clk),.Rst_n(Rst_n),.Byte_En(Byte_En),.Baud_Set(Baud_Set),.Data_Byte(Data_Byte),.Tx_Done(Tx_Done),.Rs232_Tx(Rs232_Tx));initial Clk = 1;always #10 Clk = ~Clk;initial beginRst_n = 0;Baud_Set = 3'd0;Byte_En = 1'b0;Data_Byte = 8'd0;#200;Rst_n = 1;#20000;Data_Byte = 8'h3C;Byte_En = 1'b1;#20;Byte_En = 1'b0;@(posedge Tx_Done);#20000;Data_Byte = 8'h55;Byte_En = 1'b1;#20;Byte_En = 1'b0;@(posedge Tx_Done);#2000;$stop;endendmodule[/mw_shl_code]
testbench在此,请仿真。两次收发间隔20ns,单次发送刚好1041720ns,对应10个位的长度。



仿真结果可以说明一切,并不是你所谓的1.5个停止位
2019-5-10 07:59:51 评论

举报

之前那个问题是怎么解决的呢?我觉得一样啊
2019-5-10 08:19:47 评论

举报

谢谢小梅哥,一直关注ing。
2019-5-10 08:35:07 评论

举报

2019-5-10 08:44:05 评论

举报

2019-5-10 08:53:30 评论

举报

只有小组成员才能发言,加入小组>>

12下一页

82个成员聚集在这个小组

加入小组

创建小组步骤

关闭

站长推荐 上一条 /8 下一条

快速回复 返回顶部 返回列表