本次设计的源码在http://download.csdn.net/detail/noticeable/9912383 下载
实验目的:通过uart通讯协议的编写,了解FPGA的通讯协议编写的方法。
实验现象:FPAG可以通过USB转TTL工具向电脑收发数据。
相关知识点:1、uart通讯协议是什么及其内容。2、in system surce and probes(editor)调试工具的使用。
关于串行通讯:串口通讯协议是一种主机之间常用的通讯协议,通过模块按位发送和接收字节,可以达到通讯的目的,其通讯只需要三根根数据线(GND,RXD,TXD)即可完成。其中串口通讯最重要的参数是波特率,数据位,停止位和奇偶校验位,下面来一一讲解这 些参数的作用。
(1)波特率:波特率是串口的通讯速率,常见的比特率为1200bps、4800bps、9600bps、38400bps、115200bps、256000bps、500000bps,这里bps的意思是bit/s,因此可以知道,波特率实际上是每秒可以传输的bit的个数,由此可知波特率与时钟是直接挂钩的,比如波特率位9600bps,那么时钟就是9600hz,即每秒内产生9600个时钟,每个时钟周期发送1bit的数据。(这里注意:波特率越高,传输距离越短)
波特率分频计数器的方法:
(2)数据位:数据位可以在通讯过程中告知另一个主机,完成一次通讯过程中真正有效的数据是多少位,完成对传输数据的位数的校验。
(3)起始位、停止位:起始位位于单个数据包的第一位,停止位位于单个包的最后一位,每个包都是一个字节, 另一台主机接收到停止位时就知道信号发送已经完成了。
(4)奇偶校验位:奇偶校验是通讯协议中的一种简单的检错方法,其有时钟检错方式,:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位为1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。
项目设计:
本次设计发送模块的整体结构体如下 发送时序图
首先按照系统框图编写tx项目源码:
发生时序图
源码程序如下
module uart_tx( clk, rst_n, send_en, baud_set, tx_done, rs232_tx, data_byte, uart_state ); input clk; input rst_n; input [7:0]data_byte;//数据发送寄存器 input send_en; input [2:0]baud_set; output reg rs232_tx; //输出引脚 output reg tx_done; //传输完成的标志位 output reg uart_state; //uart_state传送状态 reg [15:0]div_cnt; //分频计数器 reg bps_clk; //波特率时钟 reg [15:0]bps_dr; //分频计数最大值 reg [3:0]bps_cnt; //波特率计数时钟 reg [7:0]r_data_byte;//发送缓存区 localparam start_bit=1'b0; localparam stop_bit=1'b1; //具有优先级的信号控制,产生控制信号 always@(posedge clk or negedge rst_n) if(!rst_n) uart_state<=0; else if(send_en==1'b1) uart_state<=1; else if(bps_cnt == 4'd11) //假设穿的是一组8位的数据+起始位+停止位 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; //设计查找表DR_LUT,通过查找表设置波特率
//1/波特率/20ns
always@(posedge clk or negedge rst_n) if(!rst_n) bps_dr<=16'd5207; else begin case (baud_set) 0:bps_dr<=16'd5207;//9600 1:bps_dr<=16'd2603;//19200 2:bps_dr<=16'd1301;//28400 3:bps_dr<=16'd867;//57600 4:bps_dr<=16'd433;//115200 // 5:bps_dr<=16'd5207; // 6:bps_dr<=16'd5207; // 7:bps_dr<=16'd5207; default bps_dr<=16'd5207;//9600 endcase end //分频计数器 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; //单周期的波特率时钟产生 always@(posedge clk or negedge rst_n) if(!rst_n) bps_clk<=1'b0; else if(div_cnt==16'd1) bps_clk<=1; else bps_clk<=0; //设计波特率计数器 always@(posedge clk or negedge rst_n) if(!rst_n) bps_cnt<=4'b0; else if (bps_cnt == 4'd11) bps_cnt<=4'b0; 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 endmodule
编写testbench文件
`timescale 1ns/1ns `define clock_period 20 module uart_tx_tb; reg clk; reg rst_n; reg [7:0]data_byte; reg send_en; reg [2:0]baud_set; wire rs232_tx; wire tx_done; wire uart_state; uart_tx uart_tx0( .clk(clk), .rst_n(rst_n), .send_en(send_en), .baud_set(baud_set), .tx_done(tx_done), .rs232_tx(rs232_tx), .data_byte(data_byte), .uart_state(uart_state) ); initial clk=1; always#(`clock_period/2) clk=~clk; initial begin rst_n<=1'b0; data_byte<=8'd0; send_en<=1'd0; baud_set=3'd4; #(`clock_period*20+1) rst_n<=1'b1; #(`clock_period*50+1) data_byte<=8'haa; send_en<=1'd1; #(`clock_period) send_en<=1'd0; @(posedge tx_done)//等待传输完成的上升沿 #(`clock_period*500)//重新发送 data_byte<=8'h55; send_en<=1'd1; #(`clock_period) send_en<=1'd0; #(`clock_period*500) $stop; end endmodule
仿真结果如下,可以看到,每当有一个send_en 时,databyte都会将串口数据输出出来。
将之前用到的key_filter文件添加进来,作为传输的控制信号。
添加IP核以便使用in system sources and probe editor 工具
将.v文件添加到file中,编写uart_top
module uart_top(clk ,rst_n,rs232_tx,key_in,led); input key_in; input clk; input rst_n; output rs232_tx; wire send_en; wire [7:0]data_byte; output led; wire key_flag,key_state; assign send_en=key_flag&!key_state;//按键检测成功且为低电平时,发生使能 uart_tx uart_tx1( .clk(clk), .rst_n(rst_n), .send_en(send_en), .baud_set(3'd0), .tx_done(), .rs232_tx(rs232_tx), .data_byte(data_byte), .uart_state(led)//将串口状态用LED显示出来 ); key_filter key_filter0( .clk(clk), .rst_n(rst_n), .key_in(key_in), .key_flag(key_flag), .key_state(key_state) ); ISSP ISSP0( .probe(), .source(data_byte) ); endmodule
进行引脚分配,进行板级验证。
由于DE1-SOC上并未板载USB转TTL模块,这里需要自备一个,然后将模块的RXD口连接到开发板上的GPIO_0D1口上,打开串口调试软件并通过USB转TTL模块连接到FPGA上(波特率设为9600),按下KEY1后,可以看到开发板上的LEDR0快速闪烁一下,串口调试助手接收到00两个数据
打开in system sources and probes editor,
修改格式为hex格式,然后输入任意数据,我这里输入66,
然后再按一下key1,可以看到串口调试软件显示出来66,即我们给的source值,这里发送端的设计就完成了,下面继续按相同的方法设计接收端的协议。
UART数据接收部分:
之前已经讲过了uart 发送端的时序图,对应的,理论上接收端的时序图如下,一般采样在每一位数据的中点是最稳定的。
但是在工业等复杂的环境中,电磁场的干扰是很强的,所以在这里需要进行抗干扰处理,需要多次采样求概率来进行接收,进行改进后的单bit数据接收方式示意图
同样编写Verilog代码:
module uart_rx(clk, rs232_rx, baud_set, rst_n, data_byte, rx_done ); input clk; input rs232_rx; input [2:0]baud_set; input rst_n; output reg [7:0]data_byte; output reg rx_done; reg s0_rs232_rx,s1_rs232_rx;//两个同步寄存器 reg tmp0_rs232_rx,tmp1_rs232_rx;//数据寄存器 wire nedege; reg [15:0]bps_dr;//分频计数器计数最大值 reg [15:0]div_cnt;//分频计数器 reg uart_state; reg bps_clk; reg [7:0]bps_cnt; reg [2:0]r_data_byte [7:0];//前面[2:0]是每一位数据的存储宽度,[7:0]指位宽 reg [2:0] start_bit,stop_bit; //异步信号同步处理,消除亚稳态,有疑惑的看之前的按键消抖部分 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;//下降沿检测 //波特率设置模块 //10^9/波特率/20ns/这里为了稳定1bit数据会采16次所以还要除16 always@(posedge clk or negedge rst_n) if(!rst_n) bps_dr<=16'd324; else begin case (baud_set) 0:bps_dr<=16'd324;//9600 1:bps_dr<=16'd162;//19200 2:bps_dr<=16'd80;//28400 3:bps_dr<=16'd53;//57600 4:bps_dr<=16'd26;//115200 default bps_dr<=16'd324;//9600 endcase end //分频计数器 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; //单周期的波特率时钟产生 always@(posedge clk or negedge rst_n) if(!rst_n) bps_clk<=1'b0; else if(div_cnt==16'd1) bps_clk<=1; else bps_clk<=0; //设计波特率计数器 always@(posedge clk or negedge rst_n) if(!rst_n) bps_cnt<=8'b0; else if(bps_cnt == 8'd159 | (bps_cnt == 8'd12 && (start_bit > 2)))//到12位的时候,start_bit>2说明接收错误,起始位不对 bps_cnt <= 8'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) rx_done<=1'b0; else if(bps_cnt==8'd159) rx_done<=1'b1; else rx_done <=1'b0; //数据读取,对每次采样进行求和值 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) 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) uart_state <= 1'b0; else if(nedege) uart_state <= 1'b1; else if(rx_done || (bps_cnt == 8'd12 && (start_bit > 2))) //接收完成或者到12位的时候,start_bit>2说明接收错误,起始位不对 uart_state <= 1'b0;//关闭传输状态位 else uart_state <= uart_state; endmodule
编写testbench并添加路径,因为testbench中调用了uart_rx,所以在添加路径的时候需要将uart.v文件添加到路径中去
`timescale 1ns/1ns `define clock_period 20 module uart_rx_tb; reg rst_n; reg clk; reg rs232_rx; wire rs232_tx; reg [2:0]baud_set; wire rx_done; wire tx_done; wire [7:0]data_byte_r; reg [7:0]data_byte_t; reg send_en; wire uart_state; uart_rx uart_rx1(.clk(clk), .rs232_rx(rs232_tx), //用输入值作为读取值 .baud_set(baud_set), .rst_n(rst_n), .data_byte(data_byte_r), .rx_done(rx_done) ); uart_tx uart_tx2( .clk(clk), .rst_n(rst_n), .send_en(send_en), .baud_set(baud_set), .tx_done(tx_done), .rs232_tx(rs232_tx), .data_byte(data_byte_t), .uart_state(uart_state)//将串口状态用LED显示出来 ); initial clk=1; always#(`clock_period/2) clk=~clk; initial begin rst_n<=1'b0; data_byte_t<=8'd0; send_en<=1'd0; baud_set=3'd4; #(`clock_period*20+1) rst_n<=1'b1; #(`clock_period*50+1) data_byte_t<=8'haa; send_en<=1'd1; #(`clock_period) send_en<=1'd0; @(posedge tx_done)//等待传输完成的上升沿 #(`clock_period*5000)//重新发送 data_byte_t<=8'h55; send_en<=1'd1; #(`clock_period) send_en<=1'd0; #(`clock_period*500) $stop; end endmodule
将uart_rx设为顶层文件,编译后进行仿真,仿真图形如下:
新建ISSP IP核,添加8个探针
在uart_top中将rs_232_rx添加进来
module uart_top(clk ,rst_n,rs232_rx,rs232_tx,key_in,led); input key_in; input clk; input rst_n; input rs232_rx; output rs232_tx; wire send_en; wire [7:0]data_byte_t; reg [7:0]data_byte_r; wire [7:0]data_rx; output led; wire key_flag,key_state; wire rx_done; assign send_en=key_flag&!key_state;//按键检测成功且为低电平时,发生使能 uart_tx uart_tx1( .clk(clk), .rst_n(rst_n), .send_en(send_en), .baud_set(3'd0), .tx_done(), .rs232_tx(rs232_tx), .data_byte(data_byte_t), .uart_state(led)//将串口状态用LED显示出来 ); uart_rx uart_rx2(.clk(clk), .rs232_rx(rs232_rx), //用输入值作为读取值 .baud_set(3'd0), .rst_n(rst_n), .data_byte(data_rx),//接收到的数据传递给data_rx; .rx_done(rx_done) ); key_filter key_filter0( .clk(clk), .rst_n(rst_n), .key_in(key_in), .key_flag(key_flag), .key_state(key_state) ); ISSP ISSP( .probe(),//调用一个ISSP 发送数据 .source(data_byte_t) ); ISSP1 ISSP2( .probe(data_byte_r),//调用一个ISSP 接收数据 .source() ); //应为data_rx可能没有接收成功,为错误量,所以需要一个中间量来进行缓冲 always@(posedge clk or negedge rst_n) if(!rst_n) data_byte_r <= 8'd0; else if(rx_done) data_byte_r<= data_rx; else data_byte_r<= data_byte_r; endmodule
分配RX引脚给GPIO0_D0,然后将程序烧写到FPGA中,将GPIO0-D0与GPIO0_D1连着直接用杜邦线连接,打开ISSP工具,将两者都设置为hex显示,且将第二个ISSP工具设置为循环扫描,在第一个ISSP中输入数据,按下按键KEY1后,看到第二个ISSP 有相应的数据变化。
此时说明整个协议编写完成 了可以完成通讯了