rgpio 0.1.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 +7 -0
- data/CHANGELOG.md +103 -0
- data/LICENSE +21 -0
- data/PLAN.md +347 -0
- data/README.md +969 -0
- data/examples/adc.rb +60 -0
- data/examples/adc_led.rb +44 -0
- data/examples/button.rb +33 -0
- data/examples/lcd.rb +47 -0
- data/examples/lcd_thermometer.rb +57 -0
- data/examples/led.rb +31 -0
- data/examples/lowlevel/blink.rb +46 -0
- data/examples/lowlevel/button.rb +69 -0
- data/examples/lowlevel/servo.rb +76 -0
- data/examples/motion_sensor.rb +70 -0
- data/examples/motor.rb +38 -0
- data/examples/pwm_info.rb +68 -0
- data/examples/pwm_jitter.rb +139 -0
- data/examples/pwm_led.rb +56 -0
- data/examples/rgb_balance.rb +65 -0
- data/examples/rgb_led.rb +72 -0
- data/examples/servo.rb +70 -0
- data/examples/temperature.rb +53 -0
- data/lib/rgpio/bytes.rb +12 -0
- data/lib/rgpio/chip.rb +271 -0
- data/lib/rgpio/devices/adt7410.rb +116 -0
- data/lib/rgpio/devices/device.rb +41 -0
- data/lib/rgpio/devices/input_device.rb +150 -0
- data/lib/rgpio/devices/mcp3208.rb +104 -0
- data/lib/rgpio/devices/motor.rb +51 -0
- data/lib/rgpio/devices/output_device.rb +66 -0
- data/lib/rgpio/devices/pwm_channel.rb +25 -0
- data/lib/rgpio/devices/pwm_output_device.rb +109 -0
- data/lib/rgpio/devices/rgb_led.rb +175 -0
- data/lib/rgpio/devices/servo.rb +161 -0
- data/lib/rgpio/devices/st7032.rb +234 -0
- data/lib/rgpio/i2c.rb +175 -0
- data/lib/rgpio/line_request.rb +184 -0
- data/lib/rgpio/native.rb +238 -0
- data/lib/rgpio/pwm.rb +321 -0
- data/lib/rgpio/software_pwm.rb +290 -0
- data/lib/rgpio/spi.rb +208 -0
- data/lib/rgpio/version.rb +3 -0
- data/lib/rgpio.rb +99 -0
- metadata +152 -0
data/examples/adc.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Read all eight channels of an MCP3208 analogue-to-digital converter.
|
|
4
|
+
#
|
|
5
|
+
# Wiring (MCP3208 in a 16-pin DIP, pin 1 at the notch):
|
|
6
|
+
# pin 1-8 CH0..CH7 -- analogue inputs; a potentiometer's wiper on CH0
|
|
7
|
+
# pin 9 DGND -- GND
|
|
8
|
+
# pin 10 CS/SHDN -- GPIO8 (pin 24, CE0)
|
|
9
|
+
# pin 11 DIN -- GPIO10 (pin 19, MOSI)
|
|
10
|
+
# pin 12 DOUT -- GPIO9 (pin 21, MISO)
|
|
11
|
+
# pin 13 CLK -- GPIO11 (pin 23, SCLK)
|
|
12
|
+
# pin 14 AGND -- GND
|
|
13
|
+
# pin 15 VREF -- 3.3 V (pin 1)
|
|
14
|
+
# pin 16 VDD -- 3.3 V
|
|
15
|
+
#
|
|
16
|
+
# A potentiometer to try it with: one end to 3.3 V, the other to GND, the wiper
|
|
17
|
+
# to CH0. A 10k pot is the one to reach for: 1k wastes 3.3 mA across it for no
|
|
18
|
+
# gain in accuracy.
|
|
19
|
+
#
|
|
20
|
+
# Unconnected channels are not faulty and not arbitrary either — they mirror
|
|
21
|
+
# whatever is going on nearby. Measured here: with every channel unconnected they
|
|
22
|
+
# read codes 0-10, and with one channel tied to 3.3 V and sampled repeatedly the
|
|
23
|
+
# floating ones rose to a mean near 15 with excursions past 400.
|
|
24
|
+
#
|
|
25
|
+
# Prerequisite — the header SPI bus must be enabled:
|
|
26
|
+
# sudo raspi-config nonint do_spi 0 # or: dtparam=spi=on in config.txt
|
|
27
|
+
#
|
|
28
|
+
# Verify:
|
|
29
|
+
# ls /dev/spidev0.0
|
|
30
|
+
#
|
|
31
|
+
# Run:
|
|
32
|
+
# ruby examples/adc.rb
|
|
33
|
+
|
|
34
|
+
require_relative "../lib/rgpio"
|
|
35
|
+
|
|
36
|
+
INTERVAL = 0.5
|
|
37
|
+
REFERENCE_VOLTAGE = 3.3
|
|
38
|
+
|
|
39
|
+
$stdout.sync = true
|
|
40
|
+
|
|
41
|
+
puts "spidev buses: #{Rgpio::SPI.devices.inspect}"
|
|
42
|
+
|
|
43
|
+
adc = Rgpio::MCP3208.new(reference_voltage: REFERENCE_VOLTAGE)
|
|
44
|
+
puts "MCP3208 on #{adc.spi.path} at #{adc.spi.speed_hz / 1000} kHz. Ctrl-C to stop."
|
|
45
|
+
puts " #{(0...adc.channels).map { |c| format("%7s", "CH#{c}") }.join}"
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
loop do
|
|
49
|
+
codes = adc.read_all
|
|
50
|
+
puts "raw #{codes.map { |v| format("%7d", v) }.join}"
|
|
51
|
+
puts "volts #{codes.map do |v|
|
|
52
|
+
format("%7.3f", v / (Rgpio::MCP3208::RESOLUTION - 1).to_f * REFERENCE_VOLTAGE)
|
|
53
|
+
end.join}"
|
|
54
|
+
sleep INTERVAL
|
|
55
|
+
end
|
|
56
|
+
rescue Interrupt
|
|
57
|
+
puts "\nStopped."
|
|
58
|
+
ensure
|
|
59
|
+
adc.close
|
|
60
|
+
end
|
data/examples/adc_led.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Turn a knob, change an LED's brightness: an MCP3208 channel driving a PWMLED.
|
|
4
|
+
#
|
|
5
|
+
# Wiring: the MCP3208 as in examples/adc.rb, plus
|
|
6
|
+
# potentiometer -- one end to 3.3 V, the other to GND, wiper to CH0
|
|
7
|
+
# GPIO4 (pin 7) -- 1k resistor -- LED anode, LED cathode -- GND
|
|
8
|
+
#
|
|
9
|
+
# Run:
|
|
10
|
+
# ruby examples/adc_led.rb
|
|
11
|
+
|
|
12
|
+
require_relative "../lib/rgpio"
|
|
13
|
+
|
|
14
|
+
KNOB_CHANNEL = 0
|
|
15
|
+
LED_GPIO = 4
|
|
16
|
+
INTERVAL = 0.02
|
|
17
|
+
|
|
18
|
+
$stdout.sync = true
|
|
19
|
+
|
|
20
|
+
adc = Rgpio::MCP3208.new
|
|
21
|
+
led = Rgpio::PWMLED.new(LED_GPIO)
|
|
22
|
+
|
|
23
|
+
puts "Turn the knob on CH#{KNOB_CHANNEL} to dim the LED on GPIO#{LED_GPIO}. Ctrl-C to stop."
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
last_shown = nil
|
|
27
|
+
loop do
|
|
28
|
+
level = adc.value(KNOB_CHANNEL)
|
|
29
|
+
led.value = level
|
|
30
|
+
|
|
31
|
+
# Only redraw when it moves, so a still knob does not scroll the terminal.
|
|
32
|
+
percent = (level * 100).round
|
|
33
|
+
if percent != last_shown
|
|
34
|
+
puts format("CH%d = %4.2f (%3d%%) %s", KNOB_CHANNEL, level, percent, "#" * (percent / 2))
|
|
35
|
+
last_shown = percent
|
|
36
|
+
end
|
|
37
|
+
sleep INTERVAL
|
|
38
|
+
end
|
|
39
|
+
rescue Interrupt
|
|
40
|
+
puts "\nStopped."
|
|
41
|
+
ensure
|
|
42
|
+
led.close
|
|
43
|
+
adc.close
|
|
44
|
+
end
|
data/examples/button.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# React to a switch with the high-level Rgpio::Button device, which runs the
|
|
4
|
+
# callbacks on a background thread. For the same thing written against
|
|
5
|
+
# Chip/LineRequest directly, see examples/lowlevel/button.rb.
|
|
6
|
+
#
|
|
7
|
+
# Wiring — this circuit puts the switch on the 3.3 V side, so the line needs
|
|
8
|
+
# the internal pull-down that Button uses by default:
|
|
9
|
+
# 3.3 V (pin 1) -- switch -- 1 kΩ resistor -- GPIO4 (pin 7)
|
|
10
|
+
#
|
|
11
|
+
# For the other common wiring (switch to GND) pass pull_up: true instead:
|
|
12
|
+
# button = Rgpio::Button.new(4, pull_up: true)
|
|
13
|
+
#
|
|
14
|
+
# Run:
|
|
15
|
+
# ruby examples/button.rb
|
|
16
|
+
#
|
|
17
|
+
# Press and release the switch; stop with Ctrl-C.
|
|
18
|
+
|
|
19
|
+
require_relative "../lib/rgpio"
|
|
20
|
+
|
|
21
|
+
BUTTON_GPIO = 4
|
|
22
|
+
|
|
23
|
+
button = Rgpio::Button.new(BUTTON_GPIO)
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
button.when_pressed { puts "Pressed" }
|
|
27
|
+
button.when_released { puts "Released" }
|
|
28
|
+
|
|
29
|
+
puts "Waiting for GPIO#{BUTTON_GPIO}. Press Ctrl-C to stop."
|
|
30
|
+
Rgpio.pause
|
|
31
|
+
ensure
|
|
32
|
+
button.close
|
|
33
|
+
end
|
data/examples/lcd.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Print text on an ST7032 character LCD (Akizuki AQM0802 8x2 / AQM1602 16x2).
|
|
4
|
+
#
|
|
5
|
+
# Wiring (module runs on 3.3 V; the Akizuki kit board carries the pull-ups):
|
|
6
|
+
# 3.3V (pin 1) -- VDD
|
|
7
|
+
# GND (pin 9) -- GND
|
|
8
|
+
# GPIO2 (pin 3, SDA) -- SDA
|
|
9
|
+
# GPIO3 (pin 5, SCL) -- SCL
|
|
10
|
+
#
|
|
11
|
+
# Prerequisite — the header I2C bus must be enabled:
|
|
12
|
+
# sudo raspi-config nonint do_i2c 0 # or: dtparam=i2c_arm=on in config.txt
|
|
13
|
+
# sudo reboot
|
|
14
|
+
#
|
|
15
|
+
# Verify:
|
|
16
|
+
# i2cdetect -y 1 # the display answers at 0x3e
|
|
17
|
+
#
|
|
18
|
+
# Run:
|
|
19
|
+
# ruby examples/lcd.rb
|
|
20
|
+
#
|
|
21
|
+
# A blank display with a sensible-looking backlight is almost always contrast;
|
|
22
|
+
# pass contrast: 0..63 to ST7032.new until the characters appear.
|
|
23
|
+
|
|
24
|
+
require_relative "../lib/rgpio"
|
|
25
|
+
|
|
26
|
+
COLUMNS = 8 # 16 for an AQM1602
|
|
27
|
+
|
|
28
|
+
Rgpio::ST7032.open(columns: COLUMNS) do |lcd|
|
|
29
|
+
lcd.message = "Hello\nrgpio"
|
|
30
|
+
sleep 2
|
|
31
|
+
|
|
32
|
+
# The cursor addresses each row independently.
|
|
33
|
+
lcd.clear
|
|
34
|
+
3.times do |i|
|
|
35
|
+
lcd.move_to(0, 0)
|
|
36
|
+
lcd.print("count".ljust(COLUMNS))
|
|
37
|
+
lcd.move_to(0, 1)
|
|
38
|
+
lcd.print((i + 1).to_s.ljust(COLUMNS))
|
|
39
|
+
sleep 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
lcd.message = "bye"
|
|
43
|
+
sleep 1
|
|
44
|
+
lcd.clear
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
puts "Done."
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Show the ADT7410's temperature on an ST7032 LCD — the two I2C devices from
|
|
4
|
+
# the book on one bus, each with its own address.
|
|
5
|
+
#
|
|
6
|
+
# Wiring: both modules share the same four header pins.
|
|
7
|
+
# 3.3V (pin 1) -- VDD (sensor and display)
|
|
8
|
+
# GND (pin 9) -- GND (sensor and display)
|
|
9
|
+
# GPIO2 (pin 3, SDA) -- SDA (sensor and display)
|
|
10
|
+
# GPIO3 (pin 5, SCL) -- SCL (sensor and display)
|
|
11
|
+
#
|
|
12
|
+
# Prerequisite — the header I2C bus must be enabled:
|
|
13
|
+
# sudo raspi-config nonint do_i2c 0 # or: dtparam=i2c_arm=on in config.txt
|
|
14
|
+
# sudo reboot
|
|
15
|
+
#
|
|
16
|
+
# Verify:
|
|
17
|
+
# i2cdetect -y 1 # 0x3e (display) and 0x48 (sensor) both answer
|
|
18
|
+
#
|
|
19
|
+
# Run:
|
|
20
|
+
# ruby examples/lcd_thermometer.rb
|
|
21
|
+
|
|
22
|
+
require_relative "../lib/rgpio"
|
|
23
|
+
|
|
24
|
+
COLUMNS = 8 # 16 for an AQM1602
|
|
25
|
+
INTERVAL = 1.0
|
|
26
|
+
|
|
27
|
+
$stdout.sync = true # so the readings still appear when piped to a file
|
|
28
|
+
|
|
29
|
+
sensor = Rgpio::ADT7410.new
|
|
30
|
+
lcd = Rgpio::ST7032.new(columns: COLUMNS)
|
|
31
|
+
|
|
32
|
+
sleep Rgpio::ADT7410::CONVERSION_TIME
|
|
33
|
+
|
|
34
|
+
# The label never changes, so write it once.
|
|
35
|
+
lcd.move_to(0, 0)
|
|
36
|
+
lcd.print("Temp".ljust(COLUMNS))
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
loop do
|
|
40
|
+
celsius = sensor.temperature
|
|
41
|
+
|
|
42
|
+
# Overwrite the value in place rather than clearing: a clear blanks the
|
|
43
|
+
# panel for the width of the next transfer, which reads as a flicker once
|
|
44
|
+
# a second. Padding to the full row erases the previous, longer value.
|
|
45
|
+
lcd.move_to(0, 1)
|
|
46
|
+
lcd.print(format("%.1f C", celsius).rjust(COLUMNS))
|
|
47
|
+
|
|
48
|
+
puts format("%.2f degC", celsius)
|
|
49
|
+
sleep INTERVAL
|
|
50
|
+
end
|
|
51
|
+
rescue Interrupt
|
|
52
|
+
puts "\nStopped."
|
|
53
|
+
ensure
|
|
54
|
+
lcd.clear
|
|
55
|
+
lcd.close
|
|
56
|
+
sensor.close
|
|
57
|
+
end
|
data/examples/led.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Blink an LED with the high-level Rgpio::LED device.
|
|
4
|
+
# For the same thing written against Chip/LineRequest directly, see
|
|
5
|
+
# examples/lowlevel/blink.rb.
|
|
6
|
+
#
|
|
7
|
+
# Wiring:
|
|
8
|
+
# GPIO4 (pin 7) -- 1 kΩ resistor -- LED anode (long leg)
|
|
9
|
+
# LED cathode (short leg) -- GND (pin 6 or any GND)
|
|
10
|
+
#
|
|
11
|
+
# Run:
|
|
12
|
+
# ruby examples/led.rb
|
|
13
|
+
#
|
|
14
|
+
# The LED turns on and off five times at one-second intervals.
|
|
15
|
+
|
|
16
|
+
require_relative "../lib/rgpio"
|
|
17
|
+
|
|
18
|
+
LED_GPIO = 4
|
|
19
|
+
|
|
20
|
+
led = Rgpio::LED.new(LED_GPIO)
|
|
21
|
+
|
|
22
|
+
begin
|
|
23
|
+
5.times do
|
|
24
|
+
led.on
|
|
25
|
+
sleep 1
|
|
26
|
+
led.off
|
|
27
|
+
sleep 1
|
|
28
|
+
end
|
|
29
|
+
ensure
|
|
30
|
+
led.close
|
|
31
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Blink an LED connected to GPIO17.
|
|
4
|
+
#
|
|
5
|
+
# Wiring:
|
|
6
|
+
# Pi 5 pin 11 (GPIO17) --[330 Ω]-- LED anode
|
|
7
|
+
# LED cathode -- GND (pin 9 or any GND pin)
|
|
8
|
+
#
|
|
9
|
+
# Run:
|
|
10
|
+
# ruby examples/lowlevel/blink.rb
|
|
11
|
+
#
|
|
12
|
+
# Ctrl-C to stop.
|
|
13
|
+
|
|
14
|
+
require_relative "../../lib/rgpio"
|
|
15
|
+
|
|
16
|
+
GPIO_LED = 17
|
|
17
|
+
BLINK_DELAY = 0.5 # seconds
|
|
18
|
+
|
|
19
|
+
puts "libgpiod version: #{Rgpio.version}"
|
|
20
|
+
puts "Blinking GPIO#{GPIO_LED} at #{1.0 / (BLINK_DELAY * 2)} Hz. Press Ctrl-C to stop."
|
|
21
|
+
|
|
22
|
+
# No path given → auto-detect the header GPIO controller, so the same
|
|
23
|
+
# script runs unchanged on Pi 5 / Pi 4 / Pi Zero.
|
|
24
|
+
Rgpio::Chip.open do |chip|
|
|
25
|
+
puts "Chip: #{chip.path} #{chip.label} (#{chip.num_lines} lines)"
|
|
26
|
+
|
|
27
|
+
request = chip.request_lines(
|
|
28
|
+
offsets: [GPIO_LED],
|
|
29
|
+
direction: :output,
|
|
30
|
+
consumer: "rgpio-blink"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
loop do
|
|
35
|
+
request.set_value(GPIO_LED, :active)
|
|
36
|
+
sleep BLINK_DELAY
|
|
37
|
+
request.set_value(GPIO_LED, :inactive)
|
|
38
|
+
sleep BLINK_DELAY
|
|
39
|
+
end
|
|
40
|
+
rescue Interrupt
|
|
41
|
+
puts "\nStopped."
|
|
42
|
+
ensure
|
|
43
|
+
request.set_value(GPIO_LED, :inactive)
|
|
44
|
+
request.release
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Read a button connected to GPIO27.
|
|
4
|
+
# Prints a line each time the button is pressed or released.
|
|
5
|
+
#
|
|
6
|
+
# Wiring (active-low with internal pull-up):
|
|
7
|
+
# Pi 5 pin 13 (GPIO27) -- one leg of button
|
|
8
|
+
# Button other leg -- GND (pin 14 or any GND pin)
|
|
9
|
+
#
|
|
10
|
+
# Run:
|
|
11
|
+
# ruby examples/lowlevel/button.rb
|
|
12
|
+
#
|
|
13
|
+
# Ctrl-C to stop.
|
|
14
|
+
|
|
15
|
+
require_relative "../../lib/rgpio"
|
|
16
|
+
|
|
17
|
+
GPIO_BUTTON = 27
|
|
18
|
+
DEBOUNCE_S = 0.050 # 50 ms安定でイベント確定
|
|
19
|
+
POLL_S = 0.005 # エッジイベント待ちのタイムアウト(=ポーリング間隔)
|
|
20
|
+
|
|
21
|
+
puts "libgpiod version: #{Rgpio.version}"
|
|
22
|
+
puts "Watching GPIO#{GPIO_BUTTON} for button events. Press Ctrl-C to stop."
|
|
23
|
+
|
|
24
|
+
# No path given → auto-detect the header GPIO controller (Pi 5 / 4 / Zero).
|
|
25
|
+
Rgpio::Chip.open do |chip|
|
|
26
|
+
puts "Chip: #{chip.path} #{chip.label}"
|
|
27
|
+
|
|
28
|
+
request = chip.request_lines(
|
|
29
|
+
offsets: [GPIO_BUTTON],
|
|
30
|
+
direction: :input,
|
|
31
|
+
edge: :both, # エッジ検出を有効にしてカーネルが値を更新し続けるようにする
|
|
32
|
+
bias: :pull_up,
|
|
33
|
+
active_low: true,
|
|
34
|
+
consumer: "rgpio-button"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# エッジイベントで「変化があった」と気付き、get_valueで「実際の状態」を読む。
|
|
38
|
+
# イベントのtype(:rising/:falling)は使わない。
|
|
39
|
+
# これによりクロック差・イベント順序・ノイズ問題を全て回避する。
|
|
40
|
+
stable = request.get_value(GPIO_BUTTON)
|
|
41
|
+
candidate = stable
|
|
42
|
+
candidate_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
loop do
|
|
46
|
+
# エッジイベントが来たら即座に、なければ POLL_S 後に抜ける
|
|
47
|
+
# バッファを読み切ることで溢れを防ぐ(イベント内容は捨てる)
|
|
48
|
+
request.read_edge_events(timeout: POLL_S)
|
|
49
|
+
|
|
50
|
+
current = request.get_value(GPIO_BUTTON)
|
|
51
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
52
|
+
|
|
53
|
+
if current != candidate
|
|
54
|
+
# 状態が変わった → タイマーリセット
|
|
55
|
+
candidate = current
|
|
56
|
+
candidate_since = now
|
|
57
|
+
elsif current != stable && (now - candidate_since) >= DEBOUNCE_S
|
|
58
|
+
# DEBOUNCE_S 間ずっと同じ → 確定して報告
|
|
59
|
+
stable = current
|
|
60
|
+
label = stable == :active ? "PRESSED " : "RELEASED"
|
|
61
|
+
puts "[#{now.round(3)} s] GPIO#{GPIO_BUTTON} #{label}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
rescue Interrupt
|
|
65
|
+
puts "\nStopped."
|
|
66
|
+
ensure
|
|
67
|
+
request.release
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Drive a standard RC servo motor via hardware PWM on GPIO12.
|
|
4
|
+
#
|
|
5
|
+
# Wiring:
|
|
6
|
+
# GPIO12 (pin 32, RP1 PWM channel 0) -- servo signal wire (usually yellow/orange)
|
|
7
|
+
# 5 V (pin 2 or 4) -- servo power (red)
|
|
8
|
+
# GND (pin 6 or any GND) -- servo ground (brown/black)
|
|
9
|
+
#
|
|
10
|
+
# Prerequisites — GPIO12 must be routed to RP1 PWM0. Two options:
|
|
11
|
+
#
|
|
12
|
+
# (A) No reboot, NOT persistent (gone after reboot) — apply at runtime:
|
|
13
|
+
# sudo dtoverlay pwm pin=12 func=4
|
|
14
|
+
# Undo with: sudo dtoverlay -r pwm
|
|
15
|
+
#
|
|
16
|
+
# (B) Persistent — add to /boot/firmware/config.txt and reboot:
|
|
17
|
+
# dtoverlay=pwm,pin=12,func=4
|
|
18
|
+
#
|
|
19
|
+
# Verify either way:
|
|
20
|
+
# pinctrl get 12 # should show "a0" / PWM0_CHAN0, not "none"
|
|
21
|
+
# ls /sys/class/pwm/ # a second pwmchip (RP1 PWM0) appears
|
|
22
|
+
#
|
|
23
|
+
# Run:
|
|
24
|
+
# sudo ruby examples/servo.rb
|
|
25
|
+
#
|
|
26
|
+
# The servo sweeps from minimum to maximum position and back, three times.
|
|
27
|
+
|
|
28
|
+
require_relative "../lib/rgpio"
|
|
29
|
+
|
|
30
|
+
SERVO_GPIO = 12
|
|
31
|
+
FREQUENCY_HZ = 50 # Standard servo frequency (20 ms period)
|
|
32
|
+
PULSE_MIN_US = 500 # 0.5 ms — full counter-clockwise (varies by servo)
|
|
33
|
+
PULSE_CENTER_US = 1500 # 1.5 ms — center position
|
|
34
|
+
PULSE_MAX_US = 2500 # 2.5 ms — full clockwise (varies by servo)
|
|
35
|
+
STEP_US = 10 # microseconds per step
|
|
36
|
+
STEP_DELAY = 0.005 # seconds between steps
|
|
37
|
+
|
|
38
|
+
def sweep(pwm, from_us, to_us, step_us, delay)
|
|
39
|
+
steps = ((to_us - from_us) / step_us.to_f).ceil.abs
|
|
40
|
+
direction = to_us > from_us ? 1 : -1
|
|
41
|
+
steps.times do |i|
|
|
42
|
+
pwm.pulse_width_us = from_us + (direction * i * step_us)
|
|
43
|
+
sleep delay
|
|
44
|
+
end
|
|
45
|
+
pwm.pulse_width_us = to_us
|
|
46
|
+
sleep delay
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
puts "libgpiod version: #{Rgpio.version}"
|
|
50
|
+
puts "Available PWM chips: #{Rgpio::HardwarePWM.available_chips.inspect}"
|
|
51
|
+
puts "Driving servo on GPIO#{SERVO_GPIO} at #{FREQUENCY_HZ} Hz."
|
|
52
|
+
|
|
53
|
+
Rgpio::HardwarePWM.open(gpio: SERVO_GPIO) do |pwm|
|
|
54
|
+
puts "Using pwmchip#{pwm.chip_num}, channel #{pwm.channel}"
|
|
55
|
+
pwm.frequency = FREQUENCY_HZ
|
|
56
|
+
pwm.duty_cycle = 0.0 # start with duty=0 before enabling
|
|
57
|
+
pwm.enable
|
|
58
|
+
|
|
59
|
+
# Move to center first
|
|
60
|
+
pwm.pulse_width_us = PULSE_CENTER_US
|
|
61
|
+
sleep 0.5
|
|
62
|
+
|
|
63
|
+
3.times do |run|
|
|
64
|
+
puts "Sweep #{run + 1}/3: min → max → min"
|
|
65
|
+
sweep(pwm, PULSE_CENTER_US, PULSE_MAX_US, STEP_US, STEP_DELAY)
|
|
66
|
+
sweep(pwm, PULSE_MAX_US, PULSE_MIN_US, STEP_US, STEP_DELAY)
|
|
67
|
+
sweep(pwm, PULSE_MIN_US, PULSE_CENTER_US, STEP_US, STEP_DELAY)
|
|
68
|
+
sleep 0.3
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
puts "Done. Returning to center."
|
|
72
|
+
pwm.pulse_width_us = PULSE_CENTER_US
|
|
73
|
+
sleep 0.5
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
puts "PWM disabled and unexported."
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Report movement from a PIR sensor with Rgpio::MotionSensor.
|
|
4
|
+
#
|
|
5
|
+
# Sensor: a D-SUN PIR module — the HC-SR501 layout, with a BISS0001 controller,
|
|
6
|
+
# a white dome, two trimmers and a three-pin mode jumper. It runs on 5 V and
|
|
7
|
+
# not on 3.3 V (the onboard regulator needs the headroom), and its OUT pin
|
|
8
|
+
# swings 0/3.3 V because the BISS0001 sits behind that regulator, so it drives
|
|
9
|
+
# a GPIO line directly.
|
|
10
|
+
#
|
|
11
|
+
# Some clones drive OUT to VCC instead, and 5 V on a GPIO pin damages the Pi.
|
|
12
|
+
# With an unfamiliar module, leave OUT disconnected, power the sensor, wave at
|
|
13
|
+
# it and measure OUT against GND: 3.3 V is safe to wire up, 5 V needs a divider
|
|
14
|
+
# (OUT -- 10 kΩ -- GPIO4 -- 20 kΩ -- GND) or a level shifter.
|
|
15
|
+
#
|
|
16
|
+
# Wiring (PIR sensor):
|
|
17
|
+
# VCC -- 5 V (pin 2)
|
|
18
|
+
# GND -- GND (pin 6)
|
|
19
|
+
# OUT -- GPIO4 (pin 7)
|
|
20
|
+
#
|
|
21
|
+
# Run:
|
|
22
|
+
# ruby examples/motion_sensor.rb
|
|
23
|
+
#
|
|
24
|
+
# Move in front of the sensor; stop with Ctrl-C. Use the two trimmers on the
|
|
25
|
+
# sensor to adjust sensitivity and hold time while this is running — which
|
|
26
|
+
# trimmer is which varies by batch, so turn one and watch whether the length of
|
|
27
|
+
# a detection changes.
|
|
28
|
+
#
|
|
29
|
+
# Two things about the module shape what this prints. It settles for up to a
|
|
30
|
+
# minute after power-on and fires at random until it has, so detections inside
|
|
31
|
+
# the warm-up window are reported but left out of the count. And OUT stays high
|
|
32
|
+
# for the hold time after a detection, so continuous movement counts once per
|
|
33
|
+
# hold period rather than once per movement. Both timings come from resistors
|
|
34
|
+
# and capacitors on the board and vary between clones: the hold time bottoms
|
|
35
|
+
# out at 0.9 s on the D-SUN board this was written against, where the HC-SR501
|
|
36
|
+
# datasheet says 5 s. Measure rather than assume.
|
|
37
|
+
#
|
|
38
|
+
# These modules also trigger on power-rail noise as readily as on movement —
|
|
39
|
+
# the BISS0001 front end has a lot of gain and the Pi's 5 V pin is a switching
|
|
40
|
+
# regulator's output. If detections arrive with nothing moving, put 100 µF and
|
|
41
|
+
# 0.1 µF across VCC/GND at the sensor and turn the sensitivity down.
|
|
42
|
+
|
|
43
|
+
require_relative "../lib/rgpio"
|
|
44
|
+
|
|
45
|
+
SENSOR_GPIO = 4
|
|
46
|
+
WARMUP_SECONDS = 60
|
|
47
|
+
|
|
48
|
+
sensor = Rgpio::MotionSensor.new(SENSOR_GPIO)
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
# CLOCK_MONOTONIC, not Time.now: a Pi has no RTC, so the wall clock can jump
|
|
52
|
+
# by hours the moment NTP answers — often within a minute of boot.
|
|
53
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
54
|
+
count = 0
|
|
55
|
+
|
|
56
|
+
# Only the watcher thread runs this block, so the counter needs no lock.
|
|
57
|
+
sensor.when_motion do
|
|
58
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at < WARMUP_SECONDS
|
|
59
|
+
puts "motion detected! (warming up...)"
|
|
60
|
+
else
|
|
61
|
+
count += 1
|
|
62
|
+
puts "motion detected! #{count}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
puts "Watching GPIO#{SENSOR_GPIO}, counting after #{WARMUP_SECONDS} s of warm-up. Press Ctrl-C to stop."
|
|
67
|
+
Rgpio.pause
|
|
68
|
+
ensure
|
|
69
|
+
sensor.close
|
|
70
|
+
end
|
data/examples/motor.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Drive a DC motor forward and backward with Rgpio::Motor through a DRV8835
|
|
4
|
+
# two-input motor driver.
|
|
5
|
+
#
|
|
6
|
+
# Wiring (DRV8835):
|
|
7
|
+
# VM, GND -- motor power supply (+ / -)
|
|
8
|
+
# VCC -- 3.3 V (pin 1)
|
|
9
|
+
# AOUT1/2 -- motor A terminals
|
|
10
|
+
# AIN1 -- GPIO2 (forward)
|
|
11
|
+
# AIN2 -- GPIO14 (backward)
|
|
12
|
+
#
|
|
13
|
+
# GPIO2 is used for forward rather than GPIO4, which can already be claimed by
|
|
14
|
+
# another consumer and then fails the request as busy.
|
|
15
|
+
#
|
|
16
|
+
# Run:
|
|
17
|
+
# ruby examples/motor.rb
|
|
18
|
+
#
|
|
19
|
+
# The motor runs forward for five seconds, then backward, until Ctrl-C.
|
|
20
|
+
|
|
21
|
+
require_relative "../lib/rgpio"
|
|
22
|
+
|
|
23
|
+
motor = Rgpio::Motor.new(forward: 2, backward: 14)
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
loop do
|
|
27
|
+
puts "forward"
|
|
28
|
+
motor.forward
|
|
29
|
+
sleep 5
|
|
30
|
+
puts "backward"
|
|
31
|
+
motor.backward
|
|
32
|
+
sleep 5
|
|
33
|
+
end
|
|
34
|
+
rescue Interrupt
|
|
35
|
+
puts "\nStopping."
|
|
36
|
+
ensure
|
|
37
|
+
motor.close
|
|
38
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Non-destructive hardware-PWM diagnostic.
|
|
4
|
+
#
|
|
5
|
+
# Prints the detected board, the PWM chips present in sysfs (with their device
|
|
6
|
+
# address and channel count), and — WITHOUT exporting anything — which chip and
|
|
7
|
+
# channel each header GPIO would resolve to. Handy for validating board support
|
|
8
|
+
# (e.g. Pi 4) before driving a real signal.
|
|
9
|
+
#
|
|
10
|
+
# Run (no root needed — it only reads sysfs):
|
|
11
|
+
# ruby examples/pwm_info.rb
|
|
12
|
+
|
|
13
|
+
require_relative "../lib/rgpio"
|
|
14
|
+
|
|
15
|
+
HW = Rgpio::HardwarePWM
|
|
16
|
+
|
|
17
|
+
# Read-only preview of HardwarePWM#detect_pwm_chip! — never exports a channel.
|
|
18
|
+
def resolve_chip(board, chips)
|
|
19
|
+
profile = HW::PWM_CHIP_PROFILE[board]
|
|
20
|
+
if profile
|
|
21
|
+
by_address = chips.find do |c|
|
|
22
|
+
(File.readlink(c[:path]) rescue "").include?(profile[:address])
|
|
23
|
+
end
|
|
24
|
+
return [by_address[:chip], "address #{profile[:address]}"] if by_address
|
|
25
|
+
|
|
26
|
+
by_npwm = chips.find { |c| c[:npwm] == profile[:npwm] }
|
|
27
|
+
return [by_npwm[:chip], "npwm==#{profile[:npwm]}"] if by_npwm
|
|
28
|
+
end
|
|
29
|
+
return [chips.first[:chip], "only chip"] if chips.size == 1
|
|
30
|
+
|
|
31
|
+
[nil, "ambiguous — pass chip: explicitly"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
puts "libgpiod: #{Rgpio.available? ? Rgpio.version : "not available"}"
|
|
35
|
+
puts "board model: #{HW.board_model.inspect}"
|
|
36
|
+
|
|
37
|
+
board = HW.detect_board
|
|
38
|
+
puts "detected board: #{board.inspect}"
|
|
39
|
+
puts
|
|
40
|
+
|
|
41
|
+
chips = HW.available_chips
|
|
42
|
+
if chips.empty?
|
|
43
|
+
puts "No PWM chips under #{HW::PWM_SYSFS_ROOT}. Enable the PWM dtoverlay first."
|
|
44
|
+
exit 1
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
puts "PWM chips:"
|
|
48
|
+
chips.each do |c|
|
|
49
|
+
link = File.readlink(c[:path]) rescue "(not a symlink)"
|
|
50
|
+
puts " pwmchip#{c[:chip]} npwm=#{c[:npwm]} -> #{link}"
|
|
51
|
+
end
|
|
52
|
+
puts
|
|
53
|
+
|
|
54
|
+
chip_num, why = resolve_chip(board, chips)
|
|
55
|
+
puts "chip auto-detection for #{board.inspect}: " \
|
|
56
|
+
"#{chip_num ? "pwmchip#{chip_num}" : "FAILED"} (#{why})"
|
|
57
|
+
puts
|
|
58
|
+
|
|
59
|
+
table = HW::GPIO_TO_PWM_CHANNEL[board]
|
|
60
|
+
if table
|
|
61
|
+
puts "header GPIO -> channel (#{board.inspect}):"
|
|
62
|
+
table.each do |gpio, channel|
|
|
63
|
+
puts " GPIO#{gpio} -> pwmchip#{chip_num || "?"}, channel #{channel}"
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
puts "No header GPIO -> channel mapping for board #{board.inspect}. " \
|
|
67
|
+
"Pass board: :pi5/:pi4 or chip:/channel: explicitly."
|
|
68
|
+
end
|