denko-piboard 0.13.1 → 0.14.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +162 -132
  4. data/Rakefile +0 -5
  5. data/board_maps/README.md +59 -0
  6. data/board_maps/orange_pi_zero_2w.yml +85 -0
  7. data/board_maps/radxa_zero3.yml +88 -0
  8. data/board_maps/raspberry_pi.yml +95 -0
  9. data/denko_piboard.gemspec +6 -7
  10. data/examples/digital_io/bench_out.rb +22 -0
  11. data/examples/digital_io/rotary_encoder.rb +31 -0
  12. data/examples/display/ssd1306.rb +43 -0
  13. data/examples/i2c/bitbang_aht10.rb +18 -0
  14. data/examples/i2c/bitbang_search.rb +24 -0
  15. data/examples/i2c/bitbang_ssd1306_bench.rb +29 -0
  16. data/examples/i2c/search.rb +24 -0
  17. data/examples/led/blink.rb +10 -0
  18. data/examples/led/fade.rb +22 -0
  19. data/examples/led/ws2812_bounce.rb +36 -0
  20. data/examples/motor/servo.rb +16 -0
  21. data/examples/pi_system_monitor.rb +24 -13
  22. data/examples/pulse_io/buzzer.rb +34 -0
  23. data/examples/pulse_io/infrared.rb +25 -0
  24. data/examples/sensor/aht10.rb +17 -0
  25. data/examples/sensor/dht.rb +24 -0
  26. data/examples/sensor/ds18b20.rb +59 -0
  27. data/examples/sensor/hcsr04.rb +16 -0
  28. data/examples/sensor/neat_tph_readings.rb +32 -0
  29. data/examples/spi/bb_loopback.rb +31 -0
  30. data/examples/spi/loopback.rb +37 -0
  31. data/examples/spi/output_register.rb +38 -0
  32. data/lib/denko/piboard.rb +11 -2
  33. data/lib/denko/piboard_base.rb +18 -64
  34. data/lib/denko/piboard_core.rb +146 -131
  35. data/lib/denko/piboard_core_optimize_lookup.rb +31 -0
  36. data/lib/denko/piboard_hardware_pwm.rb +27 -0
  37. data/lib/denko/piboard_i2c.rb +59 -82
  38. data/lib/denko/piboard_i2c_bb.rb +48 -0
  39. data/lib/denko/piboard_infrared.rb +7 -44
  40. data/lib/denko/piboard_led_array.rb +9 -0
  41. data/lib/denko/piboard_map.rb +121 -38
  42. data/lib/denko/piboard_one_wire.rb +42 -0
  43. data/lib/denko/piboard_pulse.rb +11 -68
  44. data/lib/denko/piboard_servo.rb +8 -7
  45. data/lib/denko/piboard_spi.rb +44 -74
  46. data/lib/denko/piboard_spi_bb.rb +41 -0
  47. data/lib/denko/piboard_tone.rb +15 -26
  48. data/lib/denko/piboard_version.rb +1 -1
  49. data/scripts/99-denko.rules +9 -0
  50. data/scripts/set_permissions.rb +131 -0
  51. metadata +43 -15
  52. data/ext/gpiod/extconf.rb +0 -9
  53. data/ext/gpiod/gpiod.c +0 -179
  54. data/lib/gpiod.rb +0 -6
