motion-master-client 0.0.424 → 0.0.425

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.
@@ -1,16 +1,223 @@
1
1
  import { HardwareDescription } from './hardware-description';
2
2
  import { MotionMasterMessage } from './types';
3
+ /**
4
+ * One field of an iC-MU register: a named group of bits, as the datasheet names and describes it.
5
+ */
6
+ export interface IcMuRegisterField {
7
+ /** The datasheet's name of the field, e.g. `GC_M`. */
8
+ name: string;
9
+ /**
10
+ * The slice of the field this register carries, as the datasheet's register map prints it, e.g. `1:0`.
11
+ * Empty for a one-bit field. It is the field's own bits, not their position in the byte: a value wider
12
+ * than a register is spread across several, which is why `RESABZ` is `7:0` at 0x13 and `15:8` at 0x14.
13
+ */
14
+ bits: string;
15
+ /** The most significant bit the field occupies in the register byte. */
16
+ msb: number;
17
+ /** The least significant bit the field occupies in the register byte. */
18
+ lsb: number;
19
+ /** The datasheet's one-line description of the field. */
20
+ description: string;
21
+ }
22
+ /**
23
+ * A key configuration register of the iC-MU in a Circulo integrated encoder: the address the drive
24
+ * reads it at in RAM, the address it is stored at in EEPROM, and the fields it holds.
25
+ */
26
+ export interface CirculoIntegratedEncoderKeyConfigurationRegister {
27
+ /** The register address in RAM, which the encoder register communication OS command reads. */
28
+ ram: number;
29
+ /** The register address in EEPROM. */
30
+ eeprom: number;
31
+ /** The fields of the register, most significant bit first. Bits no field names are unused. */
32
+ fields: IcMuRegisterField[];
33
+ /**
34
+ * Whether the encoder calibration writes this register: the analog track adjustments of the master and the
35
+ * Nonius track (`GX`, `VOSS`, `VOSC`, `PH`, with `PHR` in the same register as `PH`) and the Nonius track
36
+ * offset table (`SPO_BASE`, `SPO_0` to `SPO_14`).
37
+ * Their values differ from encoder to encoder, because each ring is mounted slightly differently.
38
+ * The values of the other registers are the same on every encoder of a kind.
39
+ */
40
+ writtenByCalibration: boolean;
41
+ }
42
+ /**
43
+ * The 33 key configuration registers of the iC-MU in a Circulo integrated encoder, with their fields.
44
+ *
45
+ * Registers 0x00 to 0x18 have the same address in RAM and EEPROM. RAM registers 0x52 to 0x59, the Nonius
46
+ * offset table, are stored at EEPROM addresses 0x19 to 0x20.
47
+ *
48
+ * Names and descriptions are transcribed from the iC-MU Series datasheet Rev B1, Tables 90 and 91, as
49
+ * Motion Master v6 carries them (`libs/node/ic_haus_registers.cc`). The bit positions follow
50
+ * {@link CirculoIntegratedEncoderIcmuParameters}.
51
+ */
52
+ export declare const circuloIntegratedEncoderKeyConfigurationRegisters: CirculoIntegratedEncoderKeyConfigurationRegister[];
3
53
  /**
4
54
  * Mapping of key configuration register addresses for Circulo Integrated Encoders (e.g., iC-MU, iC-PVL).
5
55
  *
6
56
  * Each entry maps a configuration register's address in EEPROM to its corresponding
7
57
  * address in RAM. This mapping allows read/write access to configuration data via
8
58
  * either persistent storage (EEPROM) or volatile memory (RAM).
59
+ *
60
+ * Derived from {@link circuloIntegratedEncoderKeyConfigurationRegisters}, in the same order.
9
61
  */
10
62
  export declare const circuloIntegratedEncoderKeyConfigurationRegisterAddresses: {
11
63
  eeprom: number;
12
64
  ram: number;
13
65
  }[];
