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