ESP8266への気付き [ESP32]
ESP8266のIO0って、出力設定しないと暴れているよね!なんで???
Deep Sleep!
ESP.deepSleep( 100 * 1000 * 1000UL , WAKE_RF_DEFAULT );
で省電力に入った場合、指定した時間の間だけ省電力で、その後タイムアウトすると8mAくらい消費している感じ。
Light Sleep!
light sleepモードは1mA程度に抑えられるのは助かる。んが、結構手続きが面倒かも。あと起床する手段が必要だが外部割込みが使える。以下のサイトがよく判る。
http://okiraku-camera.tokyo/blog/?p=4996
以下のリンク先のPDFのサンプルをそのまま使っても、ただしくlight sleep modeに入らないのがムカつくよね。タイムアウトで抜ける処理をやりたいのに、、、
https://www.espressif.com/sites/default/files/documentation/2c-esp8266_non_os_sdk_api_reference_en.pdf
http://okiraku-camera.tokyo/blog/?p=4996
上記リンクのサンプルを若干アレンジして試してみたのだが、かなりの頻度でlight sleepから抜けてしまう現象が発生していた。またコールバックルーチンがいきなり呼ばれてしまう、、、
割込み入力端子はプルアップ抵抗とスイッチのみ接続し、GNDに落とすトリガーを入れている。
なぜいきなり省電力から抜けるのか結局判らない。ただし対策としてコールバックルーチンの中で端子状態を確認し、GNDレベルでなければ再度light sleepに入る様に対策して、確実に省電力状態に入る様にした。
以下、サンプルコード
light sleep時のタイマー
うすうすはそうじゃないか?とは思っていたが、しかしwifi_fpm_do_sleepの引数はμs単位の時間を指定できる事もあり、何度かタイムアウトで省電力から抜けないか?と試したが、少なくともESP-WROOM-02ではできなかった。
light sleep時はタイマー自体が止まってしまうようで、他のマイコンでよくある低消費電力モードに入ってもRCオシレータは動いている!という事は無いのかもしれない。
この為、ESP8266単体で省電力に入っていた時間を知る事はできないのかもしれない。復帰した時にNTPなどに接続すればイイのだが、、、イチイチそんな目的の為にNTPに接続するのもねぇ!
※micros()で省電力に入る直前、直後の時間を出力しても、省電力の時間分進んでいる事は無かった。
3.3Vレギュレータ!
ESP8266とは関係無い?が、3.3Vレギュレータはこれを使った。
無負荷時電流が小さいのが助かる。
http://akizukidenshi.com/catalog/g/gI-11299/
TOUT!
TOUTを使って電圧測定をしてみた。その前に、TOUTのADCは10bitのADCであり、入力範囲は0V~1Vであるらしい。
プロットさせてみたのが左のグラフ。簡易的にデータを取った為かどうか判らないが、直線ではない。でも小数点以下1桁くらいの精度の電圧測定なら十分な気もする。これって、内部で補正されているんだっけ?
※Vinは分圧してからTOUTに接続している。
ソフトウエアシリアルのバッファサイズを変更する!
なんであんなケチケチバッファを割り当てているねん?
SoftwareSerial gpsSerial( GPS_TXD, GPS_RXD ); // RX, TX
gpsSerial.begin( 9600, SWSERIAL_8N1, GPS_TXD, GPS_RXD, false , 4096, 4096 );
// 最後から二番目はバッファサイズ、最後は割込み時のバッファサイズ。大きなデータを受信する時は大きめに!
Deep Sleep!
ESP.deepSleep( 100 * 1000 * 1000UL , WAKE_RF_DEFAULT );
で省電力に入った場合、指定した時間の間だけ省電力で、その後タイムアウトすると8mAくらい消費している感じ。
Light Sleep!
light sleepモードは1mA程度に抑えられるのは助かる。んが、結構手続きが面倒かも。あと起床する手段が必要だが外部割込みが使える。以下のサイトがよく判る。
http://okiraku-camera.tokyo/blog/?p=4996
以下のリンク先のPDFのサンプルをそのまま使っても、ただしくlight sleep modeに入らないのがムカつくよね。タイムアウトで抜ける処理をやりたいのに、、、
https://www.espressif.com/sites/default/files/documentation/2c-esp8266_non_os_sdk_api_reference_en.pdf
http://okiraku-camera.tokyo/blog/?p=4996
上記リンクのサンプルを若干アレンジして試してみたのだが、かなりの頻度でlight sleepから抜けてしまう現象が発生していた。またコールバックルーチンがいきなり呼ばれてしまう、、、
割込み入力端子はプルアップ抵抗とスイッチのみ接続し、GNDに落とすトリガーを入れている。
なぜいきなり省電力から抜けるのか結局判らない。ただし対策としてコールバックルーチンの中で端子状態を確認し、GNDレベルでなければ再度light sleepに入る様に対策して、確実に省電力状態に入る様にした。
以下、サンプルコード
/* ESP8266 light sleep test. 気づき 1.ESP8266をlight sleepに入れると、タイマー系は全て停止してしまうので、 sleepに入っていた時間を知る事ができない。 2.wifi再接続時は13秒とか掛かる事が多い。 3.なぜか一発でlight sleepに入らない事も多い。 4.上記は、light sleepに入った途端にコールバックルーチンが呼ばれてしまう様で、ポートで端子状態を確認が必要。 5.コールバックルーチンの中でdelayとかyieldとか呼ぶと、コアを吐いて死んでしまう。 参考 http://okiraku-camera.tokyo/blog/?p=4996 */ #include <ESP8266WiFi.h> extern "C" { #include "user_interface.h" #include "gpio.h" } /**********************************************************/ /* defines */ /**********************************************************/ #define ASSOC 0 #define INT_X 2 #define GPS_POWER_PIN 15 #define FPM_SLEEP_MAX_TIME 0xFFFFFFF /**********************************************************/ /* global variables */ /**********************************************************/ bool lightSleepLoop; /**********************************************************/ /* setup. */ /**********************************************************/ void setup() { Serial.begin( 115200 ); delay( 20UL ); Serial.println(); Serial.println( "ESP8266 light sleep test." ); /* GPIOの初期化。消費電力を抑える目的でもある */ pinMode( ASSOC, OUTPUT ); digitalWrite( ASSOC, LOW ); /* active high */ pinMode( INT_X, INPUT_PULLUP ); /* active low input */ pinMode( GPS_POWER_PIN, OUTPUT ); digitalWrite( GPS_POWER_PIN, LOW ); /* active high */ /* enter light sleep mode. */ enterLightSleep( FPM_SLEEP_MAX_TIME ); } /**********************************************************/ /* loop. */ /**********************************************************/ void loop() { delay( 500 ); digitalWrite( ASSOC, (digitalRead( ASSOC ) == LOW) ? HIGH : LOW ); } /**********************************************************/ /* call back routine from wake up. */ /**********************************************************/ void fpm_wakup_cb( void ) { if( digitalRead( INT_X ) == LOW && digitalRead( INT_X ) == LOW && digitalRead( INT_X ) == LOW ) lightSleepLoop = false; Serial.write( 'c' ); } /**********************************************************/ /* enter the light sleep mode. */ /**********************************************************/ void enterLightSleep( uint32_t period ) { WiFi.mode( WIFI_OFF ); wifi_set_opmode_current( NULL_MODE ); wifi_fpm_set_sleep_type( LIGHT_SLEEP_T ); wifi_fpm_open(); gpio_pin_wakeup_enable( INT_X, GPIO_PIN_INTR_LOLEVEL ); /* or GPIO_PIN_INTR_HILEVEL */ wifi_fpm_set_wakeup_cb( fpm_wakup_cb ); // Set wakeup callback lightSleepLoop = true; do { wifi_fpm_do_sleep( period ); // sleep until gpio activity. delay( 50UL ); Serial.write( '*' ); } while( lightSleepLoop ); gpio_pin_wakeup_disable(); wifi_fpm_close(); // disable force sleep function }
light sleep時のタイマー
うすうすはそうじゃないか?とは思っていたが、しかしwifi_fpm_do_sleepの引数はμs単位の時間を指定できる事もあり、何度かタイムアウトで省電力から抜けないか?と試したが、少なくともESP-WROOM-02ではできなかった。
light sleep時はタイマー自体が止まってしまうようで、他のマイコンでよくある低消費電力モードに入ってもRCオシレータは動いている!という事は無いのかもしれない。
この為、ESP8266単体で省電力に入っていた時間を知る事はできないのかもしれない。復帰した時にNTPなどに接続すればイイのだが、、、イチイチそんな目的の為にNTPに接続するのもねぇ!
※micros()で省電力に入る直前、直後の時間を出力しても、省電力の時間分進んでいる事は無かった。
3.3Vレギュレータ!
ESP8266とは関係無い?が、3.3Vレギュレータはこれを使った。
無負荷時電流が小さいのが助かる。
http://akizukidenshi.com/catalog/g/gI-11299/
TOUT!
TOUTを使って電圧測定をしてみた。その前に、TOUTのADCは10bitのADCであり、入力範囲は0V~1Vであるらしい。
プロットさせてみたのが左のグラフ。簡易的にデータを取った為かどうか判らないが、直線ではない。でも小数点以下1桁くらいの精度の電圧測定なら十分な気もする。これって、内部で補正されているんだっけ?
※Vinは分圧してからTOUTに接続している。
ソフトウエアシリアルのバッファサイズを変更する!
なんであんなケチケチバッファを割り当てているねん?
SoftwareSerial gpsSerial( GPS_TXD, GPS_RXD ); // RX, TX
gpsSerial.begin( 9600, SWSERIAL_8N1, GPS_TXD, GPS_RXD, false , 4096, 4096 );
// 最後から二番目はバッファサイズ、最後は割込み時のバッファサイズ。大きなデータを受信する時は大きめに!
Arduinoライブラリでテレビジョンを制御 家庭でできるIoT! 5パルス目 [ESP32]
前回( https://hamayan.blog.ss-blog.jp/2019-12-09-1 )作成したリモコンのコードを受信するプログラムを使って、テレビジョン(AQUOS)の幾つかのリモコンコードをサンプリングしました。その内容を使ってBlynkでリモコンを実現します。
もうお判りでしょうが、AQUOSライブラリを作成するにあたってHeatpumpIR Classを使って赤外線ドライバー部分はお手軽に実現しています。
テレビはNHKとテレ東くらいしか観ないので、Blynkのパネルの一番下に一発選局ボタンを付けておきました(笑)。
もうちょい機能を追加すれば、テレビのリモコン無くしても安心ですね!
aquos.h
aquos.cpp
スケッチの追加部分のみ
もうお判りでしょうが、AQUOSライブラリを作成するにあたってHeatpumpIR Classを使って赤外線ドライバー部分はお手軽に実現しています。
テレビはNHKとテレ東くらいしか観ないので、Blynkのパネルの一番下に一発選局ボタンを付けておきました(笑)。
もうちょい機能を追加すれば、テレビのリモコン無くしても安心ですね!
aquos.h
/****************************************************************************/ /* お家のAQUOSをなんとかするヘッダー */ /* Copyright (C) 2014 hamayan All Rights Reserved. */ /****************************************************************************/ #ifndef aquos_h #define aquos_h #include <Arduino.h> #include <HeatpumpIR.h> extern "C" { // #include <mul_tsk.h> } /****************************************************************************/ /* なにかの定義 */ /****************************************************************************/ #define AEHA_T_PERIOD 425UL // #define AEHA_REPEAT_PERIOD 100UL // typedef struct /*AQUOSのフォーマットはAEHAタイプ*/ { uint8_t customerCode[2]; uint8_t data[4]; } AQUOS_CODE; class aquos : public HeatpumpIR { private: int antenaSelect; int channelSelect; uint32_t startTim; void send( IRSender& IR, const uint8_t *code, size_t size ); public: aquos(); void onOff( IRSender& IR ); void ch( IRSender& IR, int value ); void chUpDown( IRSender& IR, int value ); void volume( IRSender& IR, int value ); void antena( IRSender& IR ); void decide( IRSender& IR ); void back( IRSender& IR ); void program( IRSender& IR ); void recorderList( IRSender& IR ); void selectorSwitch( IRSender& IR, int upDown, int rightLeft ); void favorite( IRSender& IR, int sel ); char type( void ); }; #endif /*aquos_h*/ /****************************************************************************/ /* Copyright (C) 2014 hamayan All Rights Reserved. */ /****************************************************************************/
aquos.cpp
/*********************************************************************************/ /* お家のAQUOSをなんとかするソース */ /* designed by hamayan since 2015/05/28 */ /*********************************************************************************/ #include "aquos.h" #if !defined( _MULTITASK_H_ ) #define dly_tsk(tim) delay(tim) #define rot_rdq() #define loc_cpu() interrupts() #define unl_cpu() noInterrupts() #endif /*_MULTITASK_H_ */ /*************************************************************************/ /* 大域変数宣言 */ /*************************************************************************/ /*************************************************************************/ /* プロトタイプ宣言 */ /*************************************************************************/ /*************************************************************************/ /* インスタンス */ /*************************************************************************/ aquos::aquos() { antenaSelect = 0; channelSelect = 1; } /*************************************************************************/ /* send */ /*************************************************************************/ void aquos::send( IRSender& IR, const uint8_t *code, size_t size ) { uint8_t tempUC[ size ]; memcpy( tempUC, code, size ); // 40 kHz PWM frequency IR.setFrequency( 38 ); //リピートでメソッドが呼ばれた時、強制的に間隔を開ける while( (millis() - startTim) < AEHA_REPEAT_PERIOD ) delay( 10 ); // start time startTim = millis(); // Header IR.mark( 8 * AEHA_T_PERIOD ); //frame ppm on IR.space( 4 * AEHA_T_PERIOD ); //frame ppm off // Data for( unsigned int i = 0; i < size; i++ ) { IR.sendIRbyte( tempUC[i], AEHA_T_PERIOD, AEHA_T_PERIOD, 3 * AEHA_T_PERIOD ); // data,426us,425us,1275us // pulse on period, pulse off period at ZERO, pulse off period at 1 } // End mark IR.mark( AEHA_T_PERIOD ); IR.space( 0 ); } /*************************************************************************/ /* 番組表 */ /*************************************************************************/ static const uint8_t TVPROGRAMLIST[] = {0xaa,0x5a,0x8f,0x12,0x60,0xf2}; void aquos::program( IRSender& IR ) { send( IR, TVPROGRAMLIST, sizeof( TVPROGRAMLIST ) ); } /*************************************************************************/ /* 電源の入り切。だが実際にはテレビがそれに追従してはいない。 */ /*************************************************************************/ static const uint8_t TVOnOff[] = {0xaa,0x5a,0x8f,0x12,0x16,0xd1}; void aquos::onOff( IRSender& IR ) { send( IR, TVOnOff, sizeof( TVOnOff ) ); } /*************************************************************************/ /* 決定ボタン */ /*************************************************************************/ static const uint8_t DECIDE[] = {0xaa,0x5a,0x8f,0x12,0x52,0xd1}; void aquos::decide( IRSender& IR ) { send( IR, DECIDE, sizeof( DECIDE ) ); } /*************************************************************************/ /* 戻るボタン */ /*************************************************************************/ static const uint8_t BACK[] = {0xaa,0x5a,0x8f,0x12,0xe4,0x01}; void aquos::back( IRSender& IR ) { send( IR, BACK, sizeof( BACK ) ); } /*************************************************************************/ /* 録画リスト */ /*************************************************************************/ static const uint8_t RECORDERLIST[] = {0xaa,0x5a,0x8f,0x12,0xad,0x3f}; void aquos::recorderList( IRSender& IR ) { send( IR, RECORDERLIST, sizeof( RECORDERLIST ) ); } /*************************************************************************/ /* 選択用スイッチ */ /*************************************************************************/ static const uint8_t SELECTORSWITCH[][6] = { {0xaa,0x5a,0x8f,0x12,0x57,0x81}, /*up*/ {0xaa,0x5a,0x8f,0x12,0x20,0x81}, /*down*/ {0xaa,0x5a,0x8f,0x12,0xd8,0xf1}, /*right*/ {0xaa,0x5a,0x8f,0x12,0xd7,0x01}, /*left*/ }; void aquos::selectorSwitch( IRSender& IR, int upDown, int rightLeft ) { if( upDown >= 1 ) send( IR, SELECTORSWITCH[0], sizeof( SELECTORSWITCH[0] ) ); else if( upDown <= -1 ) send( IR, SELECTORSWITCH[1], sizeof( SELECTORSWITCH[1] ) ); dly_tsk( 100UL ); if( rightLeft >= 1 ) send( IR, SELECTORSWITCH[2], sizeof( SELECTORSWITCH[2] ) ); else if( rightLeft <= -1 ) send( IR, SELECTORSWITCH[3], sizeof( SELECTORSWITCH[3] ) ); } /*************************************************************************/ /* チャネルの切り替え */ /*************************************************************************/ static const uint8_t TVCH[][6] = { {0xaa,0x5a,0x8f,0x12,0x4e,0x32}, /*ch1*/ {0xaa,0x5a,0x8f,0x12,0x4f,0x22}, /*ch2*/ {0xaa,0x5a,0x8f,0x12,0x50,0xc2}, /*ch3*/ {0xaa,0x5a,0x8f,0x12,0x51,0xd2}, /*ch4*/ {0xaa,0x5a,0x8f,0x12,0x52,0xe2}, /*ch5*/ {0xaa,0x5a,0x8f,0x12,0x53,0xf2}, /*ch6*/ {0xaa,0x5a,0x8f,0x12,0x54,0x82}, /*ch7*/ {0xaa,0x5a,0x8f,0x12,0x55,0x92}, /*ch8*/ {0xaa,0x5a,0x8f,0x12,0x56,0xa2}, /*ch9*/ {0xaa,0x5a,0x8f,0x12,0x57,0xb2}, /*ch10*/ {0xaa,0x5a,0x8f,0x12,0x58,0x42}, /*ch11*/ {0xaa,0x5a,0x8f,0x12,0x59,0x52}, /*ch12*/ }; void aquos::ch( IRSender& IR, int value ) { if( value >= 1 && value <= 12 ) { send( IR, TVCH[value - 1], sizeof( TVCH[0] ) ); channelSelect = value; } } /*************************************************************************/ /* チャネルの上下 */ /*************************************************************************/ static const uint8_t TVSEL[][6] = { {0xaa,0x5a,0x8f,0x12,0x11,0xa1}, /*decide up*/ {0xaa,0x5a,0x8f,0x12,0x12,0x91}, /*decide down*/ }; void aquos::chUpDown( IRSender& IR, int value ) { if( value > 0 ) //up { send( IR, TVSEL[0], sizeof( TVSEL[0] ) ); channelSelect++; } else if( value < 0 ) //down { send( IR, TVSEL[1], sizeof( TVSEL[1] ) ); channelSelect--; } } /*************************************************************************/ /* チューナー(アンテナ)の切り替え */ /*************************************************************************/ static const uint8_t TVANTENA[][6] = { {0xaa,0x5a,0x8f,0x12,0x89,0x82}, /*地デジ*/ {0xaa,0x5a,0x8f,0x12,0x8a,0xb2}, /*BS*/ {0xaa,0x5a,0x8f,0x12,0x8b,0xa2}, /*CS*/ }; void aquos::antena( IRSender& IR ) { if( ++antenaSelect > 1 ) antenaSelect = 0; send( IR, TVANTENA[antenaSelect], sizeof( TVANTENA[0] ) ); } /*************************************************************************/ /* ボリュームの上下 */ /*************************************************************************/ static const uint8_t TVVOLUME[][6] = { {0xaa,0x5a,0x8f,0x12,0x14,0xf1}, /*volume up*/ {0xaa,0x5a,0x8f,0x12,0x15,0xe1}, /*volume down*/ }; void aquos::volume( IRSender& IR, int value ) { if( value < 0 ) { send( IR, TVVOLUME[1], sizeof( TVVOLUME[1] ) ); } else if( value > 0 ) { send( IR, TVVOLUME[0], sizeof( TVVOLUME[0] ) ); } } /*************************************************************************/ /* お気に入り番組登録 */ /*************************************************************************/ void aquos::favorite( IRSender& IR, int value ) { if( value == 0 ) /* nhk sougou. */ { antenaSelect = 0; channelSelect = 1; } else if( value == 1 ) /* nhk bs premium. */ { antenaSelect = 1; channelSelect = 3; } else if( value == 2 ) /* tokyo televison. */ { antenaSelect = 0; channelSelect = 7; } else if( value == 3 ) /* bs tokyo televison. */ { antenaSelect = 1; channelSelect = 7; } else return; send( IR, TVANTENA[antenaSelect], sizeof( TVANTENA[0] ) ); send( IR, TVCH[channelSelect - 1], sizeof( TVCH[0] ) ); } /*************************************************************************/ /* リモコンフォーマットを答える */ /*************************************************************************/ char aquos::type( void ) { return 'A'; /*AEHAのA!*/ } /*********************************************************************************/ /* end of file */ /* designed by hamayan since 2015/05/28 */ /*********************************************************************************/
スケッチの追加部分のみ
aquos *tv = new aquos(); /*************************************************************************/ /* blynk virtual pin 6 function as TV power on or off. */ /*************************************************************************/ BLYNK_WRITE( V6 ) { tv->onOff( irSender ); } /*************************************************************************/ /* blynk virtual pin 7 function as TV program list on or off. */ /*************************************************************************/ BLYNK_WRITE( V7 ) { tv->program( irSender ); } /*************************************************************************/ /* blynk virtual pin 8 function as TV record list on or off. */ /*************************************************************************/ BLYNK_WRITE( V8 ) { tv->recorderList( irSender ); } /*************************************************************************/ /* blynk virtual pin 9 function as TV antena(tuner) change. */ /*************************************************************************/ BLYNK_WRITE( V9 ) { tv->antena( irSender ); } /*************************************************************************/ /* blynk virtual pin 10 function as channel NHK SOUGOU. */ /*************************************************************************/ BLYNK_WRITE( V10 ) { tv->favorite( irSender, 0 ); } /*************************************************************************/ /* blynk virtual pin 11 function as channel NHK BS PREMIUM. */ /*************************************************************************/ BLYNK_WRITE( V11 ) { tv->favorite( irSender, 1 ); } /*************************************************************************/ /* blynk virtual pin 12 function as channel TOKYO TELEVISION. */ /*************************************************************************/ BLYNK_WRITE( V12 ) { tv->favorite( irSender, 2 ); } /*************************************************************************/ /* blynk virtual pin 13 function as channel BS TOKYO TELEVISION. */ /*************************************************************************/ BLYNK_WRITE( V13 ) { tv->favorite( irSender, 3 ); } /*************************************************************************/ /* blynk virtual pin 14 function as cursor move. */ /*************************************************************************/ BLYNK_WRITE( V14 ) { int rightLeft = param[0].asInt(); int upDown = param[1].asInt(); tv->selectorSwitch( irSender, upDown, rightLeft ); } /*************************************************************************/ /* blynk virtual pin 15 function as channel up. */ /*************************************************************************/ BLYNK_WRITE( V15 ) { int channel = param.asInt(); tv->chUpDown( irSender, channel ); } /*************************************************************************/ /* blynk virtual pin 16 function as channel down. */ /*************************************************************************/ BLYNK_WRITE( V16 ) { int channel = param.asInt(); tv->chUpDown( irSender, channel ); } /*************************************************************************/ /* blynk virtual pin 17 function as volume up. */ /*************************************************************************/ BLYNK_WRITE( V17 ) { int volume = param.asInt(); tv->volume( irSender, volume ); } /*************************************************************************/ /* blynk virtual pin 18 function as volume down. */ /*************************************************************************/ BLYNK_WRITE( V18 ) { int volume = param.asInt(); tv->volume( irSender, volume ); } /*************************************************************************/ /* blynk virtual pin 19 function as decide button. */ /*************************************************************************/ BLYNK_WRITE( V19 ) { tv->decide( irSender ); } /*************************************************************************/ /* blynk virtual pin 20 function as back button. */ /*************************************************************************/ BLYNK_WRITE( V20 ) { tv->back( irSender ); }
オーデリック LEDシーリングライト リモコン付き LED一体型 電球色~昼光色 調光・調色タイプ ~12畳 SH8237LDR
- 出版社/メーカー: オーデリック
- メディア: ホーム&キッチン
オーデリック LEDシーリングライト リモコン付き LED一体型 電球色~昼光色 調光・調色タイプ ~14畳 SH8234LDR
- 出版社/メーカー: オーデリック
- メディア: ホーム&キッチン
TDK-Lamda製DC/DC電源 RDS50-11-5の中身 [電子工作]
TDK-Lamda製DC/DC電源 RDS50-11-5がノイズ試験で飛んだ!らしいので、折角なので中身を見てみた。
RDS50-11-5の製品ページ
https://product.tdk.com/ja/search/power/switching-power/dc-dc-converter/info?part_no=RDS50-110-5
一番右のシリコンが付いているモジュールは、同期スイッチのモジュールか?そこそこ大きなヒートシンクが付いていた。
あちこちに貫通ビアを使って、パターンの強化を行っている。
端子台右となりの青いのはヒューズ。DC450V 3.15A仕様。
青いヒューズの下はバリスター。
基板中央と右下のノーマルモードノイズ用のフイルムコンデンサは、ポリミイドテープで結構がっちり保護されている。何対策?
コモンモード用の磁器コンデンサは、入力端子台直後と、スイッチングモジュール直前、スイッチングモジュール直後に入っている。
スイッチングモジュールの左隣の白いのは温度ヒューズだと思う。
温度ヒューズの左隣はコモンモードノイズフィルター。コモンモードノイズフィルターの二次側に逆接防止用のダイオードが入っている。
温度ヒューズの下はFET、おそらく何かあった時に電源を切るのだと思う。
コモンモードノイズフィルターの上のトランスは、フィードバック回路用の電源を作るためのもの。
トランスの左隣は出力センス回路の入力フィルターの様だ。
半田面側はこんな感じ。右側がスイッチング回路。黄土色はフィードバック用のフォトカプラ。
部品面、半田面共にコーティングがされている。基板上の貫通している穴はすべてマスキングしてコーティングの液体が流れないようにした形跡有り。
こんな端子台有るんだ!特注?
基板の下側のシャーシは、全面的にマイラフィルムを貼っている。
RDS50-11-5の製品ページ
https://product.tdk.com/ja/search/power/switching-power/dc-dc-converter/info?part_no=RDS50-110-5
一番右のシリコンが付いているモジュールは、同期スイッチのモジュールか?そこそこ大きなヒートシンクが付いていた。
あちこちに貫通ビアを使って、パターンの強化を行っている。
端子台右となりの青いのはヒューズ。DC450V 3.15A仕様。
青いヒューズの下はバリスター。
基板中央と右下のノーマルモードノイズ用のフイルムコンデンサは、ポリミイドテープで結構がっちり保護されている。何対策?
コモンモード用の磁器コンデンサは、入力端子台直後と、スイッチングモジュール直前、スイッチングモジュール直後に入っている。
スイッチングモジュールの左隣の白いのは温度ヒューズだと思う。
温度ヒューズの左隣はコモンモードノイズフィルター。コモンモードノイズフィルターの二次側に逆接防止用のダイオードが入っている。
温度ヒューズの下はFET、おそらく何かあった時に電源を切るのだと思う。
コモンモードノイズフィルターの上のトランスは、フィードバック回路用の電源を作るためのもの。
トランスの左隣は出力センス回路の入力フィルターの様だ。
半田面側はこんな感じ。右側がスイッチング回路。黄土色はフィードバック用のフォトカプラ。
部品面、半田面共にコーティングがされている。基板上の貫通している穴はすべてマスキングしてコーティングの液体が流れないようにした形跡有り。
こんな端子台有るんだ!特注?
基板の下側のシャーシは、全面的にマイラフィルムを貼っている。
Arduinoで赤外線リモコンコードを読み出す 家庭でできるIoT! 4パルス目 [ESP32]
前回ちらっと書いたリモコンコードの受信です。
秋月電子で売られている赤外線受光素子( http://akizukidenshi.com/catalog/g/gI-04659/ )をデータシート通りに抵抗、コンデンサ等を接続し、OUT端子をESP8266に接続します。
この受光素子はサブキャリアを検出するとLowを出力、サブキャリアが無ければHigh(HI-Z)です。
ESP8266側では端子割込みを使ってLowやHighの時間を測定し、時間をビットに換算します。
比較的簡単な処理ですが、悩むのがフレームの終了検出で、NECフォーマットであればビット数は固定長ですが、AEHAフォーマットの場合はビット数は可変長となるので、どこまで取り込んで良いのか判りません。
ですがフレーム間の間隔は必ず空く仕様なので、時間でフレームの終端を強制的に決めてしまう事としました。
とは言え三菱のエアコンのリモコンコードはデータ部が18byte有り、もし全ビットが1であればフレーム長は250msを超えてしますので、いかがなものか?と思わないでもないですが、、、
まぁリモコンコードを見るだけの処理なので、細かい事は気にしません。
実際にスケッチを組んで読み出したものです。オーデリックのリモコンとアクオスのリモコンを受信しています。
オーデリックは前出の様にNECフォーマットです。アクオスはAEHAフォーマットでした。このリモコンコードはメーカーが仕様で示したものではなく、あくまでも自分で解析したコードである事に十分注意して下さい。仮に他のプログラムでこのコードを送信しても上手くは行かないと思います。学習リモコンの様にする場合は、受信したコードを送信可能なプログラムが別途必要です。なお、前回のプログラムでは使えます。
秋月電子で売られている赤外線受光素子( http://akizukidenshi.com/catalog/g/gI-04659/ )をデータシート通りに抵抗、コンデンサ等を接続し、OUT端子をESP8266に接続します。
この受光素子はサブキャリアを検出するとLowを出力、サブキャリアが無ければHigh(HI-Z)です。
ESP8266側では端子割込みを使ってLowやHighの時間を測定し、時間をビットに換算します。
比較的簡単な処理ですが、悩むのがフレームの終了検出で、NECフォーマットであればビット数は固定長ですが、AEHAフォーマットの場合はビット数は可変長となるので、どこまで取り込んで良いのか判りません。
ですがフレーム間の間隔は必ず空く仕様なので、時間でフレームの終端を強制的に決めてしまう事としました。
とは言え三菱のエアコンのリモコンコードはデータ部が18byte有り、もし全ビットが1であればフレーム長は250msを超えてしますので、いかがなものか?と思わないでもないですが、、、
まぁリモコンコードを見るだけの処理なので、細かい事は気にしません。
実際にスケッチを組んで読み出したものです。オーデリックのリモコンとアクオスのリモコンを受信しています。
オーデリックは前出の様にNECフォーマットです。アクオスはAEHAフォーマットでした。このリモコンコードはメーカーが仕様で示したものではなく、あくまでも自分で解析したコードである事に十分注意して下さい。仮に他のプログラムでこのコードを送信しても上手くは行かないと思います。学習リモコンの様にする場合は、受信したコードを送信可能なプログラムが別途必要です。なお、前回のプログラムでは使えます。
#include <ESP8266WiFi.h> void ICACHE_RAM_ATTR irRemocon_interrupt( void ); #define IR_CONT 12 /* Ir LED ON/OFF */ #define IR_RECV 13 /* Ir receive */ #define Max_IR_Bits_NEC 32 #define Max_IR_Bits_AEHA 256 #define NEC_HEADER_LOW_MIN_PERIOD 462UL #define NEC_HEADER_LOW_MAX_PERIOD 662UL #define AEHA_HEADER_LOW_MIN_PERIOD 350UL #define AEHA_HEADER_LOW_MAX_PERIOD 500UL unsigned long IR_Low_Time; // holds IR bursting time unsigned long IR_High_Time; // holds IR high idling time unsigned long Latest_Time_ms; unsigned short IR_Data[Max_IR_Bits_AEHA] = {0,}; // holds the bit length of each data bit by micro-sec. int IR_State; int IR_Bit_Count; bool IR_Active = false; // when true, the capturing IR data stream is valid char IR_formatType; void setup() { Serial.begin( 115200UL ); Serial.println( "\r\n\r\nIR recieve code print out." ); pinMode( IR_RECV, INPUT_PULLUP ); attachInterrupt( IR_RECV, irRemocon_interrupt, CHANGE ); } void loop() { if( IR_Active ) { if( IR_formatType == 'N' && (millis() - Latest_Time_ms) >= 18UL ) { if( irRemocon_available() >= 4 ) IR_nec(); irRemocon_init(); } else if( IR_formatType == 'A' && (millis() - Latest_Time_ms) >= 10UL ) { if( irRemocon_available() >= 6 ) IR_aeha(); irRemocon_init(); } } } /*************************************************************************/ /* int to hexadecimal */ /*************************************************************************/ char *itoh( char *dst, unsigned int value ) { sprintf( dst, "%02x", value ); return dst; } /*************************************************************************/ /* IR_REMOCON初期化 */ /*************************************************************************/ void irRemocon_init( void ) { IR_Active = false; // when 1, the capturing IR data stream is valid IR_formatType = 0; IR_State = 0; } /*************************************************************************/ /* データ数確認 */ /*************************************************************************/ int irRemocon_available( void ) { if( IR_Active ) return IR_State / 8; return 0; } /*************************************************************************/ /* NECタイプのデータ受信 */ /*************************************************************************/ int irRemocon_rxNEC( byte dst[], int size ) { int index = 0; for( int i = 0; i < Max_IR_Bits_NEC; i++ ) { dst[index] >>= 1; if ( IR_Data[i] > ((NEC_HEADER_LOW_MIN_PERIOD + NEC_HEADER_LOW_MAX_PERIOD) / 2) * 3 ) dst[index] |= 0x80; if( (i % 8) == 7 ) index++; } return index; } /*************************************************************************/ /* AEHAタイプのデータ受信 */ /*************************************************************************/ int irRemocon_rxAEHA( byte dst[], int size ) { volatile int limit = IR_State; int index = 0; for( int i = 0; i < limit; i++ ) { dst[index] >>= 1; if ( IR_Data[i] > ((AEHA_HEADER_LOW_MIN_PERIOD + AEHA_HEADER_LOW_MAX_PERIOD) / 2) * 3 ) dst[index] |= 0x80; if( (i % 8) == 7 ) index++; } return index; } /****************************************************************************/ /* IR RECEIVE for NEC format */ /****************************************************************************/ void IR_nec( void ) { int sz = irRemocon_available(); byte *iRData = new byte[sz]; sz = irRemocon_rxNEC( iRData, sz ); String str = ""; char asc[8]; for(int i = 0; i < sz; i++) { str += itoh(asc,(unsigned int)iRData[i]); str += ","; } delete[] iRData; if( str.length() > 0 ) { Serial.print( "type NEC:sz = " ); Serial.print( sz, DEC ); Serial.print( " code:" ); Serial.println( str ); } } /****************************************************************************/ /* IR RECEIVE for AEHA format */ /****************************************************************************/ void IR_aeha( void ) { int sz = irRemocon_available(); byte *iRData = new byte[sz]; sz = irRemocon_rxAEHA( iRData, sz ); char buf[8]; String str = ""; for(int i = 0; i < sz; i++) { str += itoh(buf,(unsigned int)iRData[i]); str += ","; } delete[] iRData; if( str.length() > 0 ) { Serial.print( "type AEHA: sz=" ); Serial.print( sz, DEC ); Serial.print( " code:" ); Serial.println( str ); } } /*************************************************************************/ /* IR_REMOCON割り込み */ /*************************************************************************/ void ICACHE_RAM_ATTR irRemocon_interrupt( void ) { if( digitalRead( IR_RECV ) == LOW ) // edge is low. { if( IR_Active ) //LOWからLOWまでの時間を計測して配列に保存する { unsigned long period = micros() - IR_Low_Time; if( IR_formatType == 'N' && period >= NEC_HEADER_LOW_MIN_PERIOD * 2 && period <= NEC_HEADER_LOW_MAX_PERIOD * 4 ) { if( IR_State < Max_IR_Bits_NEC ) { IR_Data[IR_State++] = (unsigned short)period; IR_Bit_Count = IR_State; Latest_Time_ms = millis(); } else irRemocon_init(); } else if( IR_formatType == 'A' && period >= AEHA_HEADER_LOW_MIN_PERIOD * 2 && period <= AEHA_HEADER_LOW_MAX_PERIOD * 4 ) { if( IR_State < Max_IR_Bits_AEHA ) { IR_Data[IR_State++] = (unsigned short)period; IR_Bit_Count = IR_State; Latest_Time_ms = millis(); } else irRemocon_init(); } } IR_Low_Time = micros(); } else // edge is high. { unsigned long period = micros() - IR_Low_Time; if( period >= NEC_HEADER_LOW_MIN_PERIOD * 8 ) // NECフォーマット { IR_Active = true; IR_State = 0; IR_High_Time = millis(); //先頭フレームの後半の時間となる IR_formatType = 'N'; Latest_Time_ms = millis(); } else if( period >= AEHA_HEADER_LOW_MIN_PERIOD * 8 ) // 家製協(AEHA)フォーマット { IR_Active = true; IR_State = 0; IR_High_Time = millis(); //先頭フレームの後半の時間となる IR_formatType = 'A'; Latest_Time_ms = millis(); } else {} } }
オーデリック LEDシーリングライト リモコン付き LED一体型 電球色~昼光色 調光・調色タイプ ~12畳 SH8237LDR
- 出版社/メーカー: オーデリック
- メディア: ホーム&キッチン
オーデリック LEDシーリングライト リモコン付き LED一体型 電球色~昼光色 調光・調色タイプ ~14畳 SH8234LDR
- 出版社/メーカー: オーデリック
- メディア: ホーム&キッチン
Arduinoライブラリで天井照明を制御 家庭でできるIoT! 3パルス目 [ESP32]
オーデリック LEDシーリングライト リモコン付き LED一体型 電球色~昼光色 調光・調色タイプ ~12畳 SH8237LDR
- 出版社/メーカー: オーデリック
- メディア: ホーム&キッチン
誰も期待していないでしょうけれど、Blynkで動かす。
今回のネタは天井照明です。家にはLEDタイプのオーデリックの天井照明を全部の部屋に入れました。
アマゾンリンクの前の型ですが、多分リモコン制御に関しては変わらないと思います。
この天井照明は結構コストパフォーマンスに優れていると思います。12畳でリモコン付き、調光付きで実売が1万円切っていますから。家に入れたのは2015年3月ですので、4年以上安定稼働しています。またわざわざIoTにしなくても付属のリモコンに時計が付いていますので、指定時間に照明を消す/点けるができます。
さて、この天井照明をリモコンで制御するためにはリモコンコードを知らなくてはできませんが(※相変わらずリモコンコードの仕様は公開されていない)、、、とは言え秋月電子とかで売られている赤外線受光素子を用意すれば( http://akizukidenshi.com/catalog/g/gI-04659/ )、Arduinoを使って比較的容易にコードを読み取る事ができます。※受光についてはその内やります。
とりあえずこの天井照明はNECフォーマットとなっています。
一般的に家電に使われているリモコンフォーマットは、NECフォーマット、AEHAフォーマット、SONYフォーマットが有ります。これらのフォーマットの詳細については以下のページを読んで下さい。
ChaNさんのリモコンコードの解説ページ
http://elm-chan.org/docs/ir_format.html
ちょっと補足
キャリア:キャリアは使用する赤外線LEDで決まってしまいます。秋月で買える赤外線LED( http://akizukidenshi.com/catalog/g/gI-12612/ )は大概波長940μmです。要らないリモコンを分解して取り出しても構いません。半減角:15°(狭角)~60°(広角)や、LEDに流す電流で使い易さが変わってくるでしょう。
サブキャリア周波数:キャリアをパルス状にして出力する事で、ちまたに溢れている赤外線と、リモコンの赤外線を区別していますが、そのパルスの周波数です。一般的に38kHz前後です。
つまりリモコンコードのビット毎の0または1に対応するパターンに従って赤外線LEDをON/OFFさせれば良いことが判ります。この時ArduinoであればサブキャリアはanalogWrite、パルスの時間はdelayMicrosecondsを使えば実現できます。
/*************************************************************************/ /* NECタイプのデータ送信 */ /*************************************************************************/ void iRRemocon::txNEC(const unsigned char data[], unsigned int sz) { #define T_VALUE 555UL // 562UL #define PPM_ON 128 //170 #define PPM_OFF 255 analogWriteRange( 255 ); analogWriteFreq( 38 * 1000UL ); analogWrite(_IR_CONT,0); //frame ppm on analogWrite(_IR_CONT,PPM_ON); delayMicroseconds(T_VALUE * 16); //16T //frame ppm off analogWrite(_IR_CONT,PPM_OFF); delayMicroseconds(T_VALUE * 8); //8T for(int i = 0; i < sz; i++) { byte temp = data[i]; for(int j = 0; j < 8; j++) { if(temp & 0x01) { //frame ppm on analogWrite(_IR_CONT,PPM_ON); delayMicroseconds(T_VALUE * 1); //1T //frame ppm off analogWrite(_IR_CONT,PPM_OFF); delayMicroseconds(T_VALUE * 3); //3T } else { //frame ppm on analogWrite(_IR_CONT,PPM_ON); delayMicroseconds(T_VALUE * 1); //1T //frame ppm off analogWrite(_IR_CONT,PPM_OFF); delayMicroseconds(T_VALUE * 1); //1T } temp >>= 1; } } //stop bit //frame ppm on analogWrite(_IR_CONT,PPM_ON); delayMicroseconds(T_VALUE * 1); //1T //frame ppm off analogWrite(IR_CONT,0); }
ですがまぁ、折角エアコン制御を実現しているので、そこで使われている赤外線出力のClassを利用する事とします。
Classの使い方を知るために、三菱のエアコンのソースコードを見てみます。三菱のエアコンはAEHAフォーマットです。
MitsubishiHeatpumpIR.cpp
void MitsubishiHeatpumpIR::sendMitsubishi(IRSender& IR, uint8_t powerMode, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature, uint8_t swingV, uint8_t swingH) { 長いので抜粋 // 40 kHz PWM frequency IR.setFrequency(38); // The Mitsubishi data is repeated twice for (int j=0; j<2; j++) { // Header IR.mark(MITSUBISHI_AIRCON1_HDR_MARK); IR.space(MITSUBISHI_AIRCON1_HDR_SPACE); // Data for (unsigned int i=0; i<sizeof(MitsubishiTemplate); i++) { IR.sendIRbyte(MitsubishiTemplate[i], MITSUBISHI_AIRCON1_BIT_MARK, MITSUBISHI_AIRCON1_ZERO_SPACE, MITSUBISHI_AIRCON1_ONE_SPACE); } // Pause between the first and the second data burst // Also modify one byte for the second burst on MSY model. This does not affect the checksum of the second burst if (j == 0) { IR.mark(MITSUBISHI_AIRCON1_BIT_MARK); IR.space(MITSUBISHI_AIRCON1_MSG_SPACE); if (_mitsubishiModel == MITSUBISHI_MSY) { MitsubishiTemplate[14] = 0x24; } } } // End mark IR.mark(MITSUBISHI_AIRCON1_BIT_MARK); IR.space(0); }
MitsubishiHeatpumpIR.h
/* Mitsubishi MSZ FD-25 heatpump control (remote control P/N KM09D 0052376) */ #ifndef MitsubishiHeatpumpIR_h #define MitsubishiHeatpumpIR_h #include "HeatpumpIR.h" 長いので抜粋 // Mitsubishi MSZ FD-25 timing constants (remote control P/N KM09D 0052376) #define MITSUBISHI_AIRCON1_HDR_MARK 3500 #define MITSUBISHI_AIRCON1_HDR_SPACE 1700 #define MITSUBISHI_AIRCON1_BIT_MARK 430 #define MITSUBISHI_AIRCON1_ONE_SPACE 1250 #define MITSUBISHI_AIRCON1_ZERO_SPACE 390 #define MITSUBISHI_AIRCON1_MSG_SPACE 17500 class MitsubishiHeatpumpIR : public HeatpumpIR { protected: // Cannot create generic Mitsubishi heatpump instances MitsubishiHeatpumpIR(); uint8_t _mitsubishiModel; // Tells whether this is FD or EF (or other supported model...) public: void send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd); private: void sendMitsubishi(IRSender& IR, uint8_t powerMode, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature, uint8_t swingVCmd, uint8_t swingHCmd); }; #endif
定義のMITSUBISHI_AIRCON1_HDR_MARKは赤外線フレームのLeader部の8Tに、MITSUBISHI_AIRCON1_HDR_SPACEはLeader部の4Tに相当します。
MITSUBISHI_AIRCON1_BIT_MARKはパルスを出している時間、MITSUBISHI_AIRCON1_ONE_SPACEはビット1のパルスの無い時間、MITSUBISHI_AIRCON1_ZERO_SPACEはビット0のパルスの無い時間に相当します。
ChaNさんの解説と微妙に時間が合わないのは、調整が入っているのかもしれません。
上記を参考にオーデリックのライブラリを作成してみました。現時点ではZIPインストールをせず、プロジェクトディレクトリに保存しているので、includeパスに注意が必要です。
odelic.h
/****************************************************************************/ /* お家のオーデリック製LED照明をなんとかするヘッダー */ /* Copyright (C) 2014 hamayan All Rights Reserved. */ /****************************************************************************/ #ifndef odelic_h #define odelic_h #include <Arduino.h> #include <HeatpumpIR.h> extern "C" { // #include <mul_tsk.h> } /****************************************************************************/ /* なにかの定義 */ /****************************************************************************/ #define NEC_T_PERIOD 562UL // 562us #define NEC_REPEAT_PERIOD 120UL // 108ms #define ODELIC_CHANNEL_1 0 #define ODELIC_CHANNEL_2 1 #define ODELIC_CHANNEL_3 2 typedef struct /*オーデリックのフォーマットはNECタイプ*/ { unsigned char customerCode[2]; unsigned char data[2]; } ODELIC_CEILING_LIGHT_CODE; class odelic : public HeatpumpIR { private: uint8_t channel; uint32_t startTim; void send( IRSender& IR, uint8_t *code ); public: odelic( uint8_t ch = 0 ); void onOff( IRSender& IR, bool onoff ); void coldLight( IRSender& IR, int value ); void warmLight( IRSender& IR, int value ); char type( void ); }; #endif /*odelic_h*/ /****************************************************************************/ /* Copyright (C) 2014 hamayan All Rights Reserved. */ /****************************************************************************/
odelic.cpp
/*************************************************************************/ /* お家のオーデリック製LED照明をなんとかするソース */ /* designed by hamayan since 2015/05/28 */ /*************************************************************************/ #include "odelic.h" /*************************************************************************/ /* 大域変数宣言 */ /*************************************************************************/ /*調光を行う時は連続して2つのコードを送信する。つまり寒色と暖色の両方のLEDを制御している*/ /*また、輝度毎にコードが異なる。それ以外に3byte目と4byte目は反転の関係となる*/ static const unsigned char on[] = { 0x84, 0x51, 0x9a ,0x65 }; /*ch2: 0x84, 0x51, 0x9b,0x64*/ static const unsigned char off[] = { 0x84, 0x51, 0x08 ,0xf7 }; /*ch2: 0x84, 0x51, 0x09,0xf6*/ static const unsigned char cold[][4] = { { 0x84, 0x51, 0x6c ,0x93 }, /*10% ch2: 0x84, 0x51, 0x6d,0x92*/ { 0x84, 0x51, 0x6e ,0x91 }, /*20% ch2: 0x84, 0x51, 0x6f,0x90*/ { 0x84, 0x51, 0x70 ,0x8f }, /*30% ch2: 0x84, 0x51, 0x71,0x8e*/ { 0x84, 0x51, 0x72 ,0x8d }, /*40% ch2: 0x84, 0x51, 0x73,0x8c*/ { 0x84, 0x51, 0x74 ,0x8b }, /*50% ch2: 0x84, 0x51, 0x75,0x8a*/ { 0x84, 0x51, 0x76 ,0x89 }, /*60% ch2: 0x84, 0x51, 0x77,0x88*/ { 0x84, 0x51, 0x78 ,0x87 }, /*70% ch2: 0x84, 0x51, 0x79,0x86*/ { 0x84, 0x51, 0x7a ,0x85 }, /*80% ch2: 0x84, 0x51, 0x7b,0x84*/ { 0x84, 0x51, 0x7c ,0x83 }, /*90% ch2: 0x84, 0x51, 0x7d,0x82*/ { 0x84, 0x51, 0x7e ,0x81 }, /*100% ch2: 0x84, 0x51, 0x7f,0x80*/ }; static const unsigned char warm[][4] = { { 0x84, 0x51, 0x58 ,0xa7 }, /*10% ch2: 0x84, 0x51, 0x59,0xa6*/ { 0x84, 0x51, 0x5a ,0xa5 }, /*20% ch2: 0x84, 0x51, 0x5b,0xa4*/ { 0x84, 0x51, 0x5c ,0xa3 }, /*30% ch2: 0x84, 0x51, 0x5d,0xa2*/ { 0x84, 0x51, 0x5e ,0xa1 }, /*40% ch2: 0x84, 0x51, 0x5f,0xa0*/ { 0x84, 0x51, 0x60 ,0x9f }, /*50% ch2: 0x84, 0x51, 0x61,0x9e*/ { 0x84, 0x51, 0x62 ,0x9d }, /*60% ch2: 0x84, 0x51, 0x63,0x9c*/ { 0x84, 0x51, 0x64 ,0x9b }, /*70% ch2: 0x84, 0x51, 0x65,0x9a*/ { 0x84, 0x51, 0x66 ,0x99 }, /*80% ch2: 0x84, 0x51, 0x67,0x98*/ { 0x84, 0x51, 0x68 ,0x97 }, /*90% ch2: 0x84, 0x51, 0x69,0x96*/ { 0x84, 0x51, 0x6a ,0x95 }, /*100% ch2: 0x84, 0x51, 0x6b,0x94*/ }; /*************************************************************************/ /* プロトタイプ宣言 */ /*************************************************************************/ /*************************************************************************/ /* インスタンス */ /*************************************************************************/ odelic::odelic( uint8_t ch ) { channel = ch; startTim = millis(); } /*************************************************************************/ /* send */ /*************************************************************************/ void odelic::send( IRSender& IR, uint8_t *code ) { uint8_t tempUC[4]; memcpy( tempUC, code, sizeof(tempUC) ); tempUC[2] = tempUC[2] + channel; tempUC[3] = tempUC[3] - channel; // 40 kHz PWM frequency IR.setFrequency( 38 ); //リピートでメソッドが呼ばれた時、強制的に間隔を開ける while( (millis() - startTim) < NEC_REPEAT_PERIOD ) delay( 10 ); // start time startTim = millis(); // Header IR.mark( 16 * NEC_T_PERIOD ); //frame ppm on IR.space( 8 * NEC_T_PERIOD ); //frame ppm off // Data for( unsigned int i = 0; i < sizeof( tempUC ); i++ ) { IR.sendIRbyte( tempUC[i], NEC_T_PERIOD, NEC_T_PERIOD, 3 * NEC_T_PERIOD ); // data,562us,562us,1686us // pulse on period, pulse off period at ZERO, pulse off period at 1 } // End mark IR.mark( NEC_T_PERIOD ); IR.space( 0 ); } /*************************************************************************/ /* 引数valueは2以上で100以下の数字 */ /*************************************************************************/ void odelic::coldLight( IRSender& IR, int value ) { if( value >= 2 && value <= 100 ) { value--; value /= 10; send( IR, (uint8_t *)cold[ value ] ); } } /*************************************************************************/ /* 引数valueは2以上で100以下の数字 */ /*************************************************************************/ void odelic::warmLight( IRSender& IR, int value ) { if( value >= 2 && value <= 100 ) { value--; value /= 10; send( IR, (uint8_t *)warm[ value ] ); } } /*************************************************************************/ /* 引数valueは0または1またはそれ以上で100以下の数字 */ /*************************************************************************/ void odelic::onOff( IRSender& IR, bool onoff ) { if( onoff ) send( IR, (uint8_t *)on ); else send( IR, (uint8_t *)off ); } /*************************************************************************/ /* リモコンフォーマットを答える */ /*************************************************************************/ char odelic::type( void ) { return 'N'; /*NECのN!*/ } /*************************************************************************/ /* end of file */ /* designed by hamayan since 2015/05/28 */ /*************************************************************************/
スケッチ側はこんなんなります。
#include "odelic.h" #define IR_CONT 12 // Ir control IRSenderBitBang irSender( IR_CONT ); odelic *room1 = new odelic( ODELIC_CHANNEL_1 ); /*************************************************************************/ /* blynk virtual pin 2 function. */ /*************************************************************************/ BLYNK_WRITE( V2 ) { int value = param.asInt(); if( value ) { room1->onOff( irSender, true ); Blynk.virtualWrite( V4, 100 ); Blynk.virtualWrite( V5, 100 ); } else { room1->onOff( irSender, false ); Blynk.virtualWrite( V4, 0 ); Blynk.virtualWrite( V5, 0 ); } } /*************************************************************************/ /* blynk virtual pin 3 function. */ /*************************************************************************/ BLYNK_WRITE( V3 ) { room1->coldLight( irSender, 70 ); room1->warmLight( irSender, 70 ); Blynk.virtualWrite( V4, 70 ); Blynk.virtualWrite( V5, 70 ); } /*************************************************************************/ /* blynk virtual pin 4 function. */ /*************************************************************************/ BLYNK_WRITE( V4 ) { int value = param.asInt(); room1->warmLight( irSender, value ); } /*************************************************************************/ /* blynk virtual pin 5 function. */ /*************************************************************************/ BLYNK_WRITE( V5 ) { int value = param.asInt(); room1->coldLight( irSender, value ); }
オーデリックの天井照明は、隣り合う部屋でリモコンを使った時に混信しないように、3チャネルまで設定できます。
オーデリック LEDシーリングライト リモコン付き LED一体型 電球色~昼光色 調光・調色タイプ ~14畳 SH8234LDR
- 出版社/メーカー: オーデリック
- メディア: ホーム&キッチン
Arduinoライブラリでエアコン(HEAT PUMP)を制御 家庭でできるIoT! 2パルス目 [ESP32]
誰も期待していないでしょうけれど、Blynkで動かす。
いろいろな都合でエアコンは三菱に変更している
ChaNさんのリモコンコードの解説ページ、、、httpsではない!
http://elm-chan.org/docs/ir_format.html
いろいろな都合でエアコンは三菱に変更している
/* ダイキン,,,もとい三菱のエアコンの制御サンプル 以下のライブラリを利用する。 Arduinoのライブラリマネージャーからもインストールできる。 https://github.com/ToniA/arduino-heatpumpir */ //#include <DaikinHeatpumpIR.h> #include <MitsubishiHeatpumpIR.h> #include <ESP8266WiFi.h> #include <BlynkSimpleEsp8266.h> /* Blynk library */ #include "authentication.h" /* 認証用ファイル */ /*************************************************************************/ /* defines */ /*************************************************************************/ #define IR_CONT 12 // Ir control /*************************************************************************/ /* instances */ /*************************************************************************/ IRSenderBitBang irSender( IR_CONT ); /* 赤外線送信用インスタンスの生成。引数は赤外線LEDが接続されているピン番号 */ //HeatpumpIR *airCon = new DaikinHeatpumpIR(); /* ダイキンのエアコンのインスタンスの生成 */ HeatpumpIR *airCon = new MitsubishiFDHeatpumpIR(); /* 三菱のエアコンのインスタンス */ IPAddress myIP; // self IP address // Attach virtual serial terminal to Virtual Pin V1 WidgetTerminal terminal(V1); /*************************************************************************/ /* global variables */ /*************************************************************************/ /*wifi AP informations*/ const struct ACCESS_POINT_LIST { const char *ssid; const char *pass; } ap[] = { { SSID_STATION_HOME, PASSWORD_STATION_HOME }, { SSID_STATION, PASSWORD_STATION }, }; char blynkAuth[] = BLYNK_AUTH; /***************************************************************************/ /* associate to wifi AP */ /***************************************************************************/ int associateToAP( const char *id, const char *ps ) { WiFi.begin( id, ps ); int i = 0; while( WiFi.status() != WL_CONNECTED ) /*だいたい8秒から10秒くらい接続に掛かっている。*/ { delay( 100 ); Serial.print( "." ); if(++i >= (15000 / 100)) /*15秒で諦める*/ { return (-1); //end of routine ESP.reset(); //ESP.deepSleep(1 * 1000 * 1000UL, WAKE_RF_DEFAULT ); //old code was WAKE_RF_DISABLED delay( 100 ); } } return i; } /*************************************************************************/ /* setup */ /*************************************************************************/ void setup() { Serial.begin( 115200 ); Serial.println(); pinMode( IR_CONT, OUTPUT ); digitalWrite( IR_CONT, LOW ); /*アクセスポイントへ接続*/ Serial.println( "connect to AP." ); if( associateToAP( ap[ 0 ].ssid, ap[ 0 ].pass ) < 0 ) { /*接続に失敗した時、リストから検索して接続してみる*/ int i; for( i = 0; i < sizeof(ap) / sizeof(ap[0]); i++ ) { int ret = associateToAP( ap[i].ssid, ap[i].pass ); if( ret >= 0 ) { Serial.print( "\r\n AP connect time = " ); Serial.print( ret * 100, DEC ); Serial.println( "ms" ); break; } } if( i == sizeof(ap) / sizeof(ap[0]) ) /*接続に失敗した場合*/ { ESP.reset(); //ESP.deepSleep( 1 * 1000 * 1000UL, WAKE_RF_DEFAULT ); //old code was WAKE_RF_DISABLED delay(100); } } myIP = WiFi.localIP(); Serial.print( "\r\nIP number assigned by DHCP is " ); Serial.println( myIP ); /* initialize blynk. */ // Blynk.begin( blynkAuth, ssid, pass ); Blynk.config( blynkAuth ); } /*************************************************************************/ /* loop */ /*************************************************************************/ void loop() { Blynk.run(); /* executing blynk. */ } /*************************************************************************/ /* blynk virtual pin 0 function. */ /*************************************************************************/ BLYNK_WRITE( V0 ) { int value = param.asInt(); if( value ) { /* ir instance,power,mode,fan,temperature,swing vertical,swing horizontal */ airCon->send( irSender, POWER_ON, MODE_AUTO, FAN_AUTO, 24, VDIR_DOWN, HDIR_AUTO ); /* power on */ terminal.println( "power on." ); } else { /* ir instance,power,mode,fan,temperature,swing vertical,swing horizontal */ airCon->send( irSender, POWER_OFF, MODE_AUTO, FAN_AUTO, 24, VDIR_DOWN, HDIR_AUTO ); /* power off */ terminal.println( "power off." ); } terminal.flush(); }
ChaNさんのリモコンコードの解説ページ、、、httpsではない!
http://elm-chan.org/docs/ir_format.html
オーデリック LEDシーリングライト リモコン付き LED一体型 電球色~昼光色 調光・調色タイプ ~12畳 SH8237LDR
- 出版社/メーカー: オーデリック
- メディア: ホーム&キッチン
オーデリック LEDシーリングライト リモコン付き LED一体型 電球色~昼光色 調光・調色タイプ ~14畳 SH8234LDR
- 出版社/メーカー: オーデリック
- メディア: ホーム&キッチン
Arduinoライブラリでエアコン(HEAT PUMP)を制御 家庭でできるIoT! 1パルス目 [ESP32]
寒くなってきたので、エアコンを制御してみましょう!と言う話です。
カテゴリはESP32ですが、使っているのはESP8266です。あまり変わりませんが!
さて、とっくに寒くなってるじゃんね~!って感じですが、昔からやっているネタの再発掘。
エアコン、業務用とかではなければ赤外線リモコンが付属していると思います。
このエアコンの赤外線制御は結構面倒くさい感じでして、
1.メーカーがリモコンコードの仕様を出さない
2.コード長が100bitを超える
など自分の家のエアコンのコードを解析するのも一苦労です。
んが、世の中には地道にコード解析、ライブライを提供してくれる人もおるんですね!
ここで公開されているライブラリを利用しています。
https://github.com/ToniA/arduino-heatpumpir
HEATPUMPで検索すれば、Arduino IDEからライブラリ管理でインストールできます。
また、ライブラリ管理からIRremoteESP8266もインストールしておきます。多分、、、
ソースコードが有るので、Arduino以外の環境への移植も可能な気がします。
ダイキンのエアコンの電源入り切りサンプルです。
ESP8266の12ピンに赤外線LEDを接続しました。12ピン側がアノード、カソード側は100Ωの抵抗を通してGNDに接続しています。
赤外線が出ているかどうかは見れば、、、いえ目で見ても判らないので、スマフォのカメラを通してみればなんとなく判りますね。
/* ダイキンのエアコンの制御サンプル 以下のライブラリを利用する。 Arduinoのライブラリマネージャーからもインストールできる。 https://github.com/ToniA/arduino-heatpumpir */ #include <DaikinHeatpumpIR.h> /*************************************************************************/ /* defines */ /*************************************************************************/ #define IR_CONT 12 // Ir control /*************************************************************************/ /* instances */ /*************************************************************************/ IRSenderBitBang irSender( IR_CONT ); /* 赤外線送信用インスタンスの生成。引数は赤外線LEDが接続されているピン番号 */ HeatpumpIR *airCon = new DaikinHeatpumpIR(); /* ダイキンのエアコンのインスタンスの生成 */ /*************************************************************************/ /* setup */ /*************************************************************************/ void setup() { Serial.begin( 115200 ); pinMode( IR_CONT, OUTPUT ); digitalWrite( IR_CONT, LOW ); while ( 1 ) { /* ir instance,power,mode,fan,temperature,swing vertical,swing horizontal */ airCon->send( irSender, POWER_ON, MODE_HEAT, FAN_3, 24, VDIR_DOWN, HDIR_AUTO ); /* power on */ delay( 30 * 1000UL ); airCon->send( irSender, POWER_OFF, MODE_HEAT, FAN_3, 24, VDIR_DOWN, HDIR_AUTO ); /* power off */ break; delay( 10 * 1000UL ); } } /*************************************************************************/ /* loop */ /*************************************************************************/ void loop() { }
ライブラリを組み込んでみればエアコンの制御は非常に簡単だと思います。つくづくライブラリ提供者に感謝です。
自分の家のエアコンにどのリモコンコードを適用すればイイのか?は、ライブラリの作例にsimpleが有り、これを実行すれば判ります。
ライブラリの使い方が判ってしまえば、これをBlynkで制御!とか余裕ですね!