lgpio 0.1.5 → 0.1.6

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.
@@ -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
@@ -0,0 +1,171 @@
1
+ module LGPIO
2
+ class OneWire
3
+ # Constants
4
+ READ_POWER_SUPPLY = 0xB4
5
+ CONVERT_T = 0x44
6
+ SEARCH_ROM = 0xF0
7
+ READ_ROM = 0x33
8
+ SKIP_ROM = 0xCC
9
+ MATCH_ROM = 0x55
10
+ ALARM_SEARCH = 0xEC
11
+ READ_SCRATCH = 0xBE
12
+ WRITE_SCRATCH = 0x4E
13
+ COPY_SCRATCH = 0x48
14
+ RECALL_EEPROM = 0xB8
15
+
16
+ attr_reader :handle, :gpio, :found_addresses
17
+
18
+ def initialize(handle, gpio)
19
+ @handle = handle
20
+ @gpio = gpio
21
+ @found_addresses = []
22
+ LGPIO.gpio_claim_output(handle, LGPIO::SET_OPEN_DRAIN | LGPIO::SET_PULL_UP, gpio, LGPIO::HIGH)
23
+ end
24
+
25
+ def reset
26
+ # LGPIO.one_wire_reset returns 0 if device present on bus.
27
+ return (LGPIO.one_wire_reset(handle, gpio) == 0)
28
+ end
29
+
30
+ def read(byte_count)
31
+ read_bytes = []
32
+ byte_count.times do
33
+ byte = 0b00000000
34
+ 8.times { |i| byte |= LGPIO.one_wire_bit_read(handle, gpio) << i }
35
+ read_bytes << byte
36
+ end
37
+ read_bytes
38
+ end
39
+
40
+ def write(byte_array, parasite: nil)
41
+ byte_array.each do |byte|
42
+ 8.times { |i| LGPIO.one_wire_bit_write(handle, gpio, (byte >> i) & 0b1) }
43
+ end
44
+ # Drive bus high to feed parasite capacitor after write.
45
+ LGPIO.gpio_write(handle, gpio, LGPIO::HIGH) if parasite
46
+ end
47
+
48
+ def search
49
+ branch_mask = 0
50
+ high_discrepancy = 0
51
+
52
+ loop do
53
+ reset
54
+ write [SEARCH_ROM]
55
+ result = search_pass(branch_mask)
56
+
57
+ address, high_discrepancy = parse_search_result(result)
58
+ @found_addresses << address
59
+
60
+ # No unsearched discrepancies left.
61
+ break if high_discrepancy == -1
62
+
63
+ # Force highest new discrepancy to be a 1 on the next search.
64
+ # i.e. Go as deep as possible into each branch found then back out.
65
+ #
66
+ branch_mask = branch_mask | (2 ** high_discrepancy)
67
+
68
+ # Clear bits above high_discrepancy so we don't repeat branches.
69
+ # When high_discrepancy < MSB of branch_mask, this moves us
70
+ # one node out, closer to the root, and finishing the search.
71
+ #
72
+ unset_mask = 0xFFFFFFFFFFFFFFFF >> (63 - high_discrepancy)
73
+ branch_mask = branch_mask & unset_mask
74
+ end
75
+ end
76
+
77
+ # Read a single 64-bit address and complement from the bus.
78
+ def search_pass(mask)
79
+ bytes = []
80
+
81
+ 8.times do |i|
82
+ addr = 0
83
+ comp = 0
84
+ 8.times do |j|
85
+ addr |= LGPIO.one_wire_bit_read(handle, gpio) << j
86
+ comp |= LGPIO.one_wire_bit_read(handle, gpio) << j
87
+
88
+ # A set (1) mask bit means we're searching a branch with that bit set.
89
+ # Force it to be 1 on this pass. Write 1 to both the bus and address bit.
90
+ #
91
+ # Don't change the complement bit from 0, Even if the bus said 0/0,
92
+ # send back 1/0, hiding known discrepancies, only sending new ones.
93
+ #
94
+ # Mask is a 64-bit number, not byte array.
95
+ if ((mask >> (i*8 + j)) & 0b1) == 1
96
+ LGPIO.one_wire_bit_write(handle, gpio, 1)
97
+ addr |= 1 << j
98
+
99
+ # Whether there was no "1-branch" marked for this bit, or there is no
100
+ # discrepancy at all, just echo address bit to the bus. We will
101
+ # compare addr/comp to find discrepancies for future passes.
102
+ else
103
+ LGPIO.one_wire_bit_write(handle, gpio, (addr >> j) & 0b1)
104
+ end
105
+ end
106
+ bytes << addr
107
+ bytes << comp
108
+ end
109
+
110
+ # 16 bytes, address and complement bytes interleaved LSByteFIRST.
111
+ # DON'T CHANGE! #split_search_result deals with it. denko uses #search_pass
112
+ # directly on Linux, but MCUs send it this format to save RAM. Always match it.
113
+ return bytes
114
+ end
115
+
116
+ def parse_search_result(result)
117
+ address, complement = split_search_result(result)
118
+
119
+ raise "OneWire device not connected, or disconnected during search" if (address & complement) > 0
120
+ raise "CRC error during OneWire search" unless OneWire.crc(address)
121
+
122
+ # Gives 0 at every discrepancy we didn't write 1 for on this pass.
123
+ new_discrepancies = address ^ complement
124
+
125
+ high_discrepancy = -1
126
+ (0..63).each { |i| high_discrepancy = i if new_discrepancies[i] == 0 }
127
+
128
+ [address, high_discrepancy]
129
+ end
130
+
131
+ # Convert interleaved address/complement bytes to 64-bit numbers.
132
+ def split_search_result(data)
133
+ address = 0
134
+ complement = 0
135
+ data.reverse.each_slice(2) do |comp_byte, addr_byte|
136
+ address = (address << 8) | addr_byte
137
+ complement = (complement << 8) | comp_byte
138
+ end
139
+ [address, complement]
140
+ end
141
+
142
+ # CRC Class Methods
143
+ def self.crc(data)
144
+ calculated, received = self.calculate_crc(data)
145
+ calculated == received
146
+ end
147
+
148
+ def self.calculate_crc(data)
149
+ if data.class == Integer
150
+ bytes = address_to_bytes(data)
151
+ else
152
+ bytes = data
153
+ end
154
+
155
+ crc = 0b00000000
156
+ bytes.take(bytes.length - 1).each do |byte|
157
+ for bit in (0..7)
158
+ xor = byte[bit] ^ crc[0]
159
+ crc = crc ^ ((xor * (2 ** 3)) | (xor * (2 ** 4)))
160
+ crc = crc >> 1
161
+ crc = crc | (xor * (2 ** 7))
162
+ end
163
+ end
164
+ [crc, bytes.last]
165
+ end
166
+
167
+ def self.address_to_bytes(address)
168
+ [address].pack('Q<').split("").map(&:ord)
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,109 @@
1
+ module LGPIO
2
+ class SPIBitBang
3
+ attr_reader :handle, :clock, :input, :output
4
+
5
+ def initialize(options={})
6
+ @handle = options[:handle]
7
+ @clock = options[:clock] || options[:sck] || options[:clk]
8
+ @input = options[:input] || options[:poci] || options[:miso]
9
+ @output = options[:output] || options[:pico] || options[:mosi]
10
+
11
+ raise ArgumentError, "a gpiochip :handle is required" unless @handle
12
+ raise ArgumentError, "either input/:poci/:miso OR :output/:pico/:mosi pin required" unless (@input || @output)
13
+ raise ArgumentError, ":clock/:sck/:clk pin required" unless @clock
14
+
15
+ @output_state = nil
16
+ initialize_pins
17
+ end
18
+
19
+ def initialize_pins
20
+ LGPIO.gpio_claim_output(handle, LGPIO::SET_PULL_NONE, clock, LGPIO::LOW)
21
+ LGPIO.gpio_claim_input(handle, LGPIO::SET_PULL_NONE, input) if input
22
+ LGPIO.gpio_claim_output(handle, LGPIO::SET_PULL_NONE, output, LGPIO::LOW) if output
23
+ end
24
+
25
+ def set_output(level)
26
+ return if (level == @output_state)
27
+ LGPIO.gpio_write(handle, output, @output_state = level)
28
+ end
29
+
30
+ def transfer_bit(write_bit, reading: false, mode: 0)
31
+ case mode
32
+ when 0
33
+ set_output(write_bit) if write_bit
34
+ LGPIO.gpio_write(handle, clock, 1)
35
+ read_bit = LGPIO.gpio_read(handle, input) if reading
36
+ LGPIO.gpio_write(handle, clock, 0)
37
+ when 1
38
+ LGPIO.gpio_write(handle, clock, 1)
39
+ set_output(write_bit) if write_bit
40
+ LGPIO.gpio_write(handle, clock, 0)
41
+ read_bit = LGPIO.gpio_read(handle, input) if reading
42
+ when 2
43
+ set_output(write_bit) if write_bit
44
+ LGPIO.gpio_write(handle, clock, 0)
45
+ read_bit = LGPIO.gpio_read(handle, input) if reading
46
+ LGPIO.gpio_write(handle, clock, 1)
47
+ when 3
48
+ LGPIO.gpio_write(handle, clock, 0)
49
+ set_output(write_bit) if write_bit
50
+ LGPIO.gpio_write(handle, clock, 1)
51
+ read_bit = LGPIO.gpio_read(handle, input) if reading
52
+ else
53
+ raise ArgumentError, "invalid SPI mode: #{mode} given"
54
+ end
55
+ return reading ? read_bit : nil
56
+ end
57
+
58
+ def transfer_byte(byte, reading: false, order: :msbfirst, mode: 0)
59
+ read_byte = reading ? 0 : nil
60
+
61
+ if (order == :msbfirst)
62
+ i = 7
63
+ while i >= 0
64
+ write_bit = byte ? (byte >> i) & 0b1 : nil
65
+ read_bit = transfer_bit(write_bit, mode: mode, reading: reading)
66
+ read_byte = (read_byte << 1) | read_bit if reading
67
+ i = i - 1
68
+ end
69
+ else
70
+ i = 0
71
+ while i < 8
72
+ write_bit = byte ? (byte >> i) : nil
73
+ read_bit = transfer_bit(write_bit, mode: mode, reading: reading)
74
+ read_byte = read_byte | (read_bit << i) if reading
75
+ i = i + 1
76
+ end
77
+ end
78
+
79
+ read_byte
80
+ end
81
+
82
+ def transfer(write: [], read: 0, select: nil, order: :msbfirst, mode: 0)
83
+ # Idle clock state depends on SPI mode.
84
+ case mode
85
+ when 0..1
86
+ LGPIO.gpio_write(handle, clock, 0)
87
+ when 2..3
88
+ LGPIO.gpio_write(handle, clock, 1)
89
+ else
90
+ raise ArgumentError, "invalid SPI mode: #{mode} given"
91
+ end
92
+ raise ArgumentError, "invalid Array for write: #{write}" unless write.kind_of?(Array)
93
+ raise ArgumentError, "invalid Integer for read: #{read}" unless read.kind_of?(Integer)
94
+
95
+ read_bytes = (read > 0) ? [] : nil
96
+ LGPIO.gpio_write(handle, select, 0) if select
97
+
98
+ i = 0
99
+ while (i < read) || (i < write.length)
100
+ read_byte = transfer_byte(write[i], reading: (i < read), order: order, mode: mode)
101
+ read_bytes << read_byte if read_byte
102
+ i = i + 1
103
+ end
104
+
105
+ LGPIO.gpio_write(handle, select, 1) if select
106
+ read_bytes
107
+ end
108
+ end
109
+ end
data/lib/lgpio/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module LGPIO
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end
data/lib/lgpio.rb CHANGED
@@ -1,7 +1,13 @@
1
1
  require_relative 'lgpio/lgpio'
