2019年7月15日月曜日

esp32 と9軸センサーとサーボ

esp32 と 9軸センサー と サーボモータ

esp32 は手軽にIOT用のデバイスが作成できます。9軸センサーとサーボモータによるテスト用のプログラムを記述したので、ハードウェアとソフトウェアについて解説を記述します。

ハードウェア構成

以下の、パーツを購入することで手軽に動作させることができると思います。

ESP-WROOM-32D開発ボード

WifiやBLEもできる、最近流行っているボードです。結構いろいろできるみたいなので、持っているのも悪くないと思います。

9軸センサー

加速度・ジャイロ・磁気の9軸センサーです。esp32 のI/O は3.3v みたいなので、JP7をショートさせて使用します。

サーボモータ

ホビー用によく使用される、サーボモータです。ソフトウェアでは、4個使用していますが、予算の都合で個数を購入するのがよいと思います。

ブレッドボード

ESP32 用には 一列が 6ピンないと、結線できないのでこちらを使用します。

ブレッドボード用ジャンパーワイヤー

結線用のワイヤーです。

USBケーブル

ESP32とPCを接続するケーブルです。持っていれば別に必要ではないです。

配線

以下、配線になります。モータ用電源に別途、電池ボックスとブレッドボードを使用しています。

電源ライン
3.3 V と GNDを ブレッドボードの+-に接続します。
サーボモータ
GPIO32, 33, 25, 26 をサーボモータの信号用に使用します。
ここでは、電池ボックスから4.8v の電圧を生成して結線しています。
サーボモータが1・2個なら esp32 の 5Vを使用します。
9軸センサー
電源は上記の電源ラインから配線します。
信号用にはGPIO22, 21 は 加速度センサーとの接続用に使用します。

ソフトウェア

Arduino IDE を使用して開発しています。導入方法等は他のページを参照してください。


ソースコード

以下、全ソースコードです。加速度センサーから読み取った値から roll, pitch を計算しています。roll, pitch に連動してサーボモータを動作させています。
細かい解説は後ほど、追記します。

#include <Wire.h>
#include <MadgwickAHRS.h>
#include <ESP32Servo.h>
#include "esp_system.h"
#define LED_PIN 32
#define SERVO_COUNT 4
int SERVO_PINS[SERVO_COUNT] = { 33, 25, 26, 27 };
Servo myServo[SERVO_COUNT];
Madgwick MadgwickFilter;
#define BMX055_ACCL_ADDR 0x19
#define BMX055_GYRO_ADDR 0x69
#define BMX055_MAG_ADDR  0x13

void setup()
{
  Serial.begin(115200);
  while (!Serial);
  Serial.println("BMX055 Test setup start");
  myServo[0].attach(SERVO_PINS[0]);
  myServo[1].attach(SERVO_PINS[1]);
  myServo[2].attach(SERVO_PINS[2]);
  myServo[3].attach(SERVO_PINS[3]);
  // ic2
  Wire.begin();
  InitAcclBMX055();
  InitGyroBMX055();
  InitMagBMX055();
  MadgwickFilter.begin(50); //50Hz
  Serial.println("BMX055 Test setup end");
}

