SMDkoubou が 2021年02月28日21時13分31秒 に編集
コメント無し
記事種類の変更
製作品
本文の変更
## 概要 16x16 LED MATRIXの素子を砂に見立てて、センサーで傾きを検知し、本物の砂時計のような挙動をすることを目指しました。 ## 主な使用部品 **16x16 LED MATRIX** 鈴商で扱っている2mmピッチのLED MATRIX [LT5013T](https://suzushoweb.shop-pro.jp/?pid=100888047)です。  **加速度センサー** ADXL345 足が無いので、クリームはんだ+ヒートガン or リフローが必要です。 **LEDドライバ** 部品点数を少なくするため、中華チップ構成にしました。
アノード側 : ICN2012 カスケード接続にして16bitを確保 カソード側 : MBI5024 デイジーチェーン接続で32bitを確保
アノード側 : ICN2012 カスケード接続にして16本を確保 カソード側 : MBI5024 デイジーチェーン接続で32本を確保
**バッテリー** 砂時計のくびれ部分を細くするため、18650を使用。 USBからの充電にも対応。 **CPU** 18650の幅に収まるサイズ性を重視してSTM32F042を選択。 **スピーカー** 秋月電子の[UGCT7525AN4](https://akizukidenshi.com/catalog/g/gP-09801/)です。 タイマー出力からトランジスタ一本で駆動。 ## ブロック図 ブロック図は下記のようになります  加速度センサーで傾きを検知し、仮想の粒子の移動を行いLEDを点灯する場所を決めます。 LED MATRIXはカソード側を1ms毎に更新、アノード側の切り替えを16回行うので1フレーム16ms。62.5fpsの画面更新となります。 ICN2012は焼き付き防止回路が入っており、定期的な更新が無いと出力が停止します。Debugの際は注意が必要です。
## 回路図 基本的にブロック図の通りです。電源はBATTERY BOARDからJ2を通して供給されます。  充電はTP4057を使用しています。 
## 作成した基板 LED MATRIXのピンピッチが特殊なことや表面実装部品を多用するため、基板を起こしました。  U4, U5はICN2012ですが、LED MATRIXの下になるので、先に実装が必要です。半田不良があったらアウトです。  MAIN BOARDにはバッテリが載らないためバッテリ用に別基板を作成しました。充電回路も載っています。  裏には何もありません。  ## 実装 実装すると以下のようになります。  ピンヘッダを通してMAIN BOARDに電源を供給します。  スペーサーを使用して2枚を合わせると以下のようになります。  右横から見ると以下のようになります。こちら側には電源スイッチと、リセット用のスイッチがあります。  左横から見ると以下のようになります。こちら側には充電用のUSB端子があります。  ## 動作 電源を入れると重力に従ってLEDの点灯が移動します。  スモークのアクリルを前面に付けるとLEDが光っている部分のみが残りますので、見た目が良くなります。 (アクリルの加工が雑であまり綺麗ではありませんが…)  動作時の動画は以下になります。 砂に見立てた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++; } ```