UART协议及串口回环

一、异步通信的分类

1、UART(通用异步收发器)

一般是指元器件之间的通信,用一位低电平作为起始位,再来8位数据位后面加校验和高电平的停止位,信号一般对地电压5V、3.3V等等都可以,这就是uart通信,

2、RS422

后来人们发现这种通信方案传输距离不长,于是将这种电平信号改成差分信号,每个差分信号需要两条线,那rx和tx就一共需要四条线,不需要gnd了,这种通信就叫RS422,

3、RS485

但人们发现一般情况下发送和接收很少一起于是就掐掉一路变成一对差分,这就是RS485。

4、Modbus

在实践中人们又发现在这每家都通信协议都不一样很难集成到一起,于是就统一定义一下发送的协议,每个设备有一个地址,在一个网络中主机首先发送从机地址然后再发送功能码字节长度和实际数据以及CRC校验,这样不仅稳定而且还能兼容很多产品,于是modbus就诞生了,也就是说modbus是建立在uart或者422或者485之上的一种协议,其实modbus还可以建立在tcp/ip上面

5、接口标准

在这里插入图片描述

二、UART协议要求

1、空闲状态

保持高电平

2、起始位

master设备由空闲状态的高电平拉为低电平,且持续一个bit数据的保持时间(跟波特率有关),用于告知slave设备准备接受数据

3、数据位

支持5~8bit的有效数据传输,由通信双方约定好通信的格式。先发送数据的低位,LSB表示低位,MSB表示高位
格式:起始位,bit0~bit7,校验位,停止位

4、校验位

用于数据校验的,校验的方式分为奇偶校验,校验位的作用是平衡数据中高电平数据的个数是奇数个/偶数个。
缩位异或(^A) 用于判断1的个数,0:偶数 1:奇数
缩位同或(~^A) 用于判断0的个数,0:奇数 1:偶数
奇校验(odd):保证数据(数据位+校验位)中奇数个逻辑高电平
偶校验(even):保证数据中偶数个逻辑高电平
例如:传输的数据为0100_0011
奇校验的校验位:0
偶校验的校验位:1

5、停止位

两台设备通信可能会存在小段时间的不同步情况,从而影响数据的传输结果,因此master必须保证有停止位,即数据线拉高一段时间。停止位不仅仅表示数据传输结束,也是矫正时钟同步的机会,让slave设备能够正确的识别下一轮数据的起始位。

6、波特率

码元:携带数据信息的信号单元(uart是单根线传输,所以码元就是一个二进制数)
波特率:每秒钟信道传输的码元数(二进制数据),符号Baud,单位波特每秒(Bps)
计算方法:50M时钟,周期为20ns
(1_000_000_000/115200)/20 = 434
传输1bit信号,在115200的波特率下,需要持续434个时钟周期

7、比特率

比特率:每秒钟信道传输的信息量,单位每秒比特数(bps)
公式:比特率 = 波特率*码元(对于一次传输一个bit数据而言,波特率=比特率)

三、汉字发送

UART每次只能发送8bit的数据,发送汉字信息需要连续发送16bit的信息
(一个汉字2个字节)发送的时ASCII码,需要使用软件查询汉字的ASCII码。发送汉字,先发送汉字对应16bit的ASCII码中的高位8bit,再发送低8bit

四、串口回环

uart_tx
`include "param.v"

module  uart_tx(       //发送数据  并串转换
    input               clk     ,
    input               rst_n   ,
    input   [1:0]       baud_sel,
    input   [7:0]       din     ,
    input               din_vld ,
    output              tx_dout ,
    output              tx_busy  //发送状态指示   
);

//信号定义
    reg     [12:0]      cnt0        ;//波特率计数器
    wire                add_cnt0    ;
    wire                end_cnt0    ;
    reg     [3:0]       cnt1        ;//bit计数器
    wire                add_cnt1    ;
    wire                end_cnt1    ;
    reg                 add_flag    ;
    
    reg     [`DATA_W+1:0] tx_data   ;

    reg     [12:0]      baud        ;//选择分频系数
    reg                 tx_bit      ;

