SMDkoubouのアイコン画像
SMDkoubou 2021年02月24日作成 (2021年02月28日更新)
製作品 製作品 閲覧数 2055
SMDkoubou 2021年02月24日作成 (2021年02月28日更新) 製作品 製作品 閲覧数 2055

16x16 LED MATRIXを使用した砂時計の制作

概要

16x16 LED MATRIXの素子を砂に見立てて、センサーで傾きを検知し、本物の砂時計のような挙動をすることを目指しました。

主な使用部品

16x16 LED MATRIX
鈴商で扱っている2mmピッチのLED MATRIX LT5013Tです。
LT5013T

加速度センサー
ADXL345
足が無いので、クリームはんだ+ヒートガン or リフローが必要です。

LEDドライバ
部品点数を少なくするため、中華チップ構成にしました。
アノード側 : ICN2012 カスケード接続にして16本を確保
カソード側 : MBI5024 デイジーチェーン接続で32本を確保

バッテリー
砂時計のくびれ部分を細くするため、18650を使用。
USBからの充電にも対応。

CPU
18650の幅に収まるサイズ性を重視してSTM32F042を選択。

スピーカー
秋月電子のUGCT7525AN4です。
タイマー出力からトランジスタ一本で駆動。

ブロック図

ブロック図は下記のようになります
ブロック図
加速度センサーで傾きを検知し、仮想の粒子の移動を行いLEDを点灯する場所を決めます。
LED MATRIXはカソード側を1ms毎に更新、アノード側の切り替えを16回行うので1フレーム16ms。62.5fpsの画面更新となります。
ICN2012は焼き付き防止回路が入っており、定期的な更新が無いと出力が停止します。Debugの際は注意が必要です。

回路図

基本的にブロック図の通りです。電源はBATTERY BOARDからJ2を通して供給されます。
MAIN BOARD回路図

充電はTP4057を使用しています。
BATTERY BOARD回路図

作成した基板

LED MATRIXのピンピッチが特殊なことや表面実装部品を多用するため、基板を起こしました。
MAIN BOARD表
U4, U5はICN2012ですが、LED MATRIXの下になるので、先に実装が必要です。半田不良があったらアウトです。
MAIN BOARD裏

MAIN BOARDにはバッテリが載らないためバッテリ用に別基板を作成しました。充電回路も載っています。
BATTERY BOARD表
裏には何もありません。
BATTERY BOARD裏

実装

実装すると以下のようになります。
MAIN BOARD実装
ピンヘッダを通してMAIN BOARDに電源を供給します。
BATTERY BOARD実装

スペーサーを使用して2枚を合わせると以下のようになります。
組み合わせ

右横から見ると以下のようになります。こちら側には電源スイッチと、リセット用のスイッチがあります。
右横から見た図

左横から見ると以下のようになります。こちら側には充電用のUSB端子があります。
左横から見た図

動作

電源を入れると重力に従ってLEDの点灯が移動します。
動作図

スモークのアクリルを前面に付けるとLEDが光っている部分のみが残りますので、見た目が良くなります。
(アクリルの加工が雑であまり綺麗ではありませんが…)
スモークアクリル追加

動作時の動画は以下になります。
砂に見立てたLEDは1秒に1つ落ちるようになっており、リセットスイッチを押すと重力とは反対側のLED MATRIXに180個が集まります。
重力側に全部移動(3分経過)するとアラームが鳴ります。

ここに動画が表示されます

ソースコード

AdafruitのPixelDustライブラリを使用しています。

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++; }
7
SMDkoubouのアイコン画像
以前はDIPを使った電子工作を行っていましたが、基板製作が安価に出来るようになってきたので、表面実装を使った電子工作に移行しました。コンパクトに製作することが出来る利点を活用して今まで実現できなかったガジェットの作成を目指します
ログインしてコメントを投稿する