@@ -0,0 +1,32 @@
1
+ #
2
+ # This helper method can be used in temp/pressure/humidity sensor examples.
3
+ # Give a hash with readings as float values and it prints them neatly.
4
+ #
5
+ def print_tph_reading(reading)
6
+ elements = []
7
+
8
+ # Temperature
9
+ if reading[:temperature]
10
+ formatted_temp = reading[:temperature].to_f.round(2).to_s.ljust(5, '0')
11
+ elements << "Temperature: #{formatted_temp} \xC2\xB0C"
12
+ end
13
+
14
+ # Pressure
15
+ if reading[:pressure]
16
+ formatted_pressure = reading[:pressure].round(2).to_s.ljust(7, '0')
17
+ elements << "Pressure: #{formatted_pressure} Pa"
18
+ end
19
+
20
+ # Humidity
21
+ if reading[:humidity]
22
+ formatted_humidity = reading[:humidity].round(2).to_s.ljust(5, '0')
23
+ elements << "Humidity: #{formatted_humidity} %"
24
+ end
25
+
26
+ return if elements.empty?
27
+
28
+ # Time
29
+ print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - "
30
+
31
+ puts elements.join(" | ")
32
+ end
@@ -0,0 +1,31 @@
1
+ require 'denko'
2
+ require 'denko/piboard'
3
+
4
+ CLOCK_PIN = 256
5
+ INPUT_PIN = 271
6
+ OUTPUT_PIN = 258
7
+ SELECT_PIN = 272
8
+
9
+ board = Denko::PiBoard.new
10
+ bus = Denko::SPI::BitBang.new(board: board, pins: { clock: CLOCK_PIN, input: INPUT_PIN, output: OUTPUT_PIN })
11
+
12
+ TEST_DATA = [0, 1, 2, 3, 4, 5, 6, 7]
13
+
14
+ # Create a simple test component class.
15
+ class SPITester
16
+ include Denko::SPI::Peripheral::SinglePin
17
+ end
18
+ spi_tester = SPITester.new(bus: bus, pin: SELECT_PIN)
19
+ spi_tester.add_callback do |rx_bytes|
20
+ # If MOSI and MISO are connected this should match TEST_DATA.
21
+ # If not, should be 8 bytes of 255.
22
+ puts "RX bytes: #{rx_bytes.inspect}"
23
+ end
24
+
25
+ # Send the test data.
26
+ puts "TX bytes: #{TEST_DATA.inspect}"
27
+ spi_tester.spi_transfer(write: TEST_DATA, read: 8)
28
+
29
+ # Wait for read callback to run.
30
+ sleep 1
31
+ board.finish_write
@@ -0,0 +1,37 @@
1
+ require 'denko'
2
+ require 'denko/piboard'
3
+
4
+ # Use board map from ~/.denko_piboard_map.yml
5
+ board = Denko::PiBoard.new
6
+
7
+ # Use the first hardware SPI interface, and its defined :cs0 pin.
8
+ spi_index = board.map[:spis].keys.first
9
+ chip_select = board.map[:spis][spi_index][:cs0]
10
+ bus = Denko::SPI::Bus.new(board: board, index: spi_index)
11
+
12
+ TEST_DATA = [0, 1, 2, 3, 4, 5, 6, 7]
13
+
14
+ # Create a simple test component class.
15
+ class SPITester
16
+ include Denko::SPI::Peripheral::SinglePin
17
+ end
18
+ spi_tester = SPITester.new(bus: bus, pin: chip_select)
19
+ spi_tester.add_callback do |rx_bytes|
20
+ # If MOSI and MISO are connected this should match TEST_DATA.
21
+ # If not, should be 8 bytes of 255.
22
+ puts "Result : #{rx_bytes.inspect}"
23
+ end
24
+
25
+ # Send and receive same data.
26
+ puts "Tx 8 / Rx 8 : #{TEST_DATA.inspect}"
27
+ spi_tester.spi_transfer(write: TEST_DATA, read: 8)
28
+
29
+ puts "Tx 8 / Rx 12: #{TEST_DATA.inspect}"
30
+ spi_tester.spi_transfer(write: TEST_DATA, read: 12)
31
+
32
+ puts "Tx 8 / Rx 4 : #{TEST_DATA.inspect}"
33
+ spi_tester.spi_transfer(write: TEST_DATA, read: 4)
34
+
35
+ # Wait for read callback to run.
36
+ sleep 1
37
+ board.finish_write
@@ -0,0 +1,38 @@
1
+ #
2
+ # Example of LED connected through an output shift register (74HC595).
3
+ # Can be used over either a bit bang or hardware SPI interface.
4
+ #
5
+ require 'denko/piboard'
6
+
7
+ LED_PIN = 3 # On the register's parallel outputs
8
+
9
+ # Use board map from ~/.denko_piboard_map.yml
10
+ board = Denko::PiBoard.new
11
+
12
+ # Use the first hardware SPI interface, and its defined :cs0 pin.
13
+ spi_index = board.map[:spis].keys.first
14
+ chip_select = board.map[:spis][spi_index][:cs0]
15
+ bus = Denko::SPI::Bus.new(board: board, index: spi_index)
16
+
17
+ # OutputRegister needs a bus and pin (chip select).
18
+ # Other options and their defaults:
19
+ # bytes: 1 - For daisy-chaining registers
20
+ # spi_frequency: 1000000 - Only affects hardware SPI interfaces
21
+ # spi_mode: 0
22
+ # spi_bit_order: :msbfirst
23
+ #
24
+ register = Denko::SPI::OutputRegister.new(bus: bus, pin: chip_select)
25
+
26
+ # Turn on the LED by setting the corresponding bit, then writing to the register.
27
+ register.bit_set(LED_PIN, 1)
28
+ register.write
29
+
30
+ # OutputRegister is a BoardProxy. It has #digital_write, and other methods from Board.
31
+ register.digital_write(LED_PIN, 0)
32
+
33
+ # DigitalOutputs can treat it as a Board.
34
+ led = Denko::LED.new(board: register, pin: LED_PIN)
35
+
36
+ # Blink the LED and sleep the main thread.
37
+ led.blink 0.5
38
+ sleep
data/lib/denko/piboard.rb CHANGED
@@ -1,11 +1,20 @@
1
- require_relative '../gpiod'
2
1
  require_relative 'piboard_version'
