2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Elixir(AtomVM)でI2Cを使ってLCD制御 〜 アンチパターンもあるよ 〜

Last updated at Posted at 2025-11-29

ElixirのコードでESP32S3(LCD)を動かすことを目標にするコラムです
今回はExAtomVMを使います

開発環境

  • Ubuntu 24.04
  • elixir 1.17.1-otp-27
  • erlang 27.2.1

実行イメージ

image.png

焼き方の前提知識&プロジェクト作成方法

LCD 参考資料

  • AIを使って下調べ

AIをそのまま信じるのは危険です
場合によってはハードを壊します

AIが書いたソース

これは動きません 文法が間違っています
defmodule GroveLCD do
  # I2Cアドレス定義
  @lcd_addr 0x3E
  @rgb_addr 0x62

  # コマンド定義
  @lcd_cmd_mode 0x80
  @lcd_data_mode 0x40

  # バックライト用レジスタ
  @reg_red 0x04
  @reg_green 0x03
  @reg_blue 0x02
  @reg_mode1 0x00
  @reg_output 0x08

  @doc """
  初期化と「Hello ExAtom!」の表示
  """
  def start do
    # 1. I2Cバスの初期化 (ピン番号は使用するボードに合わせて変更してください)
    # ESP32のデフォルト: sda=21, scl=22
    {:ok, i2c} = I2C.open(sda: 21, scl: 22, speed_hz: 100_000)

    # 2. LCDとRGBのセットアップ
    init_lcd(i2c)
    init_rgb(i2c)

    # 3. テスト表示
    set_rgb(i2c, 0, 255, 0) # 緑色にする
    print(i2c, "Hello ExAtomVM!")
    
    # 2行目に移動して表示する場合の例
    # set_cursor(i2c, 0, 1) 
    # print(i2c, "Elixir works!")
  end

  # --- 内部関数: LCD制御 ---

  def init_lcd(i2c) do
    # I2C経由での初期化シーケンス (データシートに基づく)
    Process.sleep(50)
    send_cmd(i2c, 0x38) # Function set: 2 lines
    Process.sleep(5)
    send_cmd(i2c, 0x38)
    Process.sleep(1)
    send_cmd(i2c, 0x0C) # Display ON, Cursor OFF
    send_cmd(i2c, 0x01) # Clear Display
    Process.sleep(5)
    send_cmd(i2c, 0x06) # Entry Mode
  end

  def print(i2c, str) do
    String.to_charlist(str)
    |> Enum.each(fn char -> send_data(i2c, char) end)
  end

  defp send_cmd(i2c, cmd) do
    I2C.write(i2c, @lcd_addr, <<@lcd_cmd_mode, cmd>>)
  end

  defp send_data(i2c, data) do
    I2C.write(i2c, @lcd_addr, <<@lcd_data_mode, data>>)
  end

  # --- 内部関数: RGBバックライト制御 ---

  def init_rgb(i2c) do
    # バックライトICのリセットと有効化
    I2C.write(i2c, @rgb_addr, <<@reg_mode1, 0x00>>)
    I2C.write(i2c, @rgb_addr, <<@reg_output, 0xAA>>)
  end

  def set_rgb(i2c, r, g, b) do
    I2C.write(i2c, @rgb_addr, <<@reg_red, r>>)
    I2C.write(i2c, @rgb_addr, <<@reg_green, g>>)
    I2C.write(i2c, @rgb_addr, <<@reg_blue, b>>)
  end
end

修正して動いたソース

I2C.writeは存在してません
I2C.write_bytesが正解です

