0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ATtiny202で赤外線リモコン製作

Posted at

シーリングライトの選定

使用開始から10年弱経過した部屋のLEDシーリングライトが勝手に消えるようになった
Hotalux社のHLDC08301SGを購入し交換した
選んだ理由は幾つかあるが

  • 前の家で使っていた蛍光灯のホタルックが便利だった
  • NECからの独立でリモコンが多分NECフォーマットなのでリモコン製作入門に丁度いいと判断

というわけでリモコン製作入門をする

NECフォーマット

まず最初にNECフォーマットについて理解する必要がある

elm氏の解説記事が非常に参考になった

T=562us

$=PWM( frequency=38kHz, duty=1/3, duration=T )
_=LOW( duration=T )

としたとき、

  • リーダー
    • 信号の開始を表す
    • $$$$$$$$$$$$$$$$________
  • データ
    • 32bitの固定長のデータを含むことができる
    • $_が0、$__が1に対応
  • ストップビット
    • データの末尾のビットを決定するための$

の順に構成される

$$$$$$$$$$$$$$$$________$___$___$_$___$___$___$___$_$___$_$___$_$___$___$_$___$___$_$___$___$___$___$___$_$___$___$___$_$___$___$___$___$

これのデータを読むと11011110101011011011111011101111となり、今回はこれを0b11011110101011011011111011101111==0xdeadbeefのように読むことにする
正しい読み順が決まっているようだが今回は外部ライブラリを使うこともないので不自由ない

データ部32bitはカスタマーコード16bit、データ本体8bit、その反転8bitと言われているが、実際に読むと最後の8bitがデータ本体の一部になっていたりするので、とりあえず32bitのデータがあるんだな程度の認識で良いと思う

リモコンの解析

付属のリモコンの型番はRE0212

手持ちの赤外線リモコン受信モジュール GP1UXC41QSハンドヘルドオシロ HDS272S
で波形をCSVファイルとして取得した

受光モジュールのV_outは基本HIGHで受光するとLOWになる

チャンネルやボタンを変えながら色々試した
以下は中央ボタンch1の波形データである

c1.CSV
Channel			  :,CH1
Frequency			  :,F=  ?
Period 			  :,T=  ?
PK-PK				  :,Vpp=4.640V
Average			  :,V=3.383V
Vertical pos		  :,0.00mV
Probe attenuation	  :,10X
Voltage per ADC value:,80.00mV
Time interval		  :,40.00uS

index,CH1_Voltage(mV)
0,4480.00
1,4560.00
2,4560.00
3,4560.00
4,4480.00
5,4640.00
6,4560.00
...
890,4560.00
891,4480.00
892,4480.00
893,4560.00
894,160.00
895,80.00
896,0.00
897,0.00
898,0.00
899,0.00
900,0.00
901,0.00
902,0.00
903,0.00
...
1118,0.00
1119,0.00
1120,0.00
1121,4480.00
1122,4400.00
1123,4400.00

CSVを読んでデータをデコードするスクリプトを書いた

parse.mjs
#!/usr/bin/bun
const
t=562,
b2u=w=>w.reduce((a,x,i)=>a<<1|x,0).toString(16).padStart(Math.ceil(w.length/4),0),
parse=w=>(
	w=w.match(/\d+,\d+\.\d+/g).reduce((a,x,i,_)=>(
		x=+x.split(',')[1]<1000,
		a.x!=x&&(
			a.p&&(
				_=Math.round((i-a.p)*a.t/t),
				a.x?a.a.push([_]):a.a[a.a.length-1].push(_),
				a.b.push({l:_,i:[a.p,i],x:a.x})
			),
			a.p=i,a.x=x
		),
		a
	),{
				a:[],b:[],x:1,p:0,t:+w.match(/Time interval\s*:,(\d+\.\d+)uS/)[1]
	}),
	w=w.a.slice(1,33).map(x=>+(2<x[1])),
	[
		b2u(w.slice(0,16)),
		b2u(w.slice(16,24)),
		b2u(w.slice(24,32))
	]
);

