[FPGA]Spartan6 Uart固定波特率读写JY901P惯导模块

这版本是固定波特率,无法修改串口波特率,无法恢复出厂设置(出厂设置会更改波特率到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
相关推荐
s090713613 小时前
ZYNQ无SD卡纯NAND Flash启动Linux全攻略
linux·fpga开发·zynq·nand flash启动
jjinl15 小时前
AG32VF407RGT6 开发流程记录
fpga开发
FPGA小迷弟15 小时前
FPGA面试题汇总整理(一)
学习·fpga开发·verilog·fpga
Z22ZHaoGGGG15 小时前
verilog 资源占用少的滤波方法
fpga开发
S&Z346315 小时前
[SZ901]FPGA 下载器硬件介绍
fpga开发
GateWorld17 小时前
FPGA内部模块详解之四 算力引擎——数字信号处理单元(DSP Slice)深度解析
fpga开发·dsp
weiyvyy18 小时前
嵌入式硬件接口开发的核心原则
驱动开发·单片机·嵌入式硬件·fpga开发·硬件架构·硬件工程
Kong_199418 小时前
芯片开发学习笔记·二十一——primetime静态时序分析
fpga开发·芯片开发
S&Z346319 小时前
[SZ901] 多路FPGA 网络下载器总览
网络·fpga开发