Class: Denko::PiBoard

Inherits:
Object
  • Object
show all
Includes:
Behaviors::Subcomponents
Defined in:
lib/denko/piboard_i2c.rb,
lib/denko/piboard_map.rb,
lib/denko/piboard_spi.rb,
lib/denko/piboard_base.rb,
lib/denko/piboard_core.rb,
lib/denko/piboard_tone.rb,
lib/denko/piboard_pulse.rb,
lib/denko/piboard_servo.rb,
lib/denko/piboard_i2c_bb.rb,
lib/denko/piboard_spi_bb.rb,
lib/denko/piboard_version.rb,
lib/denko/piboard_infrared.rb,
lib/denko/piboard_one_wire.rb,
lib/denko/piboard_led_array.rb,
lib/denko/piboard_hardware_pwm.rb

Constant Summary collapse

I2C_ADDRESS_RANGE =

Address ranges 0..7 and 120..127 are reserved. Try each address in 8..119 (0x08 to 0x77).

(0x08..0x77).to_a
DEFAULT_MAP_FILE =
".denko_piboard_map.yml"
LOW =
0
HIGH =
1
PWM_HIGH =
100
REPORT_SLEEP_TIME =
0.001
INPUT_MODES =
[:input, :input_pullup, :input_pulldown]
OUTPUT_MODES =
[:output, :output_pwm, :output_open_drain, :output_open_source]
PIN_MODES =
INPUT_MODES + OUTPUT_MODES
VERSION =
'0.14.0'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(map_yaml_file = nil) ⇒ PiBoard

Returns a new instance of PiBoard.



16
17
18
19
20
21
22
23
# File 'lib/denko/piboard_base.rb', line 16

def initialize(map_yaml_file=nil)
  map_yaml_file ||= Dir.home + "/" + DEFAULT_MAP_FILE
  unless File.exist?(map_yaml_file)
    raise StandardError, "board map file not given to PiBoard#new, and does not exist at #{map_yaml_file}"
  end

  parse_map(map_yaml_file)
end

Instance Attribute Details

#alert_lutObject (readonly)

Returns the value of attribute alert_lut.



7
8
9
# File 'lib/denko/piboard_map.rb', line 7

def alert_lut
  @alert_lut
end

#gpiochip_lookup_optimizedObject (readonly)

Returns the value of attribute gpiochip_lookup_optimized.



7
8
9
# File 'lib/denko/piboard_map.rb', line 7

def gpiochip_lookup_optimized
  @gpiochip_lookup_optimized
end

#mapObject (readonly)

Returns the value of attribute map.



7
8
9
# File 'lib/denko/piboard_map.rb', line 7

def map
  @map
end

#spi_bbsObject (readonly)

Returns the value of attribute spi_bbs.



3
4
5
# File 'lib/denko/piboard_spi_bb.rb', line 3

def spi_bbs
  @spi_bbs
end

Instance Method Details

#analog_listen(pin, divider = 16) ⇒ Object

Raises:

  • (NotImplementedError)


125
126
127
# File 'lib/denko/piboard_core.rb', line 125

def analog_listen(pin, divider=16)
  raise NotImplementedError, "PiBoard#analog_read not implemented"
end

#analog_read(pin, negative_pin = nil, gain = nil, sample_rate = nil) ⇒ Object

Raises:

  • (NotImplementedError)


88
89
90
# File 'lib/denko/piboard_core.rb', line 88

def analog_read(pin, negative_pin=nil, gain=nil, sample_rate=nil)
  raise NotImplementedError, "PiBoard#analog_read not implemented"
end

#analog_write_resolutionObject



14
# File 'lib/denko/piboard_base.rb', line 14

def analog_write_resolution; 8; end

#binary_echo(pin, data = []) ⇒ Object

Raises:

  • (NotImplementedError)


149
150
151
# File 'lib/denko/piboard_core.rb', line 149

def binary_echo(pin, data=[])
  raise NotImplementedError, "PiBoard#binary_echo not implemented"
end

#bound_pinsObject

Keep track of pins bound by non-GPIO peripherals.



124
125
126
# File 'lib/denko/piboard_map.rb', line 124

def bound_pins
  @bound_pins ||= []
end

#convert_pin(pin) ⇒ Object



128
129
130
# File 'lib/denko/piboard_map.rb', line 128

def convert_pin(pin)
  pin.to_i if pin
end

#dac_write(pin, value) ⇒ Object

