ESP32など

電子工作してます

一番簡単なArduinoでサーボ(SG90)を回す方法(コピペOK)

やりたいこと

サーボを簡単に回したい! 簡単に回したい! Arduino NanoでSG90を動かす一番簡単なコードをまとめました。

Arduino Nano

Arduino Nano

Amazon

ボタンを押している押すとサーボが動いて、離すと戻るコード
#include <Servo.h>
Servo myservo;
int servoPin = 9; // サーボのピン番号(D9)
int buttonPin = 2; // ボタンのピン番号(D2)

void setup() {
  myservo.attach(servoPin);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  int buttonState = digitalRead(buttonPin);
  if (buttonState == HIGH) {
    myservo.write(180);  // ボタンが押されている場合、サーボモーターを180度に動かす
  } else {
    myservo.write(0);  // ボタンが離されている場合、サーボモーターを0度に戻す
  }
}
ボタンを一度押すと180度動いて、もう一度押すと0度に戻るコード
#include <Servo.h>
Servo myservo;
int servoPin = 9; // サーボのピン番号(D9)
int buttonPin = 2; // ボタンのピン番号(D2)

int flag_1 = 0; // ボタン離されたフラグ
int flag_2 = 0; // サーボ状態フラグ

void setup() {
  myservo.attach(servoPin);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  int buttonState = digitalRead(buttonPin);
  
  if (buttonState == HIGH) {
    
    if (flag_1 == 1) {
      flag_1 = 0;
  
      if (flag_2 == 1){
        flag_2 = 0;
        myservo.write(180);
      }
      else {
        flag_2 = 1;
        myservo.write(0);
      }
    }
  }
  else {
    flag_1 = 1;
  }
  delay(15);
}
自動で0度と180度を往復するコード
#include <Servo.h>
Servo myservo;
int servoPin = 9; // サーボのピン番号(D9)

void setup() {
  myservo.attach(servoPin);
}

void loop() {
  myservo.write(180);
  delay(500);
  myservo.write(0);
  delay(500);
}

Amazonで買えるESP32-CAMの比較(技適有無など)

カメラ付きのESP32(ESP32-CAM / ESP32カメラボード)はアマゾンで色々種類ありそうに見えるけど、整理すると現時点でこの4種類(多分)。比較してみる。

カメラの種類は基本OV2640っぽい?販売商品によって違う?表記無いのが多いので不明。

1. Espressif Systems社製 ESP32搭載ボード

2. Espressif Systems社製 ESP32-S3搭載ボード

3. Ai-Thinker社製 ESP-32S搭載ボード

4. Seeed Studio XIAOにカメラ付けたやつ

M5Stack Timer Cameraの電源を切る方法(最低限のコード)

内蔵バッテリー稼働で電源OFFするやり方

コード

#include "battery.h"

void setup() {
  // バッテリー使う宣言
  bat_init();

  // LED
  pinMode(GPIO_NUM_2, OUTPUT);
}

void loop() {
  // 確認用チカチカ
  for (int i = 0; i < 10; i++) {
    digitalWrite(GPIO_NUM_2, HIGH);
    delay(100);
    digitalWrite(GPIO_NUM_2, LOW);
    delay(100);
  }
  
  // 電源OFF
  bat_disable_output();
}

Timer Camera X/F単体で写真を撮影&Webサーバー化してブラウザで表示する

ESP32-CAM / Timer Camera X/F / Unit Cam(M5Stack)などででウェブサーバーを立てて、外部のPCなどのブラウザでアクセスすると、写真が撮影されて表示されるようにするコード。

最小限。

コード

#include <WiFi.h>
#include <esp_http_server.h>
#include <esp_camera.h>
#include "camera_pins.h"

const char *ssid     = "*****";
const char *password = "*****";

