FPGA学习记录(四) UART串口通信

通信方式在日常的应用中一般分为串行通信(serial communication)和并行通信(parallel communication)。并行通信是指多比特数据同时通过并行线进行传送,一般以字或字节为单位并行进行传输。这种传输方式用的通信线多、成本高,故不宜进行远距离通信,因此并行通信一般用于 近距离的通信,通常传输距离小于30米。串行通信是指数据在一条数据线上,一比特接一比特地按顺序传送的方式,这一点与并行通信是不同的。这里我们以传输一个字节(8位)数据为例,在并行通信中,一个字节的数据是在 8 条并行传输线上同时由源地传送到目的地;而在串行通信中,因为数据是在一条传输线上一位接一位地顺序传送的,所以一个字节的数据要分8次进行传送。如果以T为一个时间单位的话,那么并行通信发送一个字节的数据只需要1T的时间,而串行通信需要8T的时间,由此可以总结出串行通信的的特点:一是节省传输线,大大降低了使用成本,二是数据传送速度慢,这一点在大位宽的数据传输上尤为明显。综上可知,串行通信主要应用于长距离、低速率的通信场合。本次实验我们主要讲解下串行通信。 串行通信一般有 2 种通信方式:同步串行通信(synchronized serial communication)和异步串行通信 (asynchronous serial communication)。同步串行通信需要通信双方在同一时钟的控制下同步传输数据;异步串行通信是指具有不规则数据段传送特性的串行数据传输。在常见的通信总线协议中,I2C,SPI 属于同步通信而UART属于异步通信。同步通信的通信双方必须先建立同步,即双方的时钟要调整到同一个频率, 收发双方不停地发送和接收连续的同步比特流。异步通信在发送字符时,发送端可以在任意时刻开始发送 字符,所以,在UART通信中,数据起始位和停止位是必不可少的。

UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter), 它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。 UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。

UART在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如下图 所示。

5f2e6c7ea0f24d9aa6cb307c8878a3f6.png

对于一个8位的数据来说,一帧共11位。

起始位:当不传输数据时,UART数据传输线通常保持高电压电平。若要开始数据传输,发送UART会 将传输线从高电平拉到低电平并保持1个波特率周期。当接收UART检测到高到低电压跃迁时,便开始以 波特率对应的频率读取数据帧中的位。

奇偶校验:奇偶性描述数字是偶数还是奇数。通过奇偶校验位,接收 UART 判断传输期间是否有数据 发生改变。电磁辐射、不一致的波特率或长距离数据传输都可能改变数据位。接收 UART读取数据帧后, 将计数值为1的位,检查总数是偶数还是奇数。如果奇偶校验位为0(偶数奇偶校验),则数据帧中的1或 逻辑高位总计应为偶数。如果奇偶校验位为1(奇数奇偶校验),则数据帧中的1或逻辑高位总计应为奇数。 当奇偶校验位与数据匹配时,UART认为传输未出错。但是,如果奇偶校验位为0,而总和为奇数,或者奇 偶校验位为1,而总和为偶数,则UART认为数据帧中的位已改变。

停止位:为了表示数据包结束,发送UART将数据传输线从低电压驱动到高电压并保持1到2位时间。

d9dcac757c984470bf3fe4c6c758db14.png

发送端时序演示图

我在之前做的时候是使用了两块FPGA互相传数据,所以需要板A作为发送机传给板B接收机

对于传输时序的计算:

如果使用的是115200的波特率,其串口的比特率为115200Bps×1bit = 115200bps。由计算得串口发送或者接收1bit数据的时间为一个波特,即1/115200s,如果用50MHz的系统时钟来计数,需要 计数的个数为cnt = (1s×10^9)ns/115200bit)ns/20ns ≈ 434个系统时钟周期,即每位数据之间的间隔要在50MHz 的时钟频率下计数434次。而串口传输一个8位数据在115200的波特率下需要434*10个时钟(8位数据加起始位和停止位)周期,这个周期是很大的

对于发送端来说,我需要达成的目的是对于一串数据一个接一个且循环发送,那就添加发送计数器和发送完成标志位,控制标志位在发送地址全部走完之后翻转,随后进行下一次发送,控制发送计数器在发送完一个数据后清空,从下一个数据开始重新技术,而发送开始位根据发送计数器来控制达到循环发送的效果。

