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_map.rb
CHANGED
@@ -1,49 +1,136 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
1
3
|
module Denko
|
2
4
|
class PiBoard
|
3
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
39
|
-
|
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
|
-
#
|
42
|
-
|
43
|
-
return
|
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
|
data/lib/denko/piboard_pulse.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
data/lib/denko/piboard_spi.rb
CHANGED
@@ -1,100 +1,74 @@
|
|
1
1
|
module Denko
|
2
2
|
class PiBoard
|
3
|
+
def spi_limit
|
4
|
+
4096
|
5
|
+
end
|
3
6
|
|
4
|
-
def
|
5
|
-
|
6
|
-
|
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
|
-
#
|
25
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
digital_write(
|
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
|
-
|
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
|
-
#
|
50
|
-
|
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
|
-
|
69
|
-
|
70
|
-
end
|
35
|
+
def spi_listen(*arg, **kwargs)
|
36
|
+
raise NotImplementedError, "PiBoard#spi_listen not implemented yet"
|
71
37
|
end
|
72
38
|
|
73
|
-
|
74
|
-
def spi_listen
|
39
|
+
def spi_stop(pin)
|
75
40
|
end
|
76
41
|
|
77
|
-
|
78
|
-
|
42
|
+
def spi_listeners
|
43
|
+
@spi_listeners ||= Array.new
|
79
44
|
end
|
80
45
|
|
81
46
|
private
|
82
47
|
|
83
|
-
|
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
|
92
|
-
|
93
|
-
@spi_handle = nil
|
52
|
+
def spi_mutexes
|
53
|
+
@spi_mutexes ||= []
|
94
54
|
end
|
95
55
|
|
96
|
-
def
|
97
|
-
|
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
|
data/lib/denko/piboard_tone.rb
CHANGED
@@ -2,38 +2,27 @@ module Denko
|
|
2
2
|
class PiBoard
|
3
3
|
# CMD = 17
|
4
4
|
def tone(pin, frequency, duration=nil)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
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
|
@@ -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"
|