void setup() {
  Serial.begin(115200);

  // カメラ用パラメータ
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer   = LEDC_TIMER_0;
  config.pin_d0       = Y2_GPIO_NUM;
  config.pin_d1       = Y3_GPIO_NUM;
  config.pin_d2       = Y4_GPIO_NUM;
  config.pin_d3       = Y5_GPIO_NUM;
  config.pin_d4       = Y6_GPIO_NUM;
  config.pin_d5       = Y7_GPIO_NUM;
  config.pin_d6       = Y8_GPIO_NUM;
  config.pin_d7       = Y9_GPIO_NUM;
  config.pin_xclk     = XCLK_GPIO_NUM;
  config.pin_pclk     = PCLK_GPIO_NUM;
  config.pin_vsync    = VSYNC_GPIO_NUM;
  config.pin_href     = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn     = PWDN_GPIO_NUM;
  config.pin_reset    = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size   = FRAMESIZE_XGA; // 1024x768
  config.jpeg_quality = 10;
  config.fb_count     = 2; // CAMERA_GRAB_LATEST設定時は2以上必要
  config.grab_mode    = CAMERA_GRAB_LATEST; // 最後のフレームバッファだけを取得

  // カメラ開始
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
      Serial.printf("Camera init failed with error 0x%x", err);
      return;
  }

  // 画像反転
  sensor_t *s = esp_camera_sensor_get();
  s->set_vflip(s, 1);
  
  // WiFi開始
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }
  Serial.println(WiFi.localIP());

  // Webサーバー開始
  startServer();
}

void loop() {}

void startServer(){
  httpd_handle_t camera_httpd = NULL;
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  httpd_uri_t capture_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = capture_handler,
    .user_ctx  = NULL
  };
  if (httpd_start(&camera_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(camera_httpd, &capture_uri);
  }
}

static esp_err_t capture_handler(httpd_req_t *req){
   camera_fb_t *fb = esp_camera_fb_get();
   if (!fb) {
       Serial.println("Camera capture failed");
       httpd_resp_send_500(req);
       return ESP_FAIL;
   }
   httpd_resp_set_type(req, "image/jpeg");
   httpd_resp_send(req, (const char *)fb->buf, fb->len);
   esp_camera_fb_return(fb);
   return ESP_OK;
}

Arduino/ESP32などでHTMLコードを簡単に保持する方法

改行付きテキストをソースコードに書きたい

HTMLファイルのような改行ありのテキスト情報を簡単に持ちたい場合、Arduinoだと結構面倒だった。

Stringで一行ごとに+で繋いだり、SPIFFSにファイルを保存して読み込んだり・・・

Pythonとかみたいにソース内にコピペで改行ありでそのまま書けたら楽なのに・・・

と思ってたけど、実はできました。↓

raw文字列(生文字列リテラル / Raw string literals)で改行付きテキストをソースコードに直接記載する

こんな感じ。

『R"( ※改行を含むテキスト※ )";』で行けました。

static const String HTML_TEXT = R"(
<!DOCTYPE html>
<html>
  <head>
      <script>
      window.onload = function() {
      // imgのsrcに「http://192.168.x.x:81/stream」のurlを設定
          var id_stream = document.getElementById("img_stream");
          id_stream.src = document.location.origin + ":81/stream";
      }
      </script>
  </head>
  <body>
      <img id="img_stream" width="320" height="240">
  </body>
</html>
)";

Grove接続でTimer Cameraで撮影した画像をM5StackのLCDに表示する(少しだけ高速化)

概要

  • 前回、Grove接続(シリアル)で受け取ってLCDに表示できるようにしたが、受信側のM5Stackの処理について単純に(1)データ受け取る→(2)LCD表示→(1)データ受け取る→・・・と順番に繰り返していたので、(1)と(2)で待ち時間が両方独立して必要だった。
  • 今回は、バッファを持たせることで(1)と(2)の待ち時間を重複させてちょっとだけ早くする。

M5Stack表示

コード

送信側:Timer Camera / Unit Cam

前回と同じ

#include "esp_camera.h"

