2-3-1.カメラ_OV7670
電子工作
カメラの電子工作を行います。カメラ映像を閲覧するためには無線Wi-Fi経由でWebブラウザで映像を確認することになりますので「3-1.無線Wi-Fi」と「3-2.Webサーバ」を学習してから本電子工作をお願いします。
カメラへの配線となりますので、以下を参照し配線して下さい。LEDは無線接続状態を把握するために「2-1-1.LED」と同様に取り付けます。
【抵抗10KΩのカラーコードは「茶黒黒赤茶」です。】
【配線】(左:カメラ端子、右:ESP32開発ボード端子)
3.3V => 3.3V
GND => GND
SCL(SSCB_SCL) => IO22 (10KΩ Pull Up)
SDA(SSCB_SDA) => IO21 (10kΩ Pull Up)
VS(VSYNK) => IO13
HS => 接続なし
PLK(PCLK) => IO14
XLK(XCLK) => IO15
D7 => IO26
D6 => IO25
D5 => IO33
D4 => IO32
D3 => IO35
D2 => IO34
D1 => VN(IO39)
D0 => VP(IO36)
RET => 3.3V
PWDN => GND
【ESP32-DevKitCの入出力端子はこちらを参照下さい】
配線は間違いやすいため上記の通り確実に実施して下さい。
【注意】
回路図にはありませんがESP32開発ボードにソフトを書き込む場合はEN端子を0.1μFのコンデンサでGNDへ配線して下さい。ESP32開発ボード(30pin)固有の利用方法なので回路図は省略させて頂きます。
スケッチ(制御ソフトウェア)
ライブラリのインストール
sLab-Remo2関連のライブラリをこちらにまとめていますので、以下の3つのライブラリを手順通りにインストールして下さい。
(1)ESPAsyncWebServerライブラリ
(2)arduinoWebSocketsライブラリ
(3)OV7670-ESP32ライブラリ
スケッチ書込
ArduinoIDEでESP32開発ボードに以下のスケッチ(3ファイル)を書き込んで下さい。
(1)基本スケッチ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
//************************************************************************* // sLab-Remo2(エスラボ・リモ2) // 【カメラ用】 //************************************************************************* // ①ライブラリの読み込み #include <WiFi.h> #include <Wire.h> #include "ESPAsyncWebServer.h" #include "WebSocketsServer.h" #include "OV7670.h" // ####################### ②Wi-Fi設定(環境設定) ####################### const char *ssid = "##### SSID #####"; const char *password = "### PASSWORD ###"; IPAddress ip(192, 168, 1, 123); // IPアドレス(本機が利用するIP) IPAddress gateway(192, 168, 1, 1); // デフォルトゲートウェイ IPAddress subnet(255, 255, 255, 0); // サブネットマスク // ################################################################### // ③ピン配置 const byte LED_PIN = 16; // LED緑 // ④カメラピン配置 const camera_config_t cam_conf = { .D0 = 36, .D1 = 39, .D2 = 34, .D3 = 35, .D4 = 32, .D5 = 33, .D6 = 25, .D7 = 26, .XCLK = 15, // ★27利用時 Wi-Fi接続不可 .PCLK = 14, .VSYNC = 13, .xclk_freq_hz = 10000000, // XCLK 10MHz .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0 }; // ⑤カメラ解像度指定 #define CAM_RES QCIF // カメラ解像度 #define CAM_WIDTH 176 // カメラ幅 #define CAM_HEIGHT 144 // カメラ高さ #define CAM_DIV 1 // 1画面分割数 // ⑥グローバル変数宣言 OV7670 cam; // camera AsyncWebServer server(80); // WebサーバPort80番 WebSocketsServer webSocket = WebSocketsServer(81); uint8_t clientNum; bool wsFlag = false; // WebSocket状態フラグ(true:接続状態、false:未接続状態) bool ledFlag = true; // LED制御フラグ int dataSize = 2 * CAM_WIDTH * CAM_HEIGHT; // カメラデータ: 2 * 176 * 144 = 50688 [Byte] uint8_t wsCamData[50688]; // カメラデータを保持 int adjustAngle = 0; // Angle補正 int preAdjustAngle = 0; // 前回Angle補正 byte preAngle = -1; // 現在の角度を保存し、角度変更があるか把握 byte preCarMove = 1; // 1:初期/Go、2:Left、3:Right、4:Back、5:Other // ⑦Arduino初期設定処理 void setup() { // ⑧処理開始 Serial.begin(115200); Serial.println("sLab-Remo2 Sart"); // ⑨Wire開始、端子設定 Wire.begin(); Wire.setClock(400000); pinMode ( LED_PIN, OUTPUT ); // ⑩無線Wi-Fi接続 WiFi.config( ip, gateway, subnet ); WiFi.begin ( ssid, password ); while ( WiFi.status() != WL_CONNECTED ) { // 接続するまで無限ループ処理 // ⑪接続中はLEDを1秒毎に点滅する ledFlag = !ledFlag; digitalWrite(LED_PIN, ledFlag); delay ( 1000 ); Serial.print ( "." ); } // ⑫Wi-Fi接続完了(IPアドレス表示) Serial.print ( "Wi-Fi Connected! IP address: " ); Serial.println ( WiFi.localIP() ); // ⑬Wi-Fi接続時はLED点灯(Wi-Fi接続状態) digitalWrite ( LED_PIN, true ); // ⑭カメラ初期化処理 Serial.println( "cam init" ); esp_err_t err = cam.init(&cam_conf, CAM_RES, RGB565); if(err != ESP_OK){ Serial.print( "cam.init ERR:" ); Serial.println(err); // ⑮失敗時は15秒後に再起動しRetry delay(15000); ESP.restart(); } cam.vflip( false ); // ⑯WebServerアクセス処理設定[ホーム画面表示] server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ sendTop(request); Serial.println ( "WEB TOP" ); }); // ⑰WebServerアクセス処理設定[LED制御] server.on("/led", HTTP_GET, [](AsyncWebServerRequest *request){ String setLedVal = request->getParam("o")->value(); if (setLedVal == "ON") { digitalWrite ( LED_PIN, true ); Serial.println ( "led on" ); } else { digitalWrite ( LED_PIN, false ); Serial.println ( "led off" ); } request->send(200, "text/plain", "OK"); }); // ⑱Responseヘッダオプション追加処理 DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); // ⑲WebServer起動 server.begin(); // ⑳WebSocket起動 webSocket.begin(); webSocket.onEvent(onWebSocketEvent); Serial.println("##### setup completed #####"); } // ㉑Arduinoループ処理(通常時はこの処理) void loop(void) { // ㉒WebSocket処理 webSocket.loop(); // ㉓WebSocket接続判定 if (wsFlag) { // ㉔カメラデータ読み込み処理 cam.getLines( 1 , wsCamData , CAM_HEIGHT); Serial.println("cam.getLines"); // ㉕映像データ送信処理 webSocket.sendBIN(clientNum, wsCamData, dataSize); } } |
(2)Web(HTML)関連スケッチ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
// ①HTMLトップ返答処理 void sendTop( AsyncWebServerRequest *request ) { // ②返答HTML定義 char *htmltop = "<!doctype html><html class=\"fullscrn\"><head><meta charset=\"UTF-8\">\r\n" "<meta name=\"viewport\" content=\"width=device-width\">\r\n" "<link rel=\"icon\" href=\"http://kit.socinno.com/wp-content/uploads/2019/09/favicon.ico\">\r\n" "<style type=\"text/css\"><!--\r\n" "h1 { margin: 13px 0; font-size: 28px; line-height: 70%; }\r\n" "#contents { width: 100%; max-width: 320px; }\r\n" "footer { text-align: right; }\r\n" ".fullscrn { width: 100%; height: 100%; }\r\n" ".cosmos { color: white; background-color: #797572;\r\n" "background-image: linear-gradient(-173deg, rgba(255,255,255,0.20) 0%, #000000 100%),\r\n" "linear-gradient(72deg, rgba(255,255,255,0.25) 25%, rgba(0,0,0,0.25) 100%),\r\n" "radial-gradient(47% 102%, rgba(255,255,255,0.50) 0%, rgba(21,24,32,0.60) 120%);background-blend-mode: multiply; }\r\n" "--></style>\r\n" "<title>sLab-Car</title><style>@media print {#ghostery-purple-box {display:none !important}}</style><style>@media print {#ghostery-purple-box {display:none !important}}</style></head>\r\n" "<body class=\"cosmos fullscrn\" data-gr-c-s-loaded=\"true\"><center><div id=\"contents\"><header><h3>sLab-カメラ表示画面</h3></header>\r\n" "<form id=\"canform\" method=\"get\" action=\"javascript:void(0);\">\r\n" "<br>\r\n" "<div id=\"dcan1\" style=\"display: block;\"><canvas id=\"ccanvas\" width=\"176\" height=\"144\" style=\"background-color:grey; float:left\"></canvas>\r\n" "<span style=\"line-height:25px;text-align:center;\"><br><button id=\"startbtn\" type=\"button\" style=\"width:70px;height:25px\" onclick=\"wsConnect()\">開始</button><br>\r\n" "<font color=\"red\"><span id=\"msg\" style=\"vertical-align:middle;\">開始して下さい</span></font><br>\r\n" "<button id=\"endbtn\" type=\"button\" style=\"width:70px;height:25px\" onclick=\"wsDisconnect()\">終了</button></span><p style=\"clear: both;\"></p><br></div>\r\n" "<br>\r\n" "<footer><font size=\"-1\">Copyright© SOCINNO All rights reserved.</font></footer></form></div></center>\r\n" "\r\n" "<script language=\"javascript\" type=\"text/javascript\">\r\n" "var wsUri;\r\n" "var socket = null;\r\n" "var tms;\r\n" "var msg;\r\n" "var ctx;\r\n" "var width;\r\n" "var height;\r\n" "var imageData;\r\n" "var pixels;\r\n" "var fps = 0;\r\n" "var jobFlag = false;\r\n" "var canFlag = false;\r\n" "var wsFlag = false;\r\n" "window.onload = function(){\r\n" " wsUri = \"ws://\" + window.location.hostname + \":81/\";\r\n" " msg = document.getElementById('msg');\r\n" " var canv = document.getElementById('ccanvas');\r\n" " ctx = canv.getContext('2d');\r\n" " width = canv.width;\r\n" " height = canv.height;\r\n" " imageData = ctx.createImageData( width, 1 );\r\n" " pixels = imageData.data;\r\n" " var fullpath = location.href\r\n" " var path = fullpath.split('?');\r\n" " if ( path[1] && path[1] != \"\" ) {\r\n" " var eachData = path[1].split('&');\r\n" " for (var i = 0; i < eachData.length; i++) {\r\n" " var tmpData = eachData[i].split('=');\r\n" " if ( tmpData[0] == \"camera\" && tmpData[1] == \"on\" ) {\r\n" " canFlag = true;\r\n" " }\r\n" " }\r\n" " }\r\n" " if (canFlag) {\r\n" " document.getElementById(\"startbtn\").disabled = true;\r\n" " setTimeout('wsConnect()', 1000);\r\n" " }\r\n" "}\r\n" "function Msg(message){ msg.innerHTML = message;}\r\n" "function wsConnect(){\r\n" " wsFlag = true;\r\n" " document.getElementById(\"startbtn\").disabled = true;\r\n" " tms = new Date();\r\n" " if(socket == null){\r\n" " socket = new WebSocket(wsUri);\r\n" " socket.binaryType = 'arraybuffer';\r\n" " socket.onopen = function(evt){ onOpen(evt) };\r\n" " socket.onclose = function(evt){ onClose(evt) };\r\n" " socket.onmessage = function(evt){ onMessage(evt) };\r\n" " socket.onerror = function(evt){ onError(evt) };\r\n" " }\r\n" "}\r\n" "function wsDisconnect(){\r\n" " wsFlag = false;\r\n" " socket.close();\r\n" " socket = null;\r\n" " Msg('STOP');\r\n" " document.getElementById(\"startbtn\").disabled = false;\r\n" "}\r\n" "function onOpen(evt){ Msg('START');}\r\n" "function onClose(evt){\r\n" " Msg('DISCONNECT');\r\n" " wsClose();\r\n" " document.getElementById(\"startbtn\").disabled = false;\r\n" "}\r\n" "function onError(evt){\r\n" " Msg('Err:' + evt.data);\r\n" " document.getElementById(\"startbtn\").disabled = false;\r\n" "}\r\n" "function onMessage(evt){\r\n" " var data = evt.data;\r\n" " if( typeof data == 'string'){\r\n" " msg.innerHTML = data;\r\n" " }else if( data instanceof ArrayBuffer){\r\n" " drawLine(evt.data);\r\n" " }else if( data instanceof Blob){\r\n" " Msg('Blob data received');\r\n" " }\r\n" "}\r\n" "function wsClose(){\r\n" " socket.close();\r\n" " socket = null;\r\n" " if (wsFlag) {\r\n" " setTimeout('wsConnect()', 1);\r\n" " }\r\n" "}\r\n" "function drawLine(data){\r\n" " var buf = new Uint16Array(data);\r\n" " //console.log(\"drawLine: \" + \"%d\" + \" %d\" + \" %d\", buf[0], buf[100], buf[10000]);\r\n" " for(var y = 0; y < (buf.length)/width; y+=1){\r\n" " var base = 0;\r\n" " for(var x = 0; x < width; x += 1){\r\n" " var c = x + y * width;\r\n" " pixels[base+0] = (buf[c] & 0xf800) >> 8 | (buf[c] & 0xe000) >> 13;\r\n" " pixels[base+1] = (buf[c] & 0x07e0) >> 3 | (buf[c] & 0x0600) >> 9;\r\n" " pixels[base+2] = (buf[c] & 0x001f) << 3 | (buf[c] & 0x001c) >> 2;\r\n" " pixels[base+3] = 255;\r\n" " base += 4;\r\n" " }\r\n" " ctx.putImageData(imageData, 0, y);\r\n" " }\r\n" " if(y == height) fps+=1;\r\n" "}\r\n" "</script></body></html>\r\n" ; // ③返答HTML送信 request->send(200, "text/html", htmltop); } |
(3)WebSocket関連スケッチ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// ①WebSocketイベント処理 void onWebSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { // ②イベントタイプ判定 switch(type) { // ③WebSocket接続完了時 case WStype_CONNECTED: { clientNum = num; wsFlag = true; IPAddress ip = webSocket.remoteIP(num); Serial.printf("[%u] WebSocket Connection from ", num); Serial.println(ip.toString()); } break; // ④WebSocketテキストメッセージ受信時 case WStype_TEXT: Serial.printf("[%u] WebSocket Text: %s\n", num, payload); webSocket.sendTXT(num, payload); break; // ⑤WebSocket切断完了時 case WStype_DISCONNECTED: wsFlag = false; Serial.printf("[%u] WebSocket Disconnected!\n", num); break; // ⑥WebSocketエラー発生時 case WStype_ERROR: Serial.printf("[%u] WebSocket ERROR\n", num); wsFlag = false; break; // ⑦その他の処理は何もしない case WStype_BIN: case WStype_FRAGMENT_TEXT_START: case WStype_FRAGMENT_BIN_START: case WStype_FRAGMENT: case WStype_FRAGMENT_FIN: default: break; } } |
起動後の確認
スケッチを書込み後は自動で起動されます。起動時にLEDが点灯していることを確認し、設定したIPアドレスにアクセスしてください。
同じネットワーク内にあるパソコンなどのWebブラウザのアクセスURLに「http://192.168.x.x」(キットに設定したIPアドレス)と入力しアクセスして下さい。
Webブラウザで上記のように画面が確認されれば正常に動作しています。(カメラ映像を表示するには「開始」をクリックして下さい)
トラブルシュート
Webブラウザで画面が確認できない場合は起動時のシリアルモニター表示を確認しながらこちらを参照しトラブルシュートしてください。