denko-piboard 0.13.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -0
  3. data/LICENSE +1 -1
  4. data/README.md +181 -132
  5. data/Rakefile +0 -5
  6. data/board_maps/README.md +59 -0
  7. data/board_maps/le_potato.yml +89 -0
  8. data/board_maps/orange_pi_zero_2w.yml +85 -0
  9. data/board_maps/radxa_zero3.yml +88 -0
  10. data/board_maps/raspberry_pi.yml +95 -0
  11. data/board_maps/raspberry_pi5.yml +95 -0
  12. data/denko_piboard.gemspec +6 -7
  13. data/examples/digital_io/bench_out.rb +22 -0
  14. data/examples/digital_io/rotary_encoder.rb +31 -0
  15. data/examples/display/ssd1306.rb +53 -0
  16. data/examples/i2c/bitbang_aht10.rb +18 -0
  17. data/examples/i2c/bitbang_search.rb +24 -0
  18. data/examples/i2c/bitbang_ssd1306_bench.rb +29 -0
  19. data/examples/i2c/search.rb +24 -0
  20. data/examples/led/blink.rb +10 -0
  21. data/examples/led/fade.rb +22 -0
  22. data/examples/led/ws2812_bounce.rb +36 -0
  23. data/examples/motor/servo.rb +16 -0
  24. data/examples/pi_system_monitor.rb +10 -8
  25. data/examples/pulse_io/buzzer.rb +34 -0
  26. data/examples/pulse_io/infrared.rb +25 -0
  27. data/examples/sensor/aht10.rb +17 -0
  28. data/examples/sensor/dht.rb +24 -0
  29. data/examples/sensor/ds18b20.rb +59 -0
  30. data/examples/sensor/hcsr04.rb +16 -0
  31. data/examples/sensor/neat_tph_readings.rb +32 -0
  32. data/examples/spi/bb_loopback.rb +31 -0
  33. data/examples/spi/loopback.rb +37 -0
  34. data/examples/spi/output_register.rb +38 -0
  35. data/lib/denko/piboard.rb +10 -2
  36. data/lib/denko/piboard_base.rb +21 -63
  37. data/lib/denko/piboard_core.rb +150 -130
  38. data/lib/denko/piboard_core_optimize_lookup.rb +31 -0
  39. data/lib/denko/piboard_hardware_pwm.rb +32 -0
  40. data/lib/denko/piboard_i2c.rb +59 -82
  41. data/lib/denko/piboard_i2c_bb.rb +48 -0
  42. data/lib/denko/piboard_infrared.rb +7 -44
  43. data/lib/denko/piboard_led_array.rb +9 -0
  44. data/lib/denko/piboard_map.rb +125 -38
  45. data/lib/denko/piboard_one_wire.rb +42 -0
  46. data/lib/denko/piboard_pulse.rb +11 -68
  47. data/lib/denko/piboard_spi.rb +47 -73
  48. data/lib/denko/piboard_spi_bb.rb +41 -0
  49. data/lib/denko/piboard_tone.rb +15 -26
  50. data/lib/denko/piboard_version.rb +1 -1
  51. data/scripts/99-denko.rules +9 -0
  52. data/scripts/set_permissions.rb +131 -0
  53. metadata +48 -21
  54. data/ext/gpiod/extconf.rb +0 -9
  55. data/ext/gpiod/gpiod.c +0 -179
  56. data/lib/denko/piboard_servo.rb +0 -18
  57. data/lib/gpiod.rb +0 -6
@@ -1,49 +1,136 @@
1
+ require 'yaml'
2
+
1
3
  module Denko
2
4
  class PiBoard
3
- MAP = {
4
- # Only use SPI1 interface.
5
- SS: 18,
6
- MISO: 19,
7
- MOSI: 20,
8
- SCK: 21,
9
-
10
- # Only use I2C1 interface.
11
- SDA: 2,
12
- SCL: 3,
13
-
14
- # Single UART
15
- TX: 14,
16
- RX: 15,
17
- }
18
-
19
- def map
20
- MAP
21
- end
5
+ DEFAULT_MAP_FILE = ".denko_piboard_map.yml"
22
6
 
