lgpio 0.1.4 → 0.1.6
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 +4 -4
- data/README.md +43 -24
- data/examples/bench_in.rb +1 -1
- data/examples/bench_out.rb +1 -1
- data/examples/blink.rb +1 -1
- data/examples/dht.rb +47 -0
- data/examples/group_in.rb +2 -2
- data/examples/group_out.rb +1 -1
- data/examples/hcsr04.rb +32 -0
- data/examples/i2c_aht10.rb +3 -2
- data/examples/i2c_aht10_zip.rb +3 -2
- data/examples/i2c_bitbang-rb_aht10.rb +40 -0
- data/examples/i2c_bitbang-rb_ssd1306_bench.rb +35 -0
- data/examples/i2c_bitbang_aht10.rb +40 -0
- data/examples/i2c_bitbang_search.rb +20 -0
- data/examples/i2c_bitbang_ssd1306_bench.rb +35 -0
- data/examples/i2c_ssd1306_bench.rb +7 -6
- data/examples/infrared.rb +22 -0
- data/examples/momentary.rb +2 -2
- data/examples/one_wire_ds18b20.rb +30 -0
- data/examples/one_wire_search.rb +15 -0
- data/examples/pwm.rb +1 -1
- data/examples/reports.rb +1 -1
- data/examples/rotary_encoder.rb +3 -3
- data/examples/rotary_encoder_led.rb +4 -4
- data/examples/servo.rb +36 -0
- data/examples/spi_bitbang_loopback.rb +25 -0
- data/examples/spi_bitbang_ssd1306_bench.rb +51 -0
- data/examples/spi_bitbang_ssd1306_sim_bench.rb +48 -0
- data/examples/wave.rb +1 -1
- data/ext/lgpio/lgpio.c +454 -9
- data/lib/lgpio/hardware_pwm.rb +83 -0
- data/lib/lgpio/i2c_bitbang.rb +117 -0
- data/lib/lgpio/infrared.rb +8 -0
- data/lib/lgpio/one_wire.rb +171 -0
- data/lib/lgpio/positional_servo.rb +28 -0
- data/lib/lgpio/spi_bitbang.rb +109 -0
- data/lib/lgpio/version.rb +1 -1
- data/lib/lgpio.rb +9 -0
- metadata +22 -3
- data/examples/spi_read.rb +0 -14
@@ -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,51 @@
|
|
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 = Array.new(1024) { 0b00110011 }
|
6
|
+
PATTERN_2 = Array.new(1024) { 0b11001100 }
|
7
|
+
|
8
|
+
GPIO_CHIP = 0
|
9
|
+
CLOCK_PIN = 17
|
10
|
+
OUTPUT_PIN = 27
|
11
|
+
SELECT_PIN = 22
|
12
|
+
RESET_PIN = 5
|
13
|
+
DC_PIN = 6
|
14
|
+
|
15
|
+
# Initialize
|
16
|
+
chip_handle = LGPIO.chip_open(GPIO_CHIP)
|
17
|
+
spi_bb = LGPIO::SPIBitBang.new(handle: chip_handle, clock: CLOCK_PIN, output: OUTPUT_PIN)
|
18
|
+
LGPIO.gpio_claim_output(chip_handle, LGPIO::SET_PULL_NONE, SELECT_PIN, LGPIO::HIGH)
|
19
|
+
LGPIO.gpio_claim_output(chip_handle, LGPIO::SET_PULL_NONE, RESET_PIN, LGPIO::LOW)
|
20
|
+
LGPIO.gpio_claim_output(chip_handle, LGPIO::SET_PULL_NONE, DC_PIN, LGPIO::LOW)
|
21
|
+
|
22
|
+
# OLED STARTUP
|
23
|
+
LGPIO.gpio_write(chip_handle, RESET_PIN, 1)
|
24
|
+
LGPIO.gpio_write(chip_handle, DC_PIN, 0)
|
25
|
+
spi_bb.transfer(write: INIT_ARRAY, select: SELECT_PIN)
|
26
|
+
|
27
|
+
FRAME_COUNT = 400
|
28
|
+
|
29
|
+
start = Time.now
|
30
|
+
(FRAME_COUNT / 2).times do
|
31
|
+
LGPIO.gpio_write(chip_handle, DC_PIN, 0)
|
32
|
+
spi_bb.transfer(write: START_ARRAY, select: SELECT_PIN)
|
33
|
+
LGPIO.gpio_write(chip_handle, DC_PIN, 1)
|
34
|
+
spi_bb.transfer(write: PATTERN_1, select: SELECT_PIN)
|
35
|
+
LGPIO.gpio_write(chip_handle, DC_PIN, 0)
|
36
|
+
spi_bb.transfer(write: START_ARRAY, select: SELECT_PIN)
|
37
|
+
LGPIO.gpio_write(chip_handle, DC_PIN, 1)
|
38
|
+
spi_bb.transfer(write: PATTERN_2, select: SELECT_PIN)
|
39
|
+
end
|
40
|
+
finish = Time.now
|
41
|
+
|
42
|
+
LGPIO.chip_close(chip_handle)
|
43
|
+
|
44
|
+
fps = FRAME_COUNT / (finish - start)
|
45
|
+
# Also calculate C calls per second, using roughly 20 calls per byte written.
|
46
|
+
data_calls = START_ARRAY.length + ((PATTERN_1.length + PATTERN_2.length) / 2) * 20
|
47
|
+
# Add DC, SELECT and clock idle calls.
|
48
|
+
total_calls = data_calls + 8
|
49
|
+
cps = ((total_calls * fps) / 1000.0).round
|
50
|
+
|
51
|
+
puts "SSD1306 benchmark result: #{fps.round(2)} fps | #{cps}k C calls/s"
|
@@ -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"
|
data/examples/wave.rb
CHANGED
data/ext/lgpio/lgpio.c
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#include <lgpio.h>
|
2
2
|
#include <ruby.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <time.h>
|
3
5
|
|
4
6
|
// Set up a queue for up to 2**16 GPIO reports.
|
5
7
|
static pthread_mutex_t queueLock;
|
@@ -8,6 +10,32 @@ static lgGpioReport_t reportQueue[QUEUE_LENGTH];
|
|
8
10
|
static uint16_t qWritePos = 1;
|
9
11
|
static uint16_t qReadPos = 0;
|
10
12
|
|
13
|
+
static uint64_t nanoDiff(const struct timespec *event2, const struct timespec *event1) {
|
14
|
+
uint64_t event2_ns = (uint64_t)event2->tv_sec * 1000000000LL + event2->tv_nsec;
|
15
|
+
uint64_t event1_ns = (uint64_t)event1->tv_sec * 1000000000LL + event1->tv_nsec;
|
16
|
+
return event2_ns - event1_ns;
|
17
|
+
}
|
18
|
+
|
19
|
+
static uint64_t nanosSince(const struct timespec *event) {
|
20
|
+
struct timespec now;
|
21
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
22
|
+
return nanoDiff(&now, event);
|
23
|
+
}
|
24
|
+
|
25
|
+
static void nanoDelay(uint64_t nanos) {
|
26
|
+
struct timespec refTime;
|
27
|
+
struct timespec now;
|
28
|
+
clock_gettime(CLOCK_MONOTONIC, &refTime);
|
29
|
+
now = refTime;
|
30
|
+
while(nanoDiff(&now, &refTime) < nanos) {
|
31
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
static void microDelay(uint64_t micros) {
|
36
|
+
nanoDelay(micros * 1000);
|
37
|
+
}
|
38
|
+
|
11
39
|
static VALUE chip_open(VALUE self, VALUE gpio_dev) {
|
12
40
|
int result = lgGpiochipOpen(NUM2INT(gpio_dev));
|
13
41
|
return INT2NUM(result);
|
@@ -18,6 +46,11 @@ static VALUE chip_close(VALUE self, VALUE handle) {
|
|
18
46
|
return INT2NUM(result);
|
19
47
|
}
|
20
48
|
|
49
|
+
static VALUE gpio_get_mode(VALUE self, VALUE handle, VALUE gpio) {
|
50
|
+
int result = lgGpioGetMode(NUM2INT(handle), NUM2INT(gpio));
|
51
|
+
return INT2NUM(result);
|
52
|
+
}
|
53
|
+
|
21
54
|
static VALUE gpio_claim_output(VALUE self, VALUE handle, VALUE flags, VALUE gpio, VALUE level) {
|
22
55
|
int result = lgGpioClaimOutput(NUM2INT(handle), NUM2INT(flags), NUM2INT(gpio), NUM2INT(level));
|
23
56
|
return INT2NUM(result);
|
@@ -93,13 +126,13 @@ static VALUE gpio_claim_alert(VALUE self, VALUE handle, VALUE flags, VALUE eFlag
|
|
93
126
|
return INT2NUM(result);
|
94
127
|
}
|
95
128
|
|
96
|
-
void queue_gpio_reports(int count, lgGpioAlert_p events, void *data){
|
129
|
+
static void queue_gpio_reports(int count, lgGpioAlert_p events, void *data){
|
97
130
|
pthread_mutex_lock(&queueLock);
|
98
131
|
for(int i=0; i<count; i++) {
|
99
132
|
memcpy(&reportQueue[qWritePos], &events[i].report, sizeof(lgGpioReport_t));
|
100
|
-
qWritePos
|
133
|
+
qWritePos++;
|
101
134
|
// qReadPos is the LAST report read. If passing by 1, increment it too. Lose oldest data first.
|
102
|
-
if (qWritePos - qReadPos == 1) qReadPos
|
135
|
+
if (qWritePos - qReadPos == 1) qReadPos++;
|
103
136
|
}
|
104
137
|
pthread_mutex_unlock(&queueLock);
|
105
138
|
}
|
@@ -126,11 +159,104 @@ static VALUE gpio_get_report(VALUE self){
|
|
126
159
|
}
|
127
160
|
pthread_mutex_unlock(&queueLock);
|
128
161
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
162
|
+
return (popped) ? hash : Qnil;
|
163
|
+
}
|
164
|
+
|
165
|
+
static VALUE gpio_read_ultrasonic(VALUE self, VALUE rbHandle, VALUE rbTrigger, VALUE rbEcho, VALUE rbTriggerTime) {
|
166
|
+
int handle = NUM2UINT(rbHandle);
|
167
|
+
int trigger = NUM2UINT(rbTrigger);
|
168
|
+
int echo = NUM2UINT(rbEcho);
|
169
|
+
uint32_t triggerTime = NUM2UINT(rbTriggerTime);
|
170
|
+
struct timespec start;
|
171
|
+
struct timespec now;
|
172
|
+
bool echoSeen = false;
|
173
|
+
|
174
|
+
// Pull down avoids false readings if disconnected.
|
175
|
+
lgGpioClaimInput(handle, LG_SET_PULL_DOWN, echo);
|
176
|
+
|
177
|
+
// Initial pulse on the triger pin.
|
178
|
+
lgGpioClaimOutput(handle, LG_SET_PULL_NONE, trigger, 0);
|
179
|
+
microDelay(5);
|
180
|
+
lgGpioWrite(handle, trigger, 1);
|
181
|
+
microDelay(triggerTime);
|
182
|
+
lgGpioWrite(handle, trigger, 0);
|
183
|
+
|
184
|
+
clock_gettime(CLOCK_MONOTONIC, &start);
|
185
|
+
now = start;
|
186
|
+
|
187
|
+
// Wait for echo to go high, up to 25,000 us after trigger.
|
188
|
+
while(nanoDiff(&now, &start) < 25000000){
|
189
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
190
|
+
if (lgGpioRead(handle, echo) == 1) {
|
191
|
+
echoSeen = true;
|
192
|
+
start = now;
|
193
|
+
break;
|
194
|
+
}
|
195
|
+
}
|
196
|
+
if (!echoSeen) return Qnil;
|
197
|
+
|
198
|
+
// Wait for echo to go low again, up to 25,000 us after echo start.
|
199
|
+
while(nanoDiff(&now, &start) < 25000000){
|
200
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
201
|
+
if (lgGpioRead(handle, echo) == 0) break;
|
133
202
|
}
|
203
|
+
|
204
|
+
// High pulse time in microseconds.
|
205
|
+
return INT2NUM(round(nanoDiff(&now, &start) / 1000.0));
|
206
|
+
}
|
207
|
+
|
208
|
+
static VALUE gpio_read_pulses_us(VALUE self, VALUE rbHandle, VALUE rbGPIO, VALUE rbReset_us, VALUE rbResetLevel, VALUE rbLimit, VALUE rbTimeout_ms) {
|
209
|
+
// C values
|
210
|
+
int handle = NUM2INT(rbHandle);
|
211
|
+
int gpio = NUM2INT(rbGPIO);
|
212
|
+
uint32_t reset_us = NUM2UINT(rbReset_us);
|
213
|
+
uint8_t resetLevel = NUM2UINT(rbResetLevel);
|
214
|
+
uint32_t limit = NUM2UINT(rbLimit);
|
215
|
+
uint64_t timeout_ns = NUM2UINT(rbTimeout_ms) * 1000000;
|
216
|
+
|
217
|
+
// State setup
|
218
|
+
uint64_t pulses_ns[limit];
|
219
|
+
uint32_t pulseIndex = 0;
|
220
|
+
int gpioState;
|
221
|
+
struct timespec start;
|
222
|
+
struct timespec lastPulse;
|
223
|
+
struct timespec now;
|
224
|
+
|
225
|
+
// Perform reset
|
226
|
+
if (reset_us > 0) {
|
227
|
+
int result = lgGpioClaimOutput(handle, LG_SET_PULL_NONE, gpio, resetLevel);
|
228
|
+
if (result < 0) return NUM2INT(result);
|
229
|
+
microDelay(reset_us);
|
230
|
+
}
|
231
|
+
|
232
|
+
// Initialize timing
|
233
|
+
clock_gettime(CLOCK_MONOTONIC, &start);
|
234
|
+
lastPulse = start;
|
235
|
+
now = start;
|
236
|
+
|
237
|
+
// Switch to input and read initial state
|
238
|
+
lgGpioClaimInput(handle, LG_SET_PULL_NONE, gpio);
|
239
|
+
gpioState = lgGpioRead(handle, gpio);
|
240
|
+
|
241
|
+
// Read pulses in nanoseconds
|
242
|
+
while ((nanoDiff(&now, &start) < timeout_ns) && (pulseIndex < limit)) {
|
243
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
244
|
+
if (lgGpioRead(handle, gpio) != gpioState) {
|
245
|
+
pulses_ns[pulseIndex] = nanoDiff(&now, &lastPulse);
|
246
|
+
lastPulse = now;
|
247
|
+
gpioState = gpioState ^ 0b1;
|
248
|
+
pulseIndex++;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
// Return Ruby array of pulse as microseconds
|
253
|
+
if (pulseIndex == 0) return Qnil;
|
254
|
+
VALUE retArray = rb_ary_new2(pulseIndex);
|
255
|
+
for(int i=0; i<pulseIndex; i++){
|
256
|
+
uint32_t pulse_us = round(pulses_ns[i] / 1000.0);
|
257
|
+
rb_ary_store(retArray, i, UINT2NUM(pulse_us));
|
258
|
+
}
|
259
|
+
return retArray;
|
134
260
|
}
|
135
261
|
|
136
262
|
static VALUE tx_busy(VALUE self, VALUE handle, VALUE gpio, VALUE kind) {
|
@@ -176,6 +302,47 @@ static VALUE tx_wave(VALUE self, VALUE handle, VALUE lead_gpio, VALUE pulses) {
|
|
176
302
|
return INT2NUM(result);
|
177
303
|
}
|
178
304
|
|
305
|
+
static VALUE tx_wave_ook(VALUE self, VALUE dutyPath, VALUE dutyString, VALUE pulses) {
|
306
|
+
// NOTE: This uses hardware PWM, NOT the lgpio software PWM/wave interface.
|
307
|
+
// The Ruby class LGPIO::HardwarePWM should have already set the PWM carrier frequency.
|
308
|
+
//
|
309
|
+
// Convert pulses from microseconds to nanoseconds.
|
310
|
+
uint32_t pulseCount = rb_array_len(pulses);
|
311
|
+
uint64_t nanoPulses[pulseCount];
|
312
|
+
for (int i=0; i<pulseCount; i++) {
|
313
|
+
nanoPulses[i] = NUM2UINT(rb_ary_entry(pulses, i)) * 1000;
|
314
|
+
}
|
315
|
+
|
316
|
+
// Prepare to write duty cycle.
|
317
|
+
const char *filePath = StringValueCStr(dutyPath);
|
318
|
+
FILE *dutyFile = fopen(filePath, "w");
|
319
|
+
if (dutyFile == NULL) {
|
320
|
+
VALUE errorMessage = rb_sprintf("Could not open PWM duty_cycle file: %s", filePath);
|
321
|
+
rb_raise(rb_eRuntimeError, "%s", StringValueCStr(errorMessage));
|
322
|
+
}
|
323
|
+
fclose(dutyFile);
|
324
|
+
const char *cDuty = StringValueCStr(dutyString);
|
325
|
+
|
326
|
+
// Toggle duty cycle between given value and 0, to modulate the PWM carrier.
|
327
|
+
for (int i=0; i<pulseCount; i++) {
|
328
|
+
if (i % 2 == 0) {
|
329
|
+
dutyFile = fopen(filePath, "w");
|
330
|
+
fputs(cDuty, dutyFile);
|
331
|
+
fclose(dutyFile);
|
332
|
+
} else {
|
333
|
+
dutyFile = fopen(filePath, "w");
|
334
|
+
fputs("0", dutyFile);
|
335
|
+
fclose(dutyFile);
|
336
|
+
}
|
337
|
+
// Wait for pulse time.
|
338
|
+
nanoDelay(nanoPulses[i]);
|
339
|
+
}
|
340
|
+
// Leave the pin low.
|
341
|
+
dutyFile = fopen(filePath, "w");
|
342
|
+
fputs("0", dutyFile);
|
343
|
+
fclose(dutyFile);
|
344
|
+
}
|
345
|
+
|
179
346
|
static VALUE i2c_open(VALUE self, VALUE i2cDev, VALUE i2cAddr, VALUE i2cFlags){
|
180
347
|
int handle = lgI2cOpen(NUM2INT(i2cDev), NUM2INT(i2cAddr), NUM2INT(i2cFlags));
|
181
348
|
return INT2NUM(handle);
|
@@ -338,6 +505,263 @@ static VALUE spi_ws2812_write(VALUE self, VALUE handle, VALUE pixelArray){
|
|
338
505
|
return INT2NUM(result);
|
339
506
|
}
|
340
507
|
|
508
|
+
/*****************************************************************************/
|
509
|
+
/* ONE WIRE */
|
510
|
+
/*****************************************************************************/
|
511
|
+
static uint8_t bitReadU64(uint64_t* b, uint8_t i) {
|
512
|
+
return ((*b >> i) & 0b1);
|
513
|
+
}
|
514
|
+
|
515
|
+
static void bitWriteU64(uint64_t* b, uint8_t i, uint8_t v) {
|
516
|
+
if (v == 0) {
|
517
|
+
*b &= ~(1ULL << i);
|
518
|
+
} else {
|
519
|
+
*b |= (1ULL << i);
|
520
|
+
}
|
521
|
+
}
|
522
|
+
|
523
|
+
static uint8_t bitReadU8(uint8_t* b, uint8_t i) {
|
524
|
+
return (*b >> i) & 0b1;
|
525
|
+
}
|
526
|
+
|
527
|
+
static void bitWriteU8(uint8_t* b, uint8_t i, uint8_t v) {
|
528
|
+
if (v == 0) {
|
529
|
+
*b &= ~(1 << i);
|
530
|
+
} else {
|
531
|
+
*b |= (1 << i);
|
532
|
+
}
|
533
|
+
}
|
534
|
+
|
535
|
+
static VALUE one_wire_bit_read(VALUE self, VALUE rbHandle, VALUE rbGPIO) {
|
536
|
+
int handle = NUM2INT(rbHandle);
|
537
|
+
int gpio = NUM2INT(rbGPIO);
|
538
|
+
uint8_t bit = 1;
|
539
|
+
struct timespec start;
|
540
|
+
struct timespec now;
|
541
|
+
|
542
|
+
// Start the read slot.
|
543
|
+
lgGpioWrite(handle, gpio, 0);
|
544
|
+
microDelay(1);
|
545
|
+
lgGpioWrite(handle, gpio, 1);
|
546
|
+
|
547
|
+
// Poll for 60us to see if pin goes low.
|
548
|
+
clock_gettime(CLOCK_MONOTONIC, &start);
|
549
|
+
now = start;
|
550
|
+
while(nanoDiff(&now, &start) < 60000){
|
551
|
+
if (lgGpioRead(handle, gpio) == 0) bit = 0;
|
552
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
553
|
+
}
|
554
|
+
return UINT2NUM(bit);
|
555
|
+
}
|
556
|
+
|
557
|
+
static VALUE one_wire_bit_write(VALUE self, VALUE rbHandle, VALUE rbGPIO, VALUE rbBit) {
|
558
|
+
int handle = NUM2INT(rbHandle);
|
559
|
+
int gpio = NUM2INT(rbGPIO);
|
560
|
+
uint8_t bit = NUM2CHR(rbBit);
|
561
|
+
|
562
|
+
// Write slot starts by going low for at least 1us.
|
563
|
+
lgGpioWrite(handle, gpio, 0);
|
564
|
+
microDelay(1);
|
565
|
+
|
566
|
+
// If 0, keep it low for the rest of the 60us write slot, then release.
|
567
|
+
if (bit == 0) {
|
568
|
+
microDelay(59);
|
569
|
+
lgGpioWrite(handle, gpio, 1);
|
570
|
+
// If 1, release first, then wait the rest of the 60us slot.
|
571
|
+
} else {
|
572
|
+
lgGpioWrite(handle, gpio, 1);
|
573
|
+
microDelay(59);
|
574
|
+
}
|
575
|
+
// Minimum 1us recovery time after each slot.
|
576
|
+
microDelay(1);
|
577
|
+
return Qnil;
|
578
|
+
}
|
579
|
+
|
580
|
+
static VALUE one_wire_reset(VALUE self, VALUE rbHandle, VALUE rbGPIO) {
|
581
|
+
int handle = NUM2INT(rbHandle);
|
582
|
+
int gpio = NUM2INT(rbGPIO);
|
583
|
+
struct timespec start;
|
584
|
+
uint8_t presence = 1;
|
585
|
+
|
586
|
+
// Hold low for 500us to reset, then go high.
|
587
|
+
lgGpioFree(handle, gpio);
|
588
|
+
lgGpioClaimOutput(handle, LG_SET_OPEN_DRAIN | LG_SET_PULL_UP, gpio, 0);
|
589
|
+
microDelay(500);
|
590
|
+
lgGpioWrite(handle, gpio, 1);
|
591
|
+
|
592
|
+
// Poll for 250us. If a device pulls the line low, return 0 (device present).
|
593
|
+
clock_gettime(CLOCK_MONOTONIC, &start);
|
594
|
+
while(nanosSince(&start) < 250000){
|
595
|
+
if (lgGpioRead(handle, gpio) == 0) presence = 0;
|
596
|
+
}
|
597
|
+
|
598
|
+
return UINT2NUM(presence);
|
599
|
+
}
|
600
|
+
|
601
|
+
/*****************************************************************************/
|
602
|
+
/* BIT-BANG I2C */
|
603
|
+
/*****************************************************************************/
|
604
|
+
static uint8_t sdaState = 1;
|
605
|
+
|
606
|
+
static void i2c_bb_set_sda(int handle, int sda, uint8_t level) {
|
607
|
+
if (level == sdaState) return;
|
608
|
+
lgGpioWrite(handle, sda, level);
|
609
|
+
sdaState = level;
|
610
|
+
}
|
611
|
+
|
612
|
+
// Start condition is SDA then SCL going low, from both high.
|
613
|
+
static void i2c_bb_start(int handle, int scl, int sda) {
|
614
|
+
lgGpioWrite(handle, sda, 0);
|
615
|
+
lgGpioWrite(handle, scl, 0);
|
616
|
+
}
|
617
|
+
|
618
|
+
// Stop condition is SDA going high, while SCL is also high.
|
619
|
+
static void i2c_bb_stop(int handle, int scl, int sda) {
|
620
|
+
lgGpioWrite(handle, sda, 0);
|
621
|
+
lgGpioWrite(handle, scl, 1);
|
622
|
+
lgGpioWrite(handle, sda, 1);
|
623
|
+
}
|
624
|
+
|
625
|
+
static uint8_t i2c_bb_read_bit(int handle, int scl, int sda) {
|
626
|
+
uint8_t bit;
|
627
|
+
// Ensure SDA high before we pull SCL high.
|
628
|
+
i2c_bb_set_sda(handle, sda, 1);
|
629
|
+
lgGpioWrite(handle, scl, 1);
|
630
|
+
bit = lgGpioRead(handle, sda);
|
631
|
+
lgGpioWrite(handle, scl, 0);
|
632
|
+
return bit;
|
633
|
+
}
|
634
|
+
|
635
|
+
static void i2c_bb_write_bit(int handle, int scl, int sda, uint8_t bit) {
|
636
|
+
// Set SDA while SCL is low.
|
637
|
+
i2c_bb_set_sda(handle, sda, bit);
|
638
|
+
lgGpioWrite(handle, scl, 1);
|
639
|
+
lgGpioWrite(handle, scl, 0);
|
640
|
+
}
|
641
|
+
|
642
|
+
static uint8_t i2c_bb_read_byte(int handle, int scl, int sda, bool ack) {
|
643
|
+
uint8_t b;
|
644
|
+
|
645
|
+
// Receive MSB first.
|
646
|
+
for (int i=7; i>=0; i--) bitWriteU8(&b, i, i2c_bb_read_bit(handle, scl, sda));
|
647
|
+
|
648
|
+
// Send ACK or NACK and return byte.
|
649
|
+
if (ack) {
|
650
|
+
i2c_bb_write_bit(handle, scl, sda, 0);
|
651
|
+
} else {
|
652
|
+
i2c_bb_write_bit(handle, scl, sda, 1);
|
653
|
+
}
|
654
|
+
return b;
|
655
|
+
}
|
656
|
+
|
657
|
+
static int i2c_bb_write_byte(int handle, int scl, int sda, uint8_t b) {
|
658
|
+
// Send MSB first.
|
659
|
+
for (int i=7; i>=0; i--) i2c_bb_write_bit(handle, scl, sda, bitReadU8(&b, i));
|
660
|
+
|
661
|
+
// Return -1 for NACK, 0 for ACK.
|
662
|
+
return (i2c_bb_read_bit(handle, scl, sda) == 0) ? 0 : -1;
|
663
|
+
}
|
664
|
+
|
665
|
+
static VALUE i2c_bb_claim(VALUE self, VALUE rbHandle, VALUE rbSCL, VALUE rbSDA) {
|
666
|
+
int handle = NUM2INT(rbHandle);
|
667
|
+
int scl = NUM2INT(rbSCL);
|
668
|
+
int sda = NUM2INT(rbSDA);
|
669
|
+
|
670
|
+
// SCL is a driven output. SDA is open drain with pullup enabled.
|
671
|
+
lgGpioClaimOutput(handle, LG_SET_PULL_NONE, scl, 1);
|
672
|
+
lgGpioClaimOutput(handle, LG_SET_OPEN_DRAIN | LG_SET_PULL_UP, sda, 1);
|
673
|
+
}
|
674
|
+
|
675
|
+
static VALUE i2c_bb_search(VALUE self, VALUE rbHandle, VALUE rbSCL, VALUE rbSDA) {
|
676
|
+
int handle = NUM2INT(rbHandle);
|
677
|
+
int scl = NUM2INT(rbSCL);
|
678
|
+
int sda = NUM2INT(rbSDA);
|
679
|
+
int ack;
|
680
|
+
uint8_t present[128];
|
681
|
+
uint8_t presentCount = 0;
|
682
|
+
sdaState = 1;
|
683
|
+
|
684
|
+
// Only addresses from 0x08 to 0x77 are usable (8 to 127).
|
685
|
+
for (uint8_t addr = 0x08; addr < 0x78; addr++) {
|
686
|
+
i2c_bb_start(handle, scl, sda);
|
687
|
+
ack = i2c_bb_write_byte(handle, scl, sda, ((addr << 1) & 0b11111110));
|
688
|
+
i2c_bb_stop(handle, scl, sda);
|
689
|
+
if (ack == 0){
|
690
|
+
present[addr] = 1;
|
691
|
+
presentCount++;
|
692
|
+
} else {
|
693
|
+
present[addr] = 0;
|
694
|
+
}
|
695
|
+
}
|
696
|
+
if (presentCount == 0) return Qnil;
|
697
|
+
|
698
|
+
VALUE retArray = rb_ary_new2(presentCount);
|
699
|
+
uint8_t i = 0;
|
700
|
+
for (uint8_t addr = 0x08; addr < 0x78; addr++) {
|
701
|
+
if (present[addr] == 1) {
|
702
|
+
rb_ary_store(retArray, i, UINT2NUM(addr));
|
703
|
+
i++;
|
704
|
+
}
|
705
|
+
}
|
706
|
+
return retArray;
|
707
|
+
}
|
708
|
+
|
709
|
+
static VALUE i2c_bb_write(VALUE self, VALUE rbHandle, VALUE rbSCL, VALUE rbSDA, VALUE rbAddress, VALUE txArray) {
|
710
|
+
int handle = NUM2INT(rbHandle);
|
711
|
+
int scl = NUM2INT(rbSCL);
|
712
|
+
int sda = NUM2INT(rbSDA);
|
713
|
+
uint8_t address = NUM2CHR(rbAddress);
|
714
|
+
uint8_t writeAddress = (address << 1);
|
715
|
+
sdaState = 1;
|
716
|
+
|
717
|
+
int count = RARRAY_LEN(txArray);
|
718
|
+
uint8_t txBuf[count];
|
719
|
+
VALUE currentByte;
|
720
|
+
for(int i=0; i<count; i++){
|
721
|
+
currentByte = rb_ary_entry(txArray, i);
|
722
|
+
Check_Type(currentByte, T_FIXNUM);
|
723
|
+
txBuf[i] = NUM2CHR(currentByte);
|
724
|
+
}
|
725
|
+
|
726
|
+
i2c_bb_start(handle, scl, sda);
|
727
|
+
i2c_bb_write_byte(handle, scl, sda, writeAddress);
|
728
|
+
for (int i=0; i<count; i++) i2c_bb_write_byte(handle, scl, sda, txBuf[i]);
|
729
|
+
i2c_bb_stop(handle, scl, sda);
|
730
|
+
}
|
731
|
+
|
732
|
+
static VALUE i2c_bb_read(VALUE self, VALUE rbHandle, VALUE rbSCL, VALUE rbSDA, VALUE rbAddress, VALUE rbCount) {
|
733
|
+
int handle = NUM2INT(rbHandle);
|
734
|
+
int scl = NUM2INT(rbSCL);
|
735
|
+
int sda = NUM2INT(rbSDA);
|
736
|
+
uint8_t address = NUM2CHR(rbAddress);
|
737
|
+
uint8_t readAddress = (address << 1) | 0b00000001;
|
738
|
+
sdaState = 1;
|
739
|
+
|
740
|
+
int count = NUM2INT(rbCount);
|
741
|
+
uint8_t rxBuf[count];
|
742
|
+
|
743
|
+
i2c_bb_start(handle, scl, sda);
|
744
|
+
int ack = i2c_bb_write_byte(handle, scl, sda, readAddress);
|
745
|
+
// Device with this address not present on the bus.
|
746
|
+
if (ack != 0) return Qnil;
|
747
|
+
|
748
|
+
// Read and ACK for all but the last byte.
|
749
|
+
int pos = 0;
|
750
|
+
while(pos < count-1) {
|
751
|
+
rxBuf[pos] = i2c_bb_read_byte(handle, scl, sda, true);
|
752
|
+
pos++;
|
753
|
+
}
|
754
|
+
rxBuf[pos] = i2c_bb_read_byte(handle, scl, sda, false);
|
755
|
+
i2c_bb_stop(handle, scl, sda);
|
756
|
+
|
757
|
+
VALUE retArray = rb_ary_new2(count);
|
758
|
+
for(int i=0; i<count; i++) rb_ary_store(retArray, i, UINT2NUM(rxBuf[i]));
|
759
|
+
return retArray;
|
760
|
+
}
|
761
|
+
|
762
|
+
/*****************************************************************************/
|
763
|
+
/* EXTENSION INIT */
|
764
|
+
/*****************************************************************************/
|
341
765
|
void Init_lgpio(void) {
|
342
766
|
// Modules
|
343
767
|
VALUE mLGPIO = rb_define_module("LGPIO");
|
@@ -354,6 +778,7 @@ void Init_lgpio(void) {
|
|
354
778
|
rb_define_const(mLGPIO, "BOTH_EDGES", INT2NUM(LG_BOTH_EDGES));
|
355
779
|
rb_define_singleton_method(mLGPIO, "chip_open", chip_open, 1);
|
356
780
|
rb_define_singleton_method(mLGPIO, "chip_close", chip_close, 1);
|
781
|
+
rb_define_singleton_method(mLGPIO, "gpio_get_mode", gpio_get_mode, 2);
|
357
782
|
rb_define_singleton_method(mLGPIO, "gpio_free", gpio_free, 2);
|
358
783
|
rb_define_singleton_method(mLGPIO, "gpio_claim_input", gpio_claim_input, 3);
|
359
784
|
rb_define_singleton_method(mLGPIO, "gpio_claim_output", gpio_claim_output, 4);
|
@@ -373,15 +798,20 @@ void Init_lgpio(void) {
|
|
373
798
|
rb_define_singleton_method(mLGPIO, "gpio_start_reporting", gpio_start_reporting, 0);
|
374
799
|
rb_define_singleton_method(mLGPIO, "gpio_get_report", gpio_get_report, 0);
|
375
800
|
|
376
|
-
//
|
801
|
+
// Pulse Input
|
802
|
+
rb_define_singleton_method(mLGPIO, "gpio_read_ultrasonic", gpio_read_ultrasonic, 4);
|
803
|
+
rb_define_singleton_method(mLGPIO, "gpio_read_pulses_us", gpio_read_pulses_us, 6);
|
804
|
+
|
805
|
+
// Soft PWM / Wave
|
377
806
|
rb_define_const(mLGPIO, "TX_PWM", INT2NUM(LG_TX_PWM));
|
378
807
|
rb_define_const(mLGPIO, "TX_WAVE",INT2NUM(LG_TX_WAVE));
|
379
808
|
rb_define_singleton_method(mLGPIO, "tx_busy", tx_busy, 3);
|
380
809
|
rb_define_singleton_method(mLGPIO, "tx_room", tx_room, 3);
|
381
810
|
rb_define_singleton_method(mLGPIO, "tx_pulse", tx_pulse, 6);
|
382
811
|
rb_define_singleton_method(mLGPIO, "tx_pwm", tx_pwm, 6);
|
383
|
-
rb_define_singleton_method(mLGPIO, "tx_servo", tx_servo, 6);
|
384
812
|
rb_define_singleton_method(mLGPIO, "tx_wave", tx_wave, 3);
|
813
|
+
// Don't use this. Servo will jitter.
|
814
|
+
rb_define_singleton_method(mLGPIO, "tx_servo", tx_servo, 6);
|
385
815
|
|
386
816
|
// I2C
|
387
817
|
rb_define_singleton_method(mLGPIO, "i2c_open", i2c_open, 3);
|
@@ -397,4 +827,19 @@ void Init_lgpio(void) {
|
|
397
827
|
rb_define_singleton_method(mLGPIO, "spi_write", spi_write, 2);
|
398
828
|
rb_define_singleton_method(mLGPIO, "spi_xfer", spi_xfer, 2);
|
399
829
|
rb_define_singleton_method(mLGPIO, "spi_ws2812_write", spi_ws2812_write, 2);
|
830
|
+
|
831
|
+
// Hardware PWM waves for on-off-keying.
|
832
|
+
VALUE cHardwarePWM = rb_define_class_under(mLGPIO, "HardwarePWM", rb_cObject);
|
833
|
+
rb_define_method(cHardwarePWM, "tx_wave_ook", tx_wave_ook, 3);
|
834
|
+
|
835
|
+
// Bit-banged 1-Wire
|
836
|
+
rb_define_singleton_method(mLGPIO, "one_wire_bit_read", one_wire_bit_read, 2);
|
837
|
+
rb_define_singleton_method(mLGPIO, "one_wire_bit_write", one_wire_bit_write, 3);
|
838
|
+
rb_define_singleton_method(mLGPIO, "one_wire_reset", one_wire_reset, 2);
|
839
|
+
|
840
|
+
// Bit-banged I2C
|
841
|
+
rb_define_singleton_method(mLGPIO, "i2c_bb_claim", i2c_bb_claim, 3);
|
842
|
+
rb_define_singleton_method(mLGPIO, "i2c_bb_search", i2c_bb_search, 3);
|
843
|
+
rb_define_singleton_method(mLGPIO, "i2c_bb_write", i2c_bb_write, 5);
|
844
|
+
rb_define_singleton_method(mLGPIO, "i2c_bb_read", i2c_bb_read, 5);
|
400
845
|
}
|