Raises:

  • (NotImplementedError)


84
85
86
# File 'lib/denko/piboard_core.rb', line 84

def dac_write(pin, value)
  raise NotImplementedError, "PiBoard#dac_write not implemented"
end

#digital_listen(pin, divider = 4) ⇒ Object



121
122
123
# File 'lib/denko/piboard_core.rb', line 121

def digital_listen(pin, divider=4)
  set_listener(pin, :on, {})
end

#digital_read(pin) ⇒ Object



63
64
65
66
67
68
69
70
71
72
# File 'lib/denko/piboard_core.rb', line 63

def digital_read(pin)
  if hardware_pwms[pin]
    state = hardware_pwms[pin].duty_percent
  else
    handle, line = gpio_tuple(pin)
    state = LGPIO.gpio_read(handle, line)
  end
  self.update(pin, state)
  return state
end

#digital_write(pin, value) ⇒ Object



54
55
56
57
58
59
60
61
# File 'lib/denko/piboard_core.rb', line 54

def digital_write(pin, value)
  if hardware_pwms[pin]
    hardware_pwms[pin].duty_percent = (value == 0) ? 0 : 100
  else
    handle, line = gpio_tuple(pin)
    LGPIO.gpio_write(handle, line, value)
  end
end

#finish_writeObject



25
26
27
# File 'lib/denko/piboard_base.rb', line 25

def finish_write
  gpio_handles.each { |h| LGPIO.chip_close(h) if h }
end

#get_reportObject



167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/denko/piboard_core.rb', line 167

def get_report
  report = LGPIO.gpio_get_report
  if report
    if chip = alert_lut[report[:chip]]
      if pin = chip[report[:gpio]]
        update(pin, report[:level])
      end
    end
  else
    sleep 0.001
  end
end

#gpio_handle(index) ⇒ Object

Store multiple LGPIO handles, since one board might have multiple chips.



115
116
117
# File 'lib/denko/piboard_map.rb', line 115

def gpio_handle(index)
  gpio_handles[index] ||= LGPIO.chip_open(index)
end

#gpio_handlesObject



119
120
121
# File 'lib/denko/piboard_map.rb', line 119

def gpio_handles
  @gpio_handles ||= []
end

#gpio_tuple(index) ⇒ Object

Make a new tuple, given a human-readable pin number, using values from the map.

Raises:

  • (ArgumentError)


97
98
99
100
101
102
103
104
105
106
107
# File 'lib/denko/piboard_map.rb', line 97

def gpio_tuple(index)
  return gpio_tuples[index] if gpio_tuples[index]

  raise ArgumentError, "pin #{index} does not exist or not included in map" unless map[:pins][index]
  raise ArgumentError, "pin #{index} cannot be used as GPIO. Bound to #{bound_pins[index]}" if bound_pins[index]

  handle = gpio_handle(map[:pins][index][:chip])
  line = map[:pins][index][:line]

  gpio_tuples[index] = [handle, line]
end

#gpio_tuplesObject

Cache tuples of [handle, line_number], keyed to human-readable pin numbers.



110
111
112
# File 'lib/denko/piboard_map.rb', line 110

def gpio_tuples
  @gpio_tuples ||= []
end

#halt_resume_checkObject

Raises:

  • (NotImplementedError)


133
134
135
# File 'lib/denko/piboard_core.rb', line 133

def halt_resume_check
  raise NotImplementedError, "PiBoard#halt_resume_check not implemented"
end

#hardware_pwm_from_pin(pin, options = {}) ⇒ Object

Raises:

  • (StandardError)


7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/denko/piboard_hardware_pwm.rb', line 7

def hardware_pwm_from_pin(pin, options={})
  # Find existing hardware PWM, change the frequency if needed, then return it.
  frequency = options[:frequency]
  pwm = hardware_pwms[pin]
  if pwm
    pwm.frequency = frequency if (frequency && pwm.frequency != frequency)
    return pwm
  end

  # Make sure it's in the board map before trying to use it.
  raise StandardError, "no hardware PWM in board map for pin #{pin}" unless map[:pwms][pin]

  # Make a new hardware PWM.
  pwmchip = map[:pwms][pin][:pwmchip]
  channel = map[:pwms][pin][:channel]
  frequency ||= 1000
  pwm = LGPIO::HardwarePWM.new(pwmchip, channel, frequency: frequency)
  hardware_pwms[pin] = pwm
end

#hardware_pwmsObject



3
4
5
# File 'lib/denko/piboard_hardware_pwm.rb', line 3

def hardware_pwms
  @hardware_pwms ||= []
end

#hcsr04_read(echo_pin, trigger_pin) ⇒ Object



5
6
7
8
# File 'lib/denko/piboard_pulse.rb', line 5

def hcsr04_read(echo_pin, trigger_pin)
  microseconds = LGPIO.gpio_read_ultrasonic(@gpio_handle, trigger_pin, echo_pin, 10)
  self.update(echo_pin, microseconds.to_s)
end

#highObject



12
# File 'lib/denko/piboard_base.rb', line 12

def high;     HIGH;     end

#i2c_bb_interface(scl, sda) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/denko/piboard_i2c_bb.rb', line 7

def i2c_bb_interface(scl, sda)
  # Convert the pins into a config array to check.
  ch, cl = gpio_tuple(scl)
  dh, dl = gpio_tuple(sda)
  config = [ch, cl, dh, dl]

  # Check if any already exists with that array and return it.
  i2c_bbs.each { |bb| return bb if (config == bb.config) }

  # If not, create one.
  hash =  { scl:  { handle: ch, line: cl },
            sda:  { handle: dh, line: dl } }

  i2c_bb = LGPIO::I2CBitBang.new(hash)
  i2c_bbs << i2c_bb
  i2c_bb
end

#i2c_bb_read(scl, sda, address, register, read_length, repeated_start = false) ⇒ Object



38
39
40
41
42
43
44
45
46
# File 'lib/denko/piboard_i2c_bb.rb', line 38

def i2c_bb_read(scl, sda, address, register, read_length, repeated_start=false)
  interface = i2c_bb_interface(scl, sda)
  interface.write(address, register) if register
  bytes = interface.read(address, read_length)

  # Prepend the address (0th element) to the data, and update the SDA pin.
  bytes.unshift(address)
  self.update(sda, bytes)
end

#i2c_bb_search(scl, sda) ⇒ Object



25
26
27
28
29
30
31
# File 'lib/denko/piboard_i2c_bb.rb', line 25

def i2c_bb_search(scl, sda)
  interface    = i2c_bb_interface(scl, sda)
  devices      = interface.search
  found_string = ""
  found_string = devices.join(":") if devices
  self.update(sda, found_string)
end

#i2c_bb_write(scl, sda, address, bytes, repeated_start = false) ⇒ Object



33
34
35
36
# File 'lib/denko/piboard_i2c_bb.rb', line 33

def i2c_bb_write(scl, sda, address, bytes, repeated_start=false)
  interface = i2c_bb_interface(scl, sda)
  interface.write(address, bytes)
end

#i2c_bbsObject



3
4
5
# File 'lib/denko/piboard_i2c_bb.rb', line 3

def i2c_bbs
  @i2c_bbs ||= []
end

#i2c_limitObject

Maximum amount of bytes that can be read or written in a single I2C operation.



8
9
10
# File 'lib/denko/piboard_i2c.rb', line 8

def i2c_limit
  65535
end

#i2c_read(index, address, register, read_length, frequency = nil, repeated_start = false) ⇒ Object

CMD = 35



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/denko/piboard_i2c.rb', line 44

def i2c_read(index, address, register, read_length, frequency=nil, repeated_start=false)
  i2c_mutex(index).synchronize do
    raise ArgumentError, "can't read more than #{i2c_limit} bytes to I2C" if read_length > i2c_limit

    handle = i2c_open(index, address)
    if register
      result = LGPIO.i2c_write_device(handle, register)
      i2c_c_error("read (register write)", result, index, address) if result < 0
    end

    bytes = LGPIO.i2c_read_device(handle, read_length)
    i2c_close(handle)
    i2c_c_error("read", bytes, index, address) if bytes.class == Integer

    # Prepend the address (0th element) to the data, and update the bus.
    bytes.unshift(address)
    update_i2c(index, bytes)
  end
end

#i2c_search(index) ⇒ Object

CMD = 33



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/denko/piboard_i2c.rb', line 13

def i2c_search(index)
  i2c_mutex(index).synchronize do
    found_string = ""

    # I2C device may have reserved addresses. Exclude them.
    addresses = I2C_ADDRESS_RANGE - map[:i2cs][index][:reserved_addresses].to_a

    addresses.each do |address|
      handle = i2c_open(index, address)
      bytes = LGPIO.i2c_read_device(handle, 1)
      found_string << "#{address}:" if bytes[0] > 0
      i2c_close(handle)
    end

    update_i2c(index, found_string)
  end