23
- def convert_pin(pin)
24
- # Convert non numerical strings to symbols.
25
- pin = pin.to_sym if (pin.class == String) && !(pin.match (/\A\d+\.*\d*/))
26
-
27
- # Handle symbols.
28
- if (pin.class == Symbol)
29
- if map && map[pin]
30
- return map[pin]
31
- elsif map
32
- raise ArgumentError, "error in pin: #{pin.inspect}. Make sure that pin is defined for this board by calling Board#map"
33
- else
34
- raise ArgumentError, "error in pin: #{pin.inspect}. Given a Symbol, but board has no map. Try using GPIO integer instead"
7
+ attr_reader :map, :alert_lut, :gpiochip_lookup_optimized
8
+
9
+ def parse_map(map_yaml)
10
+ @map = YAML.load_file(map_yaml, symbolize_names: true)
11
+
12
+ # Validate GPIO chip and line numbers for pins. Also build a lookup table for alerts.
13
+ @alert_lut = []
14
+ map[:pins].each_pair do |k, h|
15
+ raise StandardError, "invalid pin number: #{k} in YAML map :pins. Should be Integer" unless k.class == Integer
16
+ raise StandardError, "invalid GPIO chip for GPIO #{k[:chip]}. Should be Integer" unless h[:chip].class == Integer
17
+ raise StandardError, "invalid GPIO chip for GPIO #{k[:line]}. Should be Integer" unless h[:line].class == Integer
18
+ @alert_lut[h[:chip]] ||= []
19
+ @alert_lut[h[:chip]][h[:line]] = k
20
+ end
21
+
22
+ # Validate PWMs
23
+ map[:pwms].each_pair do |k, h|
24
+ raise StandardError, "invalid pin number: #{k} in YAML map :pwms. Should be Integer" unless k.class == Integer
25
+ raise StandardError, "invalid pwmchip: #{h[:pwmchip]}} for pin #{k}. Should be Integer" unless h[:pwmchip].class == Integer
26
+ raise StandardError, "invalid channel: #{h[:channel]}} for pin #{k}. Should be Integer" unless h[:channel].class == Integer
27
+
28
+ dev_path = "/sys/class/pwm/pwmchip#{h[:pwmchip]}/pwm#{h[:channel]}"
29
+ raise StandardError, "board map error. Pin #{k} appears to be bound to both #{dev_path} and #{bound_pins[k]}" if bound_pins[k]
30
+ bound_pins[k] = dev_path
31
+ end
32
+
33
+ # Validate I2Cs
34
+ map[:i2cs].each_pair do |k, h|
35
+ raise StandardError, "invalid I2C index: #{k} in YAML map :i2cs. Should be Integer" unless k.class == Integer
36
+ dev_path = "/dev/i2c-#{k}"
37
+
38
+ [:scl, :sda].each do |pin_sym|
39
+ pin = h[pin_sym]
40
+ raise StandardError, "missing #{pin_sym}: for I2C#{k}" unless pin
41
+ raise StandardError, "invalid #{pin_sym}: #{pin} for I2C#{k}. Should be Integer" unless pin.class == Integer
42
+ raise StandardError, "board map error. Pin #{pin} appears to be bound to both #{dev_path} and #{bound_pins[pin]}" if bound_pins[pin]
43
+ bound_pins[pin] = dev_path
44
+ end
45
+ end
46
+
47
+ # Validate SPIs
48
+ map[:spis].each_pair do |k, h|
49
+ raise StandardError, "invalid SPI index: #{k} in YAML map :spis. Should be Integer" unless k.class == Integer
50
+ dev_path = "dev/spidev#{k}.0"
51
+
52
+ [:clk, :mosi, :miso, :cs0].each do |pin_sym|
53
+ pin = h[pin_sym]
54
+ raise StandardError, "missing #{pin_sym}: for SPI#{k}" if (pin_sym == :clk) && !pin
55
+ next unless pin
56
+
57
+ raise StandardError, "invalid #{pin_sym}: #{pin} for SPI#{k}. Should be Integer" unless pin.class == Integer
58
+ raise StandardError, "board map error. Pin #{pin} appears to be bound to both #{dev_path} and #{bound_pins[pin]}" if bound_pins[pin]
59
+ bound_pins[pin] = dev_path
35
60
  end
