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
@@ -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
- // Set up a queue for up to 2**16 GPIO reports.
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 += 1;
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 += 1;
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
- if (popped){
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 / Servo / Wave
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
  }
@@ -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("#{path}duty_cycle", 'w') { |f| f.write("0") }
45
+ File.open(duty_path, 'w') { |f| f.write("0") }
34
46
  end
35
- File.open("#{path}period", 'w') { |f| f.write(p) }
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("#{path}duty_cycle", 'w') { |f| f.write(d_ns) }
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("#{path}enable", 'w') { |f| f.write("0") }
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("#{path}enable", 'w') { |f| f.write("1") }
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
@@ -0,0 +1,8 @@
1
+ module LGPIO
2
+ class Infrared < HardwarePWM
3
+ def transmit(pulses, duty: 33.333)
4
+ self.duty_percent = duty
5
+ tx_wave_ook(duty_path, @duty.to_s, pulses)
6
+ end
7
+ end
8
+ end