lgpio 0.1.5 → 0.1.7

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +42 -28
  3. data/examples/dht.rb +47 -0
  4. data/examples/{bench_in.rb → gpio_bench_in.rb} +1 -1
  5. data/examples/{bench_out.rb → gpio_bench_out.rb} +1 -1
  6. data/examples/{blink.rb → gpio_blink.rb} +1 -1
  7. data/examples/{group_in.rb → gpio_group_in.rb} +2 -2
  8. data/examples/{group_out.rb → gpio_group_out.rb} +1 -1
  9. data/examples/{momentary.rb → gpio_momentary.rb} +2 -2
  10. data/examples/{reports.rb → gpio_reports.rb} +1 -1
  11. data/examples/{rotary_encoder.rb → gpio_rotary_encoder.rb} +3 -3
  12. data/examples/{rotary_encoder_led.rb → gpio_rotary_encoder_led.rb} +4 -4
  13. data/examples/{wave.rb → gpio_wave.rb} +1 -1
  14. data/examples/hcsr04.rb +32 -0
  15. data/examples/i2c_bb_aht10.rb +40 -0
  16. data/examples/i2c_bb_search.rb +19 -0
  17. data/examples/i2c_bb_ssd1306_bench.rb +35 -0
  18. data/examples/{i2c_aht10.rb → i2c_hw_aht10.rb} +2 -1
  19. data/examples/{i2c_aht10_zip.rb → i2c_hw_aht10_zip.rb} +2 -1
  20. data/examples/{i2c_ssd1306_bench.rb → i2c_hw_ssd1306_bench.rb} +6 -5
  21. data/examples/infrared.rb +22 -0
  22. data/examples/one_wire_ds18b20.rb +30 -0
  23. data/examples/one_wire_search.rb +15 -0
  24. data/examples/pwm_hw_bench.rb +20 -0
  25. data/examples/{pwm.rb → pwm_sw.rb} +1 -1
  26. data/examples/spi_bb_loopback.rb +25 -0
  27. data/examples/spi_bb_sim_bench.rb +48 -0
  28. data/examples/spi_bb_ssd1306_bench.rb +51 -0
  29. data/ext/lgpio/lgpio.c +289 -11
  30. data/lib/lgpio/hardware_pwm.rb +18 -5
  31. data/lib/lgpio/i2c_bitbang.rb +117 -0
  32. data/lib/lgpio/infrared.rb +8 -0
  33. data/lib/lgpio/one_wire.rb +171 -0
  34. data/lib/lgpio/spi_bitbang.rb +109 -0
  35. data/lib/lgpio/version.rb +1 -1
  36. data/lib/lgpio.rb +6 -0
  37. metadata +36 -21
  38. data/examples/spi_read.rb +0 -14
  39. /data/examples/{servo.rb → pwm_hw_servo.rb} +0 -0
  40. /data/examples/{spi_loopback.rb → spi_hw_loopback.rb} +0 -0
  41. /data/examples/{spi_ws2812.rb → spi_hw_ws2812.rb} +0 -0
  42. /data/examples/{spi_ws2812_bounce.rb → spi_hw_ws2812_bounce.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 984afd62543a10d3fc0b6d2a9a581b05b4d9bf7c159d2747f990b2247b59aef1
4
- data.tar.gz: d2431adb24613fdd42b9f9f06a428c399fe48be2e215a13463237a798113630c
3
+ metadata.gz: 175af29acc6f0bb362dc017458e1ca9948f5d0c73ac0693f4a06b2e4e64f2842
4
+ data.tar.gz: c7eebe2b025a40e98c3e7fc7574eb1f7572e7c147a88a32d7b4a9bfdb0cb0882
5
5
  SHA512:
6
- metadata.gz: f32ceb3af8218d26f1f5713302a9891365af498fe76e2a750037f2ae10a437b747b40167e9fd9c5170fc28696d90c9244ce5d9a36269eba0d0c827a79be47fcd
7
- data.tar.gz: b167ef512729754b46dd3cd14750dd715c8541987014a254465c72619f1ead5d4ae8ff74bcbb6c9122b331c5f65a159e0f0c9890f9e0ad7a5b582dafe2763e54
6
+ metadata.gz: e3f5bfc169dfeb319fdfc06c4de1058682e9bee1f547a9eebecc1eb9ad2d50403bda912a67aef171e66b828d22740b04925b83c0e3494204c73c0b32dbbe7931
7
+ data.tar.gz: f3d17767bf96c7af4631945ac0a7812628698cb483634fc0c1a45513a8ac5107bc613799be32b95b2c4b251e51a4c95c253f107691767d5351b8b1e073f5d965
data/README.md CHANGED
@@ -1,39 +1,52 @@
1
1
  # lgpio
2
2
 
3
- Ruby bindings for the [lgpio (lg)](https://github.com/joan2937/lg) Linux library, running on single board computers (SBCs), like Raspberry Pi.
3
+ Ruby gem with bindings for the [lgpio (lg)](https://github.com/joan2937/lg) C library. This is for single-board-computers (SBCs) running Linux, such as Orange Pi, Raspberry Pi, etc. It provides low-level access to the GPIO, I2C, SPI, and PWM subsytems.
4
+
5
+ ## Standard LGPIO Features
4
6
 
5
- ## Mapped LGPIO Features
6
7
  - [x] GPIO Read/Write
7
8
  - [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
9
+ - [x] GPIO Alerts
10
+ - Alerts are generated by a separate thread, and can be read from a queue in Ruby.
11
+ - No real "callback" functionality.
12
+ - [x] Software PWM Output
13
+ - Timing not 100% precise, but works on any GPIO.
14
+ - Not recommended for servo motors, will jitter.
13
15
  - [x] Wave
14
- - Software timed on any pin, as with PWM.
15
- - [x] I2C
16
- - [x] SPI
16
+ - Software timed on any pin(s), as with PWM.
17
+ - [x] Hardware I2C
18
+ - [x] Hardware SPI
19
+
20
+ ## Extra Features (Built on LGPIO)
21
+
22
+ - [x] `LGPIO.gpio_read_ultrasonic` sends a pulse on a `trigger` pin, then measures a single pulse on a separate `echo` pin. Used for HC-SR04 or similar. See `examples/hcsr04.rb`.
23
+ - [x] `LGPIO.gpio_read_pulses_us` outputs a reset pulse on a pin, then polls for a sequence of input pulses. Used for DHT enviro sensors or similar. See `examples/dht.rb`.
24
+ - [x] Bit Bang I2C
25
+ - [x] Bit Bang SPI
26
+ - [x] Bit Bang 1-Wire
27
+ - [x] WS2812 addressable LEDs over hardware SPI
28
+ - Outputs on MOSI/PICO pin
29
+ - Must be able to set SPI clock frequency to 2.4 MHz
30
+
31
+ ## Hardware PWM Features
32
+
33
+ These use the sysfs PWM interface, not lgpio C, but are a good fit for this gem.
17
34
 
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
35
+ - [x] Hardware PWM Output
36
+ - [x] Servo (based on hardware PWM)
37
+ - [x] On-off Keying (OOK) Modulated Waves
38
+ - Carrier generated by hardware PWM. Software modulated with monotonic clock timing.
39
+ - Useful for sending infrared signals at 38kHz, for example.
23
40
 
24
- ## Sysfs PWM Interface Features
25
- - [x] Hardware PWM Out (specific pins per chip)
26
- - [x] Servo
41
+ **Note:** Once hardware PWM is used on a pin, it stays PWM until reboot. The associated GPIO won't work.
27
42
 
28
43
  ## Installation
29
44
  On Debian-based Linuxes (RaspberryPi OS, Armbian, DietPi etc.):
30
45
  ```bash
31
46
  sudo apt install swig python3-dev python3-setuptools
32
47
 
33
- # NOTE: There's a bug with GPIO numbers > 255.
34
- # If you need to use those, wget the second URL instead, until that fix is merged.
35
- wget https://github.com/joan2937/lg/archive/master.zip
36
- # wget https://github.com/vickash/lg/archive/refs/heads/master.zip
48
+ # Temporary fork of: wget https://github.com/joan2937/lg/archive/master.zip
49
+ wget https://github.com/vickash/lg/archive/refs/heads/master.zip
37
50
 
38
51
  unzip master.zip
39
52
  cd lg-master
@@ -44,17 +57,18 @@ gem install lgpio
44
57
  ```
45
58
 
46
59
  ## Enabling Hardware & Permissions
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.
60
+ Depending on your SBC and Linux distro/version, you may need to manually enable hardware I2C, SPI, and PWM. You should use the config tool that came with your distro for that, if possible.
48
61
 
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:
62
+ Even when these are enabled, you may not have permission to access them. To run without `sudo`, you need read+write permission to some or all of the following:
50
63
  ```
51
- /dev/gpiochip* (For GPIO, example: /dev/gpiochip0)
52
- /dev/i2c-* (For I2C, example: /dev/i2c-1)
53
- /dev/spidev* (For SPI, example: /dev/spidev-0.1)
64
+ /dev/gpiochip* (For GPIO, example: /dev/gpiochip0)
65
+ /dev/i2c-* (For I2C, example: /dev/i2c-1)
66
+ /dev/spidev* (For SPI, example: /dev/spidev0.1)
67
+ /sys/class/pwm/pwmchip* (For PWM, example: /sys/class/pwm/pwmchip0)
54
68
  ```
55
69
 
56
70
  ## Documentation
57
- - See examples folder for demos of every implemented interface.
71
+ - See examples folder for demos of everything implemented.
58
72
  - Development was done on an Orange Pi Zero 2W. Your GPIO numbers may be different, so change them.
59
73
  - For more info, see the [lgpio C API docs.](https://abyz.me.uk/lg/lgpio.html)
60
74
  - As much as possible, the Ruby methods closely follow the C API functions, except:
data/examples/dht.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'lgpio'
2
+
3
+ GPIO_CHIP = 0
4
+ DHT_PIN = 267
5
+ chip_handle = LGPIO.chip_open(GPIO_CHIP)
6
+ #
7
+ # Read a series of pulses input to a GPIO, with an (optional) reset output pulse at start.
8
+ # Arguments in order are:
9
+ # gpiochip handle
10
+ # GPIO number
11
+ # Starting (reset) output pulse time in microseconds (0 for no reset)
12
+ # Reset pulse level (0 or 1)
13
+ # Maximum number of pulses to read
14
+ # Timeout in milliseconds
15
+ #
16
+ data = LGPIO.gpio_read_pulses_us(chip_handle, DHT_PIN, 20_000, 0, 84, 100)
17
+
18
+ # Handle errors.
19
+ raise "error: DHT sensor not connected" unless data
20
+ raise "LGPIO error: #{data}" if data.class == Integer
21
+
22
+ # Discard unneeded pulses
23
+ data = data.last(81)
24
+ raise "error: incomplete DHT data" unless data.length == 81
25
+ data = data.first(80)
26
+
27
+ # Convert to bytes
28
+ bytes = []
29
+ data.each_slice(16) do |b|
30
+ byte = 0b00000000
31
+ b.each_slice(2) do |x,y|
32
+ bit = (y<x) ? 0 : 1
33
+ byte = (byte << 1) | bit
34
+ end
35
+ bytes << byte
36
+ end
37
+
38
+ # CRC
39
+ crc = bytes[0..3].reduce(0, :+) & 0xFF == bytes[4]
40
+ raise "error: DHT CRC check failed" unless crc
41
+
42
+ # Convert and display
43
+ temperature = ((bytes[2] << 8) | bytes[3]).to_f / 10
44
+ humidity = ((bytes[0] << 8) | bytes[1]).to_f / 10
45
+
46
+ puts "DHT Temperature: #{temperature} \xC2\xB0C"
47
+ puts "DHT Humidity: #{humidity} %"
@@ -1,7 +1,7 @@
1
1
  require 'lgpio'
2
2
 
3
3
  GPIO_CHIP = 0
4
- PIN = 258
4
+ PIN = 260
5
5
  COUNT = 1000000
6
6
 
7
7
  chip_handle = LGPIO.chip_open(GPIO_CHIP)
@@ -1,7 +1,7 @@
1
1
  require 'lgpio'
2
2
 
3
3
  GPIO_CHIP = 0
4
- PIN = 260
4
+ PIN = 272
5
5
  COUNT = 1000000
6
6
 
7
7
  chip_handle = LGPIO.chip_open(GPIO_CHIP)
@@ -1,7 +1,7 @@
1
1
  require 'lgpio'
2
2
 
3
3
  GPIO_CHIP = 0
4
- LED = 260
4
+ LED = 272
5
5
  INTERVAL = 0.25
6
6
 
7
7
  chip_handle = LGPIO.chip_open(GPIO_CHIP)
@@ -2,8 +2,8 @@
2
2
  require 'lgpio'
3
3
 
4
4
  GPIO_CHIP = 0
5
- BUTTONS = [258, 256]
6
- LEDS = [260, 267]
5
+ BUTTONS = [259, 267]
6
+ LEDS = [272, 258]
7
7
  INIT_STATE = [0, 0]
8
8
 
9
9
  chip_handle = LGPIO.chip_open(GPIO_CHIP)
@@ -1,7 +1,7 @@
1
1
  require 'lgpio'
2
2
 
3
3
  GPIO_CHIP = 0
4
- LEDS = [260, 267]
4
+ LEDS = [272, 258]
5
5
  INIT_STATE = [0, 0]
6
6
  INTERVAL = 250_000 # 250ms
7
7
  TIMES = 10
@@ -1,8 +1,8 @@
1
1
  require 'lgpio'
2
2
 
3
3
  GPIO_CHIP = 0
4
- BUTTON = 258
5
- LED = 260
4
+ BUTTON = 259
5
+ LED = 272
6
6
 
7
7
  chip_handle = LGPIO.chip_open(GPIO_CHIP)
8
8
  LGPIO.gpio_claim_input(chip_handle, LGPIO::SET_PULL_UP, BUTTON)
@@ -1,7 +1,7 @@
1
1
  require 'lgpio'
2
2
 
3
3
  GPIO_CHIP = 0
4
- PIN = 76
4
+ PIN = 259
5
5
 
6
6
  chip_handle = LGPIO.chip_open(GPIO_CHIP)
7
7
 
@@ -5,9 +5,9 @@
5
5
  require 'lgpio'
6
6
 
7
7
  GPIO_CHIP = 0
8
- PIN_A = 76
9
- PIN_B = 228
10
- PIN_SW = 226
8
+ PIN_A = 260
9
+ PIN_B = 76
10
+ PIN_SW = 259
11
11
 
12
12
  # Encoder state
13
13
  position = 0
@@ -5,10 +5,10 @@
5
5
  require 'lgpio'
6
6
 
7
7
  GPIO_CHIP = 0
8
- PIN_A = 76
9
- PIN_B = 228
8
+ PIN_A = 260
9
+ PIN_B = 76
10
10
 
11
- PIN_LED = 260
11
+ PIN_LED = 272
12
12
  PWM_FREQ = 500
13
13
  PWM_OFFSET = 0
14
14
  PWM_CYCLES = 0 # 0 = infinite
@@ -44,7 +44,7 @@ loop do
44
44
  elsif report[:gpio] == PIN_B
45
45
  delta = (report[:level] == state_a) ? -1 : 1
46
46
  state_b = report[:level]
47
-
47
+
48
48
  led_duty += delta
49
49
  led_duty = 0 if led_duty < 0
50
50
  led_duty = 100 if led_duty > 100
@@ -1,7 +1,7 @@
1
1
  require 'lgpio'
2
2
 
3
3
  GPIO_CHIP = 0
4
- LEDS = [260, 267]
4
+ LEDS = [272, 258]
5
5
  INIT_STATE = [0, 0]
6
6
  INTERVAL = 250_000 # 250ms
7
7
  TIMES = 10
@@ -0,0 +1,32 @@
1
+ #
2
+ # Example showing an HC-S04 ultrasonic distance sensor.
3
+ #
4
+ # NOTE: Some versions of this sensor require 5V power to function properly.
5
+ # If using one of these, use a 5V to 3.3V level shifter between your board and
6
+ # the sensor, at least on the echo pin.
7
+ #
8
+ require 'lgpio'
9
+
10
+ GPIO_CHIP = 0
11
+ ECHO_PIN = 228
12
+ TRIGGER_PIN = 270
13
+ SPEED_OF_SOUND = 343.0
14
+
15
+ chip_handle = LGPIO.chip_open(GPIO_CHIP)
16
+
17
+ loop do
18
+ # Arguments in order are:
19
+ # chip handle, trigger pin, echo pin, trigger time (us)
20
+ #
21
+ # HC-SR04 uses 10 microseconds for trigger. Some others use 20us.
22
+ #
23
+ microseconds = LGPIO.gpio_read_ultrasonic(chip_handle, TRIGGER_PIN, ECHO_PIN, 10)
24
+
25
+ if microseconds
26
+ mm = (microseconds / 2000.0) * SPEED_OF_SOUND
27
+ puts "Distance: #{mm.round} mm"
28
+ else
29
+ puts "Cound not read HC-SR04 sensor"
30
+ end
31
+ sleep 0.5
32
+ end
@@ -0,0 +1,40 @@
1
+ require 'lgpio'
2
+
3
+ POWER_ON_DELAY = 0.100
4
+ RESET_DELAY = 0.020
5
+ COMMAND_DELAY = 0.010
6
+ MEASURE_DELAY = 0.080
7
+ DATA_LENGTH = 6
8
+ SOFT_RESET = [0xBA]
9
+ INIT_AND_CALIBRATE = [0xE1, 0x08, 0x00]
10
+ START_MEASUREMENT = [0xAC, 0x33, 0x00]
11
+
12
+ GPIO_CHIP = 0
13
+ SCL_PIN = 228
14
+ SDA_PIN = 270
15
+ ADDRESS = 0x38
16
+
17
+ chip_handle = LGPIO.chip_open(GPIO_CHIP)
18
+ i2c_bb = LGPIO::I2CBitBang.new(chip_handle, SCL_PIN, SDA_PIN)
19
+
20
+ # Startup sequence
21
+ sleep(POWER_ON_DELAY)
22
+ i2c_bb.write(ADDRESS, SOFT_RESET)
23
+ sleep(RESET_DELAY)
24
+ i2c_bb.write(ADDRESS, INIT_AND_CALIBRATE)
25
+ sleep(COMMAND_DELAY)
26
+
27
+ # Read and close
28
+ i2c_bb.write(ADDRESS, START_MEASUREMENT)
29
+ sleep(MEASURE_DELAY)
30
+ bytes = i2c_bb.read(ADDRESS, DATA_LENGTH)
31
+
32
+ # Humidity uses the upper 4 bits of the shared byte as its lowest 4 bits.
33
+ h_raw = ((bytes[1] << 16) | (bytes[2] << 8) | (bytes[3])) >> 4
34
+ humidity = (h_raw.to_f / 2**20) * 100
35
+
36
+ # Temperature uses the lower 4 bits of the shared byte as its highest 4 bits.
37
+ t_raw = ((bytes[3] & 0x0F) << 16) | (bytes[4] << 8) | bytes[5]
38
+ temperature = (t_raw.to_f / 2**20) * 200 - 50
39
+
40
+ puts "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - Temperature: #{temperature.round(2)} \xC2\xB0C | Humidity: #{humidity.round(2)} %"
@@ -0,0 +1,19 @@
1
+ require 'lgpio'
2
+
3
+ GPIO_CHIP = 0
4
+ SCL_PIN = 228
5
+ SDA_PIN = 270
6
+
7
+ chip_handle = LGPIO.chip_open(GPIO_CHIP)
8
+ i2c_bb = LGPIO::I2CBitBang.new(chip_handle, SCL_PIN, SDA_PIN)
9
+ devices = i2c_bb.search
10
+
11
+ if devices.empty?
12
+ puts "No devices found on I2C bus"
13
+ else
14
+ puts "I2C device addresses found:"
15
+ devices.each do |address|
16
+ # Print as hexadecimal.
17
+ puts "0x#{address.to_s(16).upcase}"
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ require 'lgpio'
2
+
3
+ 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]
4
+ START_ARRAY = [0, 33, 0, 127, 34, 0, 7]
5
+ PATTERN_1 = [64] + Array.new(1024) { 0b00110011 }
6
+ PATTERN_2 = [64] + Array.new(1024) { 0b11001100 }
7
+
8
+ GPIO_CHIP = 0
9
+ SCL_PIN = 228
10
+ SDA_PIN = 270
11
+ ADDRESS = 0x3C
12
+
13
+ chip_handle = LGPIO.chip_open(GPIO_CHIP)
14
+ i2c_bb = LGPIO::I2CBitBang.new(chip_handle, SCL_PIN, SDA_PIN)
15
+
16
+ i2c_bb.write(ADDRESS, INIT_ARRAY)
17
+ FRAME_COUNT = 400
18
+
19
+ start = Time.now
20
+ (FRAME_COUNT / 2).times do
21
+ i2c_bb.write(ADDRESS, START_ARRAY)
22
+ i2c_bb.write(ADDRESS, PATTERN_1)
23
+ i2c_bb.write(ADDRESS, START_ARRAY)
24
+ i2c_bb.write(ADDRESS, PATTERN_2)
25
+ end
26
+ finish = Time.now
27
+
28
+ LGPIO.chip_close(chip_handle)
29
+
30
+ fps = FRAME_COUNT / (finish - start)
31
+ # Also calculate C calls per second, using roughly 23 calls per byte written.
32
+ cps = (START_ARRAY.length + ((PATTERN_1.length + PATTERN_2.length) / 2) + 2) * 23 * fps
33
+ cps = (cps / 1000.0).round
34
+
35
+ puts "SSD1306 benchmark result: #{fps.round(2)} fps | #{cps}k C calls/s"
@@ -1,6 +1,7 @@
1
1
  require 'lgpio'
2
2
 
3
3
  I2C_DEV = 3
4
+ ADDRESS = 0x38
4
5
  POWER_ON_DELAY = 0.100
5
6
  RESET_DELAY = 0.020
6
7
  COMMAND_DELAY = 0.010
@@ -10,7 +11,7 @@ SOFT_RESET = [0xBA]
10
11
  INIT_AND_CALIBRATE = [0xE1, 0x08, 0x00]
11
12
  START_MEASUREMENT = [0xAC, 0x33, 0x00]
12
13
 
13
- aht10_handle = LGPIO.i2c_open(I2C_DEV, 0x38, 0)
14
+ aht10_handle = LGPIO.i2c_open(I2C_DEV, ADDRESS, 0)
14
15
 
15
16
  # Startup sequence
16
17
  sleep(POWER_ON_DELAY)
@@ -1,6 +1,7 @@
1
1
  require 'lgpio'
2
2
 
3
3
  I2C_DEV = 3
4
+ ADDRESS = 0x38
4
5
  POWER_ON_DELAY = 0.100
5
6
  RESET_DELAY = 0.020
6
7
  COMMAND_DELAY = 0.010
@@ -10,7 +11,7 @@ INIT_AND_CALIBRATE = [5, 3, 0xE1, 0x08, 0x00, 0]
10
11
  START_MEASUREMENT = [5, 3, 0xAC, 0x33, 0x00, 0]
11
12
  READ_SIX = [4, 6, 0]
12
13
 
13
- aht10_handle = LGPIO.i2c_open(I2C_DEV, 0x38, 0)
14
+ aht10_handle = LGPIO.i2c_open(I2C_DEV, ADDRESS, 0)
14
15
 
15
16
  # Startup sequence
16
17
  sleep(POWER_ON_DELAY)
@@ -1,21 +1,22 @@
1
1
  require 'lgpio'
2
2
 
3
3
  I2C_DEV = 3
4
+ ADDRESS = 0x3C
4
5
  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
6
  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 }
7
+ PATTERN_1 = [64] + Array.new(1024) { 0b00110011 }
8
+ PATTERN_2 = [64] + Array.new(1024) { 0b11001100 }
8
9
 
9
- ssd1306_handle = LGPIO.i2c_open(I2C_DEV, 0x3C, 0)
10
+ ssd1306_handle = LGPIO.i2c_open(I2C_DEV, ADDRESS, 0)
10
11
  LGPIO.i2c_write_device(ssd1306_handle, INIT_ARRAY)
11
12
  FRAME_COUNT = 100
12
13
 
13
14
  start = Time.now
14
15
  (FRAME_COUNT / 2).times do
15
16
  LGPIO.i2c_write_device(ssd1306_handle, START_ARRAY)
16
- LGPIO.i2c_write_device(ssd1306_handle, FILL_ARRAY)
17
+ LGPIO.i2c_write_device(ssd1306_handle, PATTERN_1)
17
18
  LGPIO.i2c_write_device(ssd1306_handle, START_ARRAY)
18
- LGPIO.i2c_write_device(ssd1306_handle, BLANK_ARRAY)
19
+ LGPIO.i2c_write_device(ssd1306_handle, PATTERN_2)
19
20
  end
20
21
  finish = Time.now
21
22
 
@@ -0,0 +1,22 @@
1
+ require 'lgpio'
2
+
3
+ FREQUENCY = 38_000
4
+
5
+ # NEC Raw-Data=0xF708FB04. LSBFIRST, so the binary for each hex digit below is backward.
6
+ CODE = [ 9000, 500, # Start bit
7
+ 560, 560, 560, 560, 560, 1690, 560, 560, # 0010 0x4 command
8
+ 560, 560, 560, 560, 560, 560, 560, 560, # 0000 0x0 command
9
+ 560, 1690, 560, 1690, 560,560, 560, 1690, # 1101 0xB command inverted
10
+ 560, 1690, 560, 1690, 560, 1690, 560, 1690, # 1111 0xF command inverted
11
+ 560, 560, 560, 560, 560, 560, 560, 1690, # 0001 0x8 address
12
+ 560, 560, 560, 560, 560, 560, 560, 560, # 0000 0x0 address
13
+ 560, 1690, 560, 1690, 560, 1690, 560, 560, # 1110 0x7 address inverted
14
+ 560, 1690, 560, 1690, 560, 1690, 560, 1690, # 1111 0xF address inverted
15
+ 560] # Stop bit
16
+
17
+ infrared = LGPIO::Infrared.new(0, 2, frequency: FREQUENCY)
18
+
19
+ loop do
20
+ infrared.transmit(CODE)
21
+ sleep 1
22
+ end
@@ -0,0 +1,30 @@
1
+ require 'lgpio'
2
+
3
+ GPIO_CHIP = 0
4
+ PIN = 256
5
+ PARASITE = false
6
+
7
+ chip_handle = LGPIO.chip_open(GPIO_CHIP)
8
+ one_wire = LGPIO::OneWire.new(chip_handle, PIN)
9
+
10
+ one_wire.reset
11
+ # Skip ROM
12
+ one_wire.write([LGPIO::OneWire::SKIP_ROM], parasite: PARASITE)
13
+ # Start conversion
14
+ one_wire.write([LGPIO::OneWire::CONVERT_T], parasite: PARASITE)
15
+ # Wait for conversion
16
+ sleep(1)
17
+ # Reset
18
+ one_wire.reset
19
+ # Skip ROM
20
+ one_wire.write([LGPIO::OneWire::SKIP_ROM], parasite: PARASITE)
21
+ # Read 9 bytes from scratchpad
22
+ one_wire.write([LGPIO::OneWire::READ_SCRATCH], parasite: PARASITE)
23
+ bytes = one_wire.read(9)
24
+
25
+ # Temperature is the first 16 bits (2 bytes of 9 read).
26
+ # It's a signed, 2's complement, little-endian decimal. LSB = 2 ^ -4.
27
+ #
28
+ temperature = bytes[0..1].pack('C*').unpack('s<')[0] * (2.0 ** -4)
29
+
30
+ puts "DS18B20 reading: #{temperature} \xC2\xB0C"
@@ -0,0 +1,15 @@
1
+ require 'lgpio'
2
+
3
+ GPIO_CHIP = 0
4
+ PIN = 256
5
+
6
+ chip_handle = LGPIO.chip_open(GPIO_CHIP)
7
+ one_wire = LGPIO::OneWire.new(chip_handle, PIN)
8
+ one_wire.search
9
+
10
+ puts; puts "Found these 1-wire addresss (HEX) on the bus:"; puts
11
+
12
+ one_wire.found_addresses.each do |address|
13
+ puts address.to_s(16)
14
+ end
15
+ puts
@@ -0,0 +1,20 @@
1
+ require 'lgpio'
2
+ #
3
+ # Writing directly to a hardware PWM channel.
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
+ pwm_out = LGPIO::HardwarePWM.new(0, 1, period: 20_000_000)
11
+
12
+ RUNS = 250_000
13
+ start = Time.now
14
+ RUNS.times do
15
+ pwm_out.duty_us = 1000
16
+ end
17
+ finish = Time.now
18
+
19
+ wps = RUNS / (finish - start)
20
+ puts "Hardware PWM writes per second: #{wps.round(2)}"
@@ -1,7 +1,7 @@
1
1
  require 'lgpio'
2
2
 
3
3
  GPIO_CHIP = 0
4
- LED = 260
4
+ LED = 272
5
5
  PWM_FREQ = 500
6
6
  PWM_OFFSET = 0
7
7
  PWM_CYCLES = 0 # 0 = infinite
@@ -0,0 +1,25 @@
1
+ require 'lgpio'
2
+
3
+ GPIO_CHIP = 0
4
+ CLOCK_PIN = 22
5
+ INPUT_PIN = 17
6
+ OUTPUT_PIN = 27
7
+
8
+ MODES = [0, 1, 2, 3]
9
+ ORDERS = [:msbfirst, :lsbfirst]
10
+ TX_BYTES = [0, 1, 2, 3, 4, 5, 6, 7]
11
+
12
+ chip_handle = LGPIO.chip_open(GPIO_CHIP)
13
+ spi_bb = LGPIO::SPIBitBang.new(handle: chip_handle, clock: CLOCK_PIN, input: INPUT_PIN, output: OUTPUT_PIN)
14
+
15
+ puts "TX bytes => #{TX_BYTES.inspect}"
16
+
17
+ # Connect (loop back) INPUT_PIN to OUTPUT_PIN to see received bytes.
18
+ ORDERS.each do |order|
19
+ MODES.each do |mode|
20
+ rx_bytes = spi_bb.transfer(write: TX_BYTES, read: TX_BYTES.length, order: order, mode: mode)
21
+ puts "RX (order: #{order}, mode: #{mode}) => #{rx_bytes.inspect}"
22
+ end
23
+ end
24
+
25
+ LGPIO.chip_close(chip_handle)
@@ -0,0 +1,48 @@
1
+ require 'lgpio'
2
+
3
+ GPIO_CHIP = 0
4
+ CLOCK_PIN = 17
5
+ INPUT_PIN = 22
6
+ OUTPUT_PIN = 27
7
+
8
+ # Emulate data sent over I2C to a SSD1306 OLED, prepending its write address.
9
+ START_ARRAY = [0x3C << 1] + [0, 33, 0, 127, 34, 0, 7]
10
+ PATTERN_1 = [0x3C << 1] + [64] + Array.new(1179) { 0b00110011 }
11
+ PATTERN_2 = [0x3C << 1] + [64] + Array.new(1179) { 0b11001100 }
12
+ FRAME_COUNT = 400
13
+
14
+ chip_handle = LGPIO.chip_open(GPIO_CHIP)
15
+ spi_bb = LGPIO::SPIBitBang.new(handle: chip_handle, clock: CLOCK_PIN, input: INPUT_PIN, output: OUTPUT_PIN)
16
+
17
+ start = Time.now
18
+ (FRAME_COUNT / 2).times do
19
+ spi_bb.transfer(write: START_ARRAY)
20
+ spi_bb.transfer(write: PATTERN_1)
21
+ spi_bb.transfer(write: START_ARRAY)
22
+ spi_bb.transfer(write: PATTERN_2)
23
+ end
24
+ finish = Time.now
25
+
26
+ LGPIO.chip_close(chip_handle)
27
+
28
+ fps = FRAME_COUNT / (finish - start)
29
+ #
30
+ # We want to calculate how many C calls were made per second, to compare with I2C:
31
+ # - SPI bytes are 8 bits on the wire, while I2C are 9 (8 data bits + ACK bit).
32
+ # - The I2C ACK should do 4 C API calls, while data bits on both buses need 3 calls.
33
+ # - This isn't always true. Because of the "lazy" optimization when setting the data pin
34
+ # in both protocols, and the line pattern being used, where the first bit of
35
+ # each byte is the inverse of the last bit of the previous byte, two things change:
36
+ # - Each I2C ACK is effecitvely 3 C calls. The data bit either before or after is always 1,
37
+ # eliminating a write to SDA.
38
+ # - For both buses, the "2 on, 2 off" patern means 8 data bits are only 20 C calls total.
39
+ # - So SPI is 20 for all pixel bytes, and I2C is 23 for all pixel bytes.
40
+ # - Ignores bit changes in the address and start bytes.
41
+ # - Ignore 10 calls per frame for I2C start/stop, and 2 calls per frame for SPI clock idle.
42
+ # - These are only 1% of total calls, so should be negligible.
43
+ # - Changing the line pattern WILL break this calculation.
44
+ #
45
+ cps = (START_ARRAY.length + ((PATTERN_1.length + PATTERN_2.length) / 2)) * 20 * fps
46
+ cps = (cps / 1000.0).round
47
+
48
+ puts "SSD1306 sim result: #{fps.round(2)} fps | #{cps}k C calls/s"