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) |
| D0 | SPIクロック入力(SCLK) |
| D1 | SPIデータ入力(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