//计数器

    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt0 <= 0; 
        end
        else if(add_cnt0) begin
            if(end_cnt0)
                cnt0 <= 0; 
            else
                cnt0 <= cnt0+1 ;
       end
    end
    assign add_cnt0 = (add_flag);
    assign end_cnt0 = add_cnt0  && cnt0 == (baud)-1 ;

    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt1 <= 0; 
        end
        else if(add_cnt1) begin
            if(end_cnt1)
                cnt1 <= 0; 
            else
                cnt1 <= cnt1+1 ;
       end
    end
    assign add_cnt1 = (end_cnt0);
    assign end_cnt1 = add_cnt1  && cnt1 == (`DATA_W)+1;

//add_flag  计数器使能信号
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            add_flag <= 1'b0;
        end
        else if(din_vld)begin
            add_flag <= 1'b1;
        end
        else if(end_cnt1)begin 
            add_flag <= 1'b0;
        end
    end

    always  @(*)begin
        case(baud_sel)
            0:baud = `BAUD_RATE_115200;
            1:baud = `BAUD_RATE_57600;
            2:baud = `BAUD_RATE_38400;
            3:baud = `BAUD_RATE_9600;
            default:baud = `BAUD_RATE_115200;
        endcase 
    end

//输出
    `ifdef PARITY_ENABLE    //使能校验功能
        //reg     [`DATA_W+1:0] tx_data     ;
        wire            parity_bit  ;
        assign parity_bit = (`PARITY_TYPE == 1'b1) ? (~^din) : (^din);
        always  @(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin
                tx_data <= 0;
            end
            else if(din_vld)begin   
                //收到请求时,将 停止位 校验位 数据 起始位 拼接
                tx_data <= {1'b1,parity_bit,din,1'b0};
            end
        end
    `else      //未使能校验功能
        //reg     [`DATA_W+1:0] tx_data     ;
        always  @(posedge clk or negedge rst_n)begin
            if(rst_n==1'b0)begin
                tx_data <= 0;
            end
            else if(din_vld)begin   
                //收到请求时,将 校验位 数据 起始位 拼接
                tx_data <= {1'b1,din,1'b0};
            end
        end
    `endif 

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_bit <= 1'b1;
        end
        else if(add_cnt0 && cnt0 == 1-1)begin
            tx_bit <= tx_data[cnt1];
        end
    end

    assign tx_busy = din_vld | add_flag;
    assign tx_dout = tx_bit;

endmodule 

uart_rx
`include "param.v"

module uart_rx (       //接收串行数据 串并转换
    input               clk     ,
    input               rst_n   ,
    input   [1:0]       baud_sel,
    input               rx_din  ,
    output  [7:0]       rx_dout ,
    output              rx_vld   
);

//定义信号
    reg     [12:0]      cnt0        ;//波特率计数器
    wire                add_cnt0    ;
    wire                end_cnt0    ;
    reg     [3:0]       cnt1        ;//bit计数器
    wire                add_cnt1    ;
    wire                end_cnt1    ;
    reg                 add_flag    ;
    
    reg     [12:0]      baud        ;//选择分频系数

    reg     [2:0]       rx_din_r    ;//同步、打拍寄存器
    wire                n_edge      ;//下降沿检测

    reg     [`DATA_W:0] rx_data     ;//采样数据寄存器

//计数器

    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt0 <= 0; 
        end
        else if(add_cnt0) begin
            if(end_cnt0)
                cnt0 <= 0; 
            else
                cnt0 <= cnt0+1 ;
       end
    end
    assign add_cnt0 = (add_flag);
    assign end_cnt0 = add_cnt0  && cnt0 == (baud)-1 ;

    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt1 <= 0; 
        end
        else if(add_cnt1) begin
            if(end_cnt1)
                cnt1 <= 0; 
            else
                cnt1 <= cnt1+1 ;
       end
    end
    assign add_cnt1 = (end_cnt0);
    assign end_cnt1 = add_cnt1  && (cnt1 == (`DATA_W) || rx_data[0]);

//add_flag  计数器使能信号
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            add_flag <= 1'b0;
        end
        else if(n_edge)begin
            add_flag <= 1'b1;
        end
        else if(end_cnt1)begin
            add_flag <= 1'b0;
        end
    end

    always  @(*)begin
        case(baud_sel)
            0:baud = `BAUD_RATE_115200;
            1:baud = `BAUD_RATE_57600;
            2:baud = `BAUD_RATE_38400;
            3:baud = `BAUD_RATE_9600;
            default:baud = `BAUD_RATE_115200;
        endcase 
    end

//下降沿检测  同步打拍
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_din_r <= 3'b111;
        end
        else begin
            rx_din_r <= {rx_din_r[1:0],rx_din};
            //rx_din_r[0] <= rx_din;      //同步
            //rx_din_r[1] <= rx_din_r[0]; //打拍
            //rx_din_r[2] <= rx_din_r[1]; //打拍
        end
    end

    assign n_edge = rx_din_r[2] & ~rx_din_r[1];

//采样数据
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_data <= 0;
        end
        else if(add_flag && cnt0 == (baud >> 1))begin
            //rx_data <= {rx_din,rx_data[`DATA_W:1]};//右移
            rx_data[cnt1] <= rx_din;
        end
    end

