ついにSPRESENSE にArduino IDEでのマルチコアプログラミング環境が誕生しました。
前回SPRESENSEを使用して製作したバーサライタ(POVディスプレイ)にマルチコアを導入してどれほどの高速動作が達成されるのか観てみました!
スマート靴占い 『 IしたoTんきになぁ〜れ 』
続きを見る
マルチコアプログラミング
SPRESENSEは6つのCPUを搭載しております。Arduino IDE環境でのプログラミング詳細は以下の公式のドキュメントの記載の通りです。
MPライブラリというマルチコア用のライブラリが今回追加となりました。Mainコアと5つのSubコアの制御やコア間の通信を司ります。
コアごとにArduinoプログラムを用意して、コアを指定してコンパイルというなかなかの力技です(;^ω^)
構成の概要
3つのCPUを使用してバーサライタしてみました。
Mainコア:フォトリフレクタで回転を検出して2つのSubコアに発光タイミングを送信
Sub1, 2コア:Mainコアからのタイミングを受けてSPIテープLED Dotstar を発光
Arduino IDEコード
Mainコア
フォトリフレクタでバーサライタ装置のマーカを検出して割り込みで回転時間を測定してMP.Send関数で表示すべき画像のアドレスを各Subコアに渡しています。
コア間でメモリが共有されているので表示データのアドレスだけを渡しています。
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
#include <MP.h>
#include <SPI.h>
#include "graphics.h"
#define itrPin 19
int sub1 = 1 ;
int sub2 = 2 ;
int8_t id = 100 ; /* user-defined msgid */
int numRot = 0 ;
int numDiv = 0 , numDiv2 = 0 ;
int stateDiv = 0 ;
unsigned long rotTime , timeOld , timeNow ;
void setup ( ) {
//Serial.begin(115200);
pinMode ( 16 , OUTPUT ) ;
digitalWrite ( 16 , LOW ) ;
pinMode ( 23 , OUTPUT ) ;
digitalWrite ( 23 , LOW ) ;
pinMode ( 11 , OUTPUT ) ;
digitalWrite ( 11 , LOW ) ;
pinMode ( 13 , OUTPUT ) ;
digitalWrite ( 13 , LOW ) ;
delay ( 1000 ) ;
MP . begin ( sub1 ) ;
MP . begin ( sub2 ) ;
delay ( 1000 ) ;
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;
numDiv2 = numDiv +6 ;
if ( numDiv2 > = Div ) numDiv2 -= Div ;
MP . Send ( id , &pic[numRot][numDiv2][0], sub1);
MP . Send ( id , &pic[numRot][numDiv][1], sub2);
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位置によって輝度を線型的に調整してバーサライタ表示時に明るさが均等になるようにしています。Divで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
# -*- coding: utf-8 -*-
import cv2
import os
import math
from PIL import Image
#配列設定
Frame = 5 #抽出フレーム数指定
NUMPIXELS = 57 #LEDの数 29セル+28セル
Div = 400 #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 ( 'uint32_t pic [Frame][Div][NUMPIXELS] = {' + '\n' )
# Gifファイルを読み込む
# 参考 https://www.tech-tech.xyz/gif-divide.html
gif_file_name = "file.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 ( )
Sub1コア
Mainコアからの画像アドレスをMP.Recv関数で受けてLED発光させます。MP.RecvTimeout関数でデータ受信の待ちモードを指定しており、ここではMP_RECV_BLOCKINGでデータを受信するまで永久に受信待ちに入るモードにしています。
Adafruitが提供するライブラリのハードウェアSPI使用では拡張ボードのSPI4しか使用できないためAdafruit_DotStar.cpp と Adafruit_DotStar.h をメインボード用に改造して
Adafruit_DotStar_SPI5.h、Adafruit_DotStar_SPI5.cpp
をつくってSPI5で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
#include <MP.h>
#include <SPI.h>
#include "Adafruit_DotStar_SPI5.h"
#define NUMPIXELS1 29 // Number of LEDs in strip
#define Fclk 38000000
Adafruit_DotStar_SPI5 strip5 = Adafruit_DotStar_SPI5 ( NUMPIXELS1 , DOTSTAR_BGR ) ;
void setup ( ) {
Serial . begin ( 115200 ) ;
MP . begin ( ) ;
strip5 . begin ( ) ;
SPI5 . beginTransaction ( SPISettings ( Fclk , MSBFIRST , SPI_MODE0 ) ) ;
strip5 . clear ( ) ;
strip5 . show ( ) ;
/* Polling */
MP . RecvTimeout ( MP_RECV_BLOCKING ) ;
}
void loop ( ) {
int8_t id ;
uint32_t rsvdata ;
MP . Recv ( &id, &rsvdata);
strip5 . clear ( ) ;
for ( int i =0 ; i < NUMPIXELS1 ; i ++) {
strip5 . setPixelColor ( i , pgm_read_dword ( rsvdata + sizeof ( uint32_t ) * 2 *i ) ) ;
}
strip5 . show ( ) ;
}
Sub2コア
Mainコアからの画像アドレスをMP.Recv関数で受けてSPI4で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
#include <MP.h>
#include <SPI.h>
#include <Adafruit_DotStar.h>
#define NUMPIXELS2 28 // Number of LEDs in strip
#define Fclk 38000000
Adafruit_DotStar strip4 = Adafruit_DotStar ( NUMPIXELS2 , DOTSTAR_BGR ) ;
void setup ( ) {
Serial . begin ( 115200 ) ;
MP . begin ( ) ;
strip4 . begin ( ) ;
SPI . beginTransaction ( SPISettings ( Fclk , MSBFIRST , SPI_MODE0 ) ) ;
strip4 . clear ( ) ;
strip4 . show ( ) ;
/* Polling */
MP . RecvTimeout ( MP_RECV_BLOCKING ) ;
}
void loop ( ) {
int8_t id ;
uint32_t rsvdata ;
MP . Recv ( &id, &rsvdata);
strip4 . clear ( ) ;
for ( int i =0 ; i < NUMPIXELS2 ; i ++) {
strip4 . setPixelColor ( i , pgm_read_dword ( rsvdata + sizeof ( uint32_t ) * 2 *i ) ) ;
}
strip4 . show ( ) ;
}
動作
1コアから3コアにすることで1周での画像分解能が120から400にまで向上しました!わーい!!
性能指数:LED 57個 × 回転速度 730rpm × 400分解能 ÷ 100 = 166440 hPOV
ちなみに性能指数とはバーサライタの性能を示す独自の指標です。
性能指数 [hPOV(ヘクトポブ)] = LED数 × 回転数 × 一周の分解能 ÷ 100
次は今回のアップデートでフラッシュメモリへの読み書きのライブラリも追加されたので長尺の動画表示にトライしたいです。
マルチコアへの道
シングルコア
性能指数:LED 58個 × 回転速度 730rpm × 120分解能 ÷ 100 = 50808 hPOV
デュアルコア
性能指数:LED 58個 × 回転速度 730rpm × 220分解能 ÷ 100 = 93148 hPOV
その後の話
フラッシュ仕様検討
分解能が上がったことによって表示データが大きくなり、現状ですと5フレームしか書き込めない状態です。
SPRESENSEのコンパイルされたコードはフラッシュメモリにインストールされ、実行されるときにSRAMに展開されて実行されます。
SRAMは1.5MBのうちのMineコアが768kB、Subコアが128kB割り当てられます。
表示データに使用できる容量はSRAMサイズを超えられないのです。。。
そこで8MBもあるフラッシュを有効に使用できないか検討しました。
Fileライブラリ を使用するとフラッシュにファイル形式でデータを保存できます。
そこで表示データ(LED57個 × 分解能400の32ビットデータ)をテキストデータとしてフラッシュに保存して、再度読み込んで配列にする実験をしました。
テキストデータを1文字づつ読んで32ビット分で文字列にして16進数に変換して配列化しました。
1フレーム分の配列を読み込むのに3分20秒もかかりました。。。。
マルチコアで読み込んで配列生成しながら、長尺の動画を表示したかったのですが時間がかかり過ぎのため断念しました。。。
参考
装置構成の変更
2本のLEDテープの配置を2本平行ではなく、1直線に並べました。回転の際の表示のずれを抑えるためです。
ずれが軽減され精度が増しました。でももっとやりたくなっちゃう。。キリがないww
さいごに
今回のSPRESENSEバージョンアップによってバーサライタの性能が大きく向上しました。
いつかフラッシュメモリの使用も気軽にできるようになるかと思います。今回のバージョンアップのように。
祈りを込めて。。。
そして バーサライタ装置をステージ上にそっと置き
地を引きずりながら漂う煙のようにその場を後にした。
しかし その表情はどこか誇らしげであったという