lgpio 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +49 -11
- data/examples/i2c_aht10.rb +36 -0
- data/examples/i2c_aht10_zip.rb +36 -0
- data/examples/i2c_ssd1306_bench.rb +25 -0
- data/examples/reports.rb +19 -0
- data/examples/rotary_encoder.rb +54 -0
- data/examples/rotary_encoder_led.rb +56 -0
- data/examples/spi_loopback.rb +18 -0
- data/examples/spi_read.rb +14 -0
- data/examples/spi_ws2812.rb +60 -0
- data/ext/lgpio/lgpio.c +206 -0
- data/lib/lgpio/version.rb +1 -1
- metadata +12 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e24e221cb39d27714d3fdd9486c47dd01e33115980d29e5104a455e14fd40890
|
4
|
+
data.tar.gz: 4ffd6e35644bc8dcde49486cd43d8e7ca73ebbfe9b46955a8de89a4ecff49d33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5060a2fd7a5ef20a1de29f01a87aa2ff2639c9d0e9005594de672d7fa011df48e7a65b893ad4310d07d780f9677c1544ccc80018a5bb4794e014513f4864fae4
|
7
|
+
data.tar.gz: 16e6a32af1a358ffcf18c7864d2748527f635a83f42734fc3191ecb6eed7fb50cef3ffe4370651b063fde6e4a28bd2075a12b6adb8d04b3e4646353d973515c3
|
data/README.md
CHANGED
@@ -1,16 +1,54 @@
|
|
1
1
|
# lgpio
|
2
2
|
|
3
|
-
Ruby bindings for the [lgpio (lg)](https://github.com/joan2937/lg) C library,
|
3
|
+
Ruby bindings for the [lgpio (lg)](https://github.com/joan2937/lg) C library, for Linux on single board computers (SBCs), like the Raspberry Pi and its competitors.
|
4
4
|
|
5
|
-
|
5
|
+
## Features
|
6
|
+
- GPIO Read/Write
|
7
|
+
- GPIO Group Read/Write
|
8
|
+
- GPIO Alerts / Callbacks
|
9
|
+
- lg generates alerts at high speed, in a separate thread. In Ruby they can be read from a queue as part of your application loop.
|
10
|
+
- PWM Output
|
11
|
+
- Software timed on any pin. No interface for hardware PWM yet.
|
12
|
+
- Wave
|
13
|
+
- Software timed on any pin, as with PWM.
|
14
|
+
- I2C
|
15
|
+
- SPI
|
6
16
|
|
7
|
-
|
17
|
+
## Installation
|
18
|
+
On Debian-based Linuxes (RaspberryPi OS, Armbian, DietPi etc.):
|
19
|
+
```bash
|
20
|
+
sudo apt install swig python3-dev python3-setuptools
|
8
21
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
-
|
16
|
-
|
22
|
+
# NOTE: There's a bug with GPIO numbers > 255.
|
23
|
+
# If you need to use those, wget the second URL instead, until that fix is merged.
|
24
|
+
wget https://github.com/joan2937/lg/archive/master.zip
|
25
|
+
# wget https://github.com/vickash/lg/archive/refs/heads/master.zip
|
26
|
+
|
27
|
+
unzip master.zip
|
28
|
+
cd lg-master
|
29
|
+
make
|
30
|
+
sudo make install
|
31
|
+
|
32
|
+
gem install lgpio
|
33
|
+
```
|
34
|
+
|
35
|
+
## Enabling Hardware & Permissions
|
36
|
+
Depending on your SBC and Linux distro/version, you may need to manually enable I2C and SPI hardware. You may use the setup or config tool that came with your distro for that.
|
37
|
+
|
38
|
+
You may also not have permission to access some or all of the GPIO and peripherals. To run without `sudo`, you will need read+write permission to some or all of the following devices:
|
39
|
+
```
|
40
|
+
/dev/gpiochip* (For GPIO, example: /dev/gpiochip0)
|
41
|
+
/dev/i2c-* (For I2C, example: /dev/i2c-1)
|
42
|
+
/dev/spidev* (For SPI, example: /dev/spidev-0.1)
|
43
|
+
```
|
44
|
+
|
45
|
+
## Documentation
|
46
|
+
- See examples folder for demos of every implemented interface.
|
47
|
+
- Development was done on an Orange Pi Zero 2W. Your GPIO numbers will be different, so change them.
|
48
|
+
- For more info, see the [lgpio C API docs.](https://abyz.me.uk/lg/lgpio.html)
|
49
|
+
- As much as possible, the Ruby methods closely follow the C API functions, except:
|
50
|
+
- Snake case instead of camel case names for methods.
|
51
|
+
- Method names have the leading `lg` removed, since inside the `LGPIO` class.
|
52
|
+
- Constants have leading `LG_` removed, as above.
|
53
|
+
- "count" or "length" arguments associated with array args are not needed.
|
54
|
+
- Check the return values of your method calls. On failure, they return negative values values, matching the `LG_` error codes at the bottom of the C API doc page.
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'lgpio'
|
2
|
+
|
3
|
+
I2C_DEV = 2
|
4
|
+
POWER_ON_DELAY = 0.100
|
5
|
+
RESET_DELAY = 0.020
|
6
|
+
COMMAND_DELAY = 0.010
|
7
|
+
MEASURE_DELAY = 0.080
|
8
|
+
DATA_LENGTH = 6
|
9
|
+
SOFT_RESET = [0xBA]
|
10
|
+
INIT_AND_CALIBRATE = [0xE1, 0x08, 0x00]
|
11
|
+
START_MEASUREMENT = [0xAC, 0x33, 0x00]
|
12
|
+
|
13
|
+
aht10_handle = LGPIO.i2c_open(I2C_DEV, 0x38, 0)
|
14
|
+
|
15
|
+
# Startup sequence
|
16
|
+
sleep(POWER_ON_DELAY)
|
17
|
+
LGPIO.i2c_write_device(aht10_handle, SOFT_RESET)
|
18
|
+
sleep(RESET_DELAY)
|
19
|
+
LGPIO.i2c_write_device(aht10_handle, INIT_AND_CALIBRATE)
|
20
|
+
sleep(COMMAND_DELAY)
|
21
|
+
|
22
|
+
# Read and close
|
23
|
+
LGPIO.i2c_write_device(aht10_handle, START_MEASUREMENT)
|
24
|
+
sleep(MEASURE_DELAY)
|
25
|
+
bytes = LGPIO.i2c_read_device(aht10_handle, DATA_LENGTH)
|
26
|
+
LGPIO.i2c_close(aht10_handle)
|
27
|
+
|
28
|
+
# Humidity uses the upper 4 bits of the shared byte as its lowest 4 bits.
|
29
|
+
h_raw = ((bytes[1] << 16) | (bytes[2] << 8) | (bytes[3])) >> 4
|
30
|
+
humidity = (h_raw.to_f / 2**20) * 100
|
31
|
+
|
32
|
+
# Temperature uses the lower 4 bits of the shared byte as its highest 4 bits.
|
33
|
+
t_raw = ((bytes[3] & 0x0F) << 16) | (bytes[4] << 8) | bytes[5]
|
34
|
+
temperature = (t_raw.to_f / 2**20) * 200 - 50
|
35
|
+
|
36
|
+
puts "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - Temperature: #{temperature.round(2)} \xC2\xB0C | Humidity: #{humidity.round(2)} %"
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'lgpio'
|
2
|
+
|
3
|
+
I2C_DEV = 2
|
4
|
+
POWER_ON_DELAY = 0.100
|
5
|
+
RESET_DELAY = 0.020
|
6
|
+
COMMAND_DELAY = 0.010
|
7
|
+
MEASURE_DELAY = 0.080
|
8
|
+
SOFT_RESET = [5, 1, 0xBA, 0]
|
9
|
+
INIT_AND_CALIBRATE = [5, 3, 0xE1, 0x08, 0x00, 0]
|
10
|
+
START_MEASUREMENT = [5, 3, 0xAC, 0x33, 0x00, 0]
|
11
|
+
READ_SIX = [4, 6, 0]
|
12
|
+
|
13
|
+
aht10_handle = LGPIO.i2c_open(I2C_DEV, 0x38, 0)
|
14
|
+
|
15
|
+
# Startup sequence
|
16
|
+
sleep(POWER_ON_DELAY)
|
17
|
+
LGPIO.i2c_zip(aht10_handle, SOFT_RESET, 0)
|
18
|
+
sleep(RESET_DELAY)
|
19
|
+
LGPIO.i2c_zip(aht10_handle, INIT_AND_CALIBRATE, 0)
|
20
|
+
sleep(COMMAND_DELAY)
|
21
|
+
|
22
|
+
# Read and close
|
23
|
+
LGPIO.i2c_zip(aht10_handle, START_MEASUREMENT, 0)
|
24
|
+
sleep(MEASURE_DELAY)
|
25
|
+
bytes = LGPIO.i2c_zip(aht10_handle, READ_SIX, 6)
|
26
|
+
LGPIO.i2c_close(aht10_handle)
|
27
|
+
|
28
|
+
# Humidity uses the upper 4 bits of the shared byte as its lowest 4 bits.
|
29
|
+
h_raw = ((bytes[1] << 16) | (bytes[2] << 8) | (bytes[3])) >> 4
|
30
|
+
humidity = (h_raw.to_f / 2**20) * 100
|
31
|
+
|
32
|
+
# Temperature uses the lower 4 bits of the shared byte as its highest 4 bits.
|
33
|
+
t_raw = ((bytes[3] & 0x0F) << 16) | (bytes[4] << 8) | bytes[5]
|
34
|
+
temperature = (t_raw.to_f / 2**20) * 200 - 50
|
35
|
+
|
36
|
+
puts "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - Temperature: #{temperature.round(2)} \xC2\xB0C | Humidity: #{humidity.round(2)} %"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'lgpio'
|
2
|
+
|
3
|
+
I2C_DEV = 2
|
4
|
+
INIT_ARRAY = [0, 168, 63, 211, 0, 64, 161, 200, 218, 18, 164, 166, 213, 128, 219, 32, 217, 241, 141, 20, 32, 0, 175]
|
5
|
+
START_ARRAY = [0, 33, 0, 127, 34, 0, 7]
|
6
|
+
BLANK_ARRAY = [64] + Array.new(1024) { 0 }
|
7
|
+
FILL_ARRAY = [64] + Array.new(1024) { 255 }
|
8
|
+
|
9
|
+
ssd1306_handle = LGPIO.i2c_open(I2C_DEV, 0x3C, 0)
|
10
|
+
LGPIO.i2c_write_device(ssd1306_handle, INIT_ARRAY)
|
11
|
+
FRAME_COUNT = 100
|
12
|
+
|
13
|
+
start = Time.now
|
14
|
+
(FRAME_COUNT / 2).times do
|
15
|
+
LGPIO.i2c_write_device(ssd1306_handle, START_ARRAY)
|
16
|
+
LGPIO.i2c_write_device(ssd1306_handle, FILL_ARRAY)
|
17
|
+
LGPIO.i2c_write_device(ssd1306_handle, START_ARRAY)
|
18
|
+
LGPIO.i2c_write_device(ssd1306_handle, BLANK_ARRAY)
|
19
|
+
end
|
20
|
+
finish = Time.now
|
21
|
+
|
22
|
+
LGPIO.i2c_close(ssd1306_handle)
|
23
|
+
|
24
|
+
fps = FRAME_COUNT / (finish - start)
|
25
|
+
puts "SSD1306 benchmark result: #{fps.round(2)} fps"
|
data/examples/reports.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'lgpio'
|
2
|
+
|
3
|
+
GPIO_CHIP = 0
|
4
|
+
PIN = 76
|
5
|
+
|
6
|
+
chip_handle = LGPIO.chip_open(GPIO_CHIP)
|
7
|
+
|
8
|
+
LGPIO.gpio_claim_input(chip_handle, LGPIO::SET_PULL_NONE, PIN)
|
9
|
+
LGPIO.gpio_set_debounce(chip_handle, PIN, 1)
|
10
|
+
LGPIO.gpio_claim_alert(chip_handle, 0, LGPIO::BOTH_EDGES, PIN)
|
11
|
+
LGPIO.gpio_start_reporting
|
12
|
+
|
13
|
+
loop do
|
14
|
+
report = LGPIO.gpio_get_report
|
15
|
+
report ? puts(report) : sleep(0.001)
|
16
|
+
end
|
17
|
+
|
18
|
+
LGPIO.gpio_free(chip_handle, PIN)
|
19
|
+
LGPIO.chip_close(chip_handle)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#
|
2
|
+
# Demo of a simple 30-detent rotary encoder.
|
3
|
+
# PIN_A = CLK/CLOCK, PIN_B = DT/DATA, PIN_SW = SWITCH
|
4
|
+
#
|
5
|
+
require 'lgpio'
|
6
|
+
|
7
|
+
GPIO_CHIP = 0
|
8
|
+
PIN_A = 76
|
9
|
+
PIN_B = 228
|
10
|
+
PIN_SW = 226
|
11
|
+
|
12
|
+
# Encoder state
|
13
|
+
position = 0
|
14
|
+
state_a = 0
|
15
|
+
state_b = 0
|
16
|
+
|
17
|
+
chip_handle = LGPIO.chip_open(GPIO_CHIP)
|
18
|
+
|
19
|
+
# Encoder pin setup
|
20
|
+
LGPIO.gpio_claim_input(chip_handle, LGPIO::SET_PULL_NONE, PIN_A)
|
21
|
+
LGPIO.gpio_set_debounce(chip_handle, PIN_A, 1)
|
22
|
+
LGPIO.gpio_claim_alert(chip_handle, 0, LGPIO::BOTH_EDGES, PIN_A)
|
23
|
+
LGPIO.gpio_claim_input(chip_handle, LGPIO::SET_PULL_NONE, PIN_B)
|
24
|
+
LGPIO.gpio_set_debounce(chip_handle, PIN_B, 1)
|
25
|
+
LGPIO.gpio_claim_alert(chip_handle, 0, LGPIO::BOTH_EDGES, PIN_B)
|
26
|
+
|
27
|
+
# Switch pin setup
|
28
|
+
LGPIO.gpio_claim_input(chip_handle, LGPIO::SET_PULL_UP, PIN_SW)
|
29
|
+
LGPIO.gpio_set_debounce(chip_handle, PIN_SW, 1)
|
30
|
+
LGPIO.gpio_claim_alert(chip_handle, 0, LGPIO::FALLING_EDGE, PIN_SW)
|
31
|
+
|
32
|
+
# Start generating reports for GPIO level changes.
|
33
|
+
LGPIO.gpio_start_reporting
|
34
|
+
|
35
|
+
# Get and reports to update state.
|
36
|
+
loop do
|
37
|
+
report = LGPIO.gpio_get_report
|
38
|
+
if report
|
39
|
+
if report[:gpio] == PIN_A
|
40
|
+
# Half quadrature, so we count every detent.
|
41
|
+
state_a = report[:level]
|
42
|
+
elsif report[:gpio] == PIN_B
|
43
|
+
delta = (report[:level] == state_a) ? -1 : 1
|
44
|
+
position += delta
|
45
|
+
state_b = report[:level]
|
46
|
+
puts "Position: #{position}"
|
47
|
+
elsif report[:gpio] == PIN_SW
|
48
|
+
position = 0
|
49
|
+
puts "Position: 0"
|
50
|
+
end
|
51
|
+
else
|
52
|
+
sleep(0.001)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#
|
2
|
+
# Demo of a simple 30-detent rotary encoder.
|
3
|
+
# PIN_A = CLK/CLOCK, PIN_B = DT/DATA, PIN_SW = SWITCH
|
4
|
+
#
|
5
|
+
require 'lgpio'
|
6
|
+
|
7
|
+
GPIO_CHIP = 0
|
8
|
+
PIN_A = 76
|
9
|
+
PIN_B = 228
|
10
|
+
|
11
|
+
PIN_LED = 260
|
12
|
+
PWM_FREQ = 500
|
13
|
+
PWM_OFFSET = 0
|
14
|
+
PWM_CYCLES = 0 # 0 = infinite
|
15
|
+
|
16
|
+
# Encoder state
|
17
|
+
state_a = 0
|
18
|
+
state_b = 0
|
19
|
+
led_duty = 0
|
20
|
+
|
21
|
+
chip_handle = LGPIO.chip_open(GPIO_CHIP)
|
22
|
+
|
23
|
+
# LED pin setup
|
24
|
+
LGPIO.gpio_claim_output(chip_handle, LGPIO::SET_PULL_NONE, PIN_LED, LGPIO::LOW)
|
25
|
+
|
26
|
+
# Encoder pin setup
|
27
|
+
LGPIO.gpio_claim_input(chip_handle, LGPIO::SET_PULL_NONE, PIN_A)
|
28
|
+
LGPIO.gpio_set_debounce(chip_handle, PIN_A, 1)
|
29
|
+
LGPIO.gpio_claim_alert(chip_handle, 0, LGPIO::BOTH_EDGES, PIN_A)
|
30
|
+
LGPIO.gpio_claim_input(chip_handle, LGPIO::SET_PULL_NONE, PIN_B)
|
31
|
+
LGPIO.gpio_set_debounce(chip_handle, PIN_B, 1)
|
32
|
+
LGPIO.gpio_claim_alert(chip_handle, 0, LGPIO::BOTH_EDGES, PIN_B)
|
33
|
+
|
34
|
+
# Start generating reports for GPIO level changes.
|
35
|
+
LGPIO.gpio_start_reporting
|
36
|
+
|
37
|
+
# Get and reports to update state.
|
38
|
+
loop do
|
39
|
+
report = LGPIO.gpio_get_report
|
40
|
+
if report
|
41
|
+
if report[:gpio] == PIN_A
|
42
|
+
# Half quadrature, so we count every detent.
|
43
|
+
state_a = report[:level]
|
44
|
+
elsif report[:gpio] == PIN_B
|
45
|
+
delta = (report[:level] == state_a) ? -1 : 1
|
46
|
+
state_b = report[:level]
|
47
|
+
|
48
|
+
led_duty += delta
|
49
|
+
led_duty = 0 if led_duty < 0
|
50
|
+
led_duty = 100 if led_duty > 100
|
51
|
+
LGPIO.tx_pwm(chip_handle, PIN_LED, PWM_FREQ, led_duty, PWM_OFFSET, PWM_CYCLES)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
sleep(0.001)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'lgpio'
|
2
|
+
|
3
|
+
SPI_DEV = 1
|
4
|
+
SPI_CHAN = 0
|
5
|
+
SPI_MODE = 0
|
6
|
+
SPI_BAUD = 1_000_000
|
7
|
+
|
8
|
+
spi_handle = LGPIO.spi_open(SPI_DEV, SPI_CHAN, SPI_BAUD, SPI_MODE)
|
9
|
+
|
10
|
+
tx_bytes = [0, 1, 2, 3, 4, 5, 6, 7]
|
11
|
+
puts "TX bytes: #{tx_bytes.inspect}"
|
12
|
+
|
13
|
+
# rx_bytes == tx_bytes if MOSI looped back to MISO.
|
14
|
+
# rx_byte all 255 when MOSI and MISO not connected.
|
15
|
+
rx_bytes = LGPIO.spi_xfer(spi_handle, tx_bytes)
|
16
|
+
puts "RX bytes: #{rx_bytes.inspect}"
|
17
|
+
|
18
|
+
LGPIO.spi_close(spi_handle)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'lgpio'
|
2
|
+
|
3
|
+
SPI_DEV = 1
|
4
|
+
SPI_CHAN = 0
|
5
|
+
SPI_MODE = 0
|
6
|
+
SPI_BAUD = 1_000_000
|
7
|
+
|
8
|
+
spi_handle = LGPIO.spi_open(SPI_DEV, SPI_CHAN, SPI_BAUD, SPI_MODE)
|
9
|
+
|
10
|
+
# Pull MISO high or low. High gives array of 255, low array of 0.
|
11
|
+
rx_bytes = LGPIO.spi_read(spi_handle, 8)
|
12
|
+
puts "RX bytes: #{rx_bytes.inspect}"
|
13
|
+
|
14
|
+
LGPIO.spi_close(spi_handle)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#
|
2
|
+
# 2.4 MHz SPI method for writing WS2812 addressable LEDs
|
3
|
+
# Based on:https://learn.adafruit.com/dma-driven-neopixels/overview
|
4
|
+
#
|
5
|
+
require 'lgpio'
|
6
|
+
|
7
|
+
# WS2812 Constants
|
8
|
+
NP_ONE = 0b110
|
9
|
+
NP_ZERO = 0b100
|
10
|
+
NP_START = [0]
|
11
|
+
NP_END = Array.new(90) { 0 }
|
12
|
+
BITS = (0..7).to_a.reverse
|
13
|
+
|
14
|
+
# SPI Config
|
15
|
+
SPI_DEV = 1
|
16
|
+
SPI_CHAN = 0
|
17
|
+
SPI_MODE = 0
|
18
|
+
SPI_BAUD = 2_400_000
|
19
|
+
|
20
|
+
# Map array of pixel values (3 bytes each) to raw SPI bytes (1bit -> 3 bits).
|
21
|
+
def pixel_to_raw_spi_bytes(pixel_arr)
|
22
|
+
raw_spi_bytes = []
|
23
|
+
pixel_arr.flatten.each do |byte|
|
24
|
+
long = 0b0
|
25
|
+
for i in BITS do
|
26
|
+
long = long << 3
|
27
|
+
if byte[i] == 0
|
28
|
+
long |= NP_ZERO
|
29
|
+
else
|
30
|
+
long |= NP_ONE
|
31
|
+
end
|
32
|
+
end
|
33
|
+
# Pack as big-endian uint32, then unpack to bytes, taking lowest 24 bits only.
|
34
|
+
long = [long].pack('L>')
|
35
|
+
raw_spi_bytes << long.unpack('C*')[1..3]
|
36
|
+
end
|
37
|
+
return raw_spi_bytes.flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
# 4 pixels: RGBW. Data order per pixel is GRB.
|
41
|
+
pixels_on = [
|
42
|
+
0, 255, 0,
|
43
|
+
255, 0, 0,
|
44
|
+
0, 0, 255,
|
45
|
+
255, 255, 255,
|
46
|
+
]
|
47
|
+
data_on = NP_START + pixel_to_raw_spi_bytes(pixels_on) + NP_END
|
48
|
+
|
49
|
+
# 4 pixels, all off.
|
50
|
+
pixels_off = Array.new(12) { 0 }
|
51
|
+
data_off = NP_START + pixel_to_raw_spi_bytes(pixels_off) + NP_END
|
52
|
+
|
53
|
+
spi_handle = LGPIO.spi_open(SPI_DEV, SPI_CHAN, SPI_BAUD, SPI_MODE)
|
54
|
+
|
55
|
+
loop do
|
56
|
+
LGPIO.spi_write(spi_handle, data_on)
|
57
|
+
sleep 0.5
|
58
|
+
LGPIO.spi_write(spi_handle, data_off)
|
59
|
+
sleep 0.5
|
60
|
+
end
|
data/ext/lgpio/lgpio.c
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
#include <lgpio.h>
|
2
2
|
#include <ruby.h>
|
3
3
|
|
4
|
+
// Set up a queue for up to 2**16 GPIO reports.
|
5
|
+
static pthread_mutex_t queueLock;
|
6
|
+
#define QUEUE_LENGTH UINT16_MAX + 1
|
7
|
+
static lgGpioReport_t reportQueue[QUEUE_LENGTH];
|
8
|
+
static uint16_t qWritePos = 1;
|
9
|
+
static uint16_t qReadPos = 0;
|
10
|
+
|
4
11
|
static VALUE chip_open(VALUE self, VALUE gpio_dev) {
|
5
12
|
int result = lgGpiochipOpen(NUM2INT(gpio_dev));
|
6
13
|
return INT2NUM(result);
|
@@ -76,6 +83,56 @@ static VALUE group_write(VALUE self, VALUE handle, VALUE gpio, VALUE bits, VALUE
|
|
76
83
|
return INT2NUM(result);
|
77
84
|
}
|
78
85
|
|
86
|
+
static VALUE gpio_set_debounce(VALUE self, VALUE handle, VALUE gpio, VALUE debounce) {
|
87
|
+
int result = lgGpioSetDebounce(NUM2INT(handle), NUM2INT(gpio), NUM2INT(debounce));
|
88
|
+
return INT2NUM(result);
|
89
|
+
}
|
90
|
+
|
91
|
+
static VALUE gpio_claim_alert(VALUE self, VALUE handle, VALUE flags, VALUE eFlags, VALUE gpio) {
|
92
|
+
int result = lgGpioClaimAlert(NUM2INT(handle), NUM2INT(flags), NUM2INT(eFlags), NUM2INT(gpio), -1);
|
93
|
+
return INT2NUM(result);
|
94
|
+
}
|
95
|
+
|
96
|
+
void queue_gpio_reports(int count, lgGpioAlert_p events, void *data){
|
97
|
+
pthread_mutex_lock(&queueLock);
|
98
|
+
for(int i=0; i<count; i++) {
|
99
|
+
memcpy(&reportQueue[qWritePos], &events[i].report, sizeof(lgGpioReport_t));
|
100
|
+
qWritePos += 1;
|
101
|
+
// qReadPos is the LAST report read. If passing by 1, increment it too. Lose oldest data first.
|
102
|
+
if (qWritePos - qReadPos == 1) qReadPos += 1;
|
103
|
+
}
|
104
|
+
pthread_mutex_unlock(&queueLock);
|
105
|
+
}
|
106
|
+
|
107
|
+
static VALUE gpio_start_reporting(VALUE self) {
|
108
|
+
lgGpioSetSamplesFunc(queue_gpio_reports, NULL);
|
109
|
+
return Qnil;
|
110
|
+
}
|
111
|
+
|
112
|
+
static VALUE gpio_get_report(VALUE self){
|
113
|
+
VALUE hash = rb_hash_new();
|
114
|
+
bool popped = false;
|
115
|
+
|
116
|
+
pthread_mutex_lock(&queueLock);
|
117
|
+
// qWritePos is where the NEXT report will go. Always trail it by 1.
|
118
|
+
if (qWritePos - qReadPos != 1){
|
119
|
+
qReadPos += 1;
|
120
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("timestamp")), ULL2NUM(reportQueue[qReadPos].timestamp));
|
121
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("chip")), UINT2NUM(reportQueue[qReadPos].chip));
|
122
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("gpio")), UINT2NUM(reportQueue[qReadPos].gpio));
|
123
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("level")), UINT2NUM(reportQueue[qReadPos].level));
|
124
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("flags")), UINT2NUM(reportQueue[qReadPos].flags));
|
125
|
+
popped = true;
|
126
|
+
}
|
127
|
+
pthread_mutex_unlock(&queueLock);
|
128
|
+
|
129
|
+
if (popped){
|
130
|
+
return hash;
|
131
|
+
} else {
|
132
|
+
return Qnil;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
79
136
|
static VALUE tx_busy(VALUE self, VALUE handle, VALUE gpio, VALUE kind) {
|
80
137
|
int result = lgTxBusy(NUM2INT(handle), NUM2INT(gpio), NUM2INT(kind));
|
81
138
|
return INT2NUM(result);
|
@@ -119,6 +176,132 @@ static VALUE tx_wave(VALUE self, VALUE handle, VALUE lead_gpio, VALUE pulses) {
|
|
119
176
|
return INT2NUM(result);
|
120
177
|
}
|
121
178
|
|
179
|
+
static VALUE i2c_open(VALUE self, VALUE i2cDev, VALUE i2cAddr, VALUE i2cFlags){
|
180
|
+
int handle = lgI2cOpen(NUM2INT(i2cDev), NUM2INT(i2cAddr), NUM2INT(i2cFlags));
|
181
|
+
return INT2NUM(handle);
|
182
|
+
}
|
183
|
+
|
184
|
+
static VALUE i2c_close(VALUE self, VALUE handle){
|
185
|
+
int result = lgI2cClose(NUM2INT(handle));
|
186
|
+
return INT2NUM(result);
|
187
|
+
}
|
188
|
+
|
189
|
+
static VALUE i2c_write_device(VALUE self, VALUE handle, VALUE byteArray){
|
190
|
+
int count = RARRAY_LEN(byteArray);
|
191
|
+
uint8_t txBuf[count];
|
192
|
+
VALUE currentByte;
|
193
|
+
for(int i=0; i<count; i++){
|
194
|
+
currentByte = rb_ary_entry(byteArray, i);
|
195
|
+
Check_Type(currentByte, T_FIXNUM);
|
196
|
+
txBuf[i] = NUM2CHR(currentByte);
|
197
|
+
}
|
198
|
+
|
199
|
+
int result = lgI2cWriteDevice(NUM2INT(handle), txBuf, count);
|
200
|
+
return INT2NUM(result);
|
201
|
+
}
|
202
|
+
|
203
|
+
static VALUE i2c_read_device(VALUE self, VALUE handle, VALUE count){
|
204
|
+
int rxCount = NUM2INT(count);
|
205
|
+
uint8_t rxBuf[rxCount];
|
206
|
+
|
207
|
+
int result = lgI2cReadDevice(NUM2INT(handle), rxBuf, rxCount);
|
208
|
+
if(result < 0) return INT2NUM(result);
|
209
|
+
|
210
|
+
VALUE retArray = rb_ary_new2(rxCount);
|
211
|
+
for(int i=0; i<rxCount; i++){
|
212
|
+
rb_ary_store(retArray, i, UINT2NUM(rxBuf[i]));
|
213
|
+
}
|
214
|
+
return retArray;
|
215
|
+
}
|
216
|
+
|
217
|
+
static VALUE i2c_zip(VALUE self, VALUE handle, VALUE txArray, VALUE rb_rxCount){
|
218
|
+
int txCount = RARRAY_LEN(txArray);
|
219
|
+
uint8_t txBuf[txCount];
|
220
|
+
VALUE currentByte;
|
221
|
+
for(int i=0; i<txCount; i++){
|
222
|
+
currentByte = rb_ary_entry(txArray, i);
|
223
|
+
Check_Type(currentByte, T_FIXNUM);
|
224
|
+
txBuf[i] = NUM2CHR(currentByte);
|
225
|
+
}
|
226
|
+
|
227
|
+
int rxCount = NUM2INT(rb_rxCount);
|
228
|
+
uint8_t rxBuf[rxCount+1];
|
229
|
+
|
230
|
+
// Buffer size must be rxCount+1 or result is LG_BAD_I2C_RLEN
|
231
|
+
int result = lgI2cZip(NUM2INT(handle), txBuf, txCount, rxBuf, rxCount+1);
|
232
|
+
if(result < 0) return INT2NUM(result);
|
233
|
+
|
234
|
+
if (rxCount == 0) return Qnil;
|
235
|
+
VALUE retArray = rb_ary_new2(rxCount);
|
236
|
+
for(int i=0; i<rxCount; i++){
|
237
|
+
rb_ary_store(retArray, i, UINT2NUM(rxBuf[i]));
|
238
|
+
}
|
239
|
+
return retArray;
|
240
|
+
}
|
241
|
+
|
242
|
+
static VALUE spi_open(VALUE self, VALUE spiDev, VALUE spiChan, VALUE spiBaud, VALUE spiFlags){
|
243
|
+
int handle = lgSpiOpen(NUM2INT(spiDev), NUM2INT(spiChan), NUM2INT(spiBaud), NUM2INT(spiFlags));
|
244
|
+
return INT2NUM(handle);
|
245
|
+
}
|
246
|
+
|
247
|
+
static VALUE spi_close(VALUE self, VALUE handle){
|
248
|
+
int result = lgSpiClose(NUM2INT(handle));
|
249
|
+
return INT2NUM(result);
|
250
|
+
}
|
251
|
+
|
252
|
+
static VALUE spi_read(VALUE self, VALUE handle, VALUE rxCount){
|
253
|
+
int count = NUM2INT(rxCount);
|
254
|
+
|
255
|
+
// Not sure if this needs null termination like I2C. +1 won't hurt.
|
256
|
+
uint8_t rxBuf[count+1];
|
257
|
+
|
258
|
+
int result = lgSpiRead(NUM2INT(handle), rxBuf, count);
|
259
|
+
if(result < 0) return INT2NUM(result);
|
260
|
+
|
261
|
+
VALUE retArray = rb_ary_new2(count);
|
262
|
+
for(int i=0; i<count; i++){
|
263
|
+
rb_ary_store(retArray, i, UINT2NUM(rxBuf[i]));
|
264
|
+
}
|
265
|
+
return retArray;
|
266
|
+
}
|
267
|
+
|
268
|
+
static VALUE spi_write(VALUE self, VALUE handle, VALUE txArray){
|
269
|
+
int count = RARRAY_LEN(txArray);
|
270
|
+
uint8_t txBuf[count];
|
271
|
+
VALUE currentByte;
|
272
|
+
for(int i=0; i<count; i++){
|
273
|
+
currentByte = rb_ary_entry(txArray, i);
|
274
|
+
Check_Type(currentByte, T_FIXNUM);
|
275
|
+
txBuf[i] = NUM2CHR(currentByte);
|
276
|
+
}
|
277
|
+
|
278
|
+
int result = lgSpiWrite(NUM2INT(handle), txBuf, count);
|
279
|
+
return INT2NUM(result);
|
280
|
+
}
|
281
|
+
|
282
|
+
static VALUE spi_xfer(VALUE self, VALUE handle, VALUE txArray){
|
283
|
+
int count = RARRAY_LEN(txArray);
|
284
|
+
uint8_t txBuf[count];
|
285
|
+
VALUE currentByte;
|
286
|
+
for(int i=0; i<count; i++){
|
287
|
+
currentByte = rb_ary_entry(txArray, i);
|
288
|
+
Check_Type(currentByte, T_FIXNUM);
|
289
|
+
txBuf[i] = NUM2CHR(currentByte);
|
290
|
+
}
|
291
|
+
|
292
|
+
// Not sure if this needs null termination like I2C. +1 won't hurt.
|
293
|
+
uint8_t rxBuf[count+1];
|
294
|
+
|
295
|
+
int result = lgSpiXfer(NUM2INT(handle), txBuf, rxBuf, count);
|
296
|
+
if(result < 0) return INT2NUM(result);
|
297
|
+
|
298
|
+
VALUE retArray = rb_ary_new2(count);
|
299
|
+
for(int i=0; i<count; i++){
|
300
|
+
rb_ary_store(retArray, i, UINT2NUM(rxBuf[i]));
|
301
|
+
}
|
302
|
+
return retArray;
|
303
|
+
}
|
304
|
+
|
122
305
|
void Init_lgpio(void) {
|
123
306
|
// Modules
|
124
307
|
VALUE mLGPIO = rb_define_module("LGPIO");
|
@@ -130,6 +313,9 @@ void Init_lgpio(void) {
|
|
130
313
|
rb_define_const(mLGPIO, "SET_PULL_UP", INT2NUM(LG_SET_PULL_UP));
|
131
314
|
rb_define_const(mLGPIO, "SET_PULL_DOWN", INT2NUM(LG_SET_PULL_DOWN));
|
132
315
|
rb_define_const(mLGPIO, "SET_PULL_NONE", INT2NUM(LG_SET_PULL_NONE));
|
316
|
+
rb_define_const(mLGPIO, "RISING_EDGE", INT2NUM(LG_RISING_EDGE));
|
317
|
+
rb_define_const(mLGPIO, "FALLING_EDGE", INT2NUM(LG_FALLING_EDGE));
|
318
|
+
rb_define_const(mLGPIO, "BOTH_EDGES", INT2NUM(LG_BOTH_EDGES));
|
133
319
|
rb_define_singleton_method(mLGPIO, "chip_open", chip_open, 1);
|
134
320
|
rb_define_singleton_method(mLGPIO, "chip_close", chip_close, 1);
|
135
321
|
rb_define_singleton_method(mLGPIO, "gpio_free", gpio_free, 2);
|
@@ -145,6 +331,12 @@ void Init_lgpio(void) {
|
|
145
331
|
rb_define_singleton_method(mLGPIO, "group_read", group_read, 2);
|
146
332
|
rb_define_singleton_method(mLGPIO, "group_write", group_write, 4);
|
147
333
|
|
334
|
+
// Alerts / Reports
|
335
|
+
rb_define_singleton_method(mLGPIO, "gpio_set_debounce", gpio_set_debounce, 3);
|
336
|
+
rb_define_singleton_method(mLGPIO, "gpio_claim_alert", gpio_claim_alert, 4);
|
337
|
+
rb_define_singleton_method(mLGPIO, "gpio_start_reporting", gpio_start_reporting, 0);
|
338
|
+
rb_define_singleton_method(mLGPIO, "gpio_get_report", gpio_get_report, 0);
|
339
|
+
|
148
340
|
// PWM / Servo / Wave
|
149
341
|
rb_define_const(mLGPIO, "TX_PWM", INT2NUM(LG_TX_PWM));
|
150
342
|
rb_define_const(mLGPIO, "TX_WAVE",INT2NUM(LG_TX_WAVE));
|
@@ -154,4 +346,18 @@ void Init_lgpio(void) {
|
|
154
346
|
rb_define_singleton_method(mLGPIO, "tx_pwm", tx_pwm, 6);
|
155
347
|
rb_define_singleton_method(mLGPIO, "tx_servo", tx_servo, 6);
|
156
348
|
rb_define_singleton_method(mLGPIO, "tx_wave", tx_wave, 3);
|
349
|
+
|
350
|
+
// I2C
|
351
|
+
rb_define_singleton_method(mLGPIO, "i2c_open", i2c_open, 3);
|
352
|
+
rb_define_singleton_method(mLGPIO, "i2c_close", i2c_close, 1);
|
353
|
+
rb_define_singleton_method(mLGPIO, "i2c_write_device", i2c_write_device, 2);
|
354
|
+
rb_define_singleton_method(mLGPIO, "i2c_read_device", i2c_read_device, 2);
|
355
|
+
rb_define_singleton_method(mLGPIO, "i2c_zip", i2c_zip, 3);
|
356
|
+
|
357
|
+
// SPI
|
358
|
+
rb_define_singleton_method(mLGPIO, "spi_open", spi_open, 4);
|
359
|
+
rb_define_singleton_method(mLGPIO, "spi_close", spi_close, 1);
|
360
|
+
rb_define_singleton_method(mLGPIO, "spi_read", spi_read, 2);
|
361
|
+
rb_define_singleton_method(mLGPIO, "spi_write", spi_write, 2);
|
362
|
+
rb_define_singleton_method(mLGPIO, "spi_xfer", spi_xfer, 2);
|
157
363
|
}
|
data/lib/lgpio/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lgpio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- vickash
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Use GPIO / PWM / I2C / SPI / UART on Linux SBCs in Ruby
|
14
14
|
email: mail@vickash.com
|
@@ -26,8 +26,17 @@ files:
|
|
26
26
|
- examples/blink.rb
|
27
27
|
- examples/group_in.rb
|
28
28
|
- examples/group_out.rb
|
29
|
+
- examples/i2c_aht10.rb
|
30
|
+
- examples/i2c_aht10_zip.rb
|
31
|
+
- examples/i2c_ssd1306_bench.rb
|
29
32
|
- examples/momentary.rb
|
30
33
|
- examples/pwm.rb
|
34
|
+
- examples/reports.rb
|
35
|
+
- examples/rotary_encoder.rb
|
36
|
+
- examples/rotary_encoder_led.rb
|
37
|
+
- examples/spi_loopback.rb
|
38
|
+
- examples/spi_read.rb
|
39
|
+
- examples/spi_ws2812.rb
|
31
40
|
- examples/wave.rb
|
32
41
|
- ext/lgpio/extconf.rb
|
33
42
|
- ext/lgpio/lgpio.c
|
@@ -54,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
63
|
- !ruby/object:Gem::Version
|
55
64
|
version: '0'
|
56
65
|
requirements: []
|
57
|
-
rubygems_version: 3.5.
|
66
|
+
rubygems_version: 3.5.16
|
58
67
|
signing_key:
|
59
68
|
specification_version: 4
|
60
69
|
summary: lgpio (lg) bindings for Ruby
|