2
2
  require_relative 'lgpio/version'
3
+
4
+ require_relative 'lgpio/i2c_bitbang'
5
+ require_relative 'lgpio/spi_bitbang'
6
+ require_relative 'lgpio/one_wire'
7
+
3
8
  require_relative 'lgpio/hardware_pwm'
4
9
  require_relative 'lgpio/positional_servo'
10
+ require_relative 'lgpio/infrared'
5
11
 
6
12
  module LGPIO
7
13
  LOW = 0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lgpio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - vickash
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-17 00:00:00.000000000 Z
11
+ date: 2024-09-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Use GPIO / PWM / I2C / SPI / UART on Linux SBCs in Ruby
14
14
  email: mail@vickash.com
@@ -24,19 +24,31 @@ files:
24
24
  - examples/bench_in.rb
25
25
  - examples/bench_out.rb
26
26
  - examples/blink.rb
27
+ - examples/dht.rb
27
28
  - examples/group_in.rb
28
29
  - examples/group_out.rb
30
+ - examples/hcsr04.rb
29
31
  - examples/i2c_aht10.rb
30
32
  - examples/i2c_aht10_zip.rb
33
+ - examples/i2c_bitbang-rb_aht10.rb
34
+ - examples/i2c_bitbang-rb_ssd1306_bench.rb
35
+ - examples/i2c_bitbang_aht10.rb
36
+ - examples/i2c_bitbang_search.rb
37
+ - examples/i2c_bitbang_ssd1306_bench.rb
31
38
  - examples/i2c_ssd1306_bench.rb
