uchip 1.0.0

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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b8066c01dd4b77f8fb6b0985b22cabb9e18580dda3b92e9281fcf1296aa4c841
4
+ data.tar.gz: d38dd83a5f1217a81fac540ccfcd93d204edb05d3e104fd3293381e8435aa93c
5
+ SHA512:
6
+ metadata.gz: 8319df9cd48dfa190eb94f4ba38e44652d69d6498cea40ee5f3fb5164f00244f77dc3727ec5a7ed3c8e16230b30d6291f93617191ed226a857eca24eab74f928
7
+ data.tar.gz: 539c3c4200f155f341d03d227f199586918eac71e768e24edb16c30d89581f9422e871336f314db03769c668495ba2c12fd1fb5b7b6b3d19bfcaa19750fdf6cc
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Aaron Patterson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # UChip
2
+
3
+ This is a library for controlling Microchip Chips. Specifically the MCP2221 and
4
+ MCP2221A.
5
+
6
+ ## Examples
7
+
8
+ See the `examples` folder for more examples, but here is an example of using
9
+ GP0 as a GPIO:
10
+
11
+
12
+ ```ruby
13
+ require "uchip/mcp2221"
14
+
15
+ def hit_bell pin
16
+ pin.value = 0
17
+ pin.value = 1
18
+ sleep 0.009
19
+ pin.value = 0
20
+ end
21
+
22
+ # Find the first connected chip
23
+ chip = UChip::MCP2221.first || raise("Couldn't find the chip!")
24
+
25
+ pin = chip.pin 0
26
+ pin.output!
27
+
28
+ loop do
29
+ hit_bell pin
30
+ sleep 2
31
+ end
32
+ ```
33
+
34
+ ## Problems
35
+
36
+ Right now this library doesn't support DAC or ADC, but it should be trivial to
37
+ implement.
@@ -0,0 +1,15 @@
1
+ require "uchip/mcp2221"
2
+
3
+ # Find the first connected chip
4
+ chip = UChip::MCP2221.first || raise("Couldn't find the chip!")
5
+
6
+ pin = chip.pin 0
7
+ pin.output!
8
+
9
+ loop do
10
+ pin.value = 0
11
+ pin.value = 1
12
+ sleep 0.01
13
+ pin.value = 0
14
+ sleep 5
15
+ end
@@ -0,0 +1,65 @@
1
+ require "uchip/mcp2221"
2
+
3
+ # Example to read from a PCF8563 real time clock
4
+ # https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf
5
+
6
+ # These are just encoding and decoding routines for data on the RTC.
7
+
8
+ def bcd2dec val
9
+ ((val >> 4) * 10) + (val & 0xF)
10
+ end
11
+
12
+ def dec2bcd val
13
+ ((val / 10) << 4) | (val % 10)
14
+ end
15
+
16
+ def time2bcd time
17
+ [ time.sec,
18
+ time.min,
19
+ time.hour,
20
+ time.day,
21
+ time.wday,
22
+ time.month,
23
+ time.year % 2000
24
+ ].map { |x| dec2bcd(x) }
25
+ end
26
+
27
+ def bcd2time bytes
28
+ seconds, minutes, hours, days, weekdays, c_months, years = bytes
29
+ Time.local bcd2dec(years) + 2000,
30
+ bcd2dec(c_months & 0xF) + 1, # don't care about century flag
31
+ bcd2dec(days & 0x3F),
32
+ bcd2dec(hours & 0x3F),
33
+ bcd2dec(minutes & 0x7F),
34
+ bcd2dec(seconds & 0x7F)
35
+ end
36
+
37
+ # Find the first connected chip
38
+ chip = UChip::MCP2221.first || raise("Couldn't find the chip!")
39
+
40
+ # The write address is 0xA2, read address is 0xA3, so 0x51 (0xA3 >> 1) is
41
+ # where we'll request a proxy
42
+ i2c = chip.i2c_on 0x51
43
+
44
+ # Reset the I2C engine. I've noticed sometimes the engine gets messed up, so
45
+ # just start off by putting it in a known state.
46
+ i2c.cancel
47
+
48
+ # Write the current time to the RTC, starting at address 0x2
49
+ i2c.write 0x2.chr + time2bcd(Time.now).pack("C7")
50
+
51
+ loop do
52
+ # Write 0 bytes at address 0x2. This moves the pointer to the seconds location
53
+ # inside the RTC.
54
+ i2c.write 0x2.chr
55
+
56
+ # Read 7 bytes
57
+ buf = i2c.read 7
58
+ p bcd2time(buf.bytes)
59
+ sleep 1
60
+ rescue UChip::MCP2221::EmptyResponse
61
+ # if the chip gets messed up, reset the i2c engine and retry
62
+ puts "oh no"
63
+ i2c.cancel
64
+ retry
65
+ end
@@ -0,0 +1,17 @@
1
+ require "uchip/mcp2221"
2
+
3
+ # This example changes the "startup" GPIO settings of the chip.
4
+
5
+ # Find the first connected chip
6
+ chip = UChip::MCP2221.first || raise("Couldn't find the chip!")
7
+
8
+ # Get the GPIO settings
9
+ settings = chip.gp_settings
10
+
11
+ settings.gp0_designation = 0 # We want to use this pin as a GPIO
12
+ settings.gp0_output_value = 0 # Set the default value to 0
13
+ settings.gp0_direction = 0 # Set the direction to output
14
+
15
+ # Write the settings to the chip. Next time the chip starts, the default
16
+ # settings for GP0 will reflect these settings
17
+ chip.gp_settings = settings
@@ -0,0 +1,392 @@
1
+ require 'myhidapi'
2
+
3
+ module UChip
4
+ class MCP2221
5
+ extend Enumerable
6
+
7
+ class Error < StandardError; end
8
+ class CommandNotSupported < Error; end
9
+ class Busy < Error; end
10
+ class EmptyResponse < Error; end
11
+ class GPIOConfigurationError < Error; end
12
+
13
+ def self.each
14
+ MyHIDAPI.enumerate(0x04d8, 0x00dd).each { |dev| yield new dev }
15
+ end
16
+
17
+ def initialize dev
18
+ @dev = dev
19
+ @handle = dev.open
20
+ end
21
+
22
+ def usb_manufacturer
23
+ handle.manufacturer
24
+ end
25
+
26
+ def usb_product
27
+ handle.product
28
+ end
29
+
30
+ def read_flash section
31
+ buf = pad [0xB0, section].pack('C*')
32
+ write_request buf
33
+ check_response read_response, 0xB0
34
+ end
35
+
36
+ def write_flash section, bytes
37
+ buf = pad ([0xB1, section] + bytes).pack('C*')
38
+ write_request buf
39
+ check_response read_response, 0xB1
40
+ end
41
+
42
+ class ChipSettings
43
+ def initialize bytes
44
+ @bytes = bytes
45
+ end
46
+
47
+ def inspect
48
+ to_s.sub(/>$/, " #{decode(@bytes).inspect}>")
49
+ end
50
+
51
+ BIT_FIELDS = []
52
+ def self.bool_attr_accessor name, index, offset
53
+ bit_attr_accesor name, index, offset, 0x1
54
+ end
55
+
56
+ def self.bit_attr_accesor name, index, offset, mask
57
+ BIT_FIELDS << name
58
+ define_method(name) do
59
+ (bytes[index] >> offset) & mask
60
+ end
61
+
62
+ define_method(:"#{name}=") do |v|
63
+ bytes[index] &= ~(mask << offset)
64
+ bytes[index] |= ((mask & v) << offset)
65
+ end
66
+ end
67
+
68
+ # bytes[i], shift, mask
69
+ bool_attr_accessor :cdc, 0, 7
70
+ bool_attr_accessor :led_uart_rx, 0, 6
71
+ bool_attr_accessor :led_uart_tx, 0, 5
72
+ bool_attr_accessor :led_i2c, 0, 4
73
+ bool_attr_accessor :sspnd, 0, 3
74
+ bool_attr_accessor :usbcfg, 0, 2
75
+ bit_attr_accesor :security, 0, 0, 0x3
76
+ bit_attr_accesor :clock_output_divider, 1, 0, 0x1F
77
+ bit_attr_accesor :dac_reference_voltage, 2, 6, 0x3
78
+ bool_attr_accessor :dac_reference_option, 2, 5
79
+ bit_attr_accesor :dac_power_up_value, 2, 0, 0x1F
80
+ bool_attr_accessor :interrupt_detection_negative, 3, 6
81
+ bool_attr_accessor :interrupt_detection_positive, 3, 5
82
+ bit_attr_accesor :adc_reference_voltage, 3, 3, 0x3
83
+ bool_attr_accessor :dac_voltage, 3, 2
84
+
85
+ def decode bytes
86
+ BIT_FIELDS.each_with_object({}) { |n, o| o[n] = send n }.merge({
87
+ :vid => bytes[4] + (bytes[5] << 8),
88
+ :pid => bytes[6] + (bytes[7] << 8),
89
+ :usb_power_attributes => bytes[8],
90
+ :usb_requested_mas => bytes[9],
91
+ })
92
+ end
93
+
94
+ attr_reader :bytes
95
+ end
96
+
97
+ module FlashData
98
+ CHIP_SETTINGS = 0x00
99
+ GP_SETTINGS = 0x01
100
+ MANUFACTURER = 0x02
101
+ PRODUCT = 0x03
102
+ SERIAL_NUMBER = 0x04
103
+ FACTORY_SERIAL_NUMBER = 0x05
104
+ end
105
+
106
+ def chip_settings
107
+ buf = read_flash(FlashData::CHIP_SETTINGS).bytes
108
+ .drop(2) # response header
109
+ .drop(2) # not care (according to data sheet)
110
+ .first(10)
111
+ ChipSettings.new buf
112
+ end
113
+
114
+ def chip_settings= settings
115
+ write_flash FlashData::CHIP_SETTINGS, settings.bytes
116
+ end
117
+
118
+ def gp_settings= settings
119
+ write_flash FlashData::GP_SETTINGS, settings.bytes
120
+ end
121
+
122
+ class GPSettings
123
+ attr_reader :bytes
124
+
125
+ def initialize bytes
126
+ @bytes = bytes
127
+ end
128
+
129
+ def inspect
130
+ to_s.sub(/>$/, " #{decode(@bytes).inspect}>")
131
+ end
132
+
133
+ def output_value_at i
134
+ (bytes[i] >> 4) & 0x1
135
+ end
136
+
137
+ def set_output_value_at i, v
138
+ bytes[i] &= ~(1 << 4)
139
+ bytes[i] |= (1 & v) << 4
140
+ end
141
+
142
+ def direction_at i
143
+ (bytes[i] >> 3) & 0x1
144
+ end
145
+
146
+ def set_direction_at i, v
147
+ bytes[i] &= ~(1 << 3)
148
+ bytes[i] |= (1 & v) << 3
149
+ end
150
+
151
+ def designation_at i
152
+ (bytes[i] >> 0) & 0x3
153
+ end
154
+
155
+ def set_designation_at i, v
156
+ bytes[i] &= ~(0x3 << 0)
157
+ bytes[i] |= (0x3 & v) << 0
158
+ end
159
+
160
+ 4.times { |i|
161
+ [:output_value, :direction, :designation].each { |n|
162
+ define_method("gp#{i}_#{n}") { send("#{n}_at", i) }
163
+ define_method("gp#{i}_#{n}=") { |v| send("set_#{n}_at", i, v) }
164
+ }
165
+ }
166
+
167
+ def decode bytes
168
+ 4.times.each_with_object({}) { |i, o|
169
+ o[:"gp#{i}_output_value"] = output_value_at(i)
170
+ o[:"gp#{i}_direction"] = direction_at(i)
171
+ o[:"gp#{i}_designation"] = designation_at(i)
172
+ }
173
+ end
174
+ end
175
+
176
+ def gp_settings
177
+ buf = read_flash(FlashData::GP_SETTINGS).bytes
178
+ .drop(2) # response header
179
+ .drop(2) # structure length, don't care
180
+ .first(4)
181
+ GPSettings.new buf
182
+ end
183
+
184
+ def manufacturer
185
+ byte_count, _, *rest = read_flash(FlashData::MANUFACTURER).bytes
186
+ .drop(2) # response header
187
+ rest[0, byte_count - 2].pack('U*')
188
+ end
189
+
190
+ def product
191
+ byte_count, _, *rest = read_flash(FlashData::PRODUCT).bytes
192
+ .drop(2) # response header
193
+ rest[0, byte_count - 2].pack('U*')
194
+ end
195
+
196
+ def serial_number
197
+ byte_count, _, *rest = read_flash(FlashData::SERIAL_NUMBER).bytes
198
+ .drop(2) # response header
199
+ rest[0, byte_count - 2].pack('U*')
200
+ end
201
+
202
+ def factory_serial_number
203
+ byte_count, _, *rest = read_flash(FlashData::FACTORY_SERIAL_NUMBER).bytes
204
+ .drop(2) # response header
205
+ rest[0, byte_count - 2].pack('U*')
206
+ end
207
+
208
+ def reset
209
+ buf = pad ([0x70, 0xAB, 0xCD, 0xEF]).pack('C*')
210
+ write_request buf
211
+ end
212
+
213
+ def i2c_write address, bytes
214
+ send_i2c_command 0x90, address, bytes.bytesize, bytes
215
+ end
216
+
217
+ def i2c_write_no_stop address, bytes
218
+ send_i2c_command 0x94, address, bytes.bytesize, bytes
219
+ end
220
+
221
+ def i2c_read_start address, length
222
+ send_i2c_command 0x91, address, length, "".b
223
+ end
224
+
225
+ def i2c_read_repeated_start address, length
226
+ send_i2c_command 0x93, address, length, "".b
227
+ end
228
+
229
+ def i2c_read
230
+ buf = pad 0x40.chr
231
+ write_request buf
232
+ buf = check_response read_response, 0x40
233
+ len = buf[3].ord
234
+ buf[4, len]
235
+ end
236
+
237
+ def i2c_cancel
238
+ buf = pad [0x10, 0x0, 0x10].pack('C*')
239
+ write_request buf
240
+ check_response read_response, 0x10
241
+ end
242
+
243
+ class I2CProxy
244
+ def initialize address, handler
245
+ @read_address = (address << 1) | 1
246
+ @write_address = address << 1
247
+ @handler = handler
248
+ end
249
+
250
+ def cancel; @handler.i2c_cancel; end
251
+
252
+ def write buf
253
+ @handler.i2c_write @write_address, buf
254
+ end
255
+
256
+ def read size
257
+ @handler.i2c_read_start @read_address, 8
258
+ @handler.i2c_read
259
+ end
260
+ end
261
+
262
+ def i2c_on address
263
+ I2CProxy.new address, self
264
+ end
265
+
266
+ class Pin < Struct.new :chip, :number
267
+ def output!
268
+ chip.configure_gpio number, :output
269
+ end
270
+
271
+ def value
272
+ chip.gpio_value number
273
+ end
274
+
275
+ def value= v
276
+ chip.set_gpio_value number, v
277
+ end
278
+ end
279
+
280
+ def pin number
281
+ Pin.new self, number
282
+ end
283
+
284
+ def gpio_value pin
285
+ cmd = 0x51
286
+ write_request pad cmd.chr
287
+ buf = check_response read_response, cmd
288
+ buf[2 + (pin * 2), 1].ord
289
+ end
290
+
291
+ def configure_gpio pin, mode, default = 0
292
+ settings = default << 4
293
+ case mode
294
+ when :input then mode = (1 << 3)
295
+ when :output then mode = 0
296
+ end
297
+ settings |= mode
298
+
299
+ # Read the current pin settings
300
+ bytes = sram.gp_settings.bytes
301
+ bytes[pin] = settings
302
+
303
+ cmd = 0x60
304
+ buf = pad ([cmd, 0x0,
305
+ 0x0, # clock output divider
306
+ 0x0, # DAC voltage reference
307
+ 0x0, # DAC output value
308
+ 0x0, # ADC voltage reference
309
+ 0x0, # interrupt detection
310
+ (1 << 7), # Alter GPIO config
311
+ ] + bytes).pack('C*')
312
+ write_request buf
313
+ check_response read_response, cmd
314
+ end
315
+
316
+ def set_gpio_value pin, value
317
+ cmd = 0x50
318
+ pin_values = [
319
+ 0x0, # Alter output
320
+ 0x0, # Output Value
321
+ 0x0, # Alter direction
322
+ 0x0 # Direction value
323
+ ] * 4
324
+ pin_values[(pin * 4)] = 0x1
325
+ pin_values[(pin * 4) + 1] = (value & 0x1)
326
+ buf = pad ([cmd, 0x0] + pin_values).pack('C*')
327
+ write_request buf
328
+ val = check_response(read_response, cmd)[3 + (pin * 2)].ord
329
+ if val == 0xEE
330
+ raise GPIOConfigurationError, "Pin #{pin} not configured as GPIO"
331
+ else
332
+ val
333
+ end
334
+ end
335
+
336
+ class SRAM
337
+ attr_reader :chip_settings, :gp_settings, :password
338
+
339
+ def initialize chip_settings, password, gp_settings
340
+ @chip_settings = chip_settings
341
+ @password = password
342
+ @gp_settings = gp_settings
343
+ end
344
+ end
345
+
346
+ def sram
347
+ cmd = 0x61
348
+ buf = pad cmd.chr
349
+ write_request buf
350
+ buf = check_response read_response, cmd
351
+ cs = ChipSettings.new buf[4, 10].bytes
352
+ pw = buf[14, 8]
353
+ gp = GPSettings.new buf[22, 4].bytes
354
+ SRAM.new cs, pw, gp
355
+ end
356
+
357
+ private
358
+
359
+ def send_i2c_command cmd, address, length, bytes
360
+ buf = pad [cmd, length & 0xFF, (length >> 16) & 0xFF, address].pack('C*') + bytes
361
+ write_request buf
362
+ check_response read_response, cmd
363
+ end
364
+
365
+ def pad buf
366
+ buf << ("\x0".b * (64 - buf.bytesize))
367
+ end
368
+
369
+ def check_response buf, type
370
+ raise Error, buf unless buf[0].ord == type
371
+ raise Busy, buf unless buf[1].ord == 0
372
+ buf
373
+ end
374
+
375
+ def read_response
376
+ # 300 ms timeout
377
+ #return @handle.read 64
378
+ @handle.read_timeout(64, 30) || raise(EmptyResponse)
379
+ end
380
+
381
+ def write_request buf
382
+ retries = 0
383
+ loop do
384
+ break if handle.write buf
385
+ retries += 1
386
+ raise "Too many retries" if retries > 3
387
+ end
388
+ end
389
+
390
+ attr_reader :handle
391
+ end
392
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |s|
4
+ s.platform = Gem::Platform::RUBY
5
+ s.name = "uchip"
6
+ s.version = "1.0.0"
7
+ s.summary = "Gem for controlling MCP2221a"
8
+ s.description = "This is a gem for controlling the MCP2221a chip over USB"
9
+
10
+ s.license = "MIT"
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.require_path = 'lib'
14
+
15
+ s.author = "Aaron Patterson"
16
+ s.email = "tenderlove@ruby-lang.org"
17
+ s.homepage = "https://github.com/tenderlove/uchip"
18
+
19
+ s.add_dependency "myhidapi", "~> 1.0"
20
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uchip
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Patterson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: myhidapi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ description: This is a gem for controlling the MCP2221a chip over USB
28
+ email: tenderlove@ruby-lang.org
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - LICENSE
34
+ - README.md
35
+ - examples/gpio.rb
36
+ - examples/pcf8563.rb
37
+ - examples/set_gpio.rb
38
+ - lib/uchip/mcp2221.rb
39
+ - uchip.gemspec
40
+ homepage: https://github.com/tenderlove/uchip
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.2.0.rc.1
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Gem for controlling MCP2221a
63
+ test_files: []