fpga中uart的使用(xilinx vivado)

Uart通信协议

1.起始位,数据线从高变低,低有效为0,数据传输开始。1位时间低电平
2.数据位,起始位传输之后便是数据位开始,一般为8位,传输时低位(LSB)在前,高位(MSB)在后。
3.校验位,校验位可以认为是一个特殊的数据位,可以简便地校验数据是否出错,但是只知道数据是否出错,却不能纠正数据。奇校验指的是,加上校验位,共有奇数个1,偶校验指的是,加上校验位共有偶数个1。可以把所有数据位加起来,观察结果是奇数还是偶数来确定数据中1的个数,也可以用按位异或的方法快速判断。通常使用的是奇偶校验,奇校验在1的个数是奇数时等于1否则为0,偶校验在1的个数是偶数时等于1否则为0。
4.停止位,停止位高有效为1,他表示这一个个字节传输结束。1位时间高电平
位时间,起始位、数据位、校验位的位宽度是一致的,停止位有0.5位、1位、1.5位格式,一般为1位。持续的高电平是空闲位。从起始位开始到停止位结束的时间间隔称之为一帧。
波特率相关 如果波特率为9600 那么1000000000/9600/1000=105us(传输一位数据需要时间)。uart中tx、rx的时钟可以不同但波特率必须一致
为115200那么1000000000/115200/1000=9us(传输一位数据需要时间)
在50m时钟里,如果波特率为300那么 1000000000/300/20=166666次

uart工作流程

在uart协议中,发送方发出数据不需要等待接收方相应就可以发送下个数据包。在TX发送数据的同时,RX也可以接收数据,并且收发可以以不同的波特率收发数据。
发送UART通过数据总线接收来自控制设备的并行数据。控制设备可以是微处理器或微控制器的CPU,RAM或ROM等存储单元。

UART 为该数据添加起始位、奇偶校验位和停止位,以便将其转换为数据包。然后数据包在移位寄存器的帮助下,将8位并行数据的每个位在不同时刻传输,从并行转换为串行,并从 TX 引脚逐位传输。

接收 UART 在 RX 引脚接收此串行数据,并通过识别开始位和停止位来检测实际数据。奇偶校验位用于检查数据的完整性。停止位不仅仅是表示传输的结束,并且提供计算机校正时钟的机会。停止位个数越多,数据传输越稳定,但是数据传输速度也越慢。

在从数据包中分离起始位、奇偶校验位和停止位之后,在移位寄存器的帮助下将数据转换为并行数据。注意在最后的通信过程中,串并转换的过程以及接口是被封装在串口模块内部的。

连接

UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫 rx(Receiver),对于 PC 来说它的 tx 要和对于 FPGA来说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接,如果连接两个 tx 或者两个 rx 连接那数据就不能正常被发送出去和接收到。
在这里插入图片描述
bps_clk为串口时钟
在这里插入图片描述
在实际设计时,可以用起始位为0开始发送,停止位为1停止

设计要求

使用Verilog设计一个UART出来,其中UART具体满足的参数如下
数据位长度可调整、可选1位奇偶校验位;
波特率可调(波特率设置端口)
pc可以通过串口助手读到fpga发出的字符
发端比较简单,发送开始,先发一个0,再发所有数据,最后发个1等待下一个send_start。
收端相对就会比较麻烦了,因为要接收数据时序会更加复杂。
这里先给出uart发送端的代码,开始我尝试按照协议定义直接写,然后发现这样的适用性很不好,于是改为使用状态机的方式。发送端采用按键给出使能信号用于开始接收,至于接收端的使能采用下降沿检测:
状态机分为5个状态,idle、start、trans_data、parity、done
idle是默认初始状态,当数据传输完成后也会跳回这一状态用来保证各个量赋值正确
start:表示开始传输,tx拉低
trans_data:传输有效数据并采用tx_cnt计数,全部发送完后根据是否需要校验决定跳转发送校验和或者终止阶段
parity:传输校验和,默认是奇校验,偶校验需要取反
done:终止信号,tx发送恢复0信号,tx_flag拉低

module uart_tx#(
    parameter PARITY_ON = 0,        //校验位,1为有校验位,0为无校验位,缺省为0
    PARITY_TYPE = 0,      //校验类型,0为奇校验,1为偶校验,缺省为奇校验
    baud_r = 115200,//波特率设置
    clk_fre = 50_000_000,
    data_width=8,
    bps_cnt=clk_fre/baud_r
 )(input clk,input rstn,input[data_width-1:0] data,input en,output reg tx,output busy);
 reg en_d0,en_d1,tx_flag,parity_check;
 wire  en_flag;
 assign en_flag=(~en_d1)&en_d0,busy=tx_flag;
 always @(posedge clk or negedge rstn) begin// en data reg
    if(!rstn)begin
        en_d0<='d0;
        en_d1<='d0;
    end
    else begin
       en_d0<=en;
       en_d1<=en_d0; 
    end  end
