SPRESENSE で バーサライタ
SonyのマイコンSPRESENSEでもバーサライタを作っておりました。
https://homemadegarbage.com/en/spresense-pov01
SPRESENSEはGPSやハイレゾオーディオコアを有する高性能マイコンで、なんとARMコアが6個も搭載してあるのです。しかし現状Arduino IDE環境ではマルチコア設計はできません。
SDカードからデータ参照は取得に時間がかかるため断念。マルチスレッドや良いストリーミング手法学習必要。DotstarライブラリいじってHW SPI動かすスキル身についたので、一旦ESP32に戻る。その間にSPRESENSEのArduino環境でマルチコア対応されたら幸運。#SPRESENSE #ザコシGIF #タイガーステップ pic.twitter.com/BOVxvMKGjU
— HomeMadeGarbage (@H0meMadeGarbage) 2019年2月17日
しばらくSPRESENSEでの製作は手を止めていたのですが、ある日耳寄りな情報が舞い込んできました。
Spresense、コアの数だけArduino IDEを使って開発するという力業環境が間もなくリリースされるらしい。ちなみに、各コアで確保してあるメモリ領域なら並列アクセスできるらしい(口頭で聞いただけなので誤解の可能性はアリ)。これは面白い。ちなみに2枚目は謎のコスプレ氏との記念撮影。 #MFKyoto2019 pic.twitter.com/pnCm65q7dO
— Yoshiki NAGATANI (@nagataniyoshiki) 2019年5月5日
次のバージョンアップでマルチコア設計がArduino IDEで可能となるらしいとのこと。これは実に楽しみ。6コアなので色々夢広がりそう(まぁ がゆえの難しさあったり、比例的向上が容易ではないとは思うが)。
そこで、ここでは一旦現状のSPRESENSEバーサライタの仕様と性能をまとめておいて、来たるXデーに備えたいと思います。
目次
構成
SPRESENSEのハードウェアSPIを2つ使用してそれぞれLEDテープを制御します。回転はフォトリフレクタQTR-1Aで検出します。回転部への給電はワイヤレスチャージモジュールで無線で実施しています。
部品
- マイコン SPRESENSE
- SPRESENSE用ミニ拡張ボード KASPI001
- フォトリフレクタ QTR-1A
- LEDテープ Dotstar
- ワイヤレスチャージモジュール
- マブチモーター RS-540SH
- レベルシフタ TXB0104
装置
木の取っ手にモータを固定して手持ちで動作できるようにしました。
回転部は以下のように独立しております。
LEDテープを2列にし、29セルの間に28セルが配置されるように設置しました。29セルのLEDテープはSPI4(メインボード)、28セルのLEDテープはSPI5(拡張ボード)で制御しています。
LED回転部への給電にはワイヤレスチャージモジュールを使用しました。
SPRESENSEのロジック電圧は1.8V系ですのでLEDとの間にレベルシフタTXB0104 を挿入して3.3Vに変換してます。
SPRESENSE
SPRESENSEとはSonyの高機能マイコンで Arduino IDEでもプログラミング可能です。
Arduino環境構築方法
以下の通りです。
ここではArduino board packageバージョン1.2.1を使用しました。
ピン配置
SPRESENSEはコネクタ介して拡張ボードを接続できます。SPI出力を2つ使用したいので拡張ボードも使用します。
メインボード
拡張ボード
ここではサイズの小さいサードパーティの拡張ボードを使用しました。
ハードウェアSPI
SPRESENSEは2つのSPI出力があります。拡張ボードがSPI、メインボードがSPI5です。SPIで制御できるLEDテープ DotStar を2つのSPI出力で制御します。
Adafruitが提供するライブラリのハードウェアSPI使用では拡張ボードのSPIしか使用できないためAdafruit_DotStar.cpp と Adafruit_DotStar.h をメインボード用に改造して
Adafruit_DotStar_SPI5.h、Adafruit_DotStar_SPI5.cpp
をつくってメインボードと拡張ボードそれぞれででLEDテープを制御できるようにしました。
以下のようにcppファイルを修正しました。
1 2 3 4 5 6 7 |
void Adafruit_DotStar_SPI5::hw_spi_init(void) { // Initialize hardware SPI SPI5.begin(); } void Adafruit_DotStar_SPI5::hw_spi_end(void) { // Stop hardware SPI SPI5.end(); } |
送信クラス、ヘッダファイルもうまいこと修正します(ここでは割愛)。
参考
バーサライタ評価
いろいろな構成やマイコンでバーサライタを作ると、どの組み合わせが最適であるかしっかり評価したくなってきます。
1周の間にLEDを切り替える分解能や回転スピードをしっかり把握して性能評価するべく回転計も自作しました。
オリジナル性能指数
今後は以下を性能指数としてバーサライタを評価し製作したいと思います。
オリジナル性能指数 [hPOV(ヘクトポブ)] = LED数 × 回転数 × 一周の分解能 ÷ 100
フォトリフレクタでマーカを検出して割り込みで1周の時間を測定します。1周時間を分割してRGBYを交互に表示して分解能を評価しました。
SPI単体での性能
まずはSPI単体でLEDテープ1本を制御して性能を観ました。
結果はSPI(4)、SPI5ともに
29個 × 720rpm × 240 ÷ 100 = 50112 hPOV
でした。
バーサライタ評価の方法
LED数×回転数× 一周の分解能 ÷ 100
をオリジナル性能指数hPOV(ヘクトポブ)として今後評価します。SPRESENSE SPI4で評価
29個 × 720rpm × 240 ÷ 100 =
50112hPOVちなみにSPIクロックは38kHz pic.twitter.com/rMFCKdYwqG
— HomeMadeGarbage (@H0meMadeGarbage) 2019年5月19日
公式文書では”通信速度はメインボードが最大で13Mbps、拡張ボードが最大で48.75Mbpsとなります。”とありましたが差はないように見えました。
そもそもレベルシフタがないメインボードの方が遅いのはなぜだろう?まぁ動いてるので良しとします。
SPI2本での性能
LED2本同時表示して評価しました。SPI(4), SPI5同時使用で、それぞれLED数は28、29個ですがコード上は2つとも29点灯で処理しています。
58個 × 730rpm × 120 ÷ 100 = 50808 hPOV でした。
SPI4, SPI5同時使用。それぞれLED数は28、29だけどコード上は2つとも29点灯で処理。
58 × 730 × 120 ÷ 100 =
50808 hPOVマルチコアでの性能向上できるのかが楽しみです。 pic.twitter.com/NJP1pUmpb3
— HomeMadeGarbage (@H0meMadeGarbage) 2019年5月19日
Arduinoコード
SPI出力2個でLED表示してます。フォトリフレクタで割り込んで1周の時間を測って120分割で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 69 70 71 72 73 74 |
#include <SPI.h> #include "Adafruit_DotStar_SPI5.h" #include <Adafruit_DotStar.h> #include "graphics.h" #define NUMPIXELS2 29 // Number of LEDs in strip #define Fclk 38000000 #define itrPin 19 int numRot = 0; int numDiv = 0, numDiv2 = 0; int stateDiv = 0; unsigned long rotTime, timeOld, timeNow; Adafruit_DotStar_SPI5 strip5 = Adafruit_DotStar_SPI5(NUMPIXELS2, DOTSTAR_BGR); Adafruit_DotStar strip4 = Adafruit_DotStar(NUMPIXELS2, DOTSTAR_BGR); void setup() { //Serial.begin(115200); strip5.begin(); SPI5.beginTransaction(SPISettings(Fclk, MSBFIRST, SPI_MODE0)); strip4.begin(); SPI.beginTransaction(SPISettings(Fclk, MSBFIRST, SPI_MODE0)); strip5.clear(); strip4.clear(); strip5.show(); strip4.show(); attachInterrupt(digitalPinToInterrupt(itrPin), RotCount, FALLING ); } void loop() { if(stateDiv == 1 && micros() - timeOld > rotTime / Div * (numDiv)){ stateDiv = 0; } if(stateDiv == 0 && micros() - timeOld < rotTime / Div * (numDiv + 1)){ stateDiv = 1; strip5.clear(); strip4.clear(); for(int i=0;i<NUMPIXELS2; i++){ numDiv2 = numDiv+1; if(numDiv2 >= Div) numDiv2 -= Div; strip5.setPixelColor(i, pic[numRot][numDiv2][i*2]); strip4.setPixelColor(i, pic[numRot][numDiv][i*2+1]); } strip5.show(); strip4.show(); numDiv++; if(numDiv >= Div ) numDiv = 0; } } void RotCount() { timeNow = micros(); rotTime = timeNow - timeOld; timeOld = timeNow; numRot++; if(numRot >= Frame) numRot = 0; //Serial.println("int"); } |
LED表示画像データ graphics.h は画像やGIFからpythonで生成しています。画像の色データを極座標変換してLED用に配列にしています。また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 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 = 14 #抽出フレーム数指定 NUMPIXELS = 57 #LEDの数 29セル+28セル Div = 120 #1周の分割数 Bright = 40 #輝度 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 = "画像データ" 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画像から表示データを生成して表示しています。
#SPRESENSE で #バーサライタ#ザコシGIF pic.twitter.com/PKjXOtA8Im
— HomeMadeGarbage (@H0meMadeGarbage) 2019年5月19日
おわりに
今後登場するであろうマルチコア設計が可能となるバージョンアップのXデーに備えて現状の仕様と性能をまとめました。
マルチコアによってどのくらい性能が上がり表示が鮮明になるのかとても楽しみです♪