//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 模块名称: ad7606_top
// 功能: 顶层模块,集成 AD7606 控制, OLED 显示和 UART 通信.
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
module ad7606_top(
input wire clk_50m,
input wire rst_n,
input wire busy,
input wire frstdata,
input wire [15:0] data_bus,
output wire convst,
output wire rd_n,
output wire cs_n,
output wire [2:0] os,
output wire range_ad, // "range" 是Verilog关键字, 已重命名
output wire reset_ad,
output wire [2:0] mux_addr,
output wire mux_en,
output wire oled_sclk,
output wire oled_mosi,
output wire oled_res,
output wire oled_dc,
output wire oled_cs,
output wire uart_tx_pin, // UART 发送引脚
output wire led_sampling_busy
);
wire [3:0] ctrl_reg_ad_top = {1'b0, 3'b000}; // 重命名以避免与 ad7606_ctrl 内部的 ctrl_reg 混淆
wire signed [15:0] ch1_data_raw;
wire signed [15:0] ch2_data_raw;
wire signed [15:0] ch3_data_raw;
wire signed [15:0] ch4_data_raw;
wire signed [15:0] ch5_data_raw;
wire signed [15:0] ch6_data_raw;
wire signed [15:0] ch7_data_raw;
wire signed [15:0] ch8_data_raw;
wire data_vld_from_adc_ctrl;
wire ctrl_mode_busy;
ad7606_ctrl u_ad7606_ctrl (
.clk (clk_50m),
.rst_n (rst_n),
.ctrl_reg (ctrl_reg_ad_top),
.busy (busy),
.frstdata (frstdata),
.data_bus (data_bus),
.os (os),
.range (range_ad),
.convst_a (convst),
.convst_b (),
.reset (reset_ad),
.rd_n (rd_n),
.cs_n (cs_n),
.mux_addr (mux_addr),
.mux_en (mux_en),
.ch1_data (ch1_data_raw), .ch2_data (ch2_data_raw), .ch3_data (ch3_data_raw), .ch4_data (ch4_data_raw),
.ch5_data (ch5_data_raw), .ch6_data (ch6_data_raw), .ch7_data (ch7_data_raw), .ch8_data (ch8_data_raw),
.mode_busy(ctrl_mode_busy),
.data_vld (data_vld_from_adc_ctrl),
.led_sampling_busy_blink (led_sampling_busy)
);
// 修改OLED驱动模块连接,传递ADC数据
oled_096inch_driver u_oled_driver (
.clk (clk_50m),
.rst_n (rst_n),
.ch1_data (ch1_data_raw),
.ch2_data (ch2_data_raw),
.ch3_data (ch3_data_raw),
.ch4_data (ch4_data_raw),
.ch5_data (ch5_data_raw),
.ch6_data (ch6_data_raw),
.ch7_data (ch7_data_raw),
.ch8_data (ch8_data_raw),
.data_valid (data_vld_from_adc_ctrl),
.oled_sclk (oled_sclk),
.oled_mosi (oled_mosi),
.oled_res (oled_res),
.oled_dc (oled_dc),
.oled_cs (oled_cs)
);
// --- UART 逻辑 ---
localparam CLK_FREQ_UART = 50_000_000; // 为UART参数使用不同的名称
localparam BAUD_RATE_UART = 115200;
localparam UART_CLK_DIV_RATIO_UART = 16;
localparam BAUD_CLK_MAX_COUNT_UART = CLK_FREQ_UART / (BAUD_RATE_UART * UART_CLK_DIV_RATIO_UART) - 1 ;
reg [$clog2(BAUD_CLK_MAX_COUNT_UART+1)-1:0] baud_clk_counter_uart;
wire uart_tick;
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) baud_clk_counter_uart <= 0;
else if (baud_clk_counter_uart == BAUD_CLK_MAX_COUNT_UART) baud_clk_counter_uart <= 0;
else baud_clk_counter_uart <= baud_clk_counter_uart + 1;
end
assign uart_tick = (baud_clk_counter_uart == BAUD_CLK_MAX_COUNT_UART);
wire uart_tx_busy_signal;
reg [7:0] uart_data_to_send;
reg uart_start_tx_pulse_signal;
uart_tx_module u_uart_tx (
.clk(clk_50m), .rst_n(rst_n), .clk_enable(uart_tick),
.data_in(uart_data_to_send), .start_tx(uart_start_tx_pulse_signal),
.tx_busy(uart_tx_busy_signal), .tx_serial_out(uart_tx_pin)
);
reg signed [15:0] adc_data_uart_buffer [7:0];
reg data_ready_for_uart_tx;
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) data_ready_for_uart_tx <= 1'b0;
else if (data_vld_from_adc_ctrl) begin
adc_data_uart_buffer[0] <= ch1_data_raw; adc_data_uart_buffer[1] <= ch2_data_raw;
adc_data_uart_buffer[2] <= ch3_data_raw; adc_data_uart_buffer[3] <= ch4_data_raw;
adc_data_uart_buffer[4] <= ch5_data_raw; adc_data_uart_buffer[5] <= ch6_data_raw;
adc_data_uart_buffer[6] <= ch7_data_raw; adc_data_uart_buffer[7] <= ch8_data_raw;
data_ready_for_uart_tx <= 1'b1;
end else if (uart_tx_state == S_UART_TX_DONE_TOP) data_ready_for_uart_tx <= 1'b0;
end
localparam S_UART_TX_IDLE_TOP = 3'd0;
localparam S_UART_TX_START_FRAME_TOP = 3'd1;
localparam S_UART_TX_SEND_HIGH_BYTE_TOP = 3'd2;
localparam S_UART_TX_SEND_LOW_BYTE_TOP = 3'd3;
localparam S_UART_TX_NEXT_CHANNEL_TOP = 3'd4;
localparam S_UART_TX_END_FRAME_TOP = 3'd5;
localparam S_UART_TX_DONE_TOP = 3'd6;
localparam S_UART_TX_WAIT_BUSY_LOW_TOP = 3'd7;
reg [2:0] uart_tx_state_prev_top;
reg [2:0] uart_tx_state;
reg [2:0] current_uart_channel_idx_top;
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) begin
uart_tx_state <= S_UART_TX_IDLE_TOP; current_uart_channel_idx_top <= 3'd0;
uart_data_to_send <= 8'd0; uart_start_tx_pulse_signal <= 1'b0;
uart_tx_state_prev_top <= S_UART_TX_IDLE_TOP;
end else begin
uart_start_tx_pulse_signal <= 1'b0;
case (uart_tx_state)
S_UART_TX_IDLE_TOP: if (data_ready_for_uart_tx && !uart_tx_busy_signal) begin current_uart_channel_idx_top <= 3'd0; uart_tx_state <= S_UART_TX_START_FRAME_TOP; end
S_UART_TX_START_FRAME_TOP: if (!uart_tx_busy_signal) begin uart_data_to_send <= 8'hAA; uart_start_tx_pulse_signal <= 1'b1; uart_tx_state_prev_top <= S_UART_TX_SEND_HIGH_BYTE_TOP; uart_tx_state <= S_UART_TX_WAIT_BUSY_LOW_TOP; end
S_UART_TX_SEND_HIGH_BYTE_TOP: if (!uart_tx_busy_signal) begin uart_data_to_send <= adc_data_uart_buffer[current_uart_channel_idx_top][15:8]; uart_start_tx_pulse_signal <= 1'b1; uart_tx_state_prev_top <= S_UART_TX_SEND_LOW_BYTE_TOP; uart_tx_state <= S_UART_TX_WAIT_BUSY_LOW_TOP; end
S_UART_TX_SEND_LOW_BYTE_TOP: if (!uart_tx_busy_signal) begin uart_data_to_send <= adc_data_uart_buffer[current_uart_channel_idx_top][7:0]; uart_start_tx_pulse_signal <= 1'b1; uart_tx_state_prev_top <= S_UART_TX_NEXT_CHANNEL_TOP; uart_tx_state <= S_UART_TX_WAIT_BUSY_LOW_TOP; end
S_UART_TX_NEXT_CHANNEL_TOP: if (current_uart_channel_idx_top < 7) begin current_uart_channel_idx_top <= current_uart_channel_idx_top + 1; uart_tx_state <= S_UART_TX_SEND_HIGH_BYTE_TOP; end else uart_tx_state <= S_UART_TX_END_FRAME_TOP;
S_UART_TX_END_FRAME_TOP: if (!uart_tx_busy_signal) begin uart_data_to_send <= 8'h55; uart_start_tx_pulse_signal <= 1'b1; uart_tx_state_prev_top <= S_UART_TX_DONE_TOP; uart_tx_state <= S_UART_TX_WAIT_BUSY_LOW_TOP; end
S_UART_TX_DONE_TOP: uart_tx_state <= S_UART_TX_IDLE_TOP;
S_UART_TX_WAIT_BUSY_LOW_TOP: if (!uart_tx_busy_signal) uart_tx_state <= uart_tx_state_prev_top;
default: uart_tx_state <= S_UART_TX_IDLE_TOP;
endcase
end
end
endmodule//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 模块名称: ad7606_ctrl
// 功能: 控制 AD7606 ADC 和 ADG408 MUX 进行差分测量。
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
module ad7606_ctrl (
input clk,
input rst_n,
input [3:0] ctrl_reg,
input busy,
input frstdata,
input [15:0] data_bus,
output [2:0] os,
output range,
output reg convst_a,
output reg convst_b,
output reg reset,
output reg rd_n,
output reg cs_n,
output reg [2:0] mux_addr,
output reg mux_en,
output reg signed [15:0] ch1_data,
output reg signed [15:0] ch2_data,
output reg signed [15:0] ch3_data,
output reg signed [15:0] ch4_data,
output reg signed [15:0] ch5_data,
output reg signed [15:0] ch6_data,
output reg signed [15:0] ch7_data,
output reg signed [15:0] ch8_data,
output reg mode_busy,
output reg data_vld,
output reg led_sampling_busy_blink
);
parameter DATA_W = 16;
parameter RD_LOW_CYCLES = 2;
parameter DATA_VALID_CYCLE = 1;
parameter RD_HIGH_CYCLES = 1;
parameter MUX_SETTLE_CYCLES = 10;
localparam [3:0] S_IDLE = 4'b0000;
localparam [3:0] S_RESET_ADC = 4'b0001;
localparam [3:0] S_SET_MUX = 4'b0010;
localparam [3:0] S_MUX_SETTLE = 4'b0011;
localparam [3:0] S_START_CONV = 4'b0100;
localparam [3:0] S_WAIT_CONV_FINISH = 4'b0101;
localparam [3:0] S_READ_START = 4'b0110;
localparam [3:0] S_READ_RD_LOW = 4'b0111;
localparam [3:0] S_READ_RD_HIGH = 4'b1000;
localparam [3:0] S_READ_NEXT_CH = 4'b1001;
localparam [3:0] S_READ_END = 4'b1010;
localparam [3:0] S_CALC_DIFF = 4'b1011;
localparam [3:0] S_CHECK_DONE = 4'b1100;
localparam [3:0] S_FINISH = 4'b1101;
reg [3:0] current_state, next_state;
reg reset_pulse_done;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
reset_pulse_done <= 1'b0;
end else if (current_state == S_RESET_ADC) begin
reset_pulse_done <= 1'b1;
end
end
reg busy_ff0, busy_ff1;
wire busy_falling_edge;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
busy_ff0 <= 1'b1;
busy_ff1 <= 1'b1;
end else begin
busy_ff0 <= busy;
busy_ff1 <= busy_ff0;
end
end
assign busy_falling_edge = busy_ff1 && !busy_ff0;
assign os = ctrl_reg[2:0];
assign range = ctrl_reg[3];
reg [2:0] current_mux_channel;
reg [7:0] cnt_mux_settle;
reg start_conv_pulse;
reg [3:0] cnt_rd_adc_channel;
reg [7:0] cnt_rd_timing;
reg [DATA_W-1:0] temp_ch_A_data;
reg [DATA_W-1:0] temp_ch_B_data;
reg signed [DATA_W-1:0] diff_results [7:0];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= S_IDLE;
end else begin
current_state <= next_state;
end
end
always @(*) begin
next_state = current_state;
start_conv_pulse = 1'b0; convst_a = 1'b1; convst_b = 1'b1;
cs_n = 1'b1; rd_n = 1'b1; reset = 1'b0;
mux_en = mux_en; mux_addr = mux_addr; data_vld = 1'b0; mode_busy = mode_busy;
case (current_state)
S_IDLE: begin
mode_busy = 1'b0; mux_en = 1'b0;
if (!reset_pulse_done) begin
next_state = S_RESET_ADC;
end else begin
next_state = S_SET_MUX; current_mux_channel = 3'd0; mode_busy = 1'b1;
end
end
S_RESET_ADC: begin reset = 1'b1; next_state = S_IDLE; end
S_SET_MUX: begin mux_addr = current_mux_channel; mux_en = 1'b1; cnt_mux_settle = {8{1'b0}}; next_state = S_MUX_SETTLE; end
S_MUX_SETTLE: begin
mux_en = 1'b1;
if (cnt_mux_settle < MUX_SETTLE_CYCLES - 1) begin
cnt_mux_settle = cnt_mux_settle + 1'b1; next_state = S_MUX_SETTLE;
end else begin
next_state = S_START_CONV;
end
end
S_START_CONV: begin mux_en = 1'b1; start_conv_pulse = 1'b1; next_state = S_WAIT_CONV_FINISH; end
S_WAIT_CONV_FINISH: begin
mux_en = 1'b1;
if (busy_falling_edge) begin
next_state = S_READ_START;
end else begin
next_state = S_WAIT_CONV_FINISH;
end
end
S_READ_START: begin mux_en = 1'b1; cs_n = 1'b0; rd_n = 1'b1; cnt_rd_adc_channel = 4'd0; cnt_rd_timing = 8'd0; next_state = S_READ_RD_LOW; end
S_READ_RD_LOW: begin
mux_en = 1'b1; cs_n = 1'b0; rd_n = 1'b0;
if (cnt_rd_timing < RD_LOW_CYCLES - 1) begin
cnt_rd_timing = cnt_rd_timing + 1'b1; next_state = S_READ_RD_LOW;
end else begin
cnt_rd_timing = 8'd0; next_state = S_READ_RD_HIGH;
end
end
S_READ_RD_HIGH: begin
mux_en = 1'b1; cs_n = 1'b0; rd_n = 1'b1;
if (cnt_rd_timing < RD_HIGH_CYCLES - 1) begin
cnt_rd_timing = cnt_rd_timing + 1'b1; next_state = S_READ_RD_HIGH;
end else begin
next_state = S_READ_NEXT_CH;
end
end
S_READ_NEXT_CH: begin
mux_en = 1'b1; cs_n = 1'b0; rd_n = 1'b1;
if (cnt_rd_adc_channel < 7) begin
cnt_rd_adc_channel = cnt_rd_adc_channel + 1'b1; cnt_rd_timing = 8'd0; next_state = S_READ_RD_LOW;
end else begin
next_state = S_READ_END;
end
end
S_READ_END: begin mux_en = 1'b1; cs_n = 1'b1; rd_n = 1'b1; next_state = S_CALC_DIFF; end
S_CALC_DIFF: begin mux_en = 1'b1; diff_results[current_mux_channel] = $signed(temp_ch_A_data) - $signed(temp_ch_B_data); next_state = S_CHECK_DONE; end
S_CHECK_DONE: begin
mux_en = 1'b1;
if (current_mux_channel < 7) begin
current_mux_channel = current_mux_channel + 1'b1; next_state = S_SET_MUX;
end else begin
next_state = S_FINISH;
end
end
S_FINISH: begin
mux_en = 1'b0; data_vld = 1'b1; mode_busy = 1'b0;
ch1_data = diff_results[0]; ch2_data = diff_results[1]; ch3_data = diff_results[2]; ch4_data = diff_results[3];
ch5_data = diff_results[4]; ch6_data = diff_results[5]; ch7_data = diff_results[6]; ch8_data = diff_results[7];
next_state = S_IDLE;
end
default: begin
next_state = S_IDLE;
start_conv_pulse = 1'b0; convst_a = 1'b1; convst_b = 1'b1;
cs_n = 1'b1; rd_n = 1'b1; reset = 1'b0;
mux_en = 1'b0;
data_vld = 1'b0;
mode_busy = 1'b0;
end
endcase
if (start_conv_pulse) begin
convst_a = 1'b0;
convst_b = 1'b0;
end else begin
convst_a = 1'b1;
convst_b = 1'b1;
end
end
wire capture_data = (current_state == S_READ_RD_HIGH) && (cnt_rd_timing == 0);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
temp_ch_A_data <= {DATA_W{1'b0}};
temp_ch_B_data <= {DATA_W{1'b0}};
end else if (capture_data) begin
if (cnt_rd_adc_channel == 4'd0) begin
temp_ch_A_data <= data_bus;
end else if (cnt_rd_adc_channel == 4'd1) begin
temp_ch_B_data <= data_bus;
end
end
end
parameter BLINK_RATE_DIV_BITS = 25;
reg [BLINK_RATE_DIV_BITS-1:0] led_blink_cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led_blink_cnt <= {BLINK_RATE_DIV_BITS{1'b0}};
led_sampling_busy_blink <= 1'b0;
end else if (mode_busy) begin
led_blink_cnt <= led_blink_cnt + 1'b1;
led_sampling_busy_blink <= led_blink_cnt[BLINK_RATE_DIV_BITS-1];
end else begin
led_blink_cnt <= {BLINK_RATE_DIV_BITS{1'b0}};
led_sampling_busy_blink <= 1'b0;
end
end
endmodulemodule oled_096inch_driver (
input wire clk,
input wire rst_n,
// 输入数据接口 (当前代码未使用 prepare_display_buffer 中的实际数据)
input wire signed [15:0] ch1_data,
input wire signed [15:0] ch2_data,
input wire signed [15:0] ch3_data,
input wire signed [15:0] ch4_data,
input wire signed [15:0] ch5_data,
input wire signed [15:0] ch6_data,
input wire signed [15:0] ch7_data,
input wire signed [15:0] ch8_data,
input wire data_valid, // ADC 数据有效信号
// OLED 接口
output reg oled_sclk,
output reg oled_mosi,
output reg oled_res,
output reg oled_dc,
output reg oled_cs
);
// 参数定义
localparam INIT_DELAY_COUNT = 24'd1000000; // 20ms @ 50MHz
localparam RESET_DELAY_COUNT = 24'd250000; // 5ms @ 50MHz
localparam SPI_CLK_DIV = 8'd100; // SPI时钟分频 (50MHz / (50+1) ~= 1MHz)
localparam SPI_DELAY_VAL = 6'd50; // *** 增加 SPI字节后延时值 ***
// OLED状态机状态 - 使用中间状态简化逻辑
localparam S_INIT_DELAY = 5'd0; // 使用5位以容纳中间状态
localparam S_RESET_OLED = 5'd1;
localparam S_INIT_OLED = 5'd2;
// Clear Screen States
localparam S_CLEAR_SET_PAGE = 5'd3;
localparam S_CLEAR_SET_COL_L = 5'd4;
localparam S_CLEAR_SET_COL_H = 5'd5;
localparam S_CLEAR_SEND_DATA = 5'd6;
localparam S_CLEAR_INC_COL = 5'd7;
// Display States
localparam S_DISP_SET_PAGE = 5'd8;
localparam S_DISP_SET_COL_L = 5'd9;
localparam S_DISP_SET_COL_H = 5'd10;
localparam S_DISP_SEND_DATA = 5'd11;
localparam S_DISP_INC_COL = 5'd12;
// Common States
localparam S_DISPLAY_DONE = 5'd13; // Frame finished
localparam S_WAIT_SPI_IDLE = 5'd14; // Wait for SPI transaction end
// SPI状态机状态
localparam SPI_IDLE = 3'd0;
localparam SPI_TRANSMIT = 3'd1;
localparam SPI_DONE = 3'd2;
localparam SPI_POST_DELAY = 3'd3;
// 寄存器定义
reg [4:0] oled_state; // OLED 主状态机状态 (5位)
reg [4:0] oled_state_next; // 用于 WAIT_SPI_IDLE 后跳转
reg [2:0] spi_state; // SPI 状态机状态
reg [7:0] spi_data; // 要发送的 SPI 数据
reg [3:0] spi_bit_count; // SPI 位计数
reg [7:0] cmd_index; // 初始化命令索引
reg [2:0] page_addr; // 当前页地址 (0-7)
reg [7:0] col_addr; // 当前列地址 (0-127)
reg [23:0] delay_count; // 通用延时计数器
reg [2:0] channel_index; // 当前显示的通道索引 (用于测试图案)
reg spi_start; // SPI 启动请求信号 (单周期脉冲)
reg spi_request_latch; // SPI 启动请求锁存 (确保请求不丢失)
reg spi_busy; // SPI 忙信号 (包括字节后延时)
reg is_command; // 标记 spi_data 是命令(1)还是数据(0)
reg [7:0] display_buffer[0:127]; // 简单的单页显示缓冲区 (8KB)
reg [7:0] spi_clk_div_cnt; // SPI 时钟分频计数器
reg [5:0] spi_delay_cnt; // SPI 字节后延时计数器
// 输入数据存储 (当前未使用,仅用于未来扩展)
reg signed [15:0] stored_adc_data [0:7];
reg data_updated; // 标志是否有新的 ADC 数据进来
reg [19:0] display_timer; // 用于轮流显示不同通道数据 (测试用)
// SPI时钟分频逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
spi_clk_div_cnt <= 8'd0;
else
spi_clk_div_cnt <= (spi_clk_div_cnt >= SPI_CLK_DIV) ? 8'd0 : spi_clk_div_cnt + 1'b1;
end
wire spi_clk_en = (spi_clk_div_cnt == SPI_CLK_DIV); // SPI 时钟使能信号
// 初始化命令序列 (共25条)
reg [7:0] init_commands [0:24];
initial begin
init_commands[ 0] = 8'hAE; // Display off
init_commands[ 1] = 8'hD5; // Set display clock divide
init_commands[ 2] = 8'h80; // Suggested ratio
init_commands[ 3] = 8'hA8; // Set multiplex ratio
init_commands[ 4] = 8'h3F; // 1/64 duty
init_commands[ 5] = 8'hD3; // Set display offset
init_commands[ 6] = 8'h00; // No offset
init_commands[ 7] = 8'h40; // Set display start line (0)
init_commands[ 8] = 8'h8D; // Charge pump setting
init_commands[ 9] = 8'h14; // Enable charge pump
init_commands[10] = 8'h20; // Set Memory Addressing Mode
init_commands[11] = 8'h00; // Horizontal Addressing Mode (might need 0x01 for Vertical or 0x02 for Page) -> Let's stick to Page Addressing Mode (0x02) for this driver
// Correcting Memory Addressing Mode to Page Addressing Mode
init_commands[10] = 8'h20; // Set Memory Addressing Mode command
init_commands[11] = 8'h02; // Page Addressing Mode ( crucial for B0-B7 commands)
init_commands[12] = 8'hA0; // Segment remap (尝试正常映射)
init_commands[13] = 8'hC0; // COM output scan direction (尝试正常扫描)
init_commands[14] = 8'hDA; // Set COM pins hardware configuration
init_commands[15] = 8'h12; // Alternative COM pin config / Sequential COM pin config
init_commands[16] = 8'h81; // Contrast control
init_commands[17] = 8'hCF; // Contrast value (try different values if needed)
init_commands[18] = 8'hD9; // Set pre-charge period
init_commands[19] = 8'hF1; // Precharge period
init_commands[20] = 8'hDB; // Set VCOMH deselect level
init_commands[21] = 8'h40; // VCOMH level (~0.77 Vcc)
init_commands[22] = 8'hA4; // Entire display ON from RAM (disable force on)
init_commands[23] = 8'hA6; // Normal display (A7=inverse)
init_commands[24] = 8'hAF; // Display on
end
// ADC 数据锁存逻辑 (当 data_valid 有效时锁存)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
stored_adc_data[0] <= 16'd0; stored_adc_data[1] <= 16'd0;
stored_adc_data[2] <= 16'd0; stored_adc_data[3] <= 16'd0;
stored_adc_data[4] <= 16'd0; stored_adc_data[5] <= 16'd0;
stored_adc_data[6] <= 16'd0; stored_adc_data[7] <= 16'd0;
data_updated <= 1'b0;
end else if (data_valid) begin // 当 ADC 数据有效时锁存
stored_adc_data[0] <= ch1_data; stored_adc_data[1] <= ch2_data;
stored_adc_data[2] <= ch3_data; stored_adc_data[3] <= ch4_data;
stored_adc_data[4] <= ch5_data; stored_adc_data[5] <= ch6_data;
stored_adc_data[6] <= ch7_data; stored_adc_data[7] <= ch8_data;
data_updated <= 1'b1; // 设置更新标志
end else begin
data_updated <= 1'b0; // 清除更新标志
end
end
// 轮流显示通道计时器 (用于 prepare_display_buffer 中的测试图案切换)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
display_timer <= 20'd0;
channel_index <= 3'd0;
end else if (oled_state == S_DISPLAY_DONE) begin // 只在每帧完成后切换
if (display_timer >= 20'd500000) begin // ~10ms @ 50MHz 更新一次图案
display_timer <= 20'd0;
channel_index <= (channel_index == 3'd7) ? 3'd0 : channel_index + 1'b1;
end else begin
display_timer <= display_timer + 1'b1;
end
end else begin
display_timer <= 20'd0; // 在其他状态重置计时器
end
end
// 准备显示缓冲区 - 使用测试图案
task prepare_display_buffer;
integer i;
begin
// *** 简化测试: 将整个缓冲区填充为静态值 0xFF ***
for (i = 0; i < 128; i = i + 1) begin
display_buffer[i] = 8'hFF; // 或者 8'hAA, 8'h55
end
$display("Filled display buffer with static test pattern (0xFF).");
end
endtask
// SPI 通信状态机 - 带字节后延时
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
spi_state <= SPI_IDLE;
spi_bit_count <= 4'd0;
spi_busy <= 1'b0;
oled_cs <= 1'b1;
oled_dc <= 1'b1; // D/C 空闲时状态不重要,会在启动时设置
oled_sclk <= 1'b1; // SPI Mode 0: CPOL=0, CPHA=0. SCLK空闲时为低,但设为高也可以
oled_mosi <= 1'b0;
spi_request_latch <= 1'b0;
spi_delay_cnt <= 5'd0;
end else begin
// 锁存 spi_start 请求
if (spi_start && spi_state == SPI_IDLE && !spi_request_latch) begin
spi_request_latch <= 1'b1;
end
case (spi_state)
SPI_IDLE: begin
oled_sclk <= 1'b1; // 保持 SCLK 高电平(或低电平,取决于偏好)
oled_cs <= 1'b1; // 保持 CS 高电平
spi_busy <= 1'b0; // SPI 空闲
if (spi_request_latch) begin // 如果有锁存的请求
spi_state <= SPI_TRANSMIT;
spi_bit_count <= 4'd0;
spi_busy <= 1'b1; // 设置 SPI 忙
oled_cs <= 1'b0; // 拉低 CS
oled_dc <= !is_command; // 设置 D/C (0=CMD, 1=DATA)
oled_sclk <= 1'b1; // 确保 SCLK 在第一个下降沿前为高
spi_request_latch <= 1'b0; // 清除锁存请求
// 第一个数据位在 TRANSMIT 状态的第一个 spi_clk_en 时钟周期发送
end
end
SPI_TRANSMIT: begin
// SPI Mode 0: 数据在上升沿采样,下降沿改变
if (spi_clk_en) begin // 仅在 SPI 时钟使能时动作
if (spi_bit_count < 4'd8) begin
if (oled_sclk) begin // SCLK 为高 -> 拉低 SCLK, 改变 MOSI
oled_sclk <= 1'b0;
oled_mosi <= spi_data[7-spi_bit_count];
end else begin // SCLK 为低 -> 拉高 SCLK (此时从设备采样 MOSI)
oled_sclk <= 1'b1;
spi_bit_count <= spi_bit_count + 1'b1; // 增加位计数
end
end else begin // 8位已发送 (在第8个上升沿之后)
oled_sclk <= 1'b1; // 确保 SCLK 在 CS 变高前为高
spi_state <= SPI_DONE;
end
end else if (!oled_sclk && spi_bit_count < 4'd8) begin
// 如果 SCLK 为低且 spi_clk_en 无效,保持低电平
oled_sclk <= 1'b0;
end else begin
// 如果 SCLK 为高且 spi_clk_en 无效,保持高电平
oled_sclk <= 1'b1;
end
end
SPI_DONE: begin // SPI 传输完成,进入字节后延时
oled_cs <= 1'b1; // 拉高 CS
oled_sclk <= 1'b1; // 保持 SCLK 高电平
spi_delay_cnt <= 5'd0; // 复位延时计数器
spi_state <= SPI_POST_DELAY;
// spi_busy 保持为高
end
SPI_POST_DELAY: begin // 字节发送后的延时
oled_cs <= 1'b1; // 保持 CS 高
oled_sclk <= 1'b1; // 保持 SCLK 高
if (spi_delay_cnt < SPI_DELAY_VAL) begin
spi_delay_cnt <= spi_delay_cnt + 1'b1; // 计数
end else begin
spi_busy <= 1'b0; // 延时结束,SPI 真正空闲
spi_state <= SPI_IDLE;
end
end
default: spi_state <= SPI_IDLE;
endcase
end
end
// OLED 控制状态机 (使用简化逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
oled_state <= S_INIT_DELAY;
oled_state_next <= S_INIT_DELAY; // 初始化跳转状态
delay_count <= 24'd0;
cmd_index <= 8'd0;
page_addr <= 3'd0; // 页地址是 3 位
col_addr <= 8'd0;
spi_start <= 1'b0;
is_command <= 1'b0; // 默认值
spi_data <= 8'd0;
oled_res <= 1'b0; // 复位有效
end else begin
spi_start <= 1'b0; // 默认每周期清零 spi_start
case (oled_state)
S_INIT_DELAY: begin // 上电延时
oled_res <= 1'b0; // 保持复位
if (delay_count < INIT_DELAY_COUNT) begin
delay_count <= delay_count + 1'b1;
end else begin
delay_count <= 24'd0;
oled_res <= 1'b1; // 释放复位
oled_state <= S_RESET_OLED;
end
end
S_RESET_OLED: begin // 复位后延时
oled_res <= 1'b1; // 保持复位无效
if (delay_count < RESET_DELAY_COUNT) begin
delay_count <= delay_count + 1'b1;
end else begin
delay_count <= 24'd0;
cmd_index <= 8'd0; // 准备发送初始化命令
oled_state <= S_INIT_OLED;
end
end
S_INIT_OLED: begin // 发送初始化命令序列
if (!spi_busy) begin // 等待 SPI 空闲
if (cmd_index >= 25) begin // 所有初始化命令发送完毕
cmd_index <= 8'd0; // 重置索引
page_addr <= 3'd0;
col_addr <= 8'd0;
oled_state <= S_CLEAR_SET_PAGE; // 跳转到清屏状态
$display("OLED Initialization Complete. Starting Clear Screen.");
end else begin
// 发送下一条初始化命令
spi_data <= init_commands[cmd_index];
is_command <= 1'b1; // 这是命令
spi_start <= 1'b1; // 请求 SPI 发送
oled_state_next <= S_INIT_OLED; // 完成后回到这里继续发送下一条
oled_state <= S_WAIT_SPI_IDLE; // 等待 SPI 完成
cmd_index <= cmd_index + 1'b1; // 准备下一条命令的索引
end
end
// else: 保持当前状态,等待 spi_busy 变低
end
// --- 清屏流程 ---
S_CLEAR_SET_PAGE: begin // 1. 设置页地址
if (!spi_busy) begin
if (page_addr < 8) begin
spi_data <= 8'hB0 | page_addr; // B0-B7 命令
is_command <= 1'b1;
spi_start <= 1'b1;
oled_state_next <= S_CLEAR_SET_COL_L; // 下一步: 设置列低位
oled_state <= S_WAIT_SPI_IDLE;
end else begin // 所有页已清空
page_addr <= 3'd0; // 重置页地址
col_addr <= 8'd0; // 重置列地址
prepare_display_buffer(); // 准备第一帧显示内容
oled_state <= S_DISP_SET_PAGE; // 跳转到显示流程
$display("Clear Screen Complete. Starting Display.");
end
end
end
S_CLEAR_SET_COL_L: begin // 2. 设置列地址低位 (0x00)
if (!spi_busy) begin
spi_data <= 8'h00;
is_command <= 1'b1;
spi_start <= 1'b1;
oled_state_next <= S_CLEAR_SET_COL_H; // 下一步: 设置列高位
oled_state <= S_WAIT_SPI_IDLE;
end
end
S_CLEAR_SET_COL_H: begin // 3. 设置列地址高位 (0x10)
if (!spi_busy) begin
spi_data <= 8'h10;
is_command <= 1'b1;
spi_start <= 1'b1;
col_addr <= 8'd0; // 重置列计数器,准备发送数据
oled_state_next <= S_CLEAR_SEND_DATA; // 下一步: 发送清屏数据
oled_state <= S_WAIT_SPI_IDLE;
end
end
S_CLEAR_SEND_DATA: begin // 4. 发送清屏数据 (0x00)
if (!spi_busy) begin
if (col_addr < 128) begin
spi_data <= 8'h00; // 发送数据 0
is_command <= 1'b0; // 这是数据
spi_start <= 1'b1;
oled_state_next <= S_CLEAR_INC_COL; // 下一步: 增加列计数
oled_state <= S_WAIT_SPI_IDLE;
end else begin // 当前页的128字节发送完毕
page_addr <= page_addr + 1'b1; // 移动到下一页
oled_state <= S_CLEAR_SET_PAGE; // 回到设置页地址状态处理下一页
end
end
end
S_CLEAR_INC_COL: begin // 5. 增加列计数 (中间状态)
if (!spi_busy) begin
col_addr <= col_addr + 1'b1;
oled_state <= S_CLEAR_SEND_DATA; // 返回发送下一个数据字节
end
end
// --- 显示流程 ---
S_DISP_SET_PAGE: begin // 1. 设置页地址
if (!spi_busy) begin
spi_data <= 8'hB0 | page_addr; // B0-B7 命令
is_command <= 1'b1;
spi_start <= 1'b1;
oled_state_next <= S_DISP_SET_COL_L; // 下一步: 设置列低位
oled_state <= S_WAIT_SPI_IDLE;
end
end
S_DISP_SET_COL_L: begin // 2. 设置列地址低位 (0x00)
if (!spi_busy) begin
spi_data <= 8'h00;
is_command <= 1'b1;
spi_start <= 1'b1;
oled_state_next <= S_DISP_SET_COL_H; // 下一步: 设置列高位
oled_state <= S_WAIT_SPI_IDLE;
end
end
S_DISP_SET_COL_H: begin // 3. 设置列地址高位 (0x10)
if (!spi_busy) begin
spi_data <= 8'h10;
is_command <= 1'b1;
spi_start <= 1'b1;
col_addr <= 8'd0; // 重置列计数器,准备发送数据
oled_state_next <= S_DISP_SEND_DATA; // 下一步: 发送显示数据
oled_state <= S_WAIT_SPI_IDLE;
end
end
S_DISP_SEND_DATA: begin // 4. 发送显示数据 (从 buffer)
if (!spi_busy) begin
if (col_addr < 128) begin
spi_data <= display_buffer[col_addr]; // 从缓冲区获取数据
is_command <= 1'b0; // 这是数据
spi_start <= 1'b1;
oled_state_next <= S_DISP_INC_COL; // 下一步: 增加列计数
oled_state <= S_WAIT_SPI_IDLE;
end else begin // 当前页的128字节发送完毕
if (page_addr < 7) begin // 是否所有页都发送完了?
page_addr <= page_addr + 1'b1; // 移动到下一页
oled_state <= S_DISP_SET_PAGE; // 回到设置页地址状态处理下一页
end else begin // 所有页都发送完毕
oled_state <= S_DISPLAY_DONE; // 一帧完成
end
end
end
end
S_DISP_INC_COL: begin // 5. 增加列计数 (中间状态)
if (!spi_busy) begin
col_addr <= col_addr + 1'b1;
oled_state <= S_DISP_SEND_DATA; // 返回发送下一个数据字节
end
end
S_DISPLAY_DONE: begin // 一帧显示完成
page_addr <= 3'd0; // 重置页地址
col_addr <= 8'd0; // 重置列地址
prepare_display_buffer(); // 准备下一帧的数据 (基于变化的 channel_index)
oled_state <= S_DISP_SET_PAGE; // 开始下一帧的显示流程
// $display("Display Frame Done. Starting next frame."); // 可以取消注释以调试
end
S_WAIT_SPI_IDLE: begin // 等待 SPI 完成 (包括字节后延时)
if (!spi_busy) begin
oled_state <= oled_state_next; // 跳转到预定的下一个状态
end
// else: 继续等待
end
default: oled_state <= S_INIT_DELAY; // 异常状态则返回初始状态
endcase
end
end
endmodule//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 模块名称: uart_tx_module
// 功能: UART发送模块
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
module uart_tx_module (
input wire clk,
input wire rst_n,
input wire clk_enable,
input wire [7:0] data_in,
input wire start_tx,
output wire tx_busy,
output wire tx_serial_out
);
localparam OVERSAMPLING_CNT_WIDTH = 4;
localparam OVERSAMPLING_MAX_CNT = (1 << OVERSAMPLING_CNT_WIDTH) - 1;
localparam BIT_CNT_WIDTH = 4;
localparam NUM_DATA_BITS = 8;
localparam STOP_BIT_VAL = 1'b1;
localparam START_BIT_VAL = 1'b0;
localparam S_IDLE = 2'b00;
localparam S_TX_START_BIT = 2'b01;
localparam S_TX_DATA_BITS = 2'b10;
localparam S_TX_STOP_BIT = 2'b11;
reg tx_busy_reg;
reg tx_serial_out_reg;
reg [OVERSAMPLING_CNT_WIDTH-1:0] sample_cnt;
reg [BIT_CNT_WIDTH-1:0] bit_cnt;
reg [NUM_DATA_BITS-1:0] data_latch;
reg [1:0] current_state;
wire end_of_bit_tick;
assign tx_busy = tx_busy_reg;
assign tx_serial_out = tx_serial_out_reg;
assign end_of_bit_tick = (sample_cnt == OVERSAMPLING_MAX_CNT);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) sample_cnt <= 0;
else if (current_state == S_IDLE) sample_cnt <= 0;
else if (clk_enable) begin
if (end_of_bit_tick) sample_cnt <= 0;
else sample_cnt <= sample_cnt + 1'b1;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= S_IDLE; tx_busy_reg <= 1'b0; tx_serial_out_reg <= 1'b1;
bit_cnt <= 0; data_latch <= 8'd0;
end else begin
case (current_state)
S_IDLE: begin
tx_serial_out_reg <= 1'b1; tx_busy_reg <= 1'b0; bit_cnt <= 0;
if (start_tx) begin
tx_busy_reg <= 1'b1; data_latch <= data_in;
current_state <= S_TX_START_BIT;
end
end
S_TX_START_BIT: begin
tx_serial_out_reg <= START_BIT_VAL;
if (clk_enable && end_of_bit_tick) begin bit_cnt <= bit_cnt + 1'b1; current_state <= S_TX_DATA_BITS; end
end
S_TX_DATA_BITS: begin
tx_serial_out_reg <= data_latch[bit_cnt - 1];
if (clk_enable && end_of_bit_tick) begin
if (bit_cnt < NUM_DATA_BITS) begin bit_cnt <= bit_cnt + 1'b1; current_state <= S_TX_DATA_BITS; end
else begin bit_cnt <= bit_cnt + 1'b1; current_state <= S_TX_STOP_BIT; end
end
end
S_TX_STOP_BIT: begin
tx_serial_out_reg <= STOP_BIT_VAL;
if (clk_enable && end_of_bit_tick) begin bit_cnt <= 0; tx_busy_reg <= 1'b0; current_state <= S_IDLE; end
end
default: begin current_state <= S_IDLE; tx_busy_reg <= 1'b0; tx_serial_out_reg <= 1'b1; bit_cnt <= 0; end
endcase
end
end
endmodule我想用modlesim测试我的oled黑屏和ad7606数据采集的情况