閉じる

UI言語[UI Language]

記事自体は翻訳されません! 記事によって英語版があったりなかったりします。翻訳がある記事は文頭に記載があるよ!
Each articles themselves will not be translated by this setting. Some of article has translation and some of them doesn't. You will notice if the article has its translation by its preamble!

テーマ[Theme]


アイキャッチ画像

U8g2ライブラリで2つ目のI2Cを使おうとしたところ、write(): NULL TX buffer pointer


ESP32でU8g2ライブラリを使ってSH1106 OLEDディスプレイを使おうとしたところ、表題のエラーが発生してそこで停止してしまうようになりました。いわゆるヌルポですね。

発生条件 espressifのバージョン

どうやらespressifのバージョンを上げたタイミングで発生したようでした。platformio.iniのplatform指定を下記にすると発生しません。

[env:esp32dev]
platform = espressif32@5.0.0

これを例えば6.8.1などに書き換えると発生します。おそらく5.1.0あたりまではOK、それより新しいバージョンではダメみたいです。確かに、エラーが発生する方のWire.hではwriteendTransmissiontxtBufferという内部変数のNULLチェックが追加されており、そこで引っかかっているのが分かります。

とは言え、古いバージョンのespressifをずっと使うというのも心配です。


原因 TwoWireオブジェクトの重複

U8g2はArduinoであらゆるディスプレイを扱えるすごいライブラリなのですが初期化方法が非常にユニーク(歪曲表現)で、液晶(チップ)の種類、解像度、バッファリング方式、使用するI2CのチャンネルやSPIなどを組み合わせた百もあるコンストラクタから適切なものを探し出さなければ使えません選んで使います。
SH1106などのI2Cディスプレイの場合は、サフィックスが_SW_I2C(ソフトウェアI2C)、_HW_I2C(ハードウェアI2C)、_2ND_HW_I2C(ハードウェアの2番目のI2C)のコンストラクタを使います。

今回自分はESP-WROOM-32をI2Cスレーブデバイスとして動作させつつ、自身でもI2C接続したSH1106を使いたかったので、スレーブデバイスとしての動作にはメインのハードウェアI2C(ESP32だとディフォルトではGPIO21と22)を使いつつ、セカンダリのI2C(ディフォルトでは)でSH1106を使いたかったので、U8G2_SH1106_128X64_NONAME_F_2ND_HW_I2Cメソッドを使っていました。
このコンストラクタだとU8g2のI2Cのメソッド呼び出しがWireの代わりにWire1に対して行われるようになりますU8x8lib.cppu8x8_byte_arduino_2nd_hw_i2cメソッド内)。

一方、自分の方ではセカンダリのI2Cで使うモジュールが他にもあるため、U8g2の初期化前にセカンダリのI2Cを初期化するコードを書いていました。ざっくり下記のような感じです。

// セカンダリのI2C(=1)を指定
TwoWire subI2C = TwoWire(1);

// SDA=GPIO32, SCL=GPIO33で初期化
subI2C.begin(32, 33, 400000UL);

これによりインスタンスとしては別でありながら、いずれもセカンダリのI2Cを指すTwoWire(1)が2つ存在することになり、こちらで呼んだbegin及びU8g2が呼ぶbeginTwoWire.beginが2回呼び出されます。すると2回目のbeginはすでに2番目のハードウェアI2Cに対しての初期化が済んでいるためエラーになり、バッファの確保等が行われません。

// Slave Begin
bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency)
{
	(中略)
end:
	if (!started) freeWireBuffer();

これがそれ以降のWire.writeメソッドなどで表題の 〈NULL TX buffer pointer〉エラーに繋がってしまうようです。


解決方法 同じI2Cバスを示す別のTwoWireインスタンスを生成しない

というわけで原因がわかったので、同じバスに対してbeginが2度呼ばれてしまわないよう修正します。具体的には先程のコードを、下記のように修正しました。

// Wire.hで用意されている規定の2個目のI2Cを指すTwoWireインスタンスを指すよう変更
TwoWire* subI2C = &Wire1;

// そのインスタンスに対してbeginを呼び出す
subI2C->begin(32, 33, 400000UL);

これでU8g2でのWire1に対するbeginが正しく失敗するようになり、バッファが確保されないままのTwoWireインスタンスが存在することがなくなり、ヌルポが出なくなります。

下記のスレッドなんかはおそらく同じ症状で悩んでる人の投稿ですね。

というわけで、

戒め TwoWireオブジェクトを生成してはいけない

Wire.hで定義済みのWireまたはWire1を使うこと。

それにしてもU8g2のコンストラクタ選択方式はあまりに使いづらい…… なにかしらのメリットがあるんだろうとは思うけれども。

いじょ。
そういえばこれが今年最初の記事かも。もう3月だけどあけおめ!

この記事のタグ[This article is filed under]: トラブル[Trouble] | ESP32[ESP32] | Arduino[Arduino] | 電子工作[Electronics]


この記事はここで終わりです。
読んでいただきありがとうございました。
良かったらシェアしてね!

That's all for this article. Thank you for your reading.
Please share this if you like it!

Twitter | Reddit | Facebook | Pinterest | Pocket

前の記事[Prev Post]

前の記事のアイキャッチ画像

Kicadの回路図エディターでピン選択しないようにする方法