MA900磁编码器真的太好用了
,几乎可以实现对电机的任何控制。
经过几天的学习,我轻松实现了用MA900控制无刷电机的正反转、速度、转几圈、并且转到指定的角度。可以看一下实际效果【5 分 20 秒!看懂无刷电机的控制原理与实战-哔哩哔哩】 https://b23.tv/BBr84Xs
MA900磁编码器真的太好用了
,几乎可以实现对电机的任何控制。
经过几天的学习,我轻松实现了用MA900控制无刷电机的正反转、速度、转几圈、并且转到指定的角度。可以看一下实际效果【5 分 20 秒!看懂无刷电机的控制原理与实战-哔哩哔哩】 https://b23.tv/BBr84Xs
关于驱动MPQ6547A和磁编MA900的数据手册,大家可以先看一下,对这两个关键的芯片有一个大致了解:
MPQ6547A
MA900
上层楼中间绿色电路板的电路图原理图如下,MPQ6537A通过外置电源直接供电,其实MPQ6547A的VG引脚是可以直接给MA900供电的,但我是第一次做这个东西,怕到时候焊接不好,不好找bug,用的是Arduino上3.3V引脚直接给MA900供电,大家如果想再改进的话,建议直接用VG引脚给MA900供电,这样电路连接还简单点。
MA900的I2C地址的后3位(2进制)由硬件定义,我让这三个引脚通过电阻接地,所以是000,最终I2C地址是0x60!
PCB图
3D预览
源文件请点击下面
:
然后就是程序部分,本项目采用的是Simple FOC库,这是它的官网:https://docs.simplefoc.com/
通过Simple foc库,即使我们不知道无刷电机的工作原理,也能在很短的时间内实现对无刷电机的控制。STM32和Arduino都支持,因为我这个不是做产品,直接用Arduino实现的。
首先需要在Arduino IDE里面安装一下Simple FOC的库,如下图所示,在左边这个小书架里面搜索Simple FOC,点击安装
接下来
就可以直接写程序控制无刷电机转动了,这个MA900功能非常丰富,I2C通讯带有CRC校验,我曾经花费很长时间把这个关掉一直不成功,因为我这的环境不是那种工业场合,只是简单的控制。
由于不能关掉,所以我这的所有程序的MA900的I2C通讯都带有crc校验。关于CRC校验怎么用,MA900的数据手册上写的也很清楚,这个可以自己查阅。
万事俱备了,我们就先来一个最简单的,不让电机转,先看看传感器有没有工作,当我们转动电机的时候,Arduino上会打印目前的角度值。
// MA900_I2C_QuickRead_CRC.ino
// 适用于 Arduino R4(或任何支持 Wire 的 Arduino)
// 读取 MA900 的 16-bit 角度(寄存器 0x60/0x61),并校验每个字节后的 CRC8(CRCEN = 1)
#include <Wire.h>
const uint8_t MA900_ADDR = 0x60; // 7-bit I2C 地址
const uint8_t ANGLE_LSB_REG = 0x60; // 寄存器起始地址
/* CRC8 查表:多项式 x^8 + x^2 + x + 1,初始值 0 (
const uint8_t crc8_table[256] = {
0x00,0x07,0x0e,0x09,0x1c,0x1b,0x12,0x15,0x38,0x3f,0x36,0x31,0x24,0x23,0x2a,0x2d,
0x70,0x77,0x7e,0x79,0x6c,0x6b,0x62,0x65,0x48,0x4f,0x46,0x41,0x54,0x53,0x5a,0x5d,
0xe0,0xe7,0xee,0xe9,0xfc,0xfb,0xf2,0xf5,0xd8,0xdf,0xd6,0xd1,0xc4,0xc3,0xca,0xcd,
0x90,0x97,0x9e,0x99,0x8c,0x8b,0x82,0x85,0xa8,0xaf,0xa6,0xa1,0xb4,0xb3,0xba,0xbd,
0xc7,0xc0,0xc9,0xce,0xdb,0xdc,0xd5,0xd2,0xff,0xf8,0xf1,0xf6,0xe3,0xe4,0xed,0xea,
0xb7,0xb0,0xb9,0xbe,0xab,0xac,0xa5,0xa2,0x8f,0x88,0x81,0x86,0x93,0x94,0x9d,0x9a,
0x27,0x20,0x29,0x2e,0x3b,0x3c,0x35,0x32,0x1f,0x18,0x11,0x16,0x03,0x04,0x0d,0x0a,
0x57,0x50,0x59,0x5e,0x4b,0x4c,0x45,0x42,0x6f,0x68,0x61,0x66,0x73,0x74,0x7d,0x7a,
0x89,0x8e,0x87,0x80,0x95,0x92,0x9b,0x9c,0xb1,0xb6,0xbf,0xb8,0xad,0xaa,0xa3,0xa4,
0xf9,0xfe,0xf7,0xf0,0xe5,0xe2,0xeb,0xec,0xc1,0xc6,0xcf,0xc8,0xdd,0xda,0xd3,0xd4,
0x69,0x6e,0x67,0x60,0x75,0x72,0x7b,0x7c,0x51,0x56,0x5f,0x58,0x4d,0x4a,0x43,0x44,
0x19,0x1e,0x17,0x10,0x05,0x02,0x0b,0x0c,0x21,0x26,0x2f,0x28,0x3d,0x3a,0x33,0x34,
0x4e,0x49,0x40,0x47,0x52,0x55,0x5c,0x5b,0x76,0x71,0x78,0x7f,0x6a,0x6d,0x64,0x63,
0x3e,0x39,0x30,0x37,0x22,0x25,0x2c,0x2b,0x06,0x01,0x08,0x0f,0x1a,0x1d,0x14,0x13,
0xae,0xa9,0xa0,0xa7,0xb2,0xb5,0xbc,0xbb,0x96,0x91,0x98,0x9f,0x8a,0x8d,0x84,0x83,
0xde,0xd9,0xd0,0xd7,0xc2,0xc5,0xcc,0xcb,0xe6,0xe1,0xe8,0xef,0xfa,0xfd,0xf4,0xf3
};
uint8_t crc8(uint8_t crc8_curr, uint8_t data) {
return crc8_table[crc8_curr ^ data];
}
void setup() {
Serial.begin(115200);
while (!Serial) ; // wait for Serial on some boards
Wire.begin(); // Arduino R4 默认 SDA/SCL 引脚
// 可尝试加速到 1MHz(MA900 支持),如果你的硬件/线长允许:
// Wire.setClock(1000000);
Wire.setClock(400000); // 先用 400kHz,稳定
Serial.println("MA900 I2C Quick Read with CRC example");
}
void loop() {
// Quick Read: 请求 ANGLE LSB(0x60) 和 ANGLE MSB(0x61)
// 当 CRCEN=1,设备会在每个字节后附加 CRC8 -> 总共 4 字节: LSB, CRC0, MSB, CRC1
const uint8_t toRead = 4;
Wire.requestFrom((uint8_t)MA900_ADDR, toRead); // Quick Read: 直接读(不先写寄存器)
unsigned long t0 = millis();
while (Wire.available() < toRead && (millis() - t0) < 50) {
// 等待最多 50ms
}
if (Wire.available() < toRead) {
Serial.println("I2C read timeout / not enough bytes");
delay(200);
return;
}
uint8_t angle_lsb = Wire.read();
uint8_t crc0 = Wire.read();
uint8_t angle_msb = Wire.read();
uint8_t crc1 = Wire.read();
// 逐个寄存器校验 CRC(按手册 Quick Read 的 CRC 算法)
bool crc_ok0 = false, crc_ok1 = false;
// 对第 0 个寄存器(addr = 0x60)
{
uint8_t c = 0;
c = crc8(c, (MA900_ADDR << 1) | 0x01); // (addr<<1)+1 : master is in read mode for Quick Read
c = crc8(c, ANGLE_LSB_REG + 0); // register address (0x60 + i)
c = crc8(c, angle_lsb);
crc_ok0 = (c == crc0);
}
// 对第 1 个寄存器(addr = 0x61)
{
uint8_t c = 0;
c = crc8(c, (MA900_ADDR << 1) | 0x01);
c = crc8(c, ANGLE_LSB_REG + 1);
c = crc8(c, angle_msb);
crc_ok1 = (c == crc1);
}
if (!crc_ok0 || !crc_ok1) {
Serial.print("CRC fail: crc0=");
Serial.print(crc0, HEX);
Serial.print(" calc0=");
{
uint8_t c = 0;
c = crc8(c, (MA900_ADDR << 1) | 0x01);
c = crc8(c, ANGLE_LSB_REG + 0);
c = crc8(c, angle_lsb);
Serial.print(c, HEX);
}
Serial.print(" | crc1=");
Serial.print(crc1, HEX);
Serial.print(" calc1=");
{
uint8_t c = 0;
c = crc8(c, (MA900_ADDR << 1) | 0x01);
c = crc8(c, ANGLE_LSB_REG + 1);
c = crc8(c, angle_msb);
Serial.print(c, HEX);
}
Serial.println();
// 如果你需要即便 CRC 错也继续使用原始数据,取消下面的 return
delay(200);
return;
}
uint16_t angle_raw = ((uint16_t)angle_msb << 8) | angle_lsb;
float angle_deg = (float)angle_raw * 360.0f / 65536.0f; // 0..65535 -> 0..360°
Serial.print("绝对角度值:");
Serial.print(angle_deg, 4);
Serial.println("°");
delay(200); // 读频率随你需求调整,手册建议测率在 kHz 级,注意不要太频繁触发 I2C 总线冲突
}
(CRC校验有两种,一种是传统的/一种是快读方式,下面这个程序用的快读方式,这样能简化很多程序步骤)
#include <SimpleFOC.h>
#include <Wire.h>
//以恒定的速度转动 闭环控制
// ================== MA900 传感器实现 ==================
const uint8_t MA900_ADDR = 0x60;
const uint8_t MA900_ANGLE_REG = 0x60; // Quick Read 起始寄存器
// CRC8 查表(多项式 0x07)
const uint8_t crc8_table[256] = {
0x00,0x07,0x0E,0x09,0x1C,0x1B,0x12,0x15,0x38,0x3F,0x36,0x31,0x24,0x23,0x2A,0x2D,
0x70,0x77,0x7E,0x79,0x6C,0x6B,0x62,0x65,0x48,0x4F,0x46,0x41,0x54,0x53,0x5A,0x5D,
0xE0,0xE7,0xEE,0xE9,0xFC,0xFB,0xF2,0xF5,0xD8,0xDF,0xD6,0xD1,0xC4,0xC3,0xCA,0xCD,
0x90,0x97,0x9E,0x99,0x8C,0x8B,0x82,0x85,0xA8,0xAF,0xA6,0xA1,0xB4,0xB3,0xBA,0xBD,
0xC7,0xC0,0xC9,0xCE,0xDB,0xDC,0xD5,0xD2,0xFF,0xF8,0xF1,0xF6,0xE3,0xE4,0xED,0xEA,
0xB7,0xB0,0xB9,0xBE,0xAB,0xAC,0xA5,0xA2,0x8F,0x88,0x81,0x86,0x93,0x94,0x9D,0x9A,
0x27,0x20,0x29,0x2E,0x3B,0x3C,0x35,0x32,0x1F,0x18,0x11,0x16,0x03,0x04,0x0D,0x0A,
0x57,0x50,0x59,0x5E,0x4B,0x4C,0x45,0x42,0x6F,0x68,0x61,0x66,0x73,0x74,0x7D,0x7A,
0x89,0x8E,0x87,0x80,0x95,0x92,0x9B,0x9C,0xB1,0xB6,0xBF,0xB8,0xAD,0xAA,0xA3,0xA4,
0xF9,0xFE,0xF7,0xF0,0xE5,0xE2,0xEB,0xEC,0xC1,0xC6,0xCF,0xC8,0xDD,0xDA,0xD3,0xD4,
0x69,0x6E,0x67,0x60,0x75,0x72,0x7B,0x7C,0x51,0x56,0x5F,0x58,0x4D,0x4A,0x43,0x44,
0x19,0x1E,0x17,0x10,0x05,0x02,0x0B,0x0C,0x21,0x26,0x2F,0x28,0x3D,0x3A,0x33,0x34,
0x4E,0x49,0x40,0x47,0x52,0x55,0x5C,0x5B,0x76,0x71,0x78,0x7F,0x6A,0x6D,0x64,0x63,
0x3E,0x39,0x30,0x37,0x22,0x25,0x2C,0x2B,0x06,0x01,0x08,0x0F,0x1A,0x1D,0x14,0x13,
0xAE,0xA9,0xA0,0xA7,0xB2,0xB5,0xBC,0xBB,0x96,0x91,0x98,0x9F,0x8A,0x8D,0x84,0x83,
0xDE,0xD9,0xD0,0xD7,0xC2,0xC5,0xCC,0xCB,0xE6,0xE1,0xE8,0xEF,0xFA,0xFD,0xF4,0xF3
};
// 自定义传感器类,继承 SimpleFOC 的 Sensor
class MA900Sensor : public Sensor {
public:
MA900Sensor(uint8_t addr = MA900_ADDR) : address(addr) {}
// 公共 init:内部调用受保护的 Sensor::init()
void init() {
// I2C 初始化(你外面已经 Wire.begin() 过也没关系)
Wire.begin();
Wire.setClock(400000); // 400k 稳一点
// 设置一次寄存器指针到角度寄存器,这样 Quick Read 从0x60开始
Wire.beginTransmission(address);
Wire.write(MA900_ANGLE_REG);
Wire.endTransmission();
last_valid_angle = 0.0f;
Sensor::init(); // 让基类把 timestamp 啥的初始化好
}
// 核心:返回机械角度(0~2PI)
float getSensorAngle() override {
float ang;
if (readAngleRad(ang)) {
last_valid_angle = ang;
}
// 如果这次读失败,就返回上一次的,避免乱跳
return last_valid_angle;
}
private:
uint8_t address;
float last_valid_angle = 0.0f;
bool readAngleRad(float &angle_rad) {
Wire.requestFrom(address, (uint8_t)4);
if (Wire.available() < 4) {
return false;
}
uint8_t lsb = Wire.read();
uint8_t crc0 = Wire.read();
uint8_t msb = Wire.read();
uint8_t crc1 = Wire.read();
// ---- CRC 校验 LSB ----
uint8_t c0 = 0;
c0 = crc8_table[c0 ^ ((address << 1) | 1)]; // 读地址
c0 = crc8_table[c0 ^ MA900_ANGLE_REG]; // reg 0x60
c0 = crc8_table[c0 ^ lsb];
// ---- CRC 校验 MSB ----
uint8_t c1 = 0;
c1 = crc8_table[c1 ^ ((address << 1) | 1)];
c1 = crc8_table[c1 ^ (MA900_ANGLE_REG + 1)]; // reg 0x61
c1 = crc8_table[c1 ^ msb];
if (c0 != crc0 || c1 != crc1) {
// CRC 不通过,丢弃本次数据
return false;
}
uint16_t raw = ((uint16_t)msb << 8) | lsb; // 0~65535
angle_rad = raw * _2PI / 65536.0f; // 映射到 0~2PI
return true;
}
};
// ================== 电机 & 驱动 ==================
// 7 极对 BLDC
BLDCMotor motor = BLDCMotor(7);
// 3PWM 驱动:相位 A/B/C 用 9/10/11
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 10, 11);
// 使能脚 5、6
const int ENA_PIN = 5;
const int ENB_PIN = 6;
// 我们的 MA900 传感器实例
MA900Sensor ma900;
// 目标速度(rad/s)
float target_velocity = 3.0f; // 你可以改成 2.0 / 3.0 先试
unsigned long last_print = 0;
void setup() {
Serial.begin(115200);
// I2C 初始化(保证 Wire 可用,MA900Sensor 内部也会再调用一次没关系)
Wire.begin();
Wire.setClock(400000);
// 使能脚拉高
pinMode(ENA_PIN, OUTPUT);
pinMode(ENB_PIN, OUTPUT);
digitalWrite(ENA_PIN, HIGH);
digitalWrite(ENB_PIN, HIGH);
// 先初始化传感器
ma900.init();
// 驱动初始化
driver.voltage_power_supply = 5; // 按你的电源改,比如 12V
driver.init();
// 将驱动和传感器链接到电机
motor.linkDriver(&driver);
motor.linkSensor(&ma900);
// ========== 电机参数配置 ==========
// 闭环速度控制
motor.controller = MotionControlType::velocity;
// 限制电压和速度,先温柔一点
motor.voltage_limit = 3.0f; // 电压上限,嫌力矩大可以改 2.0
motor.velocity_limit = 500.0f; // 最大速度上限,单位 rad/s
// 速度环 PID 参数,可以后面慢慢调
motor.PID_velocity.P = 0.5f;
motor.PID_velocity.I = 20.0f;
motor.PID_velocity.D = 0.0f;
motor.PID_velocity.output_ramp = 1000.0f; // 输出变化速度限制
motor.LPF_velocity.Tf = 0.01f; // 速度滤波时间常数
// 如果你要用角度控制时会用到
motor.P_angle.P = 20.0f;
// FOC 调制方式
motor.foc_modulation = FOCModulationType::SinePWM;
// 初始化电机
motor.init();
// 关键:FOC 对齐(这里会用到 MA900 的角度)
Serial.println("Running FOC init...");
motor.initFOC();
Serial.println("FOC init done. Closed-loop velocity control with MA900 ready!");
}
void loop() {
// 1. FOC 算磁场方向(闭环)
motor.loopFOC();
// 2. 闭环速度控制:目标速度是 target_velocity
motor.move(target_velocity);
// 3. 打印一点调试信息
if (millis() - last_print > 200) {
last_print = millis();
Serial.print("shaft_angle(deg)=");
Serial.print(motor.shaft_angle * 180.0f / PI, 1);
Serial.print(" shaft_velocity(rad/s)=");
Serial.print(motor.shaft_velocity, 2);
Serial.print(" target_vel=");
Serial.println(target_velocity, 2);
}
}
#include <SimpleFOC.h>
#include <Wire.h>
// ================== MA900 传感器实现(带 CRC) ==================
//完全闭环的情况下让电机转三圈 通过MA900
const uint8_t MA900_ADDR = 0x60; // 7bit 地址
const uint8_t MA900_ANGLE_REG = 0x60; // Quick Read 起始寄存器
// CRC8 查表(多项式 0x07)
const uint8_t crc8_table[256] = {
0x00,0x07,0x0E,0x09,0x1C,0x1B,0x12,0x15,0x38,0x3F,0x36,0x31,0x24,0x23,0x2A,0x2D,
0x70,0x77,0x7E,0x79,0x6C,0x6B,0x62,0x65,0x48,0x4F,0x46,0x41,0x54,0x53,0x5A,0x5D,
0xE0,0xE7,0xEE,0xE9,0xFC,0xFB,0xF2,0xF5,0xD8,0xDF,0xD6,0xD1,0xC4,0xC3,0xCA,0xCD,
0x90,0x97,0x9E,0x99,0x8C,0x8B,0x82,0x85,0xA8,0xAF,0xA6,0xA1,0xB4,0xB3,0xBA,0xBD,
0xC7,0xC0,0xC9,0xCE,0xDB,0xDC,0xD5,0xD2,0xFF,0xF8,0xF1,0xF6,0xE3,0xE4,0xED,0xEA,
0xB7,0xB0,0xB9,0xBE,0xAB,0xAC,0xA5,0xA2,0x8F,0x88,0x81,0x86,0x93,0x94,0x9D,0x9A,
0x27,0x20,0x29,0x2E,0x3B,0x3C,0x35,0x32,0x1F,0x18,0x11,0x16,0x03,0x04,0x0D,0x0A,
0x57,0x50,0x59,0x5E,0x4B,0x4C,0x45,0x42,0x6F,0x68,0x61,0x66,0x73,0x74,0x7D,0x7A,
0x89,0x8E,0x87,0x80,0x95,0x92,0x9B,0x9C,0xB1,0xB6,0xBF,0xB8,0xAD,0xAA,0xA3,0xA4,
0xF9,0xFE,0xF7,0xF0,0xE5,0xE2,0xEB,0xEC,0xC1,0xC6,0xCF,0xC8,0xDD,0xDA,0xD3,0xD4,
0x69,0x6E,0x67,0x60,0x75,0x72,0x7B,0x7C,0x51,0x56,0x5F,0x58,0x4D,0x4A,0x43,0x44,
0x19,0x1E,0x17,0x10,0x05,0x02,0x0B,0x0C,0x21,0x26,0x2F,0x28,0x3D,0x3A,0x33,0x34,
0x4E,0x49,0x40,0x47,0x52,0x55,0x5C,0x5B,0x76,0x71,0x78,0x7F,0x6A,0x6D,0x64,0x63,
0x3E,0x39,0x30,0x37,0x22,0x25,0x2C,0x2B,0x06,0x01,0x08,0x0F,0x1A,0x1D,0x14,0x13,
0xAE,0xA9,0xA0,0xA7,0xB2,0xB5,0xBC,0xBB,0x96,0x91,0x98,0x9F,0x8A,0x8D,0x84,0x83,
0xDE,0xD9,0xD0,0xD7,0xC2,0xC5,0xCC,0xCB,0xE6,0xE1,0xE8,0xEF,0xFA,0xFD,0xF4,0xF3
};
inline uint8_t crc8_update(uint8_t crc, uint8_t data) {
return crc8_table[crc ^ data];
}
// 自定义传感器类,接入 SimpleFOC
class MA900Sensor : public Sensor {
public:
MA900Sensor(uint8_t addr = MA900_ADDR) : address(addr) {}
void init() {
// I2C 在外面也会 init,这里再设一次也没关系
Wire.begin();
Wire.setClock(400000);
// 可选:把内部指针指到 0x60,一般 Quick Read 也不用,但这样更保险
Wire.beginTransmission(address);
Wire.write(MA900_ANGLE_REG);
Wire.endTransmission();
last_valid_angle = 0.0f;
Sensor::init(); // 让 SimpleFOC 初始化时间戳等
}
// 返回机械角度(0~2PI)
float getSensorAngle() override {
float ang;
if (readAngleRadCRC(ang)) {
last_valid_angle = ang;
}
// CRC 错/读不到时,返回上一次的值,避免跳变
return last_valid_angle;
}
private:
uint8_t address;
float last_valid_angle = 0.0f;
bool readAngleRadCRC(float &angle_rad) {
// MA900 Quick Read: 直接读 4 字节:LSB, CRC0, MSB, CRC1
Wire.requestFrom((int)address, 4);
if (Wire.available() < 4) {
return false;
}
uint8_t lsb = Wire.read();
uint8_t crc0 = Wire.read();
uint8_t msb = Wire.read();
uint8_t crc1 = Wire.read();
// CRC 校验 LSB:addr(读) + reg(0x60) + lsb
uint8_t c0 = 0;
c0 = crc8_update(c0, (address << 1) | 1); // 读地址
c0 = crc8_update(c0, MA900_ANGLE_REG); // 0x60
c0 = crc8_update(c0, lsb);
// CRC 校验 MSB:addr(读) + reg+1(0x61) + msb
uint8_t c1 = 0;
c1 = crc8_update(c1, (address << 1) | 1);
c1 = crc8_update(c1, (uint8_t)(MA900_ANGLE_REG + 1)); // 0x61
c1 = crc8_update(c1, msb);
if (c0 != crc0 || c1 != crc1) {
// CRC 错,丢掉本次数据
return false;
}
uint16_t raw = ((uint16_t)msb << 8) | lsb; // 0~65535
angle_rad = raw * _2PI / 65536.0f; // 映射到 0~2PI
return true;
}
};
// ================== 电机 & 驱动 ==================
BLDCMotor motor = BLDCMotor(7); // 7 极对
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 10, 11);
const int ENA_PIN = 5;
const int ENB_PIN = 6;
MA900Sensor ma900;
// 目标:3 圈
const float TARGET_TURNS = 3.0f;
// 巡航速度
const float CRUISE_VEL = 5.0f; // rad/s
// 从多少圈开始减速(可以自己调)
const float DECEL_START_TURNS = 2.7f;
// 软起步 / 软刹车
float current_vel = 0.0f;
const float VEL_STEP = 0.1f; // 每次 loop 速度最大变化(越小越温柔)
// 统计圈数:用 shaft_angle 差值来算
float start_shaft_angle = 0.0f;
// 状态
bool motion_done = false;
float final_turns = 0.0f;
unsigned long last_print = 0;
void setup() {
Serial.begin(115200);
// I2C
Wire.begin();
Wire.setClock(400000);
// EN 脚
pinMode(ENA_PIN, OUTPUT);
pinMode(ENB_PIN, OUTPUT);
digitalWrite(ENA_PIN, HIGH);
digitalWrite(ENB_PIN, HIGH);
// 初始化 MA900(带 CRC)
ma900.init();
// 驱动
driver.voltage_power_supply = 12; // 根据你的电源电压改,比如 12V
driver.init();
// 关联到电机
motor.linkDriver(&driver);
motor.linkSensor(&ma900);
// 真闭环:速度模式
motor.controller = MotionControlType::velocity;
motor.voltage_limit = 3.0f; // 觉得力矩大可以改小一点 2.0f
motor.velocity_limit = 50.0f;
// 速度环 PID,可以后面慢慢调
motor.PID_velocity.P = 0.5f;
motor.PID_velocity.I = 20.0f;
motor.PID_velocity.D = 0.0f;
motor.PID_velocity.output_ramp = 1000.0f;
motor.LPF_velocity.Tf = 0.01f;
// 角度环先留着,以后玩位置控制用
motor.P_angle.P = 20.0f;
motor.foc_modulation = FOCModulationType::SinePWM;
motor.init();
Serial.println("Running FOC init (会轻微抖动校准)...");
motor.initFOC(); // ★ 必然会小幅正反扭一扭,这是在标定零点和方向
Serial.println("FOC init done. Closed-loop CRC 3-turn test.");
// 标定完成后,把“开始的 shaft_angle”记录下来
start_shaft_angle = motor.shaft_angle;
current_vel = 0.0f;
}
void loop() {
if (!motion_done) {
// 1. 真闭环:用 MA900 + CRC 算磁场方向
motor.loopFOC();
// 2. 计算已经转了多少圈(用 shaft_angle 和起点差值)
float shaft = motor.shaft_angle; // 连续弧度
float turns = (shaft - start_shaft_angle) / _2PI;
// 3. 根据圈数决定目标速度:先匀速,再减速,再停
float target_vel;
float abs_turns = fabs(turns);
if (abs_turns < DECEL_START_TURNS) {
// 0 ~ DECEL_START_TURNS:用巡航速度
target_vel = CRUISE_VEL;
} else if (abs_turns < TARGET_TURNS) {
// 减速区间:线性从 CRUISE_VEL 降到 0
float k = (TARGET_TURNS - abs_turns) / (TARGET_TURNS - DECEL_START_TURNS); // 0~1
if (k < 0) k = 0;
target_vel = CRUISE_VEL * k;
} else {
target_vel = 0.0f;
}
// 4. 软起步/软刹车:current_vel 慢慢追 target_vel,避免突然冲一下
if (current_vel < target_vel) {
current_vel += VEL_STEP;
if (current_vel > target_vel) current_vel = target_vel;
} else if (current_vel > target_vel) {
current_vel -= VEL_STEP;
if (current_vel < target_vel) current_vel = target_vel;
}
// 5. 当目标速度为 0 且当前速度已经很小,同时圈数 >= 3,就认为完成
if (target_vel == 0.0f && fabs(current_vel) < 0.05f && abs_turns >= TARGET_TURNS) {
current_vel = 0.0f;
final_turns = turns;
motion_done = true;
// 停止控制并彻底断电
motor.move(0.0f);
motor.disable();
driver.disable();
digitalWrite(ENA_PIN, LOW);
digitalWrite(ENB_PIN, LOW);
Serial.println("=== 3 turns done, motor fully disabled (CRC OK) ===");
} else {
// 6. 正常闭环速度控制
motor.move(current_vel);
}
// 7. 串口打印
if (millis() - last_print > 200) {
last_print = millis();
Serial.print("shaft_angle(deg)=");
Serial.print(shaft * 180.0f / PI, 1);
Serial.print(" turns=");
Serial.print(turns, 3);
Serial.print(" current_vel=");
Serial.println(current_vel, 2);
}
} else {
// 已经完成三圈:不再调用 loopFOC / move,电机完全放松,只打印最终圈数
if (millis() - last_print > 500) {
last_print = millis();
Serial.print("turns(final)=");
Serial.println(final_turns, 3);
}
}
}
让电机转到多少度,并且在这个角度停留,即使是你把它拧一拧
,它还能自动回正,通过串口通讯控制它的角度,比如你给他发0,它就转到0度的位置。
#include <SimpleFOC.h>
#include <Wire.h>
#include "CRC.h"
// ================== MA900 快读 + CRC ==================
//可以完美运行版本 非常顺滑(启动的一刻钟会稍微震荡)当给串口角度时,它就能转到这个角度 后面即使是转到其它角度,他依旧能回到这个角度
#define MA900_ADDR 0x60
const uint8_t crc8_table[256] = {
0x00,0x07,0x0E,0x09,0x1C,0x1B,0x12,0x15,0x38,0x3F,0x36,0x31,0x24,0x23,0x2A,0x2D,
0x70,0x77,0x7E,0x79,0x6C,0x6B,0x62,0x65,0x48,0x4F,0x46,0x41,0x54,0x53,0x5A,0x5D,
0xE0,0xE7,0xEE,0xE9,0xFC,0xFB,0xF2,0xF5,0xD8,0xDF,0xD6,0xD1,0xC4,0xC3,0xCA,0xCD,
0x90,0x97,0x9E,0x99,0x8C,0x8B,0x82,0x85,0xA8,0xAF,0xA6,0xA1,0xB4,0xB3,0xBA,0xBD,
0xC7,0xC0,0xC9,0xCE,0xDB,0xDC,0xD5,0xD2,0xFF,0xF8,0xF1,0xF6,0xE3,0xE4,0xED,0xEA,
0xB7,0xB0,0xB9,0xBE,0xAB,0xAC,0xA5,0xA2,0x8F,0x88,0x81,0x86,0x93,0x94,0x9D,0x9A,
0x27,0x20,0x29,0x2E,0x3B,0x3C,0x35,0x32,0x1F,0x18,0x11,0x16,0x03,0x04,0x0D,0x0A,
0x57,0x50,0x59,0x5E,0x4B,0x4C,0x45,0x42,0x6F,0x68,0x61,0x66,0x73,0x74,0x7D,0x7A,
0x89,0x8E,0x87,0x80,0x95,0x92,0x9B,0x9C,0xB1,0xB6,0xBF,0xB8,0xAD,0xAA,0xA3,0xA4,
0xF9,0xFE,0xF7,0xF0,0xE5,0xE2,0xEB,0xEC,0xC1,0xC6,0xCF,0xC8,0xDD,0xDA,0xD3,0xD4,
0x69,0x6E,0x67,0x60,0x75,0x72,0x7B,0x7C,0x51,0x56,0x5F,0x58,0x4D,0x4A,0x43,0x44,
0x19,0x1E,0x17,0x10,0x05,0x02,0x0B,0x0C,0x21,0x26,0x2F,0x28,0x3D,0x3A,0x33,0x34,
0x4E,0x49,0x40,0x47,0x52,0x55,0x5C,0x5B,0x76,0x71,0x78,0x7F,0x6A,0x6D,0x64,0x63,
0x3E,0x39,0x30,0x37,0x22,0x25,0x2C,0x2B,0x06,0x01,0x08,0x0F,0x1A,0x1D,0x14,0x13,
0xAE,0xA9,0xA0,0xA7,0xB2,0xB5,0xBC,0xBB,0x96,0x91,0x98,0x9F,0x8A,0x8D,0x84,0x83,
0xDE,0xD9,0xD0,0xD7,0xC2,0xC5,0xCC,0xCB,0xE6,0xE1,0xE8,0xEF,0xFA,0xFD,0xF4,0xF3
};
uint8_t crc8_calc(uint8_t crc, uint8_t data) {
return crc8_table[crc ^ data];
}
// 读取 MA900 绝对角(rad),返回 true=OK
bool ma900_read_angle_rad(float &rad) {
Wire.requestFrom(MA900_ADDR, 4);
if (Wire.available() < 4) {
return false;
}
uint8_t L = Wire.read();
uint8_t C0 = Wire.read();
uint8_t H = Wire.read();
uint8_t C1 = Wire.read();
// CRC0
uint8_t crc0 = 0;
crc0 = crc8_calc(crc0, (MA900_ADDR<<1)|1);
crc0 = crc8_calc(crc0, 0x60);
crc0 = crc8_calc(crc0, L);
if (crc0 != C0) {
return false;
}
// CRC1
uint8_t crc1 = 0;
crc1 = crc8_calc(crc1, (MA900_ADDR<<1)|1);
crc1 = crc8_calc(crc1, 0x61);
crc1 = crc8_calc(crc1, H);
if (crc1 != C1) {
return false;
}
uint16_t raw = ((uint16_t)H << 8) | L;
rad = raw * _2PI / 65536.0f; // 0~2π
return true;
}
// ================== GenericSensor 回调(只要角度) ==================
float ma900_last_angle = 0.0f;
// 给 SimpleFOC 用的角度回调(rad)
float ma900_getAngle() {
float r;
if (ma900_read_angle_rad(r)) {
ma900_last_angle = r;
}
return ma900_last_angle;
}
// SimpleFOC 通用传感器(只传角度回调)
GenericSensor sensor = GenericSensor(ma900_getAngle);
// ================== 电机 & 驱动 ==================
BLDCMotor motor(7); // 7 极对数
BLDCDriver3PWM driver(9, 10, 11); // 3PWM:9/10/11
// 驱动使能脚
const int ENA_PIN = 5;
const int ENB_PIN = 6;
// 位置外环参数:根据误差生成目标速度
const float Kp_pos = 1.0f; // ☆ 原来 1.5,稍微减小一点
const float VEL_MAX = 5.0f; // 最大目标速度 (rad/s)
// 串口命令的“期望绝对角”(MA900坐标系,rad)
float cmd_abs_angle = 0.0f;
// 实际用于控制的“平滑后的目标绝对角”(rad)
float target_abs_angle = 0.0f;
unsigned long last_print = 0;
// 状态机:上电先 HOMING,之后 READY(等待串口命令)/MOVING
enum { STATE_HOMING = 0, STATE_READY = 1, STATE_MOVING = 2 };
int state = STATE_HOMING;
// 回零判定
const float HOMING_TOL_RAD = 1.0f * PI / 180.0f; // 1 度误差内算到位
const unsigned long HOMING_STABLE_MS = 500; // 连续 500ms 在范围内才算真正到位
unsigned long homing_in_tol_since = 0;
// 每次循环目标角度最多变化的角度:1度(防止刚开始猛冲)
const float TARGET_SLEW_STEP_RAD = 1.0f * PI / 180.0f; // 1 度/循环
// 角度 wrap 到 [-PI, PI]
float wrapToPi(float x) {
while (x > PI) x -= _2PI;
while (x < -PI) x += _2PI;
return x;
}
// 平滑把 target_abs_angle 追到 cmd_abs_angle(限制每次变化幅度)
void updateSmoothTarget() {
float diff = wrapToPi(cmd_abs_angle - target_abs_angle);
if (fabs(diff) <= TARGET_SLEW_STEP_RAD) {
target_abs_angle = cmd_abs_angle;
} else {
target_abs_angle += (diff > 0 ? TARGET_SLEW_STEP_RAD : -TARGET_SLEW_STEP_RAD);
}
}
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.setClock(400000);
Serial.println("=== MA900 + SimpleFOC ===");
Serial.println("功能:上电自动回到 MA900 絶対0度,然后串口输入角度(单位:度),电机转到该绝对角度。");
Serial.println("(已加入目标角平滑,减少刚开始的震荡)");
// 简单测一下面 MA900
delay(200);
Serial.println("MA900 quick test:");
for (int i = 0; i < 5; i++) {
float r;
if (ma900_read_angle_rad(r)) {
Serial.print(" ANGLE = ");
Serial.print(r * 180.0f / PI, 3);
Serial.println(" deg");
} else {
Serial.println(" MA900 READ FAIL");
}
delay(50);
}
Serial.println("---- end of MA900 test ----");
// 打开驱动使能(5、6 拉高)
pinMode(ENA_PIN, OUTPUT);
pinMode(ENB_PIN, OUTPUT);
digitalWrite(ENA_PIN, HIGH);
digitalWrite(ENB_PIN, HIGH);
// 驱动:5V 供电
driver.voltage_power_supply = 5.0f;
driver.init();
// 传感器 & 电机
sensor.init();
motor.linkSensor(&sensor);
motor.linkDriver(&driver);
// 速度闭环(外环自己做位置控制)
motor.controller = MotionControlType::velocity;
motor.voltage_limit = 2.0f; // 别超过供电电压
motor.velocity_limit = VEL_MAX;
// 速度环 PID(根据你之前“顺滑”的体验微调)
motor.PID_velocity.P = 0.3f;
motor.PID_velocity.I = 8.0f;
motor.PID_velocity.D = 0;
motor.LPF_velocity.Tf = 0.02f;
motor.init();
Serial.println("Init FOC...");
// 如果发现转向反了,可以改成 CCW 再试:
// motor.sensor_direction = Direction::CCW;
motor.initFOC();
Serial.println("FOC ready.");
// 上电之后,先把串口命令角 & 目标角都设为 0 度(homing)
cmd_abs_angle = 0.0f;
target_abs_angle = 0.0f;
state = STATE_HOMING;
homing_in_tol_since = 0;
}
// 处理串口命令:读取一行,将其当“目标绝对角度(度)”
void handleSerialCommand() {
if (!Serial.available()) return;
String line = Serial.readStringUntil('\n');
line.trim();
if (line.length() == 0) return;
float deg = line.toFloat(); // 允许负数,比如 -90
float new_cmd = deg * PI / 180.0f;
// 正规化到 [0, 2π),方便看(可选)
while (new_cmd >= _2PI) new_cmd -= _2PI;
while (new_cmd < 0.0f) new_cmd += _2PI;
cmd_abs_angle = new_cmd; // ☆ 只改命令角,由 updateSmoothTarget() 慢慢追
Serial.print("[CMD] New cmd abs angle = ");
Serial.print(deg, 3);
Serial.print(" deg (normalized = ");
Serial.print(cmd_abs_angle * 180.0f / PI, 3);
Serial.println(" deg)");
state = STATE_MOVING; // 进入“按命令转动”状态
}
void loop() {
// FOC 内环
motor.loopFOC();
// 处理串口命令(随时可以发角度命令)
handleSerialCommand();
// 更新平滑后的目标绝对角
updateSmoothTarget();
// 读取当前 MA900 绝对角
float abs_angle = ma900_getAngle(); // 0~2π
float error = wrapToPi(target_abs_angle - abs_angle); // 误差 [-π, π]
// ---- 状态机:上电先 HOMING,之后 READY/MOVING ----
if (state == STATE_HOMING) {
// 检查是否已经回到 0 度附近
if (fabs(error) < HOMING_TOL_RAD) {
if (homing_in_tol_since == 0) {
homing_in_tol_since = millis();
} else if (millis() - homing_in_tol_since > HOMING_STABLE_MS) {
// 连续一段时间都在误差范围内,认为回正完成
state = STATE_READY;
Serial.print("[HOMED] abs_angle = ");
Serial.print(abs_angle * 180.0f / PI, 3);
Serial.println(" deg (已回到绝对零度附近)");
}
} else {
homing_in_tol_since = 0; // 超出范围,重新计时
}
}
// 位置外环:根据误差生成目标速度
float vel_target = Kp_pos * error;
// 限制最大速度
if (vel_target > VEL_MAX) vel_target = VEL_MAX;
if (vel_target < -VEL_MAX) vel_target = -VEL_MAX;
// 误差很小时,就停下(避免抖动):这里 1 度
if (fabs(error) < (1.0f * PI / 180.0f)) {
vel_target = 0.0f;
}
// 速度闭环执行
motor.move(vel_target);
// 串口调试输出
if (millis() - last_print > 200) {
last_print = millis();
Serial.print("STATE=");
if (state == STATE_HOMING) Serial.print("HOMING");
else if (state == STATE_READY) Serial.print("READY");
else Serial.print("MOVING");
Serial.print(" abs(deg)=");
Serial.print(abs_angle * 180.0f / PI, 2);
Serial.print(" target_abs(deg)=");
Serial.print(target_abs_angle * 180.0f / PI, 2);
Serial.print(" cmd_abs(deg)=");
Serial.print(cmd_abs_angle * 180.0f / PI, 2);
Serial.print(" error(deg)=");
Serial.print(error * 180.0f / PI, 2);
Serial.print(" vel_target(rad/s)=");
Serial.println(vel_target, 2);
}
}
厉害!先点赞~
在这发现百万up ![]()