lgpio 0.1.5 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|