66
+ /** A register field together with the raw value its bits hold. */
67
+ export interface IcMuRegisterFieldValue extends IcMuRegisterField {
68
+ value: number;
69
+ }
70
+ /** The iC-MU parts `HARD_REV` (register 0x74) names. */
71
+ export type IcMuPartName = 'iC-MU' | 'iC-MU128' | 'iC-MU150' | 'iC-MU200';
72
+ /** The iC-MU chip and its hardware revision, from `HARD_REV` (register 0x74). */
73
+ export interface IcMuPart {
74
+ /** The raw value of `HARD_REV`. */
75
+ hardRev: number;
76
+ /** The chip, or null for a code that names no iC-MU. */
77
+ name: IcMuPartName | null;
78
+ /** The hardware revision: the lower nibble of `HARD_REV`. */
79
+ revision: number;
80
+ }
81
+ /** The register that names the iC-MU chip and its hardware revision. */
82
+ export declare const icMuHardRevRegister = 116;
83
+ /**
84
+ * Names the iC-MU chip from `HARD_REV` (register 0x74). From the iC-MU150 on, the upper nibble names the chip;
85
+ * the upper nibble 0 covers the plain iC-MU, 0x05 to 0x07, and the iC-MU128, 0x09 and 0x0A, as the iC-Haus
86
+ * library (`MU_3SL_defs.h`) and Motion Master name them.
87
+ */
88
+ export declare function decodeIcMuHardRev(hardRev: number): IcMuPart;
89
+ /**
90
+ * A register the report reads besides the key configuration registers. It lives in RAM only: the encoder
91
+ * does not store it in EEPROM, and it is not part of the recovery file.
92
+ */
93
+ export interface CirculoIntegratedEncoderStatusRegister {
94
+ /** The register address in RAM (bank 0, as the key configuration registers 0x00 to 0x18). */
95
+ ram: number;
96
+ /** The fields of the register, most significant bit first. */
97
+ fields: IcMuRegisterField[];
98
+ }
99
+ /**
100
+ * The gain the amplitude control unit currently sets, for the master and the Nonius track. It is set only
101
+ * while `ENAC` (register 0x05, bit 7) is 1; the report gives the raw value either way.
102
+ */
103
+ export declare const circuloIntegratedEncoderAmplitudeControlRegisters: CirculoIntegratedEncoderStatusRegister[];
104
+ /** A key configuration register with the raw value read from it and the raw values of its fields. */
105
+ export interface CirculoIntegratedEncoderReportRegister {
106
+ ram: number;
107
+ eeprom: number;
108
+ writtenByCalibration: boolean;
109
+ value: number;
110
+ fields: IcMuRegisterFieldValue[];
111
+ }
112
+ /** A RAM-only register with the raw value read from it and the raw values of its fields. */
113
+ export interface CirculoIntegratedEncoderReportStatusRegister {
114
+ ram: number;
115
+ value: number;
116
+ fields: IcMuRegisterFieldValue[];
117
+ }
118
+ /** The version of {@link CirculoIntegratedEncoderReport}. It changes when the shape changes. */
119
+ export declare const circuloIntegratedEncoderReportSchemaVersion = 2;
120
+ /**
121
+ * The registers of a Circulo integrated encoder as read: the data behind both the JSON and the text report.
122
+ *
123
+ * Every value is raw. The report does not decode values with a datasheet, since datasheets and chip
124
+ * revisions change; decoding happens offline, with the chip revision the report gives.
125
+ */
126
+ export interface CirculoIntegratedEncoderReport {
127
+ schemaVersion: typeof circuloIntegratedEncoderReportSchemaVersion;
128
+ encoderOrdinal: number;
129
+ /** The serial number of the device, or null when it isn't known. */
130
+ deviceSerialNumber: string | null;
131
+ /** When the registers were read, as an ISO 8601 UTC time. */
132
+ readAt: string;
133
+ /** The chip and its revision from `HARD_REV` (register 0x74), or null when it was not read. */
134
+ chip: IcMuPart | null;
135
+ /**
136
+ * Whether health monitoring is on: whether `NON_CTR` is reported in the error bit. See
137
+ * {@link isCirculoIntegratedEncoderHealthMonitoringActive}.
138
+ */
139
+ healthMonitoring: {
140
+ active: boolean;
141
+ cfgew: number;
142
+ };
143
+ /** The Nonius track offset table, `SPO_BASE` and `SPO_0` to `SPO_14`, as the raw 4-bit values. */
144
+ noniusTrackOffsetTable: {
145
+ name: string;
146
+ value: number;
147
+ }[];
148
+ /**
149
+ * `ACGAIN` and `AFGAIN` of both tracks (registers 0x2B and 0x2F), or an empty list when they were not read.
150
+ */
151
+ amplitudeControlRegisters: CirculoIntegratedEncoderReportStatusRegister[];
152
+ /** Every key configuration register in the order of {@link circuloIntegratedEncoderKeyConfigurationRegisters}. */
153
+ registers: CirculoIntegratedEncoderReportRegister[];
154
+ }
155
+ /**
156
+ * Whether health monitoring is on. Health monitoring is Synapticon's name for reporting the iC-MU's
157
+ * `NON_CTR` error, the period counter consistency error, in the error bit.
158
+ *
159
+ * `CFGEW` (register 0x0C) bit 6 enables `NON_CTR` in the error bit when 0 and disables it when 1. Motion Master
160
+ * writes 0x6C by default, which disables it, and 0x2C when a full calibration is asked to activate health
161
+ * monitoring. The two differ in bit 6 only.
162
+ *
163
+ * @param cfgew - The value of register 0x0C.
164
+ */
165
+ export declare function isCirculoIntegratedEncoderHealthMonitoringActive(cfgew: number): boolean;
166
+ /**
167
+ * Splits the key configuration register values into the raw values of their fields.
168
+ *
169
+ * @param values - The register values, in the order of {@link circuloIntegratedEncoderKeyConfigurationRegisters}.
170
+ * @returns For each register, its fields, most significant bit first, each with its raw value.
171
+ * @throws Error when the number of values does not match the number of key configuration registers.
172
+ */
173
+ export declare function decodeCirculoIntegratedEncoderKeyConfigurationRegisters(values: number[]): IcMuRegisterFieldValue[][];
174
+ /** The encoder and device a report is made for, when its registers were read, and the registers read besides. */
175
+ export interface CirculoIntegratedEncoderReportInfo {
176
+ encoderOrdinal: number;
177
+ /** The serial number of the device, when it is known. */
178
+ deviceSerialNumber?: string;
179
+ /** When the registers were read. */
180
+ readAt: Date;
181
+ /** The value of `HARD_REV` (register 0x74), when it was read. */
182
+ hardRev?: number;
183
+ /**
184
+ * The values of {@link circuloIntegratedEncoderAmplitudeControlRegisters}, in their order, when they were read.
185
+ */
186
+ amplitudeControlValues?: number[];
187
+ }
188
+ /**
189
+ * Builds the report of a Circulo integrated encoder from its registers: the chip revision, health monitoring,
190
+ * the Nonius track offset table, the amplitude control gain and every key configuration register with the raw
191
+ * values of its fields. Nothing is decoded with a datasheet.
192
+ *
193
+ * Serialize it with `JSON.stringify` for programs, or render it with
194
+ * {@link formatCirculoIntegratedEncoderReport} for people.
195
+ *
196
+ * @param values - The register values, in the order of {@link circuloIntegratedEncoderKeyConfigurationRegisters}.
197
+ * @param info - The encoder and device the values were read from, when, and the registers read besides.
198
+ * @throws Error when the number of values does not match the number of registers.
199
+ */
200
+ export declare function buildCirculoIntegratedEncoderReport(values: number[], info: CirculoIntegratedEncoderReportInfo): CirculoIntegratedEncoderReport;
201
+ /**
202
+ * The file name a report is saved under, e.g.
203
+ * `circulo-integrated-encoder-1-report-8502-03-0000001-2540-2026-11-04T10-00-00-000Z.json`.
204
+ *
205
+ * @param format - `json` or `txt`, which is also the extension.
206
+ */
207
+ export declare function circuloIntegratedEncoderReportFileName(report: Pick<CirculoIntegratedEncoderReport, 'encoderOrdinal' | 'deviceSerialNumber' | 'readAt'>, format: 'json' | 'txt'): string;
208
+ /** The chip line of the text report, e.g. `HARD_REV: 0x11 = iC-MU150 rev 1`. */
209
+ export declare function formatIcMuChip(chip: IcMuPart | null): string;
210
+ /**
211
+ * Renders a report as plain text for people. It computes nothing: every value comes from the report, raw.
212
+ *
213
+ * It opens with the device and the chip revision, then whether health monitoring is on, the Nonius track
214
+ * offset table, the amplitude control gain, and every key configuration register with its RAM and EEPROM
215
+ * address and its value, and below it every field with its name, its slice, its value and its description.
216
+ * Registers the encoder calibration writes are marked with `*`.
217
+ *
218
+ * @returns The report, lines separated by `\n` and ending with a newline.
219
+ */
220
+ export declare function formatCirculoIntegratedEncoderReport(report: CirculoIntegratedEncoderReport): string;
14
221
  /**
15
222
  * I²C device IDs (addresses) used to access specific memory areas and registers
16
223
  * of Circulo Integrated Encoders such as iC-MU and iC-PVL.
@@ -1,50 +1,349 @@
1
1
  "use strict";
2
2
  // https://github.com/synapticon/somanet_software/blob/32edf5ef572ea7d840aabe4a26e0f3d31f05caab/sw_applications/app_motion_drive/test/pytest/somanet_icmu_helpers.py
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.computeCirculoEncoderCalibrationProgressResult = exports.computeCirculoEncoderCalibrationResult = exports.getCirculoEncoderMaxPermissiblePhaseError = exports.circulo9MaxPermissiblePhaseError = exports.circulo7MaxPermissiblePhaseError = exports.CirculoExternalEncoderType = exports.getCirculoEncoderCalibrationMaxValuePercentageLabel = exports.getCirculoEncoderCalibrationMaxValuePercentageColor = exports.CirculoIntegratedEncoderIcpvlCommands = exports.CirculoIntegratedEncoderIcmuCommands = exports.CirculoIntegratedEncoderEepromParameters = exports.CirculoIntegratedEncoderIcpvlParameters = exports.CirculoIntegratedEncoderIcmuParameters = exports.circuloIntegratedEncoderI2CDeviceIdOptions = exports.CirculoIntegratedEncoderI2CDeviceId = exports.circuloIntegratedEncoderKeyConfigurationRegisterAddresses = void 0;
4
+ exports.computeCirculoEncoderCalibrationProgressResult = exports.computeCirculoEncoderCalibrationResult = exports.getCirculoEncoderMaxPermissiblePhaseError = exports.circulo9MaxPermissiblePhaseError = exports.circulo7MaxPermissiblePhaseError = exports.CirculoExternalEncoderType = exports.getCirculoEncoderCalibrationMaxValuePercentageLabel = exports.getCirculoEncoderCalibrationMaxValuePercentageColor = exports.CirculoIntegratedEncoderIcpvlCommands = exports.CirculoIntegratedEncoderIcmuCommands = exports.CirculoIntegratedEncoderEepromParameters = exports.CirculoIntegratedEncoderIcpvlParameters = exports.CirculoIntegratedEncoderIcmuParameters = exports.circuloIntegratedEncoderI2CDeviceIdOptions = exports.CirculoIntegratedEncoderI2CDeviceId = exports.formatCirculoIntegratedEncoderReport = exports.formatIcMuChip = exports.circuloIntegratedEncoderReportFileName = exports.buildCirculoIntegratedEncoderReport = exports.decodeCirculoIntegratedEncoderKeyConfigurationRegisters = exports.isCirculoIntegratedEncoderHealthMonitoringActive = exports.circuloIntegratedEncoderReportSchemaVersion = exports.circuloIntegratedEncoderAmplitudeControlRegisters = exports.decodeIcMuHardRev = exports.icMuHardRevRegister = exports.circuloIntegratedEncoderKeyConfigurationRegisterAddresses = exports.circuloIntegratedEncoderKeyConfigurationRegisters = void 0;
5
5
  const product_1 = require("./product");
6
+ /** A field of a register, with the datasheet's own slice of the field computed from its width. */
7
+ function icMuField(name, msb, lsb, description, bits) {
8
+ return { name, bits: bits !== null && bits !== void 0 ? bits : (msb === lsb ? '' : `${msb - lsb}:0`), msb, lsb, description };
9
+ }
10
+ function keyRegister(ram, eeprom, fields, writtenByCalibration = false) {
11
+ return { ram, eeprom, fields, writtenByCalibration };
12
+ }
13
+ const resAbzDescription = 'Incremental interface resolution for ABZ, STEP/DIR and CW/CCW';
14
+ const spoDescription = 'Offset of Nonius to master';
15
+ /**
16
+ * The 33 key configuration registers of the iC-MU in a Circulo integrated encoder, with their fields.
17
+ *
18
+ * Registers 0x00 to 0x18 have the same address in RAM and EEPROM. RAM registers 0x52 to 0x59, the Nonius
19
+ * offset table, are stored at EEPROM addresses 0x19 to 0x20.
20
+ *
21
+ * Names and descriptions are transcribed from the iC-MU Series datasheet Rev B1, Tables 90 and 91, as
22
+ * Motion Master v6 carries them (`libs/node/ic_haus_registers.cc`). The bit positions follow
23
+ * {@link CirculoIntegratedEncoderIcmuParameters}.
24
+ */
25
+ exports.circuloIntegratedEncoderKeyConfigurationRegisters = [
26
+ keyRegister(0x00, 0x00, [
27
+ icMuField('GC_M', 7, 6, 'Master gain range selection'),
28
+ icMuField('GF_M', 5, 0, 'Master gain'),
29
+ ]),
30
+ keyRegister(0x01, 0x01, [icMuField('GX_M', 6, 0, 'Master cosine signal gain adjustment')], true),
31
+ keyRegister(0x02, 0x02, [icMuField('VOSS_M', 6, 0, 'Master sine offset adjustment')], true),
32
+ keyRegister(0x03, 0x03, [icMuField('VOSC_M', 6, 0, 'Master cosine offset adjustment')], true),
33
+ keyRegister(0x04, 0x04, [icMuField('PHR_M', 7, 7, 'Master phase adjustment range'), icMuField('PH_M', 6, 0, 'Master phase adjustment')], true),
34
+ keyRegister(0x05, 0x05, [
35
+ icMuField('ENAC', 7, 7, 'Amplitude control unit activation'),
36
+ icMuField('CIBM', 3, 0, 'Bias current settings'),
37
+ ]),
38
+ keyRegister(0x06, 0x06, [
39
+ icMuField('GC_N', 7, 6, 'Nonius gain range selection'),
40
+ icMuField('GF_N', 5, 0, 'Nonius gain'),
41
+ ]),
42
+ keyRegister(0x07, 0x07, [icMuField('GX_N', 6, 0, 'Nonius cosine signal gain adjustment')], true),
43
+ keyRegister(0x08, 0x08, [icMuField('VOSS_N', 6, 0, 'Nonius sine offset adjustment')], true),
44
+ keyRegister(0x09, 0x09, [icMuField('VOSC_N', 6, 0, 'Nonius cosine offset adjustment')], true),
45
+ keyRegister(0x0a, 0x0a, [icMuField('PHR_N', 7, 7, 'Nonius phase adjustment range'), icMuField('PH_N', 6, 0, 'Nonius phase adjustment')], true),
46
+ keyRegister(0x0b, 0x0b, [
47
+ icMuField('MODEB', 6, 4, 'I/O port B configuration'),
48
+ icMuField('NTOA', 3, 3, 'Adaptive timeout'),
49
+ icMuField('MODEA', 2, 0, 'I/O port A configuration'),
50
+ ]),
51
+ keyRegister(0x0c, 0x0c, [icMuField('CFGEW', 7, 0, 'Error and warning bit configuration')]),
52
+ keyRegister(0x0d, 0x0d, [
53
+ icMuField('ACC_STAT', 7, 7, 'Output configuration of the status register'),
54
+ icMuField('NCHK_CRC', 6, 6, 'Cyclic check of CRC16 and CRC8'),
55
+ icMuField('NCHK_NON', 5, 5, 'Cyclic check of the Nonius value (low active)'),
56
+ icMuField('ACRM_RES', 4, 4, 'Automatic reset with master track amplitude errors'),
57
+ icMuField('EMTD', 2, 0, 'Minimum error message duration'),
58
+ ]),
59
+ keyRegister(0x0e, 0x0e, [
60
+ icMuField('ESSI_MT', 7, 6, 'Error bit external multiturn'),
61
+ icMuField('ROT_MT', 5, 5, 'Code direction external multiturn'),
62
+ icMuField('LIN', 4, 4, 'Linear scanning'),
63
+ icMuField('FILT', 2, 0, 'Digital filter settings'),
64
+ ]),
65
+ keyRegister(0x0f, 0x0f, [
66
+ icMuField('SPO_MT', 7, 4, 'Offset external multiturn'),
67
+ icMuField('MPC', 3, 0, 'Master period count'),
68
+ ]),
69
+ keyRegister(0x10, 0x10, [
70
+ icMuField('GET_MT', 7, 7, 'Multiturn interface daisy chain'),
71
+ icMuField('CHK_MT', 6, 6, 'Cyclic check of the multiturn value'),
72
+ icMuField('SBL_MT', 5, 4, 'Multiturn synchronization bit length'),
73
+ icMuField('MODE_MT', 3, 0, 'Multiturn mode'),
74
+ ]),
75
+ keyRegister(0x11, 0x11, [
76
+ icMuField('OUT_ZERO', 7, 5, 'Output shift register configuration: zeros inserted after the used bits and before an error or warning'),
77
+ icMuField('OUT_MSB', 4, 0, 'Output shift register configuration: MSB used bits'),
78
+ ]),
79
+ keyRegister(0x12, 0x12, [
80
+ icMuField('GSSI', 7, 7, 'Gray or binary data format'),
81
+ icMuField('RSSI', 6, 6, 'Ring operation'),
82
+ icMuField('MODE_ST', 5, 4, 'Data output'),
83
+ icMuField('OUT_LSB', 3, 0, 'Output shift register configuration: LSB used bits'),
84
+ ]),
85
+ keyRegister(0x13, 0x13, [icMuField('RESABZ', 7, 0, resAbzDescription, '7:0')]),
86
+ keyRegister(0x14, 0x14, [icMuField('RESABZ', 7, 0, resAbzDescription, '15:8')]),
87
+ keyRegister(0x15, 0x15, [
88
+ icMuField('ROT_ALL', 7, 7, 'Code direction'),
89
+ icMuField('SS_AB', 5, 4, 'System AB step size'),
90
+ icMuField('ENIF_AUTO', 3, 3, 'Incremental interface enable'),
91
+ icMuField('FRQAB', 2, 0, 'AB output frequency'),
92
+ ]),
93
+ keyRegister(0x16, 0x16, [
94
+ icMuField('LENZ', 7, 6, 'Index pulse length'),
95
+ icMuField('CHYS_AB', 5, 4, 'Converter hysteresis'),
96
+ icMuField('PP60UVW', 3, 3, 'Commutation signal phase position'),
97
+ icMuField('INV_A', 2, 2, 'A, STEP or CW signal inversion'),
98
+ icMuField('INV_B', 1, 1, 'B, DIR or CCW signal inversion'),
99
+ icMuField('INV_Z', 0, 0, 'Z or NCLR signal inversion'),
100
+ ]),
101
+ keyRegister(0x17, 0x17, [
102
+ icMuField('RPL', 7, 6, 'Register access control'),
103
+ icMuField('PPUVW', 5, 0, 'Number of commutation signal pole pairs'),
104
+ ]),
105
+ keyRegister(0x18, 0x18, [icMuField('TEST', 7, 0, 'Adjustment modes and iC-Haus test modes')]),
106
+ keyRegister(0x52, 0x19, [icMuField('SPO_0', 7, 4, spoDescription), icMuField('SPO_BASE', 3, 0, spoDescription)], true),
107
+ keyRegister(0x53, 0x1a, [icMuField('SPO_2', 7, 4, spoDescription), icMuField('SPO_1', 3, 0, spoDescription)], true),
108
+ keyRegister(0x54, 0x1b, [icMuField('SPO_4', 7, 4, spoDescription), icMuField('SPO_3', 3, 0, spoDescription)], true),
109
+ keyRegister(0x55, 0x1c, [icMuField('SPO_6', 7, 4, spoDescription), icMuField('SPO_5', 3, 0, spoDescription)], true),
110
+ keyRegister(0x56, 0x1d, [icMuField('SPO_8', 7, 4, spoDescription), icMuField('SPO_7', 3, 0, spoDescription)], true),
111
+ keyRegister(0x57, 0x1e, [icMuField('SPO_10', 7, 4, spoDescription), icMuField('SPO_9', 3, 0, spoDescription)], true),
112
+ keyRegister(0x58, 0x1f, [icMuField('SPO_12', 7, 4, spoDescription), icMuField('SPO_11', 3, 0, spoDescription)], true),
113
+ keyRegister(0x59, 0x20, [icMuField('SPO_14', 7, 4, spoDescription), icMuField('SPO_13', 3, 0, spoDescription)], true),
114
+ ];
6
115
  /**
7
116
  * Mapping of key configuration register addresses for Circulo Integrated Encoders (e.g., iC-MU, iC-PVL).
8
117
  *
9
118
  * Each entry maps a configuration register's address in EEPROM to its corresponding
10
119
  * address in RAM. This mapping allows read/write access to configuration data via
11
120
  * either persistent storage (EEPROM) or volatile memory (RAM).
121
+ *
122
+ * Derived from {@link circuloIntegratedEncoderKeyConfigurationRegisters}, in the same order.
123
+ */
124
+ exports.circuloIntegratedEncoderKeyConfigurationRegisterAddresses = exports.circuloIntegratedEncoderKeyConfigurationRegisters.map(({ eeprom, ram }) => ({ eeprom, ram }));
125
+ /** The register that names the iC-MU chip and its hardware revision. */
126
+ exports.icMuHardRevRegister = 0x74;
127
+ /**
128
+ * Names the iC-MU chip from `HARD_REV` (register 0x74). From the iC-MU150 on, the upper nibble names the chip;
129
+ * the upper nibble 0 covers the plain iC-MU, 0x05 to 0x07, and the iC-MU128, 0x09 and 0x0A, as the iC-Haus
130
+ * library (`MU_3SL_defs.h`) and Motion Master name them.
131
+ */
132
+ function decodeIcMuHardRev(hardRev) {
133
+ const code = hardRev & 0xff;
134
+ let name = null;
135
+ if (code >= 0x05 && code <= 0x07) {
136
+ name = 'iC-MU';
137
+ }
138
+ else if (code === 0x09 || code === 0x0a) {
139
+ name = 'iC-MU128';
140
+ }
141
+ else if (code >> 4 === 0x1) {
142
+ name = 'iC-MU150';
143
+ }
144
+ else if (code >> 4 === 0x2) {
145
+ name = 'iC-MU200';
146
+ }
147
+ return { hardRev: code, name, revision: code & 0x0f };
148
+ }
149
+ exports.decodeIcMuHardRev = decodeIcMuHardRev;
150
+ /**
151
+ * The gain the amplitude control unit currently sets, for the master and the Nonius track. It is set only
152
+ * while `ENAC` (register 0x05, bit 7) is 1; the report gives the raw value either way.
12
153
  */