36
61
  end
37
62
 
38
- # Handle integers.
39
- return pin if pin.class == Integer
63
+ load_gpiochip_lookup_optimizations
64
+ end
65
+
66
+ #
67
+ # Monkey patch to eliminate lookups, and improve performance,
68
+ # when all GPIO lines are on a single gpiochip, and the readable
69
+ # GPIO numbers exactly match the GPIO line numbers.
70
+ #
71
+ def load_gpiochip_lookup_optimizations
72
+ @gpiochip_lookup_optimized = false
73
+
74
+ # Makes performance slightly worse on YJIT?
75
+ return if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
76
+
77
+ # All pins must be defined on the same gpiochip.
78
+ unique_gpiochips = map[:pins].each_value.map { |pin_def| pin_def[:chip] }.uniq
79
+ return if unique_gpiochips.length != 1
40
80
 
41
- # Try #to_i on anyting else. Will catch numerical strings.
42
- begin
43
- return pin.to_i
44
- rescue
45
- raise ArgumentError, "error in pin: #{pin.inspect}"
81
+ # For each pin, the key integer must be equal to the line integer.
82
+ map[:pins].each_pair do |gpio_num, pin_def|
83
+ return unless (gpio_num == pin_def[:line])
46
84
  end
85
+
86
+ # Open the handle so it can be given as a literal in the optimized methods.
87
+ gpiochip_single_handle = gpio_handle(unique_gpiochips.first)
88
+
89
+ code = File.read(File.dirname(__FILE__) + "/piboard_core_optimize_lookup.rb")
90
+ code = code.gsub("__GPIOCHIP_SINGLE_HANDLE__", gpiochip_single_handle.to_s)
91
+
92
+ singleton_class.class_eval(code)
93
+ @gpiochip_lookup_optimized = true
94
+ end
95
+
96
+ # Make a new tuple, given a human-readable pin number, using values from the map.
97
+ def gpio_tuple(index)
98
+ return gpio_tuples[index] if gpio_tuples[index]
99
+
100
+ raise ArgumentError, "pin #{index} does not exist or not included in map" unless map[:pins][index]
101
+ raise ArgumentError, "pin #{index} cannot be used as GPIO. Bound to #{bound_pins[index]}" if bound_pins[index]
102
+
103
+ handle = gpio_handle(map[:pins][index][:chip])
104
+ line = map[:pins][index][:line]
105
+
106
+ gpio_tuples[index] = [handle, line]
107
+ end
108
+
109
+ # Cache tuples of [handle, line_number], keyed to human-readable pin numbers.
110
+ def gpio_tuples
111
+ @gpio_tuples ||= []
112
+ end
113
+
114
+ # Store multiple LGPIO handles, since one board might have multiple chips.
115
+ def gpio_handle(index)
116
+ gpio_handles[index] ||= LGPIO.chip_open(index)
117
+ end
118
+
119
+ def gpio_handles
120
+ @gpio_handles ||= []
121
+ end
122
+
123
+ # Keep track of pins bound by non-GPIO peripherals.
124
+ def bound_pins
125
+ @bound_pins ||= []
126
+ end
127
+
128
+ def convert_pin(pin)
129
+ pin.to_i if pin
130
+ end
131
+
132
+ def pin_is_pwm?(pin)
133
+ map[:pwms][pin]
47
134
  end
48
135
  end
49
136
  end
