2022年8月16日火曜日

SCCBバスプロトコル詳解と実装方法

 ■SCCB紹介

 SCCB (Serial Camera Control Bus) は、OmniVision社のシリアル カメラ バス プロトコルです。OV7670 などの OV で始まるカメラ モジュールは、SCCB プロトコルを使用します。I2C プロトコルと類似しているため、SIO_CSIO_DSCLSDAと呼びます。

 本文は2線式SCCBプロトコルの内容とプログラム実装を中心に解説します。

■回路図

 2線式SCCBに対して、SIO_CSIO_Dだけを接続すればよいです。

■プロトコルの内容とプログラム実装

2 線式 SCCB バス プロトコル解析

<1>送信開始フラグ: 送信 (データの読み取りまたは書き込み) ごとに、下図に従って SIO_C SIO_D に指定されたHIGH-LOWレベルを出力する必要があります。対応するレベルを受け取ると、Slaveは転送開始が判ります。次のように操作します。SIO_CHIGHのままでSIO_DHIGHからLOWに変換して、SIO_CLOWに戻ります。

void SCCB_Start(void) {

  SCCB_SDA = 1;

  SCCB_SCL = 1;

  delay_us(50);

  SCCB_SDA = 0;

  delay_us(50);

  SCCB_SCL = 0;

}

<2>送信完了フラグ:Slaveに送信完了を通知します。次のように操作します。SIO_CHIGHのままでSIO_DLOWからHIGHに変換します。 

void SCCB_Stop(void) {

  SCCB_SDA = 0;

  delay_us(50);

  SCCB_SCL = 1;

  delay_us(50);

  SCCB_SDA = 1;

  delay_us(50);

}

<3>伝送フェーズ

転送フェーズ(Transmission Phases)は、データ転送の基本単位です。9bitで構成しています、それぞれは、bit7bit09bit目のDon't careまたはNAです。Don't care ビットと NA ビットの意味は後で説明します。

<4>書き込み

Slave1バイトのデータを書き込みすることは1回書き込み操作と呼びます。1バイトのデータを書き込み時、SlaveID、対象レジスタ番号も伝える必要があるため、書き込み操作のプロセスは三つのフェーズ(Phase)が必要です。

フェーズ1SCCBプロトコルは複数のSlaveに転送ができるため、Slaveの番号が伝える必要があります。実際のIDアドレスは8bitで、bit7bit1Slaveの番号ですが、0127のトータル128Slaveに転送が可能です。bit0Slaveにデータを書き込み(0)か読み取り(1)かを示しています。9bit目はDon't careビット(上図Xのビット)です。例えば、OV7670の場合、Masterから 8bitのデータを受信した後、SIO_C=1の期間にSlaveSIO_DピンにLOWをセットします。この間、MasterSIO_Dのレベル情報を読み取って判断する、LOWレベルを読み取った場合は、Slaveはフェーズ1の最初8bitを正常受信したことを示して、送信は成功します。それ以外の場合、送信は失敗します。

フェーズ2:データが書き込まれるレジスタの番号をスレーブに送信します。レジスタの番号は、OVセンサーのデータシートに記載されています。レジスタの番号も 8bitのデータです。 同様に、フェーズ29bit目もDon't careビットであり、このbitの説明はフェーズ1と同じです。

フェーズ3:指定されたレジスタにデータを書き込みます。bit7bit0は、レジスタに書き込みたいデータです。9ビット目もDon't careビットです。

u8 SCCB_WR_Byte(u8 dat) { // 一つフェーズを書き込み

  u8 j, res;

  for (j = 0; j < 8; j++) {

    if (dat&0x80) SCCB_SDA = 1;

    else SCCB_SDA = 0;

    data<<=1;

    delay_us(50);

    SCCB_SCL = 1;

    delay_us(50);

    SCCB_SCL = 0;

}

SCCB_SDA_IN();  // 或いはpinMode(SDA, INPUT); Slaveは値をセット可能

delay_us(50);

SCCB_SCL = 1; // SCLHIGHにする、Slaveはデータを受信した後、SDALOW

delay_us(50);

if (SCCB_READ_SDA) res = 1;  // SDAHIGHの場合、送信失敗

  else res = 0;  // 送信成功

  SCCB_SCL = 0;

  SCCB_SDA_OUT(); // あるいは pinMode(SDA, OUTPUT);

  Return res;

}

