🚥

Rubyのコードで理解するシフトレジスタの基本動作

2023/01/02に公開

シフトレジスタは容量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

一度に書き込めるのは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 = value
digitalWrite(SER, value);
shift
digitalWrite(SRCLK, LOW);
digitalWrite(SRCLK, HIGH);
shift_out(value)
shiftOut(SER, SRCLK, LSBFIRST, value);
commit
digitalWrite(RCLK, LOW);
digitalWrite(RCLK, HIGH);
脚注
  1. 74HC164 にはこの機能がないためシフト中の状態が丸見えになる。 ↩︎

Discussion