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,94 +1,122 @@
1
1
  module Denko
2
2
  class PiBoard
3
- # CMD = 0
4
- def set_pin_mode(pin, mode=:input, glitch_time=nil)
5
- # Close the line in libgpiod, if was already open.
6
- Denko::GPIOD.close_line(pin)
7
-
8
- pwm_clear(pin)
9
- gpio = get_gpio(pin)
10
-
11
- # Output
12
- if mode.to_s.match /output/
13
- gpio.mode = PI_OUTPUT
14
-
15
- # Use pigpiod for setup, but still open line in libgpiod.
16
- Denko::GPIOD.open_line_output(pin)
17
-
18
- # Input
19
- else
20
- gpio.mode = PI_INPUT
21
-
22
- # State change valid only if steady for this many microseconds.
23
- # Only applies to callbacks hooked through pigpiod.
24
- if glitch_time
25
- gpio.glitch_filter(glitch_time)
26
- end
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
27
13
 
28
- # Pull down/up/none
29
- if mode.to_s.match /pulldown/
30
- gpio.pud = PI_PUD_DOWN
31
- elsif mode.to_s.match /pullup/
32
- gpio.pud = PI_PUD_UP
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)
33
21
  else
34
- gpio.pud = PI_PUD_OFF
22
+ raise "Pin #{pin} is bound to hardware PWM. It can only be used as :output or :output_pwm"
35
23
  end
36
-
37
- # Use pigpiod for setup, but still open line in libgpiod.
38
- Denko::GPIOD.open_line_input(pin)
39
24
  end
25
+
26
+ # Attempt to free the pin.
27
+ LGPIO.gpio_free(*gpio_tuple(pin))
28
+
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)
35
+ else
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
42
+
43
+ pin_configs[pin] = pin_configs[pin].to_h.merge(mode: mode).merge(options)
44
+ end
45
+
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)
40
52
  end
41
53
 
42
- # CMD = 1
43
54
  def digital_write(pin, value)
44
- pwm_clear(pin)
45
- 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
46
61
  end
47
-
48
- # CMD = 2
62
+
49
63
  def digital_read(pin)
50
- unless @pwms[pin]
51
- state = Denko::GPIOD.get_value(pin)
52
- self.update(pin, state)
53
- 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)
54
69
  end
70
+ self.update(pin, state)
71
+ return state
55
72
  end
56
-
57
- # CMD = 3
58
- def pwm_write(pin, value)
59
- # Disable servo if necessary.
60
- pwm_clear(pin) if @pwms[pin] == :servo
61
73
 
62
- unless @pwms[pin]
63
- @pwms[pin] = get_gpio(pin).pwm
64
- @pwms[pin].frequency = 1000
74
+ def pwm_write(pin, duty)
75
+ if hardware_pwms[pin]
76
+ hardware_pwms[pin].duty = duty
77
+ else
78
+ handle, line = gpio_tuple(pin)
79
+ frequency = pin_configs[pin][:frequency] || 1000
80
+ period = pin_configs[pin][:period] || 1_000_000
81
+ duty_percent = (duty.to_f / period) * 100
82
+ LGPIO.tx_pwm(handle, line, frequency, duty_percent, 0, 0)
65
83
  end
66
- @pwms[pin].dutycycle = value
67
84
  end
68
85
 
69
- # PiGPIO native callbacks. Unused now.
70
- def set_alert(pin, state=:off, options={})
71
- # Listener on
72
- if state == :on && !@pin_callbacks[pin]
73
- callback = get_gpio(pin).callback(EITHER_EDGE) do |tick, level, pin_cb|
74
- update(pin_cb, level, tick)
75
- end
76
- @pin_callbacks[pin] = callback
86
+ def dac_write(pin, value)
87
+ raise NotImplementedError, "PiBoard#dac_write not implemented"
88
+ end
77
89
 
