SDカードに保存した画像データを読み込んでPOVに表示します。回路は1回目を参照ください。
今回の目的
SDカードから画像データを読み込んでPOVに表示するだけであれば、必ずしもマルチコアを使う必要はありません。SDカード内の画像データを変数配列にコピーしてからPOVに表示すれば良いです。ただ、この方法だとSDカードの通信中はPOVの描画は止まってしまいます。最終的な目標は動画の再生であることから連続的な画像の読み込みを実現するため、マルチコアを使用します。
シングルコアでの概要
前回のフラッシュメモリから画像データ1行分を読み込む方法では一回の描画時間(一周の時間/分解能)の中でも読み込みができるだけの速度を出せましたが、SDカードの通信はそれよりも遅いため、(1行だけ読むとしても)間に合いません。そのため、1周(かそれ以上)の間描画を止めてSDカードの通信に専念する必要があります。
マルチコアでの概要
Core1はPOVの描画、Core0はSDカードの読み込みに専念させます。画像データを格納する変数配列は2つ用意し、片方の配列にSDカード内データを格納しているのと並行してもう片方の配列のデータをPOVの描画に使用します。
これのメリットとしては、例えば画像データの読み込みが1周の間に間に合わなくとも、すでに読み込み済みの配列のデータの方を使えば描画が止まらないという点があります。読み込みとどちらのデータを使うかというところを判断するために、プログラムでは複数のフラグを立てて管理しています。また、SDカードからの読み込み以外でもWiFi/BTを介した通信・データ送信もCore0で行うことにより、POVで描写しながらリアルタイムでできるというメリットもあります。
SDカードに保存するデータ
前回の画像変換のPythonプログラムで生成されたbmpファイルを保存します。今回はプログラムの実証用にA/Bと描かれた画像2枚をSDカードへ保存します。
画像変換のPythonプログラムは前回を参照ください。
Arduinoプログラム
基本は前回と同じですが、データの読み込みがフラッシュメモリからSDカードへと変わっており、Core0で読み込みを行っています。
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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
#define GPIO_0to31_REG *((volatile unsigned long *)GPIO_OUT_REG) #define GPIO_0to31SET_REG *((volatile unsigned long *)GPIO_OUT_W1TS_REG) #define GPIO_0to31CLR_REG *((volatile unsigned long *)GPIO_OUT_W1TC_REG) #include <SPI.h> #include "SdFat.h" //SDカードの設定 const uint8_t SD_CS_PIN = SS; #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(24))//24MHz SdFs sd; FsFile dir; FsFile file; //マルチスレッドのタスクハンドル格納用 TaskHandle_t thp; static uint8_t buf[512]; bool DataAvailable; bool DataReadable; bool DataSelect; // Define which pins to use. //SPI:18SCLK,23MOSI,19MISO,5CS //HSPI:14SCLK,13MOSI,12MISO,15CS #define sensorPin 27 //回転検出センサ入力 #define dataPin 13 //LEDクロック(HSPI使用時は不要) #define clockPin 14 //LEDデータ(HSPI使用時は不要) SPIClass hspi(HSPI); const bool SPIEnable = true; //SPIをLEDの通信に使うかどうか(false:GPIOをLEDの通信に使う) // Set the number of LEDs to control. const uint16_t LEDNum = 30; //LEDの数 const uint16_t circleNum = 283; //一周を何分解するか const uint16_t startDeg = 120; //回転検出センサの位置(0~359度) // Set the brightness to use (the maximum is 31). uint8_t brightness = 8; //Timing to drawing unsigned long drawTime; unsigned long preTime; unsigned long nowTime; unsigned long cycleTime; unsigned long rawCycleTime; unsigned long cycleTimeTh = 20000; //誤検知防止用のしきい値[us] bool drawFlag; byte sendData[LEDNum*4+9]; byte imgByteA[circleNum][4*(uint16_t)ceil((float)LEDNum*3/4)];//Bitmapは列数を4の倍数にする必要あり byte imgByteB[circleNum][4*(uint16_t)ceil((float)LEDNum*3/4)]; //回転検出センサの割り込み void Interrupt(){ nowTime = micros(); rawCycleTime = nowTime-preTime; //回転検出センサの誤入力を除外 if(rawCycleTime>cycleTimeTh){ cycleTime = rawCycleTime; drawTime = cycleTime/circleNum; preTime = nowTime; drawFlag = true; } } void setup() { pinMode(sensorPin,INPUT); Serial.begin(115200); while (!Serial) { delay(1); // wait for serial port to connect. } //SPI設定、もしくはGPIOの出力設定 if(SPIEnable){ hspi.begin(14, 12, 13, 15); hspi.setFrequency(30000000); hspi.setDataMode(SPI_MODE3); hspi.setHwCs(true); } else{ pinMode(dataPin,OUTPUT); pinMode(clockPin,OUTPUT); } //割り込み設定 cycleTime = 500000; preTime = 0; //attachInterrupt(sensorPin, Interrupt, RISING); attachInterrupt(sensorPin, Interrupt, FALLING); drawFlag = false; //LEDに送信するデータ配列の準備(開始Byte/終了Byte/明るさ) for(int i=0;i<sizeof(sendData);i++){ if(i<4){ sendData[i] = 0x00; } else if(i>=sizeof(sendData)-5){ sendData[i] = 0xFF; } else if(i%4 == 0){ sendData[i] = 224+brightness; } } //マルチコア設定 DataAvailable = false; DataReadable = true; DataSelect = true; xTaskCreatePinnedToCore(Core0a, "Core0a", 4096, NULL, 5, &thp, 0); delay(10); Serial.println("cycleTime,initialTime,memcpyTime,sendDataTime"); } void loop() { //drawFlag = true;//回転させずともLEDにデータを流す。デバッグ用 if(drawFlag){ if(DataAvailable){ DataReadable = true; } drawFlag=false; unsigned long nextDrawTime = micros(); uint16_t startPos = round((float)circleNum * (float)startDeg/360.0); uint16_t h = startPos; uint32_t testTime; uint32_t initialTime = 0; uint32_t memcpyTime = 0; uint32_t sendDataTime = 0; while(1){ if(nextDrawTime<=micros()){ testTime = micros(); nextDrawTime += drawTime; uint16_t h2; if(h<circleNum/2) h2=h+circleNum/2; else h2=h-circleNum/2; initialTime += micros()-testTime; testTime = micros(); for(int i=0;i<LEDNum;i++){ if(DataSelect){//現在がDataAの場合 sendData[4*i+5] = imgByteA[h][3*i+0]; sendData[4*i+6] = imgByteA[h][3*i+1]; sendData[4*i+7] = imgByteA[h][3*i+2]; } else{//現在がDataBの場合 sendData[4*i+5] = imgByteB[h][3*i+0]; sendData[4*i+6] = imgByteB[h][3*i+1]; sendData[4*i+7] = imgByteB[h][3*i+2]; } } memcpyTime += micros()-testTime; //LEDへデータを送信 testTime = micros(); if(SPIEnable){ hspi.writeBytes(sendData,sizeof(sendData)); } else{ writeBytes(sendData,sizeof(sendData)); } sendDataTime += micros()-testTime; h++; if(h == startPos)break;//hが一周したら終了 if(h == circleNum) h=0; } } Serial.printf("%ld,%ld,%ld,%ld\n",cycleTime,initialTime,memcpyTime,sendDataTime); } } //SDカード読み込み(マルチコア) void Core0a(void *args) { //SDカードの認識 Serial.print("Initializing SD card..."); if(!sd.begin(SD_CONFIG)){ Serial.println("Card Mount Failed"); } char* path; byte* imgByte; while (1) { if(DataReadable){ DataAvailable = false; if(DataSelect){//現在がDataAの場合 path = "/B.bmp"; } else{//現在がDataBの場合 path = "/A.bmp"; } if(!file.open(path, FILE_READ)){ Serial.println("Failed to open file"); } else{ Serial.println(path); file.seek(54); if(DataSelect) file.read(imgByteB,sizeof(imgByteB)); else file.read(imgByteA,sizeof(imgByteA)); file.close(); DataAvailable = true; } DataReadable = false; DataSelect = !DataSelect; } delay(1);//Watch Dog Timerエラー防止のため } } void writeBytes(uint8_t *dat, uint16_t datSize){ for(uint16_t i=0;i<datSize;i++){ GPIO_0to31_REG = (dat[i]>>7&1)<<dataPin; //Clock:LOW GPIO_0to31SET_REG = 1<<clockPin; //Clock:HIGH GPIO_0to31_REG = (dat[i]>>6&1)<<dataPin; //Clock:LOW GPIO_0to31SET_REG = 1<<clockPin; //Clock:HIGH GPIO_0to31_REG = (dat[i]>>5&1)<<dataPin; //Clock:LOW GPIO_0to31SET_REG = 1<<clockPin; //Clock:HIGH GPIO_0to31_REG = (dat[i]>>4&1)<<dataPin; //Clock:LOW GPIO_0to31SET_REG = 1<<clockPin; //Clock:HIGH GPIO_0to31_REG = (dat[i]>>3&1)<<dataPin; //Clock:LOW GPIO_0to31SET_REG = 1<<clockPin; //Clock:HIGH GPIO_0to31_REG = (dat[i]>>2&1)<<dataPin; //Clock:LOW GPIO_0to31SET_REG = 1<<clockPin; //Clock:HIGH GPIO_0to31_REG = (dat[i]>>1&1)<<dataPin; //Clock:LOW GPIO_0to31SET_REG = 1<<clockPin; //Clock:HIGH GPIO_0to31_REG = (dat[i]>>0&1)<<dataPin; //Clock:LOW GPIO_0to31SET_REG = 1<<clockPin; //Clock:HIGH } } |
プログラムの補足
- 50行目:BMPはBGRx8bitのバイナリデータが並んでいますが、1列ごとのバイナリデータの数を4の倍数にする必要があります。そのため画像の幅(LED数)が4の倍数でない場合は単純に画像の幅x3の配列を用意するとズレていってしまうので、上記プログラムのように4の倍数になるように調整します。
- 116行目:Core0の設定です。設定値は割りと適当です(エラーが出なければOK)。
- 195,198行目:今回のプログラムは実験要素が大きいので読み込む画像のファイル名はここで直接記載しています。
- 214行目:これがマルチコアで結構躓いた所なのですが、マイコンにはWDT(ウォッチドックタイマー)というものがあり、プログラムが暴走・停止していないかを定期的に監視しています。Core1で実行されるLOOPの中ではLOOPが繰り返されるたびにWDTへ信号を送っているらしく特に意識する必要は無いのですが、例えばWhileループで長時間繰り返し続けるとWDTエラーが発生します。Core0ではWhileで永久ループを回しているため、定期的にWDTへ信号を送ることをしないとエラーが発生します。具体的には”delay(1)”か”vTaskDelay(1)”を挟むとその間にWDTとの通信が行われるらしいです。
実際の動き
回転するごとにAとBの画像が切り替わります。(よく見たら文字が鏡写しになっていますね。。。)
平面POV+ESP32でマルチコアの実験中
— やまたい@ものつくり (@yamatai_mk) October 10, 2022
Core0でフラッシュを(ABのデータを交互に)読みながら、Core1でLEDに表示してます
Core0でSDカード(動画)を読むのが最終目標#POV #バーサライタ pic.twitter.com/TfIxlDI7xe
次回
次回は最終回、SDカードに保存した動画データを読み込んでPOVに表示する方法です。
コメント