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

0x29

state

0x2C

entered servo mode, payload FA FB FC FD

0x09

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

FF FC

enter MIT mode

4.99512 N·m

FF FD

exit MIT mode

4.99756 N·m

FF FE

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.