Skip to content

SH1107G OLED does not work in avr-hal environment #672

@p14c31355

Description

@p14c31355

タイトル

SH1107G OLEDドライバが avr-hal 環境で動作しない問題について

はじめに

Arduino Uno (AVRマイコン) 上で、1.12インチ 128x128 I²C OLEDディスプレイ(SH1107G)をRustで制御するドライバsh1107g-rsを開発しています。
Arduino IDEのU8g2ライブラリ(C++)では同じハードウェアが正常に動作する一方、avr-halのI²Cドライバを使用すると、画面が真っ暗なままになり、表示ができません。

問題の概要

  • 目標: Rustのavr-halembedded-graphicsを用いてSH1107G OLEDに表示を行う。
  • 期待する動作: 画面が点灯し、白い画面が表示される。
  • 実際の結果: 画面は真っ暗なまま、何も表示されない。

環境

  • ハードウェア: Arduino Uno (ATmega328P), 1.12インチ 128x128 OLEDモジュール (SH1107G)
  • ソフトウェア:
    • Rust (rustc)
    • avr-hal (GitHubリポジトリ: https://github.com/Rahix/avr-hal)
    • embedded-hal, embedded-graphics
    • sh1107g-rs (自作ドライバ)

実施したデバッグと分析

  1. 初期化シーケンスの確認:

    • 当初、Pythonドライバのロジックを元に初期化シーケンスを実装しました。
    • しかし、動作しなかったため、確実に動作するU8g2ライブラリのソースコード(u8x8_sh1107_128x128_init_seq)を詳細に解析しました。
  2. U8g2コマンドの再現:

    • U8g2の初期化シーケンスを参考に、SH1107Gのデータシートと照らし合わせながら、コマンドとデータバイトの組み合わせをRustコードで忠実に再現しました。
    • 特に、以下の点を再確認し、実装に反映しました。
      • コマンドバイトの前に0x80のコントロールバイトを付加する。
      • 複数コマンドを一度に送信する際に、各コマンドの前に0x80を付加する。
  3. flush()関数の確認:

    • embedded-graphicsのバッファ内容をディスプレイに送信するflush()関数も確認しました。
    • ページ(0〜15)ごとにループし、ページアドレスとカラムアドレスを設定する。
    • ページデータ(128バイト)を送信する際に、データの前に0x40のコントロールバイトを付加する。
  4. 最終的なコードの検証:

    • これらの修正を適用した後の最新のinit()flush()のコードを、動作しないことを確認した上で複数回レビューしました。
    • 結論: U8g2のロジックと一致しており、論理的に正しいコードになっていると判断しました。

結論と仮説

これまでのデバッグから、sh1107g-rsドライバのコード自体にロジック上のバグは存在しない可能性が極めて高いです。

問題は、avr-halが提供するembedded-halのI²Cドライバの実装が、SH1107GチップのI²Cプロトコルに特有な要件(特にタイミングやマルチバイト送信の挙動)と互換性がないことにあると強く推測されます。

  • avr-halは汎用的な抽象化レイヤーであるため、特定のチップの厳密なタイミング要件を満たせない可能性があります。
  • ArduinoのWire.hは、長年コミュニティによって最適化されたAVRマイコン向けのドライバであり、このチップとの相性が良いと考えられます。

参考コード

以下に、最終的に動作しなかった、しかしロジックは正しいと判断されたinit()flush()の実装を掲載します。

init()

impl<I2C, E> Sh1107g<I2C>
where
    I2C: embedded_hal::i2c::I2c<Error = E>,
{
    pub fn init(&mut self) -> Result<(), E> {
        // Display Off
        self.send_cmd(0xAE)?;
        // Set Display Clock Divide Ratio / Osc Frequency
        self.send_cmds(&[0xD5, 0x50])?;
        // Set Multiplex Ratio
        self.send_cmds(&[0xA8, 0x7F])?;
        // Set Display Offset
        self.send_cmds(&[0xD3, 0x60])?;
        // Set Start Line
        self.send_cmd(0xDC)?;
        self.send_cmd(0x00)?;
        // Set Segment Remap
        self.send_cmd(0xA0)?;
        // Set COM Output Scan Direction
        self.send_cmd(0xC0)?;
        // Set COM Pins Hardware Configuration
        self.send_cmds(&[0xDA, 0x12])?;
        // Set Contrast Control
        self.send_cmds(&[0x81, 0x2F])?;
        // Set Pre-charge Period
        self.send_cmds(&[0xD9, 0x22])?;
        // Set VCOM Deselect Level
        self.send_cmds(&[0xDB, 0x35])?;
        // Set Charge Pump
        self.send_cmds(&[0xAD, 0x8B])?;
        // Display ON
        self.send_cmd(0xAF)?;

        Ok(())
    }
    // ...
}

flush()

impl<I2C, E> Sh1107g<I2C>
where
    I2C: embedded_hal::i2c::I2c<Error = E>,
{
    pub fn flush(&mut self) -> Result<(), E> {
        let page_count = crate::DISPLAY_HEIGHT as usize / 8;
        let page_width = crate::DISPLAY_WIDTH as usize;

        for page in 0..page_count {
            self.send_cmd(0xB0 + page as u8)?;
            self.send_cmd(0x00)?;
            self.send_cmd(0x10)?;

            let start_index = page * page_width;
            let end_index = start_index + page_width;
            let page_data = &self.buffer[start_index..end_index];

            // 0x40 (データコントロールバイト) + 128バイトのデータ
            let mut buf: heapless::Vec<u8, 129> = heapless::Vec::new();
            buf.push(0x40).unwrap();
            buf.extend_from_slice(page_data).unwrap();

            self.i2c.write(self.address, &buf)?;
        }
        Ok(())
    }
    // ...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions