最終更新日:2020年4月29日
このプロジェクトの目的は、HomeAutomationの骨格とも云うべき赤外線リモコン制御の集約を実現する仕組みを構築出来ないかという検討です。
コマンドが明確な制御対象であれば、PCからそのターゲットの種別とコマンドをマイコン(STM32duino)に投げかけタイミング信号を用意してIR-LEDからコマンド列を送信してターゲットデバイスを制御します。
PC側のソフトウエアはLabVIEWで構築しコマンド列をDB化して管理し、必要なコマンドをマイコンに送信します。マイコン側でコマンド列をパタン化してキャリア信号に乗せて発信することになります。
マイコン側ではPCからの受信結果をLCDに表示しておきたいです。
この方法であれば、自作のターゲットデバイスを任意に作成することも可能です。
コマンド列においてNECタイプのID=0x00はメーカ不問のユーザ定義のようです。この領域を使用して独自のターゲットに対する赤外線コントロールを実現しているケースもあります。例えばホビーロボットです。コマンド体系が判ればそれらもターゲットとして含める事が可能です。
【ホストアプリケーション】
【ターゲットマシンコード】
STM32MINIShield側のコードを紹介しておきます。Arduino(STM32duino)のスケッチです。
使用しているライブラリは以下の通りです。
AdafrultsのSSD1306ライブラリはそのままではコンパイラが通りません。rogerclark氏のレタッチ版を使用します。https://github.com/rogerclarkmelbourne
/* * 2020/4/29 T.Wanibe 赤外線リモコン用モジュールを利用した通信テスト 送信側 * 受信したコマンドを展開する前に表示出来るようSSD1306に対応した。 * SSD1306はAdafruitのライブラリのままではコンパイル出来ないので、rogerclark氏のレタッチした * ライブラリを使用します。 * シリアル経由で送信出来ることを確認出来たので、NICにも拡張する * LabVIEWからはTCPSocket通信で、Port番号は50005とします。 * 如何に安定したビットパターンを送信出来るのか考えた結果、 * キャリアはタイマ割込で実現すべきだと感じました。 * また、1フレームのONOFFは最初にビットパターンを作成してそのパタンに従って処理すべきだと思いました。 * キャリア周波数がまともで無いとLEDが光りません。そして受信側のLPFも機能しません。 * データパタンですが、一貫したタイミング維持をしないと読み取りミスを起こします。 * と云う事でこのコードを書きました。 * 使い方は コンソール入力でフォーマットは N,200,1,F1,F2,F3,F4 というような感じです。現状Loopは1回のみ * 内部ではTを構成するキャリアパルスのLoopカウントおよびTカウント列で処理します。 * 出力PINはPA9固定です。 * 最大131072バイトのフラッシュメモリのうち、スケッチが74672バイト(56%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が8120バイト(39%)を使っていて、ローカル変数で12360バイト使うことができます。 */ #include <SPI.h> // needed for Arduino versions later than 0018 #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306_STM32.h> #include <Ethernet3.h> #include <EEPROM.h> #include <avr/pgmspace.h> #include <TextFinder.h> //WebSetting #define IR_OUT PA9 #define OUT_CHK PB0 #define SocketPort 50005 #define HttpPort 80 //HTTP #define vers "STM32miniShield_IR" #define debug true #define OKMSG "OK\r\n" #define NGMSG "NG\r\n" //PIN #define W550io_Rst PA8 //NIC_Reset #define SPI1_NSS_PIN PA4 //NIC_ChipSelec #define BUILTIN_LED PC13 #define LED1 PB8 #define LED2 PB9 #define DELIMITER "," #define OLED_RESET 4 byte mac[] = {0x00,0x08,0xDC,0x54,0x4D,0xD5}; //Wiznet byte ip[] = {192, 168, 0, 205}; byte dns_server[] = {192, 168, 0, 1}; byte gateway[] = {0, 0, 0, 0}; byte subnet[] = {255, 255, 255, 0}; char buff1[30] = {'0'}; char buff4[10] = {'0'}; char buff5[20] = {'0'}; int maxBitLength = 2048; bool gBuff[2048] = {'0'}; //512要素でも64byte 2048 256Byte char c[3] = {'0'}; char type = 'N'; int interval = 192; //192T ? 108msec int repeat = 1; int cc = 0; bool gEnable = false; //trueでパタン出力 int gTcount; //単位Tを満たす1/fcカウント int gSetCount; int t,gTf,gLength,LON,LOFF,RON,ROFF; String HTTP_req = ""; // stores the HTTP request bool LANConnect = false; bool alreadyConnected= false; char buffer[100]; // これはすべて切り詰められたHTMLコードです。 エディターでHTMLコードを作成し、バッファに収まるように切り分けます。 // HTMLをすべての場所で切り取ることができますが、\ "部分ではできません。HTML内でsimple"の代わりに\ "を // 使用する必要があります。そうしないと機能しません。 size_t size; char BUF[32]; char rSTR[48]; String EventBUF; //オブジェクトコンストラクタ EthernetServer socketServer(SocketPort); EthernetServer webServer(HttpPort); EthernetClient tcp; EthernetClient web; Adafruit_SSD1306 SSD1306(OLED_RESET); #if (SSD1306_LCDHEIGHT != 64) #error("Height incorrect, please fix Adafruit_SSD1306.h!"); #endif volatile bool EnableLAN = true; const byte ID = 0x95; //---------------------------------- void LANSetup(){ int idcheck = EEPROM.read(0); Serial.print(F("LocalID = 0x"));Serial.println(idcheck,HEX); if (idcheck == ID){ //idがIDと同じ値の場合、 //これは、このスケッチがシールドを設定するために使用されたことを意味します。 //EERPOMの値を読み取ってシールドを設定します。 for (int i = 0; i < 6; i++){ mac[i] = EEPROM.read(i+1); } for (int i = 0; i < 4; i++){ ip[i] = EEPROM.read(i+7); } for (int i = 0; i < 4; i++){ subnet[i] = EEPROM.read(i+11); } for (int i = 0; i < 4; i++){ gateway[i] = EEPROM.read(i+15); } }else{ //idが一致しない場合、初期値を書き込む事にします。 for (int i = 0 ; i < 6; i++){ EEPROM.write(i + 1,mac[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 7, ip[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 11, subnet[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 15, gateway[i]); } // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。 EEPROM.write(0, ID); } Ethernet.begin(mac, ip); // Ethernet.begin(mac); //DHCPの場合 // Ethernet.begin(mac, ip, subnet); //SubnetMaskを意識した場合。 // Ethernet.begin(mac, ip, subnet, gateway); //gatewayを意識した場合。 } //-------------------------- void SetWebPage( EthernetClient client){ client.print(F("<!DOCTYPE HTML PUBLIC \"\"><html><HEAD><META http-equiv=\"Content-Type\" charset=UTF-8\">")); client.print(F("<META http-equiv=\"Content-Style-Type\"><TITLE>")); client.print(vers); client.print(F(" Setup Page</TITLE></HEAD>")); client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\"><BLOCKQUOTE><BLOCKQUOTE>")); client.print(F("<table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\">")); client.print(F("<tr><td> ")); client.print(vers); client.print(F(" Setup Page</td></tr></table><br>")); // client.print(F("<script>function hex2num (s_hex) {eval(\"var n_num=0X\" + s_hex);return n_num;}</script>")); client.print(F("<FORM><input type=\"hidden\" name=\"SBM\" value=\"1\"><table><tr><td>MAC:</td><td>")); client.print(F("<input id=\"T1\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT1\" value=\"")); client.print(mac[0],HEX); client.print(F("\">.<input id=\"T3\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT2\" value=\"")); client.print(mac[1],HEX); client.print(F("\">.<input id=\"T5\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT3\" value=\"")); client.print(mac[2],HEX); client.print(F("\">.<input id=\"T7\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT4\" value=\"")); client.print(mac[3],HEX); client.print(F("\">.<input id=\"T9\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT5\" value=\"")); client.print(mac[4],HEX); client.print(F("\">.<input id=\"T11\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT6\" value=\"")); client.print(mac[5],HEX); // client.print(F("\"><input id=\"T2\" type=\"hidden\" name=\"DT1\"><input id=\"T4\" type=\"hidden\" name=\"DT2")); client.print(F("\"><input id=\"T6\" type=\"hidden\" name=\"DT3\"><input id=\"T8\" type=\"hidden\" name=\"DT4")); client.print(F("\"><input id=\"T10\" type=\"hidden\" name=\"DT5\"><input id=\"T12\" type=\"hidden\" name=\"DT6")); client.print(F("\"></td></tr><tr><td>IP:</td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT7\" value=\"")); client.print(ip[0],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT8\" value=\"")); client.print(ip[1],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT9\" value=\"")); client.print(ip[2],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT10\" value=\"")); client.print(ip[3],DEC); // client.print(F("\"></td></tr><tr><td>MASK: </td><td><input type= \"text\" size=\"3\" maxlength=\"3\" name=\"DT11\" value=\"")); client.print(subnet[0],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT12\" value=\"")); client.print(subnet[1],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT13\" value=\"")); client.print(subnet[2],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT14\" value=\"")); client.print(subnet[3],DEC); // client.print(F("\"></td></tr><tr><td>GW: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT15\" value=\"")); client.print(gateway[0],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT16\" value=\"")); client.print(gateway[1],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT17\" value=\"")); client.print(gateway[2],DEC); client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT18\" value=\"")); client.print(gateway[3],DEC); client.print(F("\"></td></tr><tr><td><br></td></tr><tr><td><input id=\"button1\"type=\"submit\" value=\"SUBMIT\" ")); // client.print(F("Onclick=\"document.getElementById('T2').value ")); client.print(F("= hex2num(document.getElementById('T1').value);")); client.print(F("document.getElementById('T4').value = hex2num(document.getElementById('T3').value);")); client.print(F("document.getElementById('T6').value = hex2num(document.getElementById('T5').value);")); client.print(F("document.getElementById('T8').value = hex2num(document.getElementById('T7').value);")); client.print(F("document.getElementById('T10').value = hex2num(document.getElementById('T9').value);")); client.print(F("document.getElementById('T12').value = hex2num(document.getElementById('T11').value);\"")); // client.print(F("></td><td></td></tr></form></table></BLOCKQUOTE></BLOCKQUOTE></BODY></html>")); } //-------------------------- void checkWebPage( EthernetClient client) { Serial.println(F("new webClient")); if (client) { TextFinder finder(client ); while (client.connected()) { //digitalWrite(LED, HIGH); if (client.available()) { //この部分はすべてのテキスト検索を行います。 if( finder.find("GET /") ) { // 「setup」とい語が見つかった場合は、さらに探してください。 // その単語が見つからない場合は、検索を停止して先に進みます。 // これにより、後でスケッチに独自のWebページを配置できます。 if (finder.findUntil("setup", "\n\r")){ // 「SBM」という単語が見つかった場合は、さらに探し続けます。 // その言葉が見つからない場合は、探して停止します。 // SUBMITボタンが押されていない、何も押されていないことを意味します // セットアップページが構築されている場所に移動し、クライアントのブラウザに表示します。 if (finder.findUntil("SBM", "\n\r")){ byte SET = finder.getValue(); // これで、「DT」という文字を探している間に、「DT」の後ろにあるすべての数字を覚えて、 // 値を一致させて、mac、ip、subnet、およびgatewayに入れる必要があります。 while(finder.findUntil("DT", "\n\r")){ int val = finder.getValue(); // 「DT」のvalが1?6の場合、対応する値はMAC値でなければなりません。 if(val >= 1 && val <= 6) { mac[val - 1] = finder.getValue(); } // 「DT」のvalが7?10の場合、対応する値はIP値でなければなりません。 if(val >= 7 && val <= 10) { ip[val - 7] = finder.getValue(); } // 「DT」のvalが11?14の場合、対応する値はMASK値でなければなりません。 if(val >= 11 && val <= 14) { subnet[val - 11] = finder.getValue(); } // 「DT」のvalが15?18の場合、対応する値はGW値でなければなりません。 if(val >= 15 && val <= 18) { gateway[val - 15] = finder.getValue(); } } // すべてのデータを取得したので、EEPROMに保存できます for (int i = 0 ; i < 6; i++){ EEPROM.write(i + 1,mac[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 7, ip[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 11, subnet[i]); } for (int i = 0 ; i < 4; i++){ EEPROM.write(i + 15, gateway[i]); } // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。 EEPROM.write(0, ID); // すべてのデータがEEPROMに書き込まれている場合、arduinoをリセットする必要があります。 //ハードウェアリセットボタンを使用する必要があります。 } // この時点から、セットアップページの構築を開始し、クライアントのブラウザーに表示できます。 client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); // SetWebPage(client); break; } } client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); // put your own html from here on client.print("IT WORKS: go to "); client.print(ip[0],DEC); for (int i= 1; i < 4; i++){ client.print("."); client.print(ip[i],DEC); } client.print("/setup"); // put your own html until here break; } } //digitalWrite(LED, LOW); delay(1); client.stop(); } } //------------------------38kHz 1/3のキャリアパルス生成 void CarryOut(){ if(gEnable){ if(gBuff[gSetCount]){ GPIOA->regs->BSRR = (1<<9); //On delayMicroseconds(8); GPIOA->regs->BRR = (1<<9); //Off } if(gTcount==0){ gTcount = gTf; if(gSetCount++ > gLength) gEnable = false; }else{ gTcount--; } } } //------------------------送信データ作成 void MakeTransData(char type,int interval,int repeat,char buff4[],int len){ SSD1306.clearDisplay(); SSD1306.setTextSize(1); SSD1306.setCursor(0,0); SSD1306.print(F("\ntype:"));SSD1306.println(type); SSD1306.print(F("interval:"));SSD1306.println(interval); SSD1306.print(F("repeat:"));SSD1306.println(repeat); SSD1306.print(F("Length="));SSD1306.print(len); for (int i = 0; i < len;i++){ SSD1306.print(F(":"));SSD1306.print(buff4[i],HEX); } SSD1306.println(); SSD1306.display(); int bitIndex = 0; switch (type) { case 'A' : // AEHA t = 425; gTf = int(t/26.3); LON = 8; LOFF = 4; RON = 8; ROFF = 8; break; case 'B' : // Bose t = 500; gTf = int(t/26.3); LON = 2; LOFF = 3; break; case 'N' : // NEC default: t = 562; gTf = int(t/26.3); LON = 16; LOFF = 8; RON = 16; ROFF = 4; break; } // Leader for (int i = 0;i< LON; i++) gBuff[bitIndex++] = true; for (int i = 0;i< LOFF;i++) gBuff[bitIndex++] = false; // Data code bit列の処理 for (int i = 0;i<len;i++){ for (int j = 7; j >= 0 ; j--){ if(buff4[i] & (1<<j)){ gBuff[bitIndex++] = true; gBuff[bitIndex++] = false; gBuff[bitIndex++] = false; gBuff[bitIndex++] = false; //1はduty25%回 }else{ gBuff[bitIndex++] = true; gBuff[bitIndex++] = false; //0はduty50%回 } } } // Stop bit列の処理 gBuff[bitIndex++] = true; gBuff[bitIndex++] = false; if(repeat>1){ for (int jj = 0;jj< (interval - bitIndex);jj++) gBuff[bitIndex++] = false; for (int i = 0;i< RON; i++) gBuff[bitIndex++] = true; for (int i = 0;i< ROFF;i++) gBuff[bitIndex++] = false; gBuff[bitIndex++] = true; } for(int ii = 0; ii < (repeat -2);ii++){ for (int jj = 0;jj< (interval-RON-ROFF);jj++) gBuff[bitIndex++] = false; for (int i = 0;i< RON; i++) gBuff[bitIndex++] = true; for (int i = 0;i< ROFF;i++) gBuff[bitIndex++] = false; gBuff[bitIndex++] = true; } gSetCount = 0; gTcount = gTf; gLength = bitIndex; if(gLength < maxBitLength) gEnable = true; } //------------------------ void setup() { Serial.begin(115200); delay(1000); Serial.print(F("Start\n")); pinMode(IR_OUT,OUTPUT); pinMode(OUT_CHK,OUTPUT); digitalWrite(IR_OUT,LOW); digitalWrite(OUT_CHK,HIGH); pinMode(BUILTIN_LED,OUTPUT); pinMode(LED1,OUTPUT);digitalWrite(LED1, LOW); pinMode(LED2,OUTPUT);digitalWrite(LED2, LOW); SSD1306.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128x64) // Clear the buffer. SSD1306.clearDisplay(); // text display tests SSD1306.setTextSize(2); SSD1306.setTextColor(WHITE); SSD1306.setCursor(0,0); SSD1306.println("Hello"); SSD1306.display(); // start Ethernet and UDP:(Ethernet3API) Ethernet.setCsPin(SPI1_NSS_PIN); Ethernet.setRstPin(W550io_Rst); // pinMode(W550io_Rst, OUTPUT); digitalWrite(W550io_Rst, LOW); delay(10); digitalWrite(W550io_Rst, HIGH); Serial.print(F("NIC_Reset\n")); LANSetup(); Serial.println(F("Ethernet interface started")); Serial.print(F("server is at "));Serial.println(Ethernet.localIP()); webServer.begin(); // Timer1.pause(); // Timer1.setPeriod(26); // 26uSEC ? 1/38kHz/ 40Khzの時は25に変更 Timer1.attachInterrupt(TIMER_UPDATE_INTERRUPT,CarryOut); //割り込みサーブルーチンの宣言:CarryOut Timer1.resume(); Serial.println(F("Ready to trans")); } //------------------------ void loop() { char *endp; bool hasData = false; int index = 0; while (Serial.available() > 0) { hasData = true; if (Serial.available() > 0) { char d = Serial.read(); buff1[cc++] = d; if( d == '\n'){ digitalWrite(LED1, HIGH); //Serial.println(buff1); sscanf(buff1,"%c,%d,%d,%s",&type,&interval,&repeat,buff5); //Serial.print(F("buff5:"));Serial.println(buff5); int strLen = strlen(buff5); int LEN = (strLen+1) /3; //Serial.print(F("size:"));Serial.println(strLen); int j = 0; int k = 0; for(int i=0;i<strLen;i++){ if(buff5[i] == ','){ c[k++] = '\0'; //Serial.print(c); sscanf(c,"%x",&buff4[j++]); k = 0; }else{ c[k++] = buff5[i]; //Serial.print(c); } } sscanf(c,"%x",&buff4[j++]); MakeTransData(type,interval,repeat,buff4,j); Serial.print(OKMSG); cc = 0; buff1[0] = '\0'; digitalWrite(LED1, LOW); } } } long int StartTCP = micros(); LANConnect = true; delay(1); tcp = socketServer.available(); if (tcp) { if (!alreadyConnected) { tcp.flush(); // clear out the input buffer: alreadyConnected = true; } while ((size = tcp.available()) > 0) { //受信処理 char* msg = (char*)malloc(size); delay(1); digitalWrite(LED2, HIGH); size = tcp.read((unsigned char*)msg, size); Serial.print(F("PacketSize = ")); Serial.println(size); sscanf(msg,"%c,%d,%d,%s",&type,&interval,&repeat,buff5); int strLen = strlen(buff5); int LEN = (strLen+1) /3; //Serial.print(F("size:"));Serial.println(strLen); int j = 0; int k = 0; for(int i=0;i<strLen;i++){ if(buff5[i] == ','){ c[k++] = '\0'; sscanf(c,"%x",&buff4[j++]); k = 0; }else{ c[k++] = buff5[i]; } } sscanf(c,"%x",&buff4[j++]); MakeTransData(type,interval,repeat,buff4,j); Serial.print(OKMSG); tcp.write(OKMSG); cc = 0; buff1[0] = '\0'; digitalWrite(LED2, LOW); free(msg); } } web = webServer.available(); if(web) checkWebPage(web); web.stop(); // コネクションを閉じる。 LANConnect = false; delay(100); }SlaveデバイスのIPアドレスやMACアドレスはBluePill内に書き込んでいます。Webブラウザでアクセスして変更が可能です。
オブジェクト自体はベクター殿のストレージをお借りしています。
https://www.vector.co.jp/soft/winnt/hardware/se521019.html 出来る事は、
- LabVIEW2014開発環境(32bit)環境で独自のアプリケーション開発が出来ます。
- テンプレート環境にて動作確認が行えるため、いろいろなターゲットデバイス用に変更可能です。
免責事項
本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。本ソフトウエアの著作権はToolsBoxに帰属します。
本ソフトウエアをご利用の結果生じた損害について、ToolsBoxは一切責任を負いません。
ToolsBoxはコンテンツとして提供する全ての文章、画像等について、内容の合法性・正確性・安全性等、において最善の注意をし、作成していますが、保証するものではありません。
ToolsBoxはリンクをしている外部サイトについては、何ら保証しません。
ToolsBoxは事前の予告無く、本ソフトウエアの開発・提供を中止する可能性があります。
商標・登録商標
Microsoft、Windows、WindowsNTは米国Microsoft Corporationの米国およびその他の国における登録商標です。
Windows Vista、Windows XPは、米国Microsoft Corporation.の商品名称です。
LabVIEW、National Instruments、NI、ni.comはNational Instrumentsの登録商標です。
I2Cは、NXP Semiconductors社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。