概要
16x16 LED MATRIXの素子を砂に見立てて、センサーで傾きを検知し、本物の砂時計のような挙動をすることを目指しました。
主な使用部品
16x16 LED MATRIX
鈴商で扱っている2mmピッチのLED MATRIX 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を通して供給されます。
作成した基板
LED MATRIXのピンピッチが特殊なことや表面実装部品を多用するため、基板を起こしました。
U4, U5はICN2012ですが、LED MATRIXの下になるので、先に実装が必要です。半田不良があったらアウトです。
MAIN BOARDにはバッテリが載らないためバッテリ用に別基板を作成しました。充電回路も載っています。
裏には何もありません。
実装
実装すると以下のようになります。
ピンヘッダを通してMAIN BOARDに電源を供給します。
右横から見ると以下のようになります。こちら側には電源スイッチと、リセット用のスイッチがあります。
左横から見ると以下のようになります。こちら側には充電用のUSB端子があります。
動作
スモークのアクリルを前面に付けると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++;
}
-
SMDkoubou
さんが
2021/02/24
に
編集
をしました。
(メッセージ: 初版)
-
SMDkoubou
さんが
2021/02/24
に
編集
をしました。
-
SMDkoubou
さんが
2021/02/28
に
編集
をしました。
ログインしてコメントを投稿する