-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmcp2221a.go
2720 lines (2252 loc) · 82.7 KB
/
mcp2221a.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Package mcp2221a provides a high-level interface to the Microchip MCP2221A
// USB to GPIO/I²C/UART protocol converter. The physical GPIO and I²C modules
// are implemented as USB HID-class devices, while the UART module is USB CDC.
// This package only supports the USB HID-class devices (GPIO/I²C) and all of
// the functions associated with them (ADC, DAC, SRAM, and flash memory).
//
// Datasheet: http://ww1.microchip.com/downloads/en/devicedoc/20005565b.pdf
package mcp2221a
import (
"fmt"
"log"
"math"
"time"
"unicode/utf16"
usb "github.com/karalabe/hid"
)
// Constants associated with package version.
const (
VersionPkg = "mcp2221a"
VersionMaj = 0
VersionMin = 3
VersionPch = 1
)
// Version returns the SemVer-compatible version string of this package.
func Version() string {
return fmt.Sprintf("%d.%d.%d", VersionMaj, VersionMin, VersionPch)
}
// PackageVersion returns the descriptive version string of this package.
func PackageVersion() string {
return fmt.Sprintf("%s v%s", VersionPkg, Version())
}
// VID and PID are the official vendor and product identifiers assigned by the
// USB-IF.
const (
VID = 0x04D8 // 16-bit vendor ID for Microchip Technology Inc.
PID = 0x00DD // 16-bit product ID for the Microchip MCP2221A.
//VID = 0x6f88 // custom-defined 16-bit vendor ID
//PID = 0x04d8 // custom-defined 16-bit product ID
)
// MsgSz is the size (in bytes) of all command and response messages.
const MsgSz = 64
// ClkHz is the internal clock frequency of the MCP2221A.
const ClkHz = 12000000
// WordSet and WordClr are the logical true and false values for a single word
// (byte) in a message.
const (
WordSet byte = 0xFF // All bits set
WordClr byte = 0x00 // All bits clear
)
// makeMsg creates a new zero'd slice with required length of command and
// response messages, both of which are always 64 bytes.
func makeMsg() []byte { return make([]byte, MsgSz) }
// logMsg pretty-prints a given byte slice using the default log object. Each
// element is printed on its own line with the following format in columns of
// uniform-width:
// IDX: DEC {0xHEX} [0bBIN]
func LogMsg(buf []byte) {
if nil == buf || 0 == len(buf) {
return
}
// calculate the number of digits in the final slice index
n := int(math.Floor(math.Log10(float64(len(buf)-1)))) + 1
for i, b := range buf {
log.Printf("%*d: %3d {0x%02X} [0b%08b]", n, i, b, b, b)
}
}
// VRef represents one of the enumerated constants that can be used as reference
// voltage for both the ADC and DAC modules.
type VRef byte
// Constants for enumerated reference voltage values used by the settings
// structure read from and written to SRAM and flash.
const (
VRefDefault VRef = 0x00 // Default (Vdd)
VRefVdd VRef = 0x00 // Vdd
VRef4p096 VRef = 0x07 // 4.096 V
VRef2p048 VRef = 0x05 // 2.048 V
VRef1p024 VRef = 0x03 // 1.024 V
VRefOff VRef = 0x01 // reference voltage disabled
)
// isVRefValid verifies the given VRef v is one of the recognized enumerated
// reference voltage values.
func isVRefValid(v VRef) bool {
return (0 == v) || ((0x1 == (v & 0x1)) && (v < 0x08))
}
// chanADC maps GPIO pin (0-3) to its respective ADC channel (0-2). if the pin
// does not have an associated ADC channel, the key is not defined.
var chanADC = map[byte]byte{1: 0, 2: 1, 3: 2}
// outpDAC maps GPIO pin (0-3) to its respective DAC output (0-1). if the pin
// does not have an associated DAC output, the key is not defined.
var outpDAC = map[byte]byte{2: 0, 3: 1}
// Constants for all recognized commands (and responses). These are sent as the
// first word in all command messages, and are echoed back as the first word in
// all response messages.
const (
cmdStatus byte = 0x10
cmdSetParams byte = 0x10
cmdFlashRead byte = 0xB0
cmdFlashWrite byte = 0xB1
cmdFlashPasswd byte = 0xB2
cmdI2CWrite byte = 0x90
cmdI2CWriteRepStart byte = 0x92
cmdI2CWriteNoStop byte = 0x94
cmdI2CRead byte = 0x91
cmdI2CReadRepStart byte = 0x93
cmdI2CReadGetData byte = 0x40
cmdGPIOSet byte = 0x50
cmdGPIOGet byte = 0x51
cmdSRAMSet byte = 0x60
cmdSRAMGet byte = 0x61
cmdReset byte = 0x70
)
// -----------------------------------------------------------------------------
// -- DEVICE -------------------------------------------------------- [start] --
//
// MCP2221A is the primary object used for interacting with the device and all
// of its modules.
// The struct contains a pointer to an opened HIDAPI device through which all
// USB communication occurs. However, the HIDAPI device should not be used
// directly, as communication should be performed through one of the respective
// on-chip modules.
// If multiple MCP2221A devices are connected to the host PC, the index of the
// desired target can be determined with AttachedDevices() and passed to New().
// An index of 0 will use the first device found.
// Pointers to structs representing each of the chip's modules are exported for
// interacting with that component of the device.
// Call Close() on the device when finished to also close the USB connection.
type MCP2221A struct {
Device *usb.Device
Index byte
VID uint16
PID uint16
// Locked indicates if the device is permanently locked, preventing
// write-access to the flash memory module.
// It is not possible in any way to unlock the device if this is set. :(
locked bool
// configuration storage interfaces are not exported because incorrect usage
// can leave the device in an unusable state, and exposing their methods
// confuses or pollutes the API by offering multiple interfaces through which
// configuration can be performed. therefore, only the configuration methods
// explicitly exported by the individual components may be used. simple.
sram *sram // volatile active settings, not restored on startup/reset
flash *flash // non-volatile inactive settings, restored on startup/reset
// each of the on-chip modules acting as primary functional interfaces.
GPIO *GPIO // 4x GPIO pins, each also have unique special functions
ADC *ADC // 3x 10-bit analog-to-digital converter
DAC *DAC // 1x 5-bit digital-to-analog converter (avail on 2 pins)
Alt *Alt // special-purpose GP alternate/dedicated functions
I2C *I2C // dedicated I²C SDA/SCL pins, up to 400 kHz
}
// AttachedDevices returns a slice of all connected USB HID device descriptors
// matching the given VID and PID. The order of devices in the returned slice
// will always be the same, so the index of the devices in this slice can be
// used as index parameter to New() and openUSBDevice().
//
// Returns an empty slice if no devices were found. See the hid package
// documentation for details on inspecting the returned objects.
func AttachedDevices(vid uint16, pid uint16) []usb.DeviceInfo {
var info []usb.DeviceInfo
for _, i := range usb.Enumerate(vid, pid) {
info = append(info, i)
}
return info
}
// openUSBDevice returns an opened USB HID device descriptor with the given vid
// and pid and which enumerates itself on the USB host at index idx (an index of
// 0 will use the first device found).
//
// Returns nil and an error if the given index is out of range (including when
// no devices matching vid/pid were found), or if the USB HID device could not
// be claimed or opened.
func openUSBDevice(idx byte, vid uint16, pid uint16) (*usb.Device, error) {
info := AttachedDevices(vid, pid)
if int(idx) >= len(info) {
return nil, fmt.Errorf("device index %d out of range [0, %d]", idx, len(info)-1)
}
var (
dev *usb.Device
err error
)
if dev, err = info[idx].Open(); nil != err {
return nil, fmt.Errorf("Open(): %v", err)
}
return dev, nil
}
// New returns a new MCP2221A object with the given VID and PID, enumerated at
// the given index (an index of 0 will use the first device found).
//
// Returns an error if index is out of range (according to AttachedDevices()) or
// if the USB HID device could not be claimed or opened.
func New(idx byte, vid uint16, pid uint16) (*MCP2221A, error) {
var (
dev *usb.Device
err error
)
if dev, err = openUSBDevice(idx, vid, pid); nil != err {
return nil, fmt.Errorf("openUSBDevice(): %v", err)
}
mcp := &MCP2221A{
Device: dev,
Index: idx,
VID: vid,
PID: pid,
}
// each module embeds the common *MCP2221A instance so that the modules can
// refer to each others' functions.
mcp.sram, mcp.GPIO, mcp.ADC, mcp.DAC, mcp.I2C =
&sram{mcp}, &GPIO{mcp}, &ADC{mcp}, &DAC{mcp}, &I2C{mcp}
// configure the write-access flag as disabled by default until we've read the
// actual settings from flash memory.
mcp.flash = &flash{mcp, false}
// the Alt struct embeds the common *MCP2221A instance, but also has several
// other fields that need the same reference.
mcp.Alt = &Alt{
MCP2221A: mcp,
// each of the special-purpose modules also embeds the common *MCP2221A.
SUSPND: &SUSPND{mcp},
CLKOUT: &CLKOUT{mcp},
USBCFG: &USBCFG{mcp},
INTCHG: &INTCHG{mcp},
LEDI2C: &LEDI2C{mcp},
LEDURX: &LEDURX{mcp},
LEDUTX: &LEDUTX{mcp},
}
// dismiss any pending I2C state machine status in case a prior session was
// not terminated gracefully.
_ = mcp.I2C.Cancel()
// initialize the device locked flag and flash write-access flag based on the
// chip security settings stored in flash memory.
if sec, err := mcp.flash.chipSecurity(); nil != err {
return nil, fmt.Errorf("flash.chipSecurity(): %v", err)
} else {
mcp.locked, mcp.flash.writeable = unlockFlags(sec)
}
return mcp, nil
}
// valid verifies the receiver and USB HID device are both not nil.
//
// Returns false with a descriptive error if any required field is nil.
func (mcp *MCP2221A) valid() (bool, error) {
if nil == mcp {
return false, fmt.Errorf("nil MCP2221A")
}
if nil == mcp.Device {
return false, fmt.Errorf("nil USB HID device")
}
return true, nil
}
// Close will clean up any resources and close the USB HID connection.
//
// Returns an error if the USB HID device is invalid or failed to close
// gracefully.
func (mcp *MCP2221A) Close() error {
if ok, err := mcp.valid(); !ok {
return err
}
// don't leave any I2C state machine status active
_ = mcp.I2C.Cancel()
mcp.locked, mcp.flash.writeable = true, false
if err := mcp.Device.Close(); nil != err {
return err
}
return nil
}
// send transmits an MCP2221A command message and returns the response message.
// The data argument is a byte slice created by makeMsg(), and the cmd argument
// is one of the recognized command byte constants. The cmd byte is inserted
// into the slice at the appropriate position automatically.
//
// A nil slice is returned with an error if the receiver is invalid or if the
// USB HID device could not be written to or read from.
// If any data was successfully read from the USB HID device, then that data
// slice is returned along with an error if fewer than expected bytes were
// received or if the reserved status byte (common to all response messages)
// does not indicate success.
// A nil slice and nil error are returned if the reset command is received and
// successfully transmitted.
func (mcp *MCP2221A) send(cmd byte, data []byte) ([]byte, error) {
if ok, err := mcp.valid(); !ok {
return nil, err
}
data[0] = cmd
if _, err := mcp.Device.Write(data); nil != err {
return nil, fmt.Errorf("Write([cmd=0x%02X]): %v", cmd, err)
}
// logMsg(data)
if cmdReset == cmd {
// reset is the only command that does not have a response packet
return nil, nil
}
rsp := makeMsg()
if recv, err := mcp.Device.Read(rsp); nil != err {
return nil, fmt.Errorf("Read([cmd=0x%02X]): %v", cmd, err)
} else {
if recv < MsgSz {
return rsp, fmt.Errorf("Read([cmd=0x%02X]): short read (%d of %d bytes)", cmd, recv, MsgSz)
}
if rsp[0] != cmd || rsp[1] != WordClr {
return rsp, fmt.Errorf("Read([cmd=0x%02X]): command failed", cmd)
}
}
return rsp, nil
}
// Reset sends a reset command and then attempts to reopen a connection to the
// same USB HID device within a given timeout duration.
//
// Returns an error if the receiver is invalid, the reset command could not be
// sent, or if the device could not be reopened before the given timeout period.
func (mcp *MCP2221A) Reset(timeout time.Duration) error {
if ok, err := mcp.valid(); !ok {
return err
}
cmd := makeMsg()
cmd[1] = 0xAB
cmd[2] = 0xCD
cmd[3] = 0xEF
if _, err := mcp.send(cmdReset, cmd); nil != err {
return fmt.Errorf("send(): %v", err)
}
ch := make(chan *usb.Device)
go func(c chan *usb.Device) {
var d *usb.Device = nil
for nil == d {
d, _ = openUSBDevice(mcp.Index, mcp.VID, mcp.PID)
}
c <- d
}(ch)
select {
case <-time.After(timeout):
return fmt.Errorf("New([%d]): timed out opening USB HID device", mcp.Index)
case dev := <-ch:
mcp.Device = dev
}
// initialize the device locked flag and flash write-access flag based on the
// chip security settings stored in flash memory.
if sec, err := mcp.flash.chipSecurity(); nil != err {
return fmt.Errorf("flash.chipSecurity(): %v", err)
} else {
mcp.locked, mcp.flash.writeable = unlockFlags(sec)
}
return nil
}
// status contains conveniently-typed fields for all data parsed from the
// response message of a status command.
type status struct {
cmd byte
ok bool
i2cCancel byte
i2cSpdChg byte
i2cClkChg byte
i2cState byte
i2cReqSz uint16
i2cSentSz uint16
i2cCounter byte
i2cClkDiv byte
i2cTimeVal byte
i2cAddr uint16
i2cSCL byte
i2cSDA byte
interrupt byte
i2cReadPnd byte
hwRevMaj rune
hwRevMin rune
fwRevMaj rune
fwRevMin rune
adcChan []uint16
}
// parseStatus parses the response message of the status command.
//
// Returns a pointer to a newly-created status object on success, or nil if the
// given response message is nil or has inadequate length.
func parseStatus(msg []byte) *status {
if nil == msg || len(msg) < MsgSz {
return nil
}
return &status{
cmd: msg[0],
ok: (0 == msg[1]),
i2cCancel: msg[2],
i2cSpdChg: msg[3],
i2cClkChg: msg[4],
// bytes 5-7 reserved
i2cState: msg[8],
i2cReqSz: (uint16(msg[10]) << 8) | uint16(msg[9]),
i2cSentSz: (uint16(msg[12]) << 8) | uint16(msg[11]),
i2cCounter: msg[13],
i2cClkDiv: msg[14],
i2cTimeVal: msg[15],
i2cAddr: (uint16(msg[17]) << 8) | uint16(msg[16]),
// bytes 18-21 reserved
i2cSCL: msg[22],
i2cSDA: msg[23],
interrupt: msg[24],
i2cReadPnd: msg[25],
// bytes 26-45 reserved
hwRevMaj: rune(msg[46]),
hwRevMin: rune(msg[47]),
fwRevMaj: rune(msg[48]),
fwRevMin: rune(msg[49]),
adcChan: []uint16{
(uint16(msg[51]) << 8) | uint16(msg[50]), // channel 0 (pin GP1)
(uint16(msg[53]) << 8) | uint16(msg[52]), // channel 1 (pin GP2)
(uint16(msg[55]) << 8) | uint16(msg[54]), // channel 2 (pin GP3)
},
}
}
// status sends a status command request, parsing the response into an object
// referred to by the return value.
// We don't ever need the actual bytes from this response message to build a
// cmdSetParams command, because these fields have "alter bits", which means it
// can ignore any fields we aren't explicitly modifying.
//
// Returns a pointer to the parsed object on success, or nil along with an error
// if the receiver is invalid or the status command could not be sent.
func (mcp *MCP2221A) status() (*status, error) {
if ok, err := mcp.valid(); !ok {
return nil, err
}
cmd := makeMsg()
if rsp, err := mcp.send(cmdStatus, cmd); nil != err {
return nil, fmt.Errorf("send(): %v", err)
} else {
return parseStatus(rsp), nil
}
}
// USBManufacturer reads the current USB manufacturer description from flash
// memory and returns it as a string.
//
// Returns an empty string and error if the receiver is invalid or if the flash
// configuration could not be read.
func (mcp *MCP2221A) USBManufacturer() (string, error) {
if ok, err := mcp.valid(); !ok {
return "", err
}
if rsp, err := mcp.flash.read(subcmdUSBMfgDesc); nil != err {
return "", fmt.Errorf("read(): %v", err)
} else {
return parseFlashString(rsp), nil
}
}
// USBProduct reads the current USB product description from flash memory and
// returns it as a string.
//
// Returns an empty string and error if the receiver is invalid or if the flash
// configuration could not be read.
func (mcp *MCP2221A) USBProduct() (string, error) {
if ok, err := mcp.valid(); !ok {
return "", err
}
if rsp, err := mcp.flash.read(subcmdUSBProdDesc); nil != err {
return "", fmt.Errorf("read(): %v", err)
} else {
return parseFlashString(rsp), nil
}
}
// USBSerialNo reads the current USB serial number from flash memory and returns
// it as a string.
//
// Returns an empty string and error if the receiver is invalid or if the flash
// configuration could not be read.
func (mcp *MCP2221A) USBSerialNo() (string, error) {
if ok, err := mcp.valid(); !ok {
return "", err
}
if rsp, err := mcp.flash.read(subcmdUSBSerialNo); nil != err {
return "", fmt.Errorf("read(): %v", err)
} else {
return parseFlashString(rsp), nil
}
}
// FactorySerialNo reads the factory serial number (read-only) from flash memory
// and returns it as a string.
//
// Returns an empty string and error if the receiver is invalid or if the flash
// configuration could not be read.
func (mcp *MCP2221A) FactorySerialNo() (string, error) {
if ok, err := mcp.valid(); !ok {
return "", err
}
if rsp, err := mcp.flash.read(subcmdSerialNo); nil != err {
return "", fmt.Errorf("read(): %v", err)
} else {
cnt := rsp[2] - 2
if cnt > MsgSz-4 {
cnt = MsgSz - 4
}
return string(rsp[4 : 4+cnt]), nil
}
}
// ConfigVIDPID changes the device's VID and PID written in flash memory.
// These settings are non-volatile and become the actual VID and PID with which
// the device will enumerate itself on the USB host (i.e., if changed, the
// global VID and PID constants defined in this package cannot be used to open
// the device) on the next reset/startup.
// Therefore, if changed, be sure your system settings are updated to permit
// access to the device, since it will appear to be a new USB HID device.
//
// For instance, on some udev-based Linux systems, you may need to update your
// udev rules to grant read-write access to your user for devices matching the
// new VID/PID (these rules are traditionally kept in /etc/udev/rules.d).
// If the udev rules are not updated, or your user does not otherwise have the
// necessary permissions, New()/Reset() will fail on the eventual call to claim
// the USB HID device (function (*DeviceInfo).Open() in package karalabe/hid).
//
// Returns an error if the receiver is invalid or if chip settings could not be
// read from or written to flash memory.
func (mcp *MCP2221A) ConfigVIDPID(vid uint16, pid uint16) error {
if ok, err := mcp.valid(); !ok {
return err
}
if cmd, err := mcp.flash.chipSettings(true); nil != err {
return fmt.Errorf("chipSettings(): %v", err)
} else {
cmd[6] = byte(vid & 0xFF)
cmd[7] = byte((vid >> 8) & 0xFF)
cmd[8] = byte(pid & 0xFF)
cmd[9] = byte((pid >> 8) & 0xFF)
if err := mcp.flash.write(subcmdChipSettings, cmd); nil != err {
return fmt.Errorf("write(): %v", err)
}
}
return nil
}
// ConfigReqCurrent writes to flash memory the minimum required current (in mA)
// requested from the USB host during enumeration for bus-powered operation.
// The provided current should be a multiple of 2 mA and at maximum 510 mA.
// If the provided current is not a multiple of 2 mA, the requested current is
// incremented by 1 mA.
// If the provided current is greater than 510 mA, the requested current is set
// to 510 mA.
//
// Returns an error if the receiver is invalid or if the settings could not be
// read from or written to flash memory.
func (mcp *MCP2221A) ConfigReqCurrent(ma uint16) error {
if ok, err := mcp.valid(); !ok {
return err
}
max := uint16(0xFF * 2)
req := uint16(ma + (ma & 1))
if req > max {
req = max
}
if cmd, err := mcp.flash.chipSettings(true); nil != err {
return fmt.Errorf("chipSettings(): %v", err)
} else {
cmd[11] = byte(req / 2)
if err := mcp.flash.write(subcmdChipSettings, cmd); nil != err {
return fmt.Errorf("write(): %v", err)
}
}
return nil
}
// unlockFlags returns the default device locked flag and flash writeable flag,
// in that order, for the given ChipSecurity sec.
func unlockFlags(sec ChipSecurity) (bool, bool) {
dev, fla := true, false
switch sec {
case SecUnsecured:
dev = false // device not locked
fla = true // flash writeable
case SecPassword:
dev = false // device not locked
fla = false // flash not writeable
case SecLocked1, SecLocked2:
dev = true // device locked
fla = false // flash not writeable
}
return dev, fla
}
// ConfigUnlock sends a flash access command with a given slice of bytes as
// password, returning true if the password was accepted and access granted.
// If more than 8 bytes are provided as password, the first 8 bytes are used and
// the remaining bytes are truncated.
// If the current chip security configuration is set to unsecured (no password),
// then this command has no effect and its return value is not guaranteed.
//
// IMPORTANT-SECURITY-1:
// Sending too many flash access commands with the incorrect password will
// **PERMANENTLY** lock the flash memory device from write access. Read access
// is still permitted, but there is absolutely no way to write any changes to
// flash memory. Congratulations, you have sorta-ruined your MCP2221A. Trust
// me, I can empathize, very unfortunately.
//
// Returns false and an error if the receiver is invalid, the password slice is
// nil, the password command could not be sent, the provided password was
// incorrect, or if the flash has been permanently locked.
func (mcp *MCP2221A) ConfigUnlock(pass []byte) (bool, error) {
if ok, err := mcp.valid(); !ok {
return false, err
}
if mcp.locked {
return false, fmt.Errorf("flash access permanently locked")
}
if nil == pass {
return false, fmt.Errorf("invalid password bytes (nil)")
}
// copy the password to a buffer exactly 8 bytes in length, padded with any
// necessary trailing zeroes.
buf := make([]byte, PasswordLen)
copy(buf, pass)
var (
rsp []byte
err error
)
cmd := makeMsg()
copy(cmd[2:], buf)
if rsp, err = mcp.send(cmdFlashPasswd, cmd); nil != err {
// err is set when our response status code (byte index 1) is non-zero, but
// we need to inspect that code to identify rejection reason (below).
if nil == rsp {
return false, fmt.Errorf("send(): %v", err)
}
}
switch ChipSecurity(rsp[1]) {
case SecUnsecured:
mcp.flash.writeable = true
return true, nil
case SecPassword:
return false, fmt.Errorf("invalid password")
case SecLocked1, SecLocked2:
return false, fmt.Errorf("flash access permanently locked")
default:
return false, fmt.Errorf("unknown reject reason")
}
}
// -- DEVICE ---------------------------------------------------------- [end] --
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// -- SRAM ---------------------------------------------------------- [start] --
// sram contains the methods associated with the SRAM component of the
// MCP2221A.
type sram struct {
*MCP2221A
}
// read sends a command requesting current SRAM configuration and returns the
// entire response message.
//
// Returns a nil slice and error if the receiver is invalid, the given range is
// invalid, or if the configuration command could not be sent.
func (mod *sram) read() ([]byte, error) {
if ok, err := mod.valid(); !ok {
return nil, err
}
cmd := makeMsg()
if rsp, err := mod.send(cmdSRAMGet, cmd); nil != err {
return nil, fmt.Errorf("send(): %v", err)
} else {
return rsp, nil
}
}
// readRange reads the current SRAM configuration and returns a byte slice
// within the given interval (inclusive) from the response message.
//
// Returns a nil slice and error if the receiver is invalid, the given range is
// invalid, or if the configuration command could not be sent.
func (mod *sram) readRange(start byte, stop byte) ([]byte, error) {
if ok, err := mod.valid(); !ok {
return nil, err
}
if (start > stop) || (stop >= MsgSz) {
return nil, fmt.Errorf("invalid byte range: [%d, %d]", start, stop)
}
if rsp, err := mod.read(); nil != err {
return nil, fmt.Errorf("read(): %v", err)
} else {
return rsp[start : stop+1], nil
}
}
// -- SRAM ------------------------------------------------------------ [end] --
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// -- FLASH --------------------------------------------------------- [start] --
// flash contains the methods associated with the flash memory component of
// the MCP2221A.
type flash struct {
*MCP2221A
// writeable helps prevent permanently locking the flash memory device on
// accident by acting as a gate that must be cleared before any flash write
// commands can be performed. Clearing the flag is performed by providing the
// correct password to ConfigUnlock(). The flag is also automatically cleared
// when the device is created and the security settings read from flash memory
// indicate no password is required. The flag is set again automatically when
// reset or closed (and cleared again after startup if no password is set).
writeable bool
}
// Constants related to the flash memory module.
const (
subcmdChipSettings byte = 0x00
subcmdGPSettings byte = 0x01
subcmdUSBMfgDesc byte = 0x02
subcmdUSBProdDesc byte = 0x03
subcmdUSBSerialNo byte = 0x04
subcmdSerialNo byte = 0x05
)
// Polarity represents the digital state of certain pins/bits in the flash
// settings struct.
type Polarity bool
// ChipSecurity holds one of the enumerated security configuration constants.
type ChipSecurity byte
// Constants related to ChipSecurity.
const (
SecUnsecured ChipSecurity = 0x00
SecPassword ChipSecurity = 0x01
SecLocked1 ChipSecurity = 0x02 // equivalent to SecLocked2
SecLocked2 ChipSecurity = 0x03 // equivalent to SecLocked1
)
// PasswordLen is the maximum number of bytes in a chip security password.
const PasswordLen = 8
// chipSettings contains the "chip settings" (subcommand 0x00) configuration
// stored in flash memory. the members are conveniently typed for general usage.
// Obtain a new instance containing the actual current data stored in flash by
// calling parseChipSettings() with the return from chipSettings(false).
type chipSettings struct {
cdcSerialNoEnumEnable bool
ledURXPol Polarity
ledUTXPol Polarity
ledI2CPol Polarity
suspndPol Polarity
usbcfgPol Polarity
chipSecurity ChipSecurity
clkOutDiv byte
dacVRef VRef
dacOutput byte
intEdge IntEdge
adcVRef VRef
usbVID uint16
usbPID uint16
usbPowerAttr byte
usbReqCurrent byte
}
// parseChipSettings parses the response message from the chip settings
// subcommand of the flash read command.
//
// Returns a pointer to a newly-created chipSettings object on success, or nil
// if the given response message is nil or has inadequate length.
func parseChipSettings(msg []byte) *chipSettings {
if nil == msg || len(msg) < MsgSz {
return nil
}
return &chipSettings{
cdcSerialNoEnumEnable: 0x01 == ((msg[4] >> 7) & 0x01),
ledURXPol: Polarity(0x01 == ((msg[4] >> 6) & 0x01)),
ledUTXPol: Polarity(0x01 == ((msg[4] >> 5) & 0x01)),
ledI2CPol: Polarity(0x01 == ((msg[4] >> 4) & 0x01)),
suspndPol: Polarity(0x01 == ((msg[4] >> 3) & 0x01)),
usbcfgPol: Polarity(0x01 == ((msg[4] >> 2) & 0x01)),
chipSecurity: ChipSecurity(msg[4] & 0x03),
clkOutDiv: msg[5] & 0x0F,
dacVRef: VRef((msg[6] >> 5) & 0x03),
dacOutput: msg[6] & 0x0F,
intEdge: IntEdge((msg[7] >> 5) & 0x03),
adcVRef: VRef((msg[7] >> 2) & 0x03),
usbVID: (uint16(msg[9]) << 8) | uint16(msg[8]),
usbPID: (uint16(msg[11]) << 8) | uint16(msg[10]),
usbPowerAttr: msg[12],
usbReqCurrent: msg[13],
}
}
// parseFlashString parses a UTF-16-encoded string stored in the response
// messages of flash read commands (0xB0).
func parseFlashString(b []byte) string {
// 16-bit unicode (2 bytes per rune), starting at byte 4
const max byte = (MsgSz - 4) / 2
n := (b[2] - 2) / 2 // length stored at byte 2
switch {
case n == 0: // no UTF symbols
return ""
case n > max: // buffer overrun
n = max
}
p := []uint16{}
for i := byte(0); i < n; i++ {
p = append(p, (uint16(b[4+2*i+1])<<8)|uint16(b[4+2*i]))
}
return string(utf16.Decode(p))
}
// read reads the settings associated with the given subcommand sub from flash
// memory and returns the response message.
//
// Returns nil and an error if the receiver is invalid, subcommand is invalid,
// or if the flash read command could not be sent.
func (mod *flash) read(sub byte) ([]byte, error) {
if ok, err := mod.valid(); !ok {
return nil, err
}
if sub > subcmdSerialNo {
return nil, fmt.Errorf("invalid subcommand: %d", sub)
}
cmd := makeMsg()
cmd[1] = sub
if rsp, err := mod.send(cmdFlashRead, cmd); nil != err {
return nil, fmt.Errorf("send(): %v", err)
} else {
return rsp, nil
}
}
// write writes the given settings data associated with subcommand sub to flash
// memory.
// The flash memory device must be unlocked for write-access prior to calling
// write() by either configuring the chip security as unsecured (default), or by
// calling ConfigUnlock() with the stored 8-byte flash access password.
//
// Returns an error if the receiver is invalid, writeable flag is false,
// subcommand is invalid, or if the flash write command could not be sent.
func (mod *flash) write(sub byte, data []byte) error {
if ok, err := mod.valid(); !ok {
return err
}
if !mod.writeable {
return fmt.Errorf("flash access permanently locked")
}
if sub >= subcmdSerialNo {
return fmt.Errorf("invalid subcommand: %d", sub)
}
data[1] = sub
if _, err := mod.send(cmdFlashWrite, data); nil != err {
return fmt.Errorf("send(): %v", err)
}
return nil
}
// chipSettings reads the current chip settings stored in flash memory and
// returns the byte slice from its response message, aligned as either a
// read-response or a write-command of the flash chip-settings subcommand.
// If write is true, the returned slice can then be used for manipulation before
// passing it on directly as the message data to flash.write().
// These settings do not necessarily reflect the current device configuration,
// which are stored in SRAM.
//
// The important/relevant content of the chip-settings flash-write command and
// flash-read response are exactly the same, except that the flash-write content
// starts at byte index 2:
//
// READ-CHIP-SETTINGS WRITE-CHIP-SETTINGS
// -------------------- ---------------------
// [0] Command Command
// [1] Success Subcommand
// [2] Length <CHIP-SETTINGS[0]>
// [3] Ignored <CHIP-SETTINGS[1]>
// [4] <CHIP-SETTINGS[0]> <CHIP-SETTINGS[2]>
// [5] <CHIP-SETTINGS[1]> <CHIP-SETTINGS[3]>
// [N] ... ...
//
// If write is false, the message formatted underneath READ-CHIP-SETTINGS is
// returned.