編集履歴一覧に戻る
SMDkoubouのアイコン画像

SMDkoubou が 2021年02月28日21時13分31秒 に編集

コメント無し

記事種類の変更

+

製作品

本文の変更

## 概要 16x16 LED MATRIXの素子を砂に見立てて、センサーで傾きを検知し、本物の砂時計のような挙動をすることを目指しました。 ## 主な使用部品 **16x16 LED MATRIX** 鈴商で扱っている2mmピッチのLED MATRIX [LT5013T](https://suzushoweb.shop-pro.jp/?pid=100888047)です。 ![LT5013T](https://camo.elchika.com/b92fb226552482c7d50e62ec93cb388393dbdf32/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f34636239633565642d633034612d346332342d613937392d346637333962303864333737/) **加速度センサー** ADXL345 足が無いので、クリームはんだ+ヒートガン or リフローが必要です。 **LEDドライバ** 部品点数を少なくするため、中華チップ構成にしました。

-

アノード側 : ICN2012 カスケード接続にして16bitを確保 カソード側 : MBI5024 デイジーチェーン接続で32bitを確保

+

アノード側 : ICN2012 カスケード接続にして16を確保 カソード側 : MBI5024 デイジーチェーン接続で32を確保

**バッテリー** 砂時計のくびれ部分を細くするため、18650を使用。 USBからの充電にも対応。 **CPU** 18650の幅に収まるサイズ性を重視してSTM32F042を選択。 **スピーカー** 秋月電子の[UGCT7525AN4](https://akizukidenshi.com/catalog/g/gP-09801/)です。 タイマー出力からトランジスタ一本で駆動。 ## ブロック図 ブロック図は下記のようになります ![ブロック図](https://camo.elchika.com/13a9476b9359d79f9ef0319c43bac87719cfed30/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f61303430363135312d373263302d343763622d383139372d356231363437616430356564/) 加速度センサーで傾きを検知し、仮想の粒子の移動を行いLEDを点灯する場所を決めます。 LED MATRIXはカソード側を1ms毎に更新、アノード側の切り替えを16回行うので1フレーム16ms。62.5fpsの画面更新となります。 ICN2012は焼き付き防止回路が入っており、定期的な更新が無いと出力が停止します。Debugの際は注意が必要です。

+

## 回路図 基本的にブロック図の通りです。電源はBATTERY BOARDからJ2を通して供給されます。 ![MAIN BOARD回路図](https://camo.elchika.com/5885aee0a1bb6c7d9f72fd694dcc1aea0330b1d9/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f33386265346365632d633261332d346162362d623866302d626432656261626433376632/) 充電はTP4057を使用しています。 ![BATTERY BOARD回路図](https://camo.elchika.com/6353d6af426d13609464acc91fc3314fd2b05262/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f38633062636436632d303364662d346364342d383935652d313264343434376433633330/)

## 作成した基板 LED MATRIXのピンピッチが特殊なことや表面実装部品を多用するため、基板を起こしました。 ![MAIN BOARD表](https://camo.elchika.com/11bbabf029594957a53e95a2d4e6f085e1f8b79d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f35393061383334662d303763612d343534372d616661332d663363666432366635646332/) U4, U5はICN2012ですが、LED MATRIXの下になるので、先に実装が必要です。半田不良があったらアウトです。 ![MAIN BOARD裏](https://camo.elchika.com/d74188ccdb1f2011a0055409241d5282bfc3a63f/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f61386466376361362d313332612d346364342d393535662d376561323330633831313464/) MAIN BOARDにはバッテリが載らないためバッテリ用に別基板を作成しました。充電回路も載っています。 ![BATTERY BOARD表](https://camo.elchika.com/86f8eeb8c758acb98e95a32ca69ac71eea07c488/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f37363532623236312d383265322d346237612d613737392d303532396638383163383364/) 裏には何もありません。 ![BATTERY BOARD裏](https://camo.elchika.com/397b9e1288e0b94509c69c7f00966aa92bafee04/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f63303533613432332d626166312d343330332d386664322d376361613738343238613965/) ## 実装 実装すると以下のようになります。 ![MAIN BOARD実装](https://camo.elchika.com/2131861f1e6c48249427e7af6a1b487bb8a4c70d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f39396634353031382d396632612d343233392d383830332d616564393665643432643765/) ピンヘッダを通してMAIN BOARDに電源を供給します。 ![BATTERY BOARD実装](https://camo.elchika.com/3e4e189d01cb0d681252a5e965e1ac98a22769a5/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f31653038623233652d326263342d343933642d393934632d633232333335623939626430/) スペーサーを使用して2枚を合わせると以下のようになります。 ![組み合わせ](https://camo.elchika.com/e661ddb5af7798c9789504d5fd2fb346685491d6/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f34383835633963642d366530382d346265312d613663612d323336613539313131366330/) 右横から見ると以下のようになります。こちら側には電源スイッチと、リセット用のスイッチがあります。 ![右横から見た図](https://camo.elchika.com/f19f8bf2c44b2157cdc918feb012d05df3c90e1f/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f34326334333031392d656238642d343464622d623765382d356265626639393163393234/) 左横から見ると以下のようになります。こちら側には充電用のUSB端子があります。 ![左横から見た図](https://camo.elchika.com/2a8dd447ee08bf7c0868ac9d5bb3d7f3cde9f77d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f31636533346139352d653264632d343837382d393237392d623331653739336136613834/) ## 動作 電源を入れると重力に従ってLEDの点灯が移動します。 ![動作図](https://camo.elchika.com/c770c5ef02dccac908d6b0bc6369211cf278e8cb/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f64623634373362392d343332382d343832302d623336642d343532646136343132626534/) スモークのアクリルを前面に付けるとLEDが光っている部分のみが残りますので、見た目が良くなります。 (アクリルの加工が雑であまり綺麗ではありませんが…) ![スモークアクリル追加](https://camo.elchika.com/4b02d0077354a5462783437deec1c1a9c52bee9f/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66613632663961622d636434302d343937352d623037662d3534643038376263636339632f38636161666238352d323531342d343937612d623638652d303631343034626166633438/) 動作時の動画は以下になります。 砂に見立てたLEDは1秒に1つ落ちるようになっており、リセットスイッチを押すと重力とは反対側のLED MATRIXに180個が集まります。 重力側に全部移動(3分経過)するとアラームが鳴ります。 @[youtube](https://youtu.be/g-y9NBGORd4)

+

## ソースコード AdafruitのPixelDustライブラリを使用しています。 ```c:main.c #include "Adafruit_PixelDust.h" #define MATRIX_SIZE 16 #define LED_DATA_MAX ((MATRIX_SIZE*2/8)*MATRIX_SIZE) // Cathode 32bit * Anode 16bit #define ADXL_SLAVE_ADDRESS 0xA6 #define TIMER_MAX 3 #define ALARM_MAX 4 I2C_HandleTypeDef hi2c1; SPI_HandleTypeDef hspi1; TIM_HandleTypeDef htim3; TIM_HandleTypeDef htim14; __IO uint16_t AnodeCounter; __IO uint8_t TimerCounter; __IO uint8_t InterruptCounter; __IO uint8_t LEDDataCounter; uint8_t AlarmCounter; uint8_t BlinkCounter; uint8_t AlarmActivate; uint8_t LEDDataBuffer[LED_DATA_MAX]; uint8_t ADXL_Cmd[2]; union ADXLDATA { uint8_t Byte[6]; int16_t Word[3]; } ADXL_Data; uint8_t conv1[256]; uint8_t conv2[256]; uint16_t FuncTimer[TIMER_MAX]; const uint8_t LEDOffData[4] = {0,0,0,0}; const uint8_t conv[MATRIX_SIZE] = {3,7,6,8,5,9,4,10,15,13,0,12,1,11,2,14}; const uint16_t alarm_table[ALARM_MAX] = { 30612, 34361, 30612, 0 }; void AlarmInvoke(void); void StopBeep(void); void CheckFallSand(void); void (* const FuncArray[TIMER_MAX])(void) = { AlarmInvoke, StopBeep, CheckFallSand, }; void makeConvTable(void) { uint16_t i; uint8_t r; for(i=0; i<256; i++) { r = 0; if(i & 0x01) r |= 0x02; if(i & 0x02) r |= 0x80; if(i & 0x04) r |= 0x01; if(i & 0x08) r |= 0x04; if(i & 0x10) r |= 0x40; if(i & 0x20) r |= 0x08; if(i & 0x40) r |= 0x20; if(i & 0x80) r |= 0x10; conv1[i] = r; r = 0; if(i & 0x01) r |= 0x10; if(i & 0x02) r |= 0x20; if(i & 0x04) r |= 0x08; if(i & 0x08) r |= 0x40; if(i & 0x10) r |= 0x04; if(i & 0x20) r |= 0x01; if(i & 0x40) r |= 0x80; if(i & 0x80) r |= 0x02; conv2[i] = r; } } void makeLEDBuffer(void) { uint8_t i, t; for(i=0; i<MATRIX_SIZE; i++) { t = conv[i] * 4; LEDDataBuffer[0 + t] = conv2[bitmap[0 + (i * 4)]]; LEDDataBuffer[1 + t] = conv1[bitmap[1 + (i * 4)]]; LEDDataBuffer[2 + t] = conv1[bitmap[3 + ((i+MATRIX_SIZE+1) * 4)]]; LEDDataBuffer[3 + t] = conv2[bitmap[2 + ((i+MATRIX_SIZE+1) * 4)]]; } } void AlarmInvoke(void) { FuncTimer[0] = 250; uint16_t alarm_frequency = alarm_table[AlarmCounter]; if(alarm_frequency) { htim3.Init.Period = alarm_frequency; TIM_Base_SetConfig(htim3.Instance, &htim3.Init); HAL_TIM_OC_Start(&htim3, TIM_CHANNEL_1); } else { HAL_TIM_OC_Stop(&htim3, TIM_CHANNEL_1); } AlarmCounter++; if(AlarmCounter >= ALARM_MAX) AlarmCounter = 0; if(BlinkCounter & 1) { memset(LEDDataBuffer, 0, LED_DATA_MAX); } else { makeLEDBuffer(); } ubBlinkCounter++; } void StartAlarm(void) { if(AlarmActivate) return; AlarmActivate = 1; AlarmCounter = 0; BlinkCounter = 0; FuncTimer[0] = 250; } void StopAlarm(void) { if(!AlarmActivate) return; AlarmActivate = 0; HAL_TIM_OC_Stop(&htim3, TIM_CHANNEL_1); FuncTimer[0] = 0; } uint8_t CheckEmpty(uint8_t pos) { uint8_t i; if(pos) { for(i=0; i<MATRIX_SIZE; i++) { if(*(uint16_t *)&bitmap[0 + (i * 4)]) return 0; } } else { for(i=0; i<MATRIX_SIZE; i++) { if(*(uint16_t *)&bitmap[2 + ((i+MATRIX_SIZE+1) * 4)]) return 0; } } return 1; } void StopBeep(void) { HAL_TIM_OC_Stop(&htim3, TIM_CHANNEL_1); } void StartBeep(void) { htim3.Init.Period = 5999; TIM_Base_SetConfig(htim3.Instance, &htim3.Init); HAL_TIM_OC_Start(&htim3, TIM_CHANNEL_1); FuncTimer[1] = 100; } void ResetSand(void) { uint8_t i; StartBeep(); for(i=0; i<MATRIX_SIZE; i++) { *(uint16_t *)&bitmap[0 + (i * 4)] = 0; *(uint16_t *)&bitmap[2 + ((i+MATRIX_SIZE+1) * 4)] = 0; } initializeDust(ADXL_Data.Word[0] < 0 ? (MATRIX_SIZE+1) : 0); } void CheckFallSand(void) { FuncTimer[2] = 1000; if(ADXL_Data.Word[0] < 0) { if(getPixel(MATRIX_SIZE, MATRIX_SIZE+1)) { if(!getPixel(MATRIX_SIZE-1, MATRIX_SIZE-1)) { int grain = SearchGrain(MATRIX_SIZE, MATRIX_SIZE+1); if(grain >= 0) { clearPixel(MATRIX_SIZE, MATRIX_SIZE+1); setPosition(grain, MATRIX_SIZE-1, MATRIX_SIZE-1); } } } } else { if(getPixel(MATRIX_SIZE-1, MATRIX_SIZE-1)) { if(!getPixel(MATRIX_SIZE, MATRIX_SIZE+1)) { int grain = SearchGrain(MATRIX_SIZE-1, MATRIX_SIZE-1); if(grain >= 0) { clearPixel(MATRIX_SIZE-1, MATRIX_SIZE-1); setPosition(grain, MATRIX_SIZE, MATRIX_SIZE+1); } } } } } int main(void) { uint8_t i; uint16_t _porta, porta; HAL_Init(); TimerCounter = 0; InterruptCounter = 0; AnodeCounter = 0; LEDDataCounter = 0; AlarmActivate = 0; SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_SPI1_Init(); MX_TIM3_Init(); MX_TIM14_Init(); HAL_GPIO_WritePin(POWER_ON_GPIO_Port, POWER_ON_Pin, GPIO_PIN_SET); HAL_Delay(200); PixelDust(MATRIX_SIZE*2, MATRIX_SIZE*2+1, 180, 8, 32, 0); for(i=0; i<MATRIX_SIZE; i++) { *(uint16_t *)&bitmap[2 + (i * 4)] = 0xffff; *(uint16_t *)&bitmap[0 + ((i+MATRIX_SIZE+1) * 4)] = 0xffff; } *(uint32_t *)&bitmap[0 + (MATRIX_SIZE * 4)] = 0xffffffff; randomize(); makeConvTable(); makeLEDBuffer(); HAL_SPI_Transmit_IT(&hspi1, ubLEDOffData, 4); ADXL_Cmd[0] = 0x2f; // INT MAP ADXL_Cmd[1] = 0x80; // DATA_READY HAL_I2C_Master_Transmit(&hi2c1 , ADXL_SLAVE_ADDRESS, ADXL_Cmd, 2, 1000); ADXL_Cmd[0] = 0x2e; // INT ENABLE ADXL_Cmd[1] = 0x80; // DATA_READY HAL_I2C_Master_Transmit(&hi2c1 , ADXL_SLAVE_ADDRESS, ADXL_Cmd, 2, 1000); ADXL_Cmd[0] = 0x2d; // POWER CTL ADXL_Cmd[1] = 0x08; // Measure HAL_I2C_Master_Transmit(&hi2c1 , ADXL_SLAVE_ADDRESS, ADXL_Cmd, 2, 1000); for(i=0; i<TIMER_MAX; i++) { FuncTimer[i] = 0; } FuncTimer[2] = 1000; porta = GPIOA->IDR; while (1) { if(InterruptCounter != 0) { InterruptCounter = 0; ADXL_Cmd[0] = 0x32; if(HAL_OK == HAL_I2C_Master_Transmit(&hi2c1 , ADXL_SLAVE_ADDRESS, ADXL_Cmd, 1, 1000)) { if(HAL_OK == HAL_I2C_Master_Receive(&hi2c1 , ADXL_SLAVE_ADDRESS, &ADXL_Data.Byte[0], 6, 1000)) { if(iterate(ADXL_Data.Word[0], -ADXL_Data.Word[1], 0)) { makeLEDBuffer(); StopAlarm(); } else { if(CheckEmpty(ADXL_Data.Word[0] < 0 ? 0 : 1)) { StartAlarm(); } } } } } if(TimerCounter != 0) { TimerCounter = 0; _porta = GPIOA->IDR; if((porta ^ _porta) & SW_Pin) { if(!(_porta & SW_Pin)) { ResetSand(); } } porta = _porta; for(i=0; i<TIMER_MAX; i++) { if(FuncTimer[i]) { if(!--FuncTimer[i]) { FuncArray[i](); } } } } } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { GPIOB->ODR = 0x02; // Latch Enable GPIOA->ODR = AnodeCounter | 0x0400; // Anode Select Change GPIOB->ODR = 0x00; // Latch Disable AnodeCounter = (AnodeCounter + 2) & 0x1e; HAL_SPI_Transmit_IT(&hspi1, &LEDDataBuffer[LEDDataCounter], 4); LEDDataCounter = (LEDDataCounter + 4) & (LED_DATA_MAX - 1); TimerCounter++; } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { InterruptCounter++; } ```