13
- exports.circuloIntegratedEncoderKeyConfigurationRegisterAddresses = [
14
- { eeprom: 0x00, ram: 0x00 },
15
- { eeprom: 0x01, ram: 0x01 },
16
- { eeprom: 0x02, ram: 0x02 },
17
- { eeprom: 0x03, ram: 0x03 },
18
- { eeprom: 0x04, ram: 0x04 },
19
- { eeprom: 0x05, ram: 0x05 },
20
- { eeprom: 0x06, ram: 0x06 },
21
- { eeprom: 0x07, ram: 0x07 },
22
- { eeprom: 0x08, ram: 0x08 },
23
- { eeprom: 0x09, ram: 0x09 },
24
- { eeprom: 0x0a, ram: 0x0a },
25
- { eeprom: 0x0b, ram: 0x0b },
26
- { eeprom: 0x0c, ram: 0x0c },
27
- { eeprom: 0x0d, ram: 0x0d },
28
- { eeprom: 0x0e, ram: 0x0e },
29
- { eeprom: 0x0f, ram: 0x0f },
30
- { eeprom: 0x10, ram: 0x10 },
31
- { eeprom: 0x11, ram: 0x11 },
32
- { eeprom: 0x12, ram: 0x12 },
33
- { eeprom: 0x13, ram: 0x13 },
34
- { eeprom: 0x14, ram: 0x14 },
35
- { eeprom: 0x15, ram: 0x15 },
36
- { eeprom: 0x16, ram: 0x16 },
37
- { eeprom: 0x17, ram: 0x17 },
38
- { eeprom: 0x18, ram: 0x18 },
39
- { eeprom: 0x19, ram: 0x52 },
40
- { eeprom: 0x1a, ram: 0x53 },
41
- { eeprom: 0x1b, ram: 0x54 },
42
- { eeprom: 0x1c, ram: 0x55 },
43
- { eeprom: 0x1d, ram: 0x56 },
44
- { eeprom: 0x1e, ram: 0x57 },
45
- { eeprom: 0x1f, ram: 0x58 },
46
- { eeprom: 0x20, ram: 0x59 },
154
+ exports.circuloIntegratedEncoderAmplitudeControlRegisters = [
155
+ {
156
+ ram: 0x2b,
157
+ fields: [
158
+ icMuField('ACGAIN_M', 4, 3, 'Master gain range currently set by the amplitude control unit'),
159
+ icMuField('AFGAIN_M', 2, 0, 'Master gain factor currently set by the amplitude control unit'),
160
+ ],
161
+ },
162
+ {
163
+ ram: 0x2f,
164
+ fields: [
165
+ icMuField('ACGAIN_N', 4, 3, 'Nonius gain range currently set by the amplitude control unit'),
166
+ icMuField('AFGAIN_N', 2, 0, 'Nonius gain factor currently set by the amplitude control unit'),
167
+ ],
168
+ },
47
169
  ];
170
+ /** The version of {@link CirculoIntegratedEncoderReport}. It changes when the shape changes. */
171
+ exports.circuloIntegratedEncoderReportSchemaVersion = 2;
172
+ function hex(value, digits = 2) {
173
+ return `0x${value.toString(16).toUpperCase().padStart(digits, '0')}`;
174
+ }
175
+ /** The raw value of each field of a register byte. */
176
+ function fieldValues(fields, value) {
177
+ return fields.map((field) => (Object.assign(Object.assign({}, field), { value: ((value & 0xff) >> field.lsb) & ((1 << (field.msb - field.lsb + 1)) - 1) })));
178
+ }
179
+ /** The bit of `CFGEW` (register 0x0C) that keeps the `NON_CTR` error out of the error bit when set. */
180
+ const cfgewNonCtrMask = 0x40;
181
+ /**
182
+ * Whether health monitoring is on. Health monitoring is Synapticon's name for reporting the iC-MU's
183
+ * `NON_CTR` error, the period counter consistency error, in the error bit.
184
+ *
185
+ * `CFGEW` (register 0x0C) bit 6 enables `NON_CTR` in the error bit when 0 and disables it when 1. Motion Master
186
+ * writes 0x6C by default, which disables it, and 0x2C when a full calibration is asked to activate health
187
+ * monitoring. The two differ in bit 6 only.
188
+ *
189
+ * @param cfgew - The value of register 0x0C.
190
+ */
191
+ function isCirculoIntegratedEncoderHealthMonitoringActive(cfgew) {
192
+ return (cfgew & cfgewNonCtrMask) === 0;
193
+ }
194
+ exports.isCirculoIntegratedEncoderHealthMonitoringActive = isCirculoIntegratedEncoderHealthMonitoringActive;
195
+ /**
196
+ * Splits the key configuration register values into the raw values of their fields.
197
+ *
198
+ * @param values - The register values, in the order of {@link circuloIntegratedEncoderKeyConfigurationRegisters}.
199
+ * @returns For each register, its fields, most significant bit first, each with its raw value.
200
+ * @throws Error when the number of values does not match the number of key configuration registers.
201
+ */
202
+ function decodeCirculoIntegratedEncoderKeyConfigurationRegisters(values) {
203
+ const registers = exports.circuloIntegratedEncoderKeyConfigurationRegisters;
204
+ if (values.length !== registers.length) {
205
+ throw new Error(`Expected ${registers.length} register values, got ${values.length}.`);
206
+ }
207
+ return registers.map((register, i) => fieldValues(register.fields, values[i]));
208
+ }
209
+ exports.decodeCirculoIntegratedEncoderKeyConfigurationRegisters = decodeCirculoIntegratedEncoderKeyConfigurationRegisters;
210
+ /**
211
+ * Builds the report of a Circulo integrated encoder from its registers: the chip revision, health monitoring,
212
+ * the Nonius track offset table, the amplitude control gain and every key configuration register with the raw
213
+ * values of its fields. Nothing is decoded with a datasheet.
214
+ *
215
+ * Serialize it with `JSON.stringify` for programs, or render it with
216
+ * {@link formatCirculoIntegratedEncoderReport} for people.
217
+ *
218
+ * @param values - The register values, in the order of {@link circuloIntegratedEncoderKeyConfigurationRegisters}.
219
+ * @param info - The encoder and device the values were read from, when, and the registers read besides.
220
+ * @throws Error when the number of values does not match the number of registers.
221
+ */
222
+ function buildCirculoIntegratedEncoderReport(values, info) {
223
+ var _a, _b;
224
+ const decoded = decodeCirculoIntegratedEncoderKeyConfigurationRegisters(values);
225
+ const fields = decoded.flat();
226
+ const code = (name) => {
227
+ const field = fields.find((f) => f.name === name);
228
+ if (!field) {
229
+ throw new Error(`No key configuration register field is named ${name}.`);
230
+ }
231
+ return field.value;
232
+ };
233
+ const amplitudeControlValues = (_a = info.amplitudeControlValues) !== null && _a !== void 0 ? _a : [];
234
+ const amplitudeControl = exports.circuloIntegratedEncoderAmplitudeControlRegisters;
235
+ if (amplitudeControlValues.length !== 0 && amplitudeControlValues.length !== amplitudeControl.length) {
236
+ throw new Error(`Expected ${amplitudeControl.length} amplitude control register values, got ${amplitudeControlValues.length}.`);
237
+ }
238
+ const cfgew = code('CFGEW');
239
+ const spoNames = ['SPO_BASE', ...Array.from({ length: 15 }, (_, i) => `SPO_${i}`)];
240
+ return {
241
+ schemaVersion: exports.circuloIntegratedEncoderReportSchemaVersion,
242
+ encoderOrdinal: info.encoderOrdinal,
243
+ deviceSerialNumber: (_b = info.deviceSerialNumber) !== null && _b !== void 0 ? _b : null,
244
+ readAt: info.readAt.toISOString(),
245
+ chip: info.hardRev === undefined ? null : decodeIcMuHardRev(info.hardRev),
246
+ healthMonitoring: { active: isCirculoIntegratedEncoderHealthMonitoringActive(cfgew), cfgew },
247
+ noniusTrackOffsetTable: spoNames.map((name) => ({ name, value: code(name) })),
248
+ amplitudeControlRegisters: amplitudeControlValues.map((value, i) => ({
249
+ ram: amplitudeControl[i].ram,
250
+ value: value & 0xff,
251
+ fields: fieldValues(amplitudeControl[i].fields, value),
252
+ })),
253
+ registers: exports.circuloIntegratedEncoderKeyConfigurationRegisters.map((register, i) => ({
254
+ ram: register.ram,
255
+ eeprom: register.eeprom,
256
+ writtenByCalibration: register.writtenByCalibration,
257
+ value: values[i] & 0xff,
258
+ fields: decoded[i],
259
+ })),
260
+ };
261
+ }
262
+ exports.buildCirculoIntegratedEncoderReport = buildCirculoIntegratedEncoderReport;
263
+ /**
264
+ * The file name a report is saved under, e.g.
265
+ * `circulo-integrated-encoder-1-report-8502-03-0000001-2540-2026-11-04T10-00-00-000Z.json`.
266
+ *
267
+ * @param format - `json` or `txt`, which is also the extension.
268
+ */
269
+ function circuloIntegratedEncoderReportFileName(report, format) {
270
+ const name = [
271
+ 'circulo-integrated-encoder',
272
+ report.encoderOrdinal,
273
+ 'report',
274
+ ...(report.deviceSerialNumber ? [report.deviceSerialNumber] : []),
275
+ report.readAt.replace(/[:.]/g, '-'),
276
+ ].join('-');
277
+ return `${name}.${format}`;
278
+ }
279
+ exports.circuloIntegratedEncoderReportFileName = circuloIntegratedEncoderReportFileName;
280
+ /** The chip line of the text report, e.g. `HARD_REV: 0x11 = iC-MU150 rev 1`. */
281
+ function formatIcMuChip(chip) {
282
+ var _a;
283
+ if (!chip) {
284
+ return 'not read';
285
+ }
286
+ return `${hex(chip.hardRev)} = ${(_a = chip.name) !== null && _a !== void 0 ? _a : 'unknown iC-MU chip'} rev ${chip.revision}`;
287
+ }
288
+ exports.formatIcMuChip = formatIcMuChip;
289
+ /** The table header and lines of registers with their fields, the first columns given per register. */
290
+ function registerTableLines(heading, registers) {
291
+ const lines = [
292
+ heading +
293
+ 'VALUE'.padEnd(6) +
294
+ 'BINARY'.padEnd(12) +
295
+ 'FIELD'.padEnd(11) +
296
+ 'BITS'.padEnd(6) +
297
+ 'FIELD VALUE'.padEnd(13) +
298
+ 'DESCRIPTION',
299
+ ];
300
+ for (const register of registers) {
301
+ const head = `${register.head}${hex(register.value)} ${register.value.toString(2).padStart(8, '0')} `;
302
+ register.fields.forEach((field, j) => {
303
+ const fieldValue = `${field.value} (${hex(field.value, 1)})`;
304
+ lines.push(`${j === 0 ? head : ' '.repeat(head.length)}${field.name.padEnd(11)}${field.bits.padEnd(6)}${fieldValue.padEnd(13)}${field.description}`.trimEnd());
305
+ });
306
+ }
307
+ return lines;
308
+ }
309
+ /**
310
+ * Renders a report as plain text for people. It computes nothing: every value comes from the report, raw.
311
+ *
312
+ * It opens with the device and the chip revision, then whether health monitoring is on, the Nonius track
313
+ * offset table, the amplitude control gain, and every key configuration register with its RAM and EEPROM
314
+ * address and its value, and below it every field with its name, its slice, its value and its description.
315
+ * Registers the encoder calibration writes are marked with `*`.
316
+ *
317
+ * @returns The report, lines separated by `\n` and ending with a newline.
318
+ */
319
+ function formatCirculoIntegratedEncoderReport(report) {
320
+ var _a;
321
+ const { healthMonitoring } = report;
322
+ const lines = [
323
+ `Circulo Integrated Encoder ${report.encoderOrdinal} - Key Configuration Registers`,
324
+ `Device serial number: ${(_a = report.deviceSerialNumber) !== null && _a !== void 0 ? _a : 'unknown'}`,
325
+ `Read at: ${report.readAt}`,
326
+ `HARD_REV: ${formatIcMuChip(report.chip)}`,
327
+ 'All values are raw, as read from the encoder.',
328
+ '',
329
+ `HEALTH MONITORING: ${healthMonitoring.active ? 'ON' : 'OFF'} (CFGEW = ${hex(healthMonitoring.cfgew)}, bit 6 = ${healthMonitoring.active ? 0 : 1})`,
330
+ '',
331
+ 'NONIUS TRACK OFFSET TABLE',
332
+ 'FIELD'.padEnd(11) + 'VALUE',
333
+ ...report.noniusTrackOffsetTable.map(({ name, value }) => `${name.padEnd(11)}${value} (${hex(value, 1)})`),
334
+ '',
335
+ 'AMPLITUDE CONTROL GAIN',
336
+ ];
337
+ if (report.amplitudeControlRegisters.length === 0) {
338
+ lines.push('Not read.');
339
+ }
340
+ else {
341
+ lines.push(...registerTableLines('RAM'.padEnd(6), report.amplitudeControlRegisters.map((register) => (Object.assign(Object.assign({}, register), { head: `${hex(register.ram)} ` })))));
342
+ }
343
+ lines.push('', 'REGISTERS', 'Registers marked * are written by the encoder calibration.', '', ...registerTableLines('RAM'.padEnd(6) + 'EEPROM'.padEnd(8), report.registers.map((register) => (Object.assign(Object.assign({}, register), { head: `${hex(register.ram)} ${hex(register.eeprom)} ${register.writtenByCalibration ? '*' : ' '} ` })))));
344
+ return lines.join('\n') + '\n';
345
+ }
346
+ exports.formatCirculoIntegratedEncoderReport = formatCirculoIntegratedEncoderReport;
48
347
  /**
49
348
  * I²C device IDs (addresses) used to access specific memory areas and registers
50
349
  * of Circulo Integrated Encoders such as iC-MU and iC-PVL.