基于FPGA的UART回环设计

发布于:2025-06-27 ⋅ 阅读:(17) ⋅ 点赞:(0)

基于FPGA的UART回环设计

一. UART介绍

UART(Universal Asynchronous Receiver/Transmitter),即通用异步收发传输器,它是一种通用的串行、异步通信总线,有两条数据线,可以实现全双工的发送与接收。

UART因为是异步通信,所以不包括同步信号中的时钟线,其两条数据数据线TX和rRX分别用来发送和接收数据。UART负责处理串行端口与发送接收数据总线见的串并转换,并规定了数据包格式,TX和RX采用相同的数据包格式和波特率就可以进行数据传输。

二. 协议原理

发送端UART将要发送的数据组装成一个数据包通过TX端口串行将数据发送出去,接收端UART通过其RX端口串行对数据包的数据进行采集,从数据包中恢复出发送端发送的数据。
在这里插入图片描述
UART的数据包包括1个开始位、5到9位数据(取决于UART设置),1个奇偶校验位,1或者2个停止位。

开始位:UART数据传输线空闲时保持高电平,开始发送数据时,发送端UART将数据线从高拉到低一个时钟周期(其长短根据波特率决定)。当接收端UART探测到下降沿时,开始根据波特率读取数据。

数据位:数据位中为要传输的真是数据,如果使用奇偶校验位,数据帧中可包含5到8位数据,如果不适用奇偶校验位的话,数据帧中最长则可包含9位数据。

奇偶校验位:奇偶校验位用于在接收端UART来显示传输过程中是否有数据被改变。在传输过程中,位数据可能因为电磁辐射、不匹配的波特率、或者长距离传输等原因被改变。当接收UART读取完数据帧后,计算其中1的个数,并且检查1的总个数是奇数还是偶数。如果奇偶校验位是0,则数据帧中1的个数应该是偶数个,如果奇偶校验位是1,则数据帧中1的个数则应该是奇数个。当奇偶校验位的值与数据位帧相匹配时,则UART认为传输中没有错误(具有局限)。但是如果奇偶校验位是0,数据帧的1的总个数是奇数;或者奇偶校验位是1,数据帧中1的共个数是偶数,则UART则会知道帧数据已经被改变。

停止位:为了表示传输包的结束,发送端UART将数据传输线从低电平拉到高电平至少1到2个数据位宽。

前面已经对数据包/数据发送时序进行了介绍,下面在对波特率以及与其容易混淆的比特率的基础知识进行补充。

波特率定义是:每秒钟传送的符号(码元)数量,单位波特(Baud、B,即symbol/s)。

比特率定义是:每秒钟传送的位(比特)数量,基本单位是比特每秒(bps,bit per second)。.

从两者的定义可以看出要想知道波特率和比特率的关系,就首先的弄清楚码元数量和比特数量的关系。此处引用参考资料2中的解释:把通信系统中码元类比为公共交通车辆,例如公交车、地铁、的士……。通信系统所传输的比特数量类比为出行的人,则比特率为出行人口流动速度,波特率就是发车率。就像例子中提及的公交车、地铁、的士可以搭乘不同数量的出行人员一样,不同码元也可以用不同位数的比特表示。码元所需要的比特位数,由码元支持的状态数量确定。

UART中每个码元就是一位数据,存在两种状态,也即波特率单位变为每秒位数,与比特率定义相同,可以理解为此处的波特率就是比特率,本次采用的波特率为9600bps、14400bps、38400bps、115200bps。

三. 基本计数器实现

本次采用可调节波特率设置

always @(*) begin
    case(sw)
    2'b00:      baud = 5208;          //9600波特率
    2'b01:      baud = 3472;          //14400波特率
    2'b10:      baud = 1302;          //38400波特率
    2'b11:      baud = 434;           //115200波特率
    default:    baud = 5208;           
    endcase
end

当sw开关拨动到不同位置时,可设置不同的波特率

1. 计数器设计

