🚥

Rubyのコードで理解するシフトレジスタ(74HC595)の挙動

2023/01/02に公開約1,800字

シフトレジスタは容量1バイトのストレージ

class Storage
  attr_writer :input
  attr_reader :memory

  def initialize
    @values = Array.new(8) { 0 }
  end

  def shift
    @values = [@input, *@values].take(@values.size)
  end

  def commit
    @memory = @values.clone
  end

  def to_i
    [@memory.join].pack("B*").ord
  end

  def inspect
    "%08b (%d)" % [to_i, to_i]
  end
end

だと思っとけばOK

一度に書き込めるのは1ビットだけ

S = Storage.new
S.input = 1
S.shift
S.commit
S.memory # => [1, 0, 0, 0, 0, 0, 0, 0]

shift するたびに input を配列に入れる
commit で memory に反映する

続けて 0 を入れる

S.input = 0
S.shift
S.commit
S.memory # => [0, 1, 0, 0, 0, 0, 0, 0]

[1, ...] がずれて [0, 1, ...] となった

8回繰り返す

170を保存する例
8.times do |i|
  S.input = 170[i]
  S.shift
  S.commit
  S.to_i # => 32, 144, 72, 164, 82, 169, 84, 170
end
S        # => 10101010 (170)

8個分詰めてやっと1バイト完成する

シフト中の状態は隠したい

8.times do |i|
  S.input = 123[i]
  S.shift
end
S    # => 10101010 (170)
S.commit
S    # => 01111011 (123)

shift 後に commit するのをやめる
そして終わったタイミングでのみ commit する
そうすれば一瞬で切り替わったように見える[1]
つまりシフトレジスタを参照している側からは1バイト毎に来ているように見える

8回ループするのが面倒

def shift_out(value)
  8.times do |i|
    S.input = value[i]
    S.shift
  end
end

そこだけメソッド化しておく
これで1バイトが1行で送れるようになった

shift_out(204)
S    # => 01111011 (123)
S.commit
S    # => 11001100 (204)

メリット

1ビットずつ溜めることで1バイト分に見えるようになった
本来1バイトを送るのにデータ用のGPIOが8本いるがこれなら1本で済む

デメリット

速度を犠牲にしている
とはいえ超速いし次のデータを用意してから切り替えているので遅さを体感するのは難しい

実際のコードとの対応

input = 1
digitalWrite(SER, 1);
shift
digitalWrite(SRCLK, LOW);
digitalWrite(SRCLK, HIGH);
shift_out(170)
shiftOut(SER, SRCLK, LSBFIRST, 170);
commit
digitalWrite(RCLK, LOW);
digitalWrite(RCLK, HIGH);
脚注
  1. 74HC164 にはこの機能がないためシフト中の状態が丸見えになるらしい ↩︎

Discussion

ログインするとコメントできます