01: // SainSmart Instabots Upright Rover rev. 2.0

02: // https://www.sainsmart.com/products/instabots-self-balancing-robot-v2

03: #include "M5Atom.h"

04: //M5Stamp_Pico用が未だリリースされてないので

05: //同じPico系のM5Atomライブラリーを使用しています。

06: #define BLYNK_PRINT Serial          // BLYNK

07: #define BLYNK_USE_DIRECT_CONNECT    // BLYNK

08: #include "BlynkSimpleEsp32_BLE.h"   // BLYNK

09: #include "I2Cdev.h"  // MPU6050-0.2.1

10: #include "MPU6050.h" // MPU6050-0.2.1

11: #include "Wire.h"

12: char auth[] = "**************************"; // ***Blynkから取得したauth codeを記入

13:

14: MPU6050 accelgyro;

15: MPU6050 initialize;

16: int16_t ax, ay, az;

17: int16_t gx, gy, gz;

18: #define Gry_offset 0  //The offset of the gyro

19: #define Gyr_Gain 16.348

20: #define Angle_offset 0 // 倒立状態で前後どちらかへ進む場合は要調整

21: #define pi 3.14159

22: #define min(X, Y) (((X)<(Y))?(X):(Y))

23:

24: float kp, ki, kd;

25: float Angle_Raw, Angle_Filtered, omega;

26: float Turn_Speed = 0;

27: float Run_Speed = 0, Run_Speed_K = 0;

28: float LOutput, ROutput;

29: unsigned long preTime = 0;

30: unsigned long lastTime;

31: float Input, Output;

32: float errSum, dErr, error, lastErr;

33: int timeChange;

34: int xVal;

35: int yVal;

36:

37: uint8_t DisBuff[2 + 5 * 5 * 3]; // 内蔵RGBカラーLED

38:

39: void setBuff(uint8_t Rdata, uint8_t Gdata, uint8_t Bdata) { // 内蔵RGBカラーLEDの設定

40:   DisBuff[0] = 0x05;

41:   DisBuff[1] = 0x05;

42:   for (int i = 0; i < 25; i++) {

43:     DisBuff[2 + i * 3 + 0] = Rdata;

44:     DisBuff[2 + i * 3 + 1] = Gdata;

45:     DisBuff[2 + i * 3 + 2] = Bdata;

46:   }

47: }

48:

49: void setup() {

50:   Wire.begin(2122); // Atom Liteの場合、pin番号が必要

51:   Serial.begin(115200);

52:   M5.begin(true, false, true); // RGBカラーLED

53:   delay(10);

54:   setBuff(0xff, 0x00, 0x00);   // RGBカラーLED

55:   M5.dis.displaybuff(DisBuff); // RGBカラーLED

56:   Blynk.setDeviceName("M5Stamp_BLE"); // Blinkのデバイス名(任意名必須)

57:   Blynk.begin(auth);

58:   accelgyro.initialize();

59:   initialize.setFullScaleGyroRange(MPU6050_GYRO_FS_2000);;

60:   for (int i = 0; i < 200; i++) { // Looping 200 times to get the real gesture when starti61: ng

62:     Filter();

63:   }

64:   if (abs(Angle_Filtered) < 45) { // Start the robot after cleaning data

65:     omega = Angle_Raw = Angle_Filtered = 0;

66:     Filter();

67:     Output = error = errSum = dErr = Run_Speed = Turn_Speed = 0;

68:     PID();

69:   }

70:   ledcSetup(0, 500, 8); // ESP32系のPWM設定、ここでは8ビットを使用

71:   ledcSetup(1, 500, 8);

72:   ledcSetup(2, 500, 8);

73:   ledcSetup(3, 500, 8);

74:  // GPIOの設定:M5 Stamp Picoを使用の場合

75:   ledcAttachPin(26, 0); // GPIO26pinに設定 モータドライバーIN1

76:   ledcAttachPin(18, 1); // GPIO18pinに設定 モータドライバーIN2

77:   ledcAttachPin(19, 2); // GPIO19pinに設定 モータドライバーIN3

78:   ledcAttachPin(25, 3); // GPIO25pinに設定 モータドライバーIN4

79:  // GPIOの設定: M5 Stack Atom liteを使用の場合

80:   ledcAttachPin(19, 0); // GPIO26pinに設定 モータドライバーIN1

81:   ledcAttachPin(23, 1); // GPIO18pinに設定 モータドライバーIN2

82:   ledcAttachPin(25, 2); // GPIO19pinに設定 モータドライバーIN3

83:   ledcAttachPin(33, 3); // GPIO25pinに設定 モータドライバーIN4

84:   ledcWrite(0, 0); // PWM出力の初期設定

85:   ledcWrite(1, 0);

86:   ledcWrite(2, 0);

87:   ledcWrite(3, 0);

88:   yVal = 30; // 初期設定

89:   xVal = 25; // 初期設定

90:   delay(100);

91: }

92:

93: BLYNK_WRITE(V0) { // Blynk V0 トグルボタン(パワーの切り替え)の設定

94:   int button1 = param.asInt();

95:   if (button1 == 1) { // High設定 :High/Lowの値は走路の条件により要調整

96:     yVal = 60;

97:     xVal = 40;

98:   } else {            // Low設定

99:     yVal = 30;

100:     xVal = 25;

101:   }

102: }

103:

104: BLYNK_WRITE(V1) { // Blynk V1 ジョイスティックの受信設定

105:   long Joy_x = param[0].asInt();

106:   long Joy_y = param[1].asInt();

107:

108:   if (Joy_y >= 100) { // 前進

109:     Run_Speed_K = map(Joy_y, 100, 255, 0, yVal);

110:     Run_Speed += Run_Speed_K;

111:     min(80,Run_Speed);

112:     setBuff(0x40, 0x40, 0x40); // LEDを白色に設定

113:   }

114:   else if (Joy_y <= -100) { // 後退

115:     Run_Speed_K = map(Joy_y, -100, -255, 0, -yVal);

116:     Run_Speed += Run_Speed_K;

117:     min(80,Run_Speed);

118:     setBuff(0x40, 0x00, 0x00); // LEDを赤色に設定

119:   }

120:   else {

121:     Run_Speed_K = 0;

122:   }

123:

124:   if (Joy_x >= 100) { // 右旋回

125:     Turn_Speed = map(Joy_x, 100, 255, 0, xVal);

126:     setBuff(0x40, 0x00, 0x40); // LEDをマゼンタ色に設定

127:   }

128:   else if (Joy_x <= -100) { // 左旋回

129:     Turn_Speed = map(Joy_x, -100, -255, 0, -xVal);

130:     setBuff(0x00, 0x00, 0x40); // LEDを青色に設定

131:   }

132:   else {

133:     Turn_Speed = 0;

134:   }

135:

136:   if ((Joy_x == 0) && (Joy_y == 0)) { // 停止

137:     setBuff(0x00, 0x40, 0x00); // LEDを緑色に設定

138:   }

139:   M5.dis.displaybuff(DisBuff);

140: }

141:

142: void loop() {

143:   Blynk.run();  // Blynk ON

144:   Filter();

145:   if (abs(Angle_Filtered) < 45) { // 前後45度以上倒すと一時停止

146:     PID();

147:     MotorControl();

148:   }

149:   else  { // 停止

150:     ledcWrite(0, 0);

151:     ledcWrite(1, 0);

152:     ledcWrite(2, 0);

153:     ledcWrite(3, 0);

154:

155:     for (int i = 0; i < 100; i++) { // Keep reading the gesture after falling down

156:       Filter();

157:     }

158:     if (abs(Angle_Filtered) < 45) { // Empty datas and restart the robot automaticly

159:       for (int i = 0; i <= 500; i++) { // Reset the robot and delay 2 seconds

160:         omega = Angle_Raw = Angle_Filtered = 0;

161:         Filter();

162:         Output = error = errSum = dErr = Run_Speed = Turn_Speed = 0;

163:         PID();

164:       }

165:     }

166:   }

167: }

168:

169: void Filter() { // V2から追加されたフィルター

170:   // Raw datas from MPU6050

171:   accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

172:   Angle_Raw = (atan2(ay, az) * 180 / pi + Angle_offset);

173:   omega =  gx / Gyr_Gain + Gry_offset;

174:   // Filters datas to get the real gesture

175:   unsigned long now = millis();

176:   float dt = (now - preTime) / 1000.00;

177:   preTime = now;

178:   float K = 0.8;

179:   float A = K / (K + dt);

180:   Angle_Filtered = A * (Angle_Filtered + omega * dt) + (1 - A) * Angle_Raw;

181: }

182:

183: void PID() { // PIDの設定は挙動を左右するので最良点を探して調整する

184:   kp = 15;   // 要調整

185:   ki = 0.10; // 要調整

186:   kd = 250;  // 要調整

187:   unsigned long now = millis();

188:   timeChange = (now - lastTime);

189:   lastTime = now;

190:   error = Angle_Filtered;  // Proportion

191:   errSum += error * timeChange;  // Integration

192:   dErr = (error - lastErr) / timeChange;  // Differentiation

193:   Output = kp * error + ki * errSum + kd * dErr;

194:   lastErr = error;

195:   LOutput = Output - Turn_Speed + Run_Speed;

196:   ROutput = Output + Turn_Speed + Run_Speed;

197: }

198:

199: void MotorControl() { // PWM出力 左右モータの回転方向と出力を制御

200:   if (LOutput > 0) {

201:     ledcWrite(0, min(255, abs(LOutput))); // min()8ビット出力の最大値255以上をカット

202:     ledcWrite(1, 0);

203:   }

204:   else if (LOutput < 0) {

205:     ledcWrite(0, 0);

206:     ledcWrite(1, min(255, abs(LOutput))); // abs()-出力を+出力に転換

207:   }

208:   else {

209:     ledcWrite(0, 0);

210:     ledcWrite(1, 0);

211:   }

212:   if (ROutput > 0) {

213:     ledcWrite(2, min(255, abs(ROutput)));

214:     ledcWrite(3, 0);

215:   }

216:   else if (ROutput < 0) {

217:     ledcWrite(2, 0);

218:     ledcWrite(3, min(255, abs(ROutput)));

219:   }

220:   else {

221:     ledcWrite(2, 0);

222:     ledcWrite(3, 0);

223:   }

224:   /*Serial.print("  LOutput: ");

225:   Serial.print(LOutput);

226:   Serial.print(",  ROutput: ");

227:   Serial.println(ROutput);*/

}