【从零开始のIC学习笔记】UART协议

本文详细介绍了UART通信协议的基础知识,包括其传输格式、校验方式等,并提供了使用Verilog实现UART发送(TX)与接收(RX)模块的具体代码。

前言

  本文介绍UART的基本概念,并给出参考的Verilog代码


UART概念

    UART(Universal Asynchronous Receiver/Transmitter),异步通用串行总线,是常见的通信协议之一. 在一次通信中,主机的TX连接从机的RX,主机的RX连接从机的TX

UART的传输格式: 起始位+数据(通常为8位)+校验+停止位 ,其中IDLE为高电平

 由于UART没有时钟,因此需要通信双方确定好传输速率,即波特率,常用的波特率由9600,115200

 校验位采用奇偶校验,在奇校验中,数据位+校验位中1的个数为奇数个,则该位置1,偶校验同理,在传输中需要确定校验方式.

停止位通常是高电平,停止位可以是多位,因此在传输中需要确定停止位的个数

UART结构

UART主要由发送TX模块与接收RX模块组成,下图为UART的发送模块,接收模块类似.

波特率生成模块:根据设定的波特率,生成控制传输的时钟

打包:将7个串行数据增加起始位,校验位和停止位,打包成符和协议的一帧数据

移位控制:使用计数器,一个一个数据发送,直一帧数据发送完毕

状态机:控制波特率何时工作,以及在传输完毕后生成TX_done


UART-TX代码

本文所介绍的UART统一采用115200波特率,奇校验,1位停止位,此外系统时钟为50MHz

1.端口定义

端口名方向说明
clkinputt系统时钟,这里采用50MHz
rst_ninputt复位信号,低有效
tx_datainputt发送数据(8bit)
tx_eninputt发送使能
txoutputTX信号线
tx_doneoutput发送完成标志
module uart_tx #(parameter BAUD_RATE = 115200,
                parameter  CLK_FREQ  = 50
                )
      (
        input         clk,     
        input         rst_n,
        input [7:0]   tx_data, 
        input         tx_en,
        output reg    tx,
        output        tx_done
      )

2.波特率生成

根据设定的波特率与系统时钟,计算每个bit数据发送需要的系统时钟个数,并且进行计数,生成协议需要的时钟

localparam    DIV_NUM = (CLK_FREQ * 1000000 / BAUD_RATE); //计算每个bit需要的时钟周期 
reg [8:0]     div_cnt;          //需要计数434个,需要9位数据
wire          tx_edg;           //发送数据的边沿


always@(posedge clk or negedge rst_n)
    if(!rst_n)
        div_cnt <= 9'd0;
    else if(div_cnt == DIV_NUM -1)
        div_cnt <= 9'd0;         //计满434个,归零
    else if(c_state == SEND)
        div_cnt <= div_cnt + 1;  //在状态机为发送时,开始计数
    else
        div_cnt <= div_cnt;

assign tx_edg = (div_cnt == DIV_NUM -1);    //当计满一次434后,该信号拉高一个周期

3.状态机与移位控制

根据协议,状态机应当具有以下几个状态,IDLE空闲状态、LOAD数据打包状态、SEND数据发送状态,不同状态进行该状态下的工作。

注意:进行数据打包时,应当保证打包的数据不会发生变化,因此需要将tx_data寄存起来

localparam IDLE = 2'b00;    //空闲状态
localparam LOAD = 2'b01;    //打包状态
localparam SEND = 2'b10;    //发送状态

reg [1:0]  c_state;          //当前状态-current
reg [1:0]  n_state;          //下个状态-next
reg [10:0] shift_data;       //需移位的数据,一帧数据为11位
reg [7:0]  tx_data_buf;      //发送数据缓存,保证该数据不会变化
reg [3:0]  send_num;         //计数发送的数据个数,一帧数据11bit
wire       parity;           //计数校验位


always@(posedge clk or negedge rst_n)
    if(!rst_n)
        tx_data_buf <= 8'd0;
    else if(tx_en)
        tx_data_buf <= tx_data;       //发送使能时,将需发送的数据进行缓存
    else
        tx_data_buf <= tx_data_buf;   //其余时候保持不变