波特率计数器设计,为简单的基础计数器,最大数为可设置的调节数

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt <= 0;
    end 
    else if(add_cnt)begin
        if(end_cnt)begin
            cnt <= 0;
        end
        else begin
            cnt <= cnt + 1;
        end
    end
    else begin
        cnt <= cnt;
    end
end

assign end_cnt = add_cnt && (cnt == baud-1)||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1));

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        add_cnt <= 1'b0;
    end
    else if(rx_start)begin
        add_cnt <= 1'b1;
    end
    else if(end_cnt_bit||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1)))begin
        add_cnt <= 1'b0;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        rx_r <= 2'b00;
    end
    else begin
        rx_r <= {rx_r[0],rx};
    end 
end

assign rx_start = ~rx_r [0] & rx_r [1];

其中,add_cnt为波特率计数器开始计数的标志,当输入的数据起始位到来,即检测其下降沿rx_r,波特率计数器开始计数。

end_cnt为波特率计数器结束的标志,当计到最大的波特率时停止,或者在10bit即停止位时(本设计由于硬件问题,停止位为半个波特率的周期),计数器停止计数。

比特计数器设计

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_bit <= 0;
    end
    else if(add_cnt_bit)begin
        if(end_cnt_bit)begin
            cnt_bit <= 0;
        end
        else begin
            cnt_bit <= cnt_bit + 1;
        end
    end
    else begin
        cnt_bit <= cnt_bit;
    end
end

assign add_cnt_bit = end_cnt ;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 4'd11-1;

开始计数标志为波特率计数器结束,当计到最大bit数,即11个bit时停止。

2. 数据接收

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        rx_data_r <= 11'b11111111111;
    end
    else if(cnt == (baud - 1)>>2)begin
        rx_data_r[cnt_bit] <= rx; 
    end
end

由于uart收发数据为串行数据,这里要将串行数据转为并行数据,设置数据寄存器rx_data_r,按照bit计数器将数据存入寄存器

assign rx_check = ~^ rx_data_r[8 :1];

奇偶校验位处理,本设计位奇校验rx_check为第十位奇偶校验位,对前8bit数据位进行按位异或运算,所得结果为0则代表数据位有偶数个,1则代表为奇数个,但本设计为奇校验,对所得数再取反,才使得所有数据加校验位为奇数个1。

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        rx_data <= 8'hff;
    end
    else if((cnt == (baud - 1)>>1)&&cnt_bit == 4'd9)begin
        if((rx_check == rx_data_r[9]))begin
            rx_data <= rx_data_r[8:1];
        end
    else begin
        rx_data <=8'hff;
    end
    end
end

将数据位的8bit数据存入寄存器,方便传出和后续使用。

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        rx_done <= 1'b0;
    end
    else if(end_cnt_bit)begin
        rx_done <= 1'b1;
    end
    else begin
        rx_done <= 1'b0;
    end
end

rx停止信号,方便tx发数据以及后续的处理。

3. tx发送设计

波特率计数器与bit计数器与rx相同,这里不再赘述。

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data<=11'b11111111111;
    end
    else if(tx_start)begin
        data<={1'b1,tx_check,tx_data,1'b0};      
    end
    else begin
        data<=data;
    end
end

发出数据的处理,uart为低位先发,先发0起始位,后接所发的数据1-8bit的tx_data为rx数据寄存器传入的数据,后接校验位tx_check(与rx中同理,对tx_data按位取异或再取反),后接停止位1.

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        tx<=1'b1;
    end
    else if(end_cnt)begin
        tx<=data[cnt_bit];
    end
    else if(end_cnt_bit)begin
        tx<=1'b1;
    end
    else begin
        tx<=tx;
    end
end

uart数据为串行数据,将缓存的data数据挨个发出。

5. 主要代码

uart_rx.v

module uart_rx (
    input               clk,
    input               rst_n,
    input               rx,
    input      [1:0]    sw,
    output reg [7:0]    rx_data,
    output reg          rx_done
);

wire                         rx_check;

//比特率计数器
reg            [12:0]       cnt;
reg                         add_cnt;
wire                        end_cnt;   

//bit计数器
reg            [3:0]        cnt_bit;  
wire                        add_cnt_bit;
wire                        end_cnt_bit;

//下降沿检测
reg            [1:0]        rx_r;
wire                        rx_start;

reg            [10:0]       rx_data_r;

reg            [12:0]       baud;

always @(*) begin
    case(sw)
    2'b00:      baud = 5208;          //9600波特率
    2'b01:      baud = 3472;          //14400波特率
    2'b10:      baud = 1302;          //38400波特率
    2'b11:      baud = 434;           //115200波特率
    default:    baud = 5208;           
    endcase
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt <= 0;
    end 
    else if(add_cnt)begin
        if(end_cnt)begin
            cnt <= 0;
        end
        else begin
            cnt <= cnt + 1;
        end
    end
    else begin
        cnt <= cnt;
    end
end

assign end_cnt = add_cnt && (cnt == baud-1)||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1));

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        add_cnt <= 1'b0;
    end
    else if(rx_start)begin
        add_cnt <= 1'b1;
    end
    else if(end_cnt_bit||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1)))begin
        add_cnt <= 1'b0;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        rx_r <= 2'b00;
    end
    else begin
        rx_r <= {rx_r[0],rx};
    end 