@@ -0,0 +1,42 @@
1
+ module Denko
2
+ class PiBoard
3
+ def one_wires
4
+ @one_wires ||= []
5
+ end
6
+
7
+ def one_wire_interface(pin)
8
+ handle, gpio = gpio_tuple(pin)
9
+
10
+ # Check if any already exists with that array and return it.
11
+ one_wires.each { |bb| return bb if (handle == bb.handle && gpio == bb.gpio) }
12
+
13
+ # If not, create one.
14
+ one_wire = LGPIO::OneWire.new(handle, gpio)
15
+ one_wires << one_wire
16
+ one_wire
17
+ end
18
+
19
+ def one_wire_reset(gpio, check_presence=0)
20
+ interface = one_wire_interface(gpio)
21
+ presence = interface.reset ? 0 : 1
22
+ self.update(gpio, [presence]) if check_presence != 0
23
+ end
24
+
25
+ def one_wire_search(gpio, branch_mask)
26
+ interface = one_wire_interface(gpio)
27
+ result_array = interface.search_pass(branch_mask)
28
+ self.update(gpio, result_array)
29
+ end
30
+
31
+ def one_wire_write(gpio, parasite, *data)
32
+ interface = one_wire_interface(gpio)
33
+ interface.write(data.flatten, parasite: parasite)
34
+ end
35
+
36
+ def one_wire_read(gpio, length)
37
+ interface = one_wire_interface(gpio)
38
+ result_array = interface.read(length)
39
+ self.update(gpio, result_array)
40
+ end
41
+ end
42
+ end
@@ -1,7 +1,12 @@
1
1
  require 'timeout'
2
2
 
3
3
  module Denko
4
- class PiBoard
4
+ class PiBoard
5
+ def hcsr04_read(echo_pin, trigger_pin)
6
+ microseconds = LGPIO.gpio_read_ultrasonic(@gpio_handle, trigger_pin, echo_pin, 10)
7
+ self.update(echo_pin, microseconds.to_s)
8
+ end
9
+
5
10
  def pulse_read(pin, reset: false, reset_time: 0, pulse_limit: 100, timeout: 200)
6
11
  # Validation
7
12
  raise ArgumentError, "error in reset: #{reset}. Should be either #{high} or #{low}" if reset && ![high, low].include?(reset)
@@ -9,74 +14,12 @@ module Denko
9
14
  raise ArgumentError, "errror in pulse_limit: #{pulse_limit}. Should be 0..255 pulses" if (pulse_limit < 0) || (pulse_limit > 0xFF)
10
15
  raise ArgumentError, "errror in timeout: #{timeout}. Should be 0..65535 milliseconds" if (timeout < 0) || (timeout > 0xFFFF)
11
16
 
12
- if reset
13
- # Reset pulse will be captured as the first 2 edges.
14
- expected_edges = pulse_limit + 2
15
- else
16
- # First edge is a starting reference, so store 1 extra.
17
- expected_edges = pulse_limit + 1
18
- end
19
-
20
- # Storage for absolute tick time of each edge received from pigpio.
21
- edges = Array.new(expected_edges) {0}
22
- edge_index = 0
23
-
24
- # Switch to input mode immediately if no reset.
25
- set_pin_mode(pin, :input) unless reset
26
-
27
- # Add callback to catch edges.
28
- callback = get_gpio(pin).callback(EITHER_EDGE) do |tick, level, pin_cb|
29
- edges[edge_index] = tick
30
- edge_index += 1
31
- callback.cancel if edge_index == expected_edges
32
- end
33
-
34
- # If using reset pulse, do it, and the mode switch, while the callback is active.
35
- if reset
36
- set_pin_mode(pin, :output)
37
- Denko::GPIOD.set_value(pin, reset)
38
- sleep(reset_time / 1000000.0)
39
-
40
- # Set pull to opposite direction of reset.
41
- if (reset == low)
42
- set_pin_mode(pin, :input_pullup)
43
- else
44
- set_pin_mode(pin, :input_pulldown)
45
- end
46
- end
47
-
48
- # Wait for pulses or timeout.
49
- begin
50
- Timeout::timeout(timeout / 1000.0) do
51
- loop do
52
- edge_index == expected_edges ? break : sleep(0.001)
53
- end
54
- end
55
- rescue Timeout::Error
56
- # Allow less than pulse_limit to be read.
57
- end
58
- callback.cancel
59
-
60
- # Ignore the first 2 edges (enable pulse) if reset used, 1 edge (starting reference) if not.
61
- pulse_offset = reset ? 2 : 1
62
- pulse_count = edge_index - pulse_offset
63
-
64
- # Handle no pulses read.
65
- if pulse_count < 1
66
- self.update(pin, "")
67
- return nil
17
+ pulses = LGPIO.gpio_read_pulses_us(@gpio_handle, pin, reset_time, reset, pulse_limit, timeout)
18
+ if pulses.class == Array
19
+ self.update(pin, pulses.join(","))
20
+ elsif pulse.class == Integer
21
+ raise "could not read pulses from GPIO #{pin}. LGPIO error: #{pulses}"
68
22
  end
69
-
70
- # Convert from edge times to pulses.
71
- i = 0
72
- pulses = Array.new(pulse_count) {0}
73
- while i < pulse_count
74
- pulses[i] = edges[i+pulse_offset] - edges[i+pulse_offset-1]
75
- i += 1
76
- end
77
-
78
- # Format pulses as if coming from a microcontroller, and update the component.
79
- self.update(pin, pulses.join(","))
80
23
  end
81
24
  end
82
25
  end
@@ -1,100 +1,74 @@
1
1
  module Denko
2
2
  class PiBoard
3
+ def spi_limit
4
+ 4096
5
+ end
3
6
 
4
- def spi_config(mode, bit_order)
5
- # Config is a 32-bit mask, where bits 0 and 1 are a 2-bit number equal to the SPI mode.
6
- # Default to SPI mode 0 when none given.
7
- config = mode || 0
8
- raise ArgumentError, "invalid SPI mode #{config}" unless (0..3).include? config
9
-
10
- # Default to :msbfirst when bit_order not given.
11
- bit_order ||= :msbfirst
12
- unless (bit_order == :msbfirst) || (bit_order == :lsbfirst)
13
- raise ArgumentError, "invalid bit order #{bitorder}"
14
- end
15
-
16
- # Bits 14 and 15 control MSBFIRST (0) or LSBFIRST (1) for MOSI and MISO respectively.
17
- # Use same order for both directions like Arduino does.
18
- config |= (0b11 << 14) if bit_order == :lsbfirst
19
-
20
- # Use SPI1 interface instead of SPI0.
21
- # Setting bit 8 means we're using SPI1.
22
- config |= (0b1 << 8)
7
+ def spi_flags(mode)
8
+ mode ||= 0
9
+ raise ArgumentError, "invalid SPI mode #{mode}" unless (0..3).include? mode
23
10
 
24
- # Bits 5-7 set leaves all CE pins free, and allows any GPIO to be used for chip enable.
25
- # We toggle separately in #spi_transfer.
26
- config |= (0b111 << 5)
11
+ # Flags is a 32-bit mask. Bits [1..0] are the SPI mode. Default to 0.
12
+ config = mode
27
13
 
28
14
  return config
29
15
  end
30
16
 
31
- # CMD = 26
32
- def spi_transfer(select_pin, write: [], read: 0, frequency: nil, mode: nil, bit_order: nil)
33
- # Default to 1MHz SPI frequency.
34
- frequency ||= 1000000
35
-
36
- # Make SPI config mask.
37
- config = spi_config(mode, bit_order)
17
+ def spi_transfer(index, select, write:[], read:0, frequency: 1_000_000, mode: 0, bit_order: nil)
18
+ # Default frequency. Flags just has mode.
19
+ frequency ||= 1_000_000
20
+ flags = spi_flags(mode)
21
+ handle = spi_open(index, frequency, flags)
38
22
 
39
- # Open SPI handle.
40
- spi_open(frequency, config)
41
-
42
- # Chip enable low. select_pin == 255 means no chip enable (mostly for APA102 LEDs).
43
- digital_write(select_pin, 0) unless select_pin == 255
23
+ # Handle select_pin unless it's same as CS0 for this interface.
24
+ digital_write(select, 0) if select && (select != map[:spis][index][:cs0])
25
+ bytes = LGPIO.spi_xfer(handle, write, read)
26
+ spi_close(handle)
27
+ digital_write(select, 1) if select && (select != map[:spis][index][:cs0])
44
28
 
45
- # Do the SPI transfer.
46
- write_bytes = write.pack("C*")
47
- read_bytes = Pigpio::IF.spi_xfer(pi_handle, spi_handle, write_bytes)
29
+ spi_c_error("xfer", bytes, index) if bytes.class == Integer
48
30
 
