FPGA实现串口通信(回环)与奇偶校验
1. 概述
本文详细介绍如何使用FPGA实现带奇偶校验的串口通信回环测试系统。该系统通过Verilog硬件描述语言设计,包含完整的发送、接收模块,支持奇偶校验功能,并可通过回环连接进行自测试验证。
2. 串口通信基础
串口通信采用异步传输方式,常见参数包括:
- 波特率:9600、115200等
- 数据位:8位
- 停止位:1位
- 校验位:1位(本文采用奇校验)
- 流控制:无
在FPGA中实现串口通信需要精确的时序控制和状态机设计,确保数据按照协议规范正确传输。
3. 如何实现回环测试
回环测试是验证串口通信功能的有效方法,实现方式如下:通过上位机串口发送数据,接收模块将数据接收,并传到顶层模块进行缓存,再将数据传到发送模块,观察上位机串口的接收部分是否有数据传回来以及是否一致。
4. 如何实现奇偶校验
奇偶校验是一种简单的错误检测机制,本文采用奇校验实现:
4.1 奇校验原理
奇校验要求数据位和校验位中"1"的总数为奇数。发送端计算校验位,接收端验证校验位。
4.2 校验位计算
对于8位数据data[7:0],奇校验位计算公式:^data_rx,通过使用异或门来实现判断数据中1的个数,若1的个数为奇数,则^data_rx等于1;反之为0;
5. 接收模块代码实现
完整的UART接收模块Verilog代码:
module uart_rx(
input wire clk,
input wire rst_n,
input wire RX,
output wire [7:0] data,
output wire done
);
parameter CLK=50000000;
parameter baudrate=115200;
parameter baud_1bit=CLK/baudrate;
//两级寄存器,并检测下降沿
reg rx_z1,rx_z2;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_z1<=1;
rx_z2<=1;
end
else begin
rx_z1<=RX;
rx_z2<=rx_z1;
end
end
wire rx_negedge;
assign rx_negedge=!rx_z1&&rx_z2;
parameter [1:0] STATE0=0,//空闲位
STATE1=1,//起始位,数据位
STATE2=2,//校验位
STATE3=3;//停止位
reg[1:0] state;
reg [12:0]cnt;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt<=0;
state<=STATE0;
end
else
case(state)
STATE0:begin
if(rx_negedge)begin
state<=STATE1;
cnt<=0;
end
else begin
state<=STATE0;
end
end
STATE1:begin//起始位与数据位
if(cnt==baud_1bit*9-1)begin
state<=STATE2;
end
else begin
cnt<=cnt+1;
state<=STATE1;
end
end
STATE2:begin//校验
if(parity_err==1)
state<=STATE0;
else if(cnt==baud_1bit*10-1)begin
state<=STATE3;
end
else begin
cnt<=cnt+1;
state<=STATE2;
end
end
STATE3:begin//停止位,只有半个周期,然后进入状态0去等待下一组数据到来
if(cnt==baud_1bit*10+(baud_1bit>>1))
state<=STATE0;
else begin
cnt<=cnt+1;
state<=STATE3;
end
end
default:;
endcase
end
reg [7:0]data_rx;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data_rx<=0;
else begin
if(cnt==baud_1bit*1+(baud_1bit>>1))
data_rx[0]<=rx_z2;
else if(cnt==baud_1bit*2+(baud_1bit>>1))
data_rx[1]<=rx_z2;
else if(cnt==baud_1bit*3+(baud_1bit>>1))
data_rx[2]<=rx_z2;
else if(cnt==baud_1bit*4+(baud_1bit>>1))
data_rx[3]<=rx_z2;
else if(cnt==baud_1bit*5+(baud_1bit>>1))
data_rx[4]<=rx_z2;
else if(cnt==baud_1bit*6+(baud_1bit>>1))
data_rx[5]<=rx_z2;
else if(cnt==baud_1bit*7+(baud_1bit>>1))
data_rx[6]<=rx_z2;
else if(cnt==baud_1bit*8+(baud_1bit>>1))
data_rx[7]<=rx_z2;
end
reg [7:0]data_reg;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
data_reg<=0;
else if(cnt==baud_1bit*9)
data_reg<=data_rx;
else
data_reg<=data_reg;
end
reg parity_err;//判断校验位
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
parity_err <= 0;
else if(cnt == baud_1bit*9 + (baud_1bit>>1)) begin
// 奇校验:数据中1个数为奇数,校验位应为0;偶数则校验位应为1
if(^data_rx == 1)
parity_err <= (rx_z2 == 0) ? 0 : 1;
else
parity_err <= (rx_z2 == 1) ? 0 : 1;
end
end
assign data=(parity_err==0)?data_reg:0;//只要校验没有问题时,才能给数据赋值
reg done_rx;//判断数据是否有效
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
done_rx<=0;
else if(state==STATE0)
done_rx<=0;
else if(cnt==baud_1bit*10+(baud_1bit>>1))
done_rx<=1;
end
assign done=done_rx;
endmodule
该接收模块实现了完整的UART接收功能,包含以下关键设计:
- 四状态状态机:
- STATE0(空闲状态):等待起始位下降沿,检测到后进入STATE1
- STATE1(起始位与数据位接收):接收起始位和8位数据位,在每个比特位的中间采样点读取数据
- STATE2(校验位验证):接收并验证奇校验位,如果校验错误则直接返回空闲状态
- STATE3(停止位处理):接收停止位,完成后返回空闲状态等待下一帧数据
- 数据采样逻辑:在STATE1状态下,分别在
cnt == baud_1bit*1+(baud_1bit>>1)到cnt == baud_1bit*8+(baud_1bit>>1)时刻采样8位数据,存储到data_rx寄存器。 - 奇校验验证:在STATE2状态下,当
cnt == baud_1bit*9 + (baud_1bit>>1)时采样校验位,并与计算得到的奇校验值比较:- 如果
^data_rx == 1(数据中1的个数为奇数),则校验位应为0 - 如果
^data_rx == 0(数据中1的个数为偶数),则校验位应为1 - 校验错误时设置
parity_err = 1,状态机直接返回STATE0
- 如果
- 数据输出控制:只有在校验通过(
parity_err == 0)时,才将data_reg赋值给输出data,否则输出0,确保错误数据不被传递。 - 完成信号生成:在STATE3状态下,当
cnt == baud_1bit*10+(baud_1bit>>1)时,设置done_rx = 1,表示一帧数据接收完成。