end

assign rx_start = ~rx_r [0] & rx_r [1];

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_bit <= 0;
    end
    else if(add_cnt_bit)begin
        if(end_cnt_bit)begin
            cnt_bit <= 0;
        end
        else begin
            cnt_bit <= cnt_bit + 1;
        end
    end
    else begin
        cnt_bit <= cnt_bit;
    end
end

assign add_cnt_bit = end_cnt ;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 4'd11-1;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        rx_data_r <= 11'b11111111111;
    end
    else if(cnt == (baud - 1)>>2)begin
        rx_data_r[cnt_bit] <= rx; 
    end
end

assign rx_check = ~^ rx_data_r[8 :1];

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        rx_data <= 8'hff;
    end
    else if((cnt == (baud - 1)>>1)&&cnt_bit == 4'd9)begin
        if((rx_check == rx_data_r[9]))begin
            rx_data <= rx_data_r[8:1];
        end
    else begin
        rx_data <=8'hff;
    end
    end
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        rx_done <= 1'b0;
    end
    else if(end_cnt_bit)begin
        rx_done <= 1'b1;
    end
    else begin
        rx_done <= 1'b0;
    end
end
endmodule

uart_tx.v

module uart_tx (
    input               clk,
    input               rst_n,
    input    [7:0]      tx_data,
    input               tx_start,
    input    [1:0]      sw,
    output   reg        tx,
    output              tx_done
);
//奇偶校验位
wire                  tx_check;
//波特率计数器参数
reg     [12:0]        cnt;
reg                   add_cnt;
wire                  end_cnt;

//bit计数器参数
reg     [3:0]         cnt_bit;
wire                  add_cnt_bit;
wire                  end_cnt_bit;

reg     [12:0]        baud;
reg     [10:0]         data;//数据寄存器

always @(*) begin
    case(sw[1:0])
    2'b00:      baud = 5208;          //9600波特率
    2'b01:      baud = 3472;          //14400波特率
    2'b10:      baud = 1302;          //38400波特率
    2'b11:      baud = 434;           //115200波特率
    default:    baud = 5208;           
    endcase
end

//波特率计数器
always @(posedge clk or negedge rst_n) begin 
    if(!rst_n)begin
        cnt <= 0;
    end 
    else if(add_cnt)begin
        if(end_cnt)begin
            cnt <= 0;
        end
        else begin
            cnt <= cnt + 1;
        end
    end
    else begin
        cnt <= cnt;
    end
end

assign end_cnt = add_cnt && (cnt == baud-1)||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1));

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        add_cnt <= 1'b0;
    end
    else if(tx_start)begin
        add_cnt <= 1'b1;
    end
    else if(end_cnt_bit||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1)))begin
        add_cnt <= 1'b0;
    end
