🎉欢迎来到FPGA专栏~串口发送模块
- ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹
- ✨博客主页:小夏与酒的博客
- 🎈该系列文章专栏:FPGA学习之旅
- 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
- 📜 欢迎大家关注! ❤️
一、效果演示
1.1 演示
🥝发送测试:
🥝issp调试测试:
数据调试:
调试数据发送:
1.2 串口发送模块完整代码(可直接使用)
🥝模块端口介绍:
信号名称 | 功能描述 |
---|---|
Clk | 系统时钟50MHz |
Rst_n | 系统复位信号 |
data_byte | 待传输的8bit数据 |
send_en | 发送使能信号 |
baud_set | 波特率设置信号 |
uart_tx | 串口信号输出 |
Tx_Done | 发送结束信号,输出一个时钟周期高电平 |
uart_state | 发送状态,处于发送状态时为1 |
🥝baud_set值与波特率对应关系:
baud_set | 波特率 |
---|---|
000 | 9600 |
001 | 19200 |
010 | 38400 |
011 | 57600 |
100 | 115200 |
uart_byte_tx.v:
//
//模块名称:串口发送模块
//
module uart_byte_tx(
input Clk,
input Rst_n,
input [7:0] data_byte,
input send_en,
input [2:0] baud_set,
output reg uart_tx,
output reg Tx_Done,
output reg uart_state
);
reg bps_clk;//波特率时钟
reg [15:0]div_cnt;//分频计数器
reg [15:0]bps_DR;//分频计数最大值
reg [3:0]bps_cnt;//波特率计数时钟
//定义数据的起始位和停止位
localparam START_BIT = 1'b0;
localparam STOP_BIT = 1'b1;
reg [7:0]r_data_byte;//数据寄存器
//--------<uart状态模块>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt == 4'd11)//bps_cnt计数达到11次,即发送结束
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
//--------<使能分频计数模块>-------
assign en_cnt = uart_state;
//--------<寄存待发送的数据,使数据保持稳定>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
r_data_byte <= 8'd0;
else if(send_en)
r_data_byte <= data_byte;
else
r_data_byte <= r_data_byte;
end
//--------<波特率查找表>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
bps_DR <= 16'd5207;
else begin
case(baud_set)
0:bps_DR <= 16'd5207;
1:bps_DR <= 16'd2603;
2:bps_DR <= 16'd1301;
3:bps_DR <= 16'd867;
4:bps_DR <= 16'd433;
default:bps_DR <= 16'd5207;
endcase
end
end
//--------<Div_Cnt模块>--------
//得到不同计数周期的计数器
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
div_cnt <= 16'd0;
else if(en_cnt)begin
if(div_cnt == bps_DR)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 16'd0;
end
//--------<bps_clk信号的产生>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1)
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
end
//--------<bps_cnt计数模块>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
bps_cnt <= 4'd0;
else if(bps_cnt == 4'd11)//clr信号
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
//--------<Tx_Done模块>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Tx_Done <= 1'b0;
else if(bps_cnt == 4'd11)
Tx_Done <= 1'b1;
else
Tx_Done <= 1'b0;
end
//--------<数据位输出模块-10选1多路器>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
uart_tx <= 1'b1;
else begin
case(bps_cnt)
0:uart_tx <= 1'b1;
1:uart_tx <= START_BIT;
2:uart_tx <= r_data_byte[0];
3:uart_tx <= r_data_byte[1];
4:uart_tx <= r_data_byte[2];
5:uart_tx <= r_data_byte[3];
6:uart_tx <= r_data_byte[4];
7:uart_tx <= r_data_byte[5];
8:uart_tx <= r_data_byte[6];
9:uart_tx <= r_data_byte[7];
10:uart_tx <= STOP_BIT;
default:uart_tx <= 1'b1;
endcase
end
end
endmodule
二、串口发送时序
串口发送一个字节数据的时序图参考如下:
根据上图,一个字节想要成功发送完成,需要计数11次
波特率时钟的高电平;而且一个完整的字节发送需要10位数据位,包括1个起始位,8个数据位和1个停止位。
需要注意,在串口未发送数据时(波特率时钟未使能时),串口输出高电平;在第一个波特率时钟高电平到来时,开始发送起始位。
🥝波特率计算:
baud_set | 波特率bps | 周期 | 分频计数值 | 50M系统时钟计数值 |
---|---|---|---|---|
0 | 9600 | 104167ns | 104167/System_clk_period | 5208-1 |
1 | 19200 | 52083ns | 52083/System_clk_period | 2604-1 |
2 | 38400 | 26041ns | 26041/System_clk_period | 1302-1 |
3 | 57600 | 17361ns | 17361/System_clk_period | 868-1 |
4 | 115200 | 8680ns | 8680/System_clk_period | 434-1 |
三、模块设计与代码详解
根据小梅哥FPGA设计的电路图进行代码编写:
代码理解主要参考前文中代码的模块注释和串口发送时序图。
在此展示简单的测试激励文件:
uart_byte_tx_tb.v:
`timescale 1ns/1ns
`define clock_period 20
module uart_byte_tx_tb;
reg Clk;
reg Rst_n;
reg [7:0]data_byte;
reg send_en;
reg [2:0]baud_set;
wire uart_tx;
wire Tx_Done;
wire uart_state;
uart_byte_tx Uuart_byte_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.data_byte(data_byte),
.send_en(send_en),
.baud_set(baud_set),
.uart_tx(uart_tx),
.Tx_Done(Tx_Done),
.uart_state(uart_state)
);
initial Clk = 1;
always#(`clock_period / 2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
data_byte = 8'd0;
send_en = 1'b0;
baud_set = 3'd4;
#(`clock_period * 20 + 1);
Rst_n = 1'b1;
#(`clock_period * 50);
data_byte = 8'haa;
send_en = 1'b1;
#(`clock_period * 1);
send_en = 1'b0;
@(posedge Tx_Done)
#(`clock_period * 5000);
data_byte = 8'hff;
send_en = 1'b1;
#(`clock_period * 1);
send_en = 1'b0;
@(posedge Tx_Done)
#(`clock_period * 5000);
$stop;
end
endmodule
仿真结果:
Tx_Done信号的正确输出:
四、按键控制串口发送数据
先看整体的RTL视图来理解设计思路:
按键信号经过KeyFilter按键消抖模块之后的有效信号用来控制串口发送模块的发送使能;HEX8模块和74HC595模块用来驱动三线制数码管显示数据;issp模块用来从电脑端调试数据;uart_byte_tx模块用来将调试数据通过串口发送给电脑。
🔸按键消抖模块的详细讲解:【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现)。
🔸HEX8模块的详细讲解:【FPGA零基础学习之旅#11】数码管动态扫描。
🔸74HC595模块的详细讲解:【FPGA零基础学习之旅#12】三线制数码管驱动(74HC595)串行移位寄存器驱动。
🔸issp ip核的创建和使用包含在文章【FPGA零基础学习之旅#11】数码管动态扫描中。
在此给出顶层模块,其余模块见文末:
uart_byte_tx_top.v:
module uart_byte_tx_top(
input Clk, //50M
input Rst_n,
input key_in,
output SH_CP, //shift clock
output ST_CP, //latch data clock
output DS, //shift serial data
output uart_tx,
output led
);
wire [7:0] sel;//数码管位选(选择当前要显示的数码管)
wire [7:0] seg;//数码管段选(当前要显示的内容)
wire [7:0] data_byte;
wire key_flag;
wire key_state;
issp issp(
.probe(),
.source(data_byte)
);
KeyFilter KeyFilter(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
//通过按键状态使能串口发送模块,按键按下时,串口发送模块使能
assign send_en = key_flag & (!key_state);
uart_byte_tx uart_byte_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.data_byte(data_byte),
.send_en(send_en),
.baud_set(3'd0),
.uart_tx(uart_tx),
.Tx_Done(),
.uart_state(led)
);
//由于串口目前只发送一个字节数据,只占用2个数码管,所以其余数码管显示0
HEX8 HEX8(
.Clk(Clk),
.Rst_n(Rst_n),
.En(1'b1),
.disp_data({24'h0,data_byte}),
.sel(sel),
.seg(seg)
);
m74HC595_Driver m74HC595_Driver(
.Clk(Clk),
.Rst_n(Rst_n),
.Data({seg,sel}),
.S_EN(1'b1),
.SH_CP(SH_CP),
.ST_CP(ST_CP),
.DS(DS)
);
endmodule
程序配置完成之后的数码管显示和led的点亮:
使用issp更改数据,数码管显示数据并通过按键发送:
🥝修改为11:
🥝修改为AF:
本案例的其余模块在此部分列出:
KeyFilter.v:
//
//模块:按键消抖模块
//key_state:输出消抖之后按键的状态
//key_flag:按键消抖结束时产生一个时钟周期的高电平脉冲
//
module KeyFilter(
input Clk,
input Rst_n,
input key_in,
output reg key_flag,
output reg key_state
);
//按键的四个状态
localparam
IDLE = 4'b0001,
FILTER1 = 4'b0010,
DOWN = 4'b0100,
FILTER2 = 4'b1000;
//状态寄存器
reg [3:0] curr_st;
//边沿检测输出上升沿或下降沿
wire pedge;
wire nedge;
//计数寄存器
reg [19:0]cnt;
//使能计数寄存器
reg en_cnt;
//计数满标志信号
reg cnt_full;//计数满寄存器
//------<边沿检测电路的实现>------
//边沿检测电路寄存器
reg key_tmp0;
reg key_tmp1;
//边沿检测
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
end
assign nedge = (!key_tmp0) & (key_tmp1);
assign pedge = (key_tmp0) & (!key_tmp1);
//------<状态机主程序>------
//状态机主程序
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(curr_st)
IDLE:begin
key_flag <= 1'b0;
if(nedge)begin
curr_st <= FILTER1;
en_cnt <= 1'b1;
end
else
curr_st <= IDLE;
end
FILTER1:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else if(pedge)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER1;
end
DOWN:begin
key_flag <= 1'b0;
if(pedge)begin
curr_st <= FILTER2;
en_cnt <= 1'b1;
end
else
curr_st <= DOWN;
end
FILTER2:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b1;
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else if(nedge)begin
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER2;
end
default:begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
end
//------<20ms计数器>------
//20ms计数器
//Clk 50_000_000Hz
//一个时钟周期为20ns
//需要计数20_000_000 / 20 = 1_000_000次
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
endmodule
HEX8.v:
module HEX8(
input Clk, //50M
input Rst_n, //复位
input En, //数码管显示使能
input [31:0] disp_data, //8 × 4 = 32(8个数码管,数据格式为hex,总共输32位)
output reg [7:0] seg, //数码管段选
output [7:0] sel //数码管位选(数码管选择)
);
reg [7:0]sel_r;
//----------<分频器>----------
reg [14:0]divider_cnt;//25000-1
reg clk_1K;
reg [3:0]data_tmp;//待显示数据缓存
//1KHz分频计数器
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
divider_cnt <= 15'd0;
else if(!En)
divider_cnt <= 15'd0;
else if(divider_cnt == 24999)
divider_cnt <= 15'd0;
else
divider_cnt <= divider_cnt + 1'b1;
end
//1KHz扫描时钟
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
clk_1K <= 1'b0;
else if(divider_cnt == 24999)
clk_1K <= ~clk_1K;
else
clk_1K <= clk_1K;
end
//----------<6位循环移位寄存器>-----------
always@(posedge clk_1K or negedge Rst_n)begin
if(!Rst_n)
sel_r <= 8'b0000_0001;
else if(sel_r == 8'b1000_0000)
sel_r <= 8'b0000_0001;
else
sel_r <= sel_r << 1;
end
//----------<6选1多路器>----------
always@(*)begin
case(sel_r)
8'b0000_0001:data_tmp = disp_data[3:0];
8'b0000_0010:data_tmp = disp_data[7:4];
8'b0000_0100:data_tmp = disp_data[11:8];
8'b0000_1000:data_tmp = disp_data[15:12];
8'b0001_0000:data_tmp = disp_data[19:16];
8'b0010_0000:data_tmp = disp_data[23:20];
8'b0100_0000:data_tmp = disp_data[27:24];
8'b1000_0000:data_tmp = disp_data[31:28];
default:data_tmp = 4'b0000;
endcase
end
//----------<LUT>----------
always@(*)begin
case(data_tmp)
4'h0:seg = 8'hc0;
4'h1:seg = 8'hf9;
4'h2:seg = 8'ha4;
4'h3:seg = 8'hb0;
4'h4:seg = 8'h99;
4'h5:seg = 8'h92;
4'h6:seg = 8'h82;
4'h7:seg = 8'hf8;
4'h8:seg = 8'h80;
4'h9:seg = 8'h90;
4'ha:seg = 8'h88;
4'hb:seg = 8'h83;
4'hc:seg = 8'hc6;
4'hd:seg = 8'ha1;
4'he:seg = 8'h86;
4'hf:seg = 8'h8e;
endcase
end
//----------<2选1多路器>----------
assign sel = (En)?(sel_r):8'b1111_1111;
endmodule
m74HC595_Driver.v:
module m74HC595_Driver(
Clk,
Rst_n,
Data,
S_EN,
SH_CP,
ST_CP,
DS
);
parameter DATA_WIDTH = 16;
input Clk;
input Rst_n;
input [DATA_WIDTH-1 : 0] Data; //data to send
input S_EN; //send en
output reg SH_CP; //shift clock
output reg ST_CP; //latch data clock
output reg DS; //shift serial data
parameter CNT_MAX = 4;
reg [15:0] divider_cnt;//分频计数器
wire sck_pluse;
reg [4:0]SHCP_EDGE_CNT;//SH_CP EDGE counter
reg [15:0]r_data;
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
r_data <= 16'd0;
else if(S_EN)
r_data <= Data;
else
r_data <= r_data;
end
//clock divide
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
divider_cnt <= 16'd0;
else if(divider_cnt == CNT_MAX)
divider_cnt <= 16'd0;
else
divider_cnt <= divider_cnt + 1'b1;
end
assign sck_pluse = (divider_cnt == CNT_MAX);
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
SHCP_EDGE_CNT <= 5'd0;
else if(sck_pluse)begin
if(SHCP_EDGE_CNT == 5'd31)
SHCP_EDGE_CNT <= 5'd0;
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'b1;
end
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
SH_CP <= 1'b0;
ST_CP <= 1'b0;
DS <= 1'b0;
end
else begin
case(SHCP_EDGE_CNT)
5'd0: begin SH_CP <= 1'b0; ST_CP <= 1'b1; DS <= r_data[15]; end
5'd1: begin SH_CP <= 1'b1; ST_CP <= 1'b0;end
5'd2: begin SH_CP <= 1'b0; DS <= r_data[14];end
5'd3: begin SH_CP <= 1'b1; end
5'd4: begin SH_CP <= 1'b0; DS <= r_data[13];end
5'd5: begin SH_CP <= 1'b1; end
5'd6: begin SH_CP <= 1'b0; DS <= r_data[12];end
5'd7: begin SH_CP <= 1'b1; end
5'd8: begin SH_CP <= 1'b0; DS <= r_data[11];end
5'd9: begin SH_CP <= 1'b1; end
5'd10:begin SH_CP <= 1'b0; DS <= r_data[10];end
5'd11:begin SH_CP <= 1'b1; end
5'd12:begin SH_CP <= 1'b0; DS <= r_data[9];end
5'd13:begin SH_CP <= 1'b1; end
5'd14:begin SH_CP <= 1'b0; DS <= r_data[8];end
5'd15:begin SH_CP <= 1'b1; end
5'd16:begin SH_CP <= 1'b0; DS <= r_data[7];end
5'd17:begin SH_CP <= 1'b1; end
5'd18:begin SH_CP <= 1'b0; DS <= r_data[6];end
5'd19:begin SH_CP <= 1'b1; end
5'd20:begin SH_CP <= 1'b0; DS <= r_data[5];end
5'd21:begin SH_CP <= 1'b1; end
5'd22:begin SH_CP <= 1'b0; DS <= r_data[4];end
5'd23:begin SH_CP <= 1'b1; end
5'd24:begin SH_CP <= 1'b0; DS <= r_data[3];end
5'd25:begin SH_CP <= 1'b1; end
5'd26:begin SH_CP <= 1'b0; DS <= r_data[2];end
5'd27:begin SH_CP <= 1'b1; end
5'd28:begin SH_CP <= 1'b0; DS <= r_data[1];end
5'd29:begin SH_CP <= 1'b1; end
5'd30:begin SH_CP <= 1'b0; DS <= r_data[0];end
5'd31:begin SH_CP <= 1'b1; end
default:begin SH_CP <= 1'b0;ST_CP <= 1'b0;DS <= 1'b0; end
endcase
end
end
endmodule
文章来源:https://www.toymoban.com/news/detail-707040.html
🧸结尾文章来源地址https://www.toymoban.com/news/detail-707040.html
- ❤️ 感谢您的支持和鼓励! 😊🙏
- 📜您可能感兴趣的内容:
- 【FPGA零基础学习之旅#11】数码管动态扫描
- 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)
- 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
- 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制
到了这里,关于【FPGA零基础学习之旅#13】串口发送模块设计与验证的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!