2022年3月19日土曜日

M5ATOMでバランスロボ

//
// M5ATOM Balancing Robot V047
// Kiraku Labo, 2022
//
// 1. Power on and wait for a circle to shows up.
// 2. Hold the robot upright and still.
// 3. When a smile shows up, release the robot.
//
// Push Matrix button to switch between standig mode and walking mode.
//
#include <M5Atom.h>
#define SDA_PIN 26
#define SCL_PIN 32
#define MOTOR_R 0x65
#define MOTOR_L 0x63
#define BLU 0x0000ff
#define GRN 0xff0000
#define RED 0x00ff00
#define BLK 0x000000
boolean serialMonitor=false;
uint16_t counter=0, counterOverSpd=0, state=0, walkPhase=0;
uint32_t time0=0, time1=0;
float power, powerR, powerL, yawPower, maxPwr;
float varAng, varOmg, varSpd, varDst, varIang, aveAbsOmg=0.0, yawAng=0.0;
float Kang, Komg, KIang, Kyaw, Kdst, Kspd;
float gyroYdata, gyroZdata, accXdata, accXoffset, gyroZoffset;
const uint32_t interval = 10; // in msec
const float deltaT=(float)interval/1000.0; // in sec
float movePower=0.0, movePwrTarget, movePwrStep=1.0;
float mechFactorR, mechFactorL;
float spinStep, spinDest, spinTarget, spinFact=1.0;
bool walkMode=false;
const byte smile[] = {0,0,0,0,0,
0,1,0,1,0,
0,0,0,0,0,
1,0,0,0,1,
0,1,1,1,0};
const byte frown[] = {0,0,0,0,0,
0,1,0,1,0,
0,0,0,0,0,
0,1,1,1,0,
1,0,0,0,1};
const byte circle[] = {0,1,1,1,0,
1,0,0,0,1,
1,0,0,0,1,
1,0,0,0,1,
0,1,1,1,0};
const byte square[] = {0,0,0,0,0,
0,1,1,1,0,
0,1,0,1,0,
0,1,1,1,0,
0,0,0,0,0};
const byte cross[] = {1,0,0,0,1,
0,1,0,1,0,
0,0,1,0,0,
0,1,0,1,0,
1,0,0,0,1};
const byte heart[] = {0,1,0,1,0,
1,0,1,0,1,
1,0,0,0,1,
0,1,0,1,0,
0,0,1,0,0};
const byte all[] = {1,1,1,1,1,
1,1,1,1,1,
1,1,1,1,1,
1,1,1,1,1,
1,1,1,1,1};
void setup() {
M5.begin(true, true, true); //Serial, i2c(Wire1), disp
Wire.begin(SDA_PIN, SCL_PIN, 50000); //clock=50kHz
Wire.beginTransmission(MOTOR_R); //check motor driver
if (Wire.endTransmission()!=0) { //motor driver connection problem
ledDispImg(cross, RED);
delay(5000);
}
M5.IMU.Init();
resetMotor();
resetPara();
resetVar();
ledDispImg(circle, GRN);
}
void loop() {
static uint32_t msec=0;
M5.update();
checkButton();
getIMU();
switch (state) {
case 0:
if (abs(accXdata)<0.25 && aveAbsOmg<1.0) {
ledDispImg(square, walkMode?BLU:GRN);
getBaseline();
msec=millis();
resetVar();
state=1;
}
break;
case 1:
if (millis()-msec>200) {
state=2;
if (walkMode) walkPhase=1;
ledDispImg(smile, walkMode?BLU:GRN);
}
break;
case 2:
if (abs(varAng)>30.0 || counterOverSpd>25) {
if (serialMonitor) sendStatus();
resetMotor();
resetVar();
ledDispImg(frown, RED);
state=0;
}
else {
if (walkMode) walk();
calcTarget();
drive();
}
break;
}
counter += 1;
if (counter >= 100) {
counter = 0;
if (serialMonitor) sendStatus();
}
do time1 = millis();
while (time1 - time0 < interval);
time0 = time1;
}
void walk() {
static uint32_t ms=0;
switch (walkPhase) {
case 1:
ms=millis();
walkPhase=2;
break;
case 2:
if (millis()-ms>2000) walkPhase=3;
break;
case 3:
spinStep=0.4;
movePower=40.0;
walkPhase=4;
break;
case 4:
break;
}
}
void checkButton() {
if (M5.Btn.wasPressed()) {
if (walkMode) {
walkMode=false;
if (state==0) ledDispImg(all, BLK);
else ledDispImg(smile, GRN);
spinStep=0.0;
movePower=0.0;
}
else {
walkMode=true;
walkPhase=1;
if (state==0) ledDispImg(heart, BLU);
else ledDispImg(smile, BLU);
}
}
}
void getIMU() {
readIMU();
varOmg = gyroYdata;
aveAbsOmg = aveAbsOmg * 0.8 + abs(varOmg) * 0.2;
yawAng += (gyroZdata - gyroZoffset) * deltaT;
varAng = (varAng + varOmg * deltaT) * 0.995 + (accXdata - accXoffset) * 57.3 * 0.005;
}
void readIMU() {
float gX, gY, gZ, aX, aY, aZ;
M5.IMU.getGyroData(&gX,&gY,&gZ);
M5.IMU.getAccelData(&aX, &aY, &aZ);
gyroYdata=-gX;
gyroZdata=-gY;
accXdata=aZ;
}
void calcTarget() {
if (abs(movePower)>10.0) spinFact=constrain(-(powerR+powerL)/150.0, -3.0, 3.0); //moving
else spinFact=1.0; //standing
spinTarget += spinStep * spinFact;
if (movePwrTarget < movePower-movePwrStep) movePwrTarget += movePwrStep;
else if (movePwrTarget > movePower+movePwrStep) movePwrTarget -= movePwrStep;
else movePwrTarget = movePower;
}
void drive() {
varSpd += power * deltaT;
varDst += Kdst * varSpd * deltaT;
varIang += KIang * varAng * deltaT;
power = varIang + varDst + (Kspd * varSpd) + (Kang * varAng) + (Komg * varOmg);
if (abs(power)>500.0) counterOverSpd += 1;
else counterOverSpd=0;
power = constrain(power, -maxPwr, maxPwr);
yawPower = (yawAng - spinTarget) * Kyaw;
powerR = power + yawPower + movePwrTarget;
powerL = power - yawPower + movePwrTarget;
int16_t ipowerL = (int16_t) constrain(powerL * mechFactorL, -maxPwr, maxPwr);
drv8830(MOTOR_L, ipowerL);
int16_t ipowerR = (int16_t) constrain(powerR * mechFactorR, -maxPwr, maxPwr);
drv8830(MOTOR_R, ipowerR);
}
void drv8830(byte adr, int16_t pwr) { //pwr -255 to 255
byte dir, st;
if (pwr < 0) dir = 2;
else dir =1;
byte ipower=(byte)(abs(pwr)/4);
if (ipower == 0) dir=3; //brake
ipower = constrain (ipower, 0, 63);
st = drv8830read(adr);
if (st & 1) drv8830write(adr, 0, 0);
drv8830write(adr, ipower, dir);
}
void drv8830write(byte adr, byte pwm, byte ctrl) {
Wire.beginTransmission(adr);
Wire.write(0);
Wire.write(pwm*4+ctrl);
Wire.endTransmission(true);
}
int drv8830read(byte adr) {
Wire.beginTransmission(adr);
Wire.write(1);
Wire.endTransmission(false);
Wire.requestFrom((int)adr, (int)1, (int)1);
return Wire.read();
}
void resetMotor() {
drv8830(MOTOR_R, 0);
drv8830(MOTOR_L, 0);
counterOverSpd=0;
}
void resetPara() {
Kang=35.0;
Komg=0.6;
KIang=500.0;
Kdst=11.0;
Kspd=2.5;
Kyaw=10.0;
mechFactorL=1.0;
mechFactorR=1.0;
maxPwr=250.0;
}
void resetVar() {
power=0.0;
movePwrTarget=0.0;
movePower=0.0;
spinStep=0.0;
spinDest=0.0;
spinTarget=0.0;
yawAng=0.0;
varAng=0.0;
varOmg=0.0;
varDst=0.0;
varSpd=0.0;
varIang=0.0;
}
void getBaseline() {
accXoffset=0.0;
gyroZoffset=0.0;
for (int i=0; i < 30; i++){
readIMU();
accXoffset += accXdata;
gyroZoffset += gyroZdata;
delay(10);
}
accXoffset /= 30.0;
gyroZoffset /= 30.0;
}
void sendStatus () {
Serial.print("state="); Serial.print(state);
Serial.print(" accX="); Serial.print(accXdata);
Serial.print(" ang=");Serial.print(varAng);
Serial.print(" gyroY="); Serial.print(gyroYdata);
Serial.print(" ");
Serial.print(millis()-time0);
Serial.println("ms");
}
void ledDispImg(const byte* p, uint64_t c) {
for (byte i=0; i<25; i++) {
M5.dis.drawpix(i, p[i]*c);
}
}