九、基于串口猎人软件的串口示波器
1、实验介绍
本实验,为芯航线开发板的综合实验,该实验利用芯航线开发板上的ADC、独立按键、UART等外设,搭建了一个具备丰富功能的数据采集卡,芯航线开发板负责进行数据的采集并将数据通过串口发送到PC机上,PC端,利用强大的串口调试工具——串口猎人,来实现数据的接收分析,并将数据分别以波形、码表、柱状图的形式动态显示出来,以让使用者能够直观的看到ADC采集到的信号细节。同时,用户也可以使用串口猎人通过串口给下位机(FPGA)发送指令,下位机将对接收到的指令进行解码,然后依据解码结果来配置FPGA中各个子模块的控制寄存器,以实现通过串口控制FPGA中子模块工作状态的功能。
本实验中,涉及到的应用模块和知识点如下所示:
- 串口收发模块的设计和使用;
- 串口收发模块仿真模型的设计;
- 串口简单数据帧的解码;
- 串口帧转Memory Mapped总线的设计;
- Memory Mapped Slave模块的设计;
- 线性序列机设计思想的应用(ADC驱动);
- 独立按键消抖的分析与实现;
- 直接数字频率合成(DDS)的设计与实现;
- 使能时钟对系统间模块协调工作的重要性;
- 串口猎人的详细使用;
- 完整系统的仿真验证设计;
- 头文件在设计中的运用;
- Quartus II软件中可定制化存储器ROM的使用;
本实验不仅注重可综合的代码编写,同时更注重代码的仿真验证。通过仿真,我们能够寻找设计中可能存在的问题并修正。最终,在整个系统仿真无误的基础上,下载到开发板上一次性成功。
2、系统结构
下图为本设计的框架结构图:
系统采用模块化设计,在模块划分的过程中,重点考虑了系统的可扩展性,下表为对系统中各模块功能的简单介绍。
系统中各端口和信号的功能介绍如下:
本实验为综合性实验,代码量较大,因此这里只针对部分代码进行讲解。如果文档中没有讲到的内容,大家可以参看代码注释。
模块详解3.1
Tx_Bps_Gen
Tx_Bps_Gen为发送波特率生成模块,每当有Byte_En信号到来时,即开始产生发送一个完整字节的数据需要的完整波特率时钟信号。
本设计,波特率支持9600bps到921600bps。例如,需要产生的波特率时钟为9600bps,即波特率时钟频率为9600Hz,周期为104.17us。生成9600Hz波特率时钟的核心思想就是对系统时钟进行计数,这里设定系统时钟为50MHz,则一个时钟的周期为20ns,我们只需要对系统时钟计数5208次,每计数5208次产生一个时钟周期的高电平脉冲,即可实现生成9600Hz波特率时钟的功能。相应代码如下所示:
第18行“parameter system_clk = 50_000_000;”,这里用一个全局参数定义了系统时钟,暂时设定为50M,可根据实际使用的板卡上的工作时钟进行修改。
所谓波特率生成,就是用一个定时器来定时,产生频率与对应波特率时钟频率相同的时钟信号。例如,我们使用波特率为115200bps,则我们需要产生一个频率为115200Hz的时钟信号。那么如何产生这样一个115200Hz的时钟信号呢?这里,我们首先将115200Hz时钟信号的周期计算出来,1秒钟为1000_000_000ns,因此波特率时钟的周期Tb= 1000000000/115200 =8680.6ns,即115200信号的一个周期为8680.6ns,那么,我们只需要设定我们的定时器定时时间为8680.6ns,每当定时时间到,产生一个系统时钟周期长度的高脉冲信号即可。系统时钟频率为50MHz,即周期为20ns,那么,我们只需要计数8680/20个系统时钟,就可获得8680ns的定时,即bps115200=Tb/Tclk - 1=Tb*fclk - 1=fclk/115200-1。相应的,其它波特率定时值的计算与此类似,这里小梅哥就不再一一分析。20行至28行为波特率定时器定时值的计算部分。
为了能够通过外部控制波特率,设计中使用了一个3位的波特率选择端口:Baud_Set。通过给此端口不同的值,就能选择不同的波特率,此端口控制不同波特率的原理很简单,就是一个多路选择器,第32行至第48行即为此多路选择器的控制代码, Baud_Set的值与各波特率的对应关系如下:
000 : 9600bps;
001 : 19200bps;
010 :38400bps;
011 :57600bps;
100 :115200bps;
101 :230400bps;
110 :460800bps;
111 :921600bps;
3.2 Uart_Byte_Tx
Uart_Byte_Tx为字节发送模块,该模块在波特率时钟的节拍下,依照UART通信协议发送一个完整的字节的数据。当一个字节发送完毕后,Tx_Done产生一个高脉冲信号,以告知其它模块或逻辑一个字节的数据已经传输完成,可以开始下一个字节的发送了。其发送一个字节数据的实现代码如下:
在UART协议中,一个完整的字节包括一位起始位、8位数据位、一位停止位即总共十位数据,那么,要想完整的实现这十位数据的发送,就需要11个波特率时钟脉冲,如下所示:
BPS_CLK信号的第一个上升沿到来时,字节发送模块开始发送起始位,接下来的2到9个上升沿,发送8个数据位,第10个上升沿到第11个上升沿为停止位的发送。
3.3 Uart_Byte_Rx
单个串口接收模块中实现串口数据接收的主要代码如下所示:
根据串口发送协议,一个字节的数据传输是以一个波特率周期的低电平作为起始位的,因此,成功接收UART串口数据的核心就是准确检测起始位。由于外部串口发送过来的数据与接收系统不在同一个时钟域,因此不能直接使用该信号的下降沿来作为检测标志,我们需要在fpga中,采用专用的边沿检测电路来实现,第25行至37行通过四个移位寄存器,存储连续四个时钟上升沿时外部发送数据线的状态,第39行通过比较前两个时钟时数据线的状态与后两个时钟时数据线的状态,来得到该数据线的准确下降沿,以此保证起始位的准确检测。
在简单的串口接收中,我们通常选取一位数据的中间时刻进行采样,因为此时数据最稳定,但是在工业环境中,存在着各种干扰,在干扰存在的情况下,如果采用传统的中间时刻采样一次的方式,采样结果就有可能受到干扰而出错。为了滤除这种干扰,这里采用多次采样求概率的方式。如下图,将一位数据平均分成9个时间段,对位于中间的三个时间段进行采样。然后对三个采样结果进行统计判断,如果某种电平状态在三次采样结果中占到了两次及以上,则可以判定此电平状态即为正确的数据电平。例如4、5、6时刻采样结果分别为1、1、0,那么就取此位解码结果为1,否则,若三次采样结果为0、1、0,则解码结果就为0。
因为采样一位需要9个时钟上升沿,因此,采样一个完整的数据需要10*9,即90个时钟上升沿,这里,采样时钟为波特率时钟的9倍。产生采样时钟的部分代码如下所示:
这里,BPS_PARA的计算原理和前面Tx_Bps_Gen模块中的BPS_PARA的计算原理一致,不过这里,因为采样时钟为波特率时钟的9倍,所以,BPS_PARA为Tx_Bps_Gen模块中的BPS_PARA的1/9。计算BPS_PARA的相关代码如下:
3.4 CMD
CMD模块为串口数据帧接收与解析模块,该模块负责对串口接收到的每一帧的数据进行解码判断,并从数据帧中提取出地址字节和数据字节。最后将地址字节和数据字节转换为类似于Avalon-MM形式的总线,以实现对其它模块的控制寄存器的读写,从而实现通过串口控制FPGA中各个模块工作的目的。
在工业应用中,串口指令大多以数据帧的格式出现,包含帧头、帧长、帧命令、帧内容、校验和以及帧尾,不会只是单纯的传输数据。在这个实验中,小梅哥也使用了数据帧的形式来通过上位机向FPGA发送命令,不过这里我使用的帧格式非常简单,帧格式以帧头、帧长、帧内容以及帧尾组成,忽略了校验部分内容,帧头、帧长以及帧尾内容都是固定的,不固定的只是帧内容,以下为小梅哥的设计中一帧数据的格式:
由于数据帧本身结构简单,因此数据帧的解析过程也相对简洁,以下为小梅哥的数据帧解析状态机设计,该状态机分为帧头解析、帧长解析、数据接收以及帧尾解析。默认时,状态机处于帧头解析状态,一旦出现帧头数据,则跳转到帧长接收状态,若下一个字节为帧长数据(这里严格意义上并不能算作帧长,因为长度固定,充其量只能算作帧头,读者不须过分纠结),则开始连续接收三个字节的数据,若非指定的帧长内容,则表明这是一次无关传输,状态机将返回到帧头解析状态继续等待新的数据帧到来。在帧尾解析状态,若解析到的数据并非指定的帧尾数据,则表明此次数据帧非有效帧,则将此帧已解析到的数据舍弃。若为帧尾数据,则解析成功,产生命令有效标志信号(CMD_Valid),Memory Mapped 总线进程在检测到此命令有效信号后,即产生写外设寄存器操作。
命令解析的状态机实现代码如下所示:
第23行到第29行为状态机编码,这里采用独热码的编码方式。状态机的编码方式有很多种,包括二进制编码、独热码、格雷码等,二进制编码最接近我们的常规思维,但是在FPGA内部,其译码电路较为复杂,且容易出现竞争冒险,导致使用二进制编码的状态机最高运行速度相对较低。独热码的译码电路最简单,因此采用独热码方式编码的状态机运行速度较二进制编码方式高很多,但是编码会占用较多的数据位宽。格雷码以其独特的编码特性,能够非常完美的解决竞争冒险的问题,使状态机综合出来的电路能够运行在很高的时钟频率,但是格雷码编码较为复杂,尤其对于位宽超过4位的格雷码,编码实现较二进制编码和独热码编码要复杂的多。这里,详细的关于状态机的编码问题,小梅哥不做过多的讨论,更加细致的内容,请大家参看夏宇闻老师经典书籍《Verilog数字系统设计教程》中第12章相关内容。
Memory Mapped 总线进程根据命令有效标志信号产生写外设寄存器操作的相关代码如下所示:
在本系统中,需要通过该Memory Mapped 总线配置的寄存器总共有12个,分别位于ADC采样速率控制模块(Sample_Ctrl)、串口发送控制模块(UART_Tx_Ctrl)、直接数字频率合成信号发生器模块(DDS)中,各寄存器地址分配及物理意义如下所示:
指令使用说明:
例如,系统在上电后,各个模块默认是没有工作的,要想在上位机上看到数据,就必须先通过上位机发送控制命令。因为系统上电后默认选择的数据通道为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中三个寄存器的定义及配置代码如下所示:
采样率的控制采用定时器的方式实现。使用一个计数器持续对系统时钟进行计数,一旦计数满设定时间,则产生一个时钟周期的高脉冲信号,作为ADC采样使能信号。这里,系统时钟周期为20ns,因此,如果要实现采样1K的采样率(采样周期为1ms),则需对系统时钟计数50000次;若实现20K的采样率(采样周期为50us),则需要对系统时钟计数2500次。以此类推,可知改变采样率的实质就是改变计数器的计数最大值,因此,我们要想改变采样速率,也只需要改变采样率控制计数器的计数最大值即可。所以这里,我们设计了两个16位的寄存器,分别存储采样率控制计数器的计数最大值的低16位和高16位,如第14、15行所示。当我们需要修改ADC的采样率时,直接通过串口发送指令,修改这两个寄存器中的内容即可。
这里,小梅哥使用自己设计的一个山寨版Memory Mapped 总线来配置各个寄存器,该总线包含三组信号,分别为:
- 写使能信号:m_wr;
- 写地址信号:m_addr;
- 写数据信号:m_wrdata;
那么,这三组信号是如何配合工作的呢?我们以配置ADC_Sample_Cnt_Max_H和ADC_Sample_Cnt_Max_L这两个寄存器来进行介绍,这里再贴上这部分代码:
复位时,让{ 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寄存器中。其他寄存器的配置原理与此相同,因此不再做阐述,相信大家举一反三,便可理解了。
如有更多问题,欢迎加入芯航线 FPGA 技术支持***流学习:472607506
小梅哥
关于学习资料,小梅哥系列所有能够开放的资料和更新(包括视频教程,程序代码,教程文档,工具软件,开发板资料)都会发布在我的云分享。(记得订阅)链接:http://yun.baidu.com/share/home?uk=402885837&view=share#category/type=0
赠送芯航线AC6102型开发板配套资料预览版下载链接:链接:http://pan.baidu.com/s/1slW2Ojj 密码:9fn3
赠送SOPC公开课链接和FPGA进阶视频教程。链接:http://pan.baidu.com/s/1bEzaFW 密码:rsyh