78
- # Listener off
79
- else
80
- @pin_callbacks[pin].cancel if @pin_callbacks[pin]
81
- @pin_callbacks[pin] = nil
82
- end
90
+ def analog_read(pin, negative_pin=nil, gain=nil, sample_rate=nil)
91
+ raise NotImplementedError, "PiBoard#analog_read not implemented"
83
92
  end
84
93
 
85
- # CMD = 6
86
94
  def set_listener(pin, state=:off, options={})
87
- # Listener on
88
- if state == :on && !@pin_listeners.include?(pin)
89
- add_listener(pin)
90
- else
91
- remove_listener(pin)
95
+ # Validate listener is digital only.
96
+ options[:mode] ||= :digital
97
+ unless options[:mode] == :digital
98
+ raise ArgumentError, "error in mode: #{options[:mode]}. Should be one of: [:digital]"
99
+ end
100
+
101
+ # Validate state.
102
+ unless (state == :on) || (state == :off)
103
+ raise ArgumentError, "error in state: #{options[:state]}. Should be one of: [:on, :off]"
104
+ end
105
+
106
+ # Only way to stop getting alerts is to free the GPIO.
107
+ LGPIO.gpio_free(*gpio_tuple(pin))
108
+
109
+ # Reclaim it as input if needed.
110
+ config = pin_configs[pin]
111
+ config ||= { mode: :input, debounce_time: nil } if state == :on
112
+ if config
113
+ set_pin_mode(pin, config[:mode])
114
+ set_pin_debounce(pin, config[:debounce_time])
115
+ end
116
+
117
+ if state == :on
118
+ LGPIO.gpio_claim_alert(*gpio_tuple(pin), 0, LGPIO::BOTH_EDGES)
119
+ start_alert_thread unless @alert_thread
92
120
  end
93
121
  end
94
122
 
@@ -96,77 +124,69 @@ module Denko
96
124
  set_listener(pin, :on, {})
97
125
  end
98
126
 
127
+ def analog_listen(pin, divider=16)
128
+ raise NotImplementedError, "PiBoard#analog_read not implemented"
129
+ end
130
+
99
131
  def stop_listener(pin)
100
132
  set_listener(pin, :off)
101
133
  end
102
134
 
103
- def add_listener(pin)
104
- @listen_mutex.synchronize do
105
- @pin_listeners |= [pin]
106
- @pin_listeners.sort!
107
- @listen_states[pin] = Denko::GPIOD.get_value(pin)
108
- end
109
- start_listen_thread
135
+ def halt_resume_check
136
+ raise NotImplementedError, "PiBoard#halt_resume_check not implemented"
110
137
  end
111
-
112
- def remove_listener(pin)
113
- @listen_mutex.synchronize do
114
- @pin_listeners.delete(pin)
115
- @listen_states[pin] = nil
116
- end
138
+
139
+ def set_register_divider(value)
140
+ raise NotImplementedError, "PiBoard#set_register_divider not implemented"
141
+ end
142
+
143
+ def set_analog_write_resolution(value)
144
+ raise NotImplementedError, "PiBoard#set_analog_write_resolution not implemented"
145
+ end
146
+
147
+ def set_analog_read_resolution(value)
148
+ raise NotImplementedError, "PiBoard#set_analog_read_resolution not implemented"
149
+ end
150
+
151
+ def binary_echo(pin, data=[])
152
+ raise NotImplementedError, "PiBoard#binary_echo not implemented"
153
+ end
154
+
155
+ def micro_delay(duration)
156
+ LGPIO.micro_delay(duration)
157
+ end
158
+
159
+ def start_alert_thread
160
+ start_gpio_reports
161
+ @alert_thread = Thread.new { loop { get_report } }
162
+ end
163
+
164
+ def stop_alert_thread
165
+ Thread.kill(@alert_thread) if @alert_thread
166
+ @alert_thread = nil
117
167
  end