三つフェーズを書き込みの関数:

u8 SCCB_WR_Reg(u8 reg, u8 data) {

  u8 res = 0;

  SCCB_Start();

  If (SCCB_WR_Byte(SCCB_ID)) res = 1; // OV7670の場合は0x42

  delay_us(100);

  if (SCCB_WR_Byte(reg)) res = 1;

  delay_us(100);

  if (SCCB_WR_Byte(data)) res = 1;

  SCCB_Stop();

  return res;

} 

<5>読み取り操作

Slaveのレジスタから 1 バイトのデータを読み取る時、次の 2 つのデータ送信プロセスが必要です。

2 フェーズ書き込み送信プロセス:MasterからSlave2フェーズを送信します。最初のフェーズは、スレーブの ID 番号を示します。 2 番目のフェーズは、スレーブのどのレジスタから読み取るかを示します。 

2フェーズ読み取り送信プロセス:Slaveからデータを読み取っています。フェーズ1は①のフェーズ1とほぼ同じですが、Slaveから読み取りから、bit01のほうをご注意ください。フェーズ2は転送データです。

したがって、SIO_Dにつながっているピンは入力モードに設定する必要があります。8bitのデータを読み取った後、データが正常に受信されたことをSlaveに伝えるために、Master1bitの応答信号(NA)Slaveに送信する必要があります。

NAを送信するために、MasterSIO_DHIGHにセットしたままで、SIO_CLOWからHIGHに変更してからLOWに戻る必要があります。

void SCCB_No_Ack(void) {

  delay_us(50);

  SCCB_SDA = 1;

  SCCB_SCL = 1;

  delay_us(50);

  SCCB_SCL = 0;

  delay_us(50);

  SCCB_SDA = 0;

  delay_us(50);

}

2フェーズを読み取り関数

u8 SCCB_RD_Byte(void) {

  u8 temp = 0, j;

  SCCB_SDA_IN();

  for (j = 8; j > 0; j--) {

    delay_us(50);

    SCCB_SCL = 1;

    temp = temp << 1;

    if (SCCB_READ_SDA) temp++;

    delay_us(50);

    SCCB_SCL = 0;

  }

  SCCB_SDA_OUT();

  return temp;

}

レジスタから読み取り関数:

u8 SCCB_RD_Reg(u8 reg) {

  u8 val = 0;

  SCCB_Start();

  SCCB_WR_Byte(SCCB_ID);

  delay_us(100);

  SCCB_WR_Byte(reg);

  delay_us(100);

  SCCB_Stop();

  

  SCCB_Start();

  SCCB_WR_Byte(SCCB_ID|0x01);

  delay_us(100);

  val = SCCB_RD_Byte();

  SCCB_No_Ack();

  SCCB_Stop();

  return val;

}

ここまで、SCCBのレジスタ読み取り操作とレジスタ書き込み操作の関数SCCB_RD_RegSCCB_WR_Regを実装しました。この二つ関数を利用して、OVセンサーの指定されたレジスタへのデータを読み取りしたり、書き込みしたり、センサーの初期化ができました。



2022年8月11日木曜日

PWMの理解ーーその2:ブザーで音楽

■周波数

PWMとは、Pulse Width Modulationの略で、波長のような制御を行います。

細かい部分で見ると、ONとOFFを一定間隔で繰り返しているだけです。

ここで周波数といった名前が登場しますが、ONの長さの事を指します。

ブザーにとっての周波数とは、ドレミの音階を指定することができることになります。

