一、串口简介
串口作为三大低速通信协议(UART、IIC、SPI)之一,是异步通信全双工协议,因此没有时钟信号线,接收方并不知道数据什么时候会到达,双方收发端都有各自的时钟。
如果要实现串口直连通信,需要将通信双方的RS232接口(图1)连上,再通过约束协议通信。
如今的电脑主板、开发板已废弃RS232接口,全部改用USB口。因此,通信图例如下:
常用的USB--UART转换芯片有CH340、FT2232、CP2102等,要想让个人电脑串口识别到开发板串口,需要使用特定的串口通信软件,下载安装对应转换芯片的驱动。
二、串口通信时序
串口通信,以帧作为通信的单位。字符帧里面包含起始位(必须为1'b0),数据位(根据设置可为8位或者7位),校验位(根据设置可为奇校验、偶校验、不校验),停止位(必须为1'b1)
注意串口发送接收的数据位中,低位数据在前,高位数据在后。
数据位 | 校验位 | 字符帧 总位数 |
7位 | 奇校验 | 10位 |
7位 | 偶校验 | 10位 |
7位 | 不校验 | 9位 |
8位 | 奇校验 | 11位 |
8位 | 偶校验 | 11位 |
8位 | 不校验 | 10位 |
三、串口各模块设计
波特(以波特率9600为例解释串口通信的频率)
①要使双方正常串口通信,需要相同的波特率,相同的数据宽度、奇偶校验位相同。
②FPGA接收串口发来的数据,并将其由串行数据转为并行数据,存储在寄存器中。而每1bit数据的持续时间为1波特,FPGA只需要在1波特时间内提取一次数据,可选在二分之一波特时间点提取。
亚稳态
串口通信中,rx信号线的变化不受FPGA的控制(此时FPGA是接收方)。而我们判断起始位信号到来的根据是rx下降沿,那么万一rx变化的下降沿,刚好是在FPGA时钟信号CLK上升沿时呢?如图:
rx在sys_clk上升沿的建立时间 Tsu(指触发器的时钟信号上升沿到来以前,数据稳定不变的最小时间)和保持时间 Th(指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间)中变化了,此时FPGA采集到的信号就是不稳定的振荡0或者1。这是异步通信的弊病。
解决该问题,可使用“打两拍法”:将rx信号传到rx_reg1再传到rx_reg2再传到rx_reg3,再判断下降沿。
这种方法,虽然会让下降沿判断延后了3个时钟周期,但是精确度提高到99%。这样可能会导致漏采样问题(因为是在FPGA时钟上升沿捕捉的信号,20ns采样一次rx,如果rx的传输时钟频率比CLK快,就有一些收不到),但是串口是低速信号,所以不用担心。
//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为 1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;
//rx_reg2:第二级寄存器,寄存器空闲状态复位为 1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;
//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;
//start_nedge:检测到下降沿时 start_nedge 产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
start_nedge <= 1'b0;
else if((~rx_reg2) && (rx_reg3))
start_nedge <= 1'b1;
else
start_nedge <= 1'b0;
状态机
状态机可按照下面格式来完成,包括时序逻辑电路与组合逻辑电路
always @(*) begin
// State transition logic
end
always @(posedge clk, posedge reset) begin
// State flip-flops with asynchronous reset
end
移位寄存器
移位寄存器在串并转换上发挥着巨大的作用。
移位寄存器包括循环移位寄存器、算术移位寄存器两种
比如对于 reg [99:0]q;
循环左移1位:q <= {q[98:0], q[99]};
循环右移1位:q <= {q[0], q[99:1]};
算术左移1位:q <= {q[98:0], 1'b0}; // 算术左移,右端自动补0
算术右移1位:q <= {q[99], q[99:1]}; //算术右移(正数最高位为0,负数为1)左端自动补充符号位
算术右移8位:q <= { { 8{q[99]} } , q[98:8]}
5.串口收取端rx设计
网上有很多成熟的设计,复制粘贴即可。
6.串口发送端tx设计
网上有很多成熟的设计,复制粘贴即可。