lgpio 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e24e221cb39d27714d3fdd9486c47dd01e33115980d29e5104a455e14fd40890
4
- data.tar.gz: 4ffd6e35644bc8dcde49486cd43d8e7ca73ebbfe9b46955a8de89a4ecff49d33
3
+ metadata.gz: 984afd62543a10d3fc0b6d2a9a581b05b4d9bf7c159d2747f990b2247b59aef1
4
+ data.tar.gz: d2431adb24613fdd42b9f9f06a428c399fe48be2e215a13463237a798113630c
5
5
  SHA512:
6
- metadata.gz: 5060a2fd7a5ef20a1de29f01a87aa2ff2639c9d0e9005594de672d7fa011df48e7a65b893ad4310d07d780f9677c1544ccc80018a5bb4794e014513f4864fae4
7
- data.tar.gz: 16e6a32af1a358ffcf18c7864d2748527f635a83f42734fc3191ecb6eed7fb50cef3ffe4370651b063fde6e4a28bd2075a12b6adb8d04b3e4646353d973515c3
6
+ metadata.gz: f32ceb3af8218d26f1f5713302a9891365af498fe76e2a750037f2ae10a437b747b40167e9fd9c5170fc28696d90c9244ce5d9a36269eba0d0c827a79be47fcd
7
+ data.tar.gz: b167ef512729754b46dd3cd14750dd715c8541987014a254465c72619f1ead5d4ae8ff74bcbb6c9122b331c5f65a159e0f0c9890f9e0ad7a5b582dafe2763e54
data/.gitignore CHANGED
@@ -23,3 +23,4 @@ tmp
23
23
  .ruby-version
24
24
  .rvmrc
25
25
  .zed
26
+ ._*
data/README.md CHANGED
@@ -1,18 +1,29 @@
1
1
  # lgpio