49
- # Close SPI handle.
50
- spi_close
51
-
52
- # Chip enable high. select_pin == 255 means no chip enable (mostly for APA102 LEDs).
53
- digital_write(select_pin, 1) unless select_pin == 255
54
-
55
- # Handle spi_xfer errors.
56
- raise StandardError, "spi_xfer error, code #{read_bytes}" if read_bytes.class == Integer
57
-
58
- # Handle read bytes.
59
- if read > 0
60
- message = ""
61
-
62
- # Format like a microcontrolelr would. Limit to number of bytes requested.
63
- i = 0
64
- while i < read
65
- message = "#{message},#{read_bytes[i].ord}"
66
- end
31
+ # Update component attached to select pin with read bytes.
32
+ self.update(select, bytes) if (read > 0 && select)
33
+ end
67
34
 
68
- # Call update with the message as if coming from select_pin.
69
- self.update(select_pin, message)
70
- end
35
+ def spi_listen(*arg, **kwargs)
36
+ raise NotImplementedError, "PiBoard#spi_listen not implemented yet"
71
37
  end
72
38
 
73
- # CMD = 27
74
- def spi_listen
39
+ def spi_stop(pin)
75
40
  end
76
41
 
77
- # CMD = 28
78
- def spi_stop
42
+ def spi_listeners
43
+ @spi_listeners ||= Array.new
79
44
  end
80
45
 
81
46
  private
82
47
 
83
- attr_reader :spi_handle
84
-
85
- def spi_open(frequency, config)
86
- # Give SPI channel as 0 (SPI CE0), even though we are toggling chip enable separately.
87
- @spi_handle = Pigpio::IF.spi_open(pi_handle, 0, frequency, config)
88
- raise StandardError, "SPI error, code #{@spi_handle}" if @spi_handle < 0
48
+ def spi_mutex(index)
49
+ spi_mutexes[index] ||= Mutex.new
89
50
  end
90
51
 
91
- def spi_close
92
- Pigpio::IF.spi_close(pi_handle, spi_handle)
93
- @spi_handle = nil
52
+ def spi_mutexes
53
+ @spi_mutexes ||= []
94
54
  end
95
55
 
96
- def spi_listeners
97
- @spi_listeners ||= Array.new
56
+ def spi_open(index, frequency, flags=0x00)
57
+ # Always use 0 (SPI CS0) for channel. We are toggling chip enable separately.
58
+ handle = LGPIO.spi_open(index, 0, frequency, flags)
59
+ spi_c_error("open", handle, index) if handle < 0
60
+ handle
61
+ end
62
+
63
+ def spi_close(handle)
64
+ result = LGPIO.spi_close(handle)
65
+ if result < 0
66
+ raise StandardError, "lgpio C SPI close error: #{result} for handle #{handle}"
67
+ end
68
+ end
69
+
70
+ def spi_c_error(name, error, index)
71
+ raise StandardError, "lgpio C SPI #{name} error: #{error} for /dev/spidev#{index}"
98
72
  end
99
73
  end
100
74
  end
@@ -0,0 +1,41 @@
1
+ module Denko
2
+ class PiBoard
3
+ attr_reader :spi_bbs
4
+
5
+ def spi_bbs
6
+ @spi_bbs ||= []
7
+ end
8
+
9
+ def spi_bb_interface(clock, input, output)
10
+ # Convert the pins into a config array to check.
11
+ ch, cl = gpio_tuple(clock)
12
+ ih, il = input ? gpio_tuple(input) : [nil, nil]
13
+ oh, ol = output ? gpio_tuple(output) : [nil, nil]
14
+ config = [ch, cl, ih, il, oh, ol]
15
+
16
+ # Check if any already exists with that array and return it.
17
+ spi_bbs.each { |bb| return bb if (config == bb.config) }
18
+
19
+ # If not, create one.
20
+ hash = { clock: { handle: ch, line: cl },
21
+ input: { handle: ih, line: il },
22
+ output: { handle: oh, line: ol } }
23
+
24
+ spi_bb = LGPIO::SPIBitBang.new(hash)
25
+ spi_bbs << spi_bb
26
+ spi_bb
27
+ end
28
+
29
+ def spi_bb_transfer(select, clock:, input: nil, output: nil, write: [], read: 0, frequency: nil, mode: nil, bit_order: nil)
30
+ interface = spi_bb_interface(clock, input, output)
31
+ select_hash = select ? { handle: gpio_tuple(select)[0], line: gpio_tuple(select)[0] } : nil
32
+
33
+ bytes = interface.transfer(write: write, read: read, select: select_hash, order: bit_order, mode: mode)
34
+ self.update(select, bytes) if (read > 0 && select)
35
+ end
36
+
37
+ def spi_bb_listen(*arg, **kwargs)
38
+ raise NotImplementedError, "PiBoard#spi_bb_listen not implemented yet"
39
+ end
40
+ end
41
+ end
@@ -2,38 +2,27 @@ module Denko
2
2
  class PiBoard
