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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +103 -0
  3. data/LICENSE +21 -0
  4. data/PLAN.md +347 -0
  5. data/README.md +969 -0
  6. data/examples/adc.rb +60 -0
  7. data/examples/adc_led.rb +44 -0
  8. data/examples/button.rb +33 -0
  9. data/examples/lcd.rb +47 -0
  10. data/examples/lcd_thermometer.rb +57 -0
  11. data/examples/led.rb +31 -0
  12. data/examples/lowlevel/blink.rb +46 -0
  13. data/examples/lowlevel/button.rb +69 -0
  14. data/examples/lowlevel/servo.rb +76 -0
  15. data/examples/motion_sensor.rb +70 -0
  16. data/examples/motor.rb +38 -0
  17. data/examples/pwm_info.rb +68 -0
  18. data/examples/pwm_jitter.rb +139 -0
  19. data/examples/pwm_led.rb +56 -0
  20. data/examples/rgb_balance.rb +65 -0
  21. data/examples/rgb_led.rb +72 -0
  22. data/examples/servo.rb +70 -0
  23. data/examples/temperature.rb +53 -0
  24. data/lib/rgpio/bytes.rb +12 -0
  25. data/lib/rgpio/chip.rb +271 -0
  26. data/lib/rgpio/devices/adt7410.rb +116 -0
  27. data/lib/rgpio/devices/device.rb +41 -0
  28. data/lib/rgpio/devices/input_device.rb +150 -0
  29. data/lib/rgpio/devices/mcp3208.rb +104 -0
  30. data/lib/rgpio/devices/motor.rb +51 -0
  31. data/lib/rgpio/devices/output_device.rb +66 -0
  32. data/lib/rgpio/devices/pwm_channel.rb +25 -0
  33. data/lib/rgpio/devices/pwm_output_device.rb +109 -0
  34. data/lib/rgpio/devices/rgb_led.rb +175 -0
  35. data/lib/rgpio/devices/servo.rb +161 -0
  36. data/lib/rgpio/devices/st7032.rb +234 -0
  37. data/lib/rgpio/i2c.rb +175 -0
  38. data/lib/rgpio/line_request.rb +184 -0
  39. data/lib/rgpio/native.rb +238 -0
  40. data/lib/rgpio/pwm.rb +321 -0
  41. data/lib/rgpio/software_pwm.rb +290 -0
  42. data/lib/rgpio/spi.rb +208 -0
  43. data/lib/rgpio/version.rb +3 -0
  44. data/lib/rgpio.rb +99 -0
  45. 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
@@ -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
@@ -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