ESP32では、PWM制御の関数が用意されております。(ledcSetup、ledcAttachPin、ledcWriteTone)

■音階

音階は、「音を高低の順番に並べたもの」あり、音の高低は周波数で表します。

音は、周波数が半分になると1オクターブ低くなり、周波数が倍になると1オクターブ高くなります。

1オクターブには12の音があり(①ド、②ド#(レ♭)、③レ、④レ#(ミ♭)、⑤ミ、⑥ファ、⑦ファ#(ソ♭)、⑧ソ、⑨ソ#(ラ♭)、⑩ラ、⑪ラ#(シ♭)、⑫シ)、

その12の音は、隣り合う半音間での周波数の比率が同じです。(音階に対して等比数列的に増える)

音階と周波数の関係は下記サイトをご参照ください。

音階の周波数

■配線図

■プログラム

#define BUZZER_PIN 2              // ブザーのピン番号
#define BUZZER_CHANEL 0       // ブザーのチャネル番号
#define BEAT 250                   // 音の長さ(0.25秒間隔)
#define G3 196 // ソ3
#define C4 261.6 // ド4       1
#define D4 293.665 // レ4     2
#define E4 329.63 // ミ4      3
#define F4 349.228 // ファ4   4
#define G4 391.995 // ソ4     5
#define A4 440 // ラ4         6
#define B4 493.883 // シ4     7
#define C5 523.251 // ド5     i
void playTone(double tone, int num) {
  ledcWriteTone(BUZZER_CHANEL, tone);
  delay(BEAT * num);
}
void playTone(long tone) {
  playTone(tone, 2);
}
void playMusic() {
  // 1 2 3 1 | 1 2 3 1 | 3 4 5 - | 3 4 5 - |
  // 56543 1 | 56543 1 | 3 5 1 - | 3 5 1 -
  playTone(C4);
  playTone(D4);
  playTone(E4);
  playTone(C4);
  playTone(C4);
  playTone(D4);
  playTone(E4);
  playTone(C4);
  playTone(E4);
  playTone(F4);
  playTone(G4, 4);
  playTone(E4);
  playTone(F4);
  playTone(G4, 4);
  playTone(G4, 1);
  playTone(A4, 1);
  playTone(G4, 1);
  playTone(F4, 1);
  playTone(E4);
  playTone(C4);
  playTone(G4, 1);
  playTone(A4, 1);
  playTone(G4, 1);
  playTone(F4, 1);
  playTone(E4);
  playTone(C4);
  playTone(E4);
  playTone(G3);
  playTone(C4, 4);
  playTone(E4);
  playTone(G3);
  playTone(C4, 4);
}
void setup() {
  ledcSetup(BUZZER_CHANEL, 12000, 8);
  ledcAttachPin(BUZZER_PIN, BUZZER_CHANEL);
}
void loop() {
  playMusic();
  delay(1000);
}

2022年8月9日火曜日

ESP32-C3開発ボードに書き込みできない解決方法

 ■問題

ARDUINO環境で、ESP32-C3の開発ボードに書き込み時、下記エラーが出てます。

A fatal error occurred: Invalid head of packet (0x47)

■原因

新発行のESP32-C3開発ボードに対して、ARDUINOはまたサポートしておりません。

解決方法としては最新のLIBをインストールする

■インストール手順:

・最新版ARDUINOをダウンロード&インストールする

https://www.arduino.cc/en/software

・最新のESP32LIBを切り替える

https://github.com/espressif/arduino-esp32から、ESP32をZIPでダウンロードする

C:\Users\XXXXXXXXXX\ArduinoData\packages\esp32\hardware\esp32\1.0.6

に解凍する

解凍後はこのイメージです

・tools\get.exeを実施する
・ARDUINOを再起動し、ESP-C3の選択肢が見つかれます。
・「ESP32C3 Dev Module」を選択し、正常書き込みできました。


PWMの理解ーーその1:雰囲気ランプ

■ESP32-C3開発ボード

 ESP32-C3LEDコントローラーが持っています。これを使って、簡単に雰囲気ランプ効果ができます。

今回は、ESP-C3-32開発ボードを利用します。下記ようなものです。

赤枠で、開発ボードで一つRGB LEDがあります。次は、このRGB LEDをコントロールしてみます。
■GPIO情報

上図をみると、IO3IO4IO5のいずれもRGB LEDを繋がっています(それぞれはRG、Bです)から、サンプルとして、今回はIO3(赤色)を利用しましょう。

■PWMコントロール
PWMとは、Pulse Width Modulationの略で、波長のような制御を行います。

PWMをコントロールするために、下記設定が必要です:

1、タイマーを配置: のDuty比率を指定する

2、チャネルを配置:PWMデータ出力のGPIO

3、ハードウェアのFadeOn機能を利用し、LEDの明るさを変更する

楽鑫は相応の構造体を提供しました。提供している構造体を利用してやりやすいです。

ledc_timer_config_t  ⇒ タイマー配置用

ledc_channel_config_t ⇒ チャネル配置用

■プログラム

ledcはESP32のLED用高精度PWM機能です。通常のPWMだけでなく、ledc_set_fade_with_timeとledc_fade_startを使えば自動的に指定秒数でfadeしてくれて大変便利。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "sdkconfig.h"
#include "driver/ledc.h"
#define BLINK_GPIO_R 3
#define BLINK_GPIO_G 4
#define BLINK_GPIO_B 5
#define LEDC_TEST_DUTY 1000
#define LEDC_TEST_FADE_TIME 2000
ledc_timer_config_t   ledc_timer;
ledc_channel_config_t ledc_channel;
void setup() {
  ledc_timer.duty_resolution = LEDC_TIMER_10_BIT; // PWM Duty
  ledc_timer.freq_hz = 1000; // PWM信号の周波数
  ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE; // タイマーモード(低速)
  ledc_timer.timer_num = LEDC_TIMER_0; // タイマー インデックス
  ledc_timer_config(&ledc_timer);
  ledc_channel.channel = LEDC_CHANNEL_0;
  ledc_channel.duty = 0;
  ledc_channel.gpio_num = BLINK_GPIO_R;
  ledc_channel.speed_mode = LEDC_LOW_SPEED_MODE;
  ledc_channel.timer_sel = LEDC_TIMER_0;
  ledc_channel_config(&ledc_channel);
  
  ledc_fade_func_install(0);
}
void loop() {
  ledc_set_fade_with_time(ledc_channel.speed_mode, ledc_channel.channel, LEDC_TEST_DUTY, LEDC_TEST_FADE_TIME);
  ledc_fade_start(ledc_channel.speed_mode, ledc_channel.channel, LEDC_FADE_NO_WAIT);
  vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS);
  ledc_set_fade_with_time(ledc_channel.speed_mode, ledc_channel.channel, 0, LEDC_TEST_FADE_TIME);
  ledc_fade_start(ledc_channel.speed_mode, ledc_channel.channel, LEDC_FADE_NO_WAIT);
  vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS);
}

■最終結果


 ■RGBを同時コントロールの場合、構造体はこのような実装する

  ledc_channel_config_t ledc_channel[3];
  ledc_channel[0].channel = LEDC_CHANNEL_0;
  ledc_channel[0].duty = 0;
  ledc_channel[0].gpio_num = BLINK_GPIO_R;
  ledc_channel[0].speed_mode = LEDC_LOW_SPEED_MODE;
  ledc_channel[0].timer_sel = LEDC_TIMER_0;
  ledc_channel[1].channel = LEDC_CHANNEL_1;
  ledc_channel[1].duty = 0;
  ledc_channel[1].gpio_num = BLINK_GPIO_G;
  ledc_channel[1].speed_mode = LEDC_LOW_SPEED_MODE;
  ledc_channel[1].timer_sel = LEDC_TIMER_1;
  ledc_channel[2].channel = LEDC_CHANNEL_2;
  ledc_channel[2].duty = 0;
  ledc_channel[2].gpio_num = BLINK_GPIO_B;
  ledc_channel[2].speed_mode = LEDC_LOW_SPEED_MODE;
  ledc_channel[2].timer_sel = LEDC_TIMER_2;
  ledc_channel_config(&ledc_channel[0]);
  ledc_channel_config(&ledc_channel[1]);
  ledc_channel_config(&ledc_channel[2]);

