├── extras ├── schematic.png └── softmodem.jpg ├── library.properties ├── examples └── SerialBridge │ └── SerialBridge.ino ├── .gitignore ├── keywords.txt ├── LICENSE ├── SoftModem.h ├── README.md └── SoftModem.cpp /extras/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arms22/SoftModem/HEAD/extras/schematic.png -------------------------------------------------------------------------------- /extras/softmodem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arms22/SoftModem/HEAD/extras/softmodem.jpg -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SoftModem 2 | version=1.0 3 | author=arms22 4 | maintainer=arms22 5 | sentence=Audio Jack Modem Library for Arduino. 6 | paragraph=Generates a FSK-modulated signal that can be fed into an audio jack for data transmission. 7 | category=Communication 8 | url=https://github.com/arms22/SoftModem 9 | architectures=avr 10 | includes=SoftModem.h -------------------------------------------------------------------------------- /examples/SerialBridge/SerialBridge.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | SoftModem modem = SoftModem(); 4 | 5 | void setup() { 6 | Serial.begin(115200); 7 | Serial.println("Booting"); 8 | delay(100); 9 | modem.begin(); 10 | } 11 | 12 | void loop() { 13 | while(modem.available()){ 14 | int c = modem.read(); 15 | if(isprint(c)){ 16 | Serial.print((char)c); 17 | } 18 | else{ 19 | Serial.print("("); 20 | Serial.print(c,HEX); 21 | Serial.println(")"); 22 | } 23 | } 24 | if(Serial.available()){ 25 | modem.write(0xff); 26 | while(Serial.available()){ 27 | char c = Serial.read(); 28 | modem.write(c); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by http://gitignore.io 2 | 3 | ### OSX ### 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | Icon 8 | 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear on external disk 14 | .Spotlight-V100 15 | .Trashes 16 | 17 | ### SVN ### 18 | .svn/ 19 | 20 | ### C ### 21 | # Object files 22 | *.o 23 | 24 | # Libraries 25 | *.lib 26 | *.a 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | 39 | ### C++ ### 40 | # Compiled Object files 41 | *.slo 42 | *.lo 43 | *.o 44 | 45 | # Compiled Dynamic libraries 46 | *.so 47 | *.dylib 48 | 49 | # Compiled Static libraries 50 | *.lai 51 | *.la 52 | *.a 53 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For SoftModem 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | SoftModem KEYWORD1 9 | 10 | ####################################### 11 | # Methods and Functions (KEYWORD2) 12 | ####################################### 13 | 14 | begin KEYWORD2 15 | end KEYWORD2 16 | available KEYWORD2 17 | read KEYWORD2 18 | write KEYWORD2 19 | 20 | ####################################### 21 | # Instances (KEYWORD2) 22 | ####################################### 23 | 24 | ####################################### 25 | # Constants (LITERAL1) 26 | ####################################### 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Arms22 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /SoftModem.h: -------------------------------------------------------------------------------- 1 | #ifndef SoftModem_h 2 | #define SoftModem_h 3 | 4 | #include 5 | 6 | //#define SOFT_MODEM_BAUD_RATE (126) 7 | //#define SOFT_MODEM_LOW_FREQ (882) 8 | //#define SOFT_MODEM_HIGH_FREQ (1764) 9 | //#define SOFT_MODEM_RX_BUF_SIZE (4) 10 | 11 | //#define SOFT_MODEM_BAUD_RATE (315) 12 | //#define SOFT_MODEM_LOW_FREQ (1575) 13 | //#define SOFT_MODEM_HIGH_FREQ (3150) 14 | //#define SOFT_MODEM_RX_BUF_SIZE (8) 15 | 16 | //#define SOFT_MODEM_BAUD_RATE (630) 17 | //#define SOFT_MODEM_LOW_FREQ (3150) 18 | //#define SOFT_MODEM_HIGH_FREQ (6300) 19 | //#define SOFT_MODEM_RX_BUF_SIZE (16) 20 | 21 | #define SOFT_MODEM_BAUD_RATE (1225) 22 | #define SOFT_MODEM_LOW_FREQ (4900) 23 | #define SOFT_MODEM_HIGH_FREQ (7350) 24 | #define SOFT_MODEM_RX_BUF_SIZE (32) 25 | 26 | //#define SOFT_MODEM_BAUD_RATE (2450) 27 | //#define SOFT_MODEM_LOW_FREQ (4900) 28 | //#define SOFT_MODEM_HIGH_FREQ (7350) 29 | //#define SOFT_MODEM_RX_BUF_SIZE (32) 30 | 31 | #define SOFT_MODEM_DEBUG_ENABLE (0) 32 | #define SOFT_MODEM_MOVING_AVERAGE_ENABLE (0) 33 | 34 | class SoftModem : public Stream 35 | { 36 | private: 37 | volatile uint8_t *_txPortReg; 38 | uint8_t _txPortMask; 39 | uint8_t _lastTCNT; 40 | uint8_t _lastDiff; 41 | uint8_t _recvStat; 42 | uint8_t _recvBits; 43 | uint8_t _recvBufferHead; 44 | uint8_t _recvBufferTail; 45 | uint8_t _recvBuffer[SOFT_MODEM_RX_BUF_SIZE]; 46 | uint8_t _lowCount; 47 | uint8_t _highCount; 48 | unsigned long _lastWriteTime; 49 | void modulate(uint8_t b); 50 | public: 51 | SoftModem(); 52 | ~SoftModem(); 53 | void begin(void); 54 | void end(void); 55 | virtual int available(); 56 | virtual int read(); 57 | virtual void flush(); 58 | virtual int peek(); 59 | virtual size_t write(const uint8_t *buffer, size_t size); 60 | virtual size_t write(uint8_t data); 61 | void demodulate(void); 62 | void recv(void); 63 | static SoftModem *activeObject; 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![SoftModem](extras/softmodem.jpg) 2 | 3 | SoftModem 4 | ==== 5 | 6 | SoftModem is a wired, low-cost and platform independent solution for communication between an Arduino and mobile phones. It uses the phone's audio jack and Bell 202 modem-like FSK encoding with up to 1225 bit/s. Check out this [blog post](http://translate.google.com/translate?js=y&prev=_t&hl=en&ie=UTF-8&layout=1&eotf=1&u=http%3A%2F%2Farms22.blog91.fc2.com%2Fblog-entry-350.html&sl=auto&tl=en). 7 | 8 | 9 | Projects based on SoftModem: 10 | 11 | - [SoftModemTerminal](https://github.com/arms22/arms22/tree/master/SoftModemTerminal) by Arms22: an iOS SoftModem terminal application 12 | - [FSK-Serial-Generator](https://github.com/NeoCat/FSK-Serial-Generator-in-JavaScript) by NeoCat: a JavaScript transmitter implementation 13 | - [WebJack](https://github.com/publiclab/webjack) by PublicLab: a JavaScript transmitter and receiver, compatible with [Firmata.js](https://github.com/firmata/firmata.js/)/[Jonny-Five](http://johnny-five.io/) 14 | 15 | 16 | ### Install via Arduino Library Manager 17 | Open the Arduino Library Manager from the menu: `Sketch → Include Library → Manage Libraries...`. 18 | Then search for 'SoftModem' and click install. 19 | 20 | ### Manual install 21 | Create a folder 'SoftModem' inside your `libraries` folder and place these files there. 22 | 23 | ## Supported Boards 24 | 25 | - ATmega328: Arduino Uno / Nano / Pro / Pro Mini / Fio 26 | - ATtiny85, ATmega32U4: not implemented yet, contributions welcome! 27 | 28 | 29 | | Board | TX pin | RX pin | AIN1 pin | Timer | Notes | 30 | |:----------------:|:------:|:------:|:--------:|:-----:|:-----:| 31 | | Arduino Uno | 3 | 6 | 7 | 2 | | 32 | | | | | | | | 33 | 34 | 35 | ## Usage 36 | This is an example sketch that forwards data to/from the serial port. 37 | 38 | ```Arduino 39 | #include 40 | 41 | SoftModem modem = SoftModem(); 42 | 43 | void setup() { 44 | Serial.begin(115200); 45 | Serial.println("Booting"); 46 | delay(100); 47 | modem.begin(); 48 | } 49 | 50 | void loop() { 51 | while(modem.available()){ 52 | int c = modem.read(); 53 | if(isprint(c)){ 54 | Serial.print((char)c); 55 | } 56 | else{ 57 | Serial.print("("); 58 | Serial.print(c,HEX); 59 | Serial.println(")"); 60 | } 61 | } 62 | if(Serial.available()){ 63 | modem.write(0xff); 64 | while(Serial.available()){ 65 | char c = Serial.read(); 66 | modem.write(c); 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | ### Notes 73 | SoftModem uses Timer2, therefore you can not make use of the `analogWrite()` function for pins 3 and 11 in your sketch. 74 | 75 | ### Hardware 76 | A shield is available [here](https://www.switch-science.com/catalog/600/) or [here](http://www.elechouse.com/elechouse/index.php?main_page=product_info&cPath=90_92&products_id=2199). You can also build your own. Here is the schematic: 77 | 78 | ![Schematic](extras/schematic.png) 79 | 80 | ##License 81 | [BSD 3](https://github.com/arms22/SoftModem/blob/master/LICENSE) -------------------------------------------------------------------------------- /SoftModem.cpp: -------------------------------------------------------------------------------- 1 | #include "SoftModem.h" 2 | 3 | #define TX_PIN (3) 4 | #define RX_PIN1 (6) // AIN0 5 | #define RX_PIN2 (7) // AIN1 6 | 7 | SoftModem *SoftModem::activeObject = 0; 8 | 9 | SoftModem::SoftModem() { 10 | } 11 | 12 | SoftModem::~SoftModem() { 13 | end(); 14 | } 15 | 16 | #if F_CPU == 16000000 17 | #if SOFT_MODEM_BAUD_RATE <= 126 18 | #define TIMER_CLOCK_SELECT (7) 19 | #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(1024)) 20 | #elif SOFT_MODEM_BAUD_RATE <= 315 21 | #define TIMER_CLOCK_SELECT (6) 22 | #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(256)) 23 | #elif SOFT_MODEM_BAUD_RATE <= 630 24 | #define TIMER_CLOCK_SELECT (5) 25 | #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(128)) 26 | #elif SOFT_MODEM_BAUD_RATE <= 1225 27 | #define TIMER_CLOCK_SELECT (4) 28 | #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(64)) 29 | #else 30 | #define TIMER_CLOCK_SELECT (3) 31 | #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(32)) 32 | #endif 33 | #else 34 | #if SOFT_MODEM_BAUD_RATE <= 126 35 | #define TIMER_CLOCK_SELECT (6) 36 | #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(256)) 37 | #elif SOFT_MODEM_BAUD_RATE <= 315 38 | #define TIMER_CLOCK_SELECT (5) 39 | #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(128)) 40 | #elif SOFT_MODEM_BAUD_RATE <= 630 41 | #define TIMER_CLOCK_SELECT (4) 42 | #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(64)) 43 | #else 44 | #define TIMER_CLOCK_SELECT (3) 45 | #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(32)) 46 | #endif 47 | #endif 48 | 49 | #define BIT_PERIOD (1000000/SOFT_MODEM_BAUD_RATE) 50 | #define HIGH_FREQ_MICROS (1000000/SOFT_MODEM_HIGH_FREQ) 51 | #define LOW_FREQ_MICROS (1000000/SOFT_MODEM_LOW_FREQ) 52 | 53 | #define HIGH_FREQ_CNT (BIT_PERIOD/HIGH_FREQ_MICROS) 54 | #define LOW_FREQ_CNT (BIT_PERIOD/LOW_FREQ_MICROS) 55 | 56 | #define MAX_CARRIR_BITS (40000/BIT_PERIOD) 57 | 58 | #define TCNT_BIT_PERIOD (BIT_PERIOD/MICROS_PER_TIMER_COUNT) 59 | #define TCNT_HIGH_FREQ (HIGH_FREQ_MICROS/MICROS_PER_TIMER_COUNT) 60 | #define TCNT_LOW_FREQ (LOW_FREQ_MICROS/MICROS_PER_TIMER_COUNT) 61 | 62 | #define TCNT_HIGH_TH_L (TCNT_HIGH_FREQ * 0.90) 63 | #define TCNT_HIGH_TH_H (TCNT_HIGH_FREQ * 1.15) 64 | #define TCNT_LOW_TH_L (TCNT_LOW_FREQ * 0.85) 65 | #define TCNT_LOW_TH_H (TCNT_LOW_FREQ * 1.10) 66 | 67 | #if SOFT_MODEM_DEBUG_ENABLE 68 | static volatile uint8_t *_portLEDReg; 69 | static uint8_t _portLEDMask; 70 | #endif 71 | 72 | enum { START_BIT = 0, DATA_BIT = 8, STOP_BIT = 9, INACTIVE = 0xff }; 73 | 74 | void SoftModem::begin(void) 75 | { 76 | pinMode(RX_PIN1, INPUT); 77 | digitalWrite(RX_PIN1, LOW); 78 | 79 | pinMode(RX_PIN2, INPUT); 80 | digitalWrite(RX_PIN2, LOW); 81 | 82 | pinMode(TX_PIN, OUTPUT); 83 | digitalWrite(TX_PIN, LOW); 84 | 85 | _txPortReg = portOutputRegister(digitalPinToPort(TX_PIN)); 86 | _txPortMask = digitalPinToBitMask(TX_PIN); 87 | 88 | #if SOFT_MODEM_DEBUG_ENABLE 89 | _portLEDReg = portOutputRegister(digitalPinToPort(13)); 90 | _portLEDMask = digitalPinToBitMask(13); 91 | pinMode(13, OUTPUT); 92 | #endif 93 | 94 | _recvStat = INACTIVE; 95 | _recvBufferHead = _recvBufferTail = 0; 96 | 97 | SoftModem::activeObject = this; 98 | 99 | _lastTCNT = TCNT2; 100 | _lastDiff = _lowCount = _highCount = 0; 101 | 102 | TCCR2A = 0; 103 | TCCR2B = TIMER_CLOCK_SELECT; 104 | ACSR = _BV(ACIE) | _BV(ACIS1); 105 | DIDR1 = _BV(AIN1D) | _BV(AIN0D); 106 | } 107 | 108 | void SoftModem::end(void) 109 | { 110 | ACSR &= ~(_BV(ACIE)); 111 | TIMSK2 &= ~(_BV(OCIE2A)); 112 | DIDR1 &= ~(_BV(AIN1D) | _BV(AIN0D)); 113 | SoftModem::activeObject = 0; 114 | } 115 | 116 | void SoftModem::demodulate(void) 117 | { 118 | uint8_t t = TCNT2; 119 | uint8_t diff; 120 | 121 | diff = t - _lastTCNT; 122 | 123 | if(diff < 4) 124 | return; 125 | 126 | _lastTCNT = t; 127 | 128 | if(diff > (uint8_t)(TCNT_LOW_TH_H)) 129 | return; 130 | 131 | // Calculating the moving average 132 | #if SOFT_MODEM_MOVING_AVERAGE_ENABLE 133 | _lastDiff = (diff >> 1) + (diff >> 2) + (_lastDiff >> 2); 134 | #else 135 | _lastDiff = diff; 136 | #endif 137 | 138 | if(_lastDiff >= (uint8_t)(TCNT_LOW_TH_L)){ 139 | _lowCount += _lastDiff; 140 | if(_recvStat == INACTIVE){ 141 | // Start bit detection 142 | if(_lowCount >= (uint8_t)(TCNT_BIT_PERIOD * 0.5)){ 143 | _recvStat = START_BIT; 144 | _highCount = 0; 145 | _recvBits = 0; 146 | OCR2A = t + (uint8_t)(TCNT_BIT_PERIOD) - _lowCount; 147 | TIFR2 |= _BV(OCF2A); 148 | TIMSK2 |= _BV(OCIE2A); 149 | } 150 | } 151 | } 152 | else if(_lastDiff <= (uint8_t)(TCNT_HIGH_TH_H)){ 153 | if(_recvStat == INACTIVE){ 154 | _lowCount = 0; 155 | _highCount = 0; 156 | } 157 | else{ 158 | _highCount += _lastDiff; 159 | } 160 | } 161 | } 162 | 163 | // Analog comparator interrupt 164 | ISR(ANALOG_COMP_vect) 165 | { 166 | SoftModem::activeObject->demodulate(); 167 | } 168 | 169 | void SoftModem::recv(void) 170 | { 171 | uint8_t high; 172 | 173 | // Bit logic determination 174 | if(_highCount > _lowCount){ 175 | _highCount = 0; 176 | high = 0x80; 177 | } 178 | else{ 179 | _lowCount = 0; 180 | high = 0x00; 181 | } 182 | 183 | // Start bit reception 184 | if(_recvStat == START_BIT){ 185 | if(!high){ 186 | _recvStat++; 187 | } 188 | else{ 189 | goto end_recv; 190 | } 191 | } 192 | // Data bit reception 193 | else if(_recvStat <= DATA_BIT) { 194 | _recvBits >>= 1; 195 | _recvBits |= high; 196 | _recvStat++; 197 | } 198 | // Stop bit reception 199 | else if(_recvStat == STOP_BIT){ 200 | if(high){ 201 | // Stored in the receive buffer 202 | uint8_t new_tail = (_recvBufferTail + 1) & (SOFT_MODEM_RX_BUF_SIZE - 1); 203 | if(new_tail != _recvBufferHead){ 204 | _recvBuffer[_recvBufferTail] = _recvBits; 205 | _recvBufferTail = new_tail; 206 | } 207 | else{ 208 | ;// Overrun error detection 209 | } 210 | } 211 | else{ 212 | ;// Fleming error detection 213 | } 214 | goto end_recv; 215 | } 216 | else{ 217 | end_recv: 218 | _recvStat = INACTIVE; 219 | TIMSK2 &= ~_BV(OCIE2A); 220 | } 221 | } 222 | 223 | // Timer 2 compare match interrupt A 224 | ISR(TIMER2_COMPA_vect) 225 | { 226 | OCR2A += (uint8_t)TCNT_BIT_PERIOD; 227 | SoftModem::activeObject->recv(); 228 | #if SOFT_MODEM_DEBUG_ENABLE 229 | *_portLEDReg ^= _portLEDMask; 230 | #endif 231 | } 232 | 233 | int SoftModem::available() 234 | { 235 | return (_recvBufferTail + SOFT_MODEM_RX_BUF_SIZE - _recvBufferHead) & (SOFT_MODEM_RX_BUF_SIZE - 1); 236 | } 237 | 238 | int SoftModem::read() 239 | { 240 | if(_recvBufferHead == _recvBufferTail) 241 | return -1; 242 | int d = _recvBuffer[_recvBufferHead]; 243 | _recvBufferHead = (_recvBufferHead + 1) & (SOFT_MODEM_RX_BUF_SIZE - 1); 244 | return d; 245 | } 246 | 247 | int SoftModem::peek() 248 | { 249 | if(_recvBufferHead == _recvBufferTail) 250 | return -1; 251 | return _recvBuffer[_recvBufferHead]; 252 | } 253 | 254 | void SoftModem::flush() 255 | { 256 | } 257 | 258 | void SoftModem::modulate(uint8_t b) 259 | { 260 | uint8_t cnt,tcnt,tcnt2; 261 | if(b){ 262 | cnt = (uint8_t)(HIGH_FREQ_CNT); 263 | tcnt2 = (uint8_t)(TCNT_HIGH_FREQ / 2); 264 | tcnt = (uint8_t)(TCNT_HIGH_FREQ) - tcnt2; 265 | }else{ 266 | cnt = (uint8_t)(LOW_FREQ_CNT); 267 | tcnt2 = (uint8_t)(TCNT_LOW_FREQ / 2); 268 | tcnt = (uint8_t)(TCNT_LOW_FREQ) - tcnt2; 269 | } 270 | do { 271 | cnt--; 272 | { 273 | OCR2B += tcnt; 274 | TIFR2 |= _BV(OCF2B); 275 | while(!(TIFR2 & _BV(OCF2B))); 276 | } 277 | *_txPortReg ^= _txPortMask; 278 | { 279 | OCR2B += tcnt2; 280 | TIFR2 |= _BV(OCF2B); 281 | while(!(TIFR2 & _BV(OCF2B))); 282 | } 283 | *_txPortReg ^= _txPortMask; 284 | } while (cnt); 285 | } 286 | 287 | // Preamble bit before transmission 288 | // 1 start bit (LOW) 289 | // 8 data bits, LSB first 290 | // 1 stop bit (HIGH) 291 | // ... 292 | // Postamble bit after transmission 293 | 294 | size_t SoftModem::write(const uint8_t *buffer, size_t size) 295 | { 296 | // To calculate the preamble bit length 297 | uint8_t cnt = ((micros() - _lastWriteTime) / BIT_PERIOD) + 1; 298 | if(cnt > MAX_CARRIR_BITS){ 299 | cnt = MAX_CARRIR_BITS; 300 | } 301 | // Preamble bit transmission 302 | for(uint8_t i = 0; i