118
-
119
- def start_listen_thread
120
- return if @listen_thread
121
-
122
- @listen_thread = Thread.new do
123
- #
124
- # @listen_monitor_thread will adjust sleep time dyanmically,
125
- # targeting even timing of 1 millisecond between loops.
126
- #
127
- @listen_count = 0
128
- @listen_sleep = 0.001
129
- start_time = Time.now
130
-
131
- loop do
132
- @listen_mutex.synchronize do
133
- @pin_listeners.each do |pin|
134
- @listen_reading = Denko::GPIOD.get_value(pin)
135
- self.update(pin, @listen_reading) if (@listen_reading != @listen_states[pin])
136
- @listen_states[pin] = @listen_reading
137
- end
168
+
169
+ def get_report
170
+ report = LGPIO.gpio_get_report
171
+ if report
172
+ if chip = alert_lut[report[:chip]]
173
+ if pin = chip[report[:gpio]]
174
+ update(pin, report[:level])
138
175
  end
139
- @listen_count += 1
140
- sleep(@listen_sleep)
141
176
  end
177
+ else
178
+ sleep 0.001
142
179
  end
180
+ end
143
181
 
144
- @listen_monitor_thread = Thread.new do
145
- loop do
146
- # Sample the listen rate over 5 seconds.
147
- time1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
148
- count1 = @listen_count
149
- sleep(5)
150
- time2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
151
- count2 = @listen_count
152
-
153
- # Quick maths.
154
- loops = count2 - count1
155
- time = time2 - time1
156
- active_time_per_loop = (time - (loops * @listen_sleep)) / loops
157
-
158
- # Target 1 millisecond.
159
- @listen_sleep = 0.001 - active_time_per_loop
160
- @listen_sleep = 0 if @listen_sleep < 0
161
- end
162
- end
182
+ def start_gpio_reports
183
+ return if @reporting_started
184
+ LGPIO.gpio_start_reporting
185
+ @reporting_started = true
163
186
  end
164
-
165
- def stop_listen_thread
166
- @listen_monitor_thread.kill
167
- @listen_monitor_thread = nil
168
- @listen_thread.kill
169
- @listen_thread = nil
187
+
188
+ def pin_configs
189
+ @pin_configs ||= []
170
190
  end
171
191
  end
172
192
  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,32 @@
1
+ module Denko
2
+ class PiBoard
3
+ # Check if a GPIO number is bound to a hardware PWM.
4
+ def pin_is_pwm?(pin)
5
+ map[:pwms][pin]
6
+ end
7
+
8
+ def hardware_pwms
9
+ @hardware_pwms ||= []
10
+ end
11
+
12
+ def hardware_pwm_from_pin(pin, options={})
13
+ # Find existing hardware PWM, change the frequency if needed, then return it.
14
+ frequency = options[:frequency]
15
+ pwm = hardware_pwms[pin]
16
+ if pwm
17
+ pwm.frequency = frequency if (frequency && pwm.frequency != frequency)
18
+ return pwm
19
+ end
20
+
21
+ # Make sure it's in the board map before trying to use it.
22
+ raise StandardError, "no hardware PWM in board map for pin #{pin}" unless map[:pwms][pin]
23
+
24
+ # Make a new hardware PWM.
25
+ pwmchip = map[:pwms][pin][:pwmchip]
26
+ channel = map[:pwms][pin][:channel]
27
+ frequency ||= 1000
28
+ pwm = LGPIO::HardwarePWM.new(pwmchip, channel, frequency: frequency)
29
+ hardware_pwms[pin] = pwm
30
+ end
31
+ end
32
+ end
@@ -1,119 +1,96 @@
1
1
  module Denko
2
2
  class PiBoard
3
+ # Address ranges 0..7 and 120..127 are reserved.
4
+ # Try each address in 8..119 (0x08 to 0x77).
5
+ I2C_ADDRESS_RANGE = (0x08..0x77).to_a
6
+
3
7
  # Maximum amount of bytes that can be read or written in a single I2C operation.
4
8
  def i2c_limit
5
9
  65535
6
10
  end
7
11
 
8
- def i2c_mutex
9
- @i2c_mutex ||= Mutex.new
10
- end
11
-
12
12
  # CMD = 33
