ヘボコン2018に参戦したロボットに搭載したシステム(2018.07.28更新):備忘録として
息子がデイリーポータルZ主催のヘボコン2018(2018年6月30日、於東京カルチャーカルチャー)に参戦しました。
息子のロボットにはArduinoベースのリモートコントロール(Wifi)システムを搭載してます。
システム構成
基本動作
- ESP8266, M5StackともWifi親機に接続。ESP8266をWifi親機、M5StackをWifi子機として接続。(2018.07.28更新)
- mDNSによりM5StackはESP8266のIPアドレスを取得。
- WebSocketによりM5StackからESP8266に指示を伝える。
2018.07.28更新
ロボット搭載システムは、以前クラウド・サービス(Twilio)を使っていたものを流用したためiPhoneにテザリング接続する作りでしたが、ESP8266をWifi親機としてM5Stackが直接接続するように変更しました。
ESP8266が制御するロボットの動き
- 熊の生首(ポンポン)
高輝度LEDを点灯させ、サーボモータにより首を左右に振らせます。 - プリズムの回転
PWMで回転速度を制御します。
超低速で回転させたかったので、周波数を低くしてます。そのためぎこちない動きとなってしまいました。
おまけの機能
E5Stack Grayは加速度センサー搭載なので、躍度(加加速度)が閾値を超えた時にブザーが鳴る機能を付けました。音が大きいので鳴るとびっくりするという意味不明の機能です。
回路とスケッチ
息子はArduinoスターターキットとGenuinoスターターキットを持っています(ヘボコン・ワールドチャンピオンシップ2016のArduino賞を受賞した時の副賞です)。
モータをPWMでドライブする回路のスイッチング素子として、最初はスターターキットに入っていたIRF520Nを流用したのですが、出力電圧3.3VのESP8266ではゲートターンオンできませんでした。そこで検索してデータシートを調べ、2SK2232というパワーMOS FETを購入し代わりに使いました。
回路図
ESP8266スケッチ(2018.07.29更新)
#ifdef ESP8266
extern "C" {
#include "user_interface.h"
}
#endif#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WebSocketsServer.h>
#include <Servo.h>
// Wi-Fi parameters
const char* ssid = "{SSID}";
const char* pass = "{PASSWORD}";
const IPAddress ip_address({Wifi Station IP address}); // 2018.07.28 added
const IPAddress ip_subnet({Subnet mask}); // 2018.07.28 added
const char* hostString = "{HOST NAME}";
#define LED1 12
#define LED2 14
#define IO_SERVO 16
#define IO_MOTOR 13#define BEAR_RESET 0
#define BEAR_START 1
#define BEAR_STOP 2
#define BEAR_LOOP 3
#define BEAR_SWING 50 // bear swinging degree#define MOTOR_RESET 0
#define MOTOR_UP 1
#define MOTOR_DOWN 2
#define MOTOR_KEEP 3
#define MOTOR_OFF 4
#define MOTOR_LOOP 5
#define MOTOR_MAX 1000 // 0 - 1023?
#define MOTOR_ACCELL 2
#define MOTOR_PWM_FREQ 10
// WebSocket Server
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length);
WebSocketsServer webSocket = WebSocketsServer(80);
// Servo
Servo servo;
// Setup
void setup() {
Serial.begin(115200);
Serial.println("Setup.");
wifiConnection();
setupMDNSServer();
setupWebSocketServer();
bear(BEAR_RESET);
motorControl(MOTOR_RESET);
}
// Loop
void loop() {
if(WiFi.status() == WL_CONNECTED || wifiConnection() || setupMDNSServer() || setupWebSocketServer()) {
webSocket.loop();
} else {
Serial.println("NO WORK");
delay(10);
}
bear(BEAR_LOOP);
motorControl(MOTOR_LOOP);
}
// WiFi 2018.07.28 modified
boolean wifiConnection() {
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(ip_address, ip_address, ip_subnet);
WiFi.softAP(ssid, pass);
return(true);
}
// mDNS Server
bool setupMDNSServer() {
if (!MDNS.begin(hostString, WiFi.localIP())) {
Serial.println("Error setting up MDNS responder!");
return(false);
}
Serial.println("mDNS responder started");
MDNS.addService("ws", "tcp", 80);
return(true);
}
// WebSocket Server
bool setupWebSocketServer() {
webSocket.begin();
webSocket.onEvent(webSocketEvent);
return(true);
}
// WebSocket Event Prosessing
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
char buff[4];
int len;
switch (type) {
case WStype_DISCONNECTED:
Serial.println("WS disconnected.");
break;
case WStype_CONNECTED:
Serial.println("WS connected.");
break;
case WStype_TEXT:
len = min(3, (int)length);
for(int i = 0; i < len; i++) {
buff[i] = (char)payload[i];
}
buff[len] = '\0';
Serial.println("WS: Text Received: " + String(buff));
parseRequest(buff);
break;
}
}
void parseRequest(char *buff) {
static boolean bear_status = false;
static boolean motor_status = false;
if(buff[0] == '1') { // button A pressed
bear_status = ! bear_status;
if(bear_status) {
bear(BEAR_START);
} else {
bear(BEAR_STOP);
}
}
if(buff[1] == '1') { // button B pressed
motor_status = ! motor_status;
if(motor_status) {
motorControl(MOTOR_UP);
} else {
// motorControl(MOTOR_DOWN);
motorControl(MOTOR_OFF);
}
}
if(buff[1] == '2' && motor_status) { // button B released
motorControl(MOTOR_KEEP);
}
}
void bear(int command) {
static int mode = 0; // 0:off, 1:on
static int status = 0; // 0:foward -> 1:left -> 2:forward -> 3:right
const int deg[] = {90, 90 - BEAR_SWING, 90, 90 + BEAR_SWING};
static unsigned long last = 0L;
switch(command) {
case BEAR_RESET:
Serial.print("Bear RESET: ");
pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT);
digitalWrite(LED1, HIGH); digitalWrite(LED2, HIGH);
servo.attach(IO_SERVO);
delay(500);
// no break
case BEAR_STOP:
Serial.println("Bear STOP");
servo.write(deg[status]);
digitalWrite(LED1, LOW); digitalWrite(LED2, LOW);
mode = 0;
status = 0;
servo.write(deg[status]);
break;
case BEAR_START:
Serial.println("Bear START");
mode = 1;
status = 0;
last = millis();
digitalWrite(LED1, HIGH); digitalWrite(LED2, HIGH);
break;
case BEAR_LOOP:
if(! mode || last + 2000L > millis()) {
break;
}
last = millis();
status = (status + 1) % 4;
Serial.print("Bear angle "); Serial.println(deg[status]);
servo.write(deg[status]);
break;
}
}
void motorControl(int command) {
static int status = 0; // 0:off, 1:up, 2:keep, 3:down
static int speed = 0;
static unsigned long last = 0L;
switch(command) {
case MOTOR_RESET:
Serial.print("Motor RESET: ");
pinMode(IO_MOTOR, OUTPUT);
analogWriteFreq(MOTOR_PWM_FREQ);
analogWriteRange(MOTOR_MAX);
// no break
case MOTOR_OFF:
status = 0;
speed = 0;
analogWrite(IO_MOTOR, speed);
Serial.println("Motor OFF: ");
break;
case MOTOR_UP:
Serial.println("Motor UP");
status = 1;
speed += MOTOR_ACCELL;
last = millis();
analogWrite(IO_MOTOR, speed);
break;
case MOTOR_DOWN:
Serial.println("Motor DOWN");
status = 3;
speed -= MOTOR_ACCELL;
last = millis();
analogWrite(IO_MOTOR, speed);
break;
case MOTOR_KEEP:
Serial.println("Motor KEEP");
status = 2;
break;
case MOTOR_LOOP:
if(status == 0 || status == 2 || last + 300L > millis() || speed <= 0 || speed >= MOTOR_MAX) {
break;
}
if(status == 1) {
speed += MOTOR_ACCELL;
} else if(status == 3) {
speed -= MOTOR_ACCELL;
}
Serial.print("Motor ACCELL "); Serial.println(speed);
last = millis();
analogWrite(IO_MOTOR, speed);
break;
}
}
M5Stackのスケッチ
#include <Arduino.h>
#include <M5Stack.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <WiFiClientSecure.h>
#include <ESPmDNS.h>
#include <WebSocketsClient.h>
#include "utility/MPU9250.h"
#include "utility/quaternionFilters.h"
WiFiMulti WiFiMulti;
WebSocketsClient webSocket;
// Wi-Fi parameters
const char* ssid = "{SSID}";
const char* pass = "{PASSWORD}";
const char* wsHost = "{ESP8266 HOST NAME}";
const char* hostString = "{MY HOSTNME}";
IPAddress esp8266Ip;
// MPU9250
#define AHRS true // Set to false for basic data read
MPU9250 IMU;#define JERK_THR 700.0f
#define BEEP_FREQ 880
#define BEEP_TIME 500
#define TSIZE_S 2
#define TSIZE_ST 3
#define TSIZE_L 7boolean mpu2950_active = true;
boolean network_active = true;
boolean beep_mode = false;
// Setup
void setup() {
Serial.begin(115200);
Serial.println("Setup.");
M5.begin();
M5.Lcd.setTextSize(TSIZE_S);
M5.Lcd.fillScreen(0); M5.Lcd.setTextColor(WHITE, 0); M5.Lcd.setCursor(0, 0);
M5.Lcd.println("Setup.");
delay(1000);
if(M5.BtnA.isPressed()) { // Button B -> network disable
network_active = false;
M5.Lcd.println("Network disabled.");
}
if(M5.BtnB.isPressed()) { // Button A -> mpu2950 disable
mpu2950_active = false;
M5.Lcd.println("MPU2950 disabled.");
}
if(mpu2950_active) {
setupMPU9250();
}
if(network_active) {
wifiConnection();
retrieveEsp8266Ip();
setupWebSocketClient();
}
display_hebocon();
}
// Loop
void loop() {
if(mpu2950_active) {
checkMPU9250();
}
if(network_active) {
if(WiFiMulti.run() == WL_CONNECTED || wifiConnection() || setupWebSocketClient()) {
webSocket.loop();
sendStatus();
} else {
Serial.println("NO WORK");
M5.Lcd.fillScreen(RED); M5.Lcd.setTextColor(WHITE, RED); M5.Lcd.setCursor(0, 0);
M5.Lcd.println("NO WORK");
}
}
}
// MPU2950
void setupMPU9250() {
Wire.begin();
byte c = IMU.readByte(MPU9250_ADDRESS, WHO_AM_I_MPU9250);
Serial.print("MPU9250: I AM ");
Serial.print(c, HEX);
Serial.print(" : ");
Serial.println(0x71, HEX);// Start by performing self test and reporting values
IMU.MPU9250SelfTest(IMU.SelfTest);// Calibrate gyro and accelerometers, load biases in bias registers
IMU.calibrateMPU9250(IMU.gyroBias, IMU.accelBias);
IMU.initMPU9250();
}
void checkMPU9250() {
static float lax = 0.0f, lay = 0.0f, laz = 0.0f;
float dax, day, daz, accl;
if (IMU.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01) {
IMU.readAccelData(IMU.accelCount); // Read the x/y/z adc values
IMU.getAres();// Now we'll calculate the accleration value into actual g's
// This depends on scale being set
IMU.ax = (float)IMU.accelCount[0]*IMU.aRes; // - accelBias[0];
IMU.ay = (float)IMU.accelCount[1]*IMU.aRes; // - accelBias[1];
IMU.az = (float)IMU.accelCount[2]*IMU.aRes; // - accelBias[2];
} // if (readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01)// Must be called before updating quaternions!
IMU.updateTime();IMU.delt_t = millis() - IMU.count;
if (IMU.delt_t > 100) {
// Print acceleration values in milligs!
Serial.print("X-acceleration: "); Serial.print(1000*IMU.ax); Serial.print(" ("); Serial.print(1000 * lax);
Serial.print(" mg ");
Serial.print("Y-acceleration: "); Serial.print(1000*IMU.ay); Serial.print(" ("); Serial.print(1000 * lay);
Serial.print(" mg ");
Serial.print("Z-acceleration: "); Serial.print(1000*IMU.az); Serial.print(" ("); Serial.print(1000 * laz);
Serial.println(" mg ");
if(lax != 0.0f && lay != 0.0f && laz != 0.0f) { // not initial
dax = IMU.ax - lax;
day = IMU.ay - lay;
daz = IMU.az - laz;
accl = sqrt*1 * 1000;
Serial.print("JERK: "); Serial.println(accl);
M5.Lcd.setCursor(0, 210);
M5.Lcd.print("JERK: ");
M5.Lcd.print(accl);
if(beep_mode) {
M5.Lcd.print(" B");
if(accl > JERK_THR) {
M5.Lcd.print("EEP ");
M5.Speaker.tone(BEEP_FREQ, BEEP_TIME);
}
M5.Lcd.println(" ");
} else {
M5.Lcd.println(" ");
}
IMU.count = millis();
}
lax = IMU.ax;
lay = IMU.ay;
laz = IMU.az;
}
}
// WiFi
boolean wifiConnection() {
WiFiMulti.addAP(ssid, pass);
int count = 0;
Serial.print("Connecting Wi-Fi");
M5.Lcd.print("Connecting Wi-Fi");
while (count < 50) {
if (WiFiMulti.run() == WL_CONNECTED) {
String mssg = "Connected.\nC: " + String(hostString) + "\nIP: " + WiFi.localIP().toString();
Serial.println();
Serial.println(mssg);
M5.Lcd.fillScreen(BLUE); M5.Lcd.setTextColor(WHITE, BLUE); M5.Lcd.setCursor(0, 0);
M5.Lcd.println(mssg);
return(true);
}
delay(500);
Serial.print(".");
M5.Lcd.print(".");
count++;
}
Serial.println("Timed out.");
M5.Lcd.fillScreen(RED); M5.Lcd.setTextColor(WHITE, RED); M5.Lcd.setCursor(0, 0);
M5.Lcd.println("WiFi connecting timed out.");
return(false);
}
// retrieve ESP8266 IP address by mDNS
void retrieveEsp8266Ip() {
if (!MDNS.begin(hostString)) {
Serial.println("Error setting up MDNS responder!");
M5.Lcd.println("Error setting up MDNS responder!");
}
Serial.println("mDNS started.");
for(int i = 0; i < 10; i++) {
esp8266Ip = MDNS.queryHost(wsHost);
Serial.println("mDNS res = " + esp8266Ip.toString());
if(esp8266Ip[0] != 0) {
Serial.println("OK");
M5.Lcd.println("S: " + esp8266Ip.toString());
return;
}
delay(500);
}
Serial.println("ESP8266 not found.");
M5.Lcd.fillScreen(RED); M5.Lcd.setTextColor(WHITE, RED); M5.Lcd.setCursor(0, 0);
M5.Lcd.println("ESP8266 not found.");
}
// WebSocket Client
bool setupWebSocketClient() {
webSocket.begin(esp8266Ip.toString(), 80, "/");
webSocket.onEvent(webSocketEvent);
webSocket.setReconnectInterval(5000);
Serial.println("WebSocket acrivated.");
M5.Lcd.println("WS activated.");
return(true);
}
// WebSocket Event Prosessing
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
switch (type) {
case WStype_DISCONNECTED:
Serial.println("WS disconnected.");
break;
case WStype_CONNECTED:
Serial.println("WS connected.");
break;
case WStype_TEXT:
String payload_str = String*2 {
return;
}
last = millis();if(M5.BtnA.wasPressed()) { // Button A
st[0] = '1';
}
if(M5.BtnA.wasReleased()) {
st[0] = '2';
}
if(M5.BtnB.wasPressed()) { // Button B
st[1] = '1';
}
if(M5.BtnB.wasReleased()) {
st[1] = '2';
}
if(M5.BtnC.wasPressed()) { // Button C
st[2] = '1';
beep_mode = ! beep_mode;
}
if(M5.BtnC.wasReleased()) {
st[2] = '2';
}
if(! String(st).equals("000")) {
webSocket.sendTXT(st);
Serial.println("Sent: " + String(st));
M5.Lcd.setCursor(0, 170);
M5.Lcd.println("Sent: " + String(st));
}
M5.update();
}
// display HEBOCON LOGO on LCD
void display_hebocon() {
delay(500);
M5.Lcd.setBrightness(0);
M5.Lcd.fillScreen(YELLOW);
M5.Lcd.setTextSize(TSIZE_L);
M5.Lcd.setTextColor(BLUE, YELLOW);
M5.Lcd.setCursor(20, 20);
M5.Lcd.println("HEBOCON");
M5.Lcd.println(" 2018");
M5.Lcd.setTextSize(TSIZE_ST);
delay(100);
M5.Lcd.setBrightness(100);
delay(400);
M5.Lcd.setBrightness(0);
delay(400);
M5.Lcd.setBrightness(100);
delay(400);
M5.Lcd.setBrightness(0);
delay(400);
M5.Lcd.setBrightness(100);
}
*1:dax * dax) + (day * day) + (daz * daz
*2:char*) payload);
Serial.println("WS: Text Received: " + payload_str);
M5.Lcd.setCursor(0, 202);
M5.Lcd.println("Received: " + payload_str);
parseRequest(payload_str);
break;
}
}
void parseRequest(String payload_str) {
}
void sendStatus() {
static unsigned long last;
char st[4] = "000"; // BtnA,BtnB,BtnC
if(last + 10 > millis(