AK 2.0 and manual v1.0.18¶
This library targets the AK-DRV-V2.1 driver and AK Series Module Driver Manual v1.0.18 (2026-01-19). Most existing Python libraries were written against AK 1.0-era firmware and an older manual.
What changed that matters¶
New motors. v1.0.17 added AK45-36, AK45-10 and AK40-10; v1.0.18 corrected the AK45-10 parameters. Libraries predating these simply do not have the field ranges.
A new fault code. Code 7 is motor stall. Code 6 is MOSFET over-temperature — older
libraries label it “phase current unbalance”, which is wrong. Indexing a 0–6 dict with a 7
raises KeyError, and in the reference library that happens inside the receive thread
where it is swallowed.
Corrected position-velocity scaling. v1.0.12 fixed the byte-order description and
v1.0.14 corrected the origin-mode and position-velocity routines. The CAN SET_POS_SPD
packet divides speed and acceleration by 10 before packing them as int16.
Permanent zero is dual-encoder only. v1.0.15 spelled this out. Origin mode 1 writes flash; on a single-encoder model such as the AK40-10 it is meaningless.
Two fault tables, not one. CAN feedback uses codes 0–7. The serial GET_VALUES reply
uses mc_fault_code, where 1 is over-voltage and the range runs to 18. They are not
interchangeable.
Protocol facts worth having in one place¶
MIT command — standard frame, arbitration id = motor id, DLC 8:
D0 = p>>8 D1 = p & 0xFF
D2 = v>>4 D3 = (v & 0xF)<<4 | kp>>8
D4 = kp & 0xFF D5 = kd>>4
D6 = (kd & 0xF)<<4 | t>>8 D7 = t & 0xFF
MIT reply — standard frame, DLC 8: D0 driver id, D1..D2 position (16 bit),
D3+D4 high nibble velocity (12 bit), D4 low nibble +D5 torque (12 bit),
D6 temperature + 40, D7 fault.
Special MIT payloads: enter FF×7,FC, exit FF×7,FD, zero FF×7,FE.
Servo — extended frame, arbitration_id = (packet_id << 8) | motor_id:
Packet |
id |
Payload |
Scale |
|---|---|---|---|
SET_DUTY |
0 |
int32 |
duty × 1e5 |
SET_CURRENT |
1 |
int32 |
A × 1000 |
SET_CURRENT_BRAKE |
2 |
int32 |
A × 1000, ≥ 0 |
SET_RPM |
3 |
int32 |
raw ERPM |
SET_POS |
4 |
int32 |
deg × 1e4 |
SET_ORIGIN |
5 |
uint8 |
0 temporary, 1 permanent |
SET_POS_SPD |
6 |
int32 + int16 + int16 |
deg × 1e4; spd/10; acc/10 |
SET_MIT |
8 |
undocumented |
in the enum, no payload given — not implemented |
Servo replies, by function id (arb_id >> 8) & 0xFF:
id |
meaning |
|---|---|
|
state |
|
entered servo mode, payload |
|
jump to bootloader |
Only 0x29 is state. Decoding 0x2C as a position yields a plausible-looking −128.5°.
The float↔uint asymmetry¶
The manual’s float_to_uint uses (1<<bits)/span and its uint_to_float uses
span/((1<<bits)-1). These are not inverses, and the pack direction overflows the field
at exactly x_max: 12.5 rad maps to 65536, which does not fit in 16 bits.
This library uses ((1<<bits)-1)/span with rounding — the exact inverse of the documented
unpack, never overflowing, and within 1 LSB of the manual’s formula everywhere else. The
simulator can decode either way (ScalingVariant.EXACT / TRUNCATED) so commands are
verified correct against both possible firmware readings.
Saturated commands collide with mode-control frames¶
With position, velocity, Kp and Kd all at maximum:
torque |
packs to |
driver reads it as |
|---|---|---|
4.99267 N·m |
|
enter MIT mode |
4.99512 N·m |
|
exit MIT mode |
4.99756 N·m |
|
set position to zero |
A controller winding up against its limits can reach that band, and a mid-motion re-zero
moves the position reference out from under the loop. pack_command steps one torque LSB
away — 2.4 mN·m, far below anything the motor can resolve.