SH1107G OLED does not work in avr-hal environment
タイトル
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-halとembedded-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-graphicssh1107g-rs(自作ドライバ)
- Rust (
実施したデバッグと分析
-
初期化シーケンスの確認:
- 当初、Pythonドライバのロジックを元に初期化シーケンスを実装しました。
- しかし、動作しなかったため、確実に動作する
U8g2ライブラリのソースコード(u8x8_sh1107_128x128_init_seq)を詳細に解析しました。
-
U8g2コマンドの再現:U8g2の初期化シーケンスを参考に、SH1107Gのデータシートと照らし合わせながら、コマンドとデータバイトの組み合わせをRustコードで忠実に再現しました。- 特に、以下の点を再確認し、実装に反映しました。
- コマンドバイトの前に
0x80のコントロールバイトを付加する。 - 複数コマンドを一度に送信する際に、各コマンドの前に
0x80を付加する。
- コマンドバイトの前に
-
flush()関数の確認:embedded-graphicsのバッファ内容をディスプレイに送信するflush()関数も確認しました。- ページ(0〜15)ごとにループし、ページアドレスとカラムアドレスを設定する。
- ページデータ(128バイト)を送信する際に、データの前に
0x40のコントロールバイトを付加する。
-
最終的なコードの検証:
- これらの修正を適用した後の最新の
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(())
}
// ...
}
動作することを確認いたしました。 しかし、画面の表示は依然として砂嵐のようで、正確に文字を表示する事はできておりません。
Hi, please post your issue in english, otherwise there is little chance that people here are able to help :)
I managed to do it by logging and picking up command differences with a self-made crate ( https://github.com/p14c31355/dvcdbg ).