3
2
  require_relative 'piboard_map'
4
3
  require_relative 'piboard_base'
4
+
5
5
  require_relative 'piboard_core'
6
+
6
7
  require_relative 'piboard_pulse'
7
- require_relative 'piboard_servo'
8
8
  require_relative 'piboard_tone'
9
+
10
+ require_relative 'piboard_hardware_pwm'
11
+ require_relative 'piboard_servo'
9
12
  require_relative 'piboard_infrared'
13
+
10
14
  require_relative 'piboard_i2c'
11
15
  require_relative 'piboard_spi'
16
+ require_relative 'piboard_led_array'
17
+
18
+ require_relative 'piboard_i2c_bb'
19
+ require_relative 'piboard_spi_bb'
20
+ require_relative 'piboard_one_wire'
@@ -1,81 +1,35 @@
1
1
  require 'denko'
2
- require 'pigpio'
2
+ require 'lgpio'
3
3
 
4
4
  module Denko
5
5
  class PiBoard
6
- include Pigpio::Constant
7
-
8
- attr_reader :high, :low, :pwm_high
9
-
10
- def initialize
11
- # On 64-bit systems there's a pigpio bug where wavelengths are doubled.
12
- @aarch64 = RUBY_PLATFORM.match(/aarch64/)
13
-
14
- # Pin state
15
- @pins = []
16
- @pwms = []
17
-
18
- # Listener state
19
- @pin_listeners = []
20
- @listen_mutex = Mutex.new
21
- @listen_states = Array.new(32) { 0 }
22
- @listen_thread = nil
23
- @listen_reading = 0
24
-
25
- # PiGPIO callback state. Unused for now.
26
- @pin_callbacks = []
6
+ include Behaviors::Subcomponents
27
7
 
28
- # Logic levels
29
- @low = 0
30
- @high = 1
31
- @pwm_high = 255
8
+ LOW = 0
9
+ HIGH = 1
10
+ PWM_HIGH = 100
11
+ def low; LOW; end
12
+ def high; HIGH; end
13
+ def pwm_high; PWM_HIGH; end
14
+ def analog_write_resolution; 8; end
15
+
16
+ def initialize(map_yaml_file=nil)
17
+ map_yaml_file ||= Dir.home + "/" + DEFAULT_MAP_FILE
18
+ unless File.exist?(map_yaml_file)
19
+ raise StandardError, "board map file not given to PiBoard#new, and does not exist at #{map_yaml_file}"
20
+ end
32
21
 
33
- # Use the pigpiod interface directly.
34
- @pi_handle = Pigpio::IF.pigpio_start
35
- exit(-1) if @pi_handle < 0
36
-
37
- # Open the libgpiod interface too.
38
- Denko::GPIOD.open_chip
22
+ parse_map(map_yaml_file)
39
23
  end
