rgpio 0.1.0 → 0.1.1
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/CHANGELOG.md +30 -2
- data/README.md +33 -891
- data/examples/rotary_encoder.rb +48 -0
- data/examples/ruby_stack.rb +169 -0
- data/examples/tft.rb +65 -0
- data/examples/touch_calibration.rb +61 -0
- data/examples/touch_paint.rb +67 -0
- data/lib/rgpio/devices/adt7410.rb +1 -1
- data/lib/rgpio/devices/font5x7.rb +209 -0
- data/lib/rgpio/devices/ili9341.rb +338 -0
- data/lib/rgpio/devices/rgb565.rb +61 -0
- data/lib/rgpio/devices/rotary_encoder.rb +202 -0
- data/lib/rgpio/devices/xpt2046.rb +294 -0
- data/lib/rgpio/i2c.rb +1 -1
- data/lib/rgpio/pwm.rb +7 -3
- data/lib/rgpio/spi.rb +39 -1
- data/lib/rgpio/version.rb +1 -1
- data/lib/rgpio.rb +5 -0
- metadata +17 -7
- data/PLAN.md +0 -347
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
module Rgpio
|
|
2
|
+
# A resistive touch panel read through an XPT2046 (or ADS7843/TSC2046)
|
|
3
|
+
# controller over SPI — the touch half of the common 2.4"/2.8" ILI9341
|
|
4
|
+
# modules, on the same bus as the display with a chip select of its own.
|
|
5
|
+
#
|
|
6
|
+
# touch = Rgpio::XPT2046.new(irq: 17, calibration: [...])
|
|
7
|
+
# touch.when_touched { |x, y| puts "#{x}, #{y}" }
|
|
8
|
+
# touch.when_released { puts "released" }
|
|
9
|
+
# Rgpio.pause
|
|
10
|
+
#
|
|
11
|
+
# #raw returns the converter's 12-bit readings. Turning those into screen
|
|
12
|
+
# coordinates needs a calibration, since the film's resistance and how it is
|
|
13
|
+
# mounted vary from panel to panel: {.calibration_from} fits one from a few
|
|
14
|
+
# touches on known screen points. With no calibration, #position is the raw
|
|
15
|
+
# [x, y].
|
|
16
|
+
#
|
|
17
|
+
# T_IRQ (PENIRQ) goes low while the panel is pressed. It is optional: with
|
|
18
|
+
# it, the watcher thread sleeps on a kernel edge event until a touch; without
|
|
19
|
+
# it, the watcher samples the pressure every POLL_INTERVAL.
|
|
20
|
+
class XPT2046
|
|
21
|
+
DEFAULT_SPEED_HZ = 2_000_000
|
|
22
|
+
|
|
23
|
+
# Control byte: start bit, channel (A2..A0), 12-bit mode, differential
|
|
24
|
+
# reference, and power-down between conversions with PENIRQ enabled.
|
|
25
|
+
CMD_X = 0xd0
|
|
26
|
+
CMD_Y = 0x90
|
|
27
|
+
CMD_Z1 = 0xb0
|
|
28
|
+
CMD_Z2 = 0xc0
|
|
29
|
+
|
|
30
|
+
RESOLUTION = 4096
|
|
31
|
+
|
|
32
|
+
# Pressure (on the scale of #raw's z) below which the panel counts as not
|
|
33
|
+
# touched. Lower it for a light touch, raise it if the panel reports touches
|
|
34
|
+
# nobody made.
|
|
35
|
+
DEFAULT_THRESHOLD = 300
|
|
36
|
+
|
|
37
|
+
# Readings per coordinate; the median of them is used, which rejects the
|
|
38
|
+
# odd sample taken while a finger is landing or lifting.
|
|
39
|
+
SAMPLES = 5
|
|
40
|
+
|
|
41
|
+
# How often a held touch is re-read to notice its release, and how often
|
|
42
|
+
# the pressure is polled with no T_IRQ line.
|
|
43
|
+
POLL_INTERVAL = 0.02
|
|
44
|
+
|
|
45
|
+
# How long a single edge wait blocks before the watcher rechecks whether it
|
|
46
|
+
# has been asked to stop.
|
|
47
|
+
WATCH_TIMEOUT = 0.2
|
|
48
|
+
|
|
49
|
+
# Fit a calibration from touches on known screen points: a least-squares
|
|
50
|
+
# affine map, so a panel mounted rotated or mirrored needs nothing extra.
|
|
51
|
+
# Three points not on one line is the minimum; the four corners and the
|
|
52
|
+
# centre average out the error of each touch.
|
|
53
|
+
# @param screen [Array<Array(Numeric, Numeric)>] screen [x, y] of each target
|
|
54
|
+
# @param raw [Array<Array(Numeric, Numeric)>] raw [x, y] touched there
|
|
55
|
+
# @return [Array<Float>] [a, b, c, d, e, f] with x = a*rx + b*ry + c and
|
|
56
|
+
# y = d*rx + e*ry + f
|
|
57
|
+
def self.calibration_from(screen, raw)
|
|
58
|
+
raise ArgumentError, "calibration needs the same number of screen and raw points" unless screen.size == raw.size
|
|
59
|
+
raise ArgumentError, "calibration needs at least three points" if screen.size < 3
|
|
60
|
+
|
|
61
|
+
rows = raw.map { |rx, ry| [rx.to_f, ry.to_f, 1.0] }
|
|
62
|
+
solve_least_squares(rows, screen.map { |sx, _| sx.to_f }) +
|
|
63
|
+
solve_least_squares(rows, screen.map { |_, sy| sy.to_f })
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Solve rows * coef ~= targets for three coefficients through the normal
|
|
67
|
+
# equations.
|
|
68
|
+
def self.solve_least_squares(rows, targets)
|
|
69
|
+
matrix = normal_equations(rows, targets)
|
|
70
|
+
3.times do |col|
|
|
71
|
+
pivot = (col...3).max_by { |row| matrix[row][col].abs }
|
|
72
|
+
raise ArgumentError, "calibration points must not all lie on one line" if matrix[pivot][col].abs < 1e-9
|
|
73
|
+
|
|
74
|
+
matrix[col], matrix[pivot] = matrix[pivot], matrix[col]
|
|
75
|
+
(0...3).each do |row|
|
|
76
|
+
next if row == col
|
|
77
|
+
|
|
78
|
+
factor = matrix[row][col] / matrix[col][col]
|
|
79
|
+
4.times { |k| matrix[row][k] -= factor * matrix[col][k] }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
Array.new(3) { |i| matrix[i][3] / matrix[i][i] }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [Array<Array<Float>>] the augmented 3x4 matrix [AᵀA | Aᵀt]
|
|
86
|
+
def self.normal_equations(rows, targets)
|
|
87
|
+
Array.new(3) do |i|
|
|
88
|
+
Array.new(3) { |j| rows.sum { |r| r[i] * r[j] } } + [rows.each_with_index.sum { |r, k| r[i] * targets[k] }]
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
private_class_method :solve_least_squares, :normal_equations
|
|
92
|
+
|
|
93
|
+
# @param irq [Integer, nil] GPIO line wired to T_IRQ, or nil to poll
|
|
94
|
+
# @param calibration [Array<Numeric>, nil] six numbers from {.calibration_from}
|
|
95
|
+
# @param threshold [Integer] pressure that counts as a touch
|
|
96
|
+
# @param bus [Integer] spidev bus number
|
|
97
|
+
# @param device [Integer] chip-select index; CE1 when the display is on CE0
|
|
98
|
+
# @param speed_hz [Integer] clock rate
|
|
99
|
+
# @param spi [SPI, nil] an open bus device to share, or nil to open one
|
|
100
|
+
# @param chip [Chip, nil] chip for the T_IRQ line, or nil to open one
|
|
101
|
+
# @param consumer [String] name shown in the kernel's request list
|
|
102
|
+
def initialize(irq: nil, calibration: nil, threshold: DEFAULT_THRESHOLD, bus: 0, device: 1,
|
|
103
|
+
speed_hz: DEFAULT_SPEED_HZ, spi: nil, chip: nil, consumer: "rgpio")
|
|
104
|
+
self.calibration = calibration
|
|
105
|
+
@threshold = threshold
|
|
106
|
+
@callbacks = {}
|
|
107
|
+
@closed = false
|
|
108
|
+
@lock = Mutex.new
|
|
109
|
+
@owns_spi = spi.nil?
|
|
110
|
+
@spi = spi || SPI.new(bus: bus, device: device, speed_hz: speed_hz, mode: 0)
|
|
111
|
+
@irq = irq
|
|
112
|
+
return unless irq
|
|
113
|
+
|
|
114
|
+
@owns_chip = chip.nil?
|
|
115
|
+
@chip = chip || Chip.new
|
|
116
|
+
# PENIRQ is open drain; the boards have no pull-up of their own.
|
|
117
|
+
@irq_request = @chip.request_lines(offsets: [irq], direction: :input, edge: :falling, bias: :pull_up,
|
|
118
|
+
consumer: consumer)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [SPI] the bus device readings go through
|
|
122
|
+
attr_reader :spi
|
|
123
|
+
|
|
124
|
+
# @return [Array<Float>, nil] the calibration in use
|
|
125
|
+
attr_reader :calibration
|
|
126
|
+
|
|
127
|
+
# @return [Integer] pressure that counts as a touch
|
|
128
|
+
attr_accessor :threshold
|
|
129
|
+
|
|
130
|
+
# @param coefficients [Array<Numeric>, nil] six numbers from {.calibration_from}
|
|
131
|
+
def calibration=(coefficients)
|
|
132
|
+
unless coefficients.nil? || (coefficients.is_a?(Array) && coefficients.size == 6 && coefficients.all?(Numeric))
|
|
133
|
+
raise ArgumentError, "calibration is six numbers from XPT2046.calibration_from, got #{coefficients.inspect}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
@calibration = coefficients&.map(&:to_f)&.freeze
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @return [Array(Integer, Integer, Integer)] raw x, y (0..4095) and
|
|
140
|
+
# pressure z (0 when untouched, larger the harder the press)
|
|
141
|
+
def raw
|
|
142
|
+
raise Error, "#{self.class} is closed" if @closed
|
|
143
|
+
|
|
144
|
+
sample
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @return [Boolean] whether the panel is pressed at least #threshold hard
|
|
148
|
+
def touched?
|
|
149
|
+
raw[2] >= @threshold
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @return [Array(Integer, Integer), nil] the touch in screen coordinates,
|
|
153
|
+
# or nil when untouched
|
|
154
|
+
def position
|
|
155
|
+
locate(raw)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @yieldparam x [Integer] screen x of the touch
|
|
159
|
+
# @yieldparam y [Integer] screen y of the touch
|
|
160
|
+
def when_touched(&)
|
|
161
|
+
on(:touched, &)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def when_released(&)
|
|
165
|
+
on(:released, &)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Stop the watcher, then release the T_IRQ line, the bus and the chip as
|
|
169
|
+
# far as this object opened them.
|
|
170
|
+
def close
|
|
171
|
+
return if @closed
|
|
172
|
+
|
|
173
|
+
@closed = true
|
|
174
|
+
@watching = false
|
|
175
|
+
@watcher&.join
|
|
176
|
+
@watcher = nil
|
|
177
|
+
@irq_request&.release
|
|
178
|
+
@spi.close if @owns_spi
|
|
179
|
+
@chip.close if @owns_chip
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def closed?
|
|
183
|
+
@closed
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
# #raw without the closed check. The watcher reads through this: #close
|
|
189
|
+
# marks the device closed before it waits for the watcher, and the bus
|
|
190
|
+
# stays open until that wait is over, so the watcher's last poll is safe.
|
|
191
|
+
def sample
|
|
192
|
+
@lock.synchronize do
|
|
193
|
+
# Pressure is read on both sides of the position and the lower one
|
|
194
|
+
# kept: a pen landing or lifting part-way through leaves x/y unsettled
|
|
195
|
+
# (or at the untouched 0/4095), and one of the two pressures reads low.
|
|
196
|
+
z_before = read_pressure
|
|
197
|
+
# The first conversion after the drivers switch on is still settling.
|
|
198
|
+
read_channel(CMD_X)
|
|
199
|
+
x = median(SAMPLES) { read_channel(CMD_X) }
|
|
200
|
+
y = median(SAMPLES) { read_channel(CMD_Y) }
|
|
201
|
+
[x, y, [z_before, read_pressure].min]
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# @return [Array(Integer, Integer), nil] screen position of a reading, or
|
|
206
|
+
# nil when it is not pressed hard enough
|
|
207
|
+
def locate(reading)
|
|
208
|
+
x, y, z = reading
|
|
209
|
+
z >= @threshold ? to_screen(x, y) : nil
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# The 12-bit result arrives MSB first after the command byte, left-aligned
|
|
213
|
+
# with three trailing zero bits.
|
|
214
|
+
def read_channel(cmd)
|
|
215
|
+
received = @spi.transfer([cmd, 0x00, 0x00])
|
|
216
|
+
((received[1] << 8) | received[2]) >> 3
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def read_pressure
|
|
220
|
+
read_channel(CMD_Z1) + (RESOLUTION - 1) - read_channel(CMD_Z2)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def median(count, &)
|
|
224
|
+
Array.new(count, &).sort[count / 2]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def to_screen(x, y)
|
|
228
|
+
return [x, y] unless @calibration
|
|
229
|
+
|
|
230
|
+
a, b, c, d, e, f = @calibration
|
|
231
|
+
[((a * x) + (b * y) + c).round, ((d * x) + (e * y) + f).round]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def on(kind, &block)
|
|
235
|
+
raise ArgumentError, "a callback block is required" unless block
|
|
236
|
+
|
|
237
|
+
@callbacks[kind] = block
|
|
238
|
+
start_watching
|
|
239
|
+
self
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def start_watching
|
|
243
|
+
return if @watcher
|
|
244
|
+
|
|
245
|
+
@watching = true
|
|
246
|
+
@watcher = Thread.new { watch_loop }
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def watch_loop
|
|
250
|
+
while @watching
|
|
251
|
+
touch = wait_for_touch
|
|
252
|
+
follow_touch(*touch) if touch
|
|
253
|
+
end
|
|
254
|
+
rescue StandardError => e
|
|
255
|
+
warn "rgpio: touch watcher stopped: #{e.class}: #{e.message}"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# @return [Array(Integer, Integer), nil] where the panel is pressed, once it is
|
|
259
|
+
def wait_for_touch
|
|
260
|
+
if @irq_request
|
|
261
|
+
return nil if @irq_request.read_edge_events(timeout: WATCH_TIMEOUT).empty?
|
|
262
|
+
else
|
|
263
|
+
sleep(POLL_INTERVAL)
|
|
264
|
+
end
|
|
265
|
+
locate(sample)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Report a touch, then poll until it lifts. Conversions make PENIRQ drop
|
|
269
|
+
# again while the pen is down, so the edges queued meanwhile are discarded
|
|
270
|
+
# rather than read as new touches.
|
|
271
|
+
def follow_touch(x, y)
|
|
272
|
+
dispatch(:touched, x, y)
|
|
273
|
+
sleep(POLL_INTERVAL) while @watching && sample[2] >= @threshold
|
|
274
|
+
dispatch(:released)
|
|
275
|
+
drain_edges
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def drain_edges
|
|
279
|
+
return unless @irq_request
|
|
280
|
+
|
|
281
|
+
nil until @irq_request.read_edge_events(timeout: 0).empty?
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# A raising callback must not take the watcher down with it.
|
|
285
|
+
def dispatch(kind, *)
|
|
286
|
+
callback = @callbacks[kind]
|
|
287
|
+
return unless callback
|
|
288
|
+
|
|
289
|
+
callback.call(*)
|
|
290
|
+
rescue StandardError => e
|
|
291
|
+
warn "rgpio: #{self.class} #{kind} callback raised #{e.class}: #{e.message}"
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
data/lib/rgpio/i2c.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Rgpio
|
|
|
17
17
|
# i2c.close
|
|
18
18
|
#
|
|
19
19
|
# The I2C bus on the 40-pin header (GPIO2 = SDA, GPIO3 = SCL) is bus 1, and
|
|
20
|
-
# only appears once it is enabled — see
|
|
20
|
+
# only appears once it is enabled — see docs/guide.md for the dtparam line.
|
|
21
21
|
class I2C
|
|
22
22
|
# ioctl numbers from <linux/i2c-dev.h>.
|
|
23
23
|
I2C_SLAVE = 0x0703
|
data/lib/rgpio/pwm.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Rgpio
|
|
|
8
8
|
# -----------------------------
|
|
9
9
|
# The RP1 PWM peripheral must be enabled via dtoverlay in
|
|
10
10
|
# /boot/firmware/config.txt before the chip appears in sysfs.
|
|
11
|
-
# See
|
|
11
|
+
# See docs/guide.md for the required overlay configuration.
|
|
12
12
|
#
|
|
13
13
|
# GPIO-to-PWM mapping on Pi 5 (RP1):
|
|
14
14
|
# GPIO12 → RP1 PWM chip, channel 0
|
|
@@ -34,6 +34,10 @@ module Rgpio
|
|
|
34
34
|
class HardwarePWM
|
|
35
35
|
PWM_SYSFS_ROOT = "/sys/class/pwm".freeze
|
|
36
36
|
|
|
37
|
+
# The overlay setup, linked from chip-detection errors. The gem ships no
|
|
38
|
+
# docs of its own, so the messages point at the guide on GitHub.
|
|
39
|
+
GUIDE_URL = "https://github.com/lumbermill/rgpio/blob/main/docs/guide.md#hardware-pwm-usage".freeze
|
|
40
|
+
|
|
37
41
|
# Device-tree model string, used to pick the board's PWM mapping.
|
|
38
42
|
BOARD_MODEL_PATH = "/proc/device-tree/model".freeze
|
|
39
43
|
|
|
@@ -251,7 +255,7 @@ module Rgpio
|
|
|
251
255
|
chips = self.class.available_chips
|
|
252
256
|
if chips.empty?
|
|
253
257
|
raise PWMError, "No PWM chips found under #{PWM_SYSFS_ROOT}. " \
|
|
254
|
-
"Is the dtoverlay configured? See
|
|
258
|
+
"Is the dtoverlay configured? See #{GUIDE_URL}"
|
|
255
259
|
end
|
|
256
260
|
|
|
257
261
|
profile = PWM_CHIP_PROFILE[@board]
|
|
@@ -273,7 +277,7 @@ module Rgpio
|
|
|
273
277
|
chip_list = chips.map { |c| "pwmchip#{c[:chip]}(npwm=#{c[:npwm]})" }.join(", ")
|
|
274
278
|
raise PWMError,
|
|
275
279
|
"Cannot auto-detect the header PWM chip for board #{@board.inspect}. " \
|
|
276
|
-
"Available: #{chip_list}. Pass chip: <number> explicitly (see
|
|
280
|
+
"Available: #{chip_list}. Pass chip: <number> explicitly (see #{GUIDE_URL})."
|
|
277
281
|
end
|
|
278
282
|
|
|
279
283
|
def export_channel
|
data/lib/rgpio/spi.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Rgpio
|
|
|
16
16
|
# {#write} and {#read} are that same transfer with one direction ignored.
|
|
17
17
|
#
|
|
18
18
|
# The header bus is SPI0 (GPIO10 = MOSI, GPIO9 = MISO, GPIO11 = SCLK, GPIO8 =
|
|
19
|
-
# CE0, GPIO7 = CE1), and only appears once it is enabled — see
|
|
19
|
+
# CE0, GPIO7 = CE1), and only appears once it is enabled — see docs/guide.md for the
|
|
20
20
|
# dtparam line.
|
|
21
21
|
class SPI
|
|
22
22
|
# ioctl numbers from <linux/spi/spidev.h>. They are built here rather than
|
|
@@ -39,6 +39,22 @@ module Rgpio
|
|
|
39
39
|
# Clock polarity and phase, as the kernel numbers them.
|
|
40
40
|
MODE_RANGE = (0..3)
|
|
41
41
|
|
|
42
|
+
# Where spidev publishes the most it accepts in one message, and what that
|
|
43
|
+
# is when the parameter cannot be read.
|
|
44
|
+
BUFSIZ_PATH = "/sys/module/spidev/parameters/bufsiz".freeze
|
|
45
|
+
DEFAULT_BUFSIZ = 4096
|
|
46
|
+
|
|
47
|
+
# @return [Integer] the largest message spidev accepts, in bytes. A module
|
|
48
|
+
# parameter (spidev.bufsiz=), so it is the same for every bus.
|
|
49
|
+
def self.max_transfer_size
|
|
50
|
+
@max_transfer_size ||= begin
|
|
51
|
+
size = File.read(BUFSIZ_PATH).to_i
|
|
52
|
+
size.positive? ? size : DEFAULT_BUFSIZ
|
|
53
|
+
rescue SystemCallError
|
|
54
|
+
DEFAULT_BUFSIZ
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
42
58
|
# @return [Integer] the ioctl request for a message of `count` transfers
|
|
43
59
|
def self.message_ioctl(count)
|
|
44
60
|
ioctl_number(IOC_WRITE, 0, TRANSFER_SIZE * count)
|
|
@@ -178,6 +194,28 @@ module Rgpio
|
|
|
178
194
|
transfer(bytes).size
|
|
179
195
|
end
|
|
180
196
|
|
|
197
|
+
# Send a block of any length, ignoring what comes back — for a display's
|
|
198
|
+
# pixel data, where a frame is many times what spidev takes in one message.
|
|
199
|
+
# The block goes out in messages of at most {.max_transfer_size} bytes, so
|
|
200
|
+
# chip select is released between them; a controller that keys on chip
|
|
201
|
+
# select rather than on a command (the ILI9341 does not) needs #transfer.
|
|
202
|
+
# Nothing is unpacked on the way back, which keeps a frame cheap.
|
|
203
|
+
# @param data [String] bytes to send
|
|
204
|
+
# @return [Integer] number of bytes sent
|
|
205
|
+
def send_bytes(data)
|
|
206
|
+
data = data.b
|
|
207
|
+
chunk = self.class.max_transfer_size
|
|
208
|
+
@tx_buffer ||= Fiddle::Pointer.malloc(chunk, Fiddle::RUBY_FREE)
|
|
209
|
+
(0...data.bytesize).step(chunk) do |offset|
|
|
210
|
+
part = data.byteslice(offset, chunk)
|
|
211
|
+
@tx_buffer[0, part.bytesize] = part
|
|
212
|
+
message = self.class.pack_transfer(@tx_buffer.to_i, 0, part.bytesize, @speed_hz,
|
|
213
|
+
bits_per_word: @bits_per_word)
|
|
214
|
+
@io.ioctl(self.class.message_ioctl(1), message)
|
|
215
|
+
end
|
|
216
|
+
data.bytesize
|
|
217
|
+
end
|
|
218
|
+
|
|
181
219
|
# Clock in `count` bytes, sending zeros.
|
|
182
220
|
# @return [Array<Integer>]
|
|
183
221
|
def read(count)
|
data/lib/rgpio/version.rb
CHANGED
data/lib/rgpio.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "rgpio/spi"
|
|
|
10
10
|
require_relative "rgpio/devices/device"
|
|
11
11
|
require_relative "rgpio/devices/output_device"
|
|
12
12
|
require_relative "rgpio/devices/input_device"
|
|
13
|
+
require_relative "rgpio/devices/rotary_encoder"
|
|
13
14
|
require_relative "rgpio/devices/motor"
|
|
14
15
|
require_relative "rgpio/devices/pwm_channel"
|
|
15
16
|
require_relative "rgpio/devices/pwm_output_device"
|
|
@@ -18,6 +19,10 @@ require_relative "rgpio/devices/servo"
|
|
|
18
19
|
require_relative "rgpio/devices/adt7410"
|
|
19
20
|
require_relative "rgpio/devices/st7032"
|
|
20
21
|
require_relative "rgpio/devices/mcp3208"
|
|
22
|
+
require_relative "rgpio/devices/rgb565"
|
|
23
|
+
require_relative "rgpio/devices/font5x7"
|
|
24
|
+
require_relative "rgpio/devices/ili9341"
|
|
25
|
+
require_relative "rgpio/devices/xpt2046"
|
|
21
26
|
|
|
22
27
|
# Ruby bindings for libgpiod v2 (Linux GPIO character device), bound through
|
|
23
28
|
# the stdlib `fiddle`. Targets Debian Trixie (libgpiod >= 2.1) on Raspberry Pi.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rgpio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ITO Yosei
|
|
@@ -70,10 +70,10 @@ description: 'Control a Raspberry Pi''s hardware from Ruby. GPIO goes through li
|
|
|
70
70
|
interface. PWM is either the hardware peripheral through sysfs or timed in Ruby
|
|
71
71
|
on any line; I2C and SPI are ioctl calls on /dev/i2c-N and /dev/spidev, needing
|
|
72
72
|
no libgpiod at all. On top sits a gpiozero-style device layer: LED, Button, Motor,
|
|
73
|
-
PWMLED, RGBLED, Servo, and drivers for the ADT7410 temperature sensor,
|
|
74
|
-
LCD
|
|
75
|
-
|
|
76
|
-
gem.'
|
|
73
|
+
PWMLED, RGBLED, Servo, RotaryEncoder, and drivers for the ADT7410 temperature sensor,
|
|
74
|
+
ST7032 character LCD, MCP3208 ADC, ILI9341 colour TFT and XPT2046 touch panel. Bound
|
|
75
|
+
through the stdlib `fiddle`, which is built with the interpreter and so matches
|
|
76
|
+
any Pi''s architecture, unlike the precompiled `ffi` gem.'
|
|
77
77
|
email:
|
|
78
78
|
- y-itou@lumber-mill.co.jp
|
|
79
79
|
executables: []
|
|
@@ -82,7 +82,6 @@ extra_rdoc_files: []
|
|
|
82
82
|
files:
|
|
83
83
|
- CHANGELOG.md
|
|
84
84
|
- LICENSE
|
|
85
|
-
- PLAN.md
|
|
86
85
|
- README.md
|
|
87
86
|
- examples/adc.rb
|
|
88
87
|
- examples/adc_led.rb
|
|
@@ -100,22 +99,32 @@ files:
|
|
|
100
99
|
- examples/pwm_led.rb
|
|
101
100
|
- examples/rgb_balance.rb
|
|
102
101
|
- examples/rgb_led.rb
|
|
102
|
+
- examples/rotary_encoder.rb
|
|
103
|
+
- examples/ruby_stack.rb
|
|
103
104
|
- examples/servo.rb
|
|
104
105
|
- examples/temperature.rb
|
|
106
|
+
- examples/tft.rb
|
|
107
|
+
- examples/touch_calibration.rb
|
|
108
|
+
- examples/touch_paint.rb
|
|
105
109
|
- lib/rgpio.rb
|
|
106
110
|
- lib/rgpio/bytes.rb
|
|
107
111
|
- lib/rgpio/chip.rb
|
|
108
112
|
- lib/rgpio/devices/adt7410.rb
|
|
109
113
|
- lib/rgpio/devices/device.rb
|
|
114
|
+
- lib/rgpio/devices/font5x7.rb
|
|
115
|
+
- lib/rgpio/devices/ili9341.rb
|
|
110
116
|
- lib/rgpio/devices/input_device.rb
|
|
111
117
|
- lib/rgpio/devices/mcp3208.rb
|
|
112
118
|
- lib/rgpio/devices/motor.rb
|
|
113
119
|
- lib/rgpio/devices/output_device.rb
|
|
114
120
|
- lib/rgpio/devices/pwm_channel.rb
|
|
115
121
|
- lib/rgpio/devices/pwm_output_device.rb
|
|
122
|
+
- lib/rgpio/devices/rgb565.rb
|
|
116
123
|
- lib/rgpio/devices/rgb_led.rb
|
|
124
|
+
- lib/rgpio/devices/rotary_encoder.rb
|
|
117
125
|
- lib/rgpio/devices/servo.rb
|
|
118
126
|
- lib/rgpio/devices/st7032.rb
|
|
127
|
+
- lib/rgpio/devices/xpt2046.rb
|
|
119
128
|
- lib/rgpio/i2c.rb
|
|
120
129
|
- lib/rgpio/line_request.rb
|
|
121
130
|
- lib/rgpio/native.rb
|
|
@@ -128,6 +137,7 @@ licenses:
|
|
|
128
137
|
- MIT
|
|
129
138
|
metadata:
|
|
130
139
|
source_code_uri: https://github.com/lumbermill/rgpio
|
|
140
|
+
documentation_uri: https://github.com/lumbermill/rgpio/blob/main/docs/guide.md
|
|
131
141
|
changelog_uri: https://github.com/lumbermill/rgpio/blob/main/CHANGELOG.md
|
|
132
142
|
bug_tracker_uri: https://github.com/lumbermill/rgpio/issues
|
|
133
143
|
rubygems_mfa_required: 'true'
|
|
@@ -145,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
145
155
|
- !ruby/object:Gem::Version
|
|
146
156
|
version: '0'
|
|
147
157
|
requirements: []
|
|
148
|
-
rubygems_version: 4.0.
|
|
158
|
+
rubygems_version: 4.0.23
|
|
149
159
|
specification_version: 4
|
|
150
160
|
summary: GPIO, PWM, I2C and SPI on Raspberry Pi, via libgpiod v2 and the Linux character
|
|
151
161
|
devices
|