Skip to content

Instantly share code, notes, and snippets.

@mnishiguchi
Last active March 2, 2024 09:11
Show Gist options
  • Save mnishiguchi/437d5f172804e2a0c778a4e3d4c72a1f to your computer and use it in GitHub Desktop.
Save mnishiguchi/437d5f172804e2a0c778a4e3d4c72a1f to your computer and use it in GitHub Desktop.
binary-clock--ja_20240211_1

Frankさんのカスタム二進数時計基板

Mix.install([
  {:circuits_spi, "~> 2.0 or ~> 1.0"},
  {:kino, "~> 0.12.2"}
])

基板の設計

少しでもサプライチェーン問題の少ない優れた LED ドライバーを見つけられるかどうかを確認するため、独自の二進数時計を構築してみました。JLCPCB で部品の入手可能性を調査しました。 以前から JLCPCB を利用したことがありましたが、単純なものであれば驚くほど安価で構築できます。 部品リストは https://jlcpcb.com/parts にあります。

Titan Micro は多くの LED ドライバーを製造しています。 TM1620 は、同社の製品の中で特にシンプルなものの 1 つです。48 個の LED を駆動できるので、二進数時計を構築するのには十分足ります。

データシートは、以下のURLからからダウンロードできます。

二つ目のデータシートは英語訳が見つかりましたが、翻訳者は不明です。

私は中国語が読めませんが、オリジナルの TM1620 データシートは、十分試してみる価値があるという印象を持ちました。 18 ページにある回路は、LED がどのように接続されているかを示しているため、非常に役に立ちました。 格子状に配線されています。 「SEGn」線が行(rows)で「GRIDn」線が列(columns)になります。

私がどのようにLED の接続したかについては、以下のURLでご覧いただけます。

この回路図は後に重要となります。

基板を起動

Raspberry Pi は SPI バスを介して TM1620 に接続されています。 まず最初に、Circuits.SPI を使用してバスを開ます。

TM1620 に関して最も注意が必要なのは、バイトの並べ方がリトルエンディアンとして送信されることです。 これはセクション 8 の図 5 に示されています。SPI は通常、データをビッグ エンディアンとして送信します。 幸いなことに、Circuits.SPI には、最下位ビットを最初に送信するための lsb_first オプションがあります。

次に注意が必要な点は、CLK 線が Low から High になる時にデータビット (DIN) が抽出されることです。 これは図 5 で確認できます。各ビットがネットワーク上にある時にクロックが上がるからです。 これはSPI バスのモードと呼ばれる のものです。 SPI モードには 4 つあります。 私が過去に使用したほとんどの部品は SPI モード 0 でした。 Circuit.SPI の初期設定もSPI モード 0なっています。 TM1620 では SPI モード 3 を使用します。詳しくは、Circuit.SPI.open/2ドキュメントをご覧ください。

SPI バスを開くコードは次のとおりです。

alias Circuits.SPI

{:ok, spi} = SPI.open("spidev0.0", mode: 3, lsb_first: true)

ハードウェアがLSB-first モードをサポートしていない場合、unsupported mode bits 8 というエラーメッセージが出力されることがあります。 Circuit.SPIがそれを自動的に処理するので、無視しても大丈夫です。

SPI通信

次のステップでは、TM1620 にデータを送信して LED を点灯させます。 実施方法はデータシートの 7 ページにある流れ図に記載されています。

  1. 表示モードを設定するコマンドを送信
  2. データメモリに書き込むコマンドを送信
  3. データバイトを送信 (各LEDの入り切り)
  4. LEDドライバーを作動させるコマンドを送信

表示モードを設定するコマンドを送信

表示モードは 3 ページの表に記載されています。6 列の LED がありますので「6 digit 8 segments」になるように設定します。要は、0b00000010(数字の2)を送信するということです。英訳のデータシートでは各列を「digit」と呼んでいるので、それに合わせて「6 digit 8 segments」としましたが、データシートには打ち間違いがあります。

SPI.transfer(spi, <<0b00000010>>)

setup: unsupported mode bits 8 というエラーが出るかもしれませんが、気にしないでください。Circuits.SPI は、リトルエンディアンでの送信をサポートしないハードウェアを自動的に対応します。

データメモリに書き込むコマンドを送信

次に、データメモリに書き込むコマンドを送信します。 流れ図では0x40を送信するようになっており、これは 3 ページでも確認できます。

SPI.transfer(spi, <<0x40>>)

データバイトを送信

次に、アドレス0にバイトを格納するコマンドを送信し、それからLED の状態を送信します。流れ図では、これを行うコマンドが0xc0であると記されており、これは 4 ページで確認できます。4 ページの下部には、LED バイトがどのように配置されるかを示す表があります。

簡単にいうと、6 列 (別名「digits」) の場合、最初のバイトのビットはその列内の使用可能な 最大8 個の LED です。 2 番目のバイトは0です。

例えば、二進数時計に「1, 2, 3, 4, 5, 6」を表示したい場合は、次のように送信します。

SPI.transfer(spi, <<0xC0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0>>)

LEDドライバーを作動させるコマンドを送信

最後のステップは、TM1620 を作動させ、明るさを設定するコマンドを送信することです。 これは、3 ページの下部の表に示されています。最も暗い値は0x88です。 最も明るい値は 0x8f です。 0x80 は切りです。

SPI.transfer(spi, <<0x88>>)

LEDが点灯しているはずです。 期待どおりの結果が得られることを願っています。 変更したい場合は、上記の 0xc0 コマンドを変更してください。

簡単な二進数時計をモジュールにまとめてみる

時刻を設定するための短いモジュールは次のとおりです。

defmodule Clock do
  @spec show(SPI.spi_bus(), 0..23, 0..59, 0..59, 0..7) :: :ok | {:error, any()}
  def show(spi, hours, minutes, seconds, brightness \\ 0) do
    with {:ok, _} <- SPI.transfer(spi, <<0x02>>),
         {:ok, _} <- SPI.transfer(spi, <<0x40>>),
         {:ok, _} <- SPI.transfer(spi, [0xC0, to_bcd(hours), to_bcd(minutes), to_bcd(seconds)]),
         {:ok, _} <- SPI.transfer(spi, <<0x88 + brightness>>) do
      :ok
    end
  end

  @spec off(SPI.spi_bus()) :: :ok | {:error, any()}
  def off(spi) do
    SPI.transfer(spi, <<0x80>>)
  end

  @spec to_bcd(0..100) :: binary()
  def to_bcd(value) when value >= 0 and value < 100 do
    <<div(value, 10), 0, rem(value, 10), 0>>
  end
end

簡単なことを試してみましょう:

Clock.show(spi, 12, 34, 56)

こちらは時計をアニメーション化したものです。見た目が面白くなるように、ランダムな時間から開始し、実際の時計よりもはるかに速く実行されるようにしています。

starting_time = :rand.uniform(86400)
interval_ms = 50

starting_time
|> Stream.iterate(&(&1 + 1))
|> Kino.animate(fn seconds ->
  Process.sleep(interval_ms)

  h = seconds |> div(3600)
  m = seconds |> div(60) |> rem(60)
  s = seconds |> rem(60)

  Clock.show(spi, h, m, s)
  Kino.Markdown.new("Clock: `#{h}:#{m}:#{s}`")
end)
@mnishiguchi
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment