Class: Denko::PiBoard
- Inherits:
-
Object
- Object
- Denko::PiBoard
- 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_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.15.0'
Instance Attribute Summary collapse
-
#alert_lut ⇒ Object
readonly
Returns the value of attribute alert_lut.
-
#gpiochip_lookup_optimized ⇒ Object
readonly
Returns the value of attribute gpiochip_lookup_optimized.
-
#map ⇒ Object
readonly
Returns the value of attribute map.
-
#spi_bbs ⇒ Object
readonly
Returns the value of attribute spi_bbs.
Instance Method Summary collapse
- #analog_listen(pin, divider = 16) ⇒ Object
- #analog_read(pin, negative_pin = nil, gain = nil, sample_rate = nil) ⇒ Object
- #analog_write_resolution ⇒ Object
- #binary_echo(pin, data = []) ⇒ Object
-
#bound_pins ⇒ Object
Keep track of pins bound by non-GPIO peripherals.
- #convert_pin(pin) ⇒ Object
- #dac_write(pin, value) ⇒ Object
- #digital_listen(pin, divider = 4) ⇒ Object
- #digital_read(pin) ⇒ Object
- #digital_write(pin, value) ⇒ Object
- #finish_write ⇒ Object
- #get_report ⇒ Object
-
#gpio_handle(index) ⇒ Object
Store multiple LGPIO handles, since one board might have multiple chips.
- #gpio_handles ⇒ Object
-
#gpio_tuple(index) ⇒ Object
Make a new tuple, given a human-readable pin number, using values from the map.
-
#gpio_tuples ⇒ Object
Cache tuples of [handle, line_number], keyed to human-readable pin numbers.
- #halt_resume_check ⇒ Object
- #hardware_pwm_from_pin(pin, options = {}) ⇒ Object
- #hardware_pwms ⇒ Object
- #hcsr04_read(echo_pin, trigger_pin) ⇒ Object
- #high ⇒ Object
- #i2c_bb_interface(scl, sda) ⇒ Object
- #i2c_bb_read(scl, sda, address, register, read_length, repeated_start = false) ⇒ Object
- #i2c_bb_search(scl, sda) ⇒ Object
- #i2c_bb_write(scl, sda, address, bytes, repeated_start = false) ⇒ Object
- #i2c_bbs ⇒ Object
-
#i2c_limit ⇒ Object
Maximum amount of bytes that can be read or written in a single I2C operation.
-
#i2c_read(index, address, register, read_length, frequency = nil, repeated_start = false) ⇒ Object
CMD = 35.
-
#i2c_search(index) ⇒ Object
CMD = 33.
-
#i2c_write(index, address, bytes, frequency = nil, repeated_start = false) ⇒ Object
CMD = 34.
- #infrared_emit(pin, frequency, pulses) ⇒ Object
-
#initialize(map_yaml_file = nil) ⇒ PiBoard
constructor
A new instance of PiBoard.
-
#load_gpiochip_lookup_optimizations ⇒ Object
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.
- #low ⇒ Object
- #micro_delay(duration) ⇒ Object
-
#no_tone(pin) ⇒ Object
CMD = 18.
- #one_wire_interface(pin) ⇒ Object
- #one_wire_read(gpio, length) ⇒ Object
- #one_wire_reset(gpio, check_presence = 0) ⇒ Object
- #one_wire_search(gpio, branch_mask) ⇒ Object
- #one_wire_write(gpio, parasite, *data) ⇒ Object
- #one_wires ⇒ Object
- #parse_map(map_yaml) ⇒ Object
- #pin_configs ⇒ Object
-
#pin_is_pwm?(pin) ⇒ Boolean
Check if a GPIO number is bound to a hardware PWM.
- #platform ⇒ Object
- #pulse_read(pin, reset: false, reset_time: 0, pulse_limit: 100, timeout: 200) ⇒ Object
- #pwm_high ⇒ Object
- #pwm_write(pin, duty) ⇒ Object
- #set_analog_read_resolution(value) ⇒ Object
- #set_analog_write_resolution(value) ⇒ Object
- #set_listener(pin, state = :off, options = {}) ⇒ Object
- #set_pin_debounce(pin, debounce_time) ⇒ Object
- #set_pin_mode(pin, mode = :input, options = {}) ⇒ Object
- #set_register_divider(value) ⇒ Object
- #show_ws2812(pin, pixel_buffer, spi_index:) ⇒ Object
- #spi_bb_interface(clock, input, output) ⇒ Object
- #spi_bb_listen(*arg, **kwargs) ⇒ Object
- #spi_bb_transfer(select, clock:, input: nil, output: nil, write: [], read: 0, frequency: nil, mode: nil, bit_order: nil) ⇒ Object
- #spi_flags(mode) ⇒ Object
- #spi_limit ⇒ Object
- #spi_listen(*arg, **kwargs) ⇒ Object
- #spi_listeners ⇒ Object
- #spi_stop(pin) ⇒ Object
- #spi_transfer(index, select, write: [], read: 0, frequency: 1_000_000, mode: 0, bit_order: nil) ⇒ Object
- #start_alert_thread ⇒ Object
- #start_gpio_reports ⇒ Object
- #stop_alert_thread ⇒ Object
- #stop_listener(pin) ⇒ Object
-
#tone(pin, frequency, duration = nil) ⇒ Object
CMD = 17.
- #tone_busy(pin) ⇒ Object
- #update(pin, message) ⇒ Object
Constructor Details
#initialize(map_yaml_file = nil) ⇒ 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_lut ⇒ Object (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_optimized ⇒ Object (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 |
#map ⇒ Object (readonly)
Returns the value of attribute map.
7 8 9 |
# File 'lib/denko/piboard_map.rb', line 7 def map @map end |
#spi_bbs ⇒ Object (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
127 128 129 |
# File 'lib/denko/piboard_core.rb', line 127 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
90 91 92 |
# File 'lib/denko/piboard_core.rb', line 90 def analog_read(pin, negative_pin=nil, gain=nil, sample_rate=nil) raise NotImplementedError, "PiBoard#analog_read not implemented" end |
#analog_write_resolution ⇒ Object
14 |
# File 'lib/denko/piboard_base.rb', line 14 def analog_write_resolution; 8; end |
#binary_echo(pin, data = []) ⇒ Object
151 152 153 |
# File 'lib/denko/piboard_core.rb', line 151 def binary_echo(pin, data=[]) raise NotImplementedError, "PiBoard#binary_echo not implemented" end |
#bound_pins ⇒ Object
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
86 87 88 |
# File 'lib/denko/piboard_core.rb', line 86 def dac_write(pin, value) raise NotImplementedError, "PiBoard#dac_write not implemented" end |
#digital_listen(pin, divider = 4) ⇒ Object
123 124 125 |
# File 'lib/denko/piboard_core.rb', line 123 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_write ⇒ Object
29 30 31 |
# File 'lib/denko/piboard_base.rb', line 29 def finish_write gpio_handles.each { |h| LGPIO.chip_close(h) if h } end |
#get_report ⇒ Object
169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/denko/piboard_core.rb', line 169 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_handles ⇒ Object
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.
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_tuples ⇒ Object
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_check ⇒ Object
135 136 137 |
# File 'lib/denko/piboard_core.rb', line 135 def halt_resume_check raise NotImplementedError, "PiBoard#halt_resume_check not implemented" end |
#hardware_pwm_from_pin(pin, options = {}) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/denko/piboard_hardware_pwm.rb', line 12 def hardware_pwm_from_pin(pin, ={}) # Find existing hardware PWM, change the frequency if needed, then return it. frequency = [: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_pwms ⇒ Object
8 9 10 |
# File 'lib/denko/piboard_hardware_pwm.rb', line 8 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 |
#high ⇒ Object
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_bbs ⇒ Object
3 4 5 |
# File 'lib/denko/piboard_i2c_bb.rb', line 3 def i2c_bbs @i2c_bbs ||= [] end |
#i2c_limit ⇒ Object
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_optimizations ⇒ Object
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 |
#low ⇒ Object
11 |
# File 'lib/denko/piboard_base.rb', line 11 def low; LOW; end |
#micro_delay(duration) ⇒ Object
155 156 157 |
# File 'lib/denko/piboard_core.rb', line 155 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_wires ⇒ Object
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_configs ⇒ Object
188 189 190 |
# File 'lib/denko/piboard_core.rb', line 188 def pin_configs @pin_configs ||= [] end |
#pin_is_pwm?(pin) ⇒ Boolean
Check if a GPIO number is bound to a hardware PWM.
4 5 6 |
# File 'lib/denko/piboard_hardware_pwm.rb', line 4 def pin_is_pwm?(pin) map[:pwms][pin] end |
#platform ⇒ Object
25 26 27 |
# File 'lib/denko/piboard_base.rb', line 25 def platform :linux end |
#pulse_read(pin, reset: false, reset_time: 0, pulse_limit: 100, timeout: 200) ⇒ Object
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_high ⇒ Object
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 83 84 |
# File 'lib/denko/piboard_core.rb', line 74 def pwm_write(pin, duty) if hardware_pwms[pin] hardware_pwms[pin].duty = duty else handle, line = gpio_tuple(pin) frequency = pin_configs[pin][:frequency] || 1000 period = pin_configs[pin][:period] || 1_000_000 duty_percent = (duty.to_f / period) * 100 LGPIO.tx_pwm(handle, line, frequency, duty_percent, 0, 0) end end |
#set_analog_read_resolution(value) ⇒ Object
147 148 149 |
# File 'lib/denko/piboard_core.rb', line 147 def set_analog_read_resolution(value) raise NotImplementedError, "PiBoard#set_analog_read_resolution not implemented" end |
#set_analog_write_resolution(value) ⇒ Object
143 144 145 |
# File 'lib/denko/piboard_core.rb', line 143 def set_analog_write_resolution(value) raise NotImplementedError, "PiBoard#set_analog_write_resolution not implemented" end |
#set_listener(pin, state = :off, options = {}) ⇒ Object
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 120 121 |
# File 'lib/denko/piboard_core.rb', line 94 def set_listener(pin, state=:off, ={}) # Validate listener is digital only. [:mode] ||= :digital unless [:mode] == :digital raise ArgumentError, "error in mode: #{[:mode]}. Should be one of: [:digital]" end # Validate state. unless (state == :on) || (state == :off) raise ArgumentError, "error in state: #{[: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, ={}) # 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, ) 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, ) 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() end |
#set_register_divider(value) ⇒ Object
139 140 141 |
# File 'lib/denko/piboard_core.rb', line 139 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
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
7 8 9 10 11 12 13 14 15 |
# File 'lib/denko/piboard_spi.rb', line 7 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_limit ⇒ Object
3 4 5 |
# File 'lib/denko/piboard_spi.rb', line 3 def spi_limit 4096 end |
#spi_listen(*arg, **kwargs) ⇒ Object
35 36 37 |
# File 'lib/denko/piboard_spi.rb', line 35 def spi_listen(*arg, **kwargs) raise NotImplementedError, "PiBoard#spi_listen not implemented yet" end |
#spi_listeners ⇒ Object
42 43 44 |
# File 'lib/denko/piboard_spi.rb', line 42 def spi_listeners @spi_listeners ||= Array.new end |
#spi_stop(pin) ⇒ Object
39 40 |
# File 'lib/denko/piboard_spi.rb', line 39 def spi_stop(pin) end |
#spi_transfer(index, select, write: [], read: 0, frequency: 1_000_000, mode: 0, bit_order: nil) ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/denko/piboard_spi.rb', line 17 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_thread ⇒ Object
159 160 161 162 |
# File 'lib/denko/piboard_core.rb', line 159 def start_alert_thread start_gpio_reports @alert_thread = Thread.new { loop { get_report } } end |
#start_gpio_reports ⇒ Object
182 183 184 185 186 |
# File 'lib/denko/piboard_core.rb', line 182 def start_gpio_reports return if @reporting_started LGPIO.gpio_start_reporting @reporting_started = true end |
#stop_alert_thread ⇒ Object
164 165 166 167 |
# File 'lib/denko/piboard_core.rb', line 164 def stop_alert_thread Thread.kill(@alert_thread) if @alert_thread @alert_thread = nil end |
#stop_listener(pin) ⇒ Object
131 132 133 |
# File 'lib/denko/piboard_core.rb', line 131 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
33 34 35 36 37 |
# File 'lib/denko/piboard_base.rb', line 33 def update(pin, ) if single_pin_components[pin] single_pin_components[pin].update() end end |