13
- def i2c_search
14
- i2c_mutex.synchronize do
13
+ def i2c_search(index)
14
+ i2c_mutex(index).synchronize do
15
15
  found_string = ""
16
16
 
17
- # Address ranges 0..7 and 120..127 are reserved.
18
- # Try each address in 8..19 (0x08 to 0x77).
19
- (0x08..0x77).each do |address|
20
- i2c_open(1, address)
21
- byte = Pigpio::IF.i2c_read_byte(pi_handle, i2c_handle)
22
- # Add to the string colon separated if byte was valid.
23
- found_string << "#{address}:" if byte >= 0
24
- i2c_close
25
- end
17
+ # I2C device may have reserved addresses. Exclude them.
18
+ addresses = I2C_ADDRESS_RANGE - map[:i2cs][index][:reserved_addresses].to_a
26
19
 
27
- # Remove trailing colon.
28
- found_string.chop! unless found_string.empty?
20
+ addresses.each do |address|
21
+ handle = i2c_open(index, address)
22
+ bytes = LGPIO.i2c_read_device(handle, 1)
23
+ found_string << "#{address}:" if bytes[0] > 0
24
+ i2c_close(handle)
25
+ end
29
26
 
30
- # Update the bus as if message came from microcontroller.
31
- self.update(2, found_string)
27
+ update_i2c(index, found_string)
32
28
  end
33
29
  end
34
30
 
35
31
  # CMD = 34
36
- def i2c_write(address, bytes, frequency=100000, repeated_start=false)
37
- i2c_mutex.synchronize do
38
- raise ArgumentError, "can't write more than #{i2c_limit} bytes to I2C" if bytes.length > i2c_limit
39
-
40
- # Pack length as a 16-bit uint, then unpack it into 2 litle endian bytes.
41
- length = [bytes.length].pack("S").unpack("C*")
42
-
43
- # Prepend length to the bytes.
44
- bytes = length + bytes
45
-
46
- # Prepend write command and escape character (necessary for double byte write length).
47
- bytes = [0x01, 0x07] + bytes
48
-
49
- # Enable and (re)disable repeated start as needed.
50
- bytes = [0x02] + bytes + [0x03] if repeated_start
51
-
52
- # Null terminate the command sequence.
53
- bytes = bytes + [0x00]
54
-
55
- # Send the command to the I2C1 interface, packed as uint8 string.
56
- i2c_open(1, address)
57
- Pigpio::IF.i2c_zip(pi_handle, i2c_handle, bytes.pack("C*"), 0)
58
- i2c_close
32
+ def i2c_write(index, address, bytes, frequency=nil, repeated_start=false)
33
+ i2c_mutex(index).synchronize do
34
+ raise ArgumentError, "exceeded #{i2c_limit} bytes for #i2c_write" if bytes.length > i2c_limit
35
+
36
+ handle = i2c_open(index, address)
37
+ result = LGPIO.i2c_write_device(handle, bytes)
38
+ i2c_close(handle)
39
+ i2c_c_error("write", result, index, address) if result < 0
59
40
  end
60
41
  end
61
42
 
62
43
  # CMD = 35
63
- def i2c_read(address, register, read_length, frequency=100000, repeated_start=false)
64
- i2c_mutex.synchronize do
44
+ def i2c_read(index, address, register, read_length, frequency=nil, repeated_start=false)
45
+ i2c_mutex(index).synchronize do
65
46
  raise ArgumentError, "can't read more than #{i2c_limit} bytes to I2C" if read_length > i2c_limit
66
-
67
- # Start with number of bytes to read (16-bit number) represented as 2 little endian bytes.
68
- buffer = [read_length].pack("S").unpack("C*")
69
-
70
- # Prepend read command and escape character (necessary for double byte write length).
71
- buffer = [0x01, 0x06] + buffer
72
47
 
73
- # If a start register was given, write it first.
48
+ handle = i2c_open(index, address)
74
49
  if register