40
24
 
41
25
  def finish_write
42
- Pigpio::IF.pigpio_stop(@pi_handle)
43
- Denko::GPIOD.close_chip
26
+ gpio_handles.each { |h| LGPIO.chip_close(h) if h }
44
27
  end
45
28
 
46
- #
47
- # Use standard Subcomponents behavior.
48
- #
49
- include Behaviors::Subcomponents
50
-
51
- def update(pin, message, time=nil)
29
+ def update(pin, message)
52
30
  if single_pin_components[pin]
53
31
  single_pin_components[pin].update(message)
54
32
  end
55
33
  end
56
-
57
- private
58
-
59
- attr_reader :pi_handle, :wave
60
-
61
- def get_gpio(pin)
62
- @pins[pin] ||= Pigpio::UserGPIO.new(@pi_handle, pin)
63
- end
64
-
65
- def pwm_clear(pin)
66
- @pwms[pin] = nil
67
- end
68
-
69
- def new_wave
70
- stop_wave
71
- @wave = Pigpio::Wave.new(pi_handle)
72
- end
73
-
74
- def stop_wave
75
- return unless wave
76
- wave.tx_stop
77
- wave.clear
78
- @wave = nil
79
- end
80
34
  end
81
35
  end
@@ -1,97 +1,120 @@
1
1
  module Denko
2
2
  class PiBoard
3
- # CMD = 0
4
- def set_pin_mode(pin, mode=:input, glitch_time=nil)
5
- # Validate pin
6
- # Validate output type
7
-
8
- # Close the line in libgpiod, if was already open.
9
- Denko::GPIOD.close_line(pin)
3
+ REPORT_SLEEP_TIME = 0.001
4
+ INPUT_MODES = [:input, :input_pullup, :input_pulldown]
5
+ OUTPUT_MODES = [:output, :output_pwm, :output_open_drain, :output_open_source]
6
+ PIN_MODES = INPUT_MODES + OUTPUT_MODES
7
+
8
+ def set_pin_mode(pin, mode=:input, options={})
9
+ # Is the mode valid?
10
+ unless PIN_MODES.include?(mode)
11
+ raise ArgumentError, "cannot set mode: #{mode}. Should be one of: #{PIN_MODES.inspect}"
12
+ end
10
13
 
11
- pwm_clear(pin)
12
- gpio = get_gpio(pin)
14
+ # If pin is bound to hardware PWM, allow it to be used as :output_pwm. OR :output.
15
+ if map[:pwms][pin]
16
+ if (mode == :output_pwm)
17
+ return hardware_pwm_from_pin(pin, options)
18
+ elsif (mode == :output)
19
+ puts "WARNING: using hardware PWM on pin #{pin} as GPIO. Will be slower than regular GPIO."
20
+ return hardware_pwm_from_pin(pin, options)
21
+ else
22
+ raise "Pin #{pin} is bound to hardware PWM. It can only be used as :output or :output_pwm"
23
+ end
24
+ end
13
25
 
14
- # Output
15
- if mode.to_s.match /output/
16
- gpio.mode = PI_OUTPUT
17
-
18
- # Use pigpiod for setup, but still open line in libgpiod.
19
- Denko::GPIOD.open_line_output(pin)
26
+ # Attempt to free the pin.
27
+ LGPIO.gpio_free(*gpio_tuple(pin))
20
28
 
21
- # Input
29
+ # Try to claim the GPIO.
30
+ if OUTPUT_MODES.include?(mode)
31
+ flags = LGPIO::SET_PULL_NONE
32
+ flags = LGPIO::SET_OPEN_DRAIN if mode == :output_open_drain
33
+ flags = LGPIO::SET_OPEN_SOURCE if mode == :output_open_source
34
+ result = LGPIO.gpio_claim_output(*gpio_tuple(pin), flags, LOW)
22
35
  else