2
2
 
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
-
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
3
+ Ruby bindings for the [lgpio (lg)](https://github.com/joan2937/lg) Linux library, running on single board computers (SBCs), like Raspberry Pi.
4
+
5
+ ## Mapped LGPIO Features
6
+ - [x] GPIO Read/Write
7
+ - [x] GPIO Group Read/Write
8
+ - [x] GPIO Alerts / Callbacks
9
+ - lg generates alerts at high speed in a separate thread. You can read them from a queue in Ruby, as part of your application loop.
10
+ - [x] Software PWM Out
11
+ - Software timed (not 100% precise), on any pin
12
+ - Not recommended for servo motors
13
+ - [x] Wave
13
14
  - Software timed on any pin, as with PWM.
14
- - I2C
15
- - SPI
15
+ - [x] I2C
16
+ - [x] SPI
17
+
18
+ ## Extra Features, based on LGPIO
19
+ - [x] WS2812 over SPI
20
+ - Only outputs on a SPI MOSI pin. Must be able to set SPI clock frequency to 2.4 MHz.
21
+ - [ ] Bit Bang SPI
22
+ - [ ] Bit Bang I2C
23
+
24
+ ## Sysfs PWM Interface Features
25
+ - [x] Hardware PWM Out (specific pins per chip)
26
+ - [x] Servo
16
27
 
17
28
  ## Installation
18
29
  On Debian-based Linuxes (RaspberryPi OS, Armbian, DietPi etc.):
@@ -33,9 +44,9 @@ gem install lgpio
33
44
  ```
34
45
 
35
46
  ## 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.
47
+ Depending on your SBC and Linux distro/version, you may need to manually enable I2C and SPI hardware. You should use the setup or config tool that came with your distro for that.
37
48
 
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:
49
+ You may also not have permission to access the GPIO and peripherals. To run without `sudo`, you need read+write permission to some or all of the following devices:
39
50
  ```
40
51
  /dev/gpiochip* (For GPIO, example: /dev/gpiochip0)
41
52
  /dev/i2c-* (For I2C, example: /dev/i2c-1)
@@ -44,11 +55,11 @@ You may also not have permission to access some or all of the GPIO and periphera
44
55
 
45
56
  ## Documentation
46
57
  - 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.
58
+ - Development was done on an Orange Pi Zero 2W. Your GPIO numbers may be different, so change them.
48
59
  - For more info, see the [lgpio C API docs.](https://abyz.me.uk/lg/lgpio.html)
49
60
  - As much as possible, the Ruby methods closely follow the C API functions, except:
50
61
  - Snake case instead of camel case names for methods.
51
62
  - Method names have the leading `lg` removed, since inside the `LGPIO` class.
52
63
  - Constants have leading `LG_` removed, as above.
53
64
  - "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.
65
+ - Check the return values of your method calls. On failure, they return negative values, matching the `LG_` error codes at the bottom of the C API doc page.
@@ -1,6 +1,6 @@
1
1
  require 'lgpio'
2
2
 
3
- I2C_DEV = 2
3
+ I2C_DEV = 3
4
4
  POWER_ON_DELAY = 0.100
5
5
  RESET_DELAY = 0.020
6
6
  COMMAND_DELAY = 0.010
@@ -1,6 +1,6 @@
1
1
  require 'lgpio'
2
2
 
3
- I2C_DEV = 2
3
+ I2C_DEV = 3
4
4
  POWER_ON_DELAY = 0.100
5
5
  RESET_DELAY = 0.020
6
6
  COMMAND_DELAY = 0.010
@@ -1,6 +1,6 @@
1
1
  require 'lgpio'
2
2
 
3
- I2C_DEV = 2
3
+ I2C_DEV = 3
4
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
5
  START_ARRAY = [0, 33, 0, 127, 34, 0, 7]
6
6
  BLANK_ARRAY = [64] + Array.new(1024) { 0 }
data/examples/servo.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'lgpio'
2
+ #
3
+ # Writing directly to a hardware PWM channel to control a servo.
4
+ # Arguments in order are:
5
+ # pwmchip index (X in /sys/class/pwm/pwmchipX/)
6
+ # PWM channel on the chip (Y in /sys/class/pwm/pwmchipX/pwmY)
7
+ # period: given in nanoseconds
8
+ # OR frequency: given in Hz
9
+ #
10
+ servo = LGPIO::HardwarePWM.new(0, 1, period: 20_000_000)
11
+ #
12
+ # Duty cycle is given in nanoseconds by default. Extra setter methods
13
+ # are provided for microseconds, and percent.
14
+ #
15
+ # servo.duty_percent = 2.5
16
+ # servo.duty_us = 500
17
+ servo.duty = 500_000
18
+
19
+ #
20
+ # Using the Servo class instead.
21
+ # Arguments in order are:
22
+ # pwmchip index (X in /sys/class/pwm/pwmchipX/)
23
+ # PWM channel on the chip (Y in /sys/class/pwm/pwmchipX/pwmY)
24
+ # Minimum servo duty cycle in microseconds
25
+ # Maximum servo duty cicle in microseconds
26
+ # Minimum servo angle
27
+ # Maximum servo angle
28
+ #
29
+ servo = LGPIO::PositionalServo.new(0, 1, 500, 2500, 0, 180)
30
+
31
+ angles = [0, 30, 60, 90, 120, 150, 180, 150, 120, 90, 60, 30]
32
+
33
+ angles.cycle do |angle|
34
+ servo.angle = angle
35
+ sleep 1
36
+ end
@@ -1,42 +1,15 @@
1
1
  #
2
- # 2.4 MHz SPI method for writing WS2812 addressable LEDs
3
- # Based on:https://learn.adafruit.com/dma-driven-neopixels/overview
2
+ # 2.4 MHz SPI method for writing WS2812 addressable LEDs.
3
+ # Based on: https://learn.adafruit.com/dma-driven-neopixels/overview
4
4
  #
5
5
  require 'lgpio'
6
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
7
  # SPI Config
15
8
  SPI_DEV = 1
16
9
  SPI_CHAN = 0
17
10
  SPI_MODE = 0
18
11
  SPI_BAUD = 2_400_000
19
12
 
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
13
  # 4 pixels: RGBW. Data order per pixel is GRB.
41
14
  pixels_on = [
42
15
  0, 255, 0,
@@ -44,17 +17,14 @@ pixels_on = [
44
17
  0, 0, 255,
45
18
  255, 255, 255,
46
19
  ]
47
- data_on = NP_START + pixel_to_raw_spi_bytes(pixels_on) + NP_END
48
-
49
20
  # 4 pixels, all off.
50
21
  pixels_off = Array.new(12) { 0 }
51
- data_off = NP_START + pixel_to_raw_spi_bytes(pixels_off) + NP_END
52
22
 
53
23
  spi_handle = LGPIO.spi_open(SPI_DEV, SPI_CHAN, SPI_BAUD, SPI_MODE)
54
24
 
55
25
  loop do
56
- LGPIO.spi_write(spi_handle, data_on)
26
+ LGPIO.spi_ws2812_write(spi_handle, pixels_on)
57
27
  sleep 0.5
58
- LGPIO.spi_write(spi_handle, data_off)
28
+ LGPIO.spi_ws2812_write(spi_handle, pixels_off)
59
29
  sleep 0.5
60
30
  end
@@ -0,0 +1,44 @@
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
+ # SPI config
8
+ SPI_DEV = 1
9
+ SPI_CHAN = 0
10
+ SPI_MODE = 0
11
+ SPI_BAUD = 2_400_000
12
+
13
+ PIXEL_COUNT = 8
14
+ COLORS = [
15
+ [255, 255, 255],
16
+ [0, 255, 0],
17
+ [255, 0, 0],
18
+ [0, 0, 255],
19
+ [255, 255, 0],
20
+ [255, 0, 255],
21
+ [0, 255, 255]
22
+ ]
23
+
24
+ # Move along the strip and back, one pixel at a time.
25
+ POSITIONS = (0..PIXEL_COUNT-1).to_a + (1..PIXEL_COUNT-2).to_a.reverse
26
+
27
+ pixels = Array.new(PIXEL_COUNT) { [0, 0, 0] }
28
+ spi_handle = LGPIO.spi_open(SPI_DEV, SPI_CHAN, SPI_BAUD, SPI_MODE)
29
+
30
+ loop do
31
+ COLORS.each do |color|
32
+ POSITIONS.each do |index|
33
+ # Clear and write.
34
+ (0..PIXEL_COUNT-1).each { |i| pixels[i] = [0, 0, 0] }
35
+ LGPIO.spi_ws2812_write(spi_handle, pixels.flatten)
36
+
37
+ # Set one pixel and write.
38
+ pixels[index] = color
39
+ LGPIO.spi_ws2812_write(spi_handle, pixels.flatten)
40
+
41
+ sleep 0.05
42
+ end
43
+ end
44
+ end
data/ext/lgpio/lgpio.c CHANGED
@@ -302,6 +302,42 @@ static VALUE spi_xfer(VALUE self, VALUE handle, VALUE txArray){
302
302
  return retArray;
303
303
  }
304
304
 
305
+ static VALUE spi_ws2812_write(VALUE self, VALUE handle, VALUE pixelArray){
306
+ int count = RARRAY_LEN(pixelArray);
307
+
308
+ // Pull low for at least one byte at 2.4 Mhz before data, and 90 after.
309
+ int zeroesBefore = 1;
310
+ int zeroesAfter = 90;
311
+ int txBufLength = zeroesBefore + (count*3) + zeroesAfter;
312
+ uint8_t txBuf[txBufLength];
313
+ for (int i=0; i<txBufLength; i++) { txBuf[i] = 0; }
314
+
315
+ VALUE currentByte_rb;
316
+ uint8_t currentByte;
317
+ uint8_t currentBit;
318
+ uint32_t temp;
319
+
320
+ for (int i=0; i<count; i++){
321
+ temp = 0;
322
+ currentByte_rb = rb_ary_entry(pixelArray, i);
323
+ Check_Type(currentByte_rb, T_FIXNUM);
324
+ currentByte = NUM2CHR(currentByte_rb);
325
+
326
+ for (int i=7; i>=0; i--) {
327
+ currentBit = (currentByte & (1 << i));
328
+ temp = temp << 3;
329
+ temp = (currentBit == 0) ? (temp | 0b100) : (temp | 0b110);
330
+ }
331
+
332
+ txBuf[zeroesBefore+(i*3)] = (temp >> 16) & 0xFF;
333
+ txBuf[zeroesBefore+(i*3)+1] = (temp >> 8) & 0xFF;
334
+ txBuf[zeroesBefore+(i*3)+2] = temp & 0xFF;
335
+ }
336
+
337
+ int result = lgSpiWrite(NUM2INT(handle), txBuf, txBufLength);
338
+ return INT2NUM(result);
339
+ }
340
+
305
341
  void Init_lgpio(void) {
306
342
  // Modules
307
343
  VALUE mLGPIO = rb_define_module("LGPIO");
@@ -360,4 +396,5 @@ void Init_lgpio(void) {
360
396
  rb_define_singleton_method(mLGPIO, "spi_read", spi_read, 2);
361
397
  rb_define_singleton_method(mLGPIO, "spi_write", spi_write, 2);
362
398
  rb_define_singleton_method(mLGPIO, "spi_xfer", spi_xfer, 2);
399
+ rb_define_singleton_method(mLGPIO, "spi_ws2812_write", spi_ws2812_write, 2);
363
400
  }
@@ -0,0 +1,70 @@
1
+ module LGPIO
2
+ class HardwarePWM
3
+ NS_PER_S = 1_000_000_000
4
+ NS_PER_US = 1_000
5
+ SYS_FS_PWM_PATH = "/sys/class/pwm/"
6
+
7
+ attr_reader :period, :duty, :enabled
8
+
9
+ def initialize(chip, channel, frequency: nil, period: nil)
10
+ @chip = chip
11
+ @channel = channel
12
+
13
+ # Accept either frequency (in Hz) or period in nanoseconds.
14
+ if (frequency && period) || (!frequency && !period)
15
+ raise "either period: or frequency: is required, but not both"
16
+ end
17
+
18
+ period ? self.period = period : self.frequency = frequency
19
+ enable
20
+ end
21
+
22
+ def path
23
+ @path ||= "#{SYS_FS_PWM_PATH}pwmchip#{@chip}/pwm#{@channel}/"
24
+ end
25
+
26
+ def frequency=(freq)
27
+ self.period = (NS_PER_S / freq.to_f).round
28
+ end
29
+
30
+ def period=(p)
31
+ old_period = File.read("#{path}period").strip.to_i
32
+ unless (old_period == 0)
33
+ File.open("#{path}duty_cycle", 'w') { |f| f.write("0") }
34
+ end
35
+ File.open("#{path}period", 'w') { |f| f.write(p) }
36
+ @period = p
37
+ end
38
+
39
+ def duty_percent
40
+ (duty / period.to_f) * 100.0
41
+ end
42
+
43
+ def duty_percent=(d)
44
+ raise "duty_cycle: #{d} % cannot be more than 100%" if d > 100
45
+ d_ns = ((d / 100.0) * @period.to_i).round
46
+ self.duty = d_ns
47
+ end
48
+
49
+ def duty_us=(d_us)
50
+ d_ns = (d_us * NS_PER_US).round
51
+ self.duty = d_ns
52
+ end
53
+
54
+ def duty=(d_ns)
55
+ raise "duty cycle: #{d_ns} ns cannot be longer than period: #{period} ns" if d_ns > period
56
+ File.open("#{path}duty_cycle", 'w') { |f| f.write(d_ns) }
57
+ @duty = d_ns
58
+ end
59
+
60
+ def disable
61
+ File.open("#{path}enable", 'w') { |f| f.write("0") }
62
+ @enabled = false
63
+ end
64
+
65
+ def enable
66
+ File.open("#{path}enable", 'w') { |f| f.write("1") }
67
+ @enabled = true
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,28 @@
1
+ module LGPIO
2
+ class PositionalServo < HardwarePWM
3
+ FREQUENCY = 50
4
+
5
+ attr_reader :angle
6
+
7
+ def initialize(chip, channel, min_us, max_us, min_angle, max_angle)
8
+ super(chip, channel, frequency: FREQUENCY)
9
+
10
+ raise "min_us: #{min_us} cannot be lower than max_us: #{max_us}" if max_us < min_us
11
+ @min_us = min_us
12
+ @max_us = max_us
13
+ @us_range = @max_us - @min_us
14
+
15
+ @min_angle = min_angle
16
+ @max_angle = max_angle
17
+ end
18
+
19
+ def angle=(a)
20
+ ratio = (a - @min_angle).to_f / (@max_angle - @min_angle)
21
+ raise "angle: #{a} outside servo range" if (ratio < 0) || (ratio > 1)
22
+
23
+ d_us = (@us_range * ratio) + @min_us
24
+ self.duty_us = d_us
25
+ @angle = a
26
+ end
27
+ end
28
+ end
data/lib/lgpio/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module LGPIO
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.5"
3
3
  end
data/lib/lgpio.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  require_relative 'lgpio/lgpio'
2
+ require_relative 'lgpio/version'
3
+ require_relative 'lgpio/hardware_pwm'
4
+ require_relative 'lgpio/positional_servo'
2
5
 
3
6
  module LGPIO
4
7
  LOW = 0
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.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - vickash
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-03 00:00:00.000000000 Z
11
+ date: 2024-08-17 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
@@ -34,14 +34,18 @@ files:
34
34
  - examples/reports.rb
35
35
  - examples/rotary_encoder.rb
36
36
  - examples/rotary_encoder_led.rb
37
+ - examples/servo.rb
37
38
  - examples/spi_loopback.rb
38
39
  - examples/spi_read.rb
39
40
  - examples/spi_ws2812.rb
41
+ - examples/spi_ws2812_bounce.rb
40
42
  - examples/wave.rb
41
43
  - ext/lgpio/extconf.rb
42
44
  - ext/lgpio/lgpio.c
43
45
  - lgpio.gemspec
44
46
  - lib/lgpio.rb
47
+ - lib/lgpio/hardware_pwm.rb
48
+ - lib/lgpio/positional_servo.rb
45
49
  - lib/lgpio/version.rb
46
50
  homepage: https://github.com/denko-rb/lgpio
47
51
  licenses: