Skip to content

Commit

Permalink
SPI: support for HW SPI indices
Browse files Browse the repository at this point in the history
  • Loading branch information
vickash committed Sep 29, 2024
1 parent 2892fe8 commit aa062ae
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 87 deletions.
8 changes: 4 additions & 4 deletions lib/denko/board/spi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ def spi_header(select_pin, write, read, frequency, mode, bit_order)
raise ArgumentError, "error in SPI frequency: #{frequency.inspect}"
end

# Get the generic part of the SPI header.
# Get the generic part of the SPI header.
header = spi_header_generic(select_pin, write, read, mode, bit_order)

# Generic header + packed frequency = hardware SPI header.
header + pack(:uint32, frequency)
end

# CMD = 26
def spi_transfer(select_pin, write: [], read: 0, frequency: nil, mode: nil, bit_order: nil)
def spi_transfer(spi_index, select_pin, write: [], read: 0, frequency: nil, mode: nil, bit_order: nil)
raise ArgumentError, "no bytes given to read or write" if (read == 0) && (write.empty?)
raise ArgumentError, "select_pin cannot be nil when reading or listening" if (read != 0) && (select_pin == nil)

Expand All @@ -51,12 +51,12 @@ def spi_transfer(select_pin, write: [], read: 0, frequency: nil, mode: nil, bit_
end

# CMD = 27
def spi_listen(select_pin, read: 0, frequency: nil, mode: nil, bit_order: nil)
def spi_listen(spi_index, select_pin, read: 0, frequency: nil, mode: nil, bit_order: nil)
raise ArgumentError, 'no bytes to read. Give read: argument > 0' unless (read > 0)
raise ArgumentError, "select_pin cannot be nil when reading or listening" if (select_pin == nil)

header = spi_header(select_pin, [], read, frequency, mode, bit_order)

self.write Message.encode command: 27,
pin: select_pin,
aux_message: header
Expand Down
1 change: 1 addition & 0 deletions lib/denko/spi.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Denko
module SPI
autoload :BusCommon, "#{__dir__}/spi/bus_common"
autoload :Bus, "#{__dir__}/spi/bus"
autoload :BitBang, "#{__dir__}/spi/bit_bang"
module Peripheral
Expand Down
36 changes: 0 additions & 36 deletions lib/denko/spi/bit_bang.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ module Denko
module SPI
class BitBang
include Behaviors::MultiPin
include Behaviors::BusController
include Behaviors::Reader
include Behaviors::Lifecycle

before_initialize do
Expand Down Expand Up @@ -36,40 +34,6 @@ def listen(select_pin, read: 0, frequency: nil, mode: 0, bit_order: :msbfirst)
board.spi_bb_listen select_pin, clock: pins[:clock], input: pins[:input],
read: read, mode: mode, bit_order: bit_order
end

# Uses regular Board#spi_stop since listeners are shared.
def stop(pin)
board.spi_stop(pin)
end

# Delegate these to board so peripherals can initialize their select pins.
def set_pin_mode(*args)
board.set_pin_mode(*args)
end

def convert_pin(pin)
board.convert_pin(pin)
end

# Add peripheral to self and the board. It gets callbacks directly from the board.
def add_component(component)
# Ignore components with no select pin. Mostly for APA102.
return unless component.pin

pins = components.map { |c| c.pin }
if pins.include? component.pin
raise ArgumentError, "duplicate select pin for #{component}"
end

components << component
board.add_component(component)
end

# Remove peripheral from self and the board.
def remove_component(component)
components.delete(component)
board.remove_component(component)
end
end
end
end
40 changes: 11 additions & 29 deletions lib/denko/spi/bus.rb
Original file line number Diff line number Diff line change
@@ -1,42 +1,24 @@
module Denko
module SPI
class Bus
include Behaviors::BusController
include Behaviors::Reader
include BusCommon

# Board expects all components to have #pin.
attr_reader :pin

# Forward some methods directly to the board.
extend Forwardable

# Forward SPI methods with prefixed names.
def_delegator :board, :spi_transfer, :transfer
def_delegator :board, :spi_listen, :listen
def_delegator :board, :spi_stop, :stop

# Forward pin control methods with same names for board proxying.
def_delegator :board, :convert_pin
def_delegator :board, :set_pin_mode

# Add peripheral to self and the board. It gets callbacks directly from the board.
def add_component(component)
# Ignore components with no select pin. Mostly for APA102.
return unless component.pin

pins = components.map { |c| c.pin }
if pins.include? component.pin
raise ArgumentError, "duplicate select pin for #{component}"
end
def spi_index
@spi_index ||= params[:spi_index] || params[:index] || 0
end

components << component
board.add_component(component)
# Prepend spi_index to these and forward to the board.
def transfer(*args, **kwargs)
args.unshift(spi_index)
board.spi_transfer(*args, **kwargs)
end

# Remove peripheral from self and the board.
def remove_component(component)
components.delete(component)
board.remove_component(component)
def listen(*args, **kwargs)
args.unshift(spi_index)
board.spi_listen(*args, **kwargs)
end
end
end
Expand Down
36 changes: 36 additions & 0 deletions lib/denko/spi/bus_common.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module Denko
module SPI
module BusCommon
include Behaviors::BusController
include Behaviors::Reader
extend Forwardable

# Add peripheral to self and the board. It gets callbacks directly from the board.
def add_component(component)
# Ignore components with no select pin. Mostly for APA102.
return unless component.pin

pins = components.map { |c| c.pin }
if pins.include? component.pin
raise ArgumentError, "duplicate select pin for #{component}"
end

components << component
board.add_component(component)
end

# Remove peripheral from self and the board.
def remove_component(component)
components.delete(component)
board.remove_component(component)
end

# Forward pin control methods to the board, for select pin setup.
def_delegator :board, :convert_pin
def_delegator :board, :set_pin_mode

# If a component calls #stop, that's just a call to Board#spi_stop giving its select pin.
def_delegator :board, :spi_stop, :stop
end
end
end
24 changes: 12 additions & 12 deletions test/board/spi_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

class APISPITest < Minitest::Test
include TestPacker

def connection
@connection ||= ConnectionMock.new
end

def board
@board ||= Denko::Board.new(connection)
end

def test_spi_modes
# Start with mode = 0.
args = [nil, [], 0, 1000000, 0, :msbfirst]
Expand All @@ -31,7 +31,7 @@ def test_spi_modes
# Invalid mode = 4.
assert_raises(ArgumentError) { board.spi_header(*args[3] = 4) }
end

def test_spi_lsbfirst
args = [nil, [], 0, 1000000, 0, :lsbfirst]
assert_equal (pack :uint8, 0b00000000), board.spi_header(*args)[0]
Expand All @@ -44,12 +44,12 @@ def test_spi_frequency
args[3] = 8000000
assert_equal (pack :uint32, 8000000), board.spi_header(*args)[3..6]
end

def test_spi_too_many_bytes
assert_raises(ArgumentError) { board.spi_header(read: 256) }
assert_raises(ArgumentError) { board.spi_header(write: Array.new(256){0})}
end

def test_spi_no_bytes
assert_raises(ArgumentError) { board.spi_transfer(3, read: 0) }
assert_raises(ArgumentError) { board.spi_listen(3, read: 0) }
Expand All @@ -58,34 +58,34 @@ def test_spi_no_bytes
def test_spi_bad_frequency
assert_raises(ArgumentError) { board.spi_transfer(3, read: 0, frequency: "string") }
end

def test_spi_transfer
board
bytes = [1,2,3,4]
header = board.spi_header(3, bytes, 4, 8000000, 2, :lsbfirst)
aux = header + pack(:uint8, bytes)
mock = Minitest::Mock.new.expect :call, nil,
[Denko::Message.encode(command: 26, pin: 3, aux_message: aux)]

board.stub(:write, mock) do
args = { write: [1,2,3,4], read: 4, bit_order: :lsbfirst, frequency: 8000000, mode: 2 }
board.spi_transfer(3, **args)
board.spi_transfer(0, 3, **args)
end
mock.verify
end

def test_spi_listen
board
header = board.spi_header(3, [], 8, 1000000, 0, :lsbfirst)
mock = Minitest::Mock.new.expect :call, nil,
[Denko::Message.encode(command: 27, pin: 3, aux_message: header)]

board.stub(:write, mock) do
board.spi_listen(3, read: 8, bit_order: :lsbfirst)
board.spi_listen(0, 3, read: 8, bit_order: :lsbfirst)
end
mock.verify
end

def test_spi_stop
board
mock = Minitest::Mock.new.expect :call, nil, [Denko::Message.encode(command: 28, pin: 3)]
Expand Down
12 changes: 6 additions & 6 deletions test/spi/bus_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class TempSpiPeripheral
def initialize(pin)
@pin = pin
end

attr_reader :pin
end

Expand All @@ -14,28 +14,28 @@ def board
end

def part
@bus ||= Denko::SPI::Bus.new(board: board)
@bus ||= Denko::SPI::Bus.new(board: board, index: 5)
end

PIN = 9
OPTIONS = { read: 2, frequency: 800000, mode: 2, bit_order: :lsbfirst }

def test_transfer
mock = Minitest::Mock.new.expect :call, nil, [PIN], **OPTIONS
mock = Minitest::Mock.new.expect :call, nil, [5, PIN], **OPTIONS
board.stub(:spi_transfer, mock) do
part.transfer(PIN, **OPTIONS)
end
mock.verify
end

def test_listen
mock = Minitest::Mock.new.expect :call, nil, [PIN], **OPTIONS
mock = Minitest::Mock.new.expect :call, nil, [5, PIN], **OPTIONS
board.stub(:spi_listen, mock) do
part.listen(PIN, **OPTIONS)
end
mock.verify
end

def test_stop
mock = Minitest::Mock.new.expect :call, nil, [PIN]
board.stub(:spi_stop, mock) do
Expand Down

0 comments on commit aa062ae

Please sign in to comment.