75
- register = [register].flatten
76
- raise ArgumentError, "can't pre-write a register address > 4 bytes" if register.length > 4
77
- buffer = [0x07, register.length] + register + buffer
50
+ result = LGPIO.i2c_write_device(handle, register)
51
+ i2c_c_error("read (register write)", result, index, address) if result < 0
78
52
  end
79
53
 
80
- # Enable and (re)disable repeated start as needed.
81
- buffer = [0x02] + buffer + [0x03] if repeated_start
54
+ bytes = LGPIO.i2c_read_device(handle, read_length)
55
+ i2c_close(handle)
56
+ i2c_c_error("read", bytes, index, address) if bytes.class == Integer
82
57
 
83
- # Null terminate the command sequence.
84
- buffer = buffer + [0x00]
85
-
86
- # Send the command to the I2C1 interface, packed as uint8 string.
87
- i2c_open(1, address)
88
- read_bytes = Pigpio::IF.i2c_zip(pi_handle, i2c_handle, buffer.pack("C*"), read_length)
89
- i2c_close
90
-
91
- # Pigpio returned an error. Denko expects blank message after address.
92
- if read_bytes.class == Integer
93
- message = "#{address}-"
94
- else
95
- # Format the bytes like denko expects from a microcontroller.
96
- message = read_bytes.split("").map { |byte| byte.ord.to_s }.join(",")
97
- message = "#{address}-#{message}"
98
- end
99
-
100
- # Call update with the message, as if it came from pin 2 (I2C1 SDA pin).
101
- self.update(2, message)
58
+ # Prepend the address (0th element) to the data, and update the bus.
59
+ bytes.unshift(address)
60
+ update_i2c(index, bytes)
102
61
  end
103
62
  end
104
63
 
105
64
  private
106
65
 
107
- attr_reader :i2c_handle
66
+ def i2c_mutex(index)
67
+ i2c_mutexes[index] ||= Mutex.new
68
+ end
69
+
70
+ def i2c_mutexes
71
+ @i2c_mutexes ||= []
72
+ end
73
+
74
+ def i2c_open(index, address, flags=0x00)
75
+ handle = LGPIO.i2c_open(index, address, flags)
76
+ i2c_c_error("open", handle, index, address) if handle < 0
77
+ handle
78
+ end
79
+
80
+ def i2c_close(handle)
81
+ result = LGPIO.i2c_close(handle)
82
+ if result < 0
83
+ raise StandardError, "lgpio C I2C close error: #{result} for handle #{handle}"
84
+ end
85
+ end
108
86
 
109
- def i2c_open(bus_index, address)
110
- @i2c_handle = Pigpio::IF.i2c_open(pi_handle, bus_index, address, 0)
111
- raise StandardError, "I2C error, code #{@i2c_handle}" if @i2c_handle < 0
87
+ def i2c_c_error(name, error, index, address)
88
+ raise StandardError, "lgpio C I2C #{name} error: #{error} for /dev/i2c-#{index} with address 0x#{address.to_s(16)}"
112
89
  end
113
90
 
114
- def i2c_close
115
- Pigpio::IF.i2c_close(pi_handle, i2c_handle)
116
- @i2c_handle = nil
91
+ def update_i2c(index, data)
92
+ dev = hw_i2c_comps[index]
93
+ dev.update(data) if dev
117
94
  end
118
95
  end
119
96
  end