reg [17:0]div_cnt;
reg [data_width-1:0]data_in;
reg [2:0] cstate,nstate;
reg [3:0]   tx_cnt;      //接收数据位计数 
localparam idle = 3'b000,         //空闲状态
 start = 3'b001,        //开始状态
 trans_data = 3'b011,       //数据发送状态
 parity = 3'b100,       //数据校验计算和发送
 done = 3'b101;          //结束状态
always @(posedge clk or negedge rstn) begin
   if(!rstn)div_cnt<=18'd0;
   else  if(tx_flag)
    begin
    if(div_cnt==bps_cnt-1)
    div_cnt<=0;
    else div_cnt<=div_cnt+1'b1;
    end
    else div_cnt<=18'd0;
end
//FSM-1
always@(posedge clk or negedge rstn)
begin
    if(!rstn)
        cstate <= idle;
    else if(!tx_flag)
        cstate <= idle;
    else  if(div_cnt=='d0)
        cstate <= nstate; 
    else ;    
end
//FSM-2
always@(*)
begin
    case(cstate)
        idle:     if(en_flag) nstate <= start; else ;
        start:    nstate <= trans_data;
        trans_data:
            if(tx_cnt == data_width)
                begin
                    if(!PARITY_ON)
                        nstate <= done;
                    else
                        nstate <= parity;       //校验位开启时进入校验状态
                end
            else
                begin
                        nstate <= trans_data;
                end
        parity:   nstate <= done;
        done:      nstate <= idle;
        default: nstate <= idle;
    endcase
end
always@(posedge clk or negedge rstn)
begin
    if(!rstn)
        begin
            tx   <= 1'b1;
            tx_cnt    <= 4'd0;
            parity_check <= 1'b0;
            data_in<={data_width{1'b0}};
            tx_flag<='d0;
        end
    else
        case(cstate)
            idle:begin
                    tx   <= 1'b1;
                    tx_cnt    <= 4'd0;
                    parity_check <= 'd0;
                    if(en_flag)
                        begin
                            data_in<=data;
                            tx_flag<='d1;
                        end
                        else ;
                end
            start:begin
                if(div_cnt==bps_cnt-1)
                        tx   <= 1'b0;  
                // pull down tx to start transfer
                end
            trans_data:begin
                if(div_cnt==bps_cnt-1)
                begin
                    tx_cnt <=tx_cnt + 1'b1;
                    tx <= data_in[0];
                    parity_check <=parity_check + data_in[0];
                    data_in <= {1'b0 ,data_in[data_width-1:1]}; 
                end
                end
            parity:begin
                            if(PARITY_TYPE&&div_cnt==bps_cnt-1 )
                                tx <= parity_check;
                            else if(div_cnt==bps_cnt-1)
                                tx <= ~parity_check ;
                                else ;
                end
            done:begin
                    if(div_cnt==bps_cnt-1)
                        begin
                             data_in<='d0;
                             tx <= 1'b1;
                            tx_flag<=0;
                        end
                end
            default:;
        endcase
end
endmodule

接收模块的代码,一般引脚会设置一个完成输出端口和正在输出信号端口
状态机分为4个状态,idle、rece_data、parity、done
idle是默认初始状态,当数据接收完成后也会跳回这一状态用来保证各个量初始赋值正确.这里注意因为波特率的问题在收到传输开始信号后不能立刻跳转状态,必须等到有效数据开始传输时再跳转,不然时序错误收到的数据也是错的
rece_data:接收有效数据并采用rx_cnt计数,全部接收完后根据是否需要校验决定跳转接收校验和或者终止阶段
parity:接收校验和,默认是奇校验,偶校验需要取反,注意这里也需要保证波特率正确
done:终止信号,done信号拉高,并行发送有效数据

module uart_rx#(
    parameter PARITY_ON = 0,        
    PARITY_TYPE = 0,     
    clk_fre = 50_000_000,
    baud_r =115200,
    data_width=8,
    bps_cnt=clk_fre/baud_r
 )(input clk,input rstn,input  rx,output reg[data_width-1:0] data,output reg done_rx);
reg [17:0]div_cnt;
reg [data_width-1:0] data_d0;
reg[3:0]rx_cnt;
reg rx_flag, parity_check;
reg [2:0] cstate,nstate;
reg rx_d0,rx_d1,parity_flag;
wire en_flag;
assign en_flag=rx_d1&(~rx_d0);
localparam idle = 3'b000,         
rece_data = 3'b010,      
parity = 3'b011,       
done = 3'b100;         
always @(posedge clk or negedge rstn) begin// rx data reg
    if(!rstn)begin
        rx_d0<='d1;
        rx_d1<='d1;
    end
    else begin
       rx_d0<=rx;
       rx_d1<=rx_d0; 
    end
end
always @(posedge clk or negedge rstn) begin
   if(!rstn)div_cnt<=18'd0;
   else  if(rx_flag)
    begin
    if(div_cnt==bps_cnt-1)
    div_cnt<=0;
    else div_cnt<=div_cnt+1'b1;
    end
    else div_cnt<=18'd0;
end

//FSM-1
always@(posedge clk or negedge rstn)
begin
    if(!rstn)
        cstate <= idle;
    else if(!rx_flag)
        cstate <= idle;
    else if(div_cnt==18'd0)
        cstate <= nstate;
end
//FSM-2
always@(*)
begin
    case(cstate)
        idle:if(div_cnt==bps_cnt-1)  nstate<=rece_data;  else ;
        rece_data:
            if(rx_cnt == data_width)
                begin
                    if(!PARITY_ON )
                        nstate <= done;
                    else
                        nstate <= parity;      
                end
            else  ;
        parity:   nstate <= done;
        done:      nstate <= idle;
        default: nstate <= idle;
    endcase
end
always@(posedge clk or negedge rstn)
begin
    if(!rstn)
        begin
            rx_cnt    <= 4'd0;
            parity_check <= 1'b0;
            data_d0<={data_width{1'b0}};
            data<={data_width{1'b0}};
            done_rx<=0;
            rx_flag <= 'd0;
            parity_flag<='d0;
        end
    else
        case(cstate)
            idle:begin
                    rx_cnt    <= 4'd0;
                    parity_check <= 'd0;
                    if(en_flag)
                    rx_flag <= 1'b1;
                    done_rx<=0;
                end
            rece_data:begin
                    if(div_cnt==bps_cnt/2)
                        begin
                            rx_cnt <=rx_cnt + 1'b1;
                            data_d0<= {rx_d1, data_d0[data_width-1 : 1]};
                            parity_check <=parity_check + rx_d1;
                        end
                    else ;
                end
            parity:
            if(div_cnt==bps_cnt/2)
            begin
                if(parity_check + rx_d1 == PARITY_TYPE )
                parity_flag<='d1;
            end
            done:begin
                if(div_cnt==bps_cnt/2)
                begin
                    if(~PARITY_ON||parity_flag )
                        begin
                            data <= data_d0;
                            done_rx <= 1'b1;
                            rx_flag <= 1'b0;
                            parity_flag<='d0;
                        end
                        else
                            begin
                                done_rx <= 1'b0;
                                rx_flag <= 1'b0;
                                data_d0<='d0;
                                data<='d0;
                                parity_flag<='d0;
                            end
                end
           else ;   
        end
            default:;
        endcase
end
endmodule

我们还需要一个串口环回模块将它们连接起来,完成并串转换

module zynq_uart_loop(input clk,input rstn,input rx_done,
input[7:0]rx_data,input tx_busy,output reg tx_en,
output reg [7:0]tx_data );
reg rx_done_d0,rx_done_d1,tx_ready;
wire rx_done_flag;
assign rx_done_flag=(~rx_done_d1)&rx_done_d0;
always @(posedge clk or negedge rstn) begin
    if(!rstn)
    begin
        tx_ready<='d0;
        tx_en<='d0;
        tx_data<='d0;
    end
    else begin
        if(rx_done_flag)
        begin
            tx_ready<='d1;
            tx_en<='d0;
            tx_data<=rx_data; 
        end
        else if(tx_ready&&(~tx_busy))   begin
            tx_ready<='d0;
            tx_en<='d1;
        end
    end
end
always @(posedge clk or negedge rstn) begin
    if(!rstn)
    begin
        rx_done_d0<='d0;
        rx_done_d1<='d1;
    end
    else begin
        rx_done_d0<=rx_done;
        rx_done_d1<=rx_done_d0;
    end
end
endmodule

顶层模块 这里给的是奇校验的代码,偶校验和零校验是一样的

module zynq_uart_top  (
   input clk,input rstn,input rx,output tx
);
wire rx_done,tx_en,tx_busy;
wire[7:0]rx_data,tx_data;
parameter PARITY_ON = 1,        
PARITY_TYPE = 0;
zynq_uart_loop loop_u(
.clk(clk),.rstn(rstn),.rx_done(rx_done),.tx_data(tx_data),
.rx_data(rx_data),.tx_busy(tx_busy),.tx_en(tx_en)
);    
uart_tx #(
   .PARITY_ON (PARITY_ON),        
   .PARITY_TYPE (PARITY_TYPE)) tx_u 
(
.clk(clk),.rstn(rstn),.en(tx_en),.data(tx_data),
.busy(tx_busy),.tx(tx));
uart_rx #(
   .PARITY_ON (PARITY_ON),        
   .PARITY_TYPE (PARITY_TYPE)) rx_u 
(
.clk(clk),.rstn(rstn),.data(rx_data),
.done_rx(rx_done),.rx(rx));
endmodule

板上测试

奇校验:

odd

偶校验:

even

零校验:

none


之前的视频都是通过typec线连接fpga内部的uart芯片传输信息的,最后把输入输出引脚改到一般io口并连接usb转ttl模块:

genral io uart

参考博客

https://blog.csdn.net/qq_38812860/article/details/119940848
https://zhuanlan.zhihu.com/p/549612117
https://www.amobbs.com/thread-5757915-1-5.html

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值