ESP32 でバーサライタ作製
以前つくったPOV(Persistent Of Vision=残像)表示装置バーサライタのマイコンをESP32にしてみました。高速で優秀なESP32で更に無線も使えるので、表示画像をBluetoothで切り替えなども試してみました。
目次
動作
スマホで画像を切り替えるとPOVの表示が切り替わります。無線搭載のESP32ならではです。
ESP32だとこれができるのよ#実験 #POV #ESP32 #Dotstar #Bluetooth #Blynk #ジャージ pic.twitter.com/ZWtBNJqYgw
— HomeMadeGarbage (@H0meMadeGarbage) 2019年2月7日
構成
基本構成は前回とほぼ同じです。回転部にLEDテープDotstarとマイコン、フォトリフレクタを搭載し、ワイヤレスチャージモジュールを使って回転部への給電します。
今回はLEDテープを2列にして表示の高分解能化をはかりました。
またスマホアプリBlynkでBluetooth介して信号を送って表示する画像を切り替えます。
部品
-
マイコン ESP32-DevKitC ESP-WROOM-32開発ボードV2
- フォトリフレクタ QTR-1A
- LEDテープ Dotstar
- ワイヤレスチャージモジュール
- マブチモーター RS-540SH
POV装置
ESP32の高速動作(CPUクロック周波数 240MHz )をいいことにSPIを2個出力してLEDテープを2列にしました。29セルの間に28セルが配置されるように設置しました。
光を拡散させるために切ったクリアファイルでコートして さらに包帯などをとめるテープを貼ってみました。
Blynk設定
スマホとESP32をスマホアプリのBlynkを用いてBluetooth通信させてPOV表示させる画像を切り替えます。Blynkアプリのバージョンは2.27.1。
新規プロジェクトを作成します。HARDWRE MODELはESP32 Dev Boardを選択。CONNECTION TYPEはBluetoothを選択。AUTH TOKENはArduinoコード生成時に使用します(アカウントに登録したメアドに送信されます)。
Bluetoothウィジェット、Vertical Sliderウィジェット、Image Galleryウィジェットを配置します。
Vertical SliderウィジェットでヴァーチャルピンV0に最小値 1、最大値 2を設定できるようにします。
Image Galleryウィジェットで入力をヴァーチャルピンV0として、2個の画像のあるURLを指定します。V0 = 1でロックマン、V0 = 2でレナが選択されます。
Arduino IDEコード
ArduinoでESP32のコードを作成しました。環境設定は以下の通りです。ライブラリのバージョンは1.0.1。
https://github.com/espressif/arduino-esp32/blob/master/docs/arduino-ide/boards_manager.md
以下のDotstar用ライブラリを使用しています。
https://github.com/adafruit/Adafruit_DotStar
フォトリフレクタ QTR-1A用のライブラリは以下を使用しました。
https://github.com/pololu/qtr-sensors-arduino
更にBlynk用ライブラリも使用します。バージョンは0.6.0。
https://github.com/blynkkk/blynk-library
[スケッチの例] -> [Blynk] -> [Boards_Bluetooth]-> [ESP32_BT]を参考にコード生成しました。
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 |
#define BLYNK_PRINT Serial #define BLYNK_USE_DIRECT_CONNECT #include <BlynkSimpleEsp32_BT.h> #include <Adafruit_DotStar.h> #include <QTRSensors.h> #include <SPI.h> #include "graphics.h" #define NUMPIXELS 29 // Number of LEDs in strip int numRot = 0; int numDiv = 0, numDiv2 = 0; int stateRot = 0; int stateDiv = 0; unsigned long rotTime, timeOld, timeNow; #define HDATAPIN 13 #define HCLOCKPIN 14 Adafruit_DotStar stripH = Adafruit_DotStar(NUMPIXELS, HDATAPIN, HCLOCKPIN, DOTSTAR_BGR); #define VDATAPIN 23 #define VCLOCKPIN 18 Adafruit_DotStar stripV = Adafruit_DotStar(NUMPIXELS, VDATAPIN, VCLOCKPIN, DOTSTAR_BGR); #define NUM_SENSORS 1 // number of sensors used #define NUM_SAMPLES_PER_SENSOR 6 // average analog samples per sensor reading #define EMITTER_PIN QTR_NO_EMITTER_PIN #define th_SENSORS 1000 QTRSensorsAnalog qtra((unsigned char[]) {34}, NUM_SENSORS, NUM_SAMPLES_PER_SENSOR, EMITTER_PIN); unsigned int sensorValues[NUM_SENSORS]; // You should get Auth Token in the Blynk App. // Go to the Project Settings (nut icon). char auth[] = "YourAuthToken"; int pic = 0; BLYNK_WRITE(V0) { pic = param[0].asInt(); Serial.println(pic); } void setup() { Serial.begin(115200); stripH.begin(); // Initialize pins for output stripV.begin(); stripH.clear(); stripV.clear(); stripH.show(); stripV.show(); // Turn all LEDs off ASAP delay(500); Serial.println("Waiting for connections..."); Blynk.setDeviceName("Blynk"); Blynk.begin(auth); } void loop() { Blynk.run(); qtra.read(sensorValues); /* Serial.print(sensorValues[0]); Serial.print("\t"); Serial.print(numRot); Serial.print("\t"); Serial.println(rotTime); */ if(stateRot == 0 && sensorValues[0] < th_SENSORS){ timeNow = micros(); rotTime = timeNow - timeOld; timeOld = timeNow; stateRot = 1; numRot++; } if(stateRot == 1 && sensorValues[0] > th_SENSORS){ stateRot = 0; } if(stateDiv == 1 && micros() - timeOld > rotTime / Div * (numDiv)){ stateDiv = 0; } if(stateDiv == 0 && micros() - timeOld < rotTime / Div * (numDiv + 1)){ stateDiv = 1; stripH.clear(); stripV.clear(); for(int i=0;i<NUMPIXELS; i++){ numDiv2 = numDiv+1; if(numDiv2 >= Div) numDiv2 -= Div; if(pic == 1){ stripH.setPixelColor(i, rock[numDiv2][i*2]); stripV.setPixelColor(i, rock[numDiv][i*2+1]); }else{ stripH.setPixelColor(i, rena[numDiv2][i*2]); stripV.setPixelColor(i, rena[numDiv][i*2+1]); } } stripH.show(); stripV.show(); numDiv++; if(numDiv >= Div ) numDiv = 0; } } |
LEDテープ Dotstarを2列使用しているのでSPIを2個出力しています。
stripH:DATA-GPIO13 、CLK-GPIO14
stripV:DATA-GPIO23 、CLK-GPIO18
フォトリフレクタ QTR-1Aで1周する時間を測定して表示する発光パターンを切り替えています。1周を100分割して画像を表示します。
ヴァーチャルピンV0の値を受信して表示する画像の配列を切り替えます。配列はgraphics.hファイルに記載しています。
57(LED数) × 100(1周の分割数)の配列を2画像分(rock[], rena [])記載しています。
画像配列の生成方法は次の節で説明します。
1 2 3 4 5 6 7 8 9 10 11 |
#define NUMPIXELS2 57 #define Div 100 const uint32_t rock [Div][NUMPIXELS2] = { //データ省略 }; const uint32_t rena [Div][NUMPIXELS2] = { //データ省略 }; |
表示グラフィックデータ作成手法
Pythonで表示データ生成プログラムをこしらえました。画像処理ライブラリのopenCVとPillowを利用しています。
表示したい画像を取り込んで縮小して極座標変換してLEDの数 (29+28 = 57) ×1周の分割数 (100)の色コードの配列を生成します。確認用に変換後の画像も出力します。
全体の輝度 (Bright)をパーセンテージで指定できるようにしました。
中心LEDの輝度を端のLEDに対するパーセンテージ (Led0Bright )で指定できるようにして、端から中心に徐々にLEDの輝度を下げれるようにしました。
これによって中心が異常に明るくなることを避けれます。
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 |
import cv2 import math from PIL import Image NUMPIXELS2 = 57 #LEDの数 Div = 100 #1周の分割数 Bright = 30 #輝度 Led0Bright = 10 #中心LEDの輝度 [%] #画像データ読み込み imgOrgin = cv2.imread('test.jpg') #画像サイズ取得 #参考:https://note.nkmk.me/python-opencv-pillow-image-size/ h, w, _ = imgOrgin.shape print('widthOrgin: ', w) print('heightOrgin:', h) #画像縮小 #参考:https://www.tech-tech.xyz/opecv_resize.html imgRedu = cv2.resize(imgOrgin,(math.floor((NUMPIXELS2 * 2 -1)/h *w), NUMPIXELS2 * 2 -1)) cv2.imwrite('img-resize.jpg',imgRedu) #縮小画像中心座標 h2, w2, _ = imgRedu.shape print('widthRedu: ', w2) print('heightRedu:', h2) wC = math.floor(w2 / 2) hC = math.floor(h2 / 2) print('widthCenter: ', wC) print('heightCenter:', hC) #極座標変換画像準備 imgPolar = Image.new('RGB', (NUMPIXELS2, Div)) #極座標変換 file = open('PolarConv.txt', 'w') for j in range(0, Div): file.write('{') for i in range(0, hC+1): #座標色取得 #参考:http://peaceandhilightandpython.hatenablog.com/entry/2016/01/03/151320 rP = int(imgRedu[hC + math.ceil(i * math.cos(2*math.pi/Div*j)), wC - math.ceil(i * math.sin(2*math.pi/Div*j)), 2] * ((100 - Led0Bright) / NUMPIXELS2 * i + Led0Bright) / 100 * Bright /100) gP = int(imgRedu[hC + math.ceil(i * math.cos(2*math.pi/Div*j)), wC - math.ceil(i * math.sin(2*math.pi/Div*j)), 1] * ((100 - Led0Bright) / NUMPIXELS2 * i + Led0Bright) / 100 * Bright /100) bP = int(imgRedu[hC + math.ceil(i * math.cos(2*math.pi/Div*j)), wC - math.ceil(i * math.sin(2*math.pi/Div*j)), 0] * ((100 - Led0Bright) / NUMPIXELS2 * i + Led0Bright) / 100 * Bright /100) file.write('0x%02X%02X%02X' % (rP,gP,bP)) if i == hC: file.write('},\n') else: file.write(', ') imgPolar.putpixel((i,j), (rP, gP, bP)) file.close() #変換画像保存 #参考:https://qiita.com/uosansatox/items/4fa34e1d8d95d8783536 imgPolar.save('PolarConv.jpg') |
元画像
変換画像
全体輝度 [%] Bright = 30
中心LEDの輝度 [%] Led0Bright = 10
サイズ 57×100ピクセル
生成された57×100の色データをgraphics.hにコピペします。
Blynk Bluetooth接続
BlynkアプリでBluetoothウィジェットをクリックします。
“Connect Bluetooth device”をクリックするとBluetoothデバイスの検索が始まり”Blynk”というデバイスが表示されますのでクリックして接続します。
接続ができましたらアプリを実行して、スライダでPOV表示画像を切り替えが可能となります。
製作の流れ
POV装置にESP32を適応するに際して、いろいろと紆余曲折ございましたので記載します。
割り込み
フォトリフレクタ QTR-1Aの出力で割り込みして回転速度検出したかったのですが、
1 |
attachInterrupt(digitalPinToInterrupt(itrPin), RotCount, FALLING ); |
がESP32ではうまく動きませんでした。
FALLINGモードなので入力ピンitrPinがLOWになると1度だけ割り込まれてRotCount()関数が呼び出されるはずなのですが、何度も割り込んでくるのです。。。
ちょっと原因不明。チャタリングとかではないはず。
https://www.switchdoc.com/2018/04/esp32-tutorial-debouncing-a-button-press-using-interrupts/
まぁ割り込みなしでもLEDテープ1列(29セル)で250分割余裕だったので。。今後の課題とさせていただきます。
POVディスプレイ ESP32で実験。割り込みなしで割と余裕で1周250分割達成。さすが。
割り込みがうまく動かないようなのであきらめて、VSPI, HSPIの2個出しやデュアルコアによる高分解能化の可否引き続き調べていきます。 #実験 #ESP32 #Dotstar #人生 pic.twitter.com/VADxx7Kv9X— HomeMadeGarbage (@H0meMadeGarbage) 2019年2月5日
ハードウェアSPI
今回のコードではSPIはソフトウェアSPIとして使用しています。
ESP32は複数のハードウェアSPIを搭載しておりArduinoでも2個(HSPI, VSPI)使用できます。ソフトウェアSPIよりハードウェアSPIのほうが一般的に高速です。
しかし、Dotstarライブラリで複数個のハードウェアSPIを使用できるようにするのはライブラリ改造が必要そうなので断念しました。。
マルチタスク
ESP32はArduino環境でもデュアルコアを使用することができます。以下の記事が非常に参考になります。
Arduino – ESP32 のマルチタスク ( Dual Core ) を試す
私もデュアルコアに挑戦して CORE0でフォトリフレクタ回転検出、CORE1でLED発光処理させてみたのですが、うまくLEDが光りませんでした。
ESP32でマルチタスクでCORE0で回転検出、CORE1でLED発光試してみたがデュアルコアでDatstar LEDテープうまく光らせれなかった。。。無念。ちなみに動画は1周16分割でRGBY表示。#実験 #ESP32 #Dotstar #岐路 pic.twitter.com/FdRXjoPUYC
— HomeMadeGarbage (@H0meMadeGarbage) 2019年2月6日
これも根が深そうなので、今後の課題とさせてください。。。
2枚羽化
当初、1枚羽から2枚羽にして高分解能化を目指しておりました。
回転の際にそれぞれのLEDは少しずれて配置しているので、隙間なく絵が出るはずです。
さすがに1周140分割が限界。でも割り込み処理未使用、シングルスレッド、ソフトウェアSPI使用だから伸びしろ満載。#実験 #ESP32 #Dotstar #伸びしろですねぇ pic.twitter.com/1i4V4Jhp5O
— HomeMadeGarbage (@H0meMadeGarbage) 2019年2月6日
SPI2個出力にしたためか250分割→140分割くらいが限界でした。
細かくはなったけどもっと目の覚めるような変化がほしい。 pic.twitter.com/NjX9U4JLLY
— HomeMadeGarbage (@H0meMadeGarbage) 2019年2月6日
1枚羽より密集した絵が表示できましたが、半周差があるためかどうしても隙間感が出ていたので結局1枚羽で2列に修正しました。そしてマージンとって1周100分割としました。
追記
もろもろ解決
本ブログでの課題は以下で解決いたしました。是非ご覧ください。
「ESP32 でバーサライタ作製」への1件のフィードバック