OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 11521|回复: 30

小梅哥和你一起深入学习FPGA之基于ZX-2型FPGA开发板的串口示波器

[复制链接]

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
发表于 2015-4-9 09:11:27 | 显示全部楼层 |阅读模式


 

本实验,为ZX-2开发板的综合实验,该实验利用ZX-2开发板上的ADC、独立按键、UART等外设,搭建了一个具备丰富功能的数据采集卡,ZX-2开发板负责进行数据的采集并将数据通过串口发送到PC机上,PC端,利用强大的串口调试工具——串口猎人,来实现数据的接收分析,并将数据分别以波形、码表、柱状图的形式动态显示出来,以让使用者能够直观的看到ADC采集到的信号细节。同时,用户也可以使用串口猎人通过串口给下位机(FPGA)发送指令,下位机将对接收到的指令进行解码,然后依据解码结果来配置FPGA中各个子模块的控制寄存器,以实现通过串口控制FPGA中子模块工作状态的功能。

 

本实验中,涉及到的应用模块和知识点如下所示:

串口收发模块的设计和使用;

串口收发模块仿真模型的设计;

串口简单数据帧的解码;

串口帧转Memory Mapped总线的设计;

Memory Mapped Slave模块的设计;

线性序列机设计思想的应用(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数据结果有效标志信号选择多路器,在ADCDDS都处于工作的状态下,通过该多路器来实现数据有效标志信号的选择

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信号到来时,即开始产生发送一个完整字节的数据需要的完整波特率时钟信号。

本设计,波特率支持9600bps921600bps。例如,需要产生的波特率时钟为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信号的第一个上升沿到来时,字节发送模块开始发送起始位,接下来的29个上升沿,发送8个数据位,第10个上升沿到第11个上升沿为停止位的发送。

 

http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-4-9 09:15:22 | 显示全部楼层

单个串口接收模块中实现串口数据接收的主要代码如下所示:



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个时间段,对位于中间的三个时间段进行采样。然后对三个采样结果进行统计判断,如果某种电平状态在三次采样结果中占到了两次及以上,则可以判定此电平状态即为正确的数据电平。例如456时刻采样结果分别为110,那么就取此位解码结果为1,否则,若三次采样结果为010,则解码结果就为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_PARATx_Bps_Gen模块中的BPS_PARA1/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

 



http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-4-9 09:21:00 | 显示全部楼层

 

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



在本系统中,需要通过该Memory Mapped 总线配置的寄存器总共有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 >使能串口发送数据(AA 03 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位,如第1415行所示。当我们需要修改ADC的采样率时,直接通过串口发送指令,修改这两个寄存器中的内容即可。


 


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


写使能信号:m_wr


写地址信号:m_addr


写数据信号:m_wrdata

那么,这三组信号是如何配合工作的呢?我们以配置ADC_Sample_Cnt_Max_HADC_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_HADC_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寄存器中。其他寄存器的配置原理与此相同,因此不再做阐述,相信大家举一反三,便可理解了。



http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-4-9 09:27:26 | 显示全部楼层

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,频率控制字fword1,则输出频率为Fout=Fclk/2N,这个频率相当于“基频”,若fwordB,则输出频率为

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


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


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


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


如果我们需要输出频率为(1/2)*1000/32Hz,即周期为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

 

http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-4-9 09:33:58 | 显示全部楼层

仿真验证:

以上分部分介绍了系统的各个关键模块的设计。接下来,我们来对该设计进行仿真验证。因为该实验是基于串口的,为了实现仿真验证,这里小梅哥分别编写了一个串口发送的仿真模型(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发送出来的数据并打印在modelsimtranscript窗口中 

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


 



http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-4-9 09:38:32 | 显示全部楼层

下图为系统仿真架构图:



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

仿真完成后,结果如图所示:



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


(如果用户在进行仿真的过程中发现仿真无法运行,在modelsim中提示错误的话,请删除simulation>modelsim文件夹下除wave.domydo.do文件外的其他所有文件,然后在quartus 中重新启动仿真)


http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-4-9 09:47:55 | 显示全部楼层

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

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


我们点击图中的动画即可让该动画消失。

接下来我们载入预先设置好的配置文件,如下图所示:


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


点击打开。

切换到高级发码选项卡,显示如下所示:


点击启动自动发码。

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

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


表明我们已经正确的接收到了波形数据。

切换到码表选项卡,效果如下图所示:



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



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



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


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


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



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

 

由于本系统涉及到的功能模块和代码较多,无法一一为各位讲解,希望各位能够仔细阅读代码,代码中小梅哥都做了详细的注释,希望大家通过代码,能进一步学习verilog语法,增强对系统级仿真的意识。


小梅哥

2015年4月8日于至芯科技

http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-4-9 09:49:04 | 显示全部楼层
回复帖子即可索取源码工程哦
http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

4

主题

27

帖子

0

精华

初级会员

Rank: 2

积分
148
金钱
148
注册时间
2013-1-11
在线时间
12 小时
发表于 2015-4-9 10:33:05 | 显示全部楼层
哈哈哈哈 真的更新了终于更新了~~~感谢小梅哥
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2015-4-9 15:40:43 | 显示全部楼层
不错啊,小梅哥直接放代码吧。。。。^_^
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

13

主题

45

帖子

0

精华

初级会员

Rank: 2

积分
166
金钱
166
注册时间
2013-8-27
在线时间
12 小时
发表于 2015-4-9 15:58:36 | 显示全部楼层
一直在关注着,终于更新了。上代码学习下
回复 支持 反对

使用道具 举报

0

主题

3

帖子

0

精华

新手入门

积分
23
金钱
23
注册时间
2015-4-10
在线时间
0 小时
发表于 2015-4-10 21:14:58 | 显示全部楼层
小梅哥您好 你这个串口我有个问题 您在前一篇《小梅哥和你一起深入学习FPGA之串口调试(一)》说道num《=12改成10是因为一共有10bit数据要发送和接收,那么为什么在这个程序里num《=11了? 还是说对于上个程序来说 其实它的停止位是0.5个?(仿真过后确实停止位只有半个)
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-4-11 12:52:59 | 显示全部楼层
回复【12楼】大肉肉熊:
---------------------------------
是的,上个帖子确实存在这个问题,所以我后来在自己设计代码的时候就注意到了这一点,然后更正了,你很细心。不过上个帖子的实际应用意义不大。这个帖子里的串口收发模块则比较好用
http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

13

主题

45

帖子

0

精华

初级会员

Rank: 2

积分
166
金钱
166
注册时间
2013-8-27
在线时间
12 小时
发表于 2015-4-12 10:02:18 | 显示全部楼层
回复【13楼】小梅哥:
---------------------------------
小梅哥,上传一下代码学习下嘛
回复 支持 反对

使用道具 举报

0

主题

3

帖子

0

精华

新手入门

积分
23
金钱
23
注册时间
2015-4-10
在线时间
0 小时
发表于 2015-4-12 15:03:38 | 显示全部楼层
回复【13楼】小梅哥:
---------------------------------
不,你这个新代码的tx模块和之前是一样的 ,虽然因为起始位是改成了cnt=1赋值但最后算下来停止位还是1。5个高电平(和上个程序num=11一样)。收发之所以能成功 是因为的rx模块没有对停止位个数进行计数,默认1bit停止位 相当于数据之间有半个延迟,也就是说只要停止位大于等于1,rx模块都能正确接收。特权这个程序的tx是无法做到停止位正好是1个或者2个bit的
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-4-12 17:09:13 | 显示全部楼层

`timescale 1ns/1ns

module 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 begin
		Rst_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;
	end
endmodule
[/mw_shl_code]


testbench在此,请仿真。两次收发间隔20ns,单次发送刚好1041720ns,对应10个位的长度。


仿真结果可以说明一切,并不是你所谓的1.5个停止位


http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

0

主题

3

帖子

0

精华

新手入门

积分
23
金钱
23
注册时间
2015-4-10
在线时间
0 小时
发表于 2015-4-12 22:16:53 | 显示全部楼层
回复【16楼】小梅哥:
---------------------------------
之前那个问题是怎么解决的呢?我觉得一样啊
回复 支持 反对

使用道具 举报

0

主题

5

帖子

0

精华

新手上路

积分
31
金钱
31
注册时间
2015-4-12
在线时间
1 小时
发表于 2015-4-12 22:34:36 | 显示全部楼层
谢谢小梅哥,一直关注ing。
回复 支持 反对

使用道具 举报

27

主题

96

帖子

0

精华

高级会员

Rank: 4

积分
790
金钱
790
注册时间
2014-7-7
在线时间
155 小时
发表于 2015-4-18 06:56:40 | 显示全部楼层
强大
正准备学FPGA
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
21
金钱
21
注册时间
2015-4-22
在线时间
0 小时
发表于 2015-4-22 15:43:36 | 显示全部楼层
非常好,准备学习。
回复 支持 反对

使用道具 举报

31

主题

270

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
450
金钱
450
注册时间
2012-5-18
在线时间
6 小时
发表于 2015-4-27 15:59:52 | 显示全部楼层
好,顶小美哥。
谁来买我的火柴.....
回复 支持 反对

使用道具 举报

40

主题

177

帖子

0

精华

高级会员

Rank: 4

积分
921
金钱
921
注册时间
2013-10-23
在线时间
94 小时
 楼主| 发表于 2015-5-2 09:32:29 | 显示全部楼层

既然大家评价还可以,那我就干脆自作主张,将整个开发板的全部资料放出来吧,大家要的赶紧下载。另外,整个内容比较大,网速快的筒子可以下载了解压好了,将其中大家需要部分再次上传上来,我一直用流量卡工作,所以流量神马的实在伤不起,就不给大家传文件了,这里只留下地址。

链接:http://pan.baidu.com/s/1qW0YXhq 密码:1wvz

http://xiaomeige.taobao.com。做最用心的FPGA学习板和教程资料
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
24
金钱
24
注册时间
2015-5-12
在线时间
0 小时
发表于 2015-5-12 11:39:54 | 显示全部楼层
小梅哥真赞!刚接触fpga,难得好资料,小梅哥要是做视频的话,肯定会让很多入门者少走些弯路。
回复 支持 反对

使用道具 举报

1

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
72
金钱
72
注册时间
2013-9-11
在线时间
5 小时
发表于 2015-11-26 17:17:41 | 显示全部楼层
很不错,学习了
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
2
金钱
2
注册时间
2017-7-25
在线时间
0 小时
发表于 2017-7-25 15:25:37 | 显示全部楼层
学习一下
回复 支持 反对

使用道具 举报

4

主题

13

帖子

0

精华

新手上路

积分
27
金钱
27
注册时间
2019-3-11
在线时间
4 小时
发表于 2019-3-21 12:47:15 | 显示全部楼层
请问
Sample_Ctrl中三个寄存器的定义及配置代码
第24行的  m_wr&&(m_addr==`ADC_S_Cnt_Max_L)是什么意思呢?`ADC_S_Cnt_Max_L)是取地址的指令吗?
那这个变量ADC_S_Cnt_Max_L的地址是在什么位置定义的呢?
回复 支持 反对

使用道具 举报

10

主题

34

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
226
金钱
226
注册时间
2014-11-7
在线时间
52 小时
发表于 2019-3-22 21:21:49 | 显示全部楼层
不错啊,小梅哥直接放代码吧。。。。^_^
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
3
金钱
3
注册时间
2019-3-24
在线时间
0 小时
发表于 2019-3-24 20:17:36 | 显示全部楼层
学习一下
回复 支持 反对

使用道具 举报

0

主题

5

帖子

0

精华

新手上路

积分
39
金钱
39
注册时间
2019-7-27
在线时间
16 小时
发表于 2020-3-23 22:46:38 | 显示全部楼层
小梅哥讲解的FPGA太清晰易懂了,要是做成视频就更完美了
回复 支持 反对

使用道具 举报

2

主题

712

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2178
金钱
2178
注册时间
2018-8-27
在线时间
258 小时
发表于 2020-3-25 08:45:55 | 显示全部楼层
楼主太牛逼了!
森罗万象
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
5
金钱
5
注册时间
2019-5-28
在线时间
1 小时
发表于 2021-9-17 07:55:14 | 显示全部楼层
需要源码学习一下
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 11:25

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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