console.log(
	(
		await Promise.all(Bun.argv.slice(2).map(async x=>[x,
			x.slice(-4).toLowerCase()=='.csv'&&parse(await Bun.file(x).text())
		]))
	).reduce((a,[i,x])=>(x&&(a[i]=x),a),{})
);

これに先程のCSVを渡した

csvの名前は ( Center, Up, Down, Left, Right, FnLeft, FnRight ) + チャンネル番号

~ % ./parse *
{
  "c1.CSV": [ "41b6", "fd", "02" ],
  "c2.CSV": [ "41b6", "fc", "03" ],
  "d1.CSV": [ "41b6", "dd", "22" ],
  "fl!1.CSV": [ "41b6", "8e", "44" ],
  "fl1.CSV": [ "41b6", "f5", "0a" ],
  "fr!1.CSV": [ "41b6", "65", "9a" ],
  "fr1.CSV": [ "41b6", "8e", "98" ],
  "l1.CSV": [ "41b6", "e5", "1a" ],
  "r1.CSV": [ "41b6", "15", "ea" ],
  "u1.CSV": [ "41b6", "5d", "a2" ],
}

32bitのデータが取れた
一応16,8,8bitで区切ったが、反転部分の8bitがデータ本体の拡張になっているボタンがちらほら

AVRに実装

ボタン電池(CR2032)、ATtiny202、IRLED、タクトスイッチ、念の為のFET(BSS138) だけでなんとかする

PWM( f=38k d=1/3 ), LOW()

まず38kHzのPWMを出せないとHIGHの表現すらできない

PWMにはTCA0を使う

クロック周波数の計算

ここで省電力化のためになるべくcpuのクロックを下げたい

クロックは20MHzか16MHz、
クロックの分周は2 4 8 16 32 64 6 10 12 24 48のいずれか、
TCA0のTOP値が適当なuint8_tで

(20M 16M)/(2 4 8 16 32 64 6 10 12 24 48)/(適当なuint8_t)≒38k
を作る必要がある
自由度の低い部分から計算して素因数分解をして様子を見る

20M/38k≒526=2*263
16M/38k≒421

突然素数で困惑
値を少し上下して素因数分解しやすい値を探すと

16M/420=38.095k

420=2*2*3*5*7=12*35

おそらく正しい探し方は

38k=2*19*1k
16M=20*20*40k
   =(19*21+1)*40k
16M/38k≒19*21*40k/(2*19*1k)
       =21*40/2
       =3*7*2*2*5
       =12*35

以上より12分周、TOP値は34(35回で1周期なので-1)とする
16MHzが扱いやすいというのはこういうことかと実感した

ところでもっと大胆に

20M/512=39.063k

512=2**8

というのも完成したあとに試したが、64分周だとcpuの処理が間に合わず、32分周だとなんとか動くがシーリングライトの反応が鈍かった

実装

PA7、GND間に繋がれたタクトスイッチを押している間38kHzを出すサンプル
押していない間はスリープして電力消費を抑える
CMP2を使ったがこちらは前のプロジェクトの使い回しで特に理由はない
CMPnBUFを使うことで突然のCMP値変更によるduty比や周波数の乱れを防ぐことができる

src/main.c
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

// 割り込みを有効にしてスリープ
static void sleep(){sei();SLPCTRL.CTRLA=SLPCTRL_SMODE_PDOWN_gc|SLPCTRL_SEN_bm;sleep_cpu();cli();}

// 割り込みリセット
ISR(PORTA_PORT_vect){PORTA.INTFLAGS=PORT_INT7_bm;} 