@@ -0,0 +1,48 @@
1
+ module Denko
2
+ class PiBoard
3
+ def i2c_bbs
4
+ @i2c_bbs ||= []
5
+ end
6
+
7
+ def i2c_bb_interface(scl, sda)
8
+ # Convert the pins into a config array to check.
9
+ ch, cl = gpio_tuple(scl)
10
+ dh, dl = gpio_tuple(sda)
11
+ config = [ch, cl, dh, dl]
12
+
13
+ # Check if any already exists with that array and return it.
14
+ i2c_bbs.each { |bb| return bb if (config == bb.config) }
15
+
16
+ # If not, create one.
17
+ hash = { scl: { handle: ch, line: cl },
18
+ sda: { handle: dh, line: dl } }
19
+
20
+ i2c_bb = LGPIO::I2CBitBang.new(hash)
21
+ i2c_bbs << i2c_bb
22
+ i2c_bb
23
+ end
24
+
25
+ def i2c_bb_search(scl, sda)
26
+ interface = i2c_bb_interface(scl, sda)
27
+ devices = interface.search
28
+ found_string = ""
29
+ found_string = devices.join(":") if devices
30
+ self.update(sda, found_string)
31
+ end
32
+
33
+ def i2c_bb_write(scl, sda, address, bytes, repeated_start=false)
34
+ interface = i2c_bb_interface(scl, sda)
35
+ interface.write(address, bytes)
36
+ end
37
+
38
+ def i2c_bb_read(scl, sda, address, register, read_length, repeated_start=false)
39
+ interface = i2c_bb_interface(scl, sda)
40
+ interface.write(address, register) if register
41
+ bytes = interface.read(address, read_length)
42
+
43
+ # Prepend the address (0th element) to the data, and update the SDA pin.
44
+ bytes.unshift(address)
45
+ self.update(sda, bytes)
46
+ end
47
+ end
48
+ end
@@ -1,51 +1,14 @@
1
1
  module Denko
2
2
  class PiBoard
3
3
  def infrared_emit(pin, frequency, pulses)
4
- # 32-bit mask where only the bit corresponding to the GPIO in use is set.
5
- pin_mask = 1 << pin
6
-
7
- # IR frequency given in kHz. Find half wave time in microseconds.
8
- if @aarch64
9
- # Compensate for pigpio doubling wave times on 64-bit systems.
10
- half_wave_time = (250.0 / frequency)
11
- else
12
- # True half wave time.
13
- half_wave_time = (500.0 / frequency)
14
- end
4
+ # Main gem uses frequency in kHz. Set it in Hz.
5
+ pwm = hardware_pwm_from_pin(pin, frequency: frequency*1000)
15
6
 
16
- # Standard wave setup.
17
- new_wave
18
- wave.tx_stop
19
- wave.clear
20
- wave.add_new
21
-
22
- # Build an array of pulses to add to the wave.
23
- wave_pulses = []
24
- pulses.each_with_index do |pulse, index|
25
- # Even indices send the carrier wave.
26
- if (index % 2 == 0)
27
- cycles = (pulse / (half_wave_time * 2)).round
28
- cycles.times do
29
- wave_pulses << wave.pulse(pin_mask, 0x00, half_wave_time)
30
- wave_pulses << wave.pulse(0x00, pin_mask, half_wave_time)
31
- end
32
-
33
- # Odd indices are idle.
34
- else
35
- if @aarch64
36
- # Half idle times for 64-bit systems.
37
- wave_pulses << wave.pulse(0x00, pin_mask, pulse / 2)
38
- else
39
- wave_pulses << wave.pulse(0x00, pin_mask, pulse)
40
- end
41
- end
42
- end
43
- wave.add_generic(wave_pulses)
44
- wave_id = wave.create
45
-
46
- # Temporary workaround while Wave#send_once gets fixed.
47
- Pigpio::IF.wave_send_once(@pi_handle, wave_id)
48
- # wave.send_once(wave_id)
7
+ # The actual strings for the sysfs PWM interface.
8
+ duty_path = "#{pwm.path}duty_cycle"
9
+ duty_ns = (0.333333 * pwm.period).round.to_s
10
+
11
+ pwm.tx_wave_ook(duty_path, duty_ns, pulses)
49
12
  end
50
13
  end
51
14
  end
@@ -0,0 +1,9 @@
1
+ module Denko
2
+ class PiBoard
3
+ def show_ws2812(pin, pixel_buffer, spi_index:)
4
+ handle = spi_open(spi_index, 2_400_000, 0)
5
+ LGPIO.spi_ws2812_write(handle, pixel_buffer)
6
+ spi_close(handle)
7
+ end
8
+ end
9
+ end