// // M5ATOM Balancing Robot // Kiraku Labo, 2022 // // 1. Power on and wait for a circle to shows up. // 2. Hold the robot upright. // 3. When a smile shows up, release the robot. // #include <M5Atom.h> #include <esp_now.h> #include <WiFi.h> #define CHANNEL 0 #define MAXDRV 255 #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=true; uint16_t counter=0, counterOverSpd=0, state=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, gyroYoffset; const uint32_t interval = 10; // in msec const float dt=(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 joyButton=false; int16_t drvOffset, punchSpd, punchSpd2, punchSpd2N, punchDur, punchCountL=0, punchCountR=0; int16_t motorDeadband=0, counterTrip=0; int8_t motorRdir=0, motorLdir=0; bool spinContinuous=false; byte joyMACaddr[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};//{0x50, 0x02, 0x91, 0x86, 0x99, 0x68}; 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(); setupESPNOW(); delay(1500); resetMotor(); resetPara(); resetVar(); getBaselineOmg(); ledDispImg(circle, GRN); } void loop() { static uint32_t msec=0; getIMU(); switch (state) { case 0: if (joyButton) ledDispImg(circle, RED); else ledDispImg(circle, GRN); if (abs(accXdata)<0.25 && aveAbsOmg<1.0) { ledDispImg(square, GRN); getBaseline(); msec=millis(); resetVar(); state=1; } break; case 1: if (millis()-msec>200) state=2; break; case 2: if (abs(varAng)>30.0 || counterOverSpd>25) { if (serialMonitor) sendStatus(); resetMotor(); resetVar(); ledDispImg(frown, RED); state=0; } else { if (joyButton) ledDispImg(smile, RED); else ledDispImg(smile, GRN); calcTarget(); drive(); } break; } counter += 1; if (counter%25==0) { sendData("vs=" + String(varSpd) + "\nvd=" + String(varDst) + "\nva=" + String(varAng) + "\npw=" + String(power)); } if (counter >= 100) { counter = 0; if (serialMonitor) sendStatus(); } do time1 = millis(); while (time1 - time0 < interval); time0 = time1; } void getIMU() { readIMU(); varOmg = gyroYdata - gyroYoffset; aveAbsOmg = aveAbsOmg * 0.8 + abs(varOmg) * 0.2; yawAng += (gyroZdata - gyroZoffset) * dt; varAng = (varAng + varOmg * dt) * 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() { spinTarget += spinStep; if (movePwrTarget < movePower-movePwrStep) movePwrTarget += movePwrStep; else if (movePwrTarget > movePower+movePwrStep) movePwrTarget -= movePwrStep; else movePwrTarget = movePower; } void drive() { varSpd += power * dt; varDst += Kdst * varSpd * dt; varIang += KIang * varAng * dt; 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); if (ipowerL > 0) { if (motorLdir == 1) { punchCountL++; punchCountL = constrain(punchCountL, 0, 100); } else punchCountL=0; motorLdir = 1; if (punchCountL<punchDur) drvMotorL(max(ipowerL, punchSpd2)+drvOffset); else drvMotorL(ipowerL+motorDeadband+drvOffset); } else if (ipowerL < 0) { if (motorLdir == -1) { punchCountL++; punchCountL = constrain(punchCountL, 0, 100); } else punchCountL=0; motorLdir = -1; if (punchCountL<punchDur) drvMotorL(min(ipowerL, punchSpd2N) +drvOffset); else drvMotorL(ipowerL-motorDeadband+drvOffset); } else { drvMotorL(0); motorLdir = 0; } int16_t ipowerR = (int16_t) constrain(powerR * mechFactorR, -maxPwr, maxPwr); if (ipowerR > 0) { if (motorRdir == 1) { punchCountR++; punchCountR = constrain(punchCountL, 0, 100); } else punchCountR=0; motorRdir = 1; if (punchCountR<punchDur) drvMotorR(max(ipowerR, punchSpd2) +drvOffset); else drvMotorR(ipowerR+motorDeadband+drvOffset); } else if (ipowerR < 0) { if (motorRdir == -1) { punchCountR++; punchCountR = constrain(punchCountR, 0, 100); } else punchCountR=0; motorRdir = -1; if (punchCountR<punchDur) drvMotorR(min(ipowerR, punchSpd2N)+drvOffset); else drvMotorR(ipowerR-motorDeadband+drvOffset); } else { drvMotorR(0); motorRdir = 0; } } void drvMotorL(int16_t pwr) { drv8830(MOTOR_L, constrain(pwr, -255, 255)); } void drvMotorR(int16_t pwr) { drv8830(MOTOR_R, constrain(pwr, -255, 255)); } 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; counterTrip=0; } void resetPara() { Kang=35.0; Komg=0.6; KIang=700.0; Kdst=11.0; Kspd=7.5; Kyaw=10.0; mechFactorL=0.7; mechFactorR=0.7; maxPwr=550.0; punchSpd=50; punchDur=1; motorDeadband=35; punchSpd2=max(punchSpd, motorDeadband); punchSpd2N=-punchSpd2; drvOffset=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 getBaselineOmg() { gyroYoffset=0.0; for (int i=0; i < 30; i++){ readIMU(); gyroYoffset += gyroYdata; delay(10); } gyroYoffset /= 30.0; } void sendStatus () { Serial.print("state="); Serial.print(state); Serial.print(" accX="); Serial.print(accXdata); Serial.print(" ang=");Serial.print(varAng); 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); } } void setupESPNOW() { Serial.print("MAC="); Serial.println(WiFi.macAddress()); WiFi.mode(WIFI_STA); esp_now_init(); esp_now_register_recv_cb(onRecv); esp_now_peer_info_t peerInfo={}; memcpy(peerInfo.peer_addr, joyMACaddr, 6); peerInfo.channel = CHANNEL; peerInfo.encrypt = false; esp_now_add_peer(&peerInfo); } void onRecv(const uint8_t * mac, const uint8_t *recvData, int len) { if ((int8_t)recvData[2]==1) joyButton=true; else joyButton=false; steer((int8_t)recvData[0], (int8_t)recvData[1]); } void sendData(String s) { esp_now_send(joyMACaddr, (const byte*)s.c_str(), s.length()); } void steer(int8_t x, int8_t y) { if (abs(y)>20) movePower=(float)y*0.5; else movePower=0.0; if (abs(x)>30) spinStep=(float)x*0.015; else spinStep=0.0; }