M5Stick-CにはIR出力が用意されている。なんていうか、あるものは使って見たいと思ったりする人がほとんどだと思う。当然、私もその一人である。(笑)
これはもう使ってみるしかないということで、まずは、リモコンデータを収集するためにリモコンデータの受信モジュールから作ることに。データシートに書かれているまんまの回路である。
リモコン受光ユニットは、GP1UXC41QSの他、秋月電子で購入できるOSRB38C9AAやPL-IRM0101などがそのまま使えると思う。
M5Stick-Cはメモリも十分にあるので、全てのリモコン形式に対応可能な方法で実装してみようかなとも思ったが、それでは芸がないし面白くもないので、以前にATtiny85用に作った省メモリなライブラリをM5Stick-CのPWM仕様に書き換えてみた。
オシロで受信データの波形と送信データの波形が同じであることとエアコン操作が実際に可能なことを確認したが、なぜか送信していない時、100nsほどの意図しない出力が連続して出ている。最初はノイズ?かとも思ったが正確に38KHz毎に出ているのでそうではなさそうだ。
調べてみた結果わかったことはledcSetup()で指定したデューティのビット数の範囲内[0 ~ (1 << ビット数)-1]でデューティを指定するとばかり思っていたが、実際には[0 ~ (1 << ビット数)]であるということだった。計算上でも38KHzの1/256が約100nsであることから間違いなさそうだ。この件は多くの人達が勘違いしていると思われる。
ちなみに、IR送信出力は少し弱めのようで、せいぜい3m程度ほどしか届かないようだ。もっと強力なIR送信モジュールでも作ったほうが良いかもしれない。
【8ビットでのledcWrite(デューティ)指定方法】
1 2 3 4 5 6 |
0=0/256 <-- 0% 1=1/256 2=2/256 ... 255=255/256 <-- 約99%。(100%にしたいときに255を指定するのは間違い) 256=256/256 <-- 100% |
※M5Stick-CのIR出力は負論理なので信号出力なしのときは100%にする必要あり。
【エアコン(Panasonic)のリモコンからの受信データの例】
1 2 3 4 5 6 7 8 9 10 |
irScan() = 51 ----------------- IR Data Info ----------------- version 1.0, flags 0 pulse widths [3507][1712][462][408][1277] next frame 1, repeats 0, interval 9947 us, data 64 bits 01000000-00000100-00000111-00100000-00000000-00000000-00000000-01100000 next frame 0, repeats 0, interval 0 us, data 152 bits 01000000-00000100-00000111-00100000-00000000-00011100-00011100-00000001-11110101-10110000-00000000-01110000-00000111-00000000-00000000-10110001-00000110-00111000-10010101 total 51 bytes ------------------------------------------------ |
【サンプル・スケッチ(SHT31用のサンプルに赤外線送受信機能を追加)】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
#include <M5StickC.h> #include "SHT3X.h" #include "IRPlay.h" #include "IRScan.h" #define M5_BUTTON_A M5_BUTTON_HOME #define M5_BUTTON_B M5_BUTTON_RST #define M5_FONT1 2 #define M5_FONT2 4 static IRScan irScan(36); static IRPlay irPlay(M5_IR); static SHT3X<> sht3x; static uint8_t contrast = 9; static int16_t temp_val = 0; static uint16_t humi_val = 0; static uint8_t ir_data[128]; void setup() { // // Setup GPIO // pinMode(M5_BUTTON_A, INPUT); pinMode(M5_BUTTON_B, INPUT); pinMode(M5_LED, OUTPUT); digitalWrite(M5_LED, HIGH); // LOW: ON, HIGH: OFF // // Setup Display // M5.begin(); M5.Axp.ScreenBreath(contrast); M5.Lcd.setRotation(1); M5.Lcd.setCursor(0, 0); M5.Lcd.setTextFont(M5_FONT1); M5.Lcd.println(" [SHT3X]"); M5.Lcd.setTextFont(M5_FONT2); // // Setup SHT3X // sht3x.begin(); // // Setup IR Scan and Play // irScan.begin(); irPlay.begin(); } void loop() { // // Handle SHT3X // switch (sht3x.handle()) { case SHT3X_NO_DEVICE: temp_val = 0; humi_val = 0; break; case SHT3X_READY: temp_val = sht3x.temperatureC(); humi_val = sht3x.humidity(); digitalWrite(M5_LED, !digitalRead(M5_LED)); break; } // // Display Temperature and Humidity // M5.Lcd.setCursor(0, 24); M5.Lcd.printf(" %2.2f C ", temp_val / 16.0); M5.Lcd.setCursor(0, 50); M5.Lcd.printf(" %2.2f %% ", humi_val / 16.0); // // IR Play // if(digitalRead(M5_BUTTON_A) == LOW) { Serial.printf("irPlay() = %d\n", irPlay.play(ir_data)); while(digitalRead(M5_BUTTON_A) == LOW) continue; } // // IR Scan // if(digitalRead(M5_BUTTON_B) == LOW) { int rv = irScan.scan(); Serial.printf("irScan() = %d\n", rv); if (rv > 0) { irScan.save(ir_data); Serial.print(IRData::info(ir_data)); } while(digitalRead(M5_BUTTON_B) == LOW) continue; } delay(10); } |
【学習型赤外線リモコンライブラリ】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
/* * IR Remort Controller Library for ESP32 * * Sasapea's Lab * * 2019-06-19: Version 1.0 * * Memory Layout * * [Header] * +---+---+----------------+--------------+------------------------+ * |'I'|'R'|Version: uint8_t|Flags: uint8_t|Pulse-Table: uint16_t[5]| == 14 bytes * +---+---+----------------+--------------+------------------------+ * * Flags: b0 Sony Frame Type * b1 Leader Less Repeat Frame * * [Frame,...] * +---------------+------------------+--------------------+-----------+ * |Repeat: uint8_t|Interval: uint16_t|Bit-Length: uint16_t|Bit Data...| >= 5 bytes * +---------------+------------------+--------------------+-----------+ * | |<----+---->| * Repeat: b0-b6 Repeat Count | | * b7 Next Frame Exists +-----------------+ * */ #ifndef _IRDATA_H #define _IRDATA_H #include <stdint.h> #include <stdbool.h> #include <EEPROM.h> // // Mark // #define IRDATA_MARK "IR" // // Version // #define IRDATA_VERSION 0x10 // // Flags // #define IRDATA_SONY_FRAME 1 // must be 1 #define IRDATA_LEADERLESS2 2 // Leaderless After 2nd Frame // // Fields // #define IRDATA_LEADER_H 0 #define IRDATA_LEADER_L 1 #define IRDATA_DATA_H 2 #define IRDATA_DATA_L 3 #define IRDATA_DATA_LONG 4 #define IRDATA_MAX_WIDTHS (IRDATA_DATA_LONG + 1) // // Max Scan Buffer Size // #define IRDATA_BUFFER_SIZE 64 class IRData { private: IRData() { } protected: static void eeprom_begin(int addr) { EEPROM.begin(addr + IRDATA_BUFFER_SIZE); } static void eeprom_end(void) { EEPROM.end(); } static uint8_t read(uint8_t *p, bool eeprom) { return eeprom ? EEPROM.read((int)p) : *p; } public: static String info(uint8_t *ir_data, boolean eeprom = false) { String rv; uint8_t h, l, *src, *p; if (eeprom) eeprom_begin((int)ir_data); src = p = ir_data; rv += "----------------- IR Data Info -----------------\n"; // mark boolean mark = (read(p++, eeprom) == IRDATA_MARK[0]); mark = mark && (read(p++, eeprom) == IRDATA_MARK[1]); if (mark) { // version uint8_t ver = read(p++, eeprom); // flags uint8_t flags = read(p++, eeprom); rv += "version "; rv += ver >> 4; rv += '.'; rv += ver & 0x0F; rv += ", flags "; rv += flags; // pulse pattern table rv += "\npulse widths "; for (uint8_t i = 0; i < IRDATA_MAX_WIDTHS; ++i) { h = read(p++, eeprom); l = read(p++, eeprom); rv += '['; rv += makeWord(h, l); rv += ']'; } rv += '\n'; uint8_t next; do { uint8_t repeat = read(p++, eeprom); h = read(p++, eeprom); l = read(p++, eeprom); uint16_t interval = makeWord(h, l); h = read(p++, eeprom); l = read(p++, eeprom); uint16_t bitlen = makeWord(h, l); next = repeat & 0x80; repeat &= 0x7F; rv += "next frame "; rv += next != 0; rv += ", repeats "; rv += repeat; rv += ", interval "; rv += interval; rv += " us, data "; rv += bitlen; rv += " bits\n"; for (uint8_t r = 0; r <= repeat; ++r) { uint8_t b = 0; for (uint16_t i = 0; i < bitlen; ++i) { if ((i & 7) == 0) b = read(p + (i >> 3), eeprom); if (i && ((i & 7) == 0)) rv += '-'; rv += b & (1 << (i & 7)) ? "1" : "0"; } rv += '\n'; } p += (bitlen + 7) >> 3; } while (next); rv += "total "; rv += (int)p - (int)src; rv += " bytes\n------------------------------------------------\n"; } if (eeprom) eeprom_end(); return rv; } }; #endif // _IRDATA_H |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
/* * IR Remort Controller Library for ESP32 * * Sasapea's Lab * * 2019-06-19: Version 1.0 */ #ifndef _IRSCAN_H #define _IRSCAN_H #include <string.h> #include "IRData.h" #define IRSCAN_ERR_TIMEOUT -1 #define IRSCAN_ERR_OVERFLOW -2 #define IRSCAN_TIMEOUT 5000000 #define IRSCAN_2NDF_TIMEOUT 38000 // ref. SONY FORMAT and etc,... #define IRSCAN_TRAILER_TIME 4200 // ref. AEHA FORMAT and etc,... #define IRSCAN_SENSOR_ADJUST 25 // ref. IR SENSOR (SHARP GP1UXC41QS) #define IRSCAN_HEADER_BYTES (4 + (IRDATA_MAX_WIDTHS * sizeof(uint16_t))) #define IRSCAN_FRAME_BYTES 5 #define IRSCAN_BIT_SET(buf, pos) ((buf)[(pos) >> 3] |= (1 << ((pos) & 7))) #define IRSCAN_BIT_CLR(buf, pos) ((buf)[(pos) >> 3] &= ~(1 << ((pos) & 7))) class IRScan { private: uint8_t _ir_pin; uint8_t _length; uint8_t _buffer[IRDATA_BUFFER_SIZE]; public: IRScan(uint8_t ir_pin) : _ir_pin(ir_pin) { } void begin(void) { pinMode(_ir_pin, INPUT); } void end(void) { } int scan(uint8_t adjust = IRSCAN_SENSOR_ADJUST, uint32_t timeout = IRSCAN_TIMEOUT) { uint32_t start = micros(); uint32_t next = start; uint32_t widths[IRDATA_MAX_WIDTHS]; uint16_t counts[IRDATA_MAX_WIDTHS]; uint16_t interval = 0; uint16_t edges = 0; uint16_t bitpos = 0; uint16_t bitlen = (IRDATA_BUFFER_SIZE > (IRSCAN_HEADER_BYTES + IRSCAN_FRAME_BYTES) ? IRDATA_BUFFER_SIZE - (IRSCAN_HEADER_BYTES + IRSCAN_FRAME_BYTES) : 0) << 3; uint8_t *bittbl = _buffer + IRSCAN_HEADER_BYTES + IRSCAN_FRAME_BYTES; uint8_t *bittbl0 = NULL; uint8_t written = 0; uint8_t flags = 0; uint8_t flags0 = 0; uint8_t pinval = digitalRead(_ir_pin); uint8_t index = 0; uint16_t t1 = 0; uint16_t ta = 0; uint16_t tb = 0; while (true) { uint32_t td = micros() - next; if (digitalRead(_ir_pin) != pinval) { if (edges == 0) { memset(widths, 0, sizeof(widths)); memset(counts, 0, sizeof(counts)); interval = (uint16_t)td; bitpos = 0; flags = flags0; } else { if (edges & 1) td -= adjust; ta = (uint16_t)td; if (edges < IRDATA_MAX_WIDTHS) { // Check Leader Less 2nd Frame if ((edges == 1) && bittbl0) { if (ta + (ta >> 1) < makeWord(_buffer[4], _buffer[5])) { flags |= IRDATA_LEADERLESS2; edges += IRDATA_DATA_H; } } index = edges - 1; } else { index = edges & 1 ? IRDATA_DATA_H : IRDATA_DATA_L; if (bitpos == 0) { if (counts[index] >= bitlen) return IRSCAN_ERR_OVERFLOW; tb = widths[index] / counts[index]; if (tb < ta) { t1 = tb + (tb >> 1); if (t1 < ta) { if (edges & 1) flags |= IRDATA_SONY_FRAME; for (uint16_t i = 0; i < counts[index]; i++, bitpos++) IRSCAN_BIT_CLR(bittbl, bitpos); IRSCAN_BIT_SET(bittbl, bitpos); bitpos++; index = IRDATA_DATA_LONG; } } else { t1 = ta + (ta >> 1); if (t1 < tb) { if (edges & 1) flags |= IRDATA_SONY_FRAME; for (uint16_t i = 0; i < counts[index]; i++, bitpos++) IRSCAN_BIT_SET(bittbl, bitpos); IRSCAN_BIT_CLR(bittbl, bitpos); bitpos++; widths[IRDATA_DATA_LONG] = widths[index]; counts[IRDATA_DATA_LONG] = counts[index]; widths[index] = 0; counts[index] = 0; } } } else if ((edges & 1) == (flags & IRDATA_SONY_FRAME)) { if (bitpos >= bitlen) return IRSCAN_ERR_OVERFLOW; if (t1 < ta) { IRSCAN_BIT_SET(bittbl, bitpos); index = IRDATA_DATA_LONG; } else IRSCAN_BIT_CLR(bittbl, bitpos); bitpos++; } } widths[index] += ta; counts[index]++; } pinval = !pinval; next += td; edges++; } else if (edges && !(edges & 1)) { if (td >= IRSCAN_TRAILER_TIME) { if (bitpos) { bool repeat = false; if (bittbl0) { if (bitpos == makeWord(bittbl0[-2], bittbl0[-1])) { uint8_t n = (bitpos >> 3); repeat = (memcmp(bittbl, bittbl0, n) == 0); if (repeat) { if (bitpos & 7) { uint8_t m = ((1 << (bitpos & 7)) - 1); if ((bittbl[n] & m) != (bittbl0[n] & m)) repeat = false; } } } } else { uint8_t *p = _buffer; // Mark *p++ = IRDATA_MARK[0]; *p++ = IRDATA_MARK[1]; // Version *p++ = IRDATA_VERSION; // Flags *p++ = 0; // Pulse Width Table for (uint8_t i = 0; i < IRDATA_MAX_WIDTHS; ++i) { uint16_t t; if (counts[i] > 1) t = (uint16_t)((widths[i] + (counts[i] >> 1)) / counts[i]); else t = (uint16_t)widths[i]; *p++ = highByte(t); *p++ = lowByte(t); } written += IRSCAN_HEADER_BYTES; } // Flags _buffer[3] = flags0 = flags; if (repeat) { // Repeat bittbl0[-5]++; // Interval bittbl0[-4] = highByte(interval); bittbl0[-3] = lowByte(interval); } else { // next frame if (bittbl0) { // Repeat bittbl0[-5] |= 0x80; // Interval bittbl0[-4] = highByte(interval); bittbl0[-3] = lowByte(interval); } bittbl0 = bittbl; // Repeat bittbl[-5] = 0; // Interval bittbl[-4] = 0; bittbl[-3] = 0; // bit length bittbl[-2] = highByte(bitpos); bittbl[-1] = lowByte(bitpos); // bitpos = ((bitpos + 7) >> 3) + IRSCAN_FRAME_BYTES; bittbl += bitpos; written += bitpos; bitpos = bitpos << 3; bitlen -= (bitlen > bitpos ? bitpos : bitlen); timeout = IRSCAN_2NDF_TIMEOUT; } start = next; } next = start; edges = 0; } } else { if (td >= timeout) { if (bittbl0) break; return IRSCAN_ERR_TIMEOUT; } } } return _length = written; } uint8_t save(uint8_t *ir_data, bool eeprom = false) { if (eeprom) { EEPROM.begin(IRDATA_BUFFER_SIZE + (int)ir_data); for (uint8_t i = 0; i < _length; ++i) EEPROM.write((int)ir_data + i, _buffer[i]); EEPROM.end(); } else if (ir_data) memcpy(ir_data, _buffer, _length); return _length; } }; #endif // _IRSCAN_H |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
/* * IR Remort Controller Library for ESP32 * * Sasapea's Lab * * 2019-06-19: Version 1.0 */ #ifndef _IRPLAY_H #define _IRPLAY_H #include "IRData.h" #define IRPLAY_CHANNEL 15 // channel 0-15 #define IRPLAY_FREQUENCY 38000 // 38kHz #define IRPLAY_RESOLUTION 8 // 8bit resolution #define IRPLAY_SUBCARRIER_ON ((1 << IRPLAY_RESOLUTION) * 2 / 3) // 1/3 duty #define IRPLAY_SUBCARRIER_OFF ((1 << IRPLAY_RESOLUTION) * 3 / 3) // 0 duty class IRPlay { private: uint8_t _ir_pin; uint8_t _ir_ch; protected: void eeprom_begin(int addr) { EEPROM.begin(addr + IRDATA_BUFFER_SIZE); } void eeprom_end(void) { EEPROM.end(); } uint8_t read(uint8_t *p, bool eeprom) { return eeprom ? EEPROM.read((int)p) : *p; } void subcarrier(uint32_t t) { ledcWrite(_ir_ch, IRPLAY_SUBCARRIER_ON); delayMicroseconds(t); ledcWrite(_ir_ch, IRPLAY_SUBCARRIER_OFF); } public: IRPlay(uint8_t ir_pin, uint8_t ir_ch = IRPLAY_CHANNEL) : _ir_pin(ir_pin) , _ir_ch(ir_ch) { } void begin(void) { ledcSetup(_ir_ch, IRPLAY_FREQUENCY, IRPLAY_RESOLUTION); ledcWrite(_ir_ch, IRPLAY_SUBCARRIER_OFF); ledcAttachPin(_ir_pin, _ir_ch); } void end(void) { ledcDetachPin(_ir_pin); } int play(uint8_t *ir_data, bool eeprom = false) { uint8_t h, l, *p = ir_data; if (eeprom) eeprom_begin((int)ir_data); // mark bool mark = (read(p++, eeprom) == IRDATA_MARK[0]); mark = mark && (read(p++, eeprom) == IRDATA_MARK[1]); int rv = 0; if (mark) { // version uint8_t ver = read(p++, eeprom); ver = ver; // flags uint8_t flags = read(p++, eeprom); // pulse widths table uint16_t widths[IRDATA_MAX_WIDTHS]; for (uint8_t i = 0; i < IRDATA_MAX_WIDTHS; ++i) { h = read(p++, eeprom); l = read(p++, eeprom); widths[i] = makeWord(h, l); } bool cframe; do { uint8_t repeat= read(p++, eeprom); h = read(p++, eeprom); l = read(p++, eeprom); uint16_t interval = makeWord(h, l); h = read(p++, eeprom); l = read(p++, eeprom); uint16_t bitlen = makeWord(h, l); cframe = repeat & 0x80; repeat &= 0x7F; for (uint8_t r = 0; r <= repeat; ++r) { uint32_t next = micros(); if (!(r && (flags & IRDATA_LEADERLESS2))) { next += widths[IRDATA_LEADER_H]; subcarrier(next - micros()); next += widths[IRDATA_LEADER_L]; delayMicroseconds(next - micros()); } uint8_t b = 0; for (uint16_t i = 0; i < bitlen; ++i) { if ((i & 7) == 0) b = read(p + (i >> 3), eeprom); bool on = b & (1 << (i & 7)); next += widths[on && (flags & IRDATA_SONY_FRAME) ? IRDATA_DATA_LONG : IRDATA_DATA_H]; subcarrier(next - micros()); next += widths[on && !(flags & IRDATA_SONY_FRAME) ? IRDATA_DATA_LONG : IRDATA_DATA_L]; delayMicroseconds(next - micros()); } if (!(flags & IRDATA_SONY_FRAME)) { next += widths[IRDATA_DATA_H]; subcarrier(next - micros()); } delayMicroseconds(interval); } p += (bitlen + 7) >> 3; } while (cframe); rv = (int)p - (int)ir_data; } if (eeprom) eeprom_end(); return rv; } }; #endif // _IRPLAY_H |