友人から、こんな面白いものがあるよとネタを振られたので遊んでみました。

物としては2023年頃から販売されているようです。3色LED×30個とリモコン付きで税抜500円ってすごくないですか? これでどうやって利益を出すのか。すごいです。ただ30個のLEDを個別に制御する、いわゆる addressable な仕様にはなっておらず、同時に指定した色にすることしかできません。さすがに NeoPixel ×30個では500円で収まらないと思う。
赤外線信号の解析
赤外線リモコンと言われたらまず信号を解析したくなりませんか? ということで何をするにもまずは解析からやってみました。超ざっくりまとめるとこんな感じ。
- 仕様は普通の NEC format
- Custom code は 0x00ef
- 左上の button から横に 00, 01, 02, …… と連番を振っている分かりやすい並び
UP | DOWN | OFF | ON |
0x00ef00ff | 0x00ef01fe | 0x00ef02fd | 0x00ef03fc |
R | G | B | W |
0x00ef04fb | 0x00ef05fa | 0x00ef06f9 | 0x00ef07f8 |
FLASH | |||
0x00ef08f7 | 0x00ef09f6 | 0x00ef0af5 | 0x00ef0bf4 |
STROBE | |||
0x00ef0cf3 | 0x00ef0df2 | 0x00ef0ef1 | 0x00ef0ff0 |
FADE | |||
0x00ef10ef | 0x00ef11ee | 0x00ef12ed | 0x00ef13ec |
SMOOTH | |||
0x00ef14eb | 0x00ef15ea | 0x00ef16e9 | 0x00ef17e8 |
なんとなくの想像ですが、きっとこういう汎用的に使えるICが出回ってるんでしょうね。で、実装側でそれぞれの信号がどの機能に対応するのか決めてるんだと思います。
STM32 の IRTIM
STM32 が好き (というか STM32 HAL と STM32CubeMX に飼い馴らされたダメ人間) という理由から STM32 で制御することにしましたが、IRTIM を使うのは初めてです。
IRTIM は TIM16 と TIM17 の論理積として動作します。STMicroElectronics の説明によると、TIM16 は送信する0/1の PWM の波形を作るために、TIM17は副搬送波(今回は 38kHz)を作るために使うようです。今回は NUCLEO-G031K8 を64MHzで動かすことにしたので、設定値はそれぞれ以下としました。
- TIM16……Prescaler: 35967, Counter Period: 24 (仮)
- TIM17……Prescaler: 40, Counter Period: 40 (64MHz/((40+1)*(40+1)) ≒ 38kHz)
- TIM16とTIM17を有効にしたらIRTIMを有効にできる
- output polarity: Polarity inverted (STM32G0固有機能、出力に抵抗とLEDを直結するときに便利)
- IR Modulation Envelope signal (STM32G0固有機能、UART でも送出できるらしい)
IRTIM を有効にしながら、TIM16とTIM17をそれぞれPA6とPA7に出力することができます。Logic analyzer による動作確認に便利です。
STM32 の DMA burst 転送
TIM16 の ARR (Auto Reload Register) と CCR1 (Capture/Compare Register 1) を同時に更新する必要があり、間に RCR (Repetition Counter Register) が挟まっているので、単純な DMA ではなく DMA burst が必要になります。が、あまり設定する箇所はありません。
- DMA Request timing: TIM16_UP
- Direction: Memory To Peripheral
- Data Width: Word
- Increment Address: Memoryのみ有効
Code
以上の設定から、書くべき code はこんな感じになります。ここでは RGB を循環させてみました。Comment にも書いてますが、明示的に HAL_TIM_PeriodElapsedCallback() の中で HAL_TIM_PWM_Stop() と HAL_TIM_DMABurst_WriteStop() を呼んでやる必要があります。
// DMA burst 転送は終了後に停止しないと次の転送ができない
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
HAL_TIM_PWM_Stop(&htim16, TIM_CHANNEL_1);
HAL_TIM_DMABurst_WriteStop(&htim16, TIM_DMA_UPDATE);
}
// size of txbuffer must be 102 or more.
void txdata(uint32_t command, uint32_t* txbuffer)
{
*txbuffer = 24; // ARR = t_on + t_off
*(txbuffer + 1) = 0; // RCR
*(txbuffer + 2) = 16; // CCR1 = t_on
for(int byteCount = 0; byteCount < 4; byteCount++)
{
for(int bitCount = 0; bitCount < 8; bitCount++)
{
*(txbuffer + 3 + (byteCount * 8 + bitCount) * 3 ) = ((((command >> ((3 - byteCount) * 8)) & 0xff) >> bitCount) & 1) == 0 ? 1 : 3;
*(txbuffer + 3 + (byteCount * 8 + bitCount) * 3 + 1) = 0;
*(txbuffer + 3 + (byteCount * 8 + bitCount) * 3 + 2) = 1;
}
}
*(txbuffer + 99) = 100;
*(txbuffer + 100) = 0;
*(txbuffer + 101) = 1;
}
int main(void)
{
// 中略
/* USER CODE BEGIN 2 */
// 副搬送波
__HAL_TIM_SET_COMPARE(&htim17, TIM_CHANNEL_1, 13);
HAL_TIM_PWM_Start(&htim17, TIM_CHANNEL_1);
uint32_t txbuffer[(1 + 16 + 8 + 8 + 1) * 3] = {0}; // leader + customer code + data + ^data + tail = 102
uint32_t commands[] = {0x00EF04FB, 0x00EF05FA, 0x00EF06F9}; // Red, Green, Blue
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
for(int index = 0; index < 3; index++)
{
txdata(commands[index], txbuffer);
while(HAL_TIM_DMABurstState(&htim16) != HAL_DMA_BURST_STATE_READY) {}
if(HAL_TIM_DMABurst_MultiWriteStart(
&htim16,
TIM_DMABASE_ARR,
TIM_DMA_UPDATE,
(uint32_t *)txbuffer,
TIM_DMABURSTLENGTH_3TRANSFERS,
sizeof(txbuffer) / sizeof(uint32_t)) != HAL_OK)
{
Error_Handler();
}
if(HAL_TIM_PWM_Start(&htim16, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
while(HAL_TIM_DMABurstState(&htim16) == HAL_DMA_BURST_STATE_BUSY) {}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
できあがり
TIM17、TIM16、IRTIM それぞれの出力と protocol decoder の結果を PulseView で観察したところ、上々の出来です。ただしRGBを循環させるとものすごいギラギラした感じになるのであまりおすすめしません。

コメントを残す