FPGA——任意位宽的UART串口通信发送模块实现
扫描二维码
随时随地手机看文章
概
在实际工程应用中,常常需要通过串口一次性传输多字节的数据。例如,以太网模块可能会发送经过8字节对齐的一个时钟周期的64位数据,然后通过串口传递给下游的传感器设备。类似地,DDR模块可能以32字节对齐的形式发送一个时钟周期的256位数据,通过串口传输给上位机。如果每次都需要通过修改Verilog代码的方式将数据拆分成8位的块再发送给串口,将会显得非常繁琐。本文通过引入只有三种状态的状态机,只需更改参数就可实现对任意字节位宽的串口数据发送。这种方法使得在不同情境下轻松适应不同的数据格式,极大地提高了系统的灵活性和可维护性。

设计思路
首先,需要设计一个字节计数器,用于追踪数据发送到了哪一个字节。
接着,设计三个状态:空闲状态、准备发送状态和发送数据状态。
在准备发送状态下,主要完成两项任务:一是每到达一个字节就启用串口模块,二是加载数据以准备发送。
对于N字节数据的处理方式:每使用完一个字节后,执行右移八位的操作,然后将N位数据的低八位发送给串口模块(其中N为整数)。这确保了数据的有效传输,并适应了不同字节长度的情况。
data先从高位字节发送:
data <= data_tmp[DATAN_W-1:DATAN_W-8]; data_tmp <= {data_tmp[DATAN_W-9:0],data_tmp[DATAN_W-1:DATAN_W-8]};
data先从低位字节发送:
data <= data_tmp[7:0]; data_tmp <= {data_tmp[7:0],data_tmp[DATAN_W-1:8]};
多字节发送模块
该模块实现了多数据传输的基础功能,当然,可以扩展一个busy信号,当状态机未工作完成的时候,反压上游模块,告诉上游模块不能再发。busy信号可以可以通过状态机的IDLE状态来生成。
`timescale 1ns / 1ns module uart_tx_multibyte( clk , //时钟 rst_n , //复位 data_n , //要发送的多字节数据 trans_go , //发送使能 uart_tx //串口发送数据 ); parameter IDLE = 3'b001; //空闲状态 parameter S1 = 3'b010; //准备发送数据状态,使能,装载数据 parameter S2 = 3'b100; //发送数据状态 //CNTBY_N字节数,CNTBY_W字节数计数器的位宽(更改这两个参数,即可实现串口任意字节发送) parameter CNTBY_N = 5; //发送数据字节个数 parameter DATAN_W = 8*CNTBY_N; //发送数据个数 parameter CNTBY_W = 3; //发送数据字节计数器位宽,5=3'b101 parameter DATA_W = 8; //一次串行发送的数据个数 parameter STATE_W = 3; //状态机位宽 input clk; input rst_n; input [DATAN_W-1:0] data_n; input trans_go; output uart_tx; wire uart_tx; reg [CNTBY_W-1:0] cnt_bytes; wire add_cnt_bytes; wire end_cnt_bytes; reg cnt_bytes_flag; wire tx_done; reg [DATA_W-1:0] data; reg send_en; reg [STATE_W-1:0] state_c; reg [STATE_W-1:0] state_n; wire IDLE2S1_start; wire S12S2_start; wire S22S1_start ; wire S22IDLE_start ; reg [DATAN_W-1:0] data_tmp; my_uart_tx my_uart_tx( .clk (clk), //时钟 .rst_n (rst_n), //复位 .data (data), //发送数据 .send_en (send_en), //发送使能 .baud_set (4'd4), //波特率设置,默认9600bps .uart_tx (uart_tx), //串口发送 .tx_done (tx_done) //发送完成标志位 ); always @(posedge clk or negedge rst_n)begin if(!rst_n) cnt_bytes <= 0; else if(add_cnt_bytes)begin if(end_cnt_bytes) cnt_bytes <= 0; else cnt_bytes <= cnt_bytes + 1'b1; end end assign add_cnt_bytes = cnt_bytes_flag; assign end_cnt_bytes = add_cnt_bytes && cnt_bytes == CNTBY_N - 1; always @(*)begin if(tx_done) cnt_bytes_flag = 1; else cnt_bytes_flag = 0; end //状态机 //状态转移描述 always @(posedge clk or negedge rst_n)begin if(!rst_n) state_c <= IDLE; else state_c <= state_n; end //状态转移条件判断描述 always @(*)begin case(state_c) IDLE:begin if(IDLE2S1_start) state_n = S1; else state_n = state_c; end S1:begin if(S12S2_start) state_n = S2; else state_n = state_c; end S2:begin if(S22S1_start) state_n = S1; else if(S22IDLE_start) state_n = IDLE; else state_n = state_c; end default: state_n = IDLE; endcase end //状态转移条件描述 assign IDLE2S1_start = state_c==IDLE && trans_go; assign S12S2_start = state_c==S1 && send_en; assign S22S1_start = state_c==S2 && tx_done && !end_cnt_bytes; assign S22IDLE_start = state_c==S2 && end_cnt_bytes; //状态输出 always @(posedge clk or negedge rst_n)begin if(!rst_n) send_en <= 0; else if(state_c == S1) send_en <= 1; else send_en <= 0; end always @(posedge clk or negedge rst_n)begin if(!rst_n)begin data <= 0; data_tmp <= 0; end else if(state_c == IDLE)begin data <= 0; data_tmp <= data_n; end else if(state_c == S1 && !send_en)begin data <= data_tmp[DATAN_W-1:DATAN_W-8]; data_tmp <= {data_tmp[DATAN_W-9:0],data_tmp[DATAN_W-1:DATAN_W-8]}; end end endmodule
UART发送模块
module my_uart_tx( clk , //时钟 rst_n , //复位 data , //发送数据 send_en , //发送使能 baud_set , //波特率设置 uart_tx , //串口发送 tx_done //发送完成标志位 ); parameter DATA_W = 8; parameter SET_W = 3; parameter BYTE_D = 10; parameter BAUT_W = 17; parameter BYTE_W = 4; input clk; input rst_n; input [DATA_W-1:0] data; input send_en; input [SET_W-1:0] baud_set; output uart_tx; output tx_done; reg uart_tx; reg tx_done; reg [BAUT_W-1:0] cnt_baud; reg [BAUT_W-1:0] baud; wire add_cnt_baud; wire end_cnt_baud; reg [BYTE_W-1:0] cnt_byte; wire add_cnt_byte; wire end_cnt_byte; reg [BYTE_D-1:0] uart_data; reg cnt_baud_flag; always @(posedge clk or negedge rst_n)begin if(!rst_n) cnt_baud <= 0; else if(add_cnt_baud)begin if(end_cnt_baud) cnt_baud <= 0; else cnt_baud <= cnt_baud + 1'b1; end end assign add_cnt_baud = cnt_baud_flag; assign end_cnt_baud = add_cnt_baud && cnt_baud == baud - 1; always @(posedge clk or negedge rst_n)begin if(!rst_n) cnt_byte <= 0; else if(add_cnt_byte)begin if(end_cnt_byte) cnt_byte <= 0; else cnt_byte <= cnt_byte + 1'b1; end end assign add_cnt_byte = end_cnt_baud; assign end_cnt_byte = add_cnt_byte && cnt_byte == BYTE_D - 1; always @(posedge clk or negedge rst_n)begin if(!rst_n) uart_tx <= 1; else if(cnt_baud == 0 && add_cnt_baud) uart_tx <= uart_data[cnt_byte]; end always @(posedge clk or negedge rst_n)begin if(!rst_n) uart_data <= 10'b0; else if(!cnt_baud_flag && send_en) uart_data <= {1'b1,data,1'b0}; end always @(posedge clk or negedge rst_n)begin if(!rst_n) cnt_baud_flag <= 0; else if(send_en) cnt_baud_flag <= 1; else if(end_cnt_byte) cnt_baud_flag <= 0; end always @(*)begin case(baud_set) 3'd0:baud = 17'd83333;//600bps 3'd1:baud = 17'd41666;//1200bps 3'd2:baud = 17'd20833;//2400bps 3'd3:baud = 17'd10416;//4800bps 3'd4:baud = 17'd5208 ;//9600bps 3'd5:baud = 17'd2604 ;//19200bps 3'd6:baud = 17'd1302 ;//38400bps 3'd7:baud = 17'd868 ;//57600bps default: baud = 0; endcase end always @(posedge clk or negedge rst_n)begin if(!rst_n) tx_done <= 0; else if(end_cnt_byte) tx_done <= 1; else tx_done <= 0; end endmodule





