fpga 串口调试代码实现
发送一串字符, rx收到波形图如下

由上图可知, 串口接收rx线一直处于拉高状态, 起始一直处于高电平
搞懂波特率
搞懂串口,首先他不依靠时钟传输数据, 通过双方约束好的波特率, 计算发送一个 bit 的数据所花费的时间
通过下图所示可以查看出串口发送一个 bit 所需要 434 个时钟周期, 已知时钟为50MHZ
t(发送1bit) = (50MHZ/波特率)*20ns;
也可以这么理解波特率, 就是 一秒发送多少个bit, 115200 意思就是一秒可以发送 115200 bit 的数据

搞懂数据位,数据格式
发送AB, 一个字节
下图是以波特率115200 停止位1,数据位8,无奇偶校验下 发送的十六进制 AB 1010, 1011
发送工具使用的是 XCOM

收到的波形图如下

发送的AB: 1010.1011
收到的AB: 1101.0101
正好和发送的数据相反, 从发送端发送数据是顺序的, 用的是一根线,在接收端收到的就是反序的

发送ABCD, 两个字节
发现S计数器总共发送了两次, 数据位是八位, 发送两个字节需要发送两次

发送两次的波形图如下

发送十次波形图如下

FPGA 实现串口数据的接收
uart_recv
`timescale 1ns / 1ps
module Uart_recv(
input sys_clk,
input uart_rx,
output reg [7:0] rdata //接收到的串口数据
);
// 波特率位 115200, 计算出一个bit需要多久,
// t(1bit) = (1*10^9)/115200 = 8680.55 ns
// c = 8680.55/20 = 434 次
parameter TIME_1BIT = 434; //根据波特率计算出来的
parameter UART_DATA = 8; //数据位
reg [8:0] rx_cnt;
reg [3:0] rx_dcnt; //接收数据位的大小, 数据位(8)
reg is_start; //是否开始采数据 00,否, 01, 开始采集数据
//监测 rx下降沿
always @(posedge sys_clk) begin
//如果还没开始采集数据,则监测 uart_tx 的下降沿
if(!is_start) begin
//如果监测到下降沿
if(!uart_rx) begin
is_start <= 1'b1; //监测到下降沿了, 开始采集数据
rx_dcnt <= 1'b0;
rx_cnt <= 1'b0; //计数器置0
end
end else if(rx_cnt == TIME_1BIT) begin //如果开始采集数据的话, 则判断累加数, 如果累加数满足 一个数据位发送的时间周期,则进行下一步
if(rx_dcnt == UART_DATA) begin //处理数据位, 当数据位为8时
is_start <= 1'b0; //start标记第一个字节获取结束
rdata <= 1'b0; //rdata 清零
end else //如果累加的数据位小于8,则将rx边沿变化赋值于 rdata
rdata[rx_dcnt] <= uart_rx;
rx_dcnt <= rx_dcnt + 1'b1; //累加
rx_cnt <= 1'b0; //计数器清零
end else begin //如果不满足一个周期,则累加数
rx_cnt <= rx_cnt + 1'b1;
end
end
//在线分析
//ila_0 ila_0 (
// .clk(sys_clk), // input wire clk
// .probe0(uart_rx), // input wire [0:0] probe0
// .probe1(rdata), // input wire [0:0] probe1
// .probe2(rx_dcnt)
//);
endmodule
分析截图

uart_send
`timescale 1ns / 1ps
module Uart_send(
input sys_clk,
input tx_val_enable,
input [7:0] tx_data,
output reg uart_tx,
output reg tx_val_disable
);
parameter TIME_1BIT = 434;
parameter UART_DATA = 8;
reg [8:0] rx_cnt = 1'b0; //累加器, 计算一个bit传送所需要的时间
reg [3:0] rx_dcnt = 1'b0; //接收数据位的大小, 数据位(8)
reg is_start = 1'b0; //记录是否第一次发送
initial begin
tx_val_disable <= 1'b0;
uart_tx <= 1'b1;
end
always @(posedge sys_clk) begin
if(tx_val_enable) begin //如果确认要发送的话, 开始发送 uart_tx
if(!is_start) begin //如果是第一次发送数据的话
is_start <= 1'b1; //标记已经开始发送数据
rx_dcnt <= 1'b0; //初始化数据位计数器为 0
rx_cnt <= 1'b0; //计时器清零
uart_tx <= 1'b0; //先将边沿拉低,告知对方自己要开始发送数据
end else if(rx_cnt == TIME_1BIT) begin //计数器,计时 TIME_1BIT 次进行数据位的发送
if(rx_dcnt == UART_DATA) begin //当数据位计时器等于8bit的时候
is_start <= 1'b0; //是否第一次发送失能
end else begin
uart_tx <= tx_data[rx_dcnt]; //从下标0开始发送,发送 0~7
end
if(rx_dcnt == UART_DATA - 1'b1) begin
tx_val_disable <= 1'b1; //输出失效状之后,还可以在发送 1bit
end
rx_dcnt <= rx_dcnt + 1'b1; //数据位累加
rx_cnt <= 1'b0; //计数器清零
end else begin
rx_cnt <= rx_cnt + 1'b1;
end
end else begin
tx_val_disable <= 1'b0;
end
end
endmodule
场景练习 (楼梯灯)
- 按键, 灯亮一段时间(1000ms)
- 感光(串口模拟发送AB), 灯常亮
- 2的优先级高于1
- 灯灭或灯亮,状态切换的时候需要在串口输出状态值 F0/F1
模块分析
- 按键消抖
- 时钟延迟
- 边沿采样 (灯状态变化-rx)
- 锁存器 (按键值状态以及AB值的保留, 串口发送一次AB,即可让灯一直亮,如果发送非AB,则灯灭)
- 串口发送
- 串口接收
代码
UART_test (top)
点击查看代码
```verilog
`timescale 1ns / 1ps
module UART_test(
input sys_clk,
input sys_rst,
input key,
input uart_rx, //串口接收线
output uart_tx, //串口发送线
output reg led
);
wire rx_val_enable; //接收有效数据使能
reg tx_val_enable; //发送数据使能
wire tx_val_disable; //发送使能失效
wire key_val_output;
reg key_val_input;
wire [7:0] rx_data;
reg [7:0] tx_data;
reg sys_clk_flag = 1'b0; //记录时钟变化
reg pre_led; //记录上一次灯的状态
initial begin
tx_val_enable = 1'b0;
led = 1'b0;
pre_led = 1'b0;
tx_data = 1'b0;
key_val_input = 1'b0;
end
always @(posedge sys_clk) begin
key_val_input = key_val_output;
end
// 当按键按下的时候, 灯亮, 将状态发送至串口
// 当接收到指定格式命令字, 让灯亮, 将灯的状态发送至串口
key_debounce u_key_debounce(
.sys_clk (sys_clk),
.sys_rst (sys_rst),
.key (key),
.key_val (key_val_output)
);
wire led_flag; //使能,灯亮, 否则 灯灭
// led 延迟亮一段时间, 然后将状态发送至串口
beep_control u_beep_control(
.sys_clk (sys_clk),
.sys_rst (sys_rst),
.key_val (key_val_input),
.beep (led_flag)
);
// 接收 命令字, 点亮灯, 然后将状态从 uart_tx 发送出去
always @(posedge sys_clk) begin
sys_clk_flag = ~sys_clk_flag;
if ((rx_val_enable && rx_data == 8'hAB) || led_flag) begin //监测rx使能的话, 则值有效
led <= 1'b1; //灯亮
end else begin
led <= 1'b0;
end
pre_led <= led;
end
ila_0 ila_0 (
.clk(sys_clk), // input wire clk
.probe0(led), // input wire [0:0] probe0
.probe1(pre_led), // input wire [0:0] probe1
.probe2(tx_val_enable), // input wire [0:0] probe2
.probe3(tx_val_disable) // input wire [0:0] probe3
);
always @(posedge sys_clk) begin
if(led != pre_led) begin
tx_val_enable <= 1'b1;
tx_data <= 8'hF0 + led; //如果发送使能, 则发送数据
end else if(tx_val_disable == 1'b1) begin
tx_val_enable <= 1'b0;
end
end
parameter TIME_1BIT = 434;
parameter UART_DATA = 8;
Uart_recv #(
.TIME_1BIT (TIME_1BIT),
.UART_DATA (UART_DATA)
) u_uart_recv(
.sys_clk (sys_clk),
.uart_rx (uart_rx),
.rx_val_enable (rx_val_enable),
.rx_data (rx_data)
);
Uart_send #(
.TIME_1BIT (TIME_1BIT),
.UART_DATA (UART_DATA)
) u_uart_send(
.sys_clk (sys_clk),
.tx_val_enable (tx_val_enable),
.tx_val_disable (tx_val_disable),
.tx_data (tx_data),
.uart_tx (uart_tx)
);
endmodule
key_debounce 按键消抖
点击查看代码
```verilog
`timescale 1ns / 1ps
module key_debounce(
input sys_clk,
input sys_rst,
input key,
output reg key_val
);
reg [19:0] cnt; //计时器
reg cur_key;
reg pre_key; //记录上一次按键的值
initial begin
key_val = 1'b0;
end
parameter DELAY = 20'd100_0000; //延时20ms, 计时 100_0000 次
parameter KEY_DOWN = 1'b0; //按键按下的状态
always @(posedge sys_clk or negedge sys_rst) begin
if(!sys_rst) begin
key_val <= 0;
pre_key <= 0;
cur_key <= 0;
cnt <= 20'd0;
end else if(key == KEY_DOWN) begin
cur_key <= ~key;
pre_key <= cur_key;
if(pre_key == cur_key)
if(cnt > 0)
cnt <= cnt - 1'b1;
else
key_val <= 1;
// else
// cnt <= 0;
end else begin
cur_key <= 1'b1;
pre_key <= 1'b0;
cnt <= DELAY;
key_val <= 0;
end
end
endmodule
beep_control 灯亮延迟
点击查看代码
```verilog
`timescale 1ns / 1ps
module beep_control(
input sys_clk,
input sys_rst,
input key_val,
output reg beep
);
initial begin
beep = 1'b0;
end
parameter BEEP_DELAY = 32'd5000_0000; // 用来延迟响应 1000ms
// parameter BEEP_DELAY = 32'd10; // 用来延迟响应 10ms
reg [31:0] beep_cnt;
always @(posedge sys_clk or negedge sys_rst) begin
if(!sys_rst) begin
beep = 1'b0;
beep_cnt <= 0;
end else if(key_val) begin
beep_cnt <= BEEP_DELAY;
beep <= 1'b0;
end else begin
if(beep_cnt > 0) begin
beep_cnt <= beep_cnt - 1'b1;
beep <= 1'b1;
end else
beep <= 1'b0;
end
end
endmodule
Uart_recv 串口接收
点击查看代码
```verilog
`timescale 1ns / 1ps
module Uart_recv(
input sys_clk,
input uart_rx,
output reg rx_val_enable,
output reg [7:0] rx_data //接收到的串口数据
);
// 波特率位 115200, 计算出一个bit需要多久,
// t(1bit) = (1*10^9)/115200 = 8680.55 ns
// c = 8680.55/20 = 434 次
parameter TIME_1BIT = 434; //根据波特率计算出来的
parameter UART_DATA = 8; //数据位
reg [7:0] rdata = 1'b0;
reg [8:0] rx_cnt = 1'b0; //累加器, 计算一个bit传送所需要的时间
reg [3:0] rx_dcnt = 1'b0; //接收数据位的大小, 数据位(8)
reg is_start = 1'b0; //是否开始采数据 00,否, 01, 开始采集数据
initial begin
rx_val_enable <= 1'b0;
rx_data <= 1'b0;
end
//监测 rx下降沿
always @(posedge sys_clk) begin
//如果还没开始采集数据,则监测 uart_tx 的下降沿
if(!is_start) begin //让值使能
if(!uart_rx) begin
is_start <= 1'b1; //监测到下降沿了, 开始采集数据
rx_dcnt <= 1'b0; //初始化数据位计数器为 0
rx_cnt <= 1'b0; //计数器置0
end
end else if(rx_cnt == TIME_1BIT) begin //如果开始采集数据的话, 则判断累加数, 如果累加数满足 一个数据位发送的时间周期,则进行下一步
if(rx_dcnt == UART_DATA) begin //处理数据位, 当数据位为8时
is_start <= 1'b0; //start标记第一个字节获取结束
rdata <= 1'b0; //rdata 清零
rx_val_enable <= 1'b1; //让值使能
rx_data <= rdata; //将有效的值输出
end else //如果累加的数据位小于8,则将rx边沿变化赋值于 rdata
rdata[rx_dcnt] <= uart_rx;
rx_dcnt <= rx_dcnt + 1'b1; //累加
rx_cnt <= 1'b0; //计数器清零
end else begin //如果不满足一个周期,则累加数
rx_cnt <= rx_cnt + 1'b1;
end
end
endmodule
Uart_send 串口发送
点击查看代码
```verilog
`timescale 1ns / 1ps
module Uart_send(
input sys_clk,
input tx_val_enable,
input [7:0] tx_data,
output reg uart_tx,
output reg tx_val_disable
);
parameter TIME_1BIT = 434;
parameter UART_DATA = 8;
reg [8:0] rx_cnt = 1'b0; //累加器, 计算一个bit传送所需要的时间
reg [3:0] rx_dcnt = 1'b0; //接收数据位的大小, 数据位(8)
reg is_start = 1'b0; //记录是否第一次发送
initial begin
tx_val_disable <= 1'b0;
uart_tx <= 1'b1;
end
always @(posedge sys_clk) begin
if(tx_val_enable) begin //如果确认要发送的话, 开始发送 uart_tx
if(!is_start) begin //如果是第一次发送数据的话
is_start <= 1'b1; //标记已经开始发送数据
rx_dcnt <= 1'b0; //初始化数据位计数器为 0
rx_cnt <= 1'b0; //计时器清零
uart_tx <= 1'b0; //先将边沿拉低,告知对方自己要开始发送数据
end else if(rx_cnt == TIME_1BIT) begin //计数器,计时 TIME_1BIT 次进行数据位的发送
if(rx_dcnt == UART_DATA) begin //当数据位计时器等于8bit的时候
is_start <= 1'b0; //是否第一次发送失能
end else begin
uart_tx <= tx_data[rx_dcnt]; //从下标0开始发送,发送 0~7
end
if(rx_dcnt == UART_DATA - 1'b1) begin
tx_val_disable <= 1'b1; //输出失效状之后,还可以在发送 1bit
end
rx_dcnt <= rx_dcnt + 1'b1; //数据位累加
rx_cnt <= 1'b0; //计数器清零
end else begin
rx_cnt <= rx_cnt + 1'b1;
end
end else begin
tx_val_disable <= 1'b0;
end
end
endmodule
design之后

本文来自博客园踩坑狭,作者:韩若明瞳,转载请注明原文链接:https://www.cnblogs.com/han-guang-xue/p/16619715.html

浙公网安备 33010602011771号