lgpio 0.1.5 → 0.1.7

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