Created at

ロボットカーをつくる ②配線とプログラミング動作検証

ロボットカーをつくる ②配線とプログラミング動作検証

これまで

前回の記事において、構想が固まったロボットについて、配線およびプログラミングを実施した。

モーター配線

モーターを初めて動かす人向けに説明すると、モーターはモータードライバーにつなぎ、そのモータードライバーにマイコンから指示を出すことで回転が制御される。その制御を行うモータードライバーがDRV8833である。

DRV8833には黒と赤がある。この辺の比較や解説はたまねぎブログさんのほうにありましたのでご参考までに。今回は黒を使用した。このように、電源をモータードライバーにつなぎ、IN1, 2に指令を入れることでmotor 1を、IN3, 4に指令を入れることでmotor 2を駆動できる。今回は、正転しか使用しないため、IN1とIN3しか使用していないが2や4を使用することで逆転させることも可能である。

ここで注意してほしいのは「ここをつなぐ」と記載したJ2である。ここをつながないとDRB8833は回らない。説明によると繋いでいない間スリーブモードになっているらしい。

マイコン初期設定

以下に示すようなESP32-C3 SuperMiniについて、PlatformIOで開発を行った。

まず、platformio.iniの中身は以下のようになっている。様々なし試行錯誤の結果、以下のようにすることでESP32-C3のシリアルが表示されない問題や書き込めない問題が解消された。

[env:esp32dev]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
monitor_port = COM8
upload_port = COM8
monitor_speed = 115200
monitor_dtr = 0
build_flags =
    -D ARDUINO_USB_MODE=1
    -D ARDUINO_USB_CDC_ON_BOOT=1
platform_packages = toolchain-riscv32-esp @ 8.4.0+2021r2-patch5

またピン配置は以下。(引用元:https://forum.arduino.cc/t/esp32-c3-supermini-pinout/1189850

これに基づいて開発を進めた。

電源の検討

電源については単三電池4本の6V電源である。検討すべきはこれを昇圧するか降圧するかであった。検討は以下

電源

メリット

デメリット

電池そのまま

  • 配線が簡単
  • 電池が減るにつれて電圧が減少する
  • マイコンに電圧を併合わせないといけない

昇圧or降圧

  • 電源の電圧変化があっても、マイコンやシステムに同じ電圧を供給可能
  • レギュレータなどモジュールの追加が必要

今回はモーターを駆動するだけなことを考え、電源をそのままつなぐことにした。ただ、ESP32にはレギュレータが搭載されている。5Vのピンにつなぎ、ESP32上のレギュレータを通して3.3Vに変換されているので、降圧をシステムに含んでいることになる。搭載するシステム内に5Vの電源供給を求めるパーツがない場合はそれで問題ないだろう。

モーター駆動用コード

ただモーターを駆動するだけのコードを示す。モーター駆動用の関数(setMotors)はmainより先にないとコンパイルエラーとなるので要注意。Arduino IDEでは起きないエラーである。

ここでは左のモーターをマイコンの5に、右のモーターはマイコンの6につないでいる。そして、それぞれ8 bitのPWM信号を受け付ける。

#include <Arduino.h>


const uint8_t leftMotorPin = 5;
const uint8_t rightMotorPin = 6;

void setMotors(uint8_t leftSpeed, uint8_t rightSpeed)
{
  analogWrite(leftMotorPin, constrain(leftSpeed, 0, 255));
  analogWrite(rightMotorPin, constrain(rightSpeed, 0, 255));
}

void setup()
{
  pinMode(leftMotorPin, OUTPUT);
  pinMode(rightMotorPin, OUTPUT);
  setMotors(0, 0);
  Serial.println("System ready");
}

void loop()
{
  setMotors(255, 255);
  delay(30);
}


距離センサ設定

続いて、距離センサを使用。これはI2Cを使用する。ライブラリを参考に、以下のようにコードを構成。PlatformIOではライブラリのインポートが必要なのでHomeからライブラリをプロジェクトにインポートすることを忘れずに。VL53L1X by Pololuをインポートした。

#include <Arduino.h>
#include <Wire.h>
#include <VL53L1X.h>

const uint8_t i2cSdaPin = 21;
const uint8_t i2cSclPin = 20;

VL53L1X sensor;

void haltOnSensorFailure()
{
  while (true)
  {
    Serial.println("VL53L1X init failed");
    delay(1000);
  }
}

void setup()
{
  Serial.begin(115200);
  uint32_t startWait = millis();
  while (!Serial && (millis() - startWait) < 2000) { delay(10); }

  Wire.begin(i2cSdaPin, i2cSclPin);
  Wire.setClock(400000);

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    haltOnSensorFailure();
  }

  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(50000);
  sensor.startContinuous(50);

  Serial.println("System ready");

}