end


//bit计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_bit <= 0;
    end
    else if(add_cnt_bit)begin
        if(end_cnt_bit)begin
            cnt_bit <= 0;
        end
        else begin
            cnt_bit <= cnt_bit + 1;
        end
    end
    else begin
        cnt_bit <= cnt_bit;
    end
end

assign add_cnt_bit = end_cnt;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 4'd11-1;

assign tx_check = ~^tx_data;

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data<=11'b11111111111;
    end
    else if(tx_start)begin
        data<={1'b1,tx_check,tx_data,1'b0};           //数据加启示位
    end
    else begin
        data<=data;
    end
end

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        tx<=1'b1;
    end
    else if(end_cnt)begin
        tx<=data[cnt_bit];
    end
    else if(end_cnt_bit)begin
        tx<=1'b1;
    end
    else begin
        tx<=tx;
    end
end

assign tx_done = end_cnt_bit;
endmodule

led.v

module led (
    input clk,
    input rst_n,
    input [1:0] sw,
    input [7:0] data,
    output reg [2:0] led
);
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        led[1:0] <= 2'b00;
    end
    else begin
        case(sw)
        2'b00:      led[1:0] <= 2'b00;
        2'b10:      led[1:0] <= 2'b01;
        2'b01:      led[1:0] <= 2'b10;
        2'b11:      led[1:0] <= 2'b11;
        default:    led[1:0] <= 2'b00;
        endcase

    end