void setup() {

  // USBシリアル通信(確認用)
  Serial.begin(115200);

  // GROVEのシリアル通信
  Serial1.begin(115200, SERIAL_8N1, 13, 4); // Timer Camera / Unit Cam

  // カメラ用パラメータ(Timer Camera / Unit Cam)
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer   = LEDC_TIMER_0;
  config.pin_d0       = 32;
  config.pin_d1       = 35;
  config.pin_d2       = 34;
  config.pin_d3       = 5;
  config.pin_d4       = 39;
  config.pin_d5       = 18;
  config.pin_d6       = 36;
  config.pin_d7       = 19;
  config.pin_xclk     = 27;
  config.pin_pclk     = 21;
  config.pin_vsync    = 22;
  config.pin_href     = 26;
  config.pin_sccb_sda = 25;
  config.pin_sccb_scl = 23;
  config.pin_pwdn     = -1;
  config.pin_reset    = 15;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG; // jpeg出力
  config.frame_size   = FRAMESIZE_QVGA; // 320x240
  config.jpeg_quality = 30; // jpeg品質 0(高品質)~63(低品質)
  config.fb_count     = 2; // CAMERA_GRAB_LATEST設定時は2以上必要
  config.grab_mode    = CAMERA_GRAB_LATEST; // 最終フレームバッファだけを取得
  config.fb_location  = CAMERA_FB_IN_PSRAM; // PSRAM使用する(Timer Camera用)
//  config.fb_location  = CAMERA_FB_IN_DRAM; // PSRAM使用しない(Unit Cam用)

  // カメラ初期化
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
      Serial.printf("Camera init failed with error 0x%x", err);
      return;
  }

  // 画像反転
  sensor_t *s = esp_camera_sensor_get();
  s->set_vflip(s, 1);
}

void loop() {

  // 画像取得&送信
  camera_fb_t *fb = esp_camera_fb_get();
  if (fb) {

    // データ区切り用文字列(DATA)
    Serial1.print("DATA");

    // サイズ送信
    Serial1.write((byte *)&(fb->len), 4); // size_t(int) -> byte[4]で送信

    // データ送信
    Serial1.write(fb->buf, fb->len);

    // フレームバッファ開放
    esp_camera_fb_return(fb);
  }
}
受信側:M5Stack

バッファを2つ用意して、空いてる方に入れる&入ってる方を表示するだけ。

#include <M5Stack.h>

uint8_t buf[4];
uint8_t buf1[10000];
volatile size_t data_size1 = 0;
uint8_t buf2[10000];
volatile size_t data_size2 = 0;

void setup() {

  M5.begin();

  // USBシリアル通信(確認用)
  Serial.begin(115200);

  // GROVEのシリアル通信
  Serial1.begin(115200, SERIAL_8N1, 21, 22); // PORT-A(Red)(GPIO21, GPIO22)

  // マルチタスク
  xTaskCreatePinnedToCore(subProcess, "subProcess", 4096, NULL, 1, NULL, 1);
}

void loop() {

  if (Serial1.available() > 4){
    
    // データ区切り用文字列(DATA)
    if (Serial1.read() == 'D') {
      if (Serial1.read() == 'A') {
        if (Serial1.read() == 'T') {
          if (Serial1.read() == 'A') {
 
            // サイズ取得
            Serial1.readBytes(buf, 4);
            size_t temp = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24 ; // byte[4] -> size_t(int)

            // データ取得
            while (1) {
              if (data_size1 == 0) {
                Serial1.readBytes(buf1, temp);
                data_size1 = temp;
                break;
              }
              else if (data_size2 == 0) {
                Serial1.readBytes(buf2, temp);
                data_size2 = temp;
                break;
              }
            }
            
          }
        }
      }
    }
  }
}

// 別タスク
void subProcess(void * pvParameters) {
  // 画面表示
  while (1) {
    if (data_size1 != 0) {
      M5.Lcd.drawJpg(buf1, data_size1, 0, 0, 320, 240);
      data_size1 = 0;
    }
    else if (data_size2 != 0) {
      M5.Lcd.drawJpg(buf2, data_size2, 0, 0, 320, 240);
      data_size2 = 0;
    }
  }
}

結果

  • ちょっとだけ早くなった感じはする。

Grove接続でTimer Cameraで撮影した画像をM5StackのLCDに表示する