3
3
  # CMD = 17
4
4
  def tone(pin, frequency, duration=nil)
5
- # 32-bit mask where only the bit corresponding to the GPIO in use is set.
6
- pin_mask = 1 << pin
7
-
8
- if @aarch64
9
- # pigpio doubles wave times on 64-bit systems for some reason. Halve it to compensate.
10
- half_wave_time = (250000.0 / frequency).round
5
+ if @hardware_pwms[pin]
6
+ @hardware_pwms[pin].frequency = frequency
7
+ @hardware_pwms[pin].duty_percent = 33
8
+ sleep duration if duration
11
9
  else
12
- # This is the true halve wave time.
13
- half_wave_time = (500000.0 / frequency).round
14
- end
15
-
16
- # Standard wave setup.
17
- new_wave
18
- wave.tx_stop
19
- wave.clear
20
- wave.add_new
21
-
22
- # Build wave with a single cycle that will repeat.
23
- wave.add_generic [
24
- wave.pulse(pin_mask, 0x00, half_wave_time),
25
- wave.pulse(0x00, pin_mask, half_wave_time)
26
- ]
27
- wave_id = wave.create
10
+ raise ArgumentError, "maximum software PWM frequency is 10 kHz" if frequency > 10_000
11
+ cycles = 0
12
+ cycles = (frequency * duration).round if duration
28
13
 
29
- # Temporary workaround while Wave#send_repeat gets fixed.
30
- Pigpio::IF.wave_send_repeat(@pi_handle, wave_id)
31
- # wave.send_repeat(wave_id)
14
+ sleep 0.05 while (LGPIO.tx_room(@gpio_handle, pin, LGPIO::TX_PWM) == 0)
15
+ LGPIO.tx_pwm(@gpio_handle, pin, frequency, 33, 0, cycles)
16
+ end
32
17
  end
33
18
 
34
19
  # CMD = 18
35
20
  def no_tone(pin)
36
- stop_wave
21
+ digital_write(pin, HIGH)
22
+ end
23
+
24
+ def tone_busy(pin)
25
+ LGPIO.tx_busy(@gpio_handle, pin, LGPIO::TX_PWM)
37
26
  end
38
27
  end
39
28
  end
@@ -1,5 +1,5 @@
1
1
  module Denko
2
2
  class PiBoard
3
- VERSION = '0.13.2'
3
+ VERSION = '0.15.0'
4
4
  end
5
5
  end
@@ -0,0 +1,9 @@
1
+ #
2
+ # A system ruby must be installed at /usr/bin/ruby
3
+ # Change path/to/set_permissions.rb to the full path where you saved the permissions script
4
+ # Ensure the USERNAME constant, in that copy of the script, is set to your Linux username as a string literal.
5
+ #
6
+ SUBSYSTEM=="gpio*", ACTION=="add", PROGRAM="/usr/bin/ruby /path/to/set_permissions.rb gpio"
7
+ SUBSYSTEM=="i2c*", ACTION=="add", PROGRAM="/usr/bin/ruby /path/to/set_permissions.rb i2c"
8
+ SUBSYSTEM=="spidev*", ACTION=="add", PROGRAM="/usr/bin/ruby /path/to/set_permissions.rb spi"
9
+ SUBSYSTEM=="pwm*", ACTION=="add", PROGRAM="/usr/bin/ruby /path/to/set_permissions.rb pwm"