UART串口通信FPGA实现 📅 发布时间:2026/8/30 6:17:13 👁 浏览次数: 操作系统 Window10 Vivado2018.2采用RS232UART串口通信协议通过CH340芯片实现FPGA与PC端的串口通信实现数据的接收和发送一、UART 介绍UART是一种通信协议它代表通用异步收发传输器Universal Asynchronous Receiver/ Transmitter。它是一种串行通信协议用于在两个设备之间传输数据。UART可以与多种设备和通信标准兼容因此广泛用于嵌入式系统、计算机、通信设备和许多其他应用中。UART工作原理是将数据的每个bit一位一位地传输。UART是一种通用串行数据总线用于异步通信该总线双向通信可以实现全双工传输和接收。RS232是UART的一种是目前常用的一种串行通讯接口。二、RS232 介绍RS232 是UART的一种有两根线分别是TX和RX这两根数据线都是1bit位宽的。其中RX是接收数据线TX是发送数据线。RX外部发送过来的1bit数据通过RX接收至内部并在内部完成数据的拼接。FPGA 通过串口一位一位地从低位到高位依次接收数据最后在FPGA 内部拼接成8bit数据。TX发送至外部的1bit数据线。当FPGA向外部发送数据时FPGA 把8bit数据通过TX数据线一位一位的传给上位机从最低到最高位依次发送。最后上位机通过串口助手把些一位一位的数据拼接成8bit数据。UART时序在不发送数据和接受数据的情况下RXTX都处于空闲状态此时RXTX都保持高电平如果有数据传递首先会有一个起始位0然后是8bit的数据位接着有1bit的停止位1如果停止位后不再发送数据将进入空闲状态否则又将数据线拉低进入起始状态。如下图所示波特率波特率时串口中十分重要的一个参数指的是单位时间内载波变化的次数例如波特率是 9600bps指的是发送1bit数据需要1/9600s串口发送或者接收每一位数据都需要一个波特率电脑和开发板的波特率一致才能正常地通信。上位机在通过串口发送8bit数据的时候会自动在发送8位有效数据前发送一个波特时钟的起始位在发送完8位数据后会发送一个停止位。同理上位机在接收数据时也会识别到起始位和停止位所以在FPGA向PC上位机发送数据时也需要产生起始位和停止位。三、CH340芯片在开发板上有一颗usb信号和uart信号相互转换的芯片CH340,FPGA 发送串口数据的时候CH340将 uart信号转化为usb信号usb信号传输到上位机解析出来FPGA 接口数据的时候usb信号从上位机传输过来CH340将usb信号转化为串口信号然后FPGA接收。四、verilog代码实现1、uart_tx 发送模块发送模块通过 send_en 和 tx_data 传递要发送的数据主要的时序图如下当识别到send_en信号的时候en_cnt拉高计数器 cnt 就开始计数bps_cnt计数现在输出的bit 的个数当en_cnt为1的时候根据bps_cnt的值在rs232_tx上给出相应的值bps_cnt为0的时候给出起始位bps_cnt为1-8的时候给出要传输的数据先给出字节的低位再给出字节的高位数据完整实现代码如下。timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2026/08/29 21:47:00 // Design Name: // Module Name: uart_tx // Project Name: // Target Devices: // Tool Versions: // Description: 串口发送程序发送起始位一个字节停止位一共10比特数据 // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module uart_tx #( parameter SYS_FRENCY 50_000_000 ,//时钟 parameter BAUD_FRENCY 115200 //波特率 ) ( input clk ,//系统时钟 input reset_n ,//系统复位信号低电平有效 input send_en , //发送使能信号高电平有效 input [7:0]tx_data ,//输入数据即要发送的数据 output reg rs232_tx ,//发送的串行数据 output tx_done//发送数据完成输出一个高电平脉冲 ); localparam CNT_MAX SYS_FRENCY/BAUD_FRENCY-1 ;//发送一个比特数据所需要的clk时钟周期 reg en_cnt ;//计数使能信号高电平时开始计数 reg [7:0] r_tx_data;//用来寄存tx_data reg [3:0] bps_cnt ;//发送一个字节数据计数0~9 reg [15:0] cnt ; //r_tx_data always (posedge clk or negedge reset_n) begin if(!reset_n) r_tx_datad0; else if(send_en)//发送使能信号 r_tx_datatx_data;//寄存输入数据 else begin r_tx_datar_tx_data;//保持不变 end end //en_cnt always (posedge clk or negedge reset_n) begin if(!reset_n) en_cnt1b0; else if(send_en) en_cnt1b1; //(CNT_MAX1)(CNT_MAX2)表示发送一个比特数据的3/4时刻 else if(cnt(CNT_MAX1)(CNT_MAX2)bps_cnt8d9)//数据发送完毕 en_cnt1b0; else en_cnten_cnt; end //bps_cnt always (posedge clk or negedge reset_n) begin if(!reset_n) bps_cntd0; else if(en_cnt)begin if(cntCNT_MAX) bps_cntbps_cnt1b1; else bps_cntbps_cnt; end else bps_cntd0; end //cnt always (posedge clk or negedge reset_n) begin if(!reset_n) cntd0; else if(en_cnt)begin if(cntCNT_MAX) cntd0; else cntcnt1b1; end else cntd0; end //rs232_tx数据输出 always (*) begin if(!reset_n) rs232_tx1b1; else if(en_cnt)begin case (bps_cnt)//串行发送数据 0:rs232_tx1b0;//起始位拉低 1:rs232_txr_tx_data[0]; 2:rs232_txr_tx_data[1]; 3:rs232_txr_tx_data[2]; 4:rs232_txr_tx_data[3]; 5:rs232_txr_tx_data[4]; 6:rs232_txr_tx_data[5]; 7:rs232_txr_tx_data[6]; 8:rs232_txr_tx_data[7]; 9:rs232_tx1b1;//停止位拉高 default:rs232_tx1b1; //空闲时刻高电平 endcase end else rs232_tx1b1;//空闲时刻高电平 end assign tx_done(cnt(CNT_MAX1)(CNT_MAX2))bps_cnt4d9;//发送停止位时输入数据发送完毕信号 ila_tx ila_tx ( .clk(clk), // input wire clk .probe0(send_en), // input wire [0:0] probe0 .probe1(en_cnt), // input wire [0:0] probe1 .probe2(bps_cnt), // input wire [3:0] probe2 .probe3(rs232_tx) // input wire [0:0] probe3 ); endmodule2、uart_rx 接收模块接受模块在接受数据的时候先用寄存器打两拍进行处理这样能够消除亚稳态带来的风险。rs232发送数据时以字节为单位的下面介绍一下串口接受模块的主要的时序图识别rs232_rx信号的下降沿表明接受一帧数据开始通过计数器在rs232_rx信号每一bit信号的中间时刻产生一个bps_vld的高电平rx_data_r信号在bps_vld为高电平的时候也就是每一bit信号的中间时刻通过移位抓取rs232_rx的数据完整实现代码如下。timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2026/08/29 22:13:21 // Design Name: // Module Name: uart_rx // Project Name: // Target Devices: // Tool Versions: // Description: 接收数据起始位一个字节停止位 // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module uart_rx #( parameter SYS_FRENCY 50_000_000 ,//系统频率50M parameter BAUD_FRENCY 115200 //波特率 ) ( input clk ,//系统时钟 input reset_n ,//系统复位信号低电平有效 input rs232_rx ,//接收串行数据 output reg rx_done ,//数据接收完毕高电平脉冲 output reg [7:0]rx_data//输出一个字节数据 ); localparam CNT_MAX SYS_FRENCY/BAUD_FRENCY-1;//发送一个比特数据所需要的clk时钟周期 reg en_cnt ; //计数器使能控制 reg [15:0] cnt ; wire bps_vld ; //计数到每bit数据的中心产生一个高电平 reg [3:0] bps_cnt ; //接受位数的计数器 reg [1:0] rs232_rx_filter ; //用两个寄存器消除亚稳态 reg rs232_rx_d; //用一个寄存器来识别下降沿 wire nedge; //下降沿信号 reg [9:0] rx_data_r; wire rx_done_r; //消除亚稳态 always (posedge clk or negedge reset_n) begin if(!reset_n) rs232_rx_filter2b11; else rs232_rx_filter{rs232_rx_filter[0],rs232_rx}; end //下降沿识别 always (posedge clk or negedge reset_n) begin if(!reset_n) rs232_rx_d1b1; else rs232_rx_drs232_rx_filter[1]; end assign nedgers232_rx_d(!rs232_rx_filter[1]); //en_cnt always (posedge clk or negedge reset_n) begin if(!reset_n) en_cnt1b0; else if(nedge) en_cnt1b1; else if(rx_done_r) en_cnt1b0; else en_cnten_cnt; end //cnt always (posedge clk or negedge reset_n) begin if(!reset_n) cntd0; else if(en_cnt)begin if(cntCNT_MAX) cntd0; else cntcnt1b1; end else cntd0; end //在中间的时刻采集信号线上的数据表明数据有效 assign bps_vldcnt(CNT_MAX1); //bps_cnt 计数到每bit信号的中心然后再读取数值 always (posedge clk or negedge reset_n) begin if(!reset_n) bps_cntd0; else if(en_cnt)begin if(cntCNT_MAX) bps_cntbps_cnt1b1; else bps_cntbps_cnt; end else bps_cntd0; end //rx_data 读出数据 always (posedge clk or negedge reset_n) begin if(!reset_n) rx_data_rd0; else if (bps_vld)begin case (bps_cnt) 0,1,2,3,4,5,6,7,8,9: rx_data_r{rs232_rx_filter[1],rx_data_r[9:1]}; default: rx_data_rrx_data_r; endcase end else rx_data_rrx_data_r; end //在停止位的的3/4处产生结束信号 assign rx_done_r(cntCNT_MAX-(CNT_MAX2))(bps_cnt4d9); always (posedge clk or negedge reset_n) begin if(!reset_n) rx_datad0; else if(rx_done_r)begin if(!rx_data_r[0]rx_data_r[9]) rx_datarx_data_r[8:1]; else rx_data8d0;//数据错误置零 end else rx_datarx_data; end always (posedge clk or negedge reset_n) begin if(!reset_n) rx_done1b0; else rx_donerx_done_r; end ila_rx ila_rx ( .clk(clk), // input wire clk .probe0(rs232_rx), // input wire [0:0] probe0 .probe1(bps_vld), // input wire [0:0] probe1 .probe2(bps_cnt), // input wire [3:0] probe2 .probe3(rx_data_r) // input wire [9:0] probe3 ); endmodule3、uart_loop模块uart_loop模块用于验证上述UART通信代码是否正确实现采用uart_tx发送数据十六进制数据AA并用uart_rx模块接收完整代码实现如下。timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2026/08/29 22:20:41 // Design Name: // Module Name: uart_loop // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module uart_loop #( parameter SYS_FRENCY 50_000_000 ,//时钟 parameter BAUD_FRENCY 115200 //波特率 )( input clk, input reset_n, input rs232_rx, output rs232_tx ); wire rx_done; wire [7:0] rx_data; uart_tx #( .SYS_FRENCY (SYS_FRENCY), //系统频率50M .BAUD_FRENCY (BAUD_FRENCY) //波特率 ) uart_tx0 ( .clk (clk), .reset_n (reset_n), .send_en (rx_done), //发数据控制信号脉冲触发 .tx_data (rx_data), .rs232_tx (rs232_tx), .tx_done () ); uart_rx #( .SYS_FRENCY (SYS_FRENCY), //系统频率50M .BAUD_FRENCY (BAUD_FRENCY) //波特率 ) uart_rx0 ( .clk (clk) , .reset_n (reset_n) , .rs232_rx (rs232_rx) , .rx_done (rx_done) , .rx_data (rx_data) ); endmodule五、实现效果串口发送与接收uart_tx发送数据uart_rx接收数据上述结果采用串口调试助手发送十六进制数据AA对应实际数据10101010通过ILA捕获输入输出数据信号并验证证明UART串口通信成功通过FPGA实现对于代码中ILA的例化及其使用可以参考Vivado ILA IP核使用详解。结语上述关于FPGA中的UART串口通信实现除了发送一个字节数据还可以连续发送多个字节数据对此我就叙述到这如有不足欢迎交流讨论。