模块工作流程:
- 复位后,模块处于STATE0空闲状态,等待起始位下降沿
- 检测到下降沿后进入STATE1,开始接收起始位和8位数据
- 数据位接收完成后进入STATE2,接收并验证奇校验位
- 如果校验错误,直接返回STATE0;如果校验正确,进入STATE3接收停止位
- 停止位接收完成后,设置
done信号有效(表示接收完成),并将有效数据输出到data - 返回STATE0,等待下一帧数据的起始位
6. 发送模块代码实现
完整的UART发送模块Verilog代码:
module uart_tx(
input wire clk,
input wire rst_n,
input wire [7:0] data,
input wire start_tx,
output wire TX,
output wire busy // 新增:忙状态指示
);
parameter CLK=50000000; // 系统时钟
parameter baudrate=115200; // 波特率
parameter sent_1bit = CLK / baudrate;
parameter sent_1Byte = sent_1bit * 11; // 1起始 + 8数据 + 1校验 + 1停止
reg [12:0] cnt_1bit;
reg [1:0] state;
parameter [1:0] STATE0 = 0, // 空闲,等待启动
STATE1 = 1, // 发送起始位、数据位、校验位、停止位
STATE2 = 2; // 清空计数器,回到空闲
// 状态机
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <= STATE0;
cnt_1bit <= 0;
end else begin
case(state)
STATE0: begin
if(start_tx) begin
state <= STATE1;
cnt_1bit <= 0;
end
else
state <= STATE0;
end
STATE1: begin
if(cnt_1bit == sent_1Byte - 1)
state <= STATE2;
else
cnt_1bit <= cnt_1bit + 1;
end
STATE2: begin
cnt_1bit <= 0;
state <= STATE0;
end
endcase
end
end
// 发送数据寄存器
reg tx_p;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
tx_p <= 1;
else if(state == STATE1) begin
if(cnt_1bit < sent_1bit * 1) // 起始位(低电平)
tx_p <= 0;
else if(cnt_1bit < sent_1bit * 2) // bit0
tx_p <= data[0];
else if(cnt_1bit < sent_1bit * 3) // bit1
tx_p <= data[1];
else if(cnt_1bit < sent_1bit * 4) // bit2
tx_p <= data[2];
else if(cnt_1bit < sent_1bit * 5) // bit3
tx_p <= data[3];
else if(cnt_1bit < sent_1bit * 6) // bit4
tx_p <= data[4];
else if(cnt_1bit < sent_1bit * 7) // bit5
tx_p <= data[5];
else if(cnt_1bit < sent_1bit * 8) // bit6
tx_p <= data[6];
else if(cnt_1bit < sent_1bit * 9) // bit7
tx_p <= data[7];
else if(cnt_1bit < sent_1bit * 10) // 奇校验位
tx_p <= (^data == 1) ? 0 : 1; // 数据中1为奇数则校验位0,否则1
else if(cnt_1bit < sent_1bit * 11) // 停止位(高电平)
tx_p <= 1;
end
else
tx_p <= 1; // 空闲时保持高电平
end
assign TX = tx_p;
assign busy = (state != STATE0); // 发送器忙指示
endmodule
该发送模块实现了以下功能:
- 三状态状态机:STATE0(空闲等待)、STATE1(发送数据)、STATE2(清空计数器)。
- 奇校验生成:在发送数据位后自动计算并发送奇校验位。
- 忙状态指示:通过
busy信号指示发送器是否正在工作。 - 完整帧格式:按照起始位(0)+ 8位数据 + 奇校验位 + 停止位(1)的格式发送数据。
模块工作流程:当 start_tx 信号有效时,状态机从 STATE0 进入 STATE1,开始发送起始位、8位数据、奇校验位和停止位。发送完成后进入 STATE2 清空计数器,然后返回 STATE0 等待下一次发送。
7. 顶层模块设计
顶层模块整合发送、接收模块,实现回环测试功能:
module uart_loopback (
input wire clk,
input wire rst_n,
input wire RX,
output wire TX
);
// 接收模块实例
wire [7:0] rx_data;
wire rx_done;
uart_rx u_rx (
.clk (clk),
.rst_n (rst_n),
.RX (RX),
.data (rx_data),
.done (rx_done)
);
// 发送模块实例
wire tx_busy;
reg [7:0] tx_data;
reg start_tx;
uart_tx u_tx (
.clk (clk),
.rst_n (rst_n),
.data (tx_data),
.start_tx(start_tx),
.TX (TX),
.busy (tx_busy)
);
reg [7:0] buf0, buf1; // 两个存储单元,buf0 为最早数据
reg [1:0] count; // 当前缓存数量:0,1,2
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
tx_data <= 8'd0;
start_tx <= 1'b0;
buf0 <= 8'd0;
buf1 <= 8'd0;
count <= 2'd0;
end
else begin
start_tx <= 1'b0; // 默认无发送启动
// 如果发送空闲且缓存非空,则取出最早的数据发送
if(!tx_busy && count > 0) begin
tx_data <= buf0; // 取 buf0(最早)
start_tx <= 1'b1;
// 更新缓存:移位
if(count == 2) begin
buf0 <= buf1; // 将 buf1 移到 buf0
buf1 <= 8'd0; // 清空(可选)
count <= 1;
end else begin // count == 1
buf0 <= 8'd0;
count <= 0;
end
end
// 如果接收到新数据且缓存未满,则存入缓存
if(rx_done) begin
if(count == 0) begin
buf0 <= rx_data;
count <= 1;
end else if(count == 1) begin
buf1 <= rx_data;
count <= 2;
end else begin
// count == 2,
end
end
end
end
endmodule
该顶层模块实现了以下功能:
- 模块实例化:实例化接收模块
uart_rx和发送模块uart_tx,建立完整的串口通信链路。 - 双缓存设计:采用
buf0和buf1两个8位寄存器作为数据缓存,实现简单的先进先出(FIFO)的数据流控制。(使用数据缓存的目的防止数据的丢失,如果没有缓存,那么在数据接收完成后,马上开始发送,在数据发送期间,又接收到了数据,导致发送模块来不及处理,从而导致丢失这一帧数据)也可以直接调用一个fifoip核来处理
模块工作流程:
- 复位状态:所有寄存器和缓存清零,发送模块处于空闲状态。
- 数据接收:当接收模块完成数据接收(
rx_done有效)且缓存未满时,数据存入缓存。 - 数据发送:当发送模块空闲(
tx_busy为0)且缓存非空时,从buf0取出最早的数据启动发送。 - 缓存更新:发送完成后更新缓存,如果缓存中有两个数据,则将
buf1移到buf0;如果只有一个数据,则清空缓存。
8. 上板验证与测试
能够实现串口回环功能,且不漏掉数据

与奇偶校验&spm=1001.2101.3001.5002&articleId=163891249&d=1&t=3&u=f1b34a97da0548f4b368d3b08f38a591)
1669

被折叠的 条评论
为什么被折叠?



