-
Notifications
You must be signed in to change notification settings - Fork 245
Open
Description
タイトル
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-graphics
sh1107g-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(())
}
// ...
}
Metadata
Metadata
Assignees
Labels
No labels