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.
- checksums.yaml +4 -4
- data/Gemfile +4 -0
- data/LICENSE +1 -1
- data/README.md +181 -132
- data/Rakefile +0 -5
- data/board_maps/README.md +59 -0
- data/board_maps/le_potato.yml +89 -0
- data/board_maps/orange_pi_zero_2w.yml +85 -0
- data/board_maps/radxa_zero3.yml +88 -0
- data/board_maps/raspberry_pi.yml +95 -0
- data/board_maps/raspberry_pi5.yml +95 -0
- data/denko_piboard.gemspec +6 -7
- data/examples/digital_io/bench_out.rb +22 -0
- data/examples/digital_io/rotary_encoder.rb +31 -0
- data/examples/display/ssd1306.rb +53 -0
- data/examples/i2c/bitbang_aht10.rb +18 -0
- data/examples/i2c/bitbang_search.rb +24 -0
- data/examples/i2c/bitbang_ssd1306_bench.rb +29 -0
- data/examples/i2c/search.rb +24 -0
- data/examples/led/blink.rb +10 -0
- data/examples/led/fade.rb +22 -0
- data/examples/led/ws2812_bounce.rb +36 -0
- data/examples/motor/servo.rb +16 -0
- data/examples/pi_system_monitor.rb +10 -8
- data/examples/pulse_io/buzzer.rb +34 -0
- data/examples/pulse_io/infrared.rb +25 -0
- data/examples/sensor/aht10.rb +17 -0
- data/examples/sensor/dht.rb +24 -0
- data/examples/sensor/ds18b20.rb +59 -0
- data/examples/sensor/hcsr04.rb +16 -0
- data/examples/sensor/neat_tph_readings.rb +32 -0
- data/examples/spi/bb_loopback.rb +31 -0
- data/examples/spi/loopback.rb +37 -0
- data/examples/spi/output_register.rb +38 -0
- data/lib/denko/piboard.rb +10 -2
- data/lib/denko/piboard_base.rb +21 -63
- data/lib/denko/piboard_core.rb +150 -130
- data/lib/denko/piboard_core_optimize_lookup.rb +31 -0
- data/lib/denko/piboard_hardware_pwm.rb +32 -0
- data/lib/denko/piboard_i2c.rb +59 -82
- data/lib/denko/piboard_i2c_bb.rb +48 -0
- data/lib/denko/piboard_infrared.rb +7 -44
- data/lib/denko/piboard_led_array.rb +9 -0
- data/lib/denko/piboard_map.rb +125 -38
- data/lib/denko/piboard_one_wire.rb +42 -0
- data/lib/denko/piboard_pulse.rb +11 -68
- data/lib/denko/piboard_spi.rb +47 -73
- data/lib/denko/piboard_spi_bb.rb +41 -0
- data/lib/denko/piboard_tone.rb +15 -26
- data/lib/denko/piboard_version.rb +1 -1
- data/scripts/99-denko.rules +9 -0
- data/scripts/set_permissions.rb +131 -0
- metadata +48 -21
- data/ext/gpiod/extconf.rb +0 -9
- data/ext/gpiod/gpiod.c +0 -179
- data/lib/denko/piboard_servo.rb +0 -18
- data/lib/gpiod.rb +0 -6
data/lib/denko/piboard_core.rb
CHANGED
@@ -1,94 +1,122 @@
|
|
1
1
|
module Denko
|
2
2
|
class PiBoard
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
45
|
-
|
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
|
-
|
51
|
-
state =
|
52
|
-
|
53
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
79
|
-
|
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
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
104
|
-
|
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
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
166
|
-
@
|
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
|
data/lib/denko/piboard_i2c.rb
CHANGED
@@ -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
|
-
#
|
18
|
-
|
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
|
-
|
28
|
-
|
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
|
-
|
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=
|
37
|
-
i2c_mutex.synchronize do
|
38
|
-
raise ArgumentError, "
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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=
|
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
|
-
|
48
|
+
handle = i2c_open(index, address)
|
74
49
|
if register
|
75
|
-
|
76
|
-
|
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
|
-
|
81
|
-
|
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
|
-
#
|
84
|
-
|
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
|
-
|
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
|
110
|
-
|
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
|
115
|
-
|
116
|
-
|
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
|
-
#
|
5
|
-
|
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
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|