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からからダウンロードできます。
- https://jlcpcb.com/parts/componentSearch?isSearch=true&searchTxt=TM1620
- https://github.com/fhunleth/binary_clock/tree/main/datasheets
二つ目のデータシートは英語訳が見つかりましたが、翻訳者は不明です。
私は中国語が読めませんが、オリジナルの 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
がそれを自動的に処理するので、無視しても大丈夫です。
次のステップでは、TM1620 にデータを送信して LED を点灯させます。 実施方法はデータシートの 7 ページにある流れ図に記載されています。
- 表示モードを設定するコマンドを送信
- データメモリに書き込むコマンドを送信
- データバイトを送信 (各LEDの入り切り)
- 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>>)
最後のステップは、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)
https://hexdocs.pm/circuits_spi/Circuits.SPI.html