uart通讯协议
本次设计的源码在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 [:]data_byte;//数据发送寄存器
input send_en;
input [:]baud_set; output reg rs232_tx; //输出引脚
output reg tx_done; //传输完成的标志位
output reg uart_state; //uart_state传送状态 reg [:]div_cnt; //分频计数器
reg bps_clk; //波特率时钟
reg [:]bps_dr; //分频计数最大值
reg [:]bps_cnt; //波特率计数时钟
reg [:]r_data_byte;//发送缓存区 localparam start_bit='b0;
localparam stop_bit='b1; //具有优先级的信号控制,产生控制信号
always@(posedge clk or negedge rst_n)
if(!rst_n)
uart_state<=;
else if(send_en=='b1)
uart_state<=;
else if(bps_cnt == 'd11) //假设穿的是一组8位的数据+起始位+停止位
uart_state <= 'b0;
else
uart_state<=uart_state; //发送过程中需要保证数据是稳定的,选用一个寄存器将数据先缓存起来
always@(posedge clk or negedge rst_n)
if(!rst_n)
r_data_byte<='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<='d5207;
else begin
case (baud_set)
:bps_dr<='d5207;//9600
:bps_dr<='d2603;//19200
:bps_dr<='d1301;//28400
:bps_dr<='d867;//57600
:bps_dr<='d433;//115200
// 5:bps_dr<=16'd5207;
// 6:bps_dr<=16'd5207;
// 7:bps_dr<=16'd5207;
default
bps_dr<='d5207;//9600
endcase
end
//分频计数器
always@(posedge clk or negedge rst_n)
if(!rst_n)
div_cnt<='d0;
else if(uart_state)begin
if(div_cnt==bps_dr)//到达计数最大值时清零
div_cnt<='d0;
else div_cnt<=div_cnt+'b1;
end
else
div_cnt<='d0; //单周期的波特率时钟产生
always@(posedge clk or negedge rst_n)
if(!rst_n)
bps_clk<='b0;
else if(div_cnt=='d1)
bps_clk<=;
else
bps_clk<=; //设计波特率计数器
always@(posedge clk or negedge rst_n)
if(!rst_n)
bps_cnt<='b0;
else if (bps_cnt == 'd11)
bps_cnt<='b0;
else if (bps_clk)
bps_cnt<=bps_cnt+'b1;
else
bps_cnt<=bps_cnt; //发送完成信号
always@(posedge clk or negedge rst_n)
if(!rst_n)
tx_done<='b0;
else if(bps_cnt=='d11)
tx_done<='b1;
else
tx_done <='b0; //数据发送,即一个十选一的多路器
always@(posedge clk or negedge rst_n)
if(!rst_n)
rs232_tx<='b1;
else begin
case(bps_cnt)
:rs232_tx<='b1;
:rs232_tx<=start_bit;//起始位
:rs232_tx<=r_data_byte[];
:rs232_tx<=r_data_byte[];
:rs232_tx<=r_data_byte[];
:rs232_tx<=r_data_byte[];
:rs232_tx<=r_data_byte[];
:rs232_tx<=r_data_byte[];
:rs232_tx<=r_data_byte[];
:rs232_tx<=r_data_byte[];
:rs232_tx<=stop_bit;//结束位,本次设计不设奇偶校验位
default
rs232_tx<='b1;
endcase
end endmodule
编写testbench文件
`timescale 1ns/1ns
`define clock_period module uart_tx_tb;
reg clk;
reg rst_n;
reg [:]data_byte;
reg send_en;
reg [:]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=;
always#(`clock_period/) clk=~clk;
initial begin
rst_n<='b0;
data_byte<='d0;
send_en<='d0;
baud_set='d4;
#(`clock_period*+)
rst_n<='b1;
#(`clock_period*+)
data_byte<='haa;
send_en<='d1;
#(`clock_period)
send_en<='d0;
@(posedge tx_done)//等待传输完成的上升沿
#(`clock_period*)//重新发送
data_byte<='h55;
send_en<='d1;
#(`clock_period)
send_en<='d0;
#(`clock_period*)
$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 [:]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('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 [:]baud_set;
input rst_n;
output reg [:]data_byte;
output reg rx_done;
reg s0_rs232_rx,s1_rs232_rx;//两个同步寄存器
reg tmp0_rs232_rx,tmp1_rs232_rx;//数据寄存器
wire nedege;
reg [:]bps_dr;//分频计数器计数最大值
reg [:]div_cnt;//分频计数器
reg uart_state;
reg bps_clk;
reg [:]bps_cnt;
reg [:]r_data_byte [:];//前面[2:0]是每一位数据的存储宽度,[7:0]指位宽
reg [:] start_bit,stop_bit; //异步信号同步处理,消除亚稳态,有疑惑的看之前的按键消抖部分
always@(posedge clk or negedge rst_n)
if(!rst_n)
begin
s0_rs232_rx<='b0;
s1_rs232_rx<='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<='b0;
tmp1_rs232_rx<='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<='d324;
else begin
case (baud_set)
:bps_dr<='d324;//9600
:bps_dr<='d162;//19200
:bps_dr<='d80;//28400
:bps_dr<='d53;//57600
:bps_dr<='d26;//115200
default
bps_dr<='d324;//9600
endcase
end
//分频计数器
always@(posedge clk or negedge rst_n)
if(!rst_n)
div_cnt<='d0;
else if(uart_state)begin
if(div_cnt==bps_dr)//到达计数最大值时清零
div_cnt<='d0;
else div_cnt<=div_cnt+'b1;
end
else
div_cnt<='d0; //单周期的波特率时钟产生
always@(posedge clk or negedge rst_n)
if(!rst_n)
bps_clk<='b0;
else if(div_cnt=='d1)
bps_clk<=;
else
bps_clk<=; //设计波特率计数器
always@(posedge clk or negedge rst_n)
if(!rst_n)
bps_cnt<='b0;
else if(bps_cnt == 'd159 | (bps_cnt == 8'd12 && (start_bit > )))//到12位的时候,start_bit>2说明接收错误,起始位不对
bps_cnt <= 'd0;//接收完成或者开始检测到错误信号,波特率时钟停止
else if (bps_clk)
bps_cnt<=bps_cnt+'b1;
else
bps_cnt<=bps_cnt; //接收完成信号
always@(posedge clk or negedge rst_n)
if(!rst_n)
rx_done<='b0;
else if(bps_cnt=='d159)
rx_done<='b1;
else
rx_done <='b0; //数据读取,对每次采样进行求和值
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
start_bit='d0;
r_data_byte[]<='d0;
r_data_byte[]<='d0;
r_data_byte[]<='d0;
r_data_byte[]<='d0;
r_data_byte[]<='d0;
r_data_byte[]<='d0;
r_data_byte[]<='d0;
r_data_byte[]<='d0;
stop_bit<='d0;
end
else if(bps_clk)begin
case(bps_cnt)
:begin
start_bit = 'd0;
r_data_byte[] <= 'd0;
r_data_byte[] <= 'd0;
r_data_byte[] <= 'd0;
r_data_byte[] <= 'd0;
r_data_byte[] <= 'd0;
r_data_byte[] <= 'd0;
r_data_byte[] <= 'd0;
r_data_byte[] <= 'd0;
stop_bit = 'd0;
end
,,,,,:start_bit <= start_bit + s1_rs232_rx;
,,,,,:r_data_byte[] <= r_data_byte[] + s1_rs232_rx;
,,,,,:r_data_byte[] <= r_data_byte[] + s1_rs232_rx;
,,,,,:r_data_byte[] <= r_data_byte[] + s1_rs232_rx;
,,,,,:r_data_byte[] <= r_data_byte[] + s1_rs232_rx;
,,,,,:r_data_byte[] <= r_data_byte[] + s1_rs232_rx;
,,,,,:r_data_byte[] <= r_data_byte[] + s1_rs232_rx;
,,,,,:r_data_byte[] <= r_data_byte[] + s1_rs232_rx;
,,,,,:r_data_byte[] <= r_data_byte[] + s1_rs232_rx;
,,,,,:stop_bit <= stop_bit + s1_rs232_rx;
default:
begin
start_bit = start_bit;
r_data_byte[] <= r_data_byte[];
r_data_byte[] <= r_data_byte[];
r_data_byte[] <= r_data_byte[];
r_data_byte[] <= r_data_byte[];
r_data_byte[] <= r_data_byte[];
r_data_byte[] <= r_data_byte[];
r_data_byte[] <= r_data_byte[];
r_data_byte[] <= r_data_byte[];
stop_bit = stop_bit;
end
endcase
end always@(posedge clk or negedge rst_n)//数据提取
if(!rst_n)
data_byte <= 'd0;
else if(bps_cnt == 'd159)begin
data_byte[] <= r_data_byte[][];
data_byte[] <= r_data_byte [][];
data_byte[] <= r_data_byte[][];
data_byte[] <= r_data_byte[][];
data_byte[] <= r_data_byte[][];
data_byte[] <= r_data_byte[][];
data_byte[] <= r_data_byte[][];
data_byte[] <= r_data_byte[][];
end //控制逻辑 always@(posedge clk or negedge rst_n)
if(!rst_n)
uart_state <= 'b0;
else if(nedege)
uart_state <= 'b1;
else if(rx_done || (bps_cnt == 'd12 && (start_bit > 2))) //接收完成或者到12位的时候,start_bit>2说明接收错误,起始位不对
uart_state <= 'b0;//关闭传输状态位
else
uart_state <= uart_state; endmodule
编写testbench并添加路径,因为testbench中调用了uart_rx,所以在添加路径的时候需要将uart.v文件添加到路径中去
`timescale 1ns/1ns
`define clock_period
module uart_rx_tb;
reg rst_n;
reg clk;
reg rs232_rx;
wire rs232_tx;
reg [:]baud_set;
wire rx_done;
wire tx_done;
wire [:]data_byte_r;
reg [:]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=;
always#(`clock_period/) clk=~clk;
initial begin
rst_n<='b0;
data_byte_t<='d0;
send_en<='d0;
baud_set='d4;
#(`clock_period*+)
rst_n<='b1;
#(`clock_period*+)
data_byte_t<='haa;
send_en<='d1;
#(`clock_period)
send_en<='d0;
@(posedge tx_done)//等待传输完成的上升沿
#(`clock_period*)//重新发送
data_byte_t<='h55;
send_en<='d1;
#(`clock_period)
send_en<='d0;
#(`clock_period*)
$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 [:]data_byte_t;
reg [:]data_byte_r;
wire [:]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('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('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 <= '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 有相应的数据变化。
此时说明整个协议编写完成 了可以完成通讯了
uart通讯协议的更多相关文章
- 串行通讯协议--起止式异步通讯协议(UART)
起止式异步通讯协议: 特点与格式: 起止式异步协议的特点是一个字符一个字符传输,并且传送一个字符总是以起始位开始,以停止位结束,字符之间没有固定的时间间隔要求.其格式如图3 所示.每一个字符的前面都有 ...
- Mavlink - 无人机通讯协议
http://qgroundcontrol.org/mavlink/start mavlink协议介绍https://pixhawk.ethz.ch/mavlink/ 消息简介 MAVLink简介 M ...
- 基于dubbo框架下的RPC通讯协议性能测试
一.前言 Dubbo RPC服务框架支持丰富的传输协议.序列化方式等通讯相关的配置和扩展.dubbo执行一次RPC请求的过程大致如下:消费者(Consumer)向注册中心(Registry)执行RPC ...
- MODBUS-RTU通讯协议简介
MODBUS-RTU通讯协议简介 什么是MODBUS? MODBUS 是MODICON公司最先倡导的一种软的通讯规约,经过大多数公司 的实际应用,逐渐被认可,成为一种标准的通讯规约,只要按照这种规 ...
- 【读书笔记】iOS-防止通讯协议被轻易破解的方法
开发者可以选择类似Protobuf之类的二进制通讯协议或者自己实现通讯协议,对于传输的内容进行一定程度的加密,以增加黑客破解协议的难度. 参考资料: <iOS开发进阶> --唐巧
- CNN 美国有线电视新闻网 wapCNN WAP 指无线应用通讯协议 ---- 美国有线电视新闻网 的无线应用
wapCNN wap指无线应用通讯协议 CNN美国有线电视新闻网 固, wapCNN 美国有线电视新闻网的无线应用 -------------------------------------- ...
- Netty 对通讯协议结构设计的启发和总结
Netty 通讯协议结构设计的总结 key words: 通信,协议,结构设计,netty,解码器,LengthFieldBasedFrameDecoder 原创 包含与机器/设备的通讯协议结构的设计 ...
- 几种通讯协议的比较RMI > Httpinvoker >= Hessian >> Burlap >> web service
一.综述本文比较了RMI,Hessian,Burlap,Httpinvoker,web service等5种通讯协议的在不同的数据结构和不同数据量时的传输性能.RMI是java语言本身提供的远程通讯协 ...
- 射频识别技术漫谈(6)——通讯协议概述【worldsing笔记】
通讯协议是通讯的双方或多方在交流时遵守的规矩,包括谁先发起通讯,先交流什么,后交流什么,一方如何问,另一方如何答等.在这里通迅的双方指的是读写器和卡片. 首先是谁先发起通讯,很显然有两种,读写器先发言 ...
随机推荐
- 【Python】pip国内安装源和yum恢复
豆瓣安装源 pip install packages -i http://pypi.doubanio.com/simple --upgrade --trusted-host pypi.doubanio ...
- 低级sql语法错误: BadSqlGrammarException
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:760) at org.springf ...
- mybatis的Selective接口和普通接口的区别
举例说明: updateByPrimaryKeySelective会对字段进行判断再更新(如果为Null就忽略更新),如果你只想更新某一字段,可以用这个方法. updateByPrimaryKey对你 ...
- SQL Server error
原因:文件没有权限 出错: TITLE: Microsoft SQL Server Management Studio------------------------------ Attach dat ...
- 吴裕雄 python深度学习与实践(15)
import tensorflow as tf import tensorflow.examples.tutorials.mnist.input_data as input_data mnist = ...
- Linux查看某个端口的连接数
一.查看哪些IP连接本机 netstat -an 二.查看TCP连接数 1)统计80端口连接数 netstat -nat | grep -i "80" | wc -l 2)统计ht ...
- Metasploit用法大全
Metasploit用户接口msfconsoleArmitage: KaliGUI启动:armitage命令启动 Metasploit功能程序msfvenom集成了载荷生成器.载荷编码器.空指令生成 ...
- 100道c++面试题(上)
1. new, delete, malloc, free关系 new/delete是c++的运算符,delete会调用对象的析构函数: malloc/free是c/c++的标准库函数,free只释放内 ...
- django mysql 数据库配置
在settings.py中保存了数据库的连接配置信息,Django默认初始配置使用sqlite数据库. DATABASES = { 'default': { 'ENGINE': 'django.db. ...
- Android后台监控指定app的输入内容,抢红包,模拟点击原理
Android开启辅助功能之后可以用AccessibilityService 去后台监控指定的app的输入内容,也可以监控到app的动作 以及通知栏的动作, 抢红包其实就根据通知栏出现了红包的通知消息 ...