概要

  • Timer Camera F/X、Unit Camなどで画像を撮影したものを、リアルタイム(一応動画っぽく)でM5Stackの画面に表示する。
  • 送信側、受信側どちらもArduino IDEで作成。
  • 撮影したjpegファイルデータのバイト列をシリアル通信(Grove接続)でそのまま渡す。
    • 同期取るために区切り文字の設定必要。
    • 送信側はjpegを撮影するごとに送信、受信側はjpegを受信するごとに表示。
  • 表示速度は遅い。シリアル通信なので、115200bps = 115.2kbps、画面表示の遅さも影響して4fps=4フレーム/1秒くらい。

Timer CameraとM5Stack

コード

送信側:Timer Camera / Unit Cam
#include "esp_camera.h"

void setup() {

  // USBシリアル通信(確認用)
  Serial.begin(115200);

  // GROVEのシリアル通信
  Serial1.begin(115200, SERIAL_8N1, 13, 4); // Timer Camera / Unit Cam

  // カメラ用パラメータ(Timer Camera / Unit Cam)
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer   = LEDC_TIMER_0;
  config.pin_d0       = 32;
  config.pin_d1       = 35;
  config.pin_d2       = 34;
  config.pin_d3       = 5;
  config.pin_d4       = 39;
  config.pin_d5       = 18;
  config.pin_d6       = 36;
  config.pin_d7       = 19;
  config.pin_xclk     = 27;
  config.pin_pclk     = 21;
  config.pin_vsync    = 22;
  config.pin_href     = 26;
  config.pin_sccb_sda = 25;
  config.pin_sccb_scl = 23;
  config.pin_pwdn     = -1;
  config.pin_reset    = 15;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG; // jpeg出力
  config.frame_size   = FRAMESIZE_QVGA; // 320x240
  config.jpeg_quality = 30; // jpeg品質 0(高品質)~63(低品質)
  config.fb_count     = 2; // CAMERA_GRAB_LATEST設定時は2以上必要
  config.grab_mode    = CAMERA_GRAB_LATEST; // 最終フレームバッファだけを取得
  config.fb_location  = CAMERA_FB_IN_PSRAM; // PSRAM使用する(Timer Camera用)
//  config.fb_location  = CAMERA_FB_IN_DRAM; // PSRAM使用しない(Unit Cam用)

  // カメラ初期化
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
      Serial.printf("Camera init failed with error 0x%x", err);
      return;
  }

  // 画像反転
  sensor_t *s = esp_camera_sensor_get();
  s->set_vflip(s, 1);
}

void loop() {

  // 画像取得&送信
  camera_fb_t *fb = esp_camera_fb_get();
  if (fb) {

    // データ区切り用文字列(DATA)
    Serial1.print("DATA");

    // サイズ送信
    Serial1.write((byte *)&(fb->len), 4); // size_t(int) -> byte[4]で送信

    // データ送信
    Serial1.write(fb->buf, fb->len);

    // フレームバッファ開放
    esp_camera_fb_return(fb);
  }
}

受信側:M5Stack
#include <M5Stack.h>

uint8_t buf[10000];
size_t data_size;

void setup() {

  M5.begin();

  // USBシリアル通信(確認用)
  Serial.begin(115200);

  // GROVEのシリアル通信
  Serial1.begin(115200, SERIAL_8N1, 21, 22); // PORT-A(Red)(GPIO21, GPIO22)
}

void loop() {

  // データ区切り用文字列(DATA)
  if (Serial1.available() > 4){
    if (Serial1.read() == 'D') {
      if (Serial1.read() == 'A') {
        if (Serial1.read() == 'T') {
          if (Serial1.read() == 'A') {
            
            // サイズ取得
            Serial1.readBytes(buf, 4);
            data_size = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24 ; // byte[4] -> size_t(int)

            // データ取得
            Serial1.readBytes(buf, data_size);

            // 画面表示
            M5.Lcd.drawJpg(buf, data_size, 0, 0, 320, 240);
          }
        }
      }
    }
  }

結果

  • 結構簡単なコードでできました。やっぱ遅いけど。
  • 早くするならWifi / Bluetoothなど使ったほうがいい。
    Timer CameraとM5Stackでの撮影