defmodule AvmLcd do
  @moduledoc """
  Documentation for `AvmLcd`.
  """

  # I2Cアドレス定義
  @lcd_addr 0x3E
  @rgb_addr 0x62

  # コマンド定義
  @lcd_cmd_mode 0x80
  @lcd_data_mode 0x40

  # バックライト用レジスタ
  @reg_red 0x04
  @reg_green 0x03
  @reg_blue 0x02
  @reg_mode1 0x00
  @reg_output 0x08

  def start do
    Process.sleep(1000)
    IO.inspect("YMN test")

    # # --- 修正箇所: XIAO ESP32S3 のピン番号 (SDA=5, SCL=6) ---
    i2c = I2C.open([{:scl, 6}, {:sda, 5}, {:clock_speed_hz, 1_000_000}])

    IO.inspect("OK")
    # # 初期化
    init_lcd(i2c)

    init_rgb(i2c)
    IO.inspect("OK2-1")

    set_rgb(i2c, 100, 100, 200)

    # 文字を表示
    print(i2c, "YMN Test")
  end

  # --- 以下は変更なし ---
  def init_lcd(i2c) do
    # 1. Function Set (0x38: DL=1: 8bit, N=1: 2R, F=0: 5x7)
    #    - AIP31068Lの Function Set コマンドは 0b0011NFXX, N=1, F=0 の場合 0b001110XX = 0x38
    send_cmd(i2c, 0x38)
    # Wait for more than 40us (1msで十分)
    Process.sleep(1)

    # 2. Display ON/OFF Control (0x0C: D=1, C=0, B=0)
    send_cmd(i2c, 0x0C)
    # Wait for more than 39us (1msで十分)
    Process.sleep(1)

    # 3. Display Clear (0x01)
    #send_cmd(i2c, 0x01)
    # Wait for more than 1.53ms (2msで十分)
    #Process.sleep(2)

    # 4. Entry Mode Set (0x06: I/D=1, S=0)
    #send_cmd(i2c, 0x06)
    # 念のため
    #Process.sleep(1)
  end

  # 代替案: パターンマッチングで1バイトずつ取り出す
  def print(i2c, <<char, rest::binary>>) do
    send_data(i2c, char)
    print(i2c, rest)
  end

  def print(_i2c, "") do
    :ok
  end

  @compile {:no_warn_undefined, [I2C]}
  defp send_cmd(i2c, cmd) do
    I2C.write_bytes(i2c, @lcd_addr, <<@lcd_cmd_mode, cmd>>)
  end

  @compile {:no_warn_undefined, [I2C]}
  defp send_data(i2c, data) do
    I2C.write_bytes(i2c, @lcd_addr, <<@lcd_data_mode, data>>)
  end

  @compile {:no_warn_undefined, [I2C]}
  def init_rgb(i2c) do
    I2C.write_bytes(i2c, @rgb_addr, <<@reg_mode1, 0x00>>)
    I2C.write_bytes(i2c, @rgb_addr, <<@reg_output, 0xAA>>)
  end

  @compile {:no_warn_undefined, [I2C]}
  def set_rgb(i2c, r, g, b) do
    I2C.write_bytes(i2c, @rgb_addr, <<@reg_red, r>>)
    I2C.write_bytes(i2c, @rgb_addr, <<@reg_green, g>>)
    I2C.write_bytes(i2c, @rgb_addr, <<@reg_blue, b>>)
  end
end

これで動きました

俺は破壊王になる 〜 アンチパターン 〜

いじりすぎてLCDを破壊しました
AIに文字が薄いから対策したいと伝えました
いろいろ試して解決しなかったので、Vccを3.3Vから5Vの提案がありました
ここでI2Cの制御が一部使えなくなってバックライトがつかなくなりました
LCD本体のバージョンがいくつかあって最新版のLCDは5Vに対応していた
僕の使ったものは1つ前のバージョンでした、非対応で破壊した恐れあり

AIをそのまま信じるのは危険です
ハードを壊します

AIをそのまま信じるのは危険です
ハードを壊します

AIをそのまま信じるのは危険です
ハードを壊します

大事なので三回書きました

  • AIを使って調査の反対はしません
  • 自分の知識を超える範囲は慎重に扱いましょう
    •  今回はLCDの使い方です
  • AIのご利用は計画的に
  • 大事な部分は人間が最終判断をすること
  • AIに責任転嫁はやめましょう
    • AIを使うのは自己責任です

AIコーディングはやっていますが…
本人以上の成果は厳しいです
僕はElixirの部分はなんとかなりますが、さすがに今時点ではハードは厳しいです

なので「AIを使えば簡単にコーディングできるでしょ?」は疑問をもっています
場合によって取り返しのつかないことが起きても不思議でないです
このコラムで一番伝えたい事です

〜 そして伝説へ 〜

まだLCDが元気だったころの姿です

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?