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.
- checksums.yaml +4 -4
- data/README.md +42 -28
- data/examples/dht.rb +47 -0
- data/examples/{bench_in.rb → gpio_bench_in.rb} +1 -1
- data/examples/{bench_out.rb → gpio_bench_out.rb} +1 -1
- data/examples/{blink.rb → gpio_blink.rb} +1 -1
- data/examples/{group_in.rb → gpio_group_in.rb} +2 -2
- data/examples/{group_out.rb → gpio_group_out.rb} +1 -1
- data/examples/{momentary.rb → gpio_momentary.rb} +2 -2
- data/examples/{reports.rb → gpio_reports.rb} +1 -1
- data/examples/{rotary_encoder.rb → gpio_rotary_encoder.rb} +3 -3
- data/examples/{rotary_encoder_led.rb → gpio_rotary_encoder_led.rb} +4 -4
- data/examples/{wave.rb → gpio_wave.rb} +1 -1
- data/examples/hcsr04.rb +32 -0
- data/examples/i2c_bb_aht10.rb +40 -0
- data/examples/i2c_bb_search.rb +19 -0
- data/examples/i2c_bb_ssd1306_bench.rb +35 -0
- data/examples/{i2c_aht10.rb → i2c_hw_aht10.rb} +2 -1
- data/examples/{i2c_aht10_zip.rb → i2c_hw_aht10_zip.rb} +2 -1
- data/examples/{i2c_ssd1306_bench.rb → i2c_hw_ssd1306_bench.rb} +6 -5
- data/examples/infrared.rb +22 -0
- data/examples/one_wire_ds18b20.rb +30 -0
- data/examples/one_wire_search.rb +15 -0
- data/examples/pwm_hw_bench.rb +20 -0
- data/examples/{pwm.rb → pwm_sw.rb} +1 -1
- data/examples/spi_bb_loopback.rb +25 -0
- data/examples/spi_bb_sim_bench.rb +48 -0
- data/examples/spi_bb_ssd1306_bench.rb +51 -0
- data/ext/lgpio/lgpio.c +289 -11
- data/lib/lgpio/hardware_pwm.rb +18 -5
- 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/spi_bitbang.rb +109 -0
- data/lib/lgpio/version.rb +1 -1
- data/lib/lgpio.rb +6 -0
- metadata +36 -21
- data/examples/spi_read.rb +0 -14
- /data/examples/{servo.rb → pwm_hw_servo.rb} +0 -0
- /data/examples/{spi_loopback.rb → spi_hw_loopback.rb} +0 -0
- /data/examples/{spi_ws2812.rb → spi_hw_ws2812.rb} +0 -0
- /data/examples/{spi_ws2812_bounce.rb → spi_hw_ws2812_bounce.rb} +0 -0
@@ -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"
|
data/ext/lgpio/lgpio.c
CHANGED
@@ -1,13 +1,50 @@
|
|
1
1
|
#include <lgpio.h>
|
2
2
|
#include <ruby.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <time.h>
|
3
5
|
|
4
|
-
|
6
|
+
/*****************************************************************************/
|
7
|
+
/* GPIO REPORT QUEUE */
|
8
|
+
/*****************************************************************************/
|
5
9
|
static pthread_mutex_t queueLock;
|
10
|
+
// Set up a queue for up to 2**16 GPIO reports.
|
6
11
|
#define QUEUE_LENGTH UINT16_MAX + 1
|
7
12
|
static lgGpioReport_t reportQueue[QUEUE_LENGTH];
|
8
13
|
static uint16_t qWritePos = 1;
|
9
14
|
static uint16_t qReadPos = 0;
|
10
15
|
|
16
|
+
/*****************************************************************************/
|
17
|
+
/* TIMING HELPERS */
|
18
|
+
/*****************************************************************************/
|
19
|
+
static uint64_t nanoDiff(const struct timespec *event2, const struct timespec *event1) {
|
20
|
+
uint64_t event2_ns = (uint64_t)event2->tv_sec * 1000000000LL + event2->tv_nsec;
|
21
|
+
uint64_t event1_ns = (uint64_t)event1->tv_sec * 1000000000LL + event1->tv_nsec;
|
22
|
+
return event2_ns - event1_ns;
|
23
|
+
}
|
24
|
+
|
25
|
+
static uint64_t nanosSince(const struct timespec *event) {
|
26
|
+
struct timespec now;
|
27
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
28
|
+
return nanoDiff(&now, event);
|
29
|
+
}
|
30
|
+
|
31
|
+
static void nanoDelay(uint64_t nanos) {
|
32
|
+
struct timespec refTime;
|
33
|
+
struct timespec now;
|
34
|
+
clock_gettime(CLOCK_MONOTONIC, &refTime);
|
35
|
+
now = refTime;
|
36
|
+
while(nanoDiff(&now, &refTime) < nanos) {
|
37
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
static void microDelay(uint64_t micros) {
|
42
|
+
nanoDelay(micros * 1000);
|
43
|
+
}
|
44
|
+
|
45
|
+
/*****************************************************************************/
|
46
|
+
/* CHIP & GPIO */
|
47
|
+
/*****************************************************************************/
|
11
48
|
static VALUE chip_open(VALUE self, VALUE gpio_dev) {
|
12
49
|
int result = lgGpiochipOpen(NUM2INT(gpio_dev));
|
13
50
|
return INT2NUM(result);
|
@@ -18,6 +55,11 @@ static VALUE chip_close(VALUE self, VALUE handle) {
|
|
18
55
|
return INT2NUM(result);
|
19
56
|
}
|
20
57
|
|
58
|
+
static VALUE gpio_get_mode(VALUE self, VALUE handle, VALUE gpio) {
|
59
|
+
int result = lgGpioGetMode(NUM2INT(handle), NUM2INT(gpio));
|
60
|
+
return INT2NUM(result);
|
61
|
+
}
|
62
|
+
|
21
63
|
static VALUE gpio_claim_output(VALUE self, VALUE handle, VALUE flags, VALUE gpio, VALUE level) {
|
22
64
|
int result = lgGpioClaimOutput(NUM2INT(handle), NUM2INT(flags), NUM2INT(gpio), NUM2INT(level));
|
23
65
|
return INT2NUM(result);
|
@@ -93,13 +135,13 @@ static VALUE gpio_claim_alert(VALUE self, VALUE handle, VALUE flags, VALUE eFlag
|
|
93
135
|
return INT2NUM(result);
|
94
136
|
}
|
95
137
|
|
96
|
-
void queue_gpio_reports(int count, lgGpioAlert_p events, void *data){
|
138
|
+
static void queue_gpio_reports(int count, lgGpioAlert_p events, void *data){
|
97
139
|
pthread_mutex_lock(&queueLock);
|
98
140
|
for(int i=0; i<count; i++) {
|
99
141
|
memcpy(&reportQueue[qWritePos], &events[i].report, sizeof(lgGpioReport_t));
|
100
|
-
qWritePos
|
142
|
+
qWritePos++;
|
101
143
|
// qReadPos is the LAST report read. If passing by 1, increment it too. Lose oldest data first.
|
102
|
-
if (qWritePos - qReadPos == 1) qReadPos
|
144
|
+
if (qWritePos - qReadPos == 1) qReadPos++;
|
103
145
|
}
|
104
146
|
pthread_mutex_unlock(&queueLock);
|
105
147
|
}
|
@@ -126,13 +168,12 @@ static VALUE gpio_get_report(VALUE self){
|
|
126
168
|
}
|
127
169
|
pthread_mutex_unlock(&queueLock);
|
128
170
|
|
129
|
-
|
130
|
-
return hash;
|
131
|
-
} else {
|
132
|
-
return Qnil;
|
133
|
-
}
|
171
|
+
return (popped) ? hash : Qnil;
|
134
172
|
}
|
135
173
|
|
174
|
+
/*****************************************************************************/
|
175
|
+
/* SOFTWARE PWM & WAVE */
|
176
|
+
/*****************************************************************************/
|
136
177
|
static VALUE tx_busy(VALUE self, VALUE handle, VALUE gpio, VALUE kind) {
|
137
178
|
int result = lgTxBusy(NUM2INT(handle), NUM2INT(gpio), NUM2INT(kind));
|
138
179
|
return INT2NUM(result);
|
@@ -176,6 +217,53 @@ static VALUE tx_wave(VALUE self, VALUE handle, VALUE lead_gpio, VALUE pulses) {
|
|
176
217
|
return INT2NUM(result);
|
177
218
|
}
|
178
219
|
|
220
|
+
/*****************************************************************************/
|
221
|
+
/* HARDWARE PWM OOK WAVE */
|
222
|
+
/*****************************************************************************/
|
223
|
+
static VALUE tx_wave_ook(VALUE self, VALUE dutyPath, VALUE dutyString, VALUE pulses) {
|
224
|
+
// NOTE: This uses hardware PWM, NOT the lgpio software PWM/wave interface.
|
225
|
+
// The Ruby class LGPIO::HardwarePWM should have already set the PWM carrier frequency.
|
226
|
+
//
|
227
|
+
// Convert pulses from microseconds to nanoseconds.
|
228
|
+
uint32_t pulseCount = rb_array_len(pulses);
|
229
|
+
uint64_t nanoPulses[pulseCount];
|
230
|
+
for (int i=0; i<pulseCount; i++) {
|
231
|
+
nanoPulses[i] = NUM2UINT(rb_ary_entry(pulses, i)) * 1000;
|
232
|
+
}
|
233
|
+
|
234
|
+
// Prepare to write duty cycle.
|
235
|
+
const char *filePath = StringValueCStr(dutyPath);
|
236
|
+
FILE *dutyFile = fopen(filePath, "w");
|
237
|
+
if (dutyFile == NULL) {
|
238
|
+
VALUE errorMessage = rb_sprintf("Could not open PWM duty_cycle file: %s", filePath);
|
239
|
+
rb_raise(rb_eRuntimeError, "%s", StringValueCStr(errorMessage));
|
240
|
+
}
|
241
|
+
fclose(dutyFile);
|
242
|
+
const char *cDuty = StringValueCStr(dutyString);
|
243
|
+
|
244
|
+
// Toggle duty cycle between given value and 0, to modulate the PWM carrier.
|
245
|
+
for (int i=0; i<pulseCount; i++) {
|
246
|
+
if (i % 2 == 0) {
|
247
|
+
dutyFile = fopen(filePath, "w");
|
248
|
+
fputs(cDuty, dutyFile);
|
249
|
+
fclose(dutyFile);
|
250
|
+
} else {
|
251
|
+
dutyFile = fopen(filePath, "w");
|
252
|
+
fputs("0", dutyFile);
|
253
|
+
fclose(dutyFile);
|
254
|
+
}
|
255
|
+
// Wait for pulse time.
|
256
|
+
nanoDelay(nanoPulses[i]);
|
257
|
+
}
|
258
|
+
// Leave the pin low.
|
259
|
+
dutyFile = fopen(filePath, "w");
|
260
|
+
fputs("0", dutyFile);
|
261
|
+
fclose(dutyFile);
|
262
|
+
}
|
263
|
+
|
264
|
+
/*****************************************************************************/
|
265
|
+
/* HARDWARE I2C */
|
266
|
+
/*****************************************************************************/
|
179
267
|
static VALUE i2c_open(VALUE self, VALUE i2cDev, VALUE i2cAddr, VALUE i2cFlags){
|
180
268
|
int handle = lgI2cOpen(NUM2INT(i2cDev), NUM2INT(i2cAddr), NUM2INT(i2cFlags));
|
181
269
|
return INT2NUM(handle);
|
@@ -239,6 +327,9 @@ static VALUE i2c_zip(VALUE self, VALUE handle, VALUE txArray, VALUE rb_rxCount){
|
|
239
327
|
return retArray;
|
240
328
|
}
|
241
329
|
|
330
|
+
/*****************************************************************************/
|
331
|
+
/* HARDWARE SPI */
|
332
|
+
/*****************************************************************************/
|
242
333
|
static VALUE spi_open(VALUE self, VALUE spiDev, VALUE spiChan, VALUE spiBaud, VALUE spiFlags){
|
243
334
|
int handle = lgSpiOpen(NUM2INT(spiDev), NUM2INT(spiChan), NUM2INT(spiBaud), NUM2INT(spiFlags));
|
244
335
|
return INT2NUM(handle);
|
@@ -338,6 +429,178 @@ static VALUE spi_ws2812_write(VALUE self, VALUE handle, VALUE pixelArray){
|
|
338
429
|
return INT2NUM(result);
|
339
430
|
}
|
340
431
|
|
432
|
+
/*****************************************************************************/
|
433
|
+
/* BIT-BANG PULSE INPUT */
|
434
|
+
/*****************************************************************************/
|
435
|
+
static VALUE gpio_read_ultrasonic(VALUE self, VALUE rbHandle, VALUE rbTrigger, VALUE rbEcho, VALUE rbTriggerTime) {
|
436
|
+
int handle = NUM2UINT(rbHandle);
|
437
|
+
int trigger = NUM2UINT(rbTrigger);
|
438
|
+
int echo = NUM2UINT(rbEcho);
|
439
|
+
uint32_t triggerTime = NUM2UINT(rbTriggerTime);
|
440
|
+
struct timespec start;
|
441
|
+
struct timespec now;
|
442
|
+
bool echoSeen = false;
|
443
|
+
|
444
|
+
// Pull down avoids false readings if disconnected.
|
445
|
+
lgGpioClaimInput(handle, LG_SET_PULL_DOWN, echo);
|
446
|
+
|
447
|
+
// Initial pulse on the triger pin.
|
448
|
+
lgGpioClaimOutput(handle, LG_SET_PULL_NONE, trigger, 0);
|
449
|
+
microDelay(5);
|
450
|
+
lgGpioWrite(handle, trigger, 1);
|
451
|
+
microDelay(triggerTime);
|
452
|
+
lgGpioWrite(handle, trigger, 0);
|
453
|
+
|
454
|
+
clock_gettime(CLOCK_MONOTONIC, &start);
|
455
|
+
now = start;
|
456
|
+
|
457
|
+
// Wait for echo to go high, up to 25,000 us after trigger.
|
458
|
+
while(nanoDiff(&now, &start) < 25000000){
|
459
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
460
|
+
if (lgGpioRead(handle, echo) == 1) {
|
461
|
+
echoSeen = true;
|
462
|
+
start = now;
|
463
|
+
break;
|
464
|
+
}
|
465
|
+
}
|
466
|
+
if (!echoSeen) return Qnil;
|
467
|
+
|
468
|
+
// Wait for echo to go low again, up to 25,000 us after echo start.
|
469
|
+
while(nanoDiff(&now, &start) < 25000000){
|
470
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
471
|
+
if (lgGpioRead(handle, echo) == 0) break;
|
472
|
+
}
|
473
|
+
|
474
|
+
// High pulse time in microseconds.
|
475
|
+
return INT2NUM(round(nanoDiff(&now, &start) / 1000.0));
|
476
|
+
}
|
477
|
+
|
478
|
+
static VALUE gpio_read_pulses_us(VALUE self, VALUE rbHandle, VALUE rbGPIO, VALUE rbReset_us, VALUE rbResetLevel, VALUE rbLimit, VALUE rbTimeout_ms) {
|
479
|
+
// C values
|
480
|
+
int handle = NUM2INT(rbHandle);
|
481
|
+
int gpio = NUM2INT(rbGPIO);
|
482
|
+
uint32_t reset_us = NUM2UINT(rbReset_us);
|
483
|
+
uint8_t resetLevel = NUM2UINT(rbResetLevel);
|
484
|
+
uint32_t limit = NUM2UINT(rbLimit);
|
485
|
+
uint64_t timeout_ns = NUM2UINT(rbTimeout_ms) * 1000000;
|
486
|
+
|
487
|
+
// State setup
|
488
|
+
uint64_t pulses_ns[limit];
|
489
|
+
uint32_t pulseIndex = 0;
|
490
|
+
int gpioState;
|
491
|
+
struct timespec start;
|
492
|
+
struct timespec lastPulse;
|
493
|
+
struct timespec now;
|
494
|
+
|
495
|
+
// Perform reset
|
496
|
+
if (reset_us > 0) {
|
497
|
+
int result = lgGpioClaimOutput(handle, LG_SET_PULL_NONE, gpio, resetLevel);
|
498
|
+
if (result < 0) return NUM2INT(result);
|
499
|
+
microDelay(reset_us);
|
500
|
+
}
|
501
|
+
|
502
|
+
// Initialize timing
|
503
|
+
clock_gettime(CLOCK_MONOTONIC, &start);
|
504
|
+
lastPulse = start;
|
505
|
+
now = start;
|
506
|
+
|
507
|
+
// Switch to input and read initial state
|
508
|
+
lgGpioClaimInput(handle, LG_SET_PULL_NONE, gpio);
|
509
|
+
gpioState = lgGpioRead(handle, gpio);
|
510
|
+
|
511
|
+
// Read pulses in nanoseconds
|
512
|
+
while ((nanoDiff(&now, &start) < timeout_ns) && (pulseIndex < limit)) {
|
513
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
514
|
+
if (lgGpioRead(handle, gpio) != gpioState) {
|
515
|
+
pulses_ns[pulseIndex] = nanoDiff(&now, &lastPulse);
|
516
|
+
lastPulse = now;
|
517
|
+
gpioState = gpioState ^ 0b1;
|
518
|
+
pulseIndex++;
|
519
|
+
}
|
520
|
+
}
|
521
|
+
|
522
|
+
// Return Ruby array of pulse as microseconds
|
523
|
+
if (pulseIndex == 0) return Qnil;
|
524
|
+
VALUE retArray = rb_ary_new2(pulseIndex);
|
525
|
+
for(int i=0; i<pulseIndex; i++){
|
526
|
+
uint32_t pulse_us = round(pulses_ns[i] / 1000.0);
|
527
|
+
rb_ary_store(retArray, i, UINT2NUM(pulse_us));
|
528
|
+
}
|
529
|
+
return retArray;
|
530
|
+
}
|
531
|
+
|
532
|
+
/*****************************************************************************/
|
533
|
+
/* BIT BANG 1-WIRE HEPERS */
|
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
|
+
/* EXTENSION INIT */
|
603
|
+
/*****************************************************************************/
|
341
604
|
void Init_lgpio(void) {
|
342
605
|
// Modules
|
343
606
|
VALUE mLGPIO = rb_define_module("LGPIO");
|
@@ -354,6 +617,7 @@ void Init_lgpio(void) {
|
|
354
617
|
rb_define_const(mLGPIO, "BOTH_EDGES", INT2NUM(LG_BOTH_EDGES));
|
355
618
|
rb_define_singleton_method(mLGPIO, "chip_open", chip_open, 1);
|
356
619
|
rb_define_singleton_method(mLGPIO, "chip_close", chip_close, 1);
|
620
|
+
rb_define_singleton_method(mLGPIO, "gpio_get_mode", gpio_get_mode, 2);
|
357
621
|
rb_define_singleton_method(mLGPIO, "gpio_free", gpio_free, 2);
|
358
622
|
rb_define_singleton_method(mLGPIO, "gpio_claim_input", gpio_claim_input, 3);
|
359
623
|
rb_define_singleton_method(mLGPIO, "gpio_claim_output", gpio_claim_output, 4);
|
@@ -373,15 +637,20 @@ void Init_lgpio(void) {
|
|
373
637
|
rb_define_singleton_method(mLGPIO, "gpio_start_reporting", gpio_start_reporting, 0);
|
374
638
|
rb_define_singleton_method(mLGPIO, "gpio_get_report", gpio_get_report, 0);
|
375
639
|
|
376
|
-
// PWM /
|
640
|
+
// Soft PWM / Wave
|
377
641
|
rb_define_const(mLGPIO, "TX_PWM", INT2NUM(LG_TX_PWM));
|
378
642
|
rb_define_const(mLGPIO, "TX_WAVE",INT2NUM(LG_TX_WAVE));
|
379
643
|
rb_define_singleton_method(mLGPIO, "tx_busy", tx_busy, 3);
|
380
644
|
rb_define_singleton_method(mLGPIO, "tx_room", tx_room, 3);
|
381
645
|
rb_define_singleton_method(mLGPIO, "tx_pulse", tx_pulse, 6);
|
382
646
|
rb_define_singleton_method(mLGPIO, "tx_pwm", tx_pwm, 6);
|
383
|
-
rb_define_singleton_method(mLGPIO, "tx_servo", tx_servo, 6);
|
384
647
|
rb_define_singleton_method(mLGPIO, "tx_wave", tx_wave, 3);
|
648
|
+
// Don't use this. Servo will jitter.
|
649
|
+
rb_define_singleton_method(mLGPIO, "tx_servo", tx_servo, 6);
|
650
|
+
|
651
|
+
// Hardware PWM waves for on-off-keying.
|
652
|
+
VALUE cHardwarePWM = rb_define_class_under(mLGPIO, "HardwarePWM", rb_cObject);
|
653
|
+
rb_define_method(cHardwarePWM, "tx_wave_ook", tx_wave_ook, 3);
|
385
654
|
|
386
655
|
// I2C
|
387
656
|
rb_define_singleton_method(mLGPIO, "i2c_open", i2c_open, 3);
|
@@ -397,4 +666,13 @@ void Init_lgpio(void) {
|
|
397
666
|
rb_define_singleton_method(mLGPIO, "spi_write", spi_write, 2);
|
398
667
|
rb_define_singleton_method(mLGPIO, "spi_xfer", spi_xfer, 2);
|
399
668
|
rb_define_singleton_method(mLGPIO, "spi_ws2812_write", spi_ws2812_write, 2);
|
669
|
+
|
670
|
+
// Bit-Bang Pulse Input
|
671
|
+
rb_define_singleton_method(mLGPIO, "gpio_read_ultrasonic", gpio_read_ultrasonic, 4);
|
672
|
+
rb_define_singleton_method(mLGPIO, "gpio_read_pulses_us", gpio_read_pulses_us, 6);
|
673
|
+
|
674
|
+
// Bit-bang 1-Wire Helpers
|
675
|
+
rb_define_singleton_method(mLGPIO, "one_wire_bit_read", one_wire_bit_read, 2);
|
676
|
+
rb_define_singleton_method(mLGPIO, "one_wire_bit_write", one_wire_bit_write, 3);
|
677
|
+
rb_define_singleton_method(mLGPIO, "one_wire_reset", one_wire_reset, 2);
|
400
678
|
}
|
data/lib/lgpio/hardware_pwm.rb
CHANGED
@@ -23,6 +23,18 @@ module LGPIO
|
|
23
23
|
@path ||= "#{SYS_FS_PWM_PATH}pwmchip#{@chip}/pwm#{@channel}/"
|
24
24
|
end
|
25
25
|
|
26
|
+
def period_path
|
27
|
+
@period_path ||= "#{path}period"
|
28
|
+
end
|
29
|
+
|
30
|
+
def duty_path
|
31
|
+
@duty_path ||= "#{path}duty_cycle"
|
32
|
+
end
|
33
|
+
|
34
|
+
def enable_path
|
35
|
+
@enable_path ||= "#{path}enable"
|
36
|
+
end
|
37
|
+
|
26
38
|
def frequency=(freq)
|
27
39
|
self.period = (NS_PER_S / freq.to_f).round
|
28
40
|
end
|
@@ -30,13 +42,14 @@ module LGPIO
|
|
30
42
|
def period=(p)
|
31
43
|
old_period = File.read("#{path}period").strip.to_i
|
32
44
|
unless (old_period == 0)
|
33
|
-
File.open(
|
45
|
+
File.open(duty_path, 'w') { |f| f.write("0") }
|
34
46
|
end
|
35
|
-
File.open(
|
47
|
+
File.open(period_path, 'w') { |f| f.write(p) }
|
36
48
|
@period = p
|
37
49
|
end
|
38
50
|
|
39
51
|
def duty_percent
|
52
|
+
return 0.0 if (!duty || !period) || (duty == 0)
|
40
53
|
(duty / period.to_f) * 100.0
|
41
54
|
end
|
42
55
|
|
@@ -53,17 +66,17 @@ module LGPIO
|
|
53
66
|
|
54
67
|
def duty=(d_ns)
|
55
68
|
raise "duty cycle: #{d_ns} ns cannot be longer than period: #{period} ns" if d_ns > period
|
56
|
-
File.open(
|
69
|
+
File.open(duty_path, 'w') { |f| f.write(d_ns) }
|
57
70
|
@duty = d_ns
|
58
71
|
end
|
59
72
|
|
60
73
|
def disable
|
61
|
-
File.open(
|
74
|
+
File.open(enable_path, 'w') { |f| f.write("0") }
|
62
75
|
@enabled = false
|
63
76
|
end
|
64
77
|
|
65
78
|
def enable
|
66
|
-
File.open(
|
79
|
+
File.open(enable_path, 'w') { |f| f.write("1") }
|
67
80
|
@enabled = true
|
68
81
|
end
|
69
82
|
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module LGPIO
|
2
|
+
class I2CBitBang
|
3
|
+
VALID_ADDRESSES = (0x08..0x77).to_a
|
4
|
+
|
5
|
+
attr_reader :handle, :scl, :sda
|
6
|
+
|
7
|
+
def initialize(handle, scl, sda)
|
8
|
+
@handle = handle
|
9
|
+
@scl = scl
|
10
|
+
@sda = sda
|
11
|
+
@sda_state = nil
|
12
|
+
initialize_pins
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize_pins
|
16
|
+
LGPIO.gpio_claim_output(handle, LGPIO::SET_PULL_NONE, scl, LGPIO::HIGH)
|
17
|
+
LGPIO.gpio_claim_output(handle, LGPIO::SET_OPEN_DRAIN | LGPIO::SET_PULL_UP, sda, LGPIO::HIGH)
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_sda(value)
|
21
|
+
return if (@sda_state == value)
|
22
|
+
LGPIO.gpio_write(handle, sda, @sda_state = value)
|
23
|
+
end
|
24
|
+
|
25
|
+
def write_form(address)
|
26
|
+
(address << 1)
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_form(address)
|
30
|
+
(address << 1) | 0b00000001
|
31
|
+
end
|
32
|
+
|
33
|
+
def start
|
34
|
+
LGPIO.gpio_write(handle, sda, 0)
|
35
|
+
LGPIO.gpio_write(handle, scl, 0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
LGPIO.gpio_write(handle, sda, 0)
|
40
|
+
LGPIO.gpio_write(handle, scl, 1)
|
41
|
+
LGPIO.gpio_write(handle, sda, 1)
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_bit
|
45
|
+
set_sda(1)
|
46
|
+
LGPIO.gpio_write(handle, scl, 1)
|
47
|
+
bit = LGPIO.gpio_read(handle, sda)
|
48
|
+
LGPIO.gpio_write(handle, scl, 0)
|
49
|
+
bit
|
50
|
+
end
|
51
|
+
|
52
|
+
def write_bit(bit)
|
53
|
+
set_sda(bit)
|
54
|
+
LGPIO.gpio_write(handle, scl, 1)
|
55
|
+
LGPIO.gpio_write(handle, scl, 0)
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_byte(ack)
|
59
|
+
byte = 0
|
60
|
+
i = 0
|
61
|
+
while i < 8
|
62
|
+
byte = (byte << 1) | read_bit
|
63
|
+
i = i + 1
|
64
|
+
end
|
65
|
+
write_bit(ack ? 0 : 1)
|
66
|
+
byte
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_byte(byte)
|
70
|
+
i = 7
|
71
|
+
while i >= 0
|
72
|
+
write_bit (byte >> i) & 0b1
|
73
|
+
i = i - 1
|
74
|
+
end
|
75
|
+
# Return ACK (SDA pulled low) or NACK (SDA stayed high).
|
76
|
+
(read_bit == 0)
|
77
|
+
end
|
78
|
+
|
79
|
+
def read(address, length)
|
80
|
+
raise ArgumentError, "invalid I2C address: #{address}. Range is 0x08..0x77" unless VALID_ADDRESSES.include?(address)
|
81
|
+
raise ArgumentError, "invalid Integer for read length: #{length}" unless length.kind_of?(Integer)
|
82
|
+
|
83
|
+
start
|
84
|
+
ack = write_byte(read_form(address))
|
85
|
+
return nil unless ack
|
86
|
+
|
87
|
+
# Read length bytes, and ACK for all but the last one.
|
88
|
+
bytes = []
|
89
|
+
(length-1).times { bytes << read_byte(true) }
|
90
|
+
bytes << read_byte(false)
|
91
|
+
stop
|
92
|
+
|
93
|
+
bytes
|
94
|
+
end
|
95
|
+
|
96
|
+
def write(address, bytes)
|
97
|
+
raise ArgumentError, "invalid I2C address: #{address}. Range is 0x08..0x77" unless VALID_ADDRESSES.include?(address)
|
98
|
+
raise ArgumentError, "invalid byte Array to write: #{bytes}" unless bytes.kind_of?(Array)
|
99
|
+
|
100
|
+
start
|
101
|
+
write_byte(write_form(address))
|
102
|
+
bytes.each { |byte| write_byte(byte) }
|
103
|
+
stop
|
104
|
+
end
|
105
|
+
|
106
|
+
def search
|
107
|
+
found = []
|
108
|
+
VALID_ADDRESSES.each do |address|
|
109
|
+
start
|
110
|
+
# Device present if ACK received when we write its address to the bus.
|
111
|
+
found << address if write_byte(write_form(address))
|
112
|
+
stop
|
113
|
+
end
|
114
|
+
found
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|