23
- gpio.mode = PI_INPUT
36
+ flags = LGPIO::SET_PULL_NONE
37
+ flags = LGPIO::SET_PULL_UP if mode == :input_pullup
38
+ flags = LGPIO::SET_PULL_DOWN if mode == :input_pulldown
39
+ result = LGPIO.gpio_claim_input(*gpio_tuple(pin), flags)
40
+ end
41
+ raise "could not claim GPIO for pin #{pin}. lgpio C error: #{result}" if result < 0
24
42
 
25
- # State change valid only if steady for this many microseconds.
26
- # Only applies to callbacks hooked through pigpiod.
27
- if glitch_time
28
- gpio.glitch_filter(glitch_time)
29
- end
43
+ pin_configs[pin] = pin_configs[pin].to_h.merge(mode: mode).merge(options)
44
+ end
30
45
 
31
- # Pull down/up/none
32
- if mode.to_s.match /pulldown/
33
- gpio.pud = PI_PUD_DOWN
34
- elsif mode.to_s.match /pullup/
35
- gpio.pud = PI_PUD_UP
36
- else
37
- gpio.pud = PI_PUD_OFF
38
- end
39
-
40
- # Use pigpiod for setup, but still open line in libgpiod.
41
- Denko::GPIOD.open_line_input(pin)
42
- end
46
+ def set_pin_debounce(pin, debounce_time)
47
+ return unless debounce_time
48
+ result = LGPIO.gpio_set_debounce(*gpio_tuple(pin), debounce_time)
49
+ raise "could not set debounce for pin #{pin}. lgpio C error: #{result}" if result < 0
50
+
51
+ pin_configs[pin] = pin_configs[pin].to_h.merge(debounce_time: debounce_time)
43
52
  end
44
53
 
45
- # CMD = 1
46
54
  def digital_write(pin, value)
47
- pwm_clear(pin)
48
- Denko::GPIOD.set_value(pin, value)
55
+ if hardware_pwms[pin]
56
+ hardware_pwms[pin].duty_percent = (value == 0) ? 0 : 100
57
+ else
58
+ handle, line = gpio_tuple(pin)
59
+ LGPIO.gpio_write(handle, line, value)
60
+ end
49
61
  end
50
-
51
- # CMD = 2
62
+
52
63
  def digital_read(pin)
53
- unless @pwms[pin]
54
- state = Denko::GPIOD.get_value(pin)
55
- self.update(pin, state)
56
- return state
64
+ if hardware_pwms[pin]
65
+ state = hardware_pwms[pin].duty_percent
66
+ else
67
+ handle, line = gpio_tuple(pin)
68
+ state = LGPIO.gpio_read(handle, line)
57
69
  end
70
+ self.update(pin, state)
71
+ return state
58
72
  end
59
-
60
- # CMD = 3
61
- def pwm_write(pin, value)
62
- # Disable servo if necessary.
63
- pwm_clear(pin) if @pwms[pin] == :servo
64
73
 
65
- unless @pwms[pin]
66
- @pwms[pin] = get_gpio(pin).pwm
67
- @pwms[pin].frequency = 1000
74
+ def pwm_write(pin, duty)
75
+ if hardware_pwms[pin]
76
+ hardware_pwms[pin].duty_percent = duty
77
+ else
78
+ frequency = pin_configs[pin][:frequency] || 1000
79
+ handle, line = gpio_tuple(pin)
80
+ LGPIO.tx_pwm(handle, line, frequency, duty, 0, 0)
68
81
  end
69
- @pwms[pin].dutycycle = value
70
82
  end
71
83
 
72
- # PiGPIO native callbacks. Unused now.
73
- def set_alert(pin, state=:off, options={})
74
- # Listener on
75
- if state == :on && !@pin_callbacks[pin]
76
- callback = get_gpio(pin).callback(EITHER_EDGE) do |tick, level, pin_cb|
77
- update(pin_cb, level, tick)
78
- end
79
- @pin_callbacks[pin] = callback
84
+ def dac_write(pin, value)
85
+ raise NotImplementedError, "PiBoard#dac_write not implemented"
86
+ end
80
87
 
81
- # Listener off
82
- else
83
- @pin_callbacks[pin].cancel if @pin_callbacks[pin]
84
- @pin_callbacks[pin] = nil
85
- end
88
+ def analog_read(pin, negative_pin=nil, gain=nil, sample_rate=nil)
89
+ raise NotImplementedError, "PiBoard#analog_read not implemented"
86
90
  end