39
+ - examples/infrared.rb
32
40
  - examples/momentary.rb
41
+ - examples/one_wire_ds18b20.rb
42
+ - examples/one_wire_search.rb
33
43
  - examples/pwm.rb
34
44
  - examples/reports.rb
35
45
  - examples/rotary_encoder.rb
36
46
  - examples/rotary_encoder_led.rb
37
47
  - examples/servo.rb
48
+ - examples/spi_bitbang_loopback.rb
49
+ - examples/spi_bitbang_ssd1306_bench.rb
50
+ - examples/spi_bitbang_ssd1306_sim_bench.rb
38
51
  - examples/spi_loopback.rb
39
- - examples/spi_read.rb
40
52
  - examples/spi_ws2812.rb
41
53
  - examples/spi_ws2812_bounce.rb
42
54
  - examples/wave.rb
@@ -45,7 +57,11 @@ files:
45
57
  - lgpio.gemspec
46
58
  - lib/lgpio.rb
47
59
  - lib/lgpio/hardware_pwm.rb
60
+ - lib/lgpio/i2c_bitbang.rb
61
+ - lib/lgpio/infrared.rb
62
+ - lib/lgpio/one_wire.rb
48
63
  - lib/lgpio/positional_servo.rb
64
+ - lib/lgpio/spi_bitbang.rb
49
65
  - lib/lgpio/version.rb
50
66
  homepage: https://github.com/denko-rb/lgpio
51
67
  licenses:
data/examples/spi_read.rb DELETED
@@ -1,14 +0,0 @@
1
- require 'lgpio'
2
-
3
- SPI_DEV = 1
4
- SPI_CHAN = 0
5
- SPI_MODE = 0
6
- SPI_BAUD = 1_000_000
7
-
8
- spi_handle = LGPIO.spi_open(SPI_DEV, SPI_CHAN, SPI_BAUD, SPI_MODE)
9
-
10
- # Pull MISO high or low. High gives array of 255, low array of 0.
11
- rx_bytes = LGPIO.spi_read(spi_handle, 8)
12
- puts "RX bytes: #{rx_bytes.inspect}"
13
-
14
- LGPIO.spi_close(spi_handle)