FPGAを用いたSSD1306搭載0.96インチOLEDのSPI制御設計

SSD1306コントローラとSPI配線仕様

0.96インチモノクロOLEDモジュールの内部駆動にはSSD1306チップが採用されており、ホストとの通信には6800/8080パラレルバス、I²C、またはSPIのいずれかを選択可能です。本設計では7ピン仕様のモジュールを対象とし、SPIモードによる単方向マスター通信を実装します。

SSD1306はホストから送信されたコマンドと描画データのみを受信するスレーブ動作を行うため、SPIの全二重機能のうちMISOラインは使用しません。必要な信号線はシリアルクロック(SCLK)、マスター出力データ(MOSI)、およびチップセレクト(CS)、データ/コマンド制御(D/C)、リセット(RES)の計5本となります。

ピン名機能説明
GNDシステムグラウンド
VCC電源供給(3.3V〜5.0V)
D0SPIクロック入力(SCLK)
D1SPIデータ入力(MOSI)
RESハードウェアリセット(Low有効)
D/Cデータ/コマンド切替(0:コマンド / 1:描画データ)
CSチップセレクト(Low有効)

SPI送信専用ステートマシン

OLEDへのデータ転送は8ビット単位のシフトアウト処理で構成されます。CPOL/CPHAは標準のMode 0(アイドルHigh、立上りサンプリング)を想定し、コマンド送信の完了通知を行う独立モジュールとして設計します。

`timescale 1ns / 1ps

module spi_tx_ctrl (
    input  wire        clk_sys,
    input  wire        rst_n,
    input  wire  [7:0] tx_payload,
    input  wire        trigger_en,
    output reg         sclk_out,
    output reg         mosi_out,
    output reg         cs_n_out,
    output reg         tx_done
);

    typedef enum reg [2:0] {
        IDLE   = 3'b000,
        PREP   = 3'b001,
        SHIFT  = 3'b010,
        FINISH = 3'b011
    } state_t;

    state_t current_state;
    reg [7:0] shift_reg;
    reg [2:0] bit_ptr;

    always @(posedge clk_sys or negedge rst_n) begin
        if (!rst_n) begin
            current_state <= IDLE;
            sclk_out  <= 1'b1;
            mosi_out  <= 1'b0;
            cs_n_out  <= 1'b1;
            tx_done   <= 1'b1;
            bit_ptr   <= 3'd0;
            shift_reg <= 8'h00;
        end else begin
            case (current_state)
                IDLE: begin
                    tx_done <= 1'b1;
                    cs_n_out <= 1'b1;
                    sclk_out <= 1'b1;
                    if (trigger_en) begin
                        tx_done   <= 1'b0;
                        shift_reg <= tx_payload;
                        bit_ptr   <= 3'd0;
                        cs_n_out  <= 1'b0;
                        current_state <= PREP;
                    end
                end
                PREP: begin
                    mosi_out <= shift_reg[7];
                    shift_reg <= {shift_reg[6:0], 1'b0};
                    sclk_out <= 1'b0;
                    current_state <= SHIFT;
                end
                SHIFT: begin
                    if (bit_ptr < 3'd7) begin
                        if (!sclk_out) begin
                            sclk_out <= 1'b1;
                        end else begin
                            sclk_out  <= 1'b0;
                            bit_ptr   <= bit_ptr + 3'd1;
                            mosi_out  <= shift_reg[7];
                            shift_reg <= {shift_reg[6:0], 1'b0};
                        end
                    end else begin
                        if (!sclk_out) begin
                            sclk_out <= 1'b1;
                        end else begin
                            current_state <= FINISH;
                        end
                    end
                end
                FINISH: begin
                    sclk_out   <= 1'b1;
                    cs_n_out   <= 1'b1;
                    tx_done    <= 1'b1;
                    current_state <= IDLE;
                end
            endcase
        end
    end
endmodule

上位制御ロジックと表示シーケンス

メイン制御モジュールでは、SSD1306の初期化コマンド列の送出、8ページ×128列のフレームバッファ管理、外部スイッチによる表示モードの切替を統合します。転送インデックスに基づきコマンドと描画データを自動で切り替え、ページアドレス設定コマンドを適切なタイミングで挿入することで連続描画を実現します。

`timescale 1ns / 1ps

module oled_main_ctrl (
    input  wire        clk_in,
    input  wire        rst_n,
    input  wire        sw_toggle,

    output reg         led_status,
    output wire        oled_sclk,
    output wire        oled_mosi,
    output reg         oled_dc,
    output reg         oled_rst,
    output wire        oled_cs
);

    // 内部信号定義
    reg [19:0]       debounce_cnt;
    reg              sw_flag;
    reg              sw_prev;
    reg [7:0]        init_rom [31:0];
    reg [7:0]        frame_buf [7:0][127:0];
    reg [10:0]       tx_index;
    reg              tx_trigger;
    reg [1:0]        page_phase;
    
    wire             tx_done;
    wire [7:0]       current_byte;

    // キー入力同期化とチャタリング除去
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            debounce_cnt <= 20'd0;
            sw_prev      <= 1'b0;
        end else begin
            sw_prev <= sw_toggle;
            if (sw_prev == 1'b0 && sw_toggle == 1'b1) begin
                if (debounce_cnt < 20'd1000000)
                    debounce_cnt <= debounce_cnt + 1'b1;
            end else begin
                debounce_cnt <= 20'd0;
            end
        end
    end

    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) sw_flag <= 1'b0;
        else if (debounce_cnt == 20'd999999) sw_flag <= 1'b1;
        else sw_flag <= 1'b0;
    end

    // 表示モード切り替え用LED
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) led_status <= 1'b0;
        else if (sw_flag) led_status <= ~led_status;
    end

    // 初期化コマンドROM設定
    integer k;
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            init_rom[0]  <= 8'hAE; init_rom[1]  <= 8'h00; init_rom[2]  <= 8'h10;
            init_rom[3]  <= 8'h40; init_rom[4]  <= 8'h81; init_rom[5]  <= 8'hCF;
            init_rom[6]  <= 8'hA1; init_rom[7]  <= 8'hC8; init_rom[8]  <= 8'hA6;
            init_rom[9]  <= 8'hA8; init_rom[10] <= 8'h3F; init_rom[11] <= 8'hD3;
            init_rom[12] <= 8'h00; init_rom[13] <= 8'hD5; init_rom[14] <= 8'h80;
            init_rom[15] <= 8'hD9; init_rom[16] <= 8'hF1; init_rom[17] <= 8'hDA;
            init_rom[18] <= 8'h12; init_rom[19] <= 8'hDB; init_rom[20] <= 8'h40;
            init_rom[21] <= 8'h20; init_rom[22] <= 8'h02; init_rom[23] <= 8'h8D;
            init_rom[24] <= 8'h14; init_rom[25] <= 8'hA4; init_rom[26] <= 8'hA6;
            init_rom[27] <= 8'hAF;
        end
    end

    // フレームバッファ初期化
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            for (k=0; k<128; k=k+1) begin
                frame_buf[0][k] <= 8'hFF; frame_buf[1][k] <= 8'h00;
                frame_buf[2][k] <= 8'hFF; frame_buf[3][k] <= 8'h00;
                frame_buf[4][k] <= 8'hFF; frame_buf[5][k] <= 8'h00;
                frame_buf[6][k] <= 8'hFF; frame_buf[7][k] <= 8'hFF;
            end
        end else if (led_status) begin
            for (k=0; k<128; k=k+1) begin
                frame_buf[0][k] <= 8'hFF; frame_buf[1][k] <= 8'h00;
                frame_buf[2][k] <= 8'hFF; frame_buf[3][k] <= 8'h00;
                frame_buf[4][k] <= 8'hFF; frame_buf[5][k] <= 8'h00;
                frame_buf[6][k] <= 8'hFF; frame_buf[7][k] <= 8'hFF;
            end
        end else begin
            for (k=0; k<128; k=k+1) begin
                frame_buf[0][k] <= 8'hFF; frame_buf[1][k] <= 8'hFF;
                frame_buf[2][k] <= 8'hFF; frame_buf[3][k] <= 8'hFF;
                frame_buf[4][k] <= 8'hFF; frame_buf[5][k] <= 8'hFF;
                frame_buf[6][k] <= 8'hFF; frame_buf[7][k] <= 8'hFF;
            end
        end
    end

    // 転送インデックス管理
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) tx_index <= 11'd0;
        else if (sw_flag) tx_index <= 11'd0;
        else if (tx_trigger) begin
            if (tx_index < 11'd1075) tx_index <= tx_index + 1'b1;
            else tx_index <= 11'd0;
        end
    end

    // データ/コマンド切替ロジック
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) oled_dc <= 1'b0;
        else if (tx_done) begin
            case (tx_index)
                11'd0 : oled_dc <= 1'b0;
                11'd32: begin oled_dc <= 1'b0; page_phase <= 2'b00; end
                default: begin
                    if ((tx_index % 130) == 129) oled_dc <= 1'b0;
                    else oled_dc <= 1'b1;
                end
            endcase
        end
    end

    // リセット信号制御
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) oled_rst <= 1'b1;
        else if (sw_flag) oled_rst <= 1'b0;
        else oled_rst <= 1'b1;
    end

    // 送信トリガー生成
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) tx_trigger <= 1'b0;
        else tx_trigger <= tx_done;
    end

    // データマッピング
    assign current_byte = (!oled_dc) ? init_rom[tx_index] : frame_buf[tx_index/129 % 8][tx_index % 129];

    // SPIインスタンス接続
    spi_tx_ctrl u_spi_tx (
        .clk_sys     (clk_in),
        .rst_n       (rst_n),
        .tx_payload  (current_byte),
        .trigger_en  (tx_trigger),
        .sclk_out    (oled_sclk),
        .mosi_out    (oled_mosi),
        .cs_n_out    (oled_cs),
        .tx_done     (tx_done)
    );

endmodule

タグ: fpga-design spi-protocol ssd1306 verilog-hdl oled-display

5月18日 16:45 投稿