本帖最后由 小王子90 于 2017-7-12 13:07 编辑
小梅哥AC620开发板试用体验继续 最近在准备电赛,在做数字频率计相关的题目时恰好有FPGA相关的应用。考虑到其高速运算性能,在数字频率计这样的高速运用中有很大的用武之地,因此今天学习一下如何利用FPGA进行收发数据。为了保证电子系统中的上位机、下位机正确通信传输数据,我们需要为它们编写通信协议。
目前常用的通信协议有I²C,SPI,UART等等,考虑到UART电路结构简单且大部分人都有基础涉猎,因此我决定先从UART开始学起。
UART通信在使用前需要做多项设置,最常见的设置包括数据位数、波特率大小、奇偶校验类型和停止位数。而对这些参数设置的过程,即是我们所谓的编写协议。下面,我分别就发送端与接收端进行简要的试用介绍
============================分割线===========================
一、波特率设定
有过 单片机开发经验的朋友们都知道,为了实现不同波特率的传输,我们通常利用查找表的思想,对板载计数器分别赋以不同的初始计数值,本模块的设计需要同时保证复用性,当需要不同波特率时,只置不同计数器的起始值即可。具体代码实现如下:
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- bps_DR <= 16'd5207;
- else begin
- case(baud_set)
- 0:bps_DR <= 16'd5207;
- 1:bps_DR <= 16'd2603;
- 2:bps_DR <= 16'd1301;
- 3:bps_DR <= 16'd867;
- 4:bps_DR <= 16'd433;
- default:bps_DR <= 16'd5207;
- endcase
- end
-
- //counter
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- div_cnt <= 16'd0;
- else if(uart_state)begin
- if(div_cnt == bps_DR)
- div_cnt <= 16'd0;
- else
- div_cnt <= div_cnt + 1'b1;
- end
- else
- div_cnt <= 16'd0;
-
- // bps_clk gen
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- bps_clk <= 1'b0;
- else if(div_cnt == 16'd1)
- bps_clk <= 1'b1;
- else
- bps_clk <= 1'b0;
复制代码
其中,div_cnt的意义在于作为波特率计数器,每计满一次,产生一个系统周期时钟长度的高电平脉冲。
二、传输模块设置
为了传输数据,我将传输模块中必要的功能作出如下归纳:
1.传输完成标志信号,用以用一个周期的脉冲标志传输完成
2.传输状态标志信号,用以标志传输的正常进行与否,高电平正常,低电平停止
3.波特率计数器,用以表征传输循环的位置,即传输到了哪一位信号
4.输入数据寄存器,由于RS232位异步传输,为了保证输入数据的稳定,需要用寄存器将其稳定化。
其具体代码实现如下所示:
- //Transport State
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- uart_state <= 1'b0;
- else if(send_en)
- uart_state <= 1'b1;
- else if(bps_cnt == 4'd11)
- uart_state <= 1'b0;
- else
- uart_state <= uart_state;
-
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- r_data_byte <= 8'd0;
- else if(send_en)
- r_data_byte <= data_byte;
- else
- r_data_byte <= r_data_byte;
- //counter
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- div_cnt <= 16'd0;
- else if(uart_state)begin
- if(div_cnt == bps_DR)
- div_cnt <= 16'd0;
- else
- div_cnt <= div_cnt + 1'b1;
- end
- else
- div_cnt <= 16'd0;
-
- // bps_clk gen
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- bps_clk <= 1'b0;
- else if(div_cnt == 16'd1)
- bps_clk <= 1'b1;
- else
- bps_clk <= 1'b0;
-
- //bps counter
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- bps_cnt <= 4'd0;
- else if(bps_cnt == 4'd11)
- bps_cnt <= 4'd0;
- else if(bps_clk)
- bps_cnt <= bps_cnt + 1'b1;
- else
- bps_cnt <= bps_cnt;
-
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- Tx_Done <= 1'b0;
- else if(bps_cnt == 4'd11)
- Tx_Done <= 1'b1;
- else
- Tx_Done <= 1'b0;
-
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- Rs232_Tx <= 1'b1;
- else begin
- case(bps_cnt)
- 0:Rs232_Tx <= 1'b1;
- 1:Rs232_Tx <= START_BIT;
- 2:Rs232_Tx <= r_data_byte[0];
- 3:Rs232_Tx <= r_data_byte[1];
- 4:Rs232_Tx <= r_data_byte[2];
- 5:Rs232_Tx <= r_data_byte[3];
- 6:Rs232_Tx <= r_data_byte[4];
- 7:Rs232_Tx <= r_data_byte[5];
- 8:Rs232_Tx <= r_data_byte[6];
- 9:Rs232_Tx <= r_data_byte[7];
- 10:Rs232_Tx <= STOP_BIT;
- default:Rs232_Tx <= 1'b1;
- endcase
- end
复制代码
注:此处为了方便大家参阅教程,我直接贴上了小梅哥的源码。仅仅将其作了简单分类。
可以看到,基本的传输模块已经设置完成,接下来,我们队它进行测试验证。
三、模块的测试与验证
我们可以用testbench去检验时序图或是直接用issp例化,在电脑上进行验证。此处,我们用串口猎人直接验证,可以看到如下结果:
至此,数据发送模块基本得以实现。接下来,介绍数据接收模块。
=============================分割线===================================
数据接收模块与发送模块大致相似,实际上,以模块来说,只需要将Uart中的tx口接到rx口,即从发送模式变为接收模式即可。其中,波特率的设定与发送端并无二致,我们需要特别注意的是数据采样的方式。
一、数据采样时钟
由于在采样中,由于多种影响可能产生误判,因此我们把一位数据分别16个时钟采样,其稳定阶段大致在中间位置。如小梅哥学习笔记中图17.3所示。那么,我们需要做的是产生一个时钟信号的16分频信号作为采样信号。其代码实现如下所示:
- //counter
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- div_cnt <= 16'd0;
- else if(uart_state)begin
- if(div_cnt == bps_DR)
- div_cnt <= 16'd0;
- else
- div_cnt <= div_cnt + 1'b1;
- end
- else
- div_cnt <= 16'd0;
-
- // bps_clk gen
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- bps_clk <= 1'b0;
- else if(div_cnt == 16'd1)
- bps_clk <= 1'b1;
- else
- bps_clk <= 1'b0;
复制代码
接下来,进行数据接收模块设计。
二、数据接收模块
同样的,我将数据接收模块必要的功能归纳如下:
1.接收完成标志信号
2.数据状态判断模块(所采样到的信号是否有效?)
3.RS232同步(与发送相同)
4.数据接收状态模块(是否正常工作?)
其代码实现如下所示:
- //同步寄存器,消除亚稳态
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)begin
- s0_Rs232_Rx <= 1'b0;
- s1_Rs232_Rx <= 1'b0;
- end
- else begin
- s0_Rs232_Rx <= Rs232_Rx;
- s1_Rs232_Rx <= s0_Rs232_Rx;
- end
-
- //数据寄存器
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)begin
- tmp0_Rs232_Rx <= 1'b0;
- tmp1_Rs232_Rx <= 1'b0;
- end
- else begin
- tmp0_Rs232_Rx <= s1_Rs232_Rx;
- tmp1_Rs232_Rx <= tmp0_Rs232_Rx;
- end
-
- assign nedege = !tmp0_Rs232_Rx & tmp1_Rs232_Rx;
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- data_byte <= 8'd0;
- else if(bps_cnt == 8'd159)begin
- data_byte[0] <= r_data_byte[0][2];
- data_byte[1] <= r_data_byte[1][2];
- data_byte[2] <= r_data_byte[2][2];
- data_byte[3] <= r_data_byte[3][2];
- data_byte[4] <= r_data_byte[4][2];
- data_byte[5] <= r_data_byte[5][2];
- data_byte[6] <= r_data_byte[6][2];
- data_byte[7] <= r_data_byte[7][2];
- end
-
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)begin
- START_BIT = 3'd0;
- r_data_byte[0] <= 3'd0;
- r_data_byte[1] <= 3'd0;
- r_data_byte[2] <= 3'd0;
- r_data_byte[3] <= 3'd0;
- r_data_byte[4] <= 3'd0;
- r_data_byte[5] <= 3'd0;
- r_data_byte[6] <= 3'd0;
- r_data_byte[7] <= 3'd0;
- STOP_BIT = 3'd0;
- end
- else if(bps_clk)begin
- case(bps_cnt)
- 0:begin
- START_BIT = 3'd0;
- r_data_byte[0] <= 3'd0;
- r_data_byte[1] <= 3'd0;
- r_data_byte[2] <= 3'd0;
- r_data_byte[3] <= 3'd0;
- r_data_byte[4] <= 3'd0;
- r_data_byte[5] <= 3'd0;
- r_data_byte[6] <= 3'd0;
- r_data_byte[7] <= 3'd0;
- STOP_BIT = 3'd0;
- end
- 6,7,8,9,10,11:START_BIT <= START_BIT + s1_Rs232_Rx;
- 22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_Rs232_Rx;
- 38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_Rs232_Rx;
- 54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_Rs232_Rx;
- 70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_Rs232_Rx;
- 86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_Rs232_Rx;
- 102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_Rs232_Rx;
- 118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_Rs232_Rx;
- 134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_Rs232_Rx;
- 150,151,152,153,154,155:STOP_BIT <= STOP_BIT + s1_Rs232_Rx;
- default:
- begin
- START_BIT = START_BIT;
- r_data_byte[0] <= r_data_byte[0];
- r_data_byte[1] <= r_data_byte[1];
- r_data_byte[2] <= r_data_byte[2];
- r_data_byte[3] <= r_data_byte[3];
- r_data_byte[4] <= r_data_byte[4];
- r_data_byte[5] <= r_data_byte[5];
- r_data_byte[6] <= r_data_byte[6];
- r_data_byte[7] <= r_data_byte[7];
- STOP_BIT = STOP_BIT;
- end
- endcase
- end
-
- always@(posedge Clk or negedge Rst_n)
- if(!Rst_n)
- uart_state <= 1'b0;
- else if(nedege)
- uart_state <= 1'b1;
- else if(Rx_Done || (bps_cnt == 8'd12 && (START_BIT > 2)))
- uart_state <= 1'b0;
- else
- uart_state <= uart_state;
复制代码
其中值得一提的是,如何判断信号是否有效。如上所述,在一位数据的中央位置,信号比较稳定。因此在这六次采样信号之中,若有三次以上为1,则可视为高电平。考虑到最高为在1+1+1时必定大于1,在高电平次数小于3时必定小鱼1。因此可直接根据高电平判断。
另外,这里我们定义的r_data_byte可以参考为C语言中的二维数组,是一个存储器型变量。在这里,他是一个有8位寻址地址的3位存储器。实际上,我们传输的数据即是在某一个八位地址之下,经过数据判决(是否为1)之后的这个三维存储器的值。关于存储器型变量的定义大家可以参照Verilog相关教材,小梅哥的教学视频中也介绍得比较充分。
至此,数据接收模块基本设置完成,接下来对其进行测试验证。
三、模块的测试验证
与发送模块类似,我们依旧利用ISSP进行例化,此时,同样利用串口猎人,我们在串口猎人处发送数据。其结果如下所示:
================================分割线=================================
上面就是我这次对AC620的串口数据发送、接收模块的使用体验。除了再次熟悉了电子系统中上下位机交换数据的必要步骤之外,更加深了对FPGA工程应用体验的认识。在此再次对小梅哥无比坚实的资源支持进行感谢!学习过程中遇到的模块理解问题,在小梅哥配套学习笔记中都有着非常详细的讲解。小梅哥AC620开发板及学习套件组绝对是入手FPGA工程开发的良心利器。
学无止境,希望大家都能加油!材料支持如此丰厚,不怕你不会,就怕你不学。
|