always@(posedge clk or negedge rst_n)
    if(!rst_n)
        send_num <= 4'd0;
    else if(send_num == 4'd10)
        send_num <= 4'd0;            //计满11个即一帧发送完毕,归零
    else if(tx_edg)
        send_num <= send_num + 1'b1; //每个发送数据边沿,计数+1
    esle
        send_num <= send_num;

/*****************三段式状态机******************/
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        c_state <= IDLE;
    else
        c_state <= n_state;

always@(*) begin
    case(c_state)
        IDLE: n_state = tx_en? LOAD:IDLE;   //空闲状态时,如果发送使能,则进入打包
        LOAD: n_state = SEND;               //打包只需要一拍,进入打包阶段直接进入发送阶段
        SEND: n_state = (send_num == 4'd10 && tx_edg)? IDLE:SEND; 
        //发送完11个数据,进入IDLE状态,注意,需要等待发送数据的边沿
        default: n_state = IDLE;
    endcase
end
    
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        shift_data <= 11'd0;
    else if(c_state == LOAD)
        shift_data <= {1'b1,parity,tx_data_buf,1'b0};  
     //将数据打包,注意移位从最低位开始,因此数据最低位为起始位,最高位为停止位
    else if(c_state == SEND && tx_edg)
        shift_data <= {1'b1,shift_data[10:1]};  
     //在发送阶段,在每个发送边沿,将数据右移一位,高位用1补
    else
        shift_data <= shitf_data;

assign parity = ~(^tx_data[7:0]);    
    //奇校验位计算,数据位依次相与,奇数个为1,此时校验位应当为0,才能保证1的个数依然是奇数
assign tx  = (c_state == SEND)? shift_data[0]:1'b0; //在发送阶段,输出移位寄存器的最低位

always@(posedge clk or negedge rst_n)
    if(!rst_n)
        tx_done <= 1'b0;
    else if(send_num == 4'd10 && tx_edg)
        tx_done <= 1'b1;           //当发送完一帧数据后,tx_done置1
    else
        tx_done <= 1'b0;  

UART-RX代码

1.端口定义

端口名方向说明
clkinput系统时钟,50MHz
rst_ninput复位信号,低有效
rxinputRX信号线
rx_enoutput接收使能
rx_dataoutput读出的数据(8bit)
rx_doneoutput读完毕标志(非必要)
module uart_rx #(parameter BAUD_RATE = 115200,
                 parameter CLK_FREQ  = 50
                )
        (input            clk,
         input            rst_n,
         input            rx,
         output reg [7:0] rx_data,
         output reg       rx_en,
         output reg       rx_done
        );

2.波特率生成

由于是接收模块,接收数据需要在中间部分采样,因此,取波特率计数的中间值作为触发接收的边沿

localparam    DIV_NUM = (CLK_FREQ * 1000000 ) / BAUD; 

reg [8:0] div_cnt;         //波特率计数值
reg       rx_edg;          //接收数据边沿

always@(posedge clk or negedge rst_n)
    if(!rst_n)
        div_cnt <= 9'd0;
    else if(div_num == DIV_NUM -1)
        div_cnt <= 9'd0;                    //计满434,归零
     else if(c_state == READ)
        div_cnt <= div_cnt + 1'b1;          //在读数据阶段,进行计数

assign rx_edg = (div_cnt == DIV_NUM>>1);     //取中间值作为采样边沿

3.读使能判定

当接收数据线RX由高电平变为低电平-即发送了起始位,表示开始传输数据,此时读使能置1;

边沿的判断通过寄存器打拍判断

reg    rx_d1;    //rx打一拍后的数据
reg    rx_d2;    //rx打两拍后的数据

always@(posedge clk or negedge rst_n)
    if(!rst_n) begin
        rx_d1 <= 1'b1;
        rx_d2 <= 1'b1;     //rx在空闲状态为高电平
    end
    else begin
        rx_d1 <= rx;
        rx_d2 <= rx_d1;
    end

assign rx_en = !rx_d1 && rx_d2;  //前为高,后为低,则为下降沿

4.接收数据及状态机

接收数据模块状态只有两个,IDLE空闲状态与READ接收数据状态,在接收完数据后,去除起始位、校验位与终止位,留些接收的数据

localparam  IDLE = 2'b00;         //空闲状态
localparam  READ = 2'b01;         //读数据状态

reg [1:0]     c_state;             //当前状态-current
reg [1:0]     n_state;             //下一状态-next
reg [3:0]     read_num;            //接收数据计数
reg           parity;              //校验位
reg [10:0]    rx_shitf_data;       //接收数据移位寄存器

alwasy@(posedge clk or negedge rst_n)
    if(!rst_n)
        read_num <= 4'd0;
    else if(read_num == 4'd10 && rx_edg)
        read_num <= 4'd0;                //计数收到11个,并且在接收边沿,归零
    else if(rx_edg)
        read_num <= read_num + 1'b1;    //每个接收边沿,计数值+1
    else
        read_num <= read_num;

/***************三段式状态机**********************/
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        c_state <= IDLE;
    else
        c_state <= n_state;

always@(*) begin
    case(c_state)
        IDLE:    n_state = rx_en? READ:IDLE;    //在空闲状态,若读使能,则进入读状态
        READ:    n_state = (read_num == 4'd10 && rx_edg)? ILDE: READ;
        //在读状态,若接受完11个数据,且在接收边沿,则返回IDLE状态
        default: n_state = IDLE;
    endcase
end

alwasy@(posedge clk or negedge rst_n)
    if(!rst_n)
        rx_shitf_data <= 11'd0;
    else if(c_state==READ && rx_edg)
        rx_shift_data <= {rx_d2,rx_shift_data[10:1]};  
    //在读状态,依次接收rx数据(打两拍后的数据)
    else
        rx_shift_data <= rx_shift_data;

assign parity = (read_num == 4'd10 && rx_edg)? ~(^rx_shift_data[10:2]):1'b0;
//在接收完数据,判断数据为+校验位是否正确
assign rx_data = rx_shift_data[8:1];  //去除起始位、校验位与终止位
    
/************rx_done*******************/
alwasy@(posedge clk or negedge rst_n)
    if(!rst_n)
        rx_done <= 1'b0;
    else if(read_num == 4'd10 && rx_edg)
        rx_done <= 1'b1;
    else
        rx_done <= 1'b0;

补充说明:

        完成了UART的TX与RX,完整的UART需要例化两个模块,根据需求编写代码(主机或从机),完成使用UART总线进行读写的功能


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值