本文章旨在记录学习的过程,如有错误之处,请各位指正
UART在FPGA中的应用: 串口通讯对于嵌入式设备开发是一个必不可少的通讯接口,在fpga中串口多用于调试或外界设备与fpga之间传输控制命令。
概念解读:
uart的关心配置:
任意打开一款PC端的串口助手,去浏览它的配置选项都大同小异,我们的重点关心项为波特率、校验位、数据位、停止位以及流控制等相关信息。在fpga实现uart中我们也主要实现这些功能的配置。
配置分析:
接下来我们对上述中提到的关键配置信息一一讲解:
波特率: 波特率表示每秒钟传送的码元符号的个数,它是对符号传输速率的一种度量,它用单位时间内载波调制状态改变的次数来表示,1波特即指每秒传输1个符号。 波特(Baud,单位符号:Bd)。
上述为波特率的基本概念,现实中很多人将波特率比特率的概念混淆,认为波特率就是每秒传输多少bit。其区别请各位自行搜索理解。
校验位: 此处校验位使用的是常用的奇偶校验方式,奇偶校验方式可以对传输的数据正确性进行简单的保障,使接收方有判断数据正确与否的能力。
校验类型 | 校验位 | 校验方式 |
---|---|---|
奇校验 (odd) | 1位(1bit) | 数据位中所有的“1”的个数加上校验位的个数总和需保持为奇数,例如:数据为:01110100 其中有4个“1”,为满足奇校验要求需要将检验位置“1”;反之当数据为:01110110 其中有5个”1“,此时我们需要将校验位置为”0“。 |
偶校验 (even) | 1位(1bit) | 数据位中所有的“1”的个数加上校验位的个数总和需保持为偶数,例如:数据为:01110100 其中有4个“1”,为满足奇校验要求需要将检验位置“0”;反之当数据为:01110110 其中有5个”1“,此时我们需要将校验位置为”1“。 |
1校验(mark) | 1位(1bit) | 校验位始终为:“1”,1校验属于固定校验的方式:其并不是对数据进行校验,而是对接收设备发送一个状态位,使接收设备判断收到的设备有没有受到噪声干扰或数据不同步。 |
0校验(space) | 1位(1bit) | 校验位始终为:“0”,0校验与1校验同属于固定校验方式,其方式与1校验也相同。 |
无校验(none) | 0位(0bit) | 无校验位 |
数据位: 每包数据中有效数据(payload)的宽度,常见配置有5bit、6bit、7bit、8bit,其中8bit最为常见使用最为频繁。
停止位: 停止位是单包的最后一位(标识位),其常见配置为1、1.5、2位,起作用不仅仅只有标志一包数据的结束,另外一个作用是“校准时钟”:由于UART之间无总线传输的时钟,主从机都是通过自己内部产生相同的时钟进行匹配,所以可通过停止位来对时钟进行校准,适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
流控制: 由于本次实验没有涉及到,所以在此并不做解释。有需要的小伙伴可以自行搜索。(后续会补充完整)。
波特率产生:
时钟是FPGA的心脏,所有时序逻辑都是需要在时钟下产生的,所以需要为UART收发时序生成所需要的参考时钟,也就是产生波特率
时钟。UART中常使用的波特率有:9600、115200、38400……等频率,以波特率 115200 为例:
两相调制(单个调制状态对应1个二进制位)的比特率等于波特率;所以当波特率为115200buad时,比特率等于115200bit/s,由此我们可推算出其传输1bit所需要的时间,可通过logic实现时钟的倍频操作得出波特率的参考频率
参
考
时
钟
/
波
特
率
=
倍
频
关
系
参考时钟/波特率=倍频关系
参考时钟/波特率=倍频关系
为了保证数据的的准确性可在buad_clk为high时的中点处采集数据,在buad_clk为low时的中点处改变数据;部分实现代码如下
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Buad_gen_cnt[0] <= 17'd0;
else if (!t_baud_en)
Buad_gen_cnt[0] <= 17'd0;
else if (Buad_gen_cnt[0] == (Buad_gen_flag - 17'd1))
Buad_gen_cnt[0] <= 17'd0;
else
Buad_gen_cnt[0] <= Buad_gen_cnt[0] + 17'd1;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
t_Baud_clk <= 1'b1;
else if (!t_baud_en)
t_Baud_clk <= 1'b1;
else if (Buad_gen_cnt[0] == (Buad_gen_flag - 17'd1))
t_Baud_clk <= ~t_Baud_clk;
else
t_Baud_clk <= t_Baud_clk ;
end
时序整理:
想要实现一个总线,在解读完概念后就要对时序进行分析整理,判断如何实现:
根据uart时序图可以分析出uart信号线在空闲状态时钟为 “1” ,当有数据需要发送时,首先将信号线置 “0” 发送起始标志(start),然后以次又低位到高位发送数据(8bit DATA),后续是校验位(parity),检验位根据配置的校验方式可为:0/1bit;后将信号线置 “1” 发送结束标志(stop)。由此可根据时序设计发送和接收的状态机:
状态 | 描述 |
---|---|
IDLE | 数据信号线空闲状态 |
START | 数据传输开始状态 |
DATA | 有效数据传输状态 |
PARITY | 校验位判断状态 |
STOP | 数据传输结束状态 |
状态机部分实现代码(运用三段式状态机描述)
发送数据
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cstate <= IDLE ;
else
cstate <= nstate ;
end
always@(*)
begin
case(cstate)
IDLE : begin
// if(state_switch) begin
if(tren_en_ctrl & !fifo_empty)
nstate = START;
else
nstate = IDLE ;
// end else
// nstate = IDLE ;
end
START : begin
if(state_switch)
nstate = TDATA ;
else
nstate = START ;
end
TDATA : begin
if(state_switch & cstate_cnt == 8'd7)
if(parity_rule != no_parity)
nstate = PARITY ;
else
nstate = STOP ;
else
nstate = TDATA ;
end
PARITY: begin
if(state_switch)
nstate = STOP ;
else
nstate = PARITY ;
end
STOP : begin
if(state_switch) begin
if(tren_en_ctrl & !fifo_empty)
nstate = START;
else
nstate = IDLE ;
end else
nstate = STOP ;
end
default : nstate = IDLE ;
endcase
end
接收数据
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cstate <= IDLE ;
else
cstate <= nstate ;
end
always@(*)
begin
case(cstate)
IDLE : begin
if(rece_en & !uart_rx) begin
nstate = START;
end else
nstate = IDLE ;
end
START : begin
if(state_switch)
nstate = RDATA ;
else
nstate = START ;
end
RDATA : begin
if(state_switch & cstate_cnt == 8'd7)
if(parity_rule != no_parity)
nstate = PARITY ;
else
nstate = STOP ;
else
nstate = RDATA ;
end
PARITY: begin
if(state_switch)
nstate = STOP ;
else
nstate = PARITY ;
end
STOP : begin
if(state_switch) begin
if(rece_en & !uart_rx)
nstate = START;
else
nstate = IDLE ;
end else
nstate = STOP ;
end
default : nstate = IDLE ;
endcase
end
到此笔记结束,若有错误的地方,敬请各位指正。由于概念部分内容参考了网络上的部分文章,如有侵权请联系笔者删除!!!