87
91
 
88
- # CMD = 6
89
92
  def set_listener(pin, state=:off, options={})
90
- # Listener on
91
- if state == :on && !@pin_listeners.include?(pin)
92
- add_listener(pin)
93
- else
94
- remove_listener(pin)
93
+ # Validate listener is digital only.
94
+ options[:mode] ||= :digital
95
+ unless options[:mode] == :digital
96
+ raise ArgumentError, "error in mode: #{options[:mode]}. Should be one of: [:digital]"
97
+ end
98
+
99
+ # Validate state.
100
+ unless (state == :on) || (state == :off)
101
+ raise ArgumentError, "error in state: #{options[:state]}. Should be one of: [:on, :off]"
102
+ end
103
+
104
+ # Only way to stop getting alerts is to free the GPIO.
105
+ LGPIO.gpio_free(*gpio_tuple(pin))
106
+
107
+ # Reclaim it as input if needed.
108
+ config = pin_configs[pin]
109
+ config ||= { mode: :input, debounce_time: nil } if state == :on
110
+ if config
111
+ set_pin_mode(pin, config[:mode])
112
+ set_pin_debounce(pin, config[:debounce_time])
113
+ end
114
+
115
+ if state == :on
116
+ LGPIO.gpio_claim_alert(*gpio_tuple(pin), 0, LGPIO::BOTH_EDGES)
117
+ start_alert_thread unless @alert_thread
95
118
  end
96
119
  end
97
120
 
@@ -99,77 +122,69 @@ module Denko
99
122
  set_listener(pin, :on, {})
100
123
  end
101
124
 
125
+ def analog_listen(pin, divider=16)
126
+ raise NotImplementedError, "PiBoard#analog_read not implemented"
127
+ end
128
+
102
129
  def stop_listener(pin)
103
130
  set_listener(pin, :off)
104
131
  end
105
132
 
106
- def add_listener(pin)
107
- @listen_mutex.synchronize do
108
- @pin_listeners |= [pin]
109
- @pin_listeners.sort!
110
- @listen_states[pin] = Denko::GPIOD.get_value(pin)
111
- end
112
- start_listen_thread
133
+ def halt_resume_check
134
+ raise NotImplementedError, "PiBoard#halt_resume_check not implemented"
113
135
  end
114
-
115
- def remove_listener(pin)
116
- @listen_mutex.synchronize do
117
- @pin_listeners.delete(pin)
118
- @listen_states[pin] = nil
119
- end
136
+
137
+ def set_register_divider(value)
138
+ raise NotImplementedError, "PiBoard#set_register_divider not implemented"
139
+ end
140
+
141
+ def set_analog_write_resolution(value)
142
+ raise NotImplementedError, "PiBoard#set_analog_write_resolution not implemented"
143
+ end
144
+
145
+ def set_analog_read_resolution(value)
146
+ raise NotImplementedError, "PiBoard#set_analog_read_resolution not implemented"
147
+ end
148
+
149
+ def binary_echo(pin, data=[])
150
+ raise NotImplementedError, "PiBoard#binary_echo not implemented"
151
+ end
152
+
153
+ def micro_delay(duration)
154
+ LGPIO.micro_delay(duration)
155
+ end
156
+
157
+ def start_alert_thread
158
+ start_gpio_reports
159
+ @alert_thread = Thread.new { loop { get_report } }
160
+ end
161
+
162
+ def stop_alert_thread
163
+ Thread.kill(@alert_thread) if @alert_thread
164
+ @alert_thread = nil
120
165
  end
121
-
122
- def start_listen_thread
123
- return if @listen_thread
124
-
125
- @listen_thread = Thread.new do
126
- #
127
- # @listen_monitor_thread will adjust sleep time dyanmically,
128
- # targeting even timing of 1 millisecond between loops.
129
- #
130
- @listen_count = 0
131
- @listen_sleep = 0.001
132
- start_time = Time.now
133
-
134
- loop do
135
- @listen_mutex.synchronize do
136
- @pin_listeners.each do |pin|
137
- @listen_reading = Denko::GPIOD.get_value(pin)
138
- self.update(pin, @listen_reading) if (@listen_reading != @listen_states[pin])
139
- @listen_states[pin] = @listen_reading
140
- end
166
+
167
+ def get_report
168
+ report = LGPIO.gpio_get_report
169
+ if report
170
+ if chip = alert_lut[report[:chip]]
171
+ if pin = chip[report[:gpio]]
172
+ update(pin, report[:level])
141
173
  end
142
- @listen_count += 1
143
- sleep(@listen_sleep)
144
174
  end
175
+ else
176
+ sleep 0.001
145
177
  end
178
+ end
146
179
 
147
- @listen_monitor_thread = Thread.new do
148
- loop do
149
- # Sample the listen rate over 5 seconds.
150
- time1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
151
- count1 = @listen_count
152
- sleep(5)
153
- time2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
154
- count2 = @listen_count
155
-
156
- # Quick maths.
157
- loops = count2 - count1
158
- time = time2 - time1
159
- active_time_per_loop = (time - (loops * @listen_sleep)) / loops
160
-
161
- # Target 1 millisecond.
162
- @listen_sleep = 0.001 - active_time_per_loop
163
- @listen_sleep = 0 if @listen_sleep < 0
164
- end
165
- end
180
+ def start_gpio_reports
181
+ return if @reporting_started
182
+ LGPIO.gpio_start_reporting
183
+ @reporting_started = true
166
184
  end
167
-
168
- def stop_listen_thread
169
- @listen_monitor_thread.kill
170
- @listen_monitor_thread = nil
171
- @listen_thread.kill
172
- @listen_thread = nil
185
+
186
+ def pin_configs
187
+ @pin_configs ||= []
173
188
  end
174
189
  end
175
190
  end
@@ -0,0 +1,31 @@
1
+ #
2
+ # DO NOT REQUIRE THIS FILE. It is evaluated at runtime, if applicable.
3
+ #
4
+ # Optimized method overrides when all GPIO pins are on one gpiochip.
5
+ #
6
+ def digital_write(pin, value)
7
+ if hardware_pwms[pin]
8
+ hardware_pwms[pin].duty_percent = (value == 0) ? 0 : 100
9
+ else
10
+ LGPIO.gpio_write(__GPIOCHIP_SINGLE_HANDLE__, pin, value)
11
+ end
12
+ end
13
+
14
+ def digital_read(pin)
15
+ if hardware_pwms[pin]
16
+ state = hardware_pwms[pin].duty_percent
17
+ else
18
+ state = LGPIO.gpio_read(__GPIOCHIP_SINGLE_HANDLE__, pin)
19
+ end
20
+ self.update(pin, state)
21
+ return state
22
+ end
23
+
24
+ def get_report
25
+ report = LGPIO.gpio_get_report
26
+ if report
27
+ update(report[:gpio], report[:level])
28
+ else
29
+ sleep REPORT_SLEEP_TIME
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ module Denko
2
+ class PiBoard
3
+ def hardware_pwms
4
+ @hardware_pwms ||= []
5
+ end
6
+
7
+ def hardware_pwm_from_pin(pin, options={})
8
+ # Find existing hardware PWM, change the frequency if needed, then return it.
9
+ frequency = options[:frequency]
10
+ pwm = hardware_pwms[pin]
11
+ if pwm
12
+ pwm.frequency = frequency if (frequency && pwm.frequency != frequency)
13
+ return pwm
14
+ end
15
+
16
+ # Make sure it's in the board map before trying to use it.
17
+ raise StandardError, "no hardware PWM in board map for pin #{pin}" unless map[:pwms][pin]
18
+
19
+ # Make a new hardware PWM.
20
+ pwmchip = map[:pwms][pin][:pwmchip]
21
+ channel = map[:pwms][pin][:channel]
22
+ frequency ||= 1000
23
+ pwm = LGPIO::HardwarePWM.new(pwmchip, channel, frequency: frequency)
24
+ hardware_pwms[pin] = pwm
25
+ end
26
+ end
27
+ end