这版本是固定波特率,无法修改串口波特率,无法恢复出厂设置(出厂设置会更改波特率到9600,除非固定波特率一开始设置为9600,其他写命令都可以成功写入)。
1. JY901P交互协议
这个是JY901P惯导模块串口的交互协议:JY901P串口通讯协议
1.1 读格式
JY901P传来的数据都是如下格式,0x55开头的。

默认传出以下四条数据:加速度输出、角速度输出、角度输出、磁场输出。分别是0x55 0x51......,0x55 0x52......,0x55 0x53......,0x55 0x54......




1.2 写格式
写格式较为简单,协议头0xFF 0xAA 后面8位寄存器地址,再接16位数据位即可。

常用的写命令有:加速度校准,波特率修改,数据输出率修改,磁场校准,恢复出厂设置,解锁,保存等。






2. verilog代码
JY901P相关代码结构如下:

2.1 JY901P_Uart_top模块
例化所有JY901P相关模块,并对外提供接口
cpp
`timescale 1ns / 1ps
module JY901P_Uart_top
(
input clk , // 系统时钟50MHz
input rst_n , // 全局复位(低有效)
// 串口物理接口
input uart_rxd , // 串口接收
output uart_txd , // 串口发送
// JY901指令配置接口
input send_cmd_en , // 发送指令使能
input [7:0] cmd_addr , // 指令寄存器地址
input [15:0] cmd_data , // 指令参数
output cmd_send_done , // 指令发送完成
// JY901解析数据输出
output [15:0] acc_x , // X轴加速度
output [15:0] acc_y ,
output [15:0] acc_z ,
output [15:0] gyro_x , // X轴角速度
output [15:0] gyro_y ,
output [15:0] gyro_z ,
output [15:0] angle_x , // X轴角度
output [15:0] angle_y ,
output [15:0] angle_z ,
output [15:0] mag_x , // X轴磁场
output [15:0] mag_y ,
output [15:0] mag_z ,
output [63:0] JY901P_data_out ,
output frame_valid // 完整帧接收完成标志
);
// ===================== 内部连线 =====================
wire [7:0] uart_rx_data; // UART接收数据
wire uart_rx_flag; // UART接收标志
wire [7:0] uart_tx_data; // UART发送数据
wire uart_tx_flag; // UART发送标志
wire uart_tx_done; // UART单字节发送完成
// ===================== 例化UART接收模块 =====================
uart_recceive
#(
.UART_BPS(115200),
.CLK_FREQ(50000000)
)
uart_RX
(
.clk (clk) ,
.sys_rst_n (rst_n) ,
.rx (uart_rxd) ,
.po_data (uart_rx_data),
.po_flag (uart_rx_flag)
);
// ===================== 例化JY901顶层模块 =====================
JY901P_top
#(
.CLK_FREQ(50000000)
)
JY901P_top_inst
(
.clk (clk) ,
.rst_n (rst_n) ,
// 写命令
.uart_tx_data (uart_tx_data) ,
.uart_tx_flag (uart_tx_flag) ,
.uart_tx_done (uart_tx_done) ,
.send_cmd_en (send_cmd_en) ,
.cmd_addr (cmd_addr) ,
.cmd_data (cmd_data) ,
.cmd_send_done (cmd_send_done),
// 读数据
.uart_rx_data (uart_rx_data) ,
.uart_rx_flag (uart_rx_flag) ,
.acc_x (acc_x) ,
.acc_y (acc_y) ,
.acc_z (acc_z) ,
.gyro_x (gyro_x) ,
.gyro_y (gyro_y) ,
.gyro_z (gyro_z) ,
.angle_x (angle_x) ,
.angle_y (angle_y) ,
.angle_z (angle_z) ,
.mag_x (mag_x) ,
.mag_y (mag_y) ,
.mag_z (mag_z) ,
.JY901P_data_out(JY901P_data_out),
.frame_valid (frame_valid)
);
// ===================== 例化UART发送模块 =====================
uart_send
#(
.UART_BPS(115200),
.CLK_FREQ(50000000)
)
uart_TX
(
.clk (clk) ,
.sys_rst_n (rst_n) ,
.pi_data (uart_tx_data),
.pi_flag (uart_tx_flag),
.tx (uart_txd) ,
.tx_done (uart_tx_done)
);
endmodule
2.2 JY901P_top模块
例化JY901P_reader和JY901P_writer
对外输出解析后的惯导数据
cpp
module JY901P_top
#(
parameter CLK_FREQ = 50_000_000 // 系统时钟50MHz
)
(
input clk , // 系统时钟50MHz
input rst_n , // 全局复位(低有效)
// UART接口
input [7:0] uart_rx_data , // UART接收数据
input uart_rx_flag , // UART接收标志
output reg [7:0] uart_tx_data , // UART发送数据
output reg uart_tx_flag , // UART发送标志
input uart_tx_done , // UART单字节发送完成
// 指令配置接口(向JY901P发命令)
input send_cmd_en , // 发送指令使能
input [7:0] cmd_addr , // 指令寄存器地址
input [15:0] cmd_data , // 指令参数
output reg cmd_send_done , // 指令发送完成
// 解析后的数据输出(给其他模块)
output reg [15:0] acc_x , // X轴加速度
output reg [15:0] acc_y ,
output reg [15:0] acc_z ,
output reg [15:0] gyro_x , // X轴角速度
output reg [15:0] gyro_y ,
output reg [15:0] gyro_z ,
output reg [15:0] angle_x , // X轴角度
output reg [15:0] angle_y ,
output reg [15:0] angle_z ,
output reg [15:0] mag_x , // X轴磁场
output reg [15:0] mag_y ,
output reg [15:0] mag_z ,
output [63:0] JY901P_data_out ,
output reg frame_valid // 同一组完整帧接收完成标志
);
// ===================== 内部连线 =====================
// 读模块输出连线
wire [15:0] acc_x_r, acc_y_r, acc_z_r;
wire [15:0] gyro_x_r, gyro_y_r, gyro_z_r;
wire [15:0] angle_x_r, angle_y_r, angle_z_r;
wire [15:0] mag_x_r, mag_y_r, mag_z_r;
wire frame_valid_r;
// 写模块输出连线
wire [7:0] uart_tx_data_w;
wire uart_tx_flag_w;
wire cmd_send_done_w;
// ===================== 例化JY901读模块 =====================
JY901P_reader
#(
.CLK_FREQ(CLK_FREQ)
)
JY901P_reader_inst
(
.clk (clk) ,
.rst_n (rst_n) ,
.uart_rx_data (uart_rx_data) ,
.uart_rx_flag (uart_rx_flag) ,
.acc_x (acc_x_r) ,
.acc_y (acc_y_r) ,
.acc_z (acc_z_r) ,
.gyro_x (gyro_x_r) ,
.gyro_y (gyro_y_r) ,
.gyro_z (gyro_z_r) ,
.angle_x (angle_x_r) ,
.angle_y (angle_y_r) ,
.angle_z (angle_z_r) ,
.mag_x (mag_x_r) ,
.mag_y (mag_y_r) ,
.mag_z (mag_z_r) ,
.JY901P_data_out(JY901P_data_out),
.frame_valid (frame_valid_r)
);
// ===================== 例化JY901写模块 =====================
JY901P_writer
#(
.CLK_FREQ(CLK_FREQ)
)
JY901P_writer_inst
(
.clk (clk) ,
.rst_n (rst_n) ,
.send_cmd_en (send_cmd_en) ,
.cmd_addr (cmd_addr) ,
.cmd_data (cmd_data) ,
.uart_tx_data (uart_tx_data_w),
.uart_tx_flag (uart_tx_flag_w),
.uart_tx_done (uart_tx_done) ,
.cmd_send_done (cmd_send_done_w)
);
// ===================== 输出赋值 =====================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
// 数据输出复位
acc_x <= 16'd0; acc_y <= 16'd0; acc_z <= 16'd0;
gyro_x <= 16'd0; gyro_y <= 16'd0; gyro_z <= 16'd0;
angle_x <= 16'd0; angle_y <= 16'd0; angle_z <= 16'd0;
mag_x <= 16'd0; mag_y <= 16'd0; mag_z <= 16'd0;
frame_valid <= 1'b0;
// UART发送接口复位
uart_tx_data <= 8'h00;
uart_tx_flag <= 1'b0;
cmd_send_done <= 1'b0;
end else begin
// 转发读模块数据
acc_x <= acc_x_r; acc_y <= acc_y_r; acc_z <= acc_z_r;
gyro_x <= gyro_x_r; gyro_y <= gyro_y_r; gyro_z <= gyro_z_r;
angle_x <= angle_x_r; angle_y <= angle_y_r; angle_z <= angle_z_r;
mag_x <= mag_x_r; mag_y <= mag_y_r; mag_z <= mag_z_r;
frame_valid <= frame_valid_r;
// 转发写模块UART数据
uart_tx_data <= uart_tx_data_w;
uart_tx_flag <= uart_tx_flag_w;
cmd_send_done <= cmd_send_done_w;
end
end
endmodule
2.3 JY901P_reader模块
检测帧头0x55
逐字节接收并计算和校验
区分0x51/0x52/0x53/0x54四种数据帧
严格按照数据顺序接收,只有完整一组才输出有效信号
解析出16位有符号数:加速度,角速度,角度,磁场
输出有效帧脉冲frame_valid
- 单帧接收状态机:IDLE→收类型→收数据→校验
- 帧顺序状态机:等待 ACC→等待 GYRO→等待 ANGLE→等待 MAG→完成只有顺序完全正确才输出有效数据,从根源上避免乱帧、丢帧问题。
cpp
module JY901P_reader
#(
parameter CLK_FREQ = 50_000_000 // 系统时钟50MHz
)
(
input clk , // 系统时钟50MHz
input rst_n , // 全局复位(低有效)
input [7:0] uart_rx_data , // UART接收的1字节数据
input uart_rx_flag , // UART接收数据有效标志(单周期高电平)
// 解析后的数据输出(同一组51→52→53→54帧)
output reg [15:0] acc_x , // 0x51帧:X轴加速度 (0.001g)
output reg [15:0] acc_y ,
output reg [15:0] acc_z ,
output reg [15:0] gyro_x , // 0x52帧:X轴角速度 (0.01°/s)
output reg [15:0] gyro_y ,
output reg [15:0] gyro_z ,
output reg [15:0] angle_x , // 0x53帧:X轴角度 (0.01°)
output reg [15:0] angle_y ,
output reg [15:0] angle_z ,
output reg [15:0] mag_x , // 0x54帧:X轴磁场 (0.01uT)
output reg [15:0] mag_y ,
output reg [15:0] mag_z ,
output reg [63:0] JY901P_data_out , //加速度、角速度、角度、磁场拼接输出
output reg frame_valid // 同一组完整帧接收完成标志
);
// ===================== JY901P 协议定义 =====================
localparam FRAME_HEAD = 8'h55; // 唯一固定帧头
localparam FRAME_LEN = 11; // 单帧总长度11字节
// 数据类型码
localparam DATA_TYPE_ACC = 8'h51; // 加速度
localparam DATA_TYPE_GYRO = 8'h52; // 角速度
localparam DATA_TYPE_ANGLE= 8'h53; // 角度
localparam DATA_TYPE_MAG = 8'h54; // 磁场
// ===================== 状态机定义 =====================
// 单帧接收状态
localparam IDLE = 4'd0; // 空闲态(等待0x55帧头)
localparam RECV_TYPE = 4'd1; // 已收0x55,等待数据类型码
localparam RECV_DATA = 4'd2; // 已收数据类型码,接收后续8字节数据
localparam RECV_CRC = 4'd3; // 接收校验和,验证帧完整性
// 帧顺序状态(确保51→52→53→54)
localparam WAIT_ACC = 4'd4; // 等待0x51帧
localparam WAIT_GYRO = 4'd5; // 等待0x52帧
localparam WAIT_ANGLE = 4'd6; // 等待0x53帧
localparam WAIT_MAG = 4'd7; // 等待0x54帧
localparam FRAME_DONE = 4'd8; // 同一组4帧接收完成
// ===================== 内部寄存器定义 =====================
reg [3:0] frame_byte_cnt; // 单帧字节计数器(0~10)
reg [7:0] frame_buf[0:10]; // 11字节帧缓存
reg [7:0] current_data_type; // 当前接收的数据类型码
reg [7:0] check_sum; // 校验和计算
reg [3:0] frame_state; // 单帧接收状态
reg [3:0] seq_state; // 帧顺序状态
// 同一组数据缓存
reg [15:0] acc_x_buf, acc_y_buf, acc_z_buf;
reg [15:0] gyro_x_buf, gyro_y_buf, gyro_z_buf;
reg [15:0] angle_x_buf, angle_y_buf, angle_z_buf;
reg [15:0] mag_x_buf, mag_y_buf, mag_z_buf;
// 捕获uart_rx_flag的上升沿
reg uart_rx_flag_d1;
reg uart_rx_flag_d2;
wire uart_rx_flag_pulse; // flag上升沿脉冲(单周期)
// ===================== 1. 捕获uart_rx_flag上升沿 =====================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
uart_rx_flag_d1 <= 1'b0;
uart_rx_flag_d2 <= 1'b0;
end else begin
uart_rx_flag_d1 <= uart_rx_flag;
uart_rx_flag_d2 <= uart_rx_flag_d1;
end
end
// 提取上升沿:仅在flag从0→1时,产生1周期脉冲
assign uart_rx_flag_pulse = uart_rx_flag_d1 & ~uart_rx_flag_d2;
// ===================== 2. 单帧接收状态机=====================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
// 所有寄存器复位
frame_byte_cnt <= 4'd0;
frame_state <= IDLE;
current_data_type <= 8'h00;
check_sum <= 8'h00;
seq_state <= WAIT_ACC;
// 数据缓存复位
acc_x_buf <= 16'd0; acc_y_buf <= 16'd0; acc_z_buf <= 16'd0;
gyro_x_buf <= 16'd0; gyro_y_buf <= 16'd0; gyro_z_buf <= 16'd0;
angle_x_buf <= 16'd0; angle_y_buf <= 16'd0; angle_z_buf <= 16'd0;
mag_x_buf <= 16'd0; mag_y_buf <= 16'd0; mag_z_buf <= 16'd0;
// 输出寄存器复位
acc_x <= 16'd0; acc_y <= 16'd0; acc_z <= 16'd0;
gyro_x <= 16'd0; gyro_y <= 16'd0; gyro_z <= 16'd0;
angle_x <= 16'd0; angle_y <= 16'd0; angle_z <= 16'd0;
mag_x <= 16'd0; mag_y <= 16'd0; mag_z <= 16'd0;
JY901P_data_out <= 64'd0;
frame_valid <= 1'b0;
end else begin
frame_valid <= 1'b0;
// ===================== 仅用flag上升沿触发,状态机持续运行 =====================
if(uart_rx_flag_pulse) begin // 仅在新字节接收完成时,执行1次字节处理
case(frame_state)
IDLE: begin
// 检测帧头0x55:仅在IDLE态,且当前字节是0x55时,进入下一状态
if(uart_rx_data == FRAME_HEAD) begin
frame_buf[0] <= uart_rx_data;
check_sum <= uart_rx_data;
frame_byte_cnt <= 4'd1;
frame_state <= RECV_TYPE; // 状态机更新,后续持续运行
end else begin
frame_state <= IDLE;
end
end
RECV_TYPE: begin
// 接收第2字节:数据类型码
frame_buf[1] <= uart_rx_data;
current_data_type <= uart_rx_data;
check_sum <= check_sum + uart_rx_data;
frame_byte_cnt <= 4'd2;
frame_state <= RECV_DATA; // 进入数据接收态
end
RECV_DATA: begin
// 接收第3~10字节(共8字节数据)
frame_buf[frame_byte_cnt] <= uart_rx_data;
check_sum <= check_sum + uart_rx_data;
frame_byte_cnt <= frame_byte_cnt + 1'b1;
// 8字节数据接收完成,进入校验态
if(frame_byte_cnt == 4'd9) begin
frame_state <= RECV_CRC;
end
end
RECV_CRC: begin
// 接收第11字节:校验和
frame_buf[10] <= uart_rx_data;
frame_byte_cnt <= 4'd0; // 重置字节计数
// 校验通过:缓存数据+更新帧顺序
if(uart_rx_data == check_sum) begin
frame_state <= IDLE; // 单帧接收完成,回到IDLE
case(current_data_type)
DATA_TYPE_ACC: begin
acc_x_buf <= {frame_buf[3], frame_buf[2]}; // Acc_X = H(3) + L(2)
acc_y_buf <= {frame_buf[5], frame_buf[4]}; // Acc_Y = H(5) + L(4)
acc_z_buf <= {frame_buf[7], frame_buf[6]}; // Acc_Z = H(7) + L(6)
end
DATA_TYPE_GYRO: begin
gyro_x_buf <= {frame_buf[3], frame_buf[2]}; // Gyro_X = H(3) + L(2)
gyro_y_buf <= {frame_buf[5], frame_buf[4]}; // Gyro_Y = H(5) + L(4)
gyro_z_buf <= {frame_buf[7], frame_buf[6]}; // Gyro_Z = H(7) + L(6)
end
DATA_TYPE_ANGLE: begin
angle_x_buf <= {frame_buf[3], frame_buf[2]};// Angle_X = H(3) + L(2)
angle_y_buf <= {frame_buf[5], frame_buf[4]};// Angle_Y = H(5) + L(4)
angle_z_buf <= {frame_buf[7], frame_buf[6]};// Angle_Z = H(7) + L(6)
end
DATA_TYPE_MAG: begin
mag_x_buf <= {frame_buf[3], frame_buf[2]}; // Mag_X = H(3) + L(2)
mag_y_buf <= {frame_buf[5], frame_buf[4]}; // Mag_Y = H(5) + L(4)
mag_z_buf <= {frame_buf[7], frame_buf[6]}; // Mag_Z = H(7) + L(6)
end
endcase
// 帧顺序状态更新(确保51→52→53→54)
case(seq_state)
WAIT_ACC: begin
if(current_data_type == DATA_TYPE_ACC) seq_state <= WAIT_GYRO;
else seq_state <= WAIT_ACC;
end
WAIT_GYRO: begin
if(current_data_type == DATA_TYPE_GYRO) seq_state <= WAIT_ANGLE;
else seq_state <= WAIT_ACC;
end
WAIT_ANGLE: begin
if(current_data_type == DATA_TYPE_ANGLE) seq_state <= WAIT_MAG;
else seq_state <= WAIT_ACC;
end
WAIT_MAG: begin
if(current_data_type == DATA_TYPE_MAG) seq_state <= FRAME_DONE;
else seq_state <= WAIT_ACC;
end
default: seq_state <= WAIT_ACC;
endcase
end else begin
// 校验失败:直接回到IDLE,丢弃当前帧
frame_state <= IDLE;
seq_state <= WAIT_ACC; // 重置帧顺序
end
end
default: frame_state <= IDLE;
endcase
end
// ===================== 3. 完整组帧输出=====================
if(seq_state == FRAME_DONE) begin
// 同一组4帧接收完成,更新输出寄存器
acc_x <= acc_x_buf; acc_y <= acc_y_buf; acc_z <= acc_z_buf;
gyro_x <= gyro_x_buf; gyro_y <= gyro_y_buf; gyro_z <= gyro_z_buf;
angle_x <= angle_x_buf; angle_y <= angle_y_buf; angle_z <= angle_z_buf;
mag_x <= mag_x_buf; mag_y <= mag_y_buf; mag_z <= mag_z_buf;
// 直接使用缓存寄存器拼接
JY901P_data_out <= {8'h55, 8'h51, angle_x_buf, angle_y_buf, angle_z_buf};
frame_valid <= 1'b1; // 有效标志拉高1周期
seq_state <= WAIT_ACC; // 重置帧顺序,等待下一组
end
end
end
endmodule
2.4 JY901P_writer模块
接收外部指令使能,地址,数据
拼接5字节指令帧:FF AA ADDR DATAH DATAL
状态机控制逐字节发送
等待UART发送完成应答tx_done
全部发送完成输出cmd_send_done
- IDLE:等待发送触发
- SEND_BYTE:发送当前字节
- WAIT_DONE:等待 UART 单字节发送完成发送流程完全自动化,外部只需要给一个单周期脉冲即可启动配置。
cpp
module JY901P_writer
#(
parameter CLK_FREQ = 50_000_000 // 系统时钟50MHz
)
(
input clk , // 系统时钟50MHz
input rst_n , // 全局复位(低有效)
// 指令配置接口
input send_cmd_en , // 发送指令使能(高有效,单次脉冲)
input [7:0] cmd_addr , // 指令寄存器地址(如0x03=输出频率)
input [15:0] cmd_data , // 指令参数(低字节在前)
// UART发送接口
output reg [7:0] uart_tx_data , // 待发送给UART的1字节数据
output reg uart_tx_flag , // 发送数据有效标志(单次时钟高)
input uart_tx_done , // UART单字节发送完成(来自uart_send)
output reg cmd_send_done // 指令发送完成标志(单次时钟高)
);
// ===================== JY901P 指令协议定义 =====================
localparam CMD_HEAD1 = 8'hFF; // 指令帧头1
localparam CMD_HEAD2 = 8'hAA; // 指令帧头2
localparam CMD_FRAME_LEN = 5; // 指令帧长度:FF AA ADDR DATAH DATAL
// ===================== 内部寄存器定义 =====================
reg [2:0] cmd_byte_cnt; // 指令字节计数器(0~4)
reg cmd_work_en; // 指令发送使能
reg send_flag_d1; // 打拍捕获send_cmd_en上升沿
// 状态机定义
localparam IDLE = 2'b00; // 空闲状态
localparam SEND_BYTE = 2'b01; // 发送当前字节
localparam WAIT_DONE = 2'b10; // 等待UART发送完成
reg [1:0] current_state;
reg [1:0] next_state;
// ===================== 状态机时序逻辑 =====================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// ===================== 状态机组合逻辑 =====================
always @(*) begin
next_state = current_state;
case(current_state)
IDLE: begin
// 检测到发送使能,进入发送字节状态
if(send_cmd_en && !cmd_work_en) begin
next_state = SEND_BYTE;
end else begin
next_state = IDLE;
end
end
SEND_BYTE: begin
// 发送字节触发后,等待UART完成
next_state = WAIT_DONE;
end
WAIT_DONE: begin
if(uart_tx_done) begin // 单字节发送完成
if(cmd_byte_cnt == CMD_FRAME_LEN - 1) begin
next_state = IDLE; // 所有字节发送完成,回到空闲
end else begin
next_state = SEND_BYTE; // 继续发送下一字节
end
end else begin
next_state = WAIT_DONE; // 等待当前字节发送完成
end
end
default: next_state = IDLE;
endcase
end
// ===================== 状态机输出逻辑 =====================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cmd_work_en <= 1'b0;
cmd_send_done <= 1'b0;
cmd_byte_cnt <= 3'd0;
uart_tx_data <= 8'h00;
uart_tx_flag <= 1'b0;
send_flag_d1 <= 1'b0;
end else begin
cmd_send_done <= 1'b0; // 默认清除完成标志
uart_tx_flag <= 1'b0; // 默认低
send_flag_d1 <= send_cmd_en;
case(current_state)
IDLE: begin
cmd_byte_cnt <= 3'd0;
cmd_work_en <= 1'b0;
// 捕获send_cmd_en上升沿,启动发送
if(send_cmd_en && !send_flag_d1) begin
cmd_work_en <= 1'b1;
end
end
SEND_BYTE: begin
if(cmd_work_en) begin
// 发送当前字节数据并拉高标志(仅1个时钟)
case(cmd_byte_cnt)
3'd0: uart_tx_data <= CMD_HEAD1;
3'd1: uart_tx_data <= CMD_HEAD2;
3'd2: uart_tx_data <= cmd_addr;
3'd3: uart_tx_data <= cmd_data[15:8];
3'd4: uart_tx_data <= cmd_data[7:0];
default: uart_tx_data <= 8'h00;
endcase
uart_tx_flag <= 1'b1; // 拉高发送标志,通知UART发送该字节
end
end
WAIT_DONE: begin
if(uart_tx_done) begin // 单字节发送完成
if(cmd_byte_cnt == CMD_FRAME_LEN - 1) begin
cmd_work_en <= 1'b0; // 发送完毕退出发送状态
cmd_send_done <= 1'b1; // 发送完成标志拉高一个时钟周期
end else begin
cmd_byte_cnt <= cmd_byte_cnt + 1'b1; // 准备下一字节
end
end
end
default: begin
uart_tx_data <= 8'h00;
uart_tx_flag <= 1'b0;
end
endcase
end
end
endmodule
2.5 uart_send模块
并行数据转串口发送
发送完成后输出tx_done应答信号
严格遵循UART协议:起始位+8位数据+停止位
cpp
module uart_send
#(
parameter UART_BPS = 115200, // 串口波特率
parameter CLK_FREQ = 50000000 // 系统时钟频率
)
(
input clk, // 系统时钟
input sys_rst_n, // 复位信号(低有效)
input [7:0] pi_data, // 待发送的1字节数据
input pi_flag, // 发送触发信号(1个时钟周期高电平)
output reg tx, // 串口发送数据
output reg tx_done // 1字节发送完成信号(1个时钟周期高电平)
);
// 本地参数定义
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS; // 波特率计数最大值
// 寄存器定义
reg [15:0] baud_cnt; // 波特率计数器
reg [3:0] bit_cnt; // 字节内位数计数器(0~9:起始位+8数据位+停止位)
reg [7:0] data_buf; // 待发送数据缓存
reg tx_en; // 发送使能信号
reg pi_flag_d1; // pi_flag打拍,避免漏采
// 提取pi_flag上升沿(确保1周期的pi_flag被捕获)
wire pi_flag_posedge = pi_flag & ~pi_flag_d1;
// 步骤1:pi_flag打拍 + 捕获上升沿锁存数据
always @(posedge clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
data_buf <= 8'd0;
tx_en <= 1'b0;
pi_flag_d1 <= 1'b0;
end else begin
pi_flag_d1 <= pi_flag; // 打拍
if (pi_flag_posedge) begin // 捕获上升沿,避免漏触发
data_buf <= pi_data;
tx_en <= 1'b1;
$display("[%0t] uart_send捕获到触发信号,锁存数据:0x%02X", $time, pi_data);
end else if (bit_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - 1) begin // 修复:停止位发送完成后再关闭tx_en
tx_en <= 1'b0;
$display("[%0t] uart_send关闭发送使能", $time);
end
end
end
// 步骤2:波特率计数器(计时1个位周期)
always @(posedge clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
baud_cnt <= 16'd0;
end else if (tx_en) begin // 发送使能时计数
if (baud_cnt == BAUD_CNT_MAX - 1) begin
baud_cnt <= 16'd0;
end else begin
baud_cnt <= baud_cnt + 1'b1;
end
end else begin
baud_cnt <= 16'd0;
end
end
// 步骤3:字节内位数计数器(计数1字节的10个位)
always @(posedge clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
bit_cnt <= 4'd0;
end else if (tx_en) begin
if (baud_cnt == BAUD_CNT_MAX - 1) begin // 1个位周期完成
if (bit_cnt == 4'd9) begin
bit_cnt <= 4'd0;
end else begin
bit_cnt <= bit_cnt + 1'b1;
end
end
end else begin
bit_cnt <= 4'd0;
end
end
// 步骤4:串口数据发送(按位输出)
always @(posedge clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx <= 1'b1; // 串口空闲时为高电平
end else if (tx_en) begin
case (bit_cnt)
4'd0: tx <= 1'b0; // 起始位(低电平)
4'd1: tx <= data_buf[0]; // 数据位bit0
4'd2: tx <= data_buf[1]; // 数据位bit1
4'd3: tx <= data_buf[2]; // 数据位bit2
4'd4: tx <= data_buf[3]; // 数据位bit3
4'd5: tx <= data_buf[4]; // 数据位bit4
4'd6: tx <= data_buf[5]; // 数据位bit5
4'd7: tx <= data_buf[6]; // 数据位bit6
4'd8: tx <= data_buf[7]; // 数据位bit7
4'd9: tx <= 1'b1; // 停止位(高电平)
default: tx <= 1'b1;
endcase
end else begin
tx <= 1'b1;
end
end
// 步骤5:发送完成信号(tx_done)生成(1个时钟周期高电平)
always @(posedge clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx_done <= 1'b0;
end else if (tx_en && (bit_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX - 1)) begin
tx_done <= 1'b1; // 1字节发送完成,拉高1周期
$display("[%0t] uart_send:1字节发送完成,tx_done拉高", $time);
end else begin
tx_done <= 1'b0;
end
end
endmodule
2.6 uart_receive模块
起始位下降沿检测
比特位中间时刻采样,保证数据稳定
接收完成后输出单周期脉冲标志
po_flag
cpp
module uart_recceive
#(
parameter UART_BPS = 'd115200, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire clk , //系统时钟100MHz
input wire sys_rst_n , //全局复位
input wire rx , //串口接收数据
output reg [7:0] po_data , //串转并后的8bit数据
output reg po_flag //串转并后的数据有效标志信号
);
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
//reg define
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg start_nedge ;
reg work_en ;
reg [12:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
reg [7:0] rx_data ;
reg rx_flag ; //接收完成标志位
//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;
//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;
//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;
//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
start_nedge <= 1'b0;
else if((~rx_reg2) && (rx_reg3))
start_nedge <= 1'b1;
else
start_nedge <= 1'b0;
//work_en:接收数据工作使能信号
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(start_nedge == 1'b1)
work_en <= 1'b1;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
work_en <= 1'b0;
//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
baud_cnt <= 13'b0;
else if(work_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX/2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
bit_cnt <= 4'b0;
else if(bit_flag ==1'b1)
bit_cnt <= bit_cnt + 1'b1;
//rx_data:输入数据进行移位
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_data <= 8'b0;
else if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
rx_data <= {rx_reg3, rx_data[7:1]};
//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_flag <= 1'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
rx_flag <= 1'b1; //移位完成拉高rx_flag电平
else
rx_flag <= 1'b0;
//po_data:输出完整的8位有效数据 比rx_data延后一个时钟周期
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_data <= 8'b0;
else if(rx_flag == 1'b1)
po_data <= rx_data;
//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
endmodule