2022年8月6日土曜日

MQTTプロトコル紹介

 ■MQTTとは

 MQTT(Message Queue Telemetry Transport)は、発行/購読型のメッセージ処理を実現するための軽量なプロトコルとして設計されています。

 メッセージのサブスクライバー(購読者)は、メッセージのパブリッシャー(発行元)が提供する MQTT トピックをあらかじめ講読しておきます。MQTT トピックに何か変化があると、サブスクライバーは PUBLISH コマンドを受け取ってそれを知ることができます。

 ネットワークが不安定な場所や、性能が低いデバイスでも動くように軽量化されているのが特徴で、TCP/IP ネットワークをベースに作られています。

■HTTPとMQTTの比較

HTTPMQTT
同期/非同期同期非同期
送受信対象1対1多対多
データ量大きい(重い)小さい(軽い)
通信が不安定×(送受信不可)○(再送受信可能)
■MQTTの特徴

・シンプル
・軽量
・省エネ
■MQTTバージョン
 メインバージョンはMQTT3.1.1とMQTT5の二つがあります。
 MQTT3.1.1は2014年10月発行
 MQTT5は2019年3月発行
■MQTTトピック
 MQTT プロトコルでは、メッセージの講読や発行の対象として MQTT トピックを使用します。
 プロトコル上、MQTT トピックはパブリッシャーに複数存在し、それらは階層を持ったトピック名として表現されます。サブスクライバーは MQTT のトピック名を指定してそのトピックを講読したり、メッセージを発行したりできます。
 使用例

 MQTTプロトコルはIoTの重要なプロトコルですが、後続でMQTTサーバーの構築とスマートフォンAPPサンプルはまた紹介します。

2022年7月31日日曜日

WebSocketでOV7670-ESP32の画像表示

 ■OV7670

 OV7670 はVGA(640×480画素)サイズのイメージセンサです。内部構造はCMOSイメージセンサと信号処理用 DSPがワンチップになっています。マイコンへの映 像信号出力は、ディジタル8ビット・パラレルです。
製品仕様:
・イメージセンサー:OV7670 CMOS、VGA-640(V)×480(V)
・レンズ焦点距離:3.6mm(F2.5)
・光学サイズ:1/6型、画素セル:3.6um x 3.6um
・フィールド角:25°(対角)
・小さいサイズ:3.5cm x 3.5cm
・低動作電圧:2.5V~3.0VDC
・低動作電流:60mw(15fps VGA YUV format)
・スリープモード<20μA
・保存温度:-30℃~70℃
・動作温度:0℃~50℃
・感度:1.3V/(Lux-sec)
・インターフェース:標準SCCB(Serial Camera Control Bus)インターフェース(I2C互換)
・データ出力フォーマット:Raw RGB、RGB(GRB4:2:2)、RGB(565/555/444)、YUV(4:2:2)、YCbCr(4:2:2)
・データ出力解像度:VGA、CIF、CIF~40x30の任意解像度
・サンプリングタイプ:Vario Pixel
・接続コネクタ:2.54mmピッチ(2列)18ピン、ピンヘッダ
■ESP32接続
 下記ように、OV7670のピンとESP32ピンを接続します。
 ※データ転送スピードを考えると、短いジャンプワイヤーが勧め
 ※SDAとSCLは1KΩの抵抗を使用でも可