对于接收端来说,也是与发送端同样的时间接收一个数据,需要注意的点就是在接收端接收到不同电平信号的时候,由于不同判决时间的原因会导致亚稳态的产生,通常采用多级寄存器的方式来解决

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        uart_rxd_d0 <= 1'b0;
        uart_rxd_d1 <= 1'b0;
        uart_rxd_d2 <= 1'b0;
    end
    else begin
        uart_rxd_d0 <= uart_rxd;
        uart_rxd_d1 <= uart_rxd_d0;
        uart_rxd_d2 <= uart_rxd_d1;
    end
end

当检测到串口接收数据的下降沿时,表示接收过程的开始,即start_flag信号拉高,此时系统时钟计数器(clk_cnt)开始计数。当系统时钟计数器记到最大值(433)时接收数据计数器(rx_cnt)开始计数,当接收数据计数器大于等于1后,计数器每计数一次,将打拍后的数据uart_rxd_d2 寄存在rxdata中。当接收数据计数器记到9时,把rxdata寄存的数据rxdata给到输出端口uart_data,同时 拉高接收完成标志信号uart_done。

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        rx_flag <= 1'b0;
    else if(start_en)    //检测到起始位
        rx_flag <= 1'b1; //接收过程中,标志信号rx_flag拉高
    //在停止位一半的时候,即接收过程结束,标志信号rx_flag拉低
    else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1'b1))
        rx_flag <= 1'b0;
    else
        rx_flag <= rx_flag;
end        

//波特率的计数器赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        baud_cnt <= 16'd0;
    else if(rx_flag) begin     //处于接收过程时,波特率计数器(baud_cnt)进行循环计数
        if(baud_cnt < BAUD_CNT_MAX - 1'b1)
            baud_cnt <= baud_cnt + 16'b1;
        else 
            baud_cnt <= 16'd0; //计数达到一个波特率周期后清零
    end    
    else
        baud_cnt <= 16'd0;     //接收过程结束时计数器清零
end

//对接收数据计数器(rx_cnt)进行赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        rx_cnt <= 4'd0;
    else if(rx_flag) begin                  //处于接收过程时rx_cnt才进行计数
        if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时
            rx_cnt <= rx_cnt + 1'b1;        //接收数据计数器加1
        else
            rx_cnt <= rx_cnt;
    end
    else
        rx_cnt <= 4'd0;                     //接收过程结束时计数器清零
end        

//根据rx_cnt来寄存rxd端口的数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        rx_data_t_real <= 8'b0;
    else if(rx_flag) begin                           //系统处于接收过程时
        if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin  //判断baud_cnt是否计数到数据位的中间
           case(rx_cnt)
               4'd1 : rx_data_t_real[0] <= uart_rxd_d2_real;   //寄存数据的最低位
               4'd2 : rx_data_t_real[1] <= uart_rxd_d2_real;
               4'd3 : rx_data_t_real[2] <= uart_rxd_d2_real;
               4'd4 : rx_data_t_real[3] <= uart_rxd_d2_real;
               4'd5 : rx_data_t_real[4] <= uart_rxd_d2_real;
               4'd6 : rx_data_t_real[5] <= uart_rxd_d2_real;
               4'd7 : rx_data_t_real[6] <= uart_rxd_d2_real;
               4'd8 : rx_data_t_real[7] <= uart_rxd_d2_real;   //寄存数据的高低位
               default : ;
            endcase  
        end
        else
            rx_data_t_real <= rx_data_t_real;
    end
    else
        rx_data_t_real <= 8'b0;
end        
//给接收完成信号和接收到的数据赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        uart_rx_done <= 1'b0;
        uart_rx_data_real <= 8'b0;
    end
    //当接收数据计数器计数到停止位,且baud_cnt计数到停止位的中间时
    else if(rx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin
        uart_rx_done <= 1'b1     ;  //拉高接收完成信号
        uart_rx_data_real <= rx_data_t_real;  //并对UART接收到的数据进行赋值
    end     
    else begin
        uart_rx_done <= 1'b0;
        uart_rx_data_real <= uart_rx_data_real;
    end
end

参考:《正点原子领航者FPGA开发指南》

 

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值