end

#i2c_write(index, address, bytes, frequency = nil, repeated_start = false) ⇒ Object

CMD = 34



32
33
34
35
36
37
38
39
40
41
# File 'lib/denko/piboard_i2c.rb', line 32

def i2c_write(index, address, bytes, frequency=nil, repeated_start=false)
  i2c_mutex(index).synchronize do
    raise ArgumentError, "exceeded #{i2c_limit} bytes for #i2c_write" if bytes.length > i2c_limit

    handle = i2c_open(index, address)
    result = LGPIO.i2c_write_device(handle, bytes)
    i2c_close(handle)
    i2c_c_error("write", result, index, address) if result < 0
  end
end

#infrared_emit(pin, frequency, pulses) ⇒ Object



3
4
5
6
7
8
9
10
11
12
# File 'lib/denko/piboard_infrared.rb', line 3

def infrared_emit(pin, frequency, pulses)
  # Main gem uses frequency in kHz. Set it in Hz.
  pwm = hardware_pwm_from_pin(pin, frequency: frequency*1000)

  # The actual strings for the sysfs PWM interface.
  duty_path = "#{pwm.path}duty_cycle"
  duty_ns   = (0.333333 * pwm.period).round.to_s

  pwm.tx_wave_ook(duty_path, duty_ns, pulses)
end

#load_gpiochip_lookup_optimizationsObject

Monkey patch to eliminate lookups, and improve performance, when all GPIO lines are on a single gpiochip, and the readable GPIO numbers exactly match the GPIO line numbers.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/denko/piboard_map.rb', line 71

def load_gpiochip_lookup_optimizations
  @gpiochip_lookup_optimized = false

  # Makes performance slightly worse on YJIT?
  return if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?

  # All pins must be defined on the same gpiochip.
  unique_gpiochips = map[:pins].each_value.map { |pin_def| pin_def[:chip] }.uniq
  return if unique_gpiochips.length != 1

  # For each pin, the key integer must be equal to the line integer.
  map[:pins].each_pair do |gpio_num, pin_def|
    return unless (gpio_num == pin_def[:line])
  end

  # Open the handle so it can be given as a literal in the optimized methods.
  gpiochip_single_handle = gpio_handle(unique_gpiochips.first)

  code = File.read(File.dirname(__FILE__) + "/piboard_core_optimize_lookup.rb")
  code = code.gsub("__GPIOCHIP_SINGLE_HANDLE__", gpiochip_single_handle.to_s)

  singleton_class.class_eval(code)
  @gpiochip_lookup_optimized = true
end

#lowObject



11
# File 'lib/denko/piboard_base.rb', line 11

def low;      LOW;      end

#micro_delay(duration) ⇒ Object



153
154
155
# File 'lib/denko/piboard_core.rb', line 153

def micro_delay(duration)
  LGPIO.micro_delay(duration)
end

#no_tone(pin) ⇒ Object

CMD = 18



20
21
22
# File 'lib/denko/piboard_tone.rb', line 20

def no_tone(pin)
  digital_write(pin, HIGH)
end

#one_wire_interface(pin) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
# File 'lib/denko/piboard_one_wire.rb', line 7

def one_wire_interface(pin)
  handle, gpio  = gpio_tuple(pin)

  # Check if any already exists with that array and return it.
  one_wires.each { |bb| return bb if (handle == bb.handle && gpio == bb.gpio) }

  # If not, create one.
  one_wire = LGPIO::OneWire.new(handle, gpio)
  one_wires << one_wire
  one_wire
end

#one_wire_read(gpio, length) ⇒ Object



36
37
38
39
40
# File 'lib/denko/piboard_one_wire.rb', line 36

def one_wire_read(gpio, length)
  interface    = one_wire_interface(gpio)
  result_array = interface.read(length)
  self.update(gpio, result_array)
end

#one_wire_reset(gpio, check_presence = 0) ⇒ Object



19
20
21
22
23
# File 'lib/denko/piboard_one_wire.rb', line 19

def one_wire_reset(gpio, check_presence=0)
  interface = one_wire_interface(gpio)
  presence  = interface.reset ? 0 : 1
  self.update(gpio, [presence]) if check_presence != 0
end

#one_wire_search(gpio, branch_mask) ⇒ Object



25
26
27
28
29
# File 'lib/denko/piboard_one_wire.rb', line 25

def one_wire_search(gpio, branch_mask)
  interface    = one_wire_interface(gpio)
  result_array = interface.search_pass(branch_mask)
  self.update(gpio, result_array)
end

#one_wire_write(gpio, parasite, *data) ⇒ Object



31
32
33
34
# File 'lib/denko/piboard_one_wire.rb', line 31

def one_wire_write(gpio, parasite, *data)
  interface = one_wire_interface(gpio)
  interface.write(data.flatten, parasite: parasite)
end

#one_wiresObject



3
4
5
# File 'lib/denko/piboard_one_wire.rb', line 3

def one_wires
  @one_wires ||= []
end

#parse_map(map_yaml) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/denko/piboard_map.rb', line 9

def parse_map(map_yaml)
  @map = YAML.load_file(map_yaml, symbolize_names: true)

  # Validate GPIO chip and line numbers for pins. Also build a lookup table for alerts.
  @alert_lut = []
  map[:pins].each_pair do |k, h|
    raise StandardError, "invalid pin number: #{k} in YAML map :pins. Should be Integer"  unless k.class == Integer
    raise StandardError, "invalid GPIO chip for GPIO #{k[:chip]}. Should be Integer"      unless h[:chip].class == Integer
    raise StandardError, "invalid GPIO chip for GPIO #{k[:line]}. Should be Integer"      unless h[:line].class == Integer
    @alert_lut[h[:chip]] ||= []
    @alert_lut[h[:chip]][h[:line]] = k
  end

  # Validate PWMs
  map[:pwms].each_pair do |k, h|
    raise StandardError, "invalid pin number: #{k} in YAML map :pwms. Should be Integer"    unless k.class == Integer
    raise StandardError, "invalid pwmchip: #{h[:pwmchip]}} for pin #{k}. Should be Integer" unless h[:pwmchip].class == Integer
    raise StandardError, "invalid channel: #{h[:channel]}} for pin #{k}. Should be Integer" unless h[:channel].class == Integer

    dev_path = "/sys/class/pwm/pwmchip#{h[:pwmchip]}/pwm#{h[:channel]}"
    raise StandardError, "board map error. Pin #{k} appears to be bound to both #{dev_path} and #{bound_pins[k]}" if bound_pins[k]
    bound_pins[k] = dev_path
  end

  # Validate I2Cs
  map[:i2cs].each_pair do |k, h|
    raise StandardError, "invalid I2C index: #{k} in YAML map :i2cs. Should be Integer" unless k.class == Integer
    dev_path = "/dev/i2c-#{k}"

    [:scl, :sda].each do |pin_sym|
      pin = h[pin_sym]
      raise StandardError, "missing #{pin_sym}: for I2C#{k}" unless pin
      raise StandardError, "invalid #{pin_sym}: #{pin} for I2C#{k}. Should be Integer" unless pin.class == Integer
      raise StandardError, "board map error. Pin #{pin} appears to be bound to both #{dev_path} and #{bound_pins[pin]}" if bound_pins[pin]
      bound_pins[pin] = dev_path
    end
  end

  # Validate SPIs
  map[:spis].each_pair do |k, h|
    raise StandardError, "invalid SPI index: #{k} in YAML map :spis. Should be Integer" unless k.class == Integer
    dev_path = "dev/spidev#{k}.0"

    [:clk, :mosi, :miso, :cs0].each do |pin_sym|
      pin = h[pin_sym]
      raise StandardError, "missing #{pin_sym}: for SPI#{k}" if (pin_sym == :clk) && !pin
      next unless pin

      raise StandardError, "invalid #{pin_sym}: #{pin} for SPI#{k}. Should be Integer" unless pin.class == Integer
      raise StandardError, "board map error. Pin #{pin} appears to be bound to both #{dev_path} and #{bound_pins[pin]}" if bound_pins[pin]
      bound_pins[pin] = dev_path
    end
  end

  load_gpiochip_lookup_optimizations
end

#pin_configsObject



186
187
188
# File 'lib/denko/piboard_core.rb', line 186

def pin_configs
  @pin_configs ||= []
end

#pulse_read(pin, reset: false, reset_time: 0, pulse_limit: 100, timeout: 200) ⇒ Object

Raises:

  • (ArgumentError)


10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/denko/piboard_pulse.rb', line 10

def pulse_read(pin, reset: false, reset_time: 0, pulse_limit: 100, timeout: 200)
  # Validation
  raise ArgumentError, "error in reset: #{reset}. Should be either #{high} or #{low}"         if reset && ![high, low].include?(reset)
  raise ArgumentError, "errror in reset_time: #{reset_time}. Should be 0..65535 microseconds" if (reset_time < 0) || (reset_time > 0xFFFF)
  raise ArgumentError, "errror in pulse_limit: #{pulse_limit}. Should be 0..255 pulses"       if (pulse_limit < 0) || (pulse_limit > 0xFF)
  raise ArgumentError, "errror in timeout: #{timeout}. Should be 0..65535 milliseconds"       if (timeout < 0) || (timeout > 0xFFFF)

  pulses = LGPIO.gpio_read_pulses_us(@gpio_handle, pin, reset_time, reset, pulse_limit, timeout)
  if pulses.class == Array
    self.update(pin, pulses.join(","))
  elsif pulse.class == Integer
    raise "could not read pulses from GPIO #{pin}. LGPIO error: #{pulses}"
  end
end

#pwm_highObject



13
# File 'lib/denko/piboard_base.rb', line 13

def pwm_high; PWM_HIGH; end

#pwm_write(pin, duty) ⇒ Object



74
75
76
77
78
79
80
81
82
# File 'lib/denko/piboard_core.rb', line 74

def pwm_write(pin, duty)
  if hardware_pwms[pin]
    hardware_pwms[pin].duty_percent = duty
  else
    frequency    = pin_configs[pin][:frequency] || 1000
    handle, line = gpio_tuple(pin)
    LGPIO.tx_pwm(handle, line, frequency, duty, 0, 0)
  end
end

#servo_toggle(pin, value = :off, options = {}) ⇒ Object

CMD = 10



4
5
6
7
8
9
10
11
12
# File 'lib/denko/piboard_servo.rb', line 4

def servo_toggle(pin, value=:off, options={})
  pwm = hardware_pwm_from_pin(pin)
  if (value == :off)
    pwm.duty_cycle = 0
    pwm.disable
  elsif
    pwm.frequency = 50
  end
end

#servo_write(pin, value = 0) ⇒ Object

CMD = 11



15
16
17
# File 'lib/denko/piboard_servo.rb', line 15

def servo_write(pin, value=0)
  hardware_pwms[pin].duty_us = value
end

#set_analog_read_resolution(value) ⇒ Object

Raises:

  • (NotImplementedError)


145
146
147
# File 'lib/denko/piboard_core.rb', line 145

def set_analog_read_resolution(value)
  raise NotImplementedError, "PiBoard#set_analog_read_resolution not implemented"
end

#set_analog_write_resolution(value) ⇒ Object

Raises:

  • (NotImplementedError)


141
142
143
# File 'lib/denko/piboard_core.rb', line 141

def set_analog_write_resolution(value)
  raise NotImplementedError, "PiBoard#set_analog_write_resolution not implemented"
end

#set_listener(pin, state = :off, options = {}) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/denko/piboard_core.rb', line 92

def set_listener(pin, state=:off, options={})
  # Validate listener is digital only.
  options[:mode] ||= :digital
  unless options[:mode] == :digital
    raise ArgumentError, "error in mode: #{options[:mode]}. Should be one of: [:digital]"
  end

  # Validate state.
  unless (state == :on) || (state == :off)
    raise ArgumentError, "error in state: #{options[:state]}. Should be one of: [:on, :off]"
  end

  # Only way to stop getting alerts is to free the GPIO.
  LGPIO.gpio_free(*gpio_tuple(pin))

  # Reclaim it as input if needed.
  config   = pin_configs[pin]
  config ||= { mode: :input, debounce_time: nil } if state == :on
  if config
    set_pin_mode(pin, config[:mode])
    set_pin_debounce(pin, config[:debounce_time])
  end

  if state == :on
    LGPIO.gpio_claim_alert(*gpio_tuple(pin), 0, LGPIO::BOTH_EDGES)
    start_alert_thread unless @alert_thread
  end
end

#set_pin_debounce(pin, debounce_time) ⇒ Object



46
47
48
49
50
51
52
# File 'lib/denko/piboard_core.rb', line 46

def set_pin_debounce(pin, debounce_time)
  return unless debounce_time
  result = LGPIO.gpio_set_debounce(*gpio_tuple(pin), debounce_time)
  raise "could not set debounce for pin #{pin}. lgpio C error: #{result}" if result < 0

  pin_configs[pin] = pin_configs[pin].to_h.merge(debounce_time: debounce_time)
end

#set_pin_mode(pin, mode = :input, options = {}) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/denko/piboard_core.rb', line 8

def set_pin_mode(pin, mode=:input, options={})
  # Is the mode valid?
  unless PIN_MODES.include?(mode)
    raise ArgumentError, "cannot set mode: #{mode}. Should be one of: #{PIN_MODES.inspect}"
  end

  # If pin is bound to hardware PWM, allow it to be used as :output_pwm. OR :output.
  if map[:pwms][pin]
    if (mode == :output_pwm)
      return hardware_pwm_from_pin(pin, options)
    elsif (mode == :output)
      puts "WARNING: using hardware PWM on pin #{pin} as GPIO. Will be slower than regular GPIO."
      return hardware_pwm_from_pin(pin, options)
    else
      raise "Pin #{pin} is bound to hardware PWM. It can only be used as :output or :output_pwm"
    end
  end

  # Attempt to free the pin.
  LGPIO.gpio_free(*gpio_tuple(pin))

  # Try to claim the GPIO.
  if OUTPUT_MODES.include?(mode)
    flags  = LGPIO::SET_PULL_NONE
    flags  = LGPIO::SET_OPEN_DRAIN  if mode == :output_open_drain
    flags  = LGPIO::SET_OPEN_SOURCE if mode == :output_open_source
    result = LGPIO.gpio_claim_output(*gpio_tuple(pin), flags, LOW)
  else
    flags  = LGPIO::SET_PULL_NONE
    flags  = LGPIO::SET_PULL_UP   if mode == :input_pullup
    flags  = LGPIO::SET_PULL_DOWN if mode == :input_pulldown
    result = LGPIO.gpio_claim_input(*gpio_tuple(pin), flags)
  end
  raise "could not claim GPIO for pin #{pin}. lgpio C error: #{result}" if result < 0

  pin_configs[pin] = pin_configs[pin].to_h.merge(mode: mode).merge(options)
end

#set_register_divider(value) ⇒ Object

Raises:

  • (NotImplementedError)


137
138
139
# File 'lib/denko/piboard_core.rb', line 137

def set_register_divider(value)
  raise NotImplementedError, "PiBoard#set_register_divider not implemented"
end

#show_ws2812(pin, pixel_buffer, spi_index:) ⇒ Object



3
4
5
6
7
# File 'lib/denko/piboard_led_array.rb', line 3

def show_ws2812(pin, pixel_buffer, spi_index:)
  handle = spi_open(spi_index, 2_400_000, 0)
  LGPIO.spi_ws2812_write(handle, pixel_buffer)
  spi_close(handle)
end

#spi_bb_interface(clock, input, output) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/denko/piboard_spi_bb.rb', line 9

def spi_bb_interface(clock, input, output)
  # Convert the pins into a config array to check.
  ch, cl = gpio_tuple(clock)
  ih, il = input  ? gpio_tuple(input)  : [nil, nil]
  oh, ol = output ? gpio_tuple(output) : [nil, nil]
  config = [ch, cl, ih, il, oh, ol]

  # Check if any already exists with that array and return it.
  spi_bbs.each { |bb| return bb if (config == bb.config) }

  # If not, create one.
  hash =  { clock:  { handle: ch, line: cl },
            input:  { handle: ih, line: il },
            output: { handle: oh, line: ol } }

  spi_bb = LGPIO::SPIBitBang.new(hash)
  spi_bbs << spi_bb
  spi_bb
end

#spi_bb_listen(*arg, **kwargs) ⇒ Object

Raises:

  • (NotImplementedError)


37
38
39
# File 'lib/denko/piboard_spi_bb.rb', line 37

def spi_bb_listen(*arg, **kwargs)
  raise NotImplementedError, "PiBoard#spi_bb_listen not implemented yet"
end

#spi_bb_transfer(select, clock:, input: nil, output: nil, write: [], read: 0, frequency: nil, mode: nil, bit_order: nil) ⇒ Object



29
30
31
32
33
34
35
# File 'lib/denko/piboard_spi_bb.rb', line 29

def spi_bb_transfer(select, clock:, input: nil, output: nil, write: [], read: 0, frequency: nil, mode: nil, bit_order: nil)
  interface = spi_bb_interface(clock, input, output)
  select_hash = select ? { handle: gpio_tuple(select)[0], line: gpio_tuple(select)[0] } : nil

  bytes = interface.transfer(write: write, read: read, select: select_hash, order: bit_order, mode: mode)
  self.update(select, bytes) if (read > 0 && select)
end

#spi_flags(mode) ⇒ Object

Raises:

  • (ArgumentError)


3
4
5
6
7
8
9
10
11
# File 'lib/denko/piboard_spi.rb', line 3

def spi_flags(mode)
  mode ||= 0
  raise ArgumentError, "invalid SPI mode #{mode}" unless (0..3).include? mode

  # Flags is a 32-bit mask. Bits [1..0] are the SPI mode. Default to 0.
  config = mode

  return config
end

#spi_listen(*arg, **kwargs) ⇒ Object

Raises:

  • (NotImplementedError)


31
32
33
# File 'lib/denko/piboard_spi.rb', line 31

def spi_listen(*arg, **kwargs)
  raise NotImplementedError, "PiBoard#spi_listen not implemented yet"
end

#spi_listenersObject



38
39
40
# File 'lib/denko/piboard_spi.rb', line 38

def spi_listeners
  @spi_listeners ||= Array.new
end

#spi_stop(pin) ⇒ Object



35
36
# File 'lib/denko/piboard_spi.rb', line 35

def spi_stop(pin)
end

#spi_transfer(index, select, write: [], read: 0, frequency: 1_000_000, mode: 0, bit_order: nil) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/denko/piboard_spi.rb', line 13

def spi_transfer(index, select, write:[], read:0, frequency: 1_000_000, mode: 0, bit_order: nil)
  # Default frequency. Flags just has mode.
  frequency ||= 1_000_000
  flags       = spi_flags(mode)
  handle      = spi_open(index, frequency, flags)

  # Handle select_pin unless it's same as CS0 for this interface.
  digital_write(select, 0) if select && (select != map[:spis][index][:cs0])
  bytes = LGPIO.spi_xfer(handle, write, read)
  spi_close(handle)
  digital_write(select, 1) if select && (select != map[:spis][index][:cs0])

  spi_c_error("xfer", bytes, index) if bytes.class == Integer

  # Update component attached to select pin with read bytes.
  self.update(select, bytes) if (read > 0 && select)
end

#start_alert_threadObject



157
158
159
160
# File 'lib/denko/piboard_core.rb', line 157

def start_alert_thread
  start_gpio_reports
  @alert_thread = Thread.new { loop { get_report } }
end

#start_gpio_reportsObject



180
181
182
183
184
# File 'lib/denko/piboard_core.rb', line 180

def start_gpio_reports
  return if @reporting_started
  LGPIO.gpio_start_reporting
  @reporting_started = true
end

#stop_alert_threadObject



162
163
164
165
# File 'lib/denko/piboard_core.rb', line 162

def stop_alert_thread
  Thread.kill(@alert_thread) if @alert_thread
  @alert_thread = nil
end

#stop_listener(pin) ⇒ Object



129
130
131
# File 'lib/denko/piboard_core.rb', line 129

def stop_listener(pin)
  set_listener(pin, :off)
end

#tone(pin, frequency, duration = nil) ⇒ Object

CMD = 17



4
5
6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/denko/piboard_tone.rb', line 4

def tone(pin, frequency, duration=nil)
  if @hardware_pwms[pin]
    @hardware_pwms[pin].frequency    = frequency
    @hardware_pwms[pin].duty_percent = 33
    sleep duration if duration
  else
    raise ArgumentError, "maximum software PWM frequency is 10 kHz" if frequency > 10_000
    cycles = 0
    cycles = (frequency * duration).round if duration

    sleep 0.05 while (LGPIO.tx_room(@gpio_handle, pin, LGPIO::TX_PWM) == 0)
    LGPIO.tx_pwm(@gpio_handle, pin, frequency, 33, 0, cycles)
  end
end

#tone_busy(pin) ⇒ Object



24
25
26
# File 'lib/denko/piboard_tone.rb', line 24

def tone_busy(pin)
  LGPIO.tx_busy(@gpio_handle, pin, LGPIO::TX_PWM)
end

#update(pin, message) ⇒ Object



29
30
31
32
33
# File 'lib/denko/piboard_base.rb', line 29

def update(pin, message)
  if single_pin_components[pin]
    single_pin_components[pin].update(message)
  end
end