void main(){
    // クロックを 16M/12=1333k Hz に
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB,CLKCTRL_PDIV_12X_gc|CLKCTRL_PEN_bm);

    // TCA0 IR 1333kHz/35=38.095kHz
    // TCA0有効化
	TCA0.SINGLE.CTRLA=TCA_SINGLE_ENABLE_bm;
    // CMP2有効化 単傾斜PWM
	TCA0.SINGLE.CTRLB=TCA_SINGLE_CMP2EN_bm|TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
	TCA0.SINGLE.PER=34;// TOP 35-1

    // PA2を出力に
    PORTA.DIRSET=0b100 

    // PA7をプルアップ付き入力に スリープ復帰用の割り込みもつける
    PORTA.PIN7CTRL=PORT_PULLUPEN_bm|PORT_ISC_LEVEL_gc;
    
    while(1){
		sleep();
        TCA0.SINGLE.CMP2BUF=11 // 35/3-1
		while(~VPORTA.IN&(1<<7));
        TCA0.SINGLE.CMP2BUF=0
	}
}
platformio.ini
[env:t202]
platform = atmelmegaavr
targets = upload
board_build.f_cpu = 1333333L
board_hardware.bod = 0
upload_protocol = serialupdi
upload_flags = -xrtsdtr=high
board = ATtiny202

pio run -t fusesでヒューズ書き換え、pio runでプログラム書き込み
ヒューズを書き込まないとRC発振器が20MHzで動いて指定した周波数で動作しないので注意

T=562us

正確な周期で何かを実行したい場合に<avr/delay.h>を使うのはあまり良くない
命令A,B,C,Dがあってwhile(1){A;B;C;D;}、かつDがdelayの場合、指定するべき値はABCの実行時間を考慮する必要がある
タイマーを使うことでこれを回避する
TCBが余っているのでこれをdelayに用いる

//...

void wait(){while(!(TCB0.INTFLAGS&TCB_CAPT_bm));TCB0.INTFLAGS=1;}

//...
void main(){
//...

    // TCB0 delay 1333kHz/749=1780.151Hz 
    TCB0.CTRLA=TCB_ENABLE_bm;
    TCB0.CCMP=748;// TOP 749-1

//...
}

タイマーはcpuとは関係なく動くのでwhile(1){A;B;C;wait();}でABCの実行がなされている間もタイマーは進む
結果、wait()~wait()間の処理時間がタイマーの1周期より短い場合はタイマーの周期がそのままdelayとして機能する

ところで562us1/562u=1779.36もあまりに中途半端な数字なのでもしかすると1780Hzが先に決まっている?

完成

Lチカの延長線といった感じ
動作確認用にLEDをつけたり、ボタンをもう一つつけたり

src/main.c
/*
IR: PA2
LED: PA3
SW_A: PA7
SW_B: PA1
*/
#define CODE_A 0x41b6fd02
#define CODE_B 0x41b68e44

#define IR_HZ 38000
#define T 562
#define LEADER_ON 16
#define LEADER_OFF 8


#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

#define IR_TOP (F_CPU+IR_HZ/2)/IR_HZ
#define IR_ON TCA0.SINGLE.CMP2BUF=(IR_TOP+3/2)/3-1// duty=1/3
#define IR_OFF TCA0.SINGLE.CMP2BUF=0
#define LED_ON PORTA.OUTSET=1<<3
#define LED_OFF PORTA.OUTCLR=1<<3
#define FOR(X) for(uint8_t i=0;i<X;++i)
void wait(){while(!(TCB0.INTFLAGS&TCB_CAPT_bm));TCB0.INTFLAGS=1;}// TCB0 T us

static void sleep(){sei();SLPCTRL.CTRLA=SLPCTRL_SMODE_PDOWN_gc|SLPCTRL_SEN_bm;sleep_cpu();cli();}
ISR(PORTA_PORT_vect){PORTA.INTFLAGS=PORT_INT7_bm|PORT_INT1_bm;}

static void send(uint32_t x){
	IR_ON;FOR(LEADER_ON)wait();IR_OFF;FOR(LEADER_OFF)wait();
	FOR(32){IR_ON;wait();IR_OFF;wait();if(x>>(31-i)&1)FOR(2)wait();}
	IR_ON;wait();IR_OFF;
}

void main(){
	// TCA0 IR 1333kHz/35=38.095kHz
	TCA0.SINGLE.CTRLA=TCA_SINGLE_ENABLE_bm;
	TCA0.SINGLE.CTRLB=TCA_SINGLE_CMP2EN_bm|TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
	TCA0.SINGLE.PER=IR_TOP-1;// TOP 35-1

	// TCB0 delay 1333kHz/749=1780.151Hz T us
	TCB0.CTRLA=TCB_ENABLE_bm;
	TCB0.CCMP=(F_CPU*T+500000)/1000000-1;// TOP 749-1

	_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB,CLKCTRL_PDIV_12X_gc|CLKCTRL_PEN_bm);// CLR_PER=16M/12=1333k Hz

	PORTA.DIRSET=0b1100;// out: PA2,3
	PORTA.PIN1CTRL=PORT_PULLUPEN_bm|PORT_ISC_LEVEL_gc;// BOTHEDGES|LEVEL
	PORTA.PIN7CTRL=PORT_PULLUPEN_bm|PORT_ISC_LEVEL_gc;// BOTHEDGES|LEVEL

	LED_ON;FOR(255)wait();LED_OFF;

	while(1){
		sleep();
		if(~VPORTA.IN&(1<<7))send(CODE_A);
		if(~VPORTA.IN&(1<<1))send(CODE_B);
		LED_ON;wait();LED_OFF;
		while(~VPORTA.IN&(1<<7|1<<1))FOR(18)wait();// about 10ms
	}
}
 re0212 % pio run                                
Processing t202 (board: ATtiny202; platform: atmelmegaavr)
----------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelmegaavr/ATtiny202.html
PLATFORM: Atmel megaAVR (1.9.0) > ATtiny202
HARDWARE: ATTINY202 1MHz, 128B RAM, 2KB Flash
PACKAGES: 
 - tool-avrdude @ 1.70100.0 (7.1.0) 
 - toolchain-atmelavr @ 1.70300.191015 (7.3.0)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 0 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Compiling .pio/build/t202/src/main.o
Linking .pio/build/t202/firmware.elf
Checking size .pio/build/t202/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [          ]   0.0% (used 0 bytes from 128 bytes)
Flash: [==        ]  22.0% (used 450 bytes from 2048 bytes)
Building .pio/build/t202/firmware.hex
Configuring upload protocol...
AVAILABLE: serialupdi
CURRENT: upload_protocol = serialupdi
Looking for upload port...
Auto-detected: /dev/ttyUSB0
Uploading .pio/build/t202/firmware.hex

avrdude warning: forcing serial DTR/RTS handshake lines HIGH
avrdude: AVR device initialized and ready to accept instructions
avrdude: device signature = 0x1e9123 (probably t202)
avrdude: Note: flash memory has been specified, an erase cycle will be performed.
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file .pio/build/t202/firmware.hex for flash
         with 450 bytes in 1 section within [0, 0x1c1]
         using 8 pages and 62 pad bytes
avrdude: writing 450 bytes flash ...

Writing | ################################################## | 100% 0.28s

avrdude: 450 bytes of flash written
avrdude: verifying flash memory against .pio/build/t202/firmware.hex

Reading | ################################################## | 100% 0.12s

avrdude: 450 bytes of flash verified
avrdude warning: releasing DTR/RTS handshake lines

avrdude done.  Thank you.

================================ [SUCCESS] Took 1.27 seconds ================================

Environment    Status    Duration
-------------  --------  ------------
t202           SUCCESS   00:00:01.266
================================ 1 succeeded in 00:00:01.266 ================================

ユニバーサル基板に実装した

コードはこちら

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?