電子部品販売代理店のマウザーエレクトロニクス様から、8/31の初音ミク誕生祭に関連した電子工作を作っていただけないか、と提案を受けて作りました。マウザー様のサイト内のForteという部品表作成ツールを使って部品を選定し、ご提供いただいたものを一部使用しています。
- M5 ATOM
動画
YouTube
ニコニコ動画
概要
主な構成等は動画を見ていただければと思います。今回使用したArduinoのコードと画像変換に使用したPythonのコードを添付します。
Arduinoコード
ネギを振るたびに9個の画像が切り替わります。振り下ろすときにも振り上げる時にも画像を表示できます。ネギを振った時の判定は当初はXYZ方向加速度の合計で判定していましたが、角加速度(Gyro)の方が安定して判定されたためこちらを使用しています。表示する画像データはCファイルで作成してそれぞれincludeしています。Cファイルの中身を見てもらえばわかるのですが、中身はunsigned longの200×100の配列です。unsignd longは32bitのサイズで、実際に必要なのは24bitのデータ(RGBx8bit)のため、無駄に4/3倍のデータサイズとなっていますが、これはメンドクサイ今回修正する時間が無かった&M5 ATOMのフラッシュ容量が大きいため、この仕様にしています。8bitの3x200x100の配列にするとか16bit(RRRRRGGGGGGBBBBB)の色情報に圧縮するとかすればより多くの画像を収納することができます。
画像のCファイルはコチラからダウンロードできます。
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 |
#include <M5Atom.h> #define GPIO_0to31_REG *((volatile unsigned long *)GPIO_OUT_REG) #define GPIO_32to39_REG *((volatile unsigned long *)GPIO_OUT1_REG) #include "image0.c" #include "image1.c" #include "image2.c" #include "image3.c" #include "image4.c" #include "image5.c" #include "image6.c" #include "image7.c" #include "image8.c" #include "image9.c" // Define which pins to use. const uint8_t dataPin = 19; const uint8_t clockPin =22; // Set the number of LEDs to control. const uint16_t ledCount = 100; const uint16_t verticalCount = 200; const uint16_t drawTime = 1000; const uint16_t drawDelayTime = 10; // Set the brightness to use (the maximum is 31). const uint8_t brightness = 8; //Set Acc Sensor float accX = 0, accY = 0, accZ = 0; float gyroX = 0, gyroY = 0, gyroZ = 0; const float accTh = 0.1; const float gyroTh = 50; uint8_t c = 0; bool sword_flag; uint8_t imageNum; uint32_t data1[ledCount];//3200byteを確保している場合、Core Panicになる可能性 void setup() { M5.begin(true, false, true); M5.IMU.Init(); Serial.begin(115200); sword_flag = false; pinMode(39, INPUT_PULLUP); pinMode(dataPin, OUTPUT); pinMode(clockPin, OUTPUT); imageNum = 0; LED_Clear(); for(int16_t i=0;i<ledCount;i++){ data1[i] = 0x00FF6E; sendPacket24(data1); delay(10); } delay(3000); for(int16_t i=0;i<ledCount;i++){ data1[ledCount-i-1] = 0x000000; sendPacket24(data1); delay(10); } } void loop() { M5.IMU.getAccelData(&accX, &accY, &accZ); M5.IMU.getGyroData(&gyroX, &gyroY, &gyroZ); float acc = sqrt(accX*accX + accY*accY + accZ*accZ); float gyro = gyroZ; Serial.printf("%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f\r\n", accX * 1000, accY * 1000, accZ * 1000, acc * 1000, gyroX, gyroY, gyroZ); if (gyro<-gyroTh) { imageNum %= 9; Serial.println(imageNum); delay(drawDelayTime); for(int16_t v=0;v<verticalCount;v++){ imageMemcopy(imageNum, v); sendPacket24(data1); delayMicroseconds(drawTime); } LED_Clear(); imageNum++; delay(500);//バーサライタ停止時の再描写防止用 } else if (gyro>gyroTh) { imageNum %= 9; Serial.println(imageNum); delay(drawDelayTime); for(int16_t v=verticalCount-1;v>=0;v--){//uintにするとpanic errorが出る imageMemcopy(imageNum, v); sendPacket24(data1); delayMicroseconds(drawTime); } LED_Clear(); imageNum++; delay(500);//バーサライタ停止時の再描写防止用 } delay(20); M5.update(); } void imageMemcopy(uint8_t imgNum, uint16_t v){ switch(imgNum){ case 0: memcpy(data1, image0[v], sizeof(data1)); break; case 1: memcpy(data1, image1[v], sizeof(data1)); break; case 2: memcpy(data1, image2[v], sizeof(data1)); break; case 3: memcpy(data1, image3[v], sizeof(data1)); break; case 4: memcpy(data1, image4[v], sizeof(data1)); break; case 5: memcpy(data1, image5[v], sizeof(data1)); break; case 6: memcpy(data1, image6[v], sizeof(data1)); break; case 7: memcpy(data1, image7[v], sizeof(data1)); break; case 8: memcpy(data1, image8[v], sizeof(data1)); break; case 9: memcpy(data1, image9[v], sizeof(data1)); break; } } //Send 24bit color data for APA102 void sendPacket24(uint32_t rgb[]){ initGPIO(); //Start Frame send8bit(0x00); send8bit(0x00); send8bit(0x00); send8bit(0x00); //Data Field for(int i=0;i<ledCount;i++){ send8bit(224+brightness);//brightness send8bit(rgb[i]&0x0000FF);//Blue send8bit((rgb[i]&0x00FF00)>>8);//Green send8bit((rgb[i]&0xFF0000)>>16);//Red } //End Frame endFrame(); } void send8bit(uint8_t dat){ GPIO_0to31_REG = (dat>>7&1)<<dataPin;//Clock:LOW GPIO_0to31_REG = (dat>>7&1)<<dataPin|1<<clockPin;//Clock:HIGH GPIO_0to31_REG = (dat>>6&1)<<dataPin;//Clock:LOW GPIO_0to31_REG = (dat>>6&1)<<dataPin|1<<clockPin;//Clock:HIGH GPIO_0to31_REG = (dat>>5&1)<<dataPin;//Clock:LOW GPIO_0to31_REG = (dat>>5&1)<<dataPin|1<<clockPin;//Clock:HIGH GPIO_0to31_REG = (dat>>4&1)<<dataPin;//Clock:LOW GPIO_0to31_REG = (dat>>4&1)<<dataPin|1<<clockPin;//Clock:HIGH GPIO_0to31_REG = (dat>>3&1)<<dataPin;//Clock:LOW GPIO_0to31_REG = (dat>>3&1)<<dataPin|1<<clockPin;//Clock:HIGH GPIO_0to31_REG = (dat>>2&1)<<dataPin;//Clock:LOW GPIO_0to31_REG = (dat>>2&1)<<dataPin|1<<clockPin;//Clock:HIGH GPIO_0to31_REG = (dat>>1&1)<<dataPin;//Clock:LOW GPIO_0to31_REG = (dat>>1&1)<<dataPin|1<<clockPin;//Clock:HIGH GPIO_0to31_REG = (dat>>0&1)<<dataPin;//Clock:LOW GPIO_0to31_REG = (dat>>0&1)<<dataPin|1<<clockPin;//Clock:HIGH } void endFrame() { for (uint16_t i = 0; i < (ledCount + 14)/16; i++) { send8bit(0xFF); } initGPIO(); } void initGPIO(){ GPIO_0to31_REG = 0<<dataPin|0<<clockPin; } void LED_Clear(){ for(uint16_t i=0; i < ledCount; i++){ data1[i] = 0x000000; } sendPacket24(data1); } |
画像変換のPythonコード
filenameには変換する画像のファイルパスを入れてください。reWidthとreHightは球形POVの時の名残なので気にしないでください(球形POVでは元画像の下部分をトリミングする必要があった)。元画像の色のままでLEDに表示させると明るさが強くなり、実際の見た目に対して白っぽくなるためガンマ補正をかけて調整しています。普通のガンマ補正は±0.0~1.0程度なので5.0という過剰な値にしていますが、バーサライタ(POV)に表示させる場合はこのぐらいでちょうどよくなります。
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 |
# -*- coding: utf-8 -*- from PIL import Image import numpy as np gammaR = 5.0 gammaG = 5.0 gammaB = 5.0 np.set_printoptions(threshold=np.inf)#printの表示を省略しない print('ファイル名') imageNum = 9 filename = '変換する画像のファイルパス' # 元となる画像の読み込み img = Image.open(filename) #出力画像の幅と高さを設定 width = 100 height = 200 #リサイズ画像の幅と高さを設定 reWidth = width reHeight = height #リサイズ img_resize = img.resize((reWidth, reHeight), Image.LANCZOS) # オリジナル画像と同じサイズのImageオブジェクトを作成する img2 = Image.new('RGB', (width, height)) img_pixels = [] for y in range(height): for x in range(width): # getpixel((x,y))で左からx番目,上からy番目のピクセルの色を取得し、img_pixelsに追加する img_pixels.append(img_resize.getpixel((width-x-1,y))) # あとで計算しやすいようにnumpyのarrayに変換しておく img_pixels = np.array(img_pixels) #print(img_pixels) img_data = np.zeros(height*width) path_w = 'Image'+str(imageNum)+'.c' with open(path_w, mode='w') as f: f.write('//' + filename + '\n') f.write('const unsigned long image'+str(imageNum)+'['+str(height)+']['+str(width)+'] = {\n') f.write('{') for i in range(height*width): R = img_pixels[i,0] G = img_pixels[i,1] B = img_pixels[i,2] #ガンマ補正 R = round(((R/255.0)**gammaR)*255) G = round(((G/255.0)**gammaG)*255) B = round(((B/255.0)**gammaB)*255) img_data[i] = R*65536+G*256+B f.write(format(int(img_data[i]),'#010x')) if i+1==height*width: f.write('}\n};') elif (i+1)%width==0: f.write('},\n{') else: f.write(',') |
コメント