//校验
    `ifdef PARITY_ENABLE       //开启校验
        wire        parity_result  ;
        assign parity_result = ^rx_data[`DATA_W:1];
        assign rx_dout = rx_data[`DATA_W-1:1];
        assign rx_vld  = end_cnt1 & ~rx_data[0] & parity_result == `PARITY_TYPE;
    `elsif          //不开启校验
        assign rx_dout = rx_data[`DATA_W:1];
        assign rx_vld  = end_cnt1 & ~rx_data[0];
    `endif 

//输出

//    assign rx_dout = rx_data[8:1];
//    assign rx_vld  = end_cnt0 & cnt1 == (9)-1 & ~rx_data[0];

endmodule 


ctrl
module control (     //缓存
    input               clk     ,
    input               rst_n   ,
    input   [7:0]       din     ,
    input               din_vld ,
    input               busy    ,
    output  [7:0]       dout    ,
    output              dout_vld    
);

//信号定义
    reg                 rd_flag     ;
    reg     [7:0]       tx_data     ;
    reg                 tx_data_vld ;

    wire                fifo_rdreq  ;
    wire                fifo_wrreq  ;
    wire                fifo_empty  ;
    wire                fifo_full   ;
    wire    [7:0]       fifo_qout   ;
    wire    [3:0]       fifo_usedw  ;

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rd_flag <= 1'b0;
        end
        else if(fifo_usedw >= 8)begin
            rd_flag <= 1'b1;
        end
        else if(fifo_empty)begin 
            rd_flag <= 1'b0;
        end 
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_data <= 0;
            tx_data_vld <= 1'b0; 
        end
        else begin
            tx_data <= fifo_qout;
            tx_data_vld <= fifo_rdreq;
        end
    end

//FIFO例化

    fifo u_fifo(
	.aclr   (~rst_n     ),
	.clock  (clk        ),
	.data   (din        ),
	.rdreq  (fifo_rdreq ),
	.wrreq  (fifo_wrreq ),
	.empty  (fifo_empty ),
	.full   (fifo_full  ),
	.q      (fifo_qout  ),
	.usedw  (fifo_usedw )
    );
    
    assign fifo_wrreq = din_vld & ~fifo_full;
    assign fifo_rdreq = rd_flag & ~busy;  //发送模块 非忙状态下 给发送请求
    
//    assign dout_vld = fifo_rdreq;
//    assign dout = fifo_qout;

    assign dout_vld = tx_data_vld;  //时序逻辑输出
    assign dout = tx_data;

endmodule 


top
module top(
    input               clk     ,
    input               rst_n   ,
    
    input               uart_rxd,
    output              uart_txd    
);

//信号定义

    wire        [7:0]       rx_byte     ;
    wire                    rx_byte_vld ;
    wire        [7:0]       tx_byte     ;
    wire                    tx_byte_vld ;
    wire                    tx_busy     ;
    wire        [1:0]       baud_sel    ;
    
    assign baud_sel = 2'd0; //选择波特率

//模块例化
    
    uart_rx u_rx(       //接收串行数据 串并转换
    /*input               */.clk     (clk           ),
    /*input               */.rst_n   (rst_n         ),
    /*input   [1:0]       */.baud_sel(baud_sel      ),
    /*input               */.rx_din  (uart_rxd      ),
    /*output  [7:0]       */.rx_dout (rx_byte       ),
    /*output              */.rx_vld  (rx_byte_vld   ) 
    );

    control u_ctrl(     //缓存
    /*input               */.clk     (clk           ),
    /*input               */.rst_n   (rst_n         ),
    /*input   [7:0]       */.din     (rx_byte       ),
    /*input               */.din_vld (rx_byte_vld   ),
    /*input               */.busy    (tx_busy       ),
    /*output  [7:0]       */.dout    (tx_byte       ),
    /*output              */.dout_vld(tx_byte_vld   )    
    );

    uart_tx u_tx(       //发送数据  并串转换
    /*input               */.clk     (clk           ),
    /*input               */.rst_n   (rst_n         ),
    /*input   [1:0]       */.baud_sel(baud_sel      ),
    /*input   [7:0]       */.din     (tx_byte       ),
    /*input               */.din_vld (tx_byte_vld   ),
    /*output              */.tx_dout (uart_txd      ),
    /*output              */.tx_busy (tx_busy       ) //发送状态指示   
    );

endmodule 


五、参考

UART的fpga实现

六、源码

https://github.com/IvanXiang/FPGA_UART

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
UART是一种通信协议,用于将数据从一个设备传输到另一个设备。在串口通信中,一个设备通过一个串口发送数据,而另一个设备通过另一个串口接收数据。以下是基于UART协议串口发送的步骤: 1. 设置串口参数:波特率、数据位、停止位、奇偶校验位等。这些参数必须与接收端设置的参数匹配,才能正确地接收数据。 2. 将要发送的数据存储在一个缓冲区中。 3. 使用串口发送寄存器将数据发送到串口。 4. 等待数据传输完成,通常通过检查发送寄存器中的状态位来完成。 5. 如果有更多的数据需要发送,重复步骤2到4。 下面是使用C语言编写的基于UART协议串口发送示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <termios.h> int main() { int fd; struct termios options; fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY); if (fd < 0) { printf("Failed to open serial port\n"); return -1; } tcgetattr(fd, &options); cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); options.c_iflag &= ~(IXON | IXOFF | IXANY); options.c_oflag &= ~OPOST; options.c_cc[VMIN] = 1; options.c_cc[VTIME] = 0; tcsetattr(fd, TCSANOW, &options); char buffer[] = "Hello World\n"; int len = strlen(buffer); write(fd, buffer, len); close(fd); return 0; } ``` 在这个例子中,我们打开了/dev/ttyUSB0串口,在Linux中这通常是一个USB串口适配器。然后,我们设置了波特率为9600,数据位为8,无奇偶校验,停止位为1。接下来,我们将要发送的数据存储在缓冲区中,并使用write函数将其发送到串口。最后,我们关闭了串口并返回0。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ivan@Xiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值