FPGA Verilog 实现串口发送任意字节数据(8的倍数)
FPGA Verilog 实现串口发送任意字节数据(8的倍数)
FPGA Verilog 实现串口发送任意字节数据(8的倍数)
前言
// ** 功能 : 1、基于FPGA的串口多字节发送模块;
// 2、可设置一次发送的字节数、波特率BPS、主时钟CLK_FRE;
// 3、UART协议设置为起始位1bit,数据位8bit,停止位1bit,无奇偶校验(不可在端口更改,只能更改发送驱动源码);
// 4、每发送1次多字节后拉高指示信号一个周期,指示一次多字节发送结束;
// 5、数据发送顺序,先发送低字节、再发送高字节。如:发送16’h12_34,先发送单字节8’h34,再发送单字节8’h12。
一、工程代码
代码参考gitHub上一位博主的代码 需要的同志可以直接去这里下载
顶层模块
module uart_bytes_tx
#(
parameter integer BYTES = 2 , //发送字节数,单字节8bit
parameter integer BPS = 1000000 , //发送波特率
parameter integer CLK_FRE = 100_000_000 //输入时钟频率
)
(
//系统接口
input sys_clk , //系统时钟
input sys_rst_n , //系统复位,低电平有效
//用户接口
//input [(BYTES * 8 - 1):0] uart_bytes_data , //需要通过UART发送的多字节数据,在uart_bytes_en为高电平时有效
input uart_bytes_en , //发送有效,当其为高电平时,代表此时需要发送的数据有效
//UART发送
output uart_bytes_done , //成功发送完所有字节数据后拉高1个时钟周期
output uart_txd //UART发送数据线tx
);
reg [(BYTES * 8 - 1):0] uart_bytes_data=16'b1101001010010110;
//reg define
reg [(BYTES*8-1):0] uart_bytes_data_reg; //寄存接收到的多字节数据
reg work_en; //高电平表示处于发送状态,低电平表示空闲状态
reg [9:0] byte_cnt; //发送的字节个数计数(因为懒直接用10bit计数,最大可以表示1024BYTE,大概率不会溢出)
reg [7:0] uart_sing_data; //拆解的要发送的单个字节数据
reg uart_sing_en; //要发送的单个字节数据发送使能
reg uart_bytes_done_reg; //所有字节发送完毕打拍
reg uart_sing_done_reg; //单个字节数据发送完毕打拍
//wire define
wire uart_sing_done; //单个字节发送完成标志信号
//对端口赋值
assign uart_bytes_done = uart_bytes_done_reg;
//当发送使能信号到达时,寄存待发送的多字节数据以免后续变化、丢失
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_bytes_data_reg <= 0;
else if(uart_bytes_en && ~work_en) //要发送有效的数据,且并未处于发送状态
uart_bytes_data_reg <= uart_bytes_data; //寄存需要发送的数据
else if(uart_sing_done)
uart_bytes_data_reg <= uart_bytes_data_reg >> 8; //发送完一个数据后,把多字节数据右移8bit
else
uart_bytes_data_reg <= uart_bytes_data_reg;
end
//当发送使能信号到达时,进入工作状态
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
work_en <= 1'b0;
else if(uart_bytes_en && ~work_en) //要发送有效的数据且未处于工作状态
work_en <= 1'b1; //进入发送状态
else if(uart_sing_done && byte_cnt == BYTES - 1) //发送完了最后一个字节的数据
work_en <= 1'b0; //发送完毕,退出工作状态
else
work_en <= work_en;
end
//在工作状态对发送的数据个数进行计数
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
byte_cnt <= 0;
else if(work_en)begin //处于发送状态则需要对发送的字节个数计数
if(uart_sing_done && byte_cnt == BYTES - 1) //计数到了最大值则清零
byte_cnt <= 0;
else if(uart_sing_done) //发送完一个单字节则计数器+1
byte_cnt <= byte_cnt + 1'b1;
else
byte_cnt <= byte_cnt;
end
else //不处于发送状态则清零
byte_cnt <= 0;
end
//打拍凑时序·~·
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_sing_done_reg <= 0;
else
uart_sing_done_reg <= uart_sing_done;
end
//发送单个字节的数据
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_sing_data <= 8'd0;
else if(uart_bytes_en && ~work_en) //进入工作状态后马上发送第一个数据
uart_sing_data <= uart_bytes_data[7:0]; //发送最低字节
else if(uart_sing_done_reg) //发送完一个字节则发送另一个字节
uart_sing_data <= uart_bytes_data_reg[7:0]; //先右移8bit,然后取低8bit
else
uart_sing_data <= uart_sing_data; //保持稳定
end
//发送单个字节的数据使能
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_sing_en <= 1'b0;
else if(uart_bytes_en && ~work_en) //进入工作状态后马上发送第一个数据
uart_sing_en <= 1'b1;
else if(uart_sing_done_reg && work_en) //发送完一个字节则发送另一个字节
uart_sing_en <= 1'b1;
else
uart_sing_en <= 1'b0; //其他时候则为0
end
//所有数据发送完毕
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_bytes_done_reg <= 1'b0;
else if(uart_sing_done && byte_cnt == BYTES - 1)
uart_bytes_done_reg <= 1'b1;
else
uart_bytes_done_reg <= 1'b0;
end
//例化发送驱动模块
uart_tx #(
.BPS (BPS ),
.CLK_FRE (CLK_FRE )
)
uart_tx_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.uart_tx_data (uart_sing_data ),
.uart_tx_en (uart_sing_en ),
.uart_tx_done (uart_sing_done ),
.uart_txd (uart_txd )
);
endmodule
子模块
module uart_tx
#(
parameter integer BPS = 1000000 , //发送波特率
parameter integer CLK_FRE = 100_000_000 //主时钟频率
)
(
//系统接口
input sys_clk , //系统时钟
input sys_rst_n , //系统复位,低电平有效
//用户接口
input [7:0] uart_tx_data , //需要通过UART发送的数据,在uart_tx_en为高电平时有效
input uart_tx_en , //发送有效,当其为高电平时,代表此时需要发送的数据有效
//UART发送
output reg uart_tx_done , //成功发送1BYTE数据后拉高一个周期
output reg uart_txd , //UART发送数据线tx
output reg uart_change
);
//param define
localparam integer BPS_CNT = CLK_FRE / BPS; //根据波特率计算传输每个bit需要计数多个系统时钟
localparam integer BITS_NUM = 10 ; //发送格式确定需要发送的bit数,10bit = 1起始位 + 8数据位 + 1停止位
//reg define
reg tx_state ; //发送标志信号,拉高代表发送过程正在进行
reg [7:0] uart_tx_data_reg ; //寄存要发送的数据
reg [31:0] clk_cnt ; //计数器,用于计数发送一个bit数据所需要的时钟数
reg [3:0] bit_cnt ; //bit计数器,标志当前发送了多少个bit
//当发送使能信号到达时,寄存待发送的数据以免后续变化、丢失
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_tx_data_reg <=8'd0;
else if(uart_tx_en) //要发送有效的数据
uart_tx_data_reg <= uart_tx_data; //寄存需要发送的数据
else
uart_tx_data_reg <= uart_tx_data_reg;
end
//当发送使能信号到达时,进入发送过程
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
tx_state <=1'b0;
else if(uart_tx_en)
tx_state <= 1'b1; //发送信号有效则进入发送过程
//发送完了最后一个数据则退出发送过程
else if((bit_cnt == BITS_NUM - 1'b1) && (clk_cnt == BPS_CNT - 1'b1))
tx_state <= 1'b0;
else
tx_state <= tx_state;
end
//发送数据完毕后拉高发送完毕信号一个周期,指示一个字节发送完毕
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_tx_done <=1'b0;
//发送数据完毕后拉高发送完毕信号一个周期
else if((bit_cnt == BITS_NUM - 1'b1) && (clk_cnt == BPS_CNT - 1'b1))
uart_tx_done <=1'b1;
else
uart_tx_done <=1'b0;
end
//进入发送过程后,启动时钟计数器与发送个数bit计数器
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
clk_cnt <= 32'd0;
bit_cnt <= 4'd0;
end
else if(tx_state) begin //在发送状态
if(clk_cnt < BPS_CNT - 1'd1)begin //一个bit数据没有发送完
clk_cnt <= clk_cnt + 1'b1; //时钟计数器+1
bit_cnt <= bit_cnt; //bit计数器不变
end
else begin //一个bit数据发送完了
clk_cnt <= 32'd0; //清空时钟计数器,重新开始计时
bit_cnt <= bit_cnt+1'b1; //bit计数器+1,表示发送完了一个bit的数据
uart_change <= ~uart_change;
end
end
else begin //不在发送状态
clk_cnt <= 32'd0; //清零
bit_cnt <= 4'd0; //清零
end
end
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_txd <= 1'b1; //默认为高状态,空闲状态
else if(tx_state) //处于发送状态
case(bit_cnt) //数据发送从低位到高位
4'd0: uart_txd <= 1'b0; //起始位,拉低
4'd1: uart_txd <= uart_tx_data_reg[0]; //发送最低位数据
4'd2: uart_txd <= uart_tx_data_reg[1]; //
4'd3: uart_txd <= uart_tx_data_reg[2]; //
4'd4: uart_txd <= uart_tx_data_reg[3]; //
4'd5: uart_txd <= uart_tx_data_reg[4]; //
4'd6: uart_txd <= uart_tx_data_reg[5]; //
4'd7: uart_txd <= uart_tx_data_reg[6]; //
4'd8: uart_txd <= uart_tx_data_reg[7]; //发送最高位数据
4'd9: uart_txd <= 1'b1; //终止位,拉高
default:uart_txd <= 1'b1;
endcase
else //不处于发送状态
uart_txd <= 1'b1; //默认为高状态,空闲状态
end
endmodule
二、注意事项
1.找对串口
2.设置好波特率
3.停止位 数据位设置等
总结
本代码已通过实际上板验证,仿真也没问题,如果用起来有问题请评论区告诉我。