end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        led[2] <= 1'b0;
    end
    else if(data == 8'h80)begin
        led[2] <= 1'b1;
    end
    else if(data == 8'hb3)begin
        led[2] <= 1'b0;
    end
    else begin
        led[2] <= led[2];
    end

end
    
endmodule

uart_loop.v

module uart_loop (
    input              clk,
    input              rst_n,
    input              rx,
    input    [1:0]     sw,
    output   [2:0]     led,
    output             tx
);
wire      [7:0]          data;
wire                     tx_done;

uart_rx inst_uart_rx (
    .clk(clk),
    .rst_n(rst_n),
    .rx(rx),
    .sw(sw),
    .rx_data(data),
    .rx_done(rx_done)
);

uart_tx inst_uart_tx (
    .clk(clk),
    .rst_n(rst_n),
    .tx_data(data),
    .tx_start(rx_done),
    .sw(sw),
    .tx(tx),
    .tx_done(tx_done)
);
led inst_led (
    .clk(clk),
    .rst_n(rst_n),
    .sw(sw),
    .data(data),
    .led(led)
);

endmodule

四. 状态机实现

1. rx接收模块

设置波特率部分与上文相同不在赘述。
以下为状态机部分

//状态机1段
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        state_c <= IDLE;
    end 
    else begin
        state_c <= state_n;
    end
end

状态机基本逻辑

//状态机2段
always @(*) begin
    case(state_c)
    IDLE:  state_n = (IDLE_2_START)? START  : state_c;
    START: begin state_n = (START_2_DATA)? DATA   : state_c; bit_num = 4'd0 ;end
    DATA:  begin state_n = (DATA_2_STOP )? STOP   : state_c; bit_num = 4'd8 ;end
    STOP:  begin state_n = (STOP_2_IDLE )? IDLE   : state_c; bit_num = 4'd0 ;end
    default: begin state_n = IDLE;bit_num = 4'd0; end
    endcase
end

本次设置状态有四个,分别为空闲IDLE,起始位START,数据位DATA,停止位STOP。各个跳转条件为***2***,即为 ***to ***(例如IDLE_to_START即写为IDLE_2_START)。

//状态机3段
assign    IDLE_2_START = rx_start;
assign    START_2_DATA = end_cnt_bit;
assign    DATA_2_STOP  = end_cnt_bit;
assign    STOP_2_IDLE  = end_cnt_bit;

对跳转条件进行描述,其中rx_start即为下降沿检测,检测到下降沿到来时,状态进行跳转,起始位到来,之后每个状态跳转都基于bit计数器,bit计数器结束时跳转状态。

波特率计数器与上文相同,但开始与结束条件有所改变。

assign end_cnt = add_cnt && ((cnt == baud-1)||( (cnt == ((baud-1)>>1)&& state_c == STOP)));

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        add_cnt <= 1'b0;
    end
    else if(state_c == IDLE)begin
        if(rx_start)begin
            add_cnt <= 1'b1;
        end
        else begin
            add_cnt <= 1'b0;
        end
    end
    else if(START_2_DATA||DATA_2_STOP||STOP_2_IDLE)begin
        add_cnt <= 1'b1;
    end
    else if((cnt == (baud-1)>>1)&& state_c == STOP)begin
        add_cnt <= 1'b0;
    end
end

正常情况下,记到当前所设波特率的最大值是停止,或当状态为STOP且记到半个波特率周期时停止。
当状态为空闲IDLE且rx_start为真,开始起始位的波特率计数。
当跳转条件成立,也将add_cnt拉高,开始计数。
当记到当前所设波特率的最大值是停止,或当状态为STOP且记到半个波特率周期时,将add_cnt拉低,不计数。

assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_num;

bit计数器主体无改变,只有停止信号改为状态机中所设置的bit_num。

rx_data_r数据缓存与rx_check停止位写法与上文形同

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        rx_data <= 8'hff;
    end
    else if(end_cnt_bit && state_c == DATA)begin
        if((rx_check == rx_data_r[8]))begin
            rx_data <= rx_data_r[7:0];
        end
    else begin
        rx_data <=8'hff;
    end
    end
end

assign rx_done = STOP_2_IDLE && state_c == STOP;

当状态处于DATA时且数据计到停止位,将八位数据位的数据赋给数据寄存器,传给tx发送模块。
状态从停止位STOP跳转到IDLE空闲位时,代表一次数据的接收完成,将完成信号传出。

2. tx发送模块

三段状态机、波特率计数器、bit计数器、下降沿、校验位都与rx写法相同不在赘述。

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        data_r <=9'b111111111;
    end
    else if(tx_start)begin
        data_r <= {tx_check,tx_data};
    end 
end
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        tx <= 1'b1;
    end
    else begin
        case(state_c) 
            IDLE:  tx <= 1'b1;
            START: tx <= 1'b0;
            DATA:  tx <= data_r[cnt_bit];
            STOP:  tx <= 1'b1;
            default: tx <= 1'b1;
        endcase
    end
end

tx_start为rx收数据的停止信号,此时进行数据的发送操作。
将所收数据与校验位进行拼接,组成新的数据,再进行并串转换,IDLE状态下发送1,即为空闲,START发送0
为起始位,接下来依次发送寄存器data_r的数据,最后发送停止位1。

五. 实现效果

在这里插入图片描述
电脑向FPGA发送hello,FPGA接收到并返回电脑hello在这里插入图片描述
sw[0]与sw[1]都为1,同时led[2]与led[3]亮起,对应波特率为115200,数据传输正确。
在这里插入图片描述
在这里插入图片描述
sw[0]为0,sw[1]为1,led[2]量led[3]灭,对应波特率为38400,
数据传输正确。
在这里插入图片描述
在这里插入图片描述
电脑向FPGA发送“开”,led[0]亮起
在这里插入图片描述
在这里插入图片描述
发送关led[0]熄灭。

六. 总结

本次对uart的回环设计用到了两种设计方法,分别为简单的计数器堆叠,和用状态机实现,前者实现较为简单且更容易理解,后者能够清晰的看到各种状态之间的逻辑,本人更偏向于前者。两种方法所实现的效果相同。


网站公告

今日签到

点亮在社区的每一天
去签到