void loop()
{
  uint16_t distance = sensor.readRangeContinuousMillimeters();
  if (sensor.timeoutOccurred())
  {
    Serial.println("VL53L1X timeout");
    delay(200);
    return;
  }  
  else
  {
    Serial.print(distance);
    Serial.println(" mm");
    delay(200);
  }
}


システム統合

これらを基板に実装し、コードを統合した。

アルゴリズムとしては、距離センサで前方との距離を取得。その値が閾値以下になった場合左折するようなシンプルなプログラムである。

#include <Arduino.h>
#include <Wire.h>
#include <VL53L1X.h>

const uint8_t leftMotorPin = 5;
const uint8_t rightMotorPin = 6;

const uint8_t i2cSdaPin = 21;
const uint8_t i2cSclPin = 20;
const uint8_t xshutPin = 2;

const uint8_t baseMotorSpeed = 80;
const uint8_t turnMotorSpeed_out = 100;
const uint8_t turnMotorSpeed_in = 60;
const uint16_t obstacleThresholdMm = 250;
const uint16_t turnDurationMs = 30;


VL53L1X sensor;

void setMotors(uint8_t leftSpeed, uint8_t rightSpeed)
{
  analogWrite(leftMotorPin, constrain(leftSpeed, 0, 255));
  analogWrite(rightMotorPin, constrain(rightSpeed, 0, 255));
}

void haltOnSensorFailure()
{
  while (true)
  {
    Serial.println("VL53L1X init failed");
    delay(1000);
  }
}

void setup()
{
  Serial.begin(115200);
  uint32_t startWait = millis();
  while (!Serial && (millis() - startWait) < 2000) { delay(10); }

  pinMode(leftMotorPin, OUTPUT);
  pinMode(rightMotorPin, OUTPUT);
  setMotors(0, 0);

  pinMode(xshutPin, OUTPUT);
  digitalWrite(xshutPin, HIGH);
  delay(10);

  Wire.begin(i2cSdaPin, i2cSclPin);
  Wire.setClock(400000);

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    haltOnSensorFailure();
  }

  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(50000);
  sensor.startContinuous(50);

  Serial.println("System ready");
  setMotors(255,255);
}

void loop()
{
  uint16_t distance = sensor.readRangeContinuousMillimeters();
  if (sensor.timeoutOccurred())
  {
    Serial.println("VL53L1X timeout");
    setMotors(0, 0);
    delay(200);
    return;
  }

  if (distance < obstacleThresholdMm)
  {
    Serial.print("Avoiding obstacle at ");
    Serial.print(distance);
    Serial.println(" mm");
    setMotors(255, 255);
    delay(5);
    setMotors(turnMotorSpeed_in, turnMotorSpeed_out);
    delay(turnDurationMs);
  }
  else
  {
    setMotors(255, 255);
    delay(5);
    setMotors(baseMotorSpeed, baseMotorSpeed);
    delay(30);
  }

}

発生したトラブル

ここで、モーターが動かないトラブルが生じた。電圧は適切に送られているが回転し始めないで音がなっている。これは以前コアレスモーターでも生じた現象であった。問題は突入電流による電圧低下である。モーターは回転し始めるときに定常で回るより多くのパワーがいる。幸いにも今回は、255の入力に対しては動き出せることが判明したので、以下のようにコードを構成。一度、最高パワーで一瞬押し込みその後目標の駆動値を入れるような制御にしている。あまりに遅く駆動したい場合にはこれが必須である。

  {
    setMotors(255, 255);
    delay(5);
    setMotors(baseMotorSpeed, baseMotorSpeed);
    delay(30);
  }

テストラン

テストランの様子。正常な動作が確認できた。

https://x.com/Lockhoda_Martin/status/2012917143705128982?s=20

最後に

ここまでで今回の最低限の目標は達成された。今後、大きく余ったユニバーサル基板において色んなセンサーを積み様々な進化をさせたいと考えています。また、これをプリント基板にし、洗練した小型化や操縦も考えたいですね。