VxMi 步进电机协议
适用于 Vx/Mi/Amorlinkvex 系列设备的步进电机控制协议
概述
VxMi Protocol 用于控制 Vx / Mi / Amorlinkvex 系列步进电机设备,支持位置(position)和速度(speed)的精确控制。该协议使用 Nordic UART Service (NUS) 作为通信通道。
适用设备
通过设备型号名称(modelName)前缀判断是否使用此协议:
Vx开头的设备Mi开头的设备Amorlinkvex开头的设备
BLE UUID
VxMi 使用 Nordic UART Service UUID:
| 特征 | UUID |
|---|---|
| Service | 6e400001-b5a3-f393-e0a9-e50e24dcca9e |
| Write (RX) | 6e400002-b5a3-f393-e0a9-e50e24dcca9e |
| Notify (TX) | 6e400003-b5a3-f393-e0a9-e50e24dcca9e |
// VxMiProtocol.ts
public static readonly SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'
public static readonly WRITE_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'
public static readonly NOTIFY_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'帧格式
+--------+--------+------+-------------------------+----------+
| Header | Length | Cmd | Payload | CRC16 |
| A5 5A | 1 byte | A0 | 变长 | 2 bytes |
+--------+--------+------+-------------------------+----------+| 字段 | 长度 | 说明 |
|---|---|---|
| Header | 2 字节 | 固定帧头 A5 5A |
| Length | 1 字节 | 整帧字节长度(含 Header 和 CRC) |
| Cmd | 1 字节 | 命令类型 |
| Payload | 变长 | 命令参数 |
| CRC16 | 2 字节 | CRC16-CCITT-FALSE 校验(高低字节交换) |
电机控制
参数映射
VxMi 接收两个主要参数,均为 0-100 的百分比值,内部映射为协议参数:
| 输入参数 | 输入范围 | 映射公式 | 输出范围 | 协议参数 |
|---|---|---|---|---|
| amplitude(幅度) | 0-100 | (amplitude / 100) * 10000 | 0-10000 | Position |
| vibration(振动) | 0-100 | (vibration / 100) * 255 | 0-255 | Speed |
控制帧结构
A5 5A 0D A0 B0 [Speed] A0 01 0F [Pos_HI] [Pos_LO] [CRC_HI] [CRC_LO]| 偏移 | 长度 | 值 | 说明 |
|---|---|---|---|
| 0-1 | 2 | A5 5A | 帧头 |
| 2 | 1 | 0D | 帧长度(13 字节) |
| 3 | 1 | A0 | 命令类型 |
| 4 | 1 | B0 | 子命令标记(速度控制) |
| 5 | 1 | Speed | 速度值 (0-255) |
| 6 | 1 | A0 | 参数标记 |
| 7 | 1 | 01 | 参数 |
| 8 | 1 | 0F | 参数 |
| 9 | 1 | Position High | 位置高字节 |
| 10 | 1 | Position Low | 位置低字节 |
| 11 | 1 | CRC16 High | 校验高字节 |
| 12 | 1 | CRC16 Low | 校验低字节 |
计算示例
amplitude=50, vibration=75:
Position = (50 / 100) * 10000 = 5000 = 0x1388
Speed = (75 / 100) * 255 = 191 = 0xBF
帧数据(不含 CRC): A5 5A 0D A0 B0 BF A0 01 0F 13 88
CRC16 计算范围: 0D A0 B0 BF A0 01 0F 13 88
CRC16 值: [CRC_HI] [CRC_LO]
完整帧: A5 5A 0D A0 B0 BF A0 01 0F 13 88 [CRC_HI] [CRC_LO]字节序转换
Position 使用 Big-Endian 传输。内部计算时需要从 Little-Endian 转换:
function littleToBigEndian(hexStr: string): string {
const bytes: string[] = []
for (let i = 0; i < hexStr.length; i += 2) {
bytes.push(hexStr.slice(i, i + 2))
}
return bytes.reverse().join('')
}
// 示例: "8813" -> "1388" (5000 的大端表示)设备信息查询
查询设备固件版本和状态的命令:
A5 5A 07 00 01 1E 90
偏移 0-1: A5 5A -- 帧头
偏移 2: 07 -- 帧长度(7 字节)
偏移 3: 00 -- 设备信息查询命令
偏移 4: 01 -- 参数
偏移 5-6: 1E 90 -- 预计算的 CRC16CRC16-CCITT-FALSE
参数
| 参数 | 值 |
|---|---|
| 多项式 | 0x1021 |
| 初始值 | 0xFFFF |
| 最终异或 | 0x0000 |
计算范围
CRC16 的计算范围为 Length 字节开始,到 CRC 字段之前,不包含 A5 5A 帧头和 CRC 本身。
算法实现
function crc16CCITT(data: Uint8Array): number {
let crc = 0xFFFF // 初始值
for (let i = 0; i < data.length; i++) {
crc ^= (data[i] << 8)
for (let j = 0; j < 8; j++) {
if (crc & 0x8000) {
crc = ((crc << 1) ^ 0x1021) & 0xFFFF
} else {
crc = (crc << 1) & 0xFFFF
}
}
}
return crc
}高低字节交换
CRC16 写入帧时需要交换高低字节:
function crc16Swapped(data: Uint8Array): [number, number] {
const crc = crc16CCITT(data)
const high = (crc >> 8) & 0xFF
const low = crc & 0xFF
return [high, low] // 注意:高字节在前
}设备通知
通知帧结构
设备回复的数据帧同样以 A5 5A 开头:
A5 5A [Length] ... [Payload] ... [CRC16]解析流程
- 验证帧头
A5 5A - 检查响应类型标记(偏移 8-10 包含
02) - 提取有效载荷(跳过帧头 10 字节和尾部 CRC 4 字节)
- 将 Hex 载荷转为 UTF-8 字符串
- 解析 JSON
返回数据结构
interface VxMiNotification {
type: 'status'
battery: number // 电量百分比 (0-100)
voltage: number // 电压值
firmwareVersion: string // 固件版本
mcu1Firmware: string // MCU1 固件版本
mcu2Firmware: string // MCU2 固件版本
mtu: number // MTU 值
rawData: Uint8Array // 原始数据
}电量计算
根据电压线性插值计算电量百分比:
function voltageToPercent(voltage: number): number {
const MIN_VOLTAGE = 3.0 // 0%
const MAX_VOLTAGE = 4.2 // 100%
if (voltage <= MIN_VOLTAGE) return 0
if (voltage >= MAX_VOLTAGE) return 100
return Math.round(
((voltage - MIN_VOLTAGE) / (MAX_VOLTAGE - MIN_VOLTAGE)) * 100
)
}
// 示例:
// 电压 3.6V => ((3.6 - 3.0) / (4.2 - 3.0)) * 100 = 50%完整使用示例
// 假设 VxMiProtocol 类已实现
import { VxMiProtocol } from '@/utils/bluetooth/protocol/VxMiProtocol'
// 1. 构造控制帧
// amplitude=80 (幅度80%), vibration=60 (振动60%)
// Position = 8000 = 0x1F40, Speed = 153 = 0x99
const controlFrame = VxMiProtocol.makeMotorControl(80, 60)
// 帧: A5 5A 0D A0 B0 99 A0 01 0F 1F 40 [CRC_HI] [CRC_LO]
// 2. 发送到设备
await unifiedBluetoothManager.write(controlFrame, connectedDevice)
// 3. 查询设备信息
const infoFrame = VxMiProtocol.makeDeviceInfoQuery()
// 帧: A5 5A 07 00 01 1E 90
await unifiedBluetoothManager.write(infoFrame, connectedDevice)
// 4. 停止电机
// amplitude=0, vibration=0
const stopFrame = VxMiProtocol.makeMotorControl(0, 0)
// Position = 0, Speed = 0
await unifiedBluetoothManager.write(stopFrame, connectedDevice)