SVX日記
2014-12-24(Wed) 敢えてオーバフローしちゃうリングバッファ
先日からXBeeをAPIモードで使うべく、ごちゃごちゃとやっていたのだが、APIモードだと不特定のタイミングでドカッとAPIデータフレームが到着してしまうので、割り込みを使ってシリアル受信しないと、取りこぼしまくってしまうことに気がついた。
割り込みを使って受信するならば、リングバッファしかないよな、と思ったのだが、XBeeのAPIデータフレームは結構大きく、間欠動作、かつ、メモリ効率を考えると、バッファオーバフローの発生を考慮しないわけにはいかない。しかし、どのように考慮するのか? オーバフローを検知して、受信を抑制すれば、新しいフレームを取りこぼしてしまう。ならば、古いデータから捨てていくべきか? いずれにせよ、どちらかをあきらめる必要がある。
考えた末、今回の結論は「オーバフローを考慮しない」となった。読み書きのポインタは丸めずに走らせておいて、リングバッファにアクセスする時だけ、剰余を取ってからアクセスする。まさに「オーバフローを考慮しない」だけ。判断が必要ないので動作も軽い。
これにより、トータルで受け取るバイト数をそのままに、化けたデータや、重複して同じデータフレームを読んでしまう、という仕様となる。言い方を変えると、スタートデリミタとチェックサムの効用により、途中のデータフレームは取りこぼしてしまうが、最後に送信されたデータフレームは正しく受け取れる、という仕様となる。いいじゃん。
#!/usr/bin/env ruby
class LappedRingBuffer
attr_reader :buf
def initialize(size = 16)
@size = size
@buf = []; @size.times {
@buf << '..'
}
@wp = 0; @rp = 0
end
def write(dat)
@buf[@wp % @size] = dat
@wp += 1
end
def read
if(@rp != @wp)
r = @buf[@rp % @size]
@rp += 1
end
r
end
end
buf = LappedRingBuffer.new(16)
p buf.buf
frame = 'A'; 4.times {
(0...10).each {|d| # write n frames
buf.write('%s%d' % [frame, d])
}
frame.next!
}
p buf.buf
dat = []; while(d = buf.read)
dat << d
end
p dat
["..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", ".."]
["D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "C4", "C5", "C6", "C7", "C8", "C9", "D0", "D1"]
["D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "C4", "C5", "C6", "C7", "C8", "C9", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "C4", "C5", "C6", "C7", "C8", "C9", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9"]