ESP32でバーサライタ 高分解能化
前回、課題となったPOV(Persistent Of Vision=残像)表示装置バーサライタの高速化が実現できましたのでご報告いたします 😀 。
目次
前回の課題
割り込み制御できない
フォトリフレクタ QTR-1Aで回転を検出(1周の時間計測)しており、フォトリフレクタの出力を受けて割り込み制御したかったのですがESP32ではattachInterrupt()うまく機能しない。
例えば立下り検出モードにして入力をGNDに落とすと、一度だけ割り込み関数を呼んでほしいのだが何度も割り込み検出してしまう。
ハードウェアSPI
SPI制御LEDテープDotstarのライブラリではESP32のハードウェアSPIを使用することができなかった。
前回はソフトウェアSPIで制御した。
マルチタスク
前回はCORE0でフォトリフレクタ回転検出、CORE1(loop)でLED発光処理させてみたのですが、うまくLEDが光りませんでした。
ESP32でマルチタスクでCORE0で回転検出、CORE1でLED発光試してみたがデュアルコアでDatstar LEDテープうまく光らせれなかった。。。無念。ちなみに動画は1周16分割でRGBY表示。#実験 #ESP32 #Dotstar #岐路 pic.twitter.com/FdRXjoPUYC
— HomeMadeGarbage (@H0meMadeGarbage) 2019年2月6日
割り込み制御
あいにくオシロスコープを持ってないので予測ですがチャタリングが原因だと考えられます。
ということでシュミットトリガと抵抗器とコンデンサをフォトリフレクタの出力とESP32の割り込み検出ピン(D34)に挿入しました。
抵抗10kohmとコンデンサ0.1uFでチャタの急峻電圧変動をフィルタしてシュミットトリガのヒステリシス判定でチャタリングを完全除去して正常な割り込み動作を実現しました。
構成
部品
-
マイコン ESP32-DevKitC ESP-WROOM-32開発ボードV2
- フォトリフレクタ QTR-1A
-
シュミットトリガインバータ TC7W14FU
- MSOPパッケージ
- LEDテープ Dotstar
- ワイヤレスチャージモジュール
- マブチモーター RS-540SH
装置
前回同様、LEDは2列にして29セルの間に28セルが配置されるように設置しました。
また光を拡散させるために切ったクリアファイルでコートして さらに包帯などをとめるテープを貼ってみました。
手持ちです。
参考
ハードウェアSPI
Hackster.ioのSPRESENSEのコンテストにSPRESENSEを使用したバーサライタを投稿しました。
この際にDotsterライブラリを改造してハードウェアSPIを強制的につかうスキルが身についたので、今回ESP32でもハードウェアSPIを使うためにライブラリの改修を行いました。
ESP32は2つのSPI出力(VSPI, HSPI)があります。
Adafruit_DotStar.cpp と Adafruit_DotStar.h を改造して
Adafruit_DotStar_VSPI.h、Adafruit_DotStar_VSPI.cpp
Adafruit_DotStar_HSPI.h、Adafruit_DotStar_HSPI.cpp
をつくってハードウェアSPIであるVSPI, HSPIでLEDテープを制御しました。
以下のようにcppファイルを修正しました。
1 2 3 4 5 6 7 8 9 |
// SPI STUFF --------------------------------------------------------------- SPIClass * vspi = NULL; void Adafruit_DotStar_VSPI::hw_spi_init(void) { // Initialize hardware SPI vspi = new SPIClass(VSPI); vspi->begin(); vspi->beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0)); } |
SPI終了や送信クラス、ヘッダファイルもうまいこと修正します(ここでは割愛)。
マルチタスク
割り込み動作と2つのハードウェアSPI制御が実現できたことにより、CORE0とCORE1でそれぞれ1本づつLEDを制御して高速動作を実現できました。
1周 200分割の分解能での表示が可能となりました。
バーサライタ表示データ生成
折角容量の多いESP32なので動画を表示させてみたく、pythonでGIFファイルからフレームごとにバーサライタ表示用に画僧縮小と色抽出して配列生成するプログラムを作成しました。
抽出するフレーム数と1周の分割数(ここでは200)を指定してGIFファイルからバーサライタに表示するRGB配列データ(graphics.h)を生成します。
pythonコード
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 |
# -*- coding: utf-8 -*- import cv2 import os import math from PIL import Image #配列設定 Frame = 40 #フレーム数 NUMPIXELS = 57 #LEDの数 Div = 120 #1周の分割数 Bright = 30 #輝度 Led0Bright = 3 #中心LEDの輝度 [%] #ファイル作成 file = open('graphics.h', 'w') file.write('#define Frame ' + str(Frame) + '\n') file.write('#define NUMPIXELS ' + str(NUMPIXELS) + '\n') file.write('#define Div ' + str(Div) + '\n' + '\n') #file.write('#define Frame ' + str(Frame) + '\n' + '\n') file.write('const uint32_t pic [Frame][Div][NUMPIXELS] = {' + '\n') # Gifファイルを読み込む # 参考 https://www.tech-tech.xyz/gif-divide.html gif_file_name = "03-Baby-giphy-3.gif" gif = cv2.VideoCapture(gif_file_name) #画像変換関数 def polarConv(pic, i): imgOrgin = cv2.imread(pic) #画像データ読み込み h, w, _ = imgOrgin.shape #画像サイズ取得 #画像縮小 imgRedu = cv2.resize(imgOrgin,(math.floor((NUMPIXELS * 2 -1)/h *w), NUMPIXELS * 2 -1)) #cv2.imwrite(str(i) + '-resize.jpg',imgRedu) #縮小画像中心座標 h2, w2, _ = imgRedu.shape wC = math.floor(w2 / 2) hC = math.floor(h2 / 2) #極座標変換画像準備 imgPolar = Image.new('RGB', (NUMPIXELS, Div)) #極座標変換 file.write('\t{\n') for j in range(0, Div): file.write('\t\t{') 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) / NUMPIXELS * 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) / NUMPIXELS * 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) / NUMPIXELS * 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.write('\t},\n\n') # スクリーンキャプチャを保存するディレクトリを生成 dir_name = "screen_caps" if not os.path.exists(dir_name): os.mkdir(dir_name) for i in range(Frame): is_success, frame = gif.read() # 画像ファイルに書き出す img_name = str(i) + ".jpg" img_path = os.path.join(dir_name, img_name) cv2.imwrite(img_path, frame) #変換 polarConv(img_path, i) file.write('};' + '\n' + '\n') file.close() |
以下のGIFを7フレーム分抽出しました。
生成された graphics.h
1 2 3 4 5 6 7 |
#define Frame 7 #define NUMPIXELS 57 #define Div 200 const uint32_t pic [Frame][Div][NUMPIXELS] = { //データ省略 }; |
Arduinoコード
修正したDotsterライブラリと表示データgraphics.hをインクルードして、マルチタスク、割り込みによる回転検出を導入しています。
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 |
#include <SPI.h> #include "Adafruit_DotStar_VSPI.h" #include "Adafruit_DotStar_HSPI.h" #include "graphics.h" #define NUMPIXELS2 29 // Number of LEDs in strip #define itrPin 34 int num = 0; int num2 = 0; int numRot = 0; int numDiv = 0, numDiv2 = 0; int stateRot = 0; int stateDiv = 0; int stateDiv2 = 0; unsigned long rotTime, timeOld, timeNow; Adafruit_DotStar_VSPI stripV = Adafruit_DotStar_VSPI(NUMPIXELS2, DOTSTAR_BGR); Adafruit_DotStar_HSPI stripH = Adafruit_DotStar_HSPI(NUMPIXELS2, DOTSTAR_BGR); //LED2点灯 void LED2(void *pvParameters) { for (;;){ disableCore0WDT(); if(stateDiv2 == 1){ Serial.println("LED2"); stripH.clear(); for(int i=0;i<NUMPIXELS2; i++){ stripH.setPixelColor(i, pic[numRot][numDiv][i*2+1]); } stripH.show(); stateDiv2 = 0; } } } void setup() { Serial.begin(115200); stripV.begin(); stripV.clear(); stripV.show(); stripH.begin(); stripH.clear(); stripH.show(); pinMode(itrPin, INPUT); delay(500); attachInterrupt(digitalPinToInterrupt(itrPin), RotCount, RISING ); //LED2点灯 タスク xTaskCreatePinnedToCore( LED2 , "LED2" // A name just for humans , 4096 // This stack size can be checked & adjusted by reading the Stack Highwater , NULL , 1 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest. , NULL , 0); } void loop() { //disableLoopWDT(); if(stateDiv == 1 &µs() - timeOld > rotTime / Div * (numDiv)){ stateDiv = 0; } if(stateDiv == 0 && micros() - timeOld < rotTime / Div * (numDiv + 1)){ stateDiv = 1; stateDiv2 = 1; stripV.clear(); for(int i=0;i<NUMPIXELS2; i++){ numDiv2 = numDiv+3; if(numDiv2 >= Div) numDiv2 -= Div; stripV.setPixelColor(i, pic[numRot][numDiv2][i*2]); } stripV.show(); numDiv++; if(numDiv >= Div ) numDiv = 0; num++; if(num > 3 ) num = 0; } } void RotCount() { timeNow = micros(); rotTime = timeNow - timeOld; timeOld = timeNow; numRot++; if(numRot >= Frame ) numRot = 0; Serial.println("int"); } |
動作
LEDテープ2列(計57セル)で1周200分割での表示が可能となりました。
先ほどのはソフトウェアSPIでハードウェアSPIでも高速化が確認できました。1周200分割。色のムラがなくなりました。わーい#ESP32 #ザコシGIF #バーサライタ pic.twitter.com/iMzgnFREdu
— HomeMadeGarbage (@H0meMadeGarbage) 2019年2月27日
細かいところも割とうまいこと表示できててうれしいです。折角のESP32なので無線で映像制御とか今後試す予定です 😆 。
付録
高分機能化に向けて色々条件変えて試したので本装置での1周の分解能を表にまとめます。
- 割り込みなし(回転検出をloop内で実行)の時の分解能
ソフトウェアSPI ハードウェアSPI シングルコア 100 100 デュアルコア LED正常に点灯しない 160 - 割り込みで回転検出実施時の分解能(正規化)
ソフトウェアSPI ハードウェアSPI シングルコア 120 120 デュアルコア 200 200
ESP32ではソフトウェアSPIとハードウェアSPIで分解能に大きな差はありませんでしたがソフトウェアSPIはLED表示の際に輝度にムラがありました。
以下は割り込みあり、デュアルコア、ソフトウェアSPIでの動作です。
ESP32でバーサライタ。シュミットトリガでチャタリング対策施し割り込みで回転速度検出。かつデュアルコアでLED2本を制御で1周200分割達成。若干色がちらつくが。。#ESP32 #ザコシGIF #バーサライタ pic.twitter.com/96jDNTC1NH
— HomeMadeGarbage (@H0meMadeGarbage) 2019年2月27日
はじめまして!
私も球体POVを作成しています.今年のMakerFaireTokyo2019にも出展しました.
https://www.youtube.com/watch?v=oyAgYzRlOu4
マイコンはESP32(m5stack),LEDはAPA102-2020を使用しています.
https://twitter.com/Yakatano/status/1158398492449992705?s=20
コレを見ていただくとわかるんですが,LEDのPWM点滅がそのまま映像として出てしまっています...例えば赤が255だとキレイに横線がはいるのですが,赤が減ると点線になってしまいます(これはこれで味があって良いのですが)
今は位相同期していないなど,まだ足りない部分があるかと思いますが,homemadegabageさんのところではキレイに表示されていますよね.
現在,FastLEDというライブラリを使用しており,
https://github.com/FastLED/FastLED
これが原因の一つかと類推しています.これはおそらくハードウェアSPIを使っていないので,homemadegabageさんのAdafruit_DotStar_VSPI.h を試してみようかとも思っています.
もしお気づきの点などありましたら,ご指摘いただけると助かります.
POVを作る者としてhomemadegabageさんのページはよく参考にさせていただいています.
今後ともがんばってください!
この制作記も近いうちにアップしますー
コメントありがとうございます!
すごいですね!私は以前、球体POVは作りこみが難しくて断念いたしました。。
私の認識ですとAPA102はリフレッシュレートが19.2 kHzと非常に高速のため, 色データ0-255で残像に影響は出ないという認識でした。
全体の輝度を決定するグローバル輝度は低周波数のPWMパターン( ~440 Hz)を重畳
しているようなので残像に関係しそうですが、
私がAPA102でバーサライタでグローバル輝度下げても極端に色飛びすることはなかった記憶です。
私はAdafruit_DotStarライブラリを使用しておりますので、FastLEDライブラリの影響でしょうか?
FastLEDライブラリの輝度コントロール方法など確認必要でしょうか。
Adafruit_DotStarで一度比較してみてはいかがでしょうか?
https://github.com/adafruit/Adafruit_DotStar
製作ブログ楽しみしております!