uchip 1.0.0

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