OV7670ESP32
D0IO36
D1IO39
D2IO34
D3IO35
D4IO32
D5IO33
D6IO25
D7IO26
MCLKIO15
PCLKIO14
HREFIO23
VSYNCIO13
SDAIO21 (1kΩ Pull Up)
SCLIO22 (1KΩ Pull Up)
RST3.3V
PWDNGND
■Web送信
 Web送信はWebSocketを使いました。
 下記ライブラリが必要です。
■部品一覧:
・ESP-WROOM-32開発ボード
・OV7670
・USBケーブル
・ジャンプワイヤー
・1KΩ抵抗*2
・ブレッドボード
■プログラム:
 単純なESP32、OV7670と接続ため(FIFOなどがない)、インターネットのいろいろなサンプルを試した結果で、
は使えます。
 
 下記箇所をちょっと修正しました。
 ①13行目~14行目までのWiFi接続情報
 ②23行目~33行目までのピン情報
 ③APモードは不要のため、削除
 ④DBABuffer.hはコンパイルエラーあるため、「#include <stdlib.h>」を追加

#include "OV7670.h"

#include <WebSockets.h>
#include <WebSocketsClient.h>
#include <WebSocketsServer.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <WiFiClient.h>
#include "canvas_htm.h"

const char *ssid_AP_1 = "XXXXXX";
const char *pwd_AP_1  = "XXXXXXX";

const int SIOD = 21; //SDA
const int SIOC = 22; //SCL

const int VSYNC = 13;
const int HREF = 23;

const int XCLK = 15;
const int PCLK = 14;

const int D0 = 36;
const int D1 = 39;
const int D2 = 34;
const int D3 = 35;
const int D4 = 32;
const int D5 = 33;
const int D6 = 25;
const int D7 = 26;

const int TFT_DC = 2;
const int TFT_CS = 5;

OV7670 *camera;

WiFiMulti wifiMulti;
WiFiServer server(80);

unsigned char pix = 0;

unsigned char start_flag = 0xAA;
unsigned char end_flag = 0xFF;
unsigned char ip_flag = 0x11;

WebSocketsServer webSocket(81);    // create a websocket server on port 81

void startWebSocket() { // Start a WebSocket server
  webSocket.begin();                          // start the websocket server
  webSocket.onEvent(webSocketEvent);          // if there's an incomming websocket message, go to function 'webSocketEvent'
  Serial.println("WebSocket server started.");
}

void startWebServer()
{
   server.begin();
   Serial.println("Http web server started.");
}
void serve() {
  WiFiClient client = server.available();
  if (client) 
  {
    //Serial.println("New Client.");
    String currentLine = "";
    while (client.connected()) 
    {
      if (client.available()) 
      {
        char c = client.read();
        //Serial.write(c);
        if (c == '\n') 
        {
          if (currentLine.length() == 0) 
          {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();
            client.print(canvas_htm);
            client.println();
            break;
          } 
          else 
          {
            currentLine = "";
          }
        } 
        else if (c != '\r') 
        {
          currentLine += c;
        }
        
      }
    }
    // close the connection:
    client.stop();

  }  
}

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t payloadlength) { // When a WebSocket message is received
 
  int blk_count = 0;

  char canvas_VGA[] = "canvas-VGA";
  char canvas_Q_VGA[] = "canvas-Q-VGA";
  char canvas_QQ_VGA[] = "canvas-QQ-VGA";
  char canvas_QQQ_VGA[] = "canvas-QQQ-VGA";
  char ipaddr[26] ;
  IPAddress localip;
  
  switch (type) {
    case WStype_DISCONNECTED:             // if the websocket is disconnected
      Serial.printf("[%u] Disconnected!\n", num);
      break;
    case WStype_CONNECTED: {              // if a new websocket connection is established
        IPAddress ip = webSocket.remoteIP(num);
        Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
           webSocket.sendBIN(0, &ip_flag, 1);
           localip = WiFi.localIP();
           sprintf(ipaddr, "%d.%d.%d.%d", localip[0], localip[1], localip[2], localip[3]);
           webSocket.sendTXT(0, (const char *)ipaddr);
           
      }
      break;
    case WStype_TEXT:                     // if new text data is received
      if (payloadlength == sizeof(canvas_QQQ_VGA)-1) {
        if (memcmp(canvas_QQQ_VGA, payload, payloadlength) == 0) {
              Serial.printf("canvas_QQQ_VGA");
              webSocket.sendBIN(0, &end_flag, 1);
              camera = new OV7670(OV7670::Mode::QQQVGA_RGB565, SIOD, SIOC, VSYNC, HREF, XCLK, PCLK, D0, D1, D2, D3, D4, D5, D6, D7);
        }
      } else if (payloadlength == sizeof(canvas_QQ_VGA)-1) {
        if (memcmp(canvas_QQ_VGA, payload, payloadlength) == 0) {
              Serial.printf("canvas_QQ_VGA");
              webSocket.sendBIN(0, &end_flag, 1);
              camera = new OV7670(OV7670::Mode::QQVGA_RGB565, SIOD, SIOC, VSYNC, HREF, XCLK, PCLK, D0, D1, D2, D3, D4, D5, D6, D7);
        }
      } else if (payloadlength == sizeof(canvas_Q_VGA)-1) {
        if (memcmp(canvas_Q_VGA, payload, payloadlength) == 0) {
              Serial.printf("canvas_Q_VGA");
              webSocket.sendBIN(0, &end_flag, 1);
              camera = new OV7670(OV7670::Mode::QVGA_RGB565, SIOD, SIOC, VSYNC, HREF, XCLK, PCLK, D0, D1, D2, D3, D4, D5, D6, D7);
        }
      } else if (payloadlength == sizeof(canvas_VGA)-1) {
        if (memcmp(canvas_VGA, payload, payloadlength) == 0) {
              Serial.printf("canvas_VGA");
              webSocket.sendBIN(0, &end_flag, 1);
              camera = new OV7670(OV7670::Mode::VGA_RGB565, SIOD, SIOC, VSYNC, HREF, XCLK, PCLK, D0, D1, D2, D3, D4, D5, D6, D7);
        }
      } 

      
      blk_count = camera->yres/I2SCamera::blockSlice;//30, 60, 120
      for (int i=0; i<blk_count; i++) {

          if (i == 0) {
              camera->startBlock = 1;
              camera->endBlock = I2SCamera::blockSlice;
              webSocket.sendBIN(0, &start_flag, 1);
          }

          if (i == blk_count-1) {
              webSocket.sendBIN(0, &end_flag, 1);
          }
        
          camera->oneFrame();
          webSocket.sendBIN(0, camera->frame, camera->xres * I2SCamera::blockSlice * 2);
          camera->startBlock += I2SCamera::blockSlice;
          camera->endBlock   += I2SCamera::blockSlice;
      }
      
      break;
    case WStype_ERROR:                     // if new text data is received
      Serial.printf("Error \n");
    default:
      Serial.printf("WStype %x not handled \n", type);

  }
}

void initWifiMulti()
{

    wifiMulti.addAP(ssid_AP_1, pwd_AP_1);

    Serial.println("Connecting Wifi...");
    while(wifiMulti.run() != WL_CONNECTED) {
       delay(5000);        
       Serial.print(".");
    }
    
    Serial.print("\n");
    Serial.print("WiFi connected : ");
    Serial.print("IP address : ");
    Serial.println(WiFi.localIP());
}

void setup() {
  Serial.begin(115200);
  initWifiMulti();
  startWebSocket();
  startWebServer();
}

void loop()
{
  webSocket.loop();
  serve();
}
■結果
ブラウザーを開いて、URLを入力し
配線イメージ
写真工場 :)

シンプルロボットカーセットの使い方

■ マイコン :ESP3266-CH340  シンプルの案として、マイコンは安いESP8266-CH340開発ボードを選びました。該当マイコンもWIFI機能があります。  ドライバーダウンロード場所:    https://sparks.gogo.co.nz/ch340.html...