差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
串口监视系统设计 [2018/10/22 15:29] anran [实验原理] |
串口监视系统设计 [2020/01/18 21:54] (当前版本) gongyu |
||
---|---|---|---|
行 1: | 行 1: | ||
- | =====串口监视系统设计===== | + | ### 串口监视系统设计 |
- | ----- | + | |
- | ====实验任务==== | + | --- |
+ | |||
+ | #### 实验任务 | ||
* 任务:基于 STEP-MAX10M08核心板 和 STEP BaseBoard V3.0底板 完成串口监视系统设计并观察调试结果。 | * 任务:基于 STEP-MAX10M08核心板 和 STEP BaseBoard V3.0底板 完成串口监视系统设计并观察调试结果。 | ||
行 7: | 行 9: | ||
* 解析:通过FPGA编程驱动底板上的CP2102串口通信模块,接收来自PC(串口调试助手)或其他串口设备的数据,经过处理,最后通过驱动8位扫描式数码管模块,将接收到的数据显示在底板数码管上。 | * 解析:通过FPGA编程驱动底板上的CP2102串口通信模块,接收来自PC(串口调试助手)或其他串口设备的数据,经过处理,最后通过驱动8位扫描式数码管模块,将接收到的数据显示在底板数码管上。 | ||
- | ====实验目的==== | + | #### 实验目的 |
本实验主要学习串口(UART)总线工作原理、协议及相关知识,练习如何使用FPGA驱动CP2102模块实现串口通信设计,同时复习上节中扫描式数码管模块的实例化应用。 | 本实验主要学习串口(UART)总线工作原理、协议及相关知识,练习如何使用FPGA驱动CP2102模块实现串口通信设计,同时复习上节中扫描式数码管模块的实例化应用。 | ||
行 14: | 行 16: | ||
* 完成串口监视系统设计实现 | * 完成串口监视系统设计实现 | ||
- | ====设计框图==== | + | #### 设计框图 |
根据前面的实验解析我们可以得知,该设计可以拆分成三个功能模块实现, | 根据前面的实验解析我们可以得知,该设计可以拆分成三个功能模块实现, | ||
行 24: | 行 26: | ||
* Uart_Rx:根据数据传输速率节拍控制UART通信数据格式。 | * Uart_Rx:根据数据传输速率节拍控制UART通信数据格式。 | ||
- | {{:6-Top-Down层次设计.png?500|Top-Down层次设计}}{{:6-模块结构设计.png?500|模块结构设计}} | + | {{:6-Top-Down层次设计.png?600|Top-Down层次设计}}{{:6-模块结构设计.png?500|模块结构设计}} |
- | ====实验原理==== | + | |
- | ===UART接口介绍=== | + | #### 实验原理 |
+ | |||
+ | ##### UART接口介绍 | ||
{{:6-UART通信接口.png?400|UART通信接口}} | {{:6-UART通信接口.png?400|UART通信接口}} | ||
行 53: | 行 56: | ||
* 空闲位:处于逻辑 1 状态,表示当前线路上没有资料传送。 | * 空闲位:处于逻辑 1 状态,表示当前线路上没有资料传送。 | ||
- | ===UART模块连接=== | + | ##### UART模块连接 |
STEP BaseBoard V3.0底板上的基于CP2102方案的UART通信模块电路图如下: | STEP BaseBoard V3.0底板上的基于CP2102方案的UART通信模块电路图如下: | ||
- | {{:6-UART通信模块电路.png?600|UART通信模块电路}} | + | {{:6-UART通信模块电路.png?1000|UART通信模块电路}} |
上图为基于CP2102方案的UART通信模块电路图,可以看到CP2102方案非常简洁,无需外置USB通信时钟晶体(内部集成),CP2102芯片TXD和RXD分别与FPGA芯片RXD和TXD连接,同时两个信号都连接了LED灯,这样当UART通信时,随着数据传输对应LED灯也会快速闪烁,起到UART通信指示灯的作用。CP2102芯片DTR和RTS通过两个三极管搭建流控电路,连接WIFI模块ESP8266-12F,使用UART模块烧写ESP8266模块的固件时就无需手动进入固件烧写模式了,这个会在后续涉及WIFI通信的实验中详细介绍,这里可以不用理会。 | 上图为基于CP2102方案的UART通信模块电路图,可以看到CP2102方案非常简洁,无需外置USB通信时钟晶体(内部集成),CP2102芯片TXD和RXD分别与FPGA芯片RXD和TXD连接,同时两个信号都连接了LED灯,这样当UART通信时,随着数据传输对应LED灯也会快速闪烁,起到UART通信指示灯的作用。CP2102芯片DTR和RTS通过两个三极管搭建流控电路,连接WIFI模块ESP8266-12F,使用UART模块烧写ESP8266模块的固件时就无需手动进入固件烧写模式了,这个会在后续涉及WIFI通信的实验中详细介绍,这里可以不用理会。 | ||
- | ===UART驱动实现=== | + | ##### UART驱动实现 |
+ | {{:6-串行总线对比.png?600|串行总线对比}} | ||
+ | SPI、I2C、UART总线对比表: | ||
+ | |SPI总线 |I2C总线 |UART总线| | ||
+ | |SS |<wrap hi>SCL</wrap> |TXD| | ||
+ | |<wrap hi>SCK</wrap> |SDA |RXD| | ||
+ | |MOSI/MISO| | ||
- | ===系统总体实现=== | + | 对于SPI总线,通信双方在总线使能的情况下,通过SCK的上升沿或下降沿触发完成总线数据的采样,这样通信双方就可以准确的接收到对方传送的数据了。对于I2C总线,通信接收方通过SCL的高电平触发完成总线数据的采样。综上,SPI总线中的SCK和I2C总线中的SCL在通信中起到时钟的作用,接收方都是根据时钟的对应状态采样数据,最终保证通信能够正常进行。 |
+ | 对于UART总线,TXD和RXD分别用于发送和接收数据,相当于两根独立工作的单线总线,没有了时钟线的配合,那么接收端应该怎样获取发送端传输的数据呢?其实也是有方法的,那就是通信双方需要约定好UART总线数据传输的<wrap hi>通信速率</wrap>和<wrap hi>时序格式</wrap>。 | ||
+ | **<wrap hi>通信速率</wrap>** | ||
- | ====实验步骤==== | + | UART的数据传输速度用波特率来描述,也就是UART每秒接收或发送的数据位。例如9600波特率表示每秒钟发送或接收9600比特的数据,即发送端需要将发送的每个数据位保持对应的时间,计算如下: |
+ | * 1s / 9600 = 1000000us / 9600 = 104.17us | ||
+ | |||
+ | 小脚丫硬件上使用12MHz的时钟晶振,如果以12MHz时钟信号作为系统时钟,使用计数器延时完成UART通信数据采样,那么计数器延时计数终值计算如下: | ||
+ | * 12M / 9600 = 1250 | ||
+ | |||
+ | 因为波特率是协议里约定的,为保证协议的通用性和灵活性,波特率参数有固定的选项,不可以随意设置(如果UART通信双方都是自己编程的,可以根据自己的要求定义自己需要的波特率,这种情况除外),波特率参数选项很多,大家可以打开串口调试助手工具找到波特率配置列表查看,我们比较常用的波特率值有以下几种: | ||
+ | |||
+ | UART常用波特率: | ||
+ | |1200 |4800 |9600 |38400 |115200| | ||
+ | |||
+ | **<wrap hi>时序格式</wrap>** | ||
+ | |||
+ | 关于时序格式在前面UART接口介绍部分也简单说了一下,通信过程中时序依次为:起始位、数据位、校验位、停止位、空闲位,其中数据位可以是5~8位,本设计我们使用8位数据,校验位可以省略,最后确定的时序格式如下: | ||
+ | |||
+ | {{:6-本实验UART通信时序.png?600|本实验UART通信时序}} | ||
+ | |||
+ | 前面所说的通信速率和时序格式其实就是UART通信中的两个重要的参数,需要传输的数据根据通信速率的节拍按照UART的时序格式输出,就可以实现UART通信了,可以按照下面三个步骤实现。 | ||
+ | - 将需要发送的数据与起始位和停止位组成10bit位宽的数据 | ||
+ | - 计数器计数延迟产生相应波特率需要的时序节拍 | ||
+ | - 数据按照(起始位—bit0~bit7—停止位)的时序串行输出 | ||
+ | |||
+ | 例如,将8‘h73和8’h5a通过UART发送的时序,红色箭头为波特率对应的节拍点 | ||
+ | |||
+ | {{:6-UART发送数据实例.png?600|UART发送数据实例}} | ||
+ | |||
+ | 对于UART发送数据来说,波特率节拍是自己产生的,数据是自己主动发出的,逻辑相对简单,而当UART接收数据的时候,因为不确定对方什么时候发送数据,所以需要对RX信号持续检测,当检测到有数据传送时,根据约定的波特率节拍采样,可以按照下面三个步骤实现。 | ||
+ | - 检测UART的RXD信号的下降沿(自锁,完成接收后再解锁继续检测) | ||
+ | - 接收采样时,采样点应该在计数器的中值点进行 | ||
+ | - 将采样后的数据按照UART时序的要求重新组成8bit的数据 | ||
+ | |||
+ | 例如,当UART的RX端接收到数据8‘h73和8’h5a的时候,红色箭头为检测到数据传输的点,绿色箭头为对应的采样节拍点(采样点在数据中间最是稳定)。 | ||
+ | |||
+ | {{:6-UART接收数据实例.png?600|UART接收数据实例}} | ||
+ | |||
+ | 通过以上理论,我们了解了UART发送和接收数据的整个流程,两个过程中我们都需要波特率节拍,那么我们就可以设计一个节拍模块Baud,这样我们的发送和接收都可以实例化节拍模块用于产生对应波特率的节拍信号。 | ||
+ | |||
+ | **节拍模块Baud设计实现:** | ||
+ | |||
+ | 节拍模块Baud的端口程序实现如下: | ||
+ | <code verilog> | ||
+ | module Baud # | ||
+ | ( | ||
+ | parameter BPS_PARA = 1250 //12MHz时钟时参数1250对应9600的波特率 | ||
+ | ) | ||
+ | ( | ||
+ | input clk, //系统时钟 | ||
+ | input rst_n, //系统复位,低有效 | ||
+ | input bps_en, //接收或发送时钟使能 | ||
+ | output reg bps_clk //接收或发送时钟输出 | ||
+ | ); | ||
+ | </code> | ||
+ | |||
+ | 设计一个计数器用于分频产生对应波特率节拍信号,因为UART随时可能接收数据,所以节拍模块必须随时待命,保持计数器清零,当需要节拍信号时精准地输出。 | ||
+ | |||
+ | 计数器设计程序实现如下: | ||
+ | <code verilog> | ||
+ | reg [12:0] cnt; | ||
+ | //计数器计数满足波特率时钟要求 | ||
+ | always @ (posedge clk or negedge rst_n) begin | ||
+ | if(!rst_n) | ||
+ | cnt <= 1'b0; | ||
+ | else if((cnt >= BPS_PARA-1)||(!bps_en)) //当时钟信号不使能(bps_en为低电平)时,计数器清零并停止计数 | ||
+ | cnt <= 1'b0; //当时钟信号使能时,计数器对系统时钟计数,周期为BPS_PARA个系统时钟周期 | ||
+ | else | ||
+ | cnt <= cnt + 1'b1; | ||
+ | end | ||
+ | </code> | ||
+ | |||
+ | 当bps_en(高有效)使能,计数器计数周期由参数BPS_PARA来决定,前面数据接收时序部分了解到,从RX检测到下降沿开始计数器工作,到数据采样点需要半个节拍的时间,而数据发送时只要保证相邻两个节拍点之间的时间为一个计数器周期即可,所以我们可以在计数器计数到中值时产生一个脉冲信号充当节拍信号。 | ||
+ | |||
+ | 节拍信号产生程序实现如下: | ||
+ | <code verilog> | ||
+ | //产生相应波特率的时钟节拍,接收模块将以此节拍进行UART数据接收 | ||
+ | always @ (posedge clk or negedge rst_n) begin | ||
+ | if(!rst_n) | ||
+ | bps_clk <= 1'b0; | ||
+ | else if(cnt == (BPS_PARA>>1)) //右移一位等于除以2,终值BPS_PARA为数据更替点,中值数据稳定,做采样点 | ||
+ | bps_clk <= 1'b1; | ||
+ | else | ||
+ | bps_clk <= 1'b0; | ||
+ | end | ||
+ | </code> | ||
+ | |||
+ | **发送模块Uart_Tx设计实现:** | ||
+ | |||
+ | 前级电路通过tx_data_valid和tx_data_in将需要发送的数据传输进来,当tx_data_valid有脉冲信号时,tx_data_in信号为有效数据,拼接起始位和停止位后赋值给tx_data_r,同时控制节拍使能信号使能并自锁,然后等发送完10bit数据后解除使能。 | ||
+ | |||
+ | 数据发送控制程序实现如下: | ||
+ | <code verilog> | ||
+ | output reg bps_en; //发送时钟使能 | ||
+ | input bps_clk; //发送时钟输入 | ||
+ | input tx_data_valid; //发送数据有效脉冲 | ||
+ | input [7:0] tx_data_in; //要发送的数据 | ||
+ | output reg uart_tx; //UART发送输出 | ||
+ | |||
+ | reg [3:0] num; | ||
+ | reg [9:0] tx_data_r; //融合了起始位和停止位的数据 | ||
+ | //驱动发送数据操作 | ||
+ | always @ (posedge clk or negedge rst_n) begin | ||
+ | if(!rst_n) begin | ||
+ | bps_en <= 1'b0; | ||
+ | tx_data_r <= 10'd0; | ||
+ | end else if(tx_data_valid && (!bps_en))begin | ||
+ | bps_en <= 1'b1; //当检测到接收时钟使能信号的下降沿,表明接收完成,需要发送数据,使能发送时钟使能信号 | ||
+ | tx_data_r <= {1'b1,tx_data_in,1'b0}; | ||
+ | end else if(num==4'd10) begin | ||
+ | bps_en <= 1'b0; //一次UART发送需要10个时钟信号,然后结束 | ||
+ | end | ||
+ | end | ||
+ | </code> | ||
+ | |||
+ | UART数据发送时序程序实现如下: | ||
+ | <code verilog> | ||
+ | //当处于工作状态中时,按照发送时钟的节拍发送数据 | ||
+ | always @ (posedge clk or negedge rst_n) begin | ||
+ | if(!rst_n) begin | ||
+ | num <= 1'b0; | ||
+ | uart_tx <= 1'b1; | ||
+ | end else if(bps_en) begin | ||
+ | if(bps_clk) begin | ||
+ | num <= num + 1'b1; | ||
+ | uart_tx <= tx_data_r[num]; | ||
+ | end else if(num>=4'd10) | ||
+ | num <= 4'd0; | ||
+ | end | ||
+ | end | ||
+ | </code> | ||
+ | |||
+ | 将节拍模块Baud和发送模块Uart_tx实例化并连接,完成发送功能的设计,如下 | ||
+ | |||
+ | {{:6-UART发送功能设计实现.png?600|UART发送功能设计实现}} | ||
+ | |||
+ | **接收模块Uart_Rx设计实现:** | ||
+ | |||
+ | 首先对RX信号多级缓存消除亚稳态,同时检测下降沿,程序实现如下: | ||
+ | <code verilog> | ||
+ | input uart_rx; //UART接收输入 | ||
+ | |||
+ | reg uart_rx0,uart_rx1,uart_rx2; | ||
+ | //多级延时锁存去除亚稳态 | ||
+ | always @ (posedge clk) begin | ||
+ | uart_rx0 <= uart_rx; | ||
+ | uart_rx1 <= uart_rx0; | ||
+ | uart_rx2 <= uart_rx1; | ||
+ | end | ||
+ | |||
+ | //检测UART接收输入信号的下降沿 | ||
+ | wire neg_uart_rx = uart_rx2 & ~uart_rx1; | ||
+ | </code> | ||
+ | |||
+ | 当检测RX有下降沿后,使能节拍使能信号,同时自锁直到完成接收操作后再复位节拍使能信号。程序实现如下: | ||
+ | <code verilog> | ||
+ | reg [3:0] num; | ||
+ | //接收时钟使能信号的控制 | ||
+ | always @ (posedge clk or negedge rst_n) begin | ||
+ | if(!rst_n) | ||
+ | bps_en <= 1'b0; | ||
+ | else if(neg_uart_rx && (!bps_en)) | ||
+ | //当空闲状态(bps_en为低电平)时检测到UART接收信号下降沿,进入工作状态(bps_en为高电平),控制时钟模块产生接收时钟 | ||
+ | bps_en <= 1'b1; | ||
+ | else if(num==4'd9) //当完成一次UART接收操作后,退出工作状态,恢复空闲状态 | ||
+ | bps_en <= 1'b0; | ||
+ | end | ||
+ | </code> | ||
+ | |||
+ | 根据节拍信号完成UART总线的数据采样,得到8位有效数据,程序实现如下: | ||
+ | <code verilog> | ||
+ | reg [7:0] rx_data; | ||
+ | //当处于工作状态中时,按照接收时钟的节拍获取数据 | ||
+ | always @ (posedge clk or negedge rst_n) begin | ||
+ | if(!rst_n) begin | ||
+ | num <= 4'd0; | ||
+ | rx_data <= 8'd0; | ||
+ | end else if(bps_en) begin | ||
+ | if(bps_clk) begin | ||
+ | num <= num + 1'b1; | ||
+ | if(num<=4'd8) rx_data[num-1] <= uart_rx1; //先接受低位再接收高位,8位有效数据 | ||
+ | end else if(num == 4'd9) begin //完成一次UART接收操作后,将获取的数据输出 | ||
+ | num <= 4'd0; | ||
+ | end | ||
+ | end else begin | ||
+ | num <= 4'd0; | ||
+ | end | ||
+ | end | ||
+ | </code> | ||
+ | |||
+ | 当UART接收操作完成后,将得到的8位有效数据输出给后级电路,程序实现如下: | ||
+ | <code verilog> | ||
+ | //将接收的数据输出,同时控制输出有效信号产生脉冲 | ||
+ | always @ (posedge clk or negedge rst_n) begin | ||
+ | if(!rst_n) begin | ||
+ | rx_data_out <= 8'd0; | ||
+ | rx_data_valid <= 1'b0; | ||
+ | end else if(num == 4'd9) begin | ||
+ | rx_data_out <= rx_data; | ||
+ | rx_data_valid <= 1'b1; | ||
+ | end else begin | ||
+ | rx_data_out <= rx_data_out; | ||
+ | rx_data_valid <= 1'b0; | ||
+ | end | ||
+ | end | ||
+ | </code> | ||
+ | |||
+ | 最后将节拍模块Baud和接收模块Uart_rx实例化并连接,完成发送功能的设计,如下 | ||
+ | |||
+ | {{:6-UART发送功能设计实现.png?600|UART发送功能设计实现}} | ||
+ | |||
+ | 整个UART驱动设计是由两个独立的功能组合而成:发送功能部分和接收功能部分。UART功能总体设计框图如下: | ||
+ | |||
+ | {{:6-UART-RTL设计框图.png?600|RTL设计框图}} | ||
+ | |||
+ | 当我们需要UART发送数据的时候只需要实例化发送功能部分设计,需要UART接收数据的时候只需要实例化接收功能部分设计,例如本设计中FPGA驱动UART模块接收电脑串口调试助手发出的数据,所以我们就只需要实例化接收功能部分设计即可。 | ||
+ | |||
+ | ##### 系统总体实现 | ||
+ | |||
+ | 刚刚学习了UART通信模块,本设计只需要使用接收功能部分设计,每一次通信都会得到一个8位数据,怎样将8位数据对应得数据显示在数码管上呢?我来先来了解一下UART接受到的8位数据与要显示数字的关系 | ||
+ | |||
+ | {{:6-串口调试助手界面.png?600|串口调试助手界面}} | ||
+ | |||
+ | 上图为电脑端友善串口调试助手的界面,当我们将硬件连接,在串口设置串口选定串口对应的端口,并按上图配置波特率、数据位、校验位、停止位、流控等,点击开始建立连接,接下来我们就可以在串口发送窗口输入要发送的数据,点击发送后数据传输出去。在发送设置有两个选项:ASCII和Hex , | ||
+ | * 当选择ASCII的时候,通过UART发出的数据是数据窗口中字符的ASCII码值,每个字符的ASCII码值都是8位数据,所以窗口中字符数量与UART传输的次数是相等的,同时数字的值与ASCII码值相差48,例如数字0的ASCII码值为48。 | ||
+ | * 当选择Hex的时候,通过UART发出的数据(必须是16进制数据)就是数据窗口中的数据本身,这样每次UART传输都会发送两个数字,如果只发送一个数字,则高位补零组成8位数据,例如发送数字1,实际UART传输的数据为8‘h01。 | ||
+ | |||
+ | 我们设计一个32位的移位寄存器对应8位数码管,按照BCD码格式每4位表示一个数字,每次接收到UART数据都存到移位寄存器中,同时控制数码管显示相应的数码管位,Decoder程序实现如下: | ||
+ | <code verilog> | ||
+ | `ifdef HEX_FORMAT //如果用define定义过HEX_FORMAT | ||
+ | //采用16进制格式,接收到的数据等于数值本身 | ||
+ | wire [7:0] seg_data_r = rx_data_out; | ||
+ | |||
+ | //移位寄存器,对应8位数码管数据BCD码 | ||
+ | always @ (posedge rx_data_valid or negedge rst_n) begin | ||
+ | if(!rst_n) seg_data <= 1'b0; | ||
+ | else seg_data <= {seg_data[23:0],seg_data_r}; | ||
+ | end | ||
+ | |||
+ | //移位寄存器,对应8位数码管数据显示使能 | ||
+ | always @ (posedge rx_data_valid or negedge rst_n) begin | ||
+ | if(!rst_n) data_en <= 1'b0; | ||
+ | else data_en <= {data_en[5:0],2'b11}; | ||
+ | end | ||
+ | `else | ||
+ | //采用字符格式,接收到的数据为字符ASCII码值,与数字值相差48 | ||
+ | wire [7:0] seg_data_r = rx_data_out - 8'd48; | ||
+ | |||
+ | //移位寄存器,对应8位数码管数据BCD码 | ||
+ | always @ (posedge rx_data_valid or negedge rst_n) begin | ||
+ | if(!rst_n) seg_data <= 1'b0; | ||
+ | else seg_data <= {seg_data[27:0],seg_data_r[3:0]}; | ||
+ | end | ||
+ | |||
+ | //移位寄存器,对应8位数码管数据显示使能 | ||
+ | always @ (posedge rx_data_valid or negedge rst_n) begin | ||
+ | if(!rst_n) data_en <= 1'b0; | ||
+ | else data_en <= {data_en[6:0],1'b1}; | ||
+ | end | ||
+ | `endif | ||
+ | </code> | ||
+ | |||
+ | 上面程序中`ifdef……`else……`endif语句为预编译指令,与C预演类似。如果我们使用串口助手Hex(16进制)格式发送数据,需要在程序中使用define定义参数HEX_FORMAT,如果使用ASCII格式发送数据,则不需要定义。 | ||
+ | |||
+ | <code verilog> | ||
+ | `define HEX_FORMAT //串口助手使用Hex格式发送时定义HEX_FORMAT,否则不定义 | ||
+ | </code> | ||
+ | |||
+ | 综合后的设计框图如下: | ||
+ | |||
+ | {{:6-系统-RTL设计框图.png?800|RTL设计框图}} | ||
+ | |||
+ | |||
+ | #### 实验步骤 | ||
- 双击打开Quartus Prime工具软件; | - 双击打开Quartus Prime工具软件; | ||
- 新建工程:File → New Project Wizard(工程命名,工程目录选择,设备型号选择,EDA工具选择); | - 新建工程:File → New Project Wizard(工程命名,工程目录选择,设备型号选择,EDA工具选择); | ||
行 80: | 行 361: | ||
- | ====实验现象==== | + | #### 实验现象 |
+ | |||
+ | 使用两根Micro-USB线同时连接核心板和底板的USB接口,将程序下载到FPGA中,数码管处于不显示的状态,打开电脑上的串口调试助手,按照前面图片配置相应参数,在数据发送窗口输入数字,点击发送观察底板数码管的变化,重新输入数字,点击发送再次观察底板数码管的变化。 |