void loop()
{
  //Serial.println("Read...");
  float ax, ay, az;
  ReadAcclBMX055(ax, ay, az);
  float gx, gy, gz;
  RaddGyroBMX055(gx, gy, gz);
  float mx, my, mz;
  ReadMagBMX055(mx, my, mz);
  // calculate roll/pitch/yaw
  //MadgwickFilter.updateIMU(gx, gy, gz, ax, ay, az);
  MadgwickFilter.update(gx, gy, gz, ax, ay, az, mx, my, mz);

  float pitch = MadgwickFilter.getPitch();
  float roll    = MadgwickFilter.getRoll();
  float yaw   = MadgwickFilter.getYaw();
  //Serial.print(pitch); Serial.print(",");
  //Serial.print(roll); Serial.println("");
  if( pitch > -90 && pitch < 90) {
    float angle = pitch + 90;
    myServo[0].write(angle);
    myServo[1].write(angle);
  } else {
    myServo[0].write(90);
    myServo[1].write(90);
  }
  if( roll > -90 && roll < 90) {
    float angle = roll + 90;
    myServo[2].write(angle);
    myServo[3].write(angle);
  } else {
    myServo[2].write(90);
    myServo[3].write(90);
  }

  delay(20);         
}
void  InitAcclBMX055()
{
  // 加速度
  Wire.beginTransmission(BMX055_ACCL_ADDR);
  Wire.write(0x0F); // Select PMU_Range register
  Wire.write(0x03);   // Range = +/- 2g
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_ACCL_ADDR);
  Wire.write(0x10);  // Select PMU_BW register
  Wire.write(0x08);  // Bandwidth = 7.81 Hz
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_ACCL_ADDR);
  Wire.write(0x11);  // Select PMU_LPW register
  Wire.write(0x00);  // Normal mode, Sleep duration = 0.5ms
  Wire.endTransmission();
  delay(10);
}
void  InitGyroBMX055()
{
 //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_GYRO_ADDR);
  Wire.write(0x0F);  // Select Range register
  Wire.write(0x04);  // Full scale = +/- 125 degree/s
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_GYRO_ADDR);
  Wire.write(0x10);  // Select Bandwidth register
  Wire.write(0x07);  // ODR = 100 Hz
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_GYRO_ADDR);
  Wire.write(0x11);  // Select LPM1 register
  Wire.write(0x00);  // Normal mode, Sleep duration = 2ms
  Wire.endTransmission();
  delay(100);
}
void  InitMagBMX055()
{
  Wire.beginTransmission(BMX055_MAG_ADDR);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x83);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_MAG_ADDR);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x01);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_MAG_ADDR);
  Wire.write(0x4C);  // Select Mag register
  Wire.write(0x00);  // Normal Mode, ODR = 10 Hz
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_MAG_ADDR);
  Wire.write(0x4E);  // Select Mag register
  Wire.write(0x84);  // X, Y, Z-Axis enabled
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_MAG_ADDR);
  Wire.write(0x51);  // Select Mag register
  Wire.write(0x04);  // No. of Repetitions for X-Y Axis = 9
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(BMX055_MAG_ADDR);
  Wire.write(0x52);  // Select Mag register
  Wire.write(0x16);  // No. of Repetitions for Z-Axis = 15
  Wire.endTransmission();
}
void ReadAcclBMX055(float &xAccl, float& yAccl, float& zAccl)
{
  int data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(BMX055_ACCL_ADDR);
    Wire.write((2 + i));// Select data register
    Wire.endTransmission();
    Wire.requestFrom(BMX055_ACCL_ADDR, 1);// Request 1 byte of data
    // Read 6 bytes of data
    // xAccl lsb, xAccl msb, yAccl lsb, yAccl msb, zAccl lsb, zAccl msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data to 12-bits
  xAccl = ((data[1] * 256) + (data[0] & 0xF0)) / 16;
  if (xAccl > 2047)  xAccl -= 4096;
  yAccl = ((data[3] * 256) + (data[2] & 0xF0)) / 16;
  if (yAccl > 2047)  yAccl -= 4096;
  zAccl = ((data[5] * 256) + (data[4] & 0xF0)) / 16;
  if (zAccl > 2047)  zAccl -= 4096;
  xAccl = xAccl * 0.0098; // renge +-2g
  yAccl = yAccl * 0.0098; // renge +-2g
  zAccl = zAccl * 0.0098; // renge +-2g
}
void RaddGyroBMX055(float& xGyro, float& yGyro, float& zGyro)
{
  int data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(BMX055_GYRO_ADDR);
    Wire.write((2 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(BMX055_GYRO_ADDR, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xGyro lsb, xGyro msb, yGyro lsb, yGyro msb, zGyro lsb, zGyro msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xGyro = (data[1] * 256) + data[0];
  if (xGyro > 32767)  xGyro -= 65536;
  yGyro = (data[3] * 256) + data[2];
  if (yGyro > 32767)  yGyro -= 65536;
  zGyro = (data[5] * 256) + data[4];
  if (zGyro > 32767)  zGyro -= 65536;
  xGyro = xGyro * 0.0038; //  Full scale = +/- 125 degree/s
  yGyro = yGyro * 0.0038; //  Full scale = +/- 125 degree/s
  zGyro = zGyro * 0.0038; //  Full scale = +/- 125 degree/s
}
void ReadMagBMX055(float& xMag, float& yMag, float& zMag)
{
  int data[8];
  for (int i = 0; i < 8; i++)
  {
    Wire.beginTransmission(BMX055_MAG_ADDR);
    Wire.write((0x42 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(BMX055_MAG_ADDR, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xMag lsb, xMag msb, yMag lsb, yMag msb, zMag lsb, zMag msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xMag = ((data[1] <<8) | (data[0]>>3));
  if (xMag > 4095)  xMag -= 8192;
  yMag = ((data[3] <<8) | (data[2]>>3));
  if (yMag > 4095)  yMag -= 8192;
  zMag = ((data[5] <<8) | (data[4]>>3));
  if (zMag > 16383)  zMag -= 32768;
}


2019年7月8日月曜日

Jetson Nano と コマンドでLチカ

LEDのチカチカは、GPIO を操作して行いますが。デフォルトでは root でしか操作できないようです。

配線

回路図とか描けないので、文章で・・
LEDと抵抗を直列につないで、Jestson の GND と GPIO16(Pin番号19)を接続します。

コマンドでLチカ

以下、シェルによるGPIOの操作になります。各行で何をしているか解説してみます。
$ sudo su
# echo 16 > /sys/class/gpio/export
# echo out > /sys/class/gpio/gpio16/direction
# echo 1 > /sys/class/gpio/gpio16/value
# echo 0 > /sys/class/gpio/gpio16/value
# echo 1 > /sys/class/gpio/gpio16/value
# echo 16 > /sys/class/gpio/unexport

1行目:root になります。/sys/class/gpio/export 等にアクセス権がないためです。
2行目:GPIO16を使用するため、/sys/class/gpio/export に "16" を書き込んでいます。
3行目:GPIO16を出力用にするために、/sys/class/gpio/gpio16/direction に "out" を書き込んでいます。
4行目:GPIO16の値に 1を設定しています。
5行目:GPIO16の値に 0を設定しています。
6行目:GPIO16の値に 1を設定しています。
7行目:GPIO16の使用をやめます。

GPIOの権限変更

root でしか使用できないのは、不便なのでデバイスの権限を変更します。

$ wget https://github.com/NVIDIA/jetson-gpio/archive/master.zip
$ unzip master.zip
$ cd jetson-gpio-master/
$ sudo cp etc/99-gpio.rules /etc/udev/rules.d/ 
$ sudo groupadd -f -r gpio
$ sudo usermod -a -G gpio ユーザ

1行目:NVIDIA さんから、gpio のファイル関係を取得
2行目:アーカイブファイルの展開
3行目:ディレクトリ移動
4行目:udev 用のファイルを配置
5行目:グループに gpio を追加
6行目:グループ gpio にユーザ(任意)を追加

再起動後、以下のパスの group がgpio になっていれば成功です。上記のシェルも gpio へ追加したユーザで実行できます。
ls -l /sys/class/gpio/export

2019年7月6日土曜日

Jetson Nano と C++ と Visual Studio 2019

概要

Jetson Nano の 開発環境として eclpse 系の開発環境を使用しようと思いましたが、エラーが発生するため諦めました。
以前少しだけ使用したことのある、Visual Studioによるリモートデバッグを試行することにしました。

Visual Studio 2019 Community

インストール

全てチェックしておりよくわからないが、C++によるLinux開発を選択していれと思われる。


プロジェクト作成

C++/Linux でフィルタリングをするとプロジェクトは以下の通りあり。今回はコンソールアプリを選択。


  1. 共有アイテムプロジェクト
  2. コンソールアプリ
  3. 空のプロジェクト
  4. Raspberry Pi の点滅
  5. メイクファイルプロジェクト
  6. CMakeプロジェクト

1,6 はLinux以外でも動作させるプログラムを作るようですが、Jetson Nano のみで動かす分にはちょっと邪魔臭いです。
4 は生成されるソースが、Raspberry Pi の部分が生成されますが削ってしまえばビルド・実行はできます。

ビルド

Jetson Nano

Jetson Nanoのほうでは以下の設定が必要になります。

  1. SSHによる接続設定
  2. rsyncのインストール( apt install rsync)

ファイルをコピーしてビルドするような形態になっており。CMakeプロジェクトで行ったときには、rsyncがjetson側にインストールされていなかったため、rsyncを導入する必要がありました。

Visual Studio

初めての場合は Jetson Nano への接続の設定をする必要があります。


ARM64のとなりの、緑の三角の部分をクリックすると、以下の画面が現れますので、SSH接続の設定を行います。


初めてでない場合は、設定画面からアクセスして追加します。

Jetson とVisual Studio の設定ができれば、ビルド・デバッグに成功すると思います。

感想

VS2017で Linux ビルドを試していたときは、Linux 側の include / lib を Windows 側にもって来ないといけないため非常に大変でした。
Linux 側へソースをコピーしてビルドする形態なので、その必要がなくて結構いい感じに思えます。