├── .gitattributes ├── .gitignore ├── README.md ├── library.properties ├── src ├── NeoSWSerial.cpp └── NeoSWSerial.h └── test └── test.ino /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The **NeoSWSerial** class is intended as an more-efficient drop-in replacement for the Arduino built-in class `SoftwareSerial`. If you could use `Serial`, `Serial1`, `Serial2` or `Serial3`, you should use [NeoHWSerial](https://github.com/SlashDevin/NeoHWSerial) instead. If you could use an Input Capture pin (ICP1, pins 8 & 9 on an UNO), you should consider [NeoICSerial](https://github.com/SlashDevin/NeoICSerial) instead. 2 | 3 | **NeoSWSerial** is limited to four baud rates: 9600 (default), 19200, 31250 (MIDI) and 38400. 4 | 5 | The class methods are nearly identical to the built-in `SoftwareSerial`, except for two new methods, `attachInterrupt` and `detachInterrupt`: 6 | 7 | ``` 8 | typedef void (* isr_t)( uint8_t ); 9 | void attachInterrupt( isr_t fn ); 10 | void detachInterrupt() { attachInterrupt( (isr_t) NULL ); }; 11 | 12 | private: 13 | isr_t _isr; 14 | ``` 15 | 16 | There are five, nay, **six** advantages over `SoftwareSerial`: 17 | 18 | **1)** It uses *much* less CPU time. 19 | 20 | **2)** Simultaneous transmit and receive is fully supported. 21 | 22 | **3)** Interrupts are not disabled for the entire RX character time. (They are disabled for most of each TX character time.) 23 | 24 | **4)** It is much more reliable (far fewer receive data errors). 25 | 26 | **5)** Characters can be handled with a user-defined procedure at interrupt time. This should prevent most input buffer overflow problems. Simply register your procedure with the 'NeoSWSerial' instance: 27 | 28 | ``` 29 | #include 30 | NeoSWSerial ss( 4, 3 ); 31 | 32 | volatile uint32_t newlines = 0UL; 33 | 34 | static void handleRxChar( uint8_t c ) 35 | { 36 | if (c == '\n') 37 | newlines++; 38 | } 39 | 40 | void setup() 41 | { 42 | ss.attachInterrupt( handleRxChar ); 43 | ss.begin( 9600 ); 44 | } 45 | ``` 46 | 47 | Remember that the registered procedure is called from an interrupt context, and it should return as quickly as possible. Taking too much time in the procedure will cause many unpredictable behaviors, including loss of received data. See the similar warnings for the built-in [`attachInterrupt`](https://www.arduino.cc/en/Reference/AttachInterrupt) for digital pins. 48 | 49 | The registered procedure will be called from the ISR whenever a character is received. The received character **will not** be stored in the `rx_buffer`, and it **will not** be returned from `read()`. Any characters that were received and buffered before `attachInterrupt` was called remain in `rx_buffer`, and could be retrieved by calling `read()`. 50 | 51 | If `attachInterrupt` is never called, or it is passed a `NULL` procedure, the normal buffering occurs, and all received characters must be obtained by calling `read()`. 52 | 53 | **6)** The NeoSWSerial ISRs can be disabled. This can help you avoid linking conflicts with other PinChangeInterrupt libraries, like EnableInterrupt: 54 | 55 | ``` 56 | void myDeviceISR() 57 | { 58 | NeoSWSerial::rxISR( *portInputRegister( digitalPinToPort( RX_PIN ) ) ); 59 | // if you know the exact PIN register, you could do this: 60 | // NeoSWSerial::rxISR( PIND ); 61 | } 62 | 63 | void setup() 64 | { 65 | myDevice.begin( 9600 ); 66 | enableInterrupt( RX_PIN, myDeviceISR, CHANGE ); 67 | enableInterrupt( OTHER_PIN, otherISR, RISING ); 68 | } 69 | ``` 70 | 71 | This class supports the following MCUs: ATtinyx61, ATtinyx4, ATtinyx5, ATmega328P (Pro, UNO, Nano), ATmega32U4 (Micro, Leonardo), ATmega2560 (Mega), ATmega2560RFR2, ATmega1284P and ATmega1286 72 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=NeoSWSerial 2 | version=3.0.5 3 | author=SlashDevin 4 | maintainer=SlashDevin 5 | sentence=An efficient replacement for SoftwareSerial at baud rates 9600, 19200 and 38400. 6 | paragraph=Simultaneous RX & TX, does not require additional TIMERs, interrupts not disabled during RX 7 | category=Communication 8 | url=https://github.com/SlashDevin/NeoSWSerial 9 | architectures=avr 10 | includes=NeoSWSerial.h 11 | -------------------------------------------------------------------------------- /src/NeoSWSerial.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // NeoSWSerial 3 | // Copyright (C) 2015-2017, SlashDevin 4 | // 5 | // NeoSWSerial is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // NeoSWSerial is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details: 14 | // 15 | // . 16 | // 17 | // Methods 18 | // ------- 19 | // 20 | // begin(baudRate) - initialization, optionally set baudrate, and then enable RX 21 | // listen() - enables RX interrupts, allowing data to enter the RX buffer 22 | // ignore() - disables RX interrupts 23 | // setBaudRate(baudRate) - selects the baud rate (9600, 19200, 31250, 38400) 24 | // - any other value is ignored 25 | // available() - returns the number of characters in the RX buffer 26 | // read() - returns a single character from the buffer 27 | // write(s) - transmits a string 28 | // 29 | // print() is supported 30 | //============================================================================= 31 | 32 | #include 33 | 34 | // Default baud rate is 9600 35 | static const uint8_t TICKS_PER_BIT_9600 = (uint8_t) 26; 36 | // 9600 baud bit width in units of 4us 37 | static const uint8_t TICKS_PER_BIT_31250 = 8; 38 | // 31250 baud bit width in units of 4us 39 | 40 | static const uint8_t BITS_PER_TICK_31250_Q10 = 128; 41 | // 31250 bps * 0.000004 s * 2^10 "multiplier" 42 | static const uint8_t BITS_PER_TICK_38400_Q10 = 157; 43 | // 1s/(38400 bits) * (1 tick)/(4 us) * 2^10 "multiplier" 44 | 45 | #if F_CPU == 16000000L 46 | #define TCNTX TCNT0 47 | #define PCI_FLAG_REGISTER PCIFR 48 | #elif F_CPU == 8000000L 49 | #if defined(__AVR_ATtiny25__) | \ 50 | defined(__AVR_ATtiny45__) | \ 51 | defined(__AVR_ATtiny85__) 52 | #define TCNTX TCNT1 53 | #define PCI_FLAG_REGISTER GIFR 54 | #else 55 | #define TCNTX TCNT2 56 | #define PCI_FLAG_REGISTER PCIFR 57 | #endif 58 | #endif 59 | 60 | static NeoSWSerial *listener = (NeoSWSerial *) NULL; 61 | 62 | static uint8_t txBitWidth; 63 | static uint8_t rxWindowWidth; 64 | static uint8_t bitsPerTick_Q10; // multiplier! 65 | 66 | static const uint8_t WAITING_FOR_START_BIT = 0xFF; 67 | static uint8_t rxState; // 0: got start bit; >0: bits rcvd 68 | static uint8_t prev_t0; // previous RX transition: timer0 time stamp (4us) 69 | static uint8_t rxMask; // bit mask for building received character 70 | static uint8_t rxValue; // character being built 71 | 72 | static const uint8_t RX_BUFFER_SIZE = 64; // power of 2 for optimal speed 73 | static uint8_t rxBuffer[RX_BUFFER_SIZE]; 74 | static uint8_t rxHead; // buffer pointer input 75 | static uint8_t rxTail; // buffer pointer output 76 | 77 | static uint8_t rxBitMask, txBitMask; // port bit masks 78 | static volatile uint8_t *txPort; // port register 79 | 80 | //#define DEBUG_NEOSWSERIAL 81 | #ifdef DEBUG_NEOSWSERIAL 82 | 83 | uint8_t bitTransitionTimes[16]; 84 | uint8_t bitTransitions; 85 | static const uint8_t MAX_DIFF_RX_BITS = 8; 86 | struct rbd_t { uint8_t loop_bits; uint8_t mul_bits; uint16_t prod; }; 87 | rbd_t diffRXbits[MAX_DIFF_RX_BITS]; 88 | uint8_t diffRXbitsCount; 89 | 90 | uint16_t availCompletions; 91 | uint16_t rxStartCompletions; 92 | uint8_t rxStartCompletionBits[128]; 93 | uint16_t checkRxCompletions; 94 | uint16_t polledPCI; 95 | uint16_t polledPCICompletions; 96 | uint16_t stopBitCompletions; 97 | 98 | #define DBG_NSS_COUNT(v) { v++; } 99 | #define DBG_NSS_COUNT_RESET(v) { v = 0; } 100 | #define DBG_NSS_ARRAY(a,i,v) \ 101 | { if (i < sizeof(a)/sizeof(a[0])) \ 102 | a[ i++ ] = v; } 103 | 104 | #else 105 | 106 | #define DBG_NSS_COUNT(v) 107 | #define DBG_NSS_COUNT_RESET(v) 108 | #define DBG_NSS_ARRAY(a,i,v) 109 | 110 | #endif 111 | 112 | static uint16_t mul8x8to16(uint8_t x, uint8_t y) 113 | {return x*y;} 114 | 115 | //.......................................... 116 | 117 | static uint16_t bitTimes( uint8_t dt ) 118 | { 119 | return mul8x8to16( dt + rxWindowWidth, bitsPerTick_Q10 ) >> 10; 120 | 121 | } // bitTimes 122 | 123 | //---------------------------------------------------------------------------- 124 | 125 | void NeoSWSerial::begin(uint16_t baudRate) 126 | { 127 | setBaudRate( baudRate ); 128 | listen(); 129 | } 130 | 131 | //---------------------------------------------------------------------------- 132 | 133 | void NeoSWSerial::listen() 134 | { 135 | if (listener) 136 | listener->ignore(); 137 | 138 | pinMode(rxPin, INPUT); 139 | rxBitMask = digitalPinToBitMask( rxPin ); 140 | rxPort = portInputRegister( digitalPinToPort( rxPin ) ); 141 | 142 | txBitMask = digitalPinToBitMask( txPin ); 143 | txPort = portOutputRegister( digitalPinToPort( txPin ) ); 144 | if (txPort) 145 | *txPort |= txBitMask; // high = idle 146 | pinMode(txPin, OUTPUT); 147 | 148 | if (F_CPU == 8000000L) { 149 | // Have to use timer 2 for an 8 MHz system. 150 | #if defined(__AVR_ATtiny25__) | \ 151 | defined(__AVR_ATtiny45__) | \ 152 | defined(__AVR_ATtiny85__) 153 | TCCR1 = 0x06; // divide by 32 154 | #else 155 | TCCR2A = 0x00; 156 | TCCR2B = 0x03; // divide by 32 157 | #endif 158 | } 159 | 160 | volatile uint8_t *pcmsk = digitalPinToPCMSK(rxPin); 161 | if (pcmsk) { 162 | rxState = WAITING_FOR_START_BIT; 163 | rxHead = rxTail = 0; // no characters in buffer 164 | flush(); 165 | 166 | // Set up timings based on baud rate 167 | 168 | switch (_baudRate) { 169 | case 9600: 170 | txBitWidth = TICKS_PER_BIT_9600 ; 171 | bitsPerTick_Q10 = BITS_PER_TICK_38400_Q10 >> 2; 172 | rxWindowWidth = 10; 173 | break; 174 | case 31250: 175 | if (F_CPU > 12000000L) { 176 | txBitWidth = TICKS_PER_BIT_31250; 177 | bitsPerTick_Q10 = BITS_PER_TICK_31250_Q10; 178 | rxWindowWidth = 5; 179 | break; 180 | } // else use 19200 181 | case 38400: 182 | if (F_CPU > 12000000L) { 183 | txBitWidth = TICKS_PER_BIT_9600 >> 2; 184 | bitsPerTick_Q10 = BITS_PER_TICK_38400_Q10 ; 185 | rxWindowWidth = 4; 186 | break; 187 | } // else use 19200 188 | case 19200: 189 | txBitWidth = TICKS_PER_BIT_9600 >> 1; 190 | bitsPerTick_Q10 = BITS_PER_TICK_38400_Q10 >> 1; 191 | rxWindowWidth = 6; 192 | break; 193 | } 194 | 195 | // Enable the pin change interrupts 196 | 197 | uint8_t prevSREG = SREG; 198 | cli(); 199 | { 200 | *pcmsk |= _BV(digitalPinToPCMSKbit(rxPin)); 201 | *digitalPinToPCICR(rxPin) |= _BV(digitalPinToPCICRbit(rxPin)); 202 | listener = this; 203 | } 204 | SREG = prevSREG; 205 | } 206 | 207 | } // listen 208 | 209 | //---------------------------------------------------------------------------- 210 | 211 | void NeoSWSerial::ignore() 212 | { 213 | if (listener) { 214 | volatile uint8_t *pcmsk = digitalPinToPCMSK(rxPin); 215 | 216 | uint8_t prevSREG = SREG; 217 | cli(); 218 | { 219 | listener = (NeoSWSerial *) NULL; 220 | if (pcmsk) { 221 | *digitalPinToPCICR(rxPin) &= ~_BV(digitalPinToPCICRbit(rxPin)); 222 | *pcmsk &= ~_BV(digitalPinToPCMSKbit(rxPin)); 223 | } 224 | } 225 | SREG = prevSREG; 226 | } 227 | 228 | } // ignore 229 | 230 | //---------------------------------------------------------------------------- 231 | 232 | void NeoSWSerial::setBaudRate(uint16_t baudRate) 233 | { 234 | if (( 235 | ( baudRate == 9600) || 236 | ( baudRate == 19200) || 237 | ((baudRate == 31250) && (F_CPU == 16000000L)) || 238 | ((baudRate == 38400) && (F_CPU == 16000000L)) 239 | ) 240 | && 241 | (_baudRate != baudRate)) { 242 | 243 | _baudRate = baudRate; 244 | 245 | if (this == listener) 246 | listen(); 247 | } 248 | } // setBaudRate 249 | 250 | //---------------------------------------------------------------------------- 251 | 252 | int NeoSWSerial::available() 253 | { 254 | uint8_t avail = ((rxHead - rxTail + RX_BUFFER_SIZE) % RX_BUFFER_SIZE); 255 | 256 | if (avail == 0) { 257 | cli(); 258 | if (checkRxTime()) { 259 | avail = 1; 260 | DBG_NSS_COUNT(availCompletions); 261 | } 262 | sei(); 263 | } 264 | 265 | return avail; 266 | 267 | } // available 268 | 269 | //---------------------------------------------------------------------------- 270 | 271 | int NeoSWSerial::read() 272 | { 273 | if (rxHead == rxTail) return -1; 274 | uint8_t c = rxBuffer[rxTail]; 275 | rxTail = (rxTail + 1) % RX_BUFFER_SIZE; 276 | 277 | return c; 278 | 279 | } // read 280 | 281 | //---------------------------------------------------------------------------- 282 | 283 | void NeoSWSerial::attachInterrupt( isr_t fn ) 284 | { 285 | uint8_t oldSREG = SREG; 286 | cli(); 287 | _isr = fn; 288 | SREG = oldSREG; 289 | 290 | } // attachInterrupt 291 | 292 | //---------------------------------------------------------------------------- 293 | 294 | void NeoSWSerial::startChar() 295 | { 296 | rxState = 0; // got a start bit 297 | rxMask = 0x01; // bit mask, lsb first 298 | rxValue = 0x00; // RX character to be, a blank slate 299 | 300 | DBG_NSS_COUNT_RESET(bitTransitions); 301 | 302 | } // startChar 303 | 304 | //---------------------------------------------------------------------------- 305 | 306 | void NeoSWSerial::rxISR( uint8_t rxPort ) 307 | { 308 | uint8_t t0 = TCNTX; // time of data transition (plus ISR latency) 309 | uint8_t d = rxPort & rxBitMask; // read RX data level 310 | 311 | if (rxState == WAITING_FOR_START_BIT) { 312 | 313 | // If it looks like a start bit then initialize; 314 | // otherwise ignore the rising edge and exit. 315 | 316 | if (d != 0) return; // it's high so not a start bit, exit 317 | startChar(); 318 | 319 | } else { // data bit or stop bit (probably) received 320 | 321 | DBG_NSS_ARRAY(bitTransitionTimes, bitTransitions, (t0-prev_t0)); 322 | 323 | // Determine how many bit periods have elapsed since the last transition. 324 | 325 | uint16_t rxBits = bitTimes( t0-prev_t0 ); 326 | uint8_t bitsLeft = 9 - rxState; // ignores stop bit 327 | bool nextCharStarted = (rxBits > bitsLeft); 328 | 329 | if (nextCharStarted) 330 | DBG_NSS_ARRAY(rxStartCompletionBits,rxStartCompletions,(10*rxBits + bitsLeft)); 331 | 332 | uint8_t bitsThisFrame = nextCharStarted ? bitsLeft : rxBits; 333 | 334 | rxState += bitsThisFrame; 335 | 336 | // Set all those bits 337 | 338 | if (d == 0) { 339 | // back fill previous bits with 1's 340 | while (bitsThisFrame-- > 0) { 341 | rxValue |= rxMask; 342 | rxMask = rxMask << 1; 343 | } 344 | rxMask = rxMask << 1; 345 | } else { // d==1 346 | // previous bits were 0's so only this bit is a 1. 347 | rxMask = rxMask << (bitsThisFrame-1); 348 | rxValue |= rxMask; 349 | } 350 | 351 | // If 8th bit or stop bit then the character is complete. 352 | 353 | if (rxState > 7) { 354 | rxChar( rxValue ); 355 | 356 | if ((d == 1) || !nextCharStarted) { 357 | rxState = WAITING_FOR_START_BIT; 358 | // DISABLE STOP BIT TIMER 359 | 360 | } else { 361 | // The last char ended with 1's, so this 0 is actually 362 | // the start bit of the next character. 363 | 364 | startChar(); 365 | } 366 | } 367 | } 368 | 369 | prev_t0 = t0; // remember time stamp 370 | 371 | } // rxISR 372 | 373 | //---------------------------------------------------------------------------- 374 | 375 | bool NeoSWSerial::checkRxTime() 376 | { 377 | if (rxState != WAITING_FOR_START_BIT) { 378 | 379 | uint8_t d = *rxPort & rxBitMask; 380 | 381 | if (d) { 382 | // Ended on a 1, see if it has been too long 383 | uint8_t t0 = TCNTX; // now 384 | uint16_t rxBits = bitTimes( t0-prev_t0 ); 385 | uint8_t bitsLeft = 9 - rxState; 386 | bool completed = (rxBits > bitsLeft); 387 | 388 | if (completed) { 389 | DBG_NSS_COUNT(checkRxCompletions); 390 | 391 | while (bitsLeft-- > 0) { 392 | rxValue |= rxMask; 393 | rxMask = rxMask << 1; 394 | } 395 | 396 | rxState = WAITING_FOR_START_BIT; 397 | rxChar( rxValue ); 398 | if (!_isr) 399 | return true; 400 | } 401 | } 402 | } 403 | return false; 404 | 405 | } // checkRxTime 406 | 407 | //---------------------------------------------------------------------------- 408 | 409 | void NeoSWSerial::rxChar( uint8_t c ) 410 | { 411 | if (listener) { 412 | if (listener->_isr) 413 | listener->_isr( c ); 414 | else { 415 | uint8_t index = (rxHead+1) % RX_BUFFER_SIZE; 416 | if (index != rxTail) { 417 | rxBuffer[rxHead] = c; 418 | rxHead = index; 419 | } 420 | } 421 | } 422 | 423 | } // rxChar 424 | 425 | //---------------------------------------------------------------------------- 426 | 427 | #ifdef NEOSWSERIAL_EXTERNAL_PCINT 428 | 429 | // Client code must call NeoSWSerial::rxISR(PINB) in PCINT handler 430 | 431 | #else 432 | 433 | // Must define all of the vectors even though only one is used. 434 | 435 | // This handy PCINT code for different boards is based on PinChangeInterrupt.* 436 | // from the excellent Cosa project: http://github.com/mikaelpatel/Cosa 437 | 438 | #define PCINT_ISR(vec,pin) \ 439 | extern "C" { \ 440 | ISR(PCINT ## vec ## _vect) \ 441 | { \ 442 | NeoSWSerial::rxISR(pin); \ 443 | } } 444 | 445 | #if defined(__AVR_ATtiny261__) | \ 446 | defined(__AVR_ATtiny461__) | \ 447 | defined(__AVR_ATtiny861__) 448 | 449 | ISR(PCINT0_vect) 450 | { 451 | if (GIFR & _BV(INTF0)) { 452 | NeoSWSerial::rxISR(PINA); 453 | } else { 454 | NeoSWSerial::rxISR(PINB); 455 | } 456 | } 457 | 458 | #elif defined(__AVR_ATtiny25__) | \ 459 | defined(__AVR_ATtiny45__) | \ 460 | defined(__AVR_ATtiny85__) 461 | 462 | PCINT_ISR(0, PINB); 463 | 464 | #elif defined(__AVR_ATtiny24__) | \ 465 | defined(__AVR_ATtiny44__) | \ 466 | defined(__AVR_ATtiny84__) 467 | 468 | PCINT_ISR(0, PINA); 469 | PCINT_ISR(1, PINB); 470 | 471 | #elif defined(__AVR_ATmega328P__) 472 | 473 | PCINT_ISR(0, PINB); 474 | PCINT_ISR(1, PINC); 475 | PCINT_ISR(2, PIND); 476 | 477 | #elif defined(__AVR_ATmega32U4__) 478 | 479 | PCINT_ISR(0, PINB); 480 | 481 | #elif defined(__AVR_AT90USB1286__) 482 | 483 | PCINT_ISR(0, PINB); 484 | 485 | #elif defined(__AVR_ATmega2560__) 486 | 487 | PCINT_ISR(0, PINB); 488 | PCINT_ISR(1, PINJ); 489 | PCINT_ISR(2, PINK); 490 | 491 | #elif defined(__AVR_ATmega1281__) 492 | 493 | PCINT_ISR(0, PINB); 494 | // PCINT8 on PE0 not supported. Other 7 are on PJ0..6 495 | PCINT_ISR(1, PINJ); 496 | PCINT_ISR(2, PINK); 497 | 498 | #elif defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644P__) 499 | 500 | PCINT_ISR(0, PINA); 501 | PCINT_ISR(1, PINB); 502 | PCINT_ISR(2, PINC); 503 | PCINT_ISR(3, PIND); 504 | 505 | #elif defined(__AVR_ATmega2560RFR2__) 506 | 507 | PCINT_ISR(0, PINB); 508 | PCINT_ISR(1, PINE); 509 | 510 | #else 511 | #error MCU not supported by NeoSWSerial! 512 | #endif 513 | 514 | #endif 515 | 516 | //----------------------------------------------------------------------------- 517 | // Instead of using a TX buffer and interrupt 518 | // service, the transmit function is a simple timer0 based delay loop. 519 | // 520 | // Interrupts are disabled while the character is being transmitted and 521 | // re-enabled after each character. 522 | 523 | size_t NeoSWSerial::write(uint8_t txChar) 524 | { 525 | if (!txPort) 526 | return 0; 527 | 528 | uint8_t width; // ticks for one bit 529 | uint8_t txBit = 0; // first bit is start bit 530 | uint8_t b = 0; // start bit is low 531 | uint8_t PCIbit = bit(digitalPinToPCICRbit(rxPin)); 532 | 533 | uint8_t prevSREG = SREG; 534 | cli(); // send the character with interrupts disabled 535 | 536 | uint8_t t0 = TCNTX; // start time 537 | 538 | // TODO: This would benefit from an early break after 539 | // the last 0 data bit. Then we could wait for the 540 | // remaining 1 data bits and stop bit with interrupts 541 | // re-enabled. 542 | 543 | while (txBit++ < 9) { // repeat for start bit + 8 data bits 544 | if (b) // if bit is set 545 | *txPort |= txBitMask; // set TX line high 546 | else 547 | *txPort &= ~txBitMask; // else set TX line low 548 | 549 | width = txBitWidth; 550 | if ((F_CPU == 16000000L) && 551 | (width == TICKS_PER_BIT_9600/4) && 552 | (txBit & 0x01)) { 553 | // The width is 6.5 ticks, so add a tick every other bit 554 | width++; 555 | } 556 | 557 | // Hold the line for the bit duration 558 | 559 | while ((uint8_t)(TCNTX - t0) < width) { 560 | // Receive interrupt pending? 561 | if (PCI_FLAG_REGISTER & PCIbit) { 562 | PCI_FLAG_REGISTER |= PCIbit; // clear it because... 563 | rxISR( *rxPort ); // ... this handles it 564 | DBG_NSS_COUNT(polledPCI); 565 | } else if (checkRxTime()) { 566 | DBG_NSS_COUNT(polledPCICompletions); 567 | } 568 | } 569 | t0 += width; // advance start time 570 | b = txChar & 0x01; // get next bit in the character to send 571 | txChar = txChar >> 1; // shift character to expose the following bit 572 | // Q: would a signed >> pull in a 1? 573 | } 574 | 575 | *txPort |= txBitMask; // stop bit is high 576 | SREG = prevSREG; // interrupts on for stop bit 577 | while ((uint8_t)(TCNTX - t0) < width) { 578 | if (checkRxTime()) 579 | DBG_NSS_COUNT(stopBitCompletions); 580 | } 581 | 582 | return 1; // 1 character sent 583 | 584 | } // write 585 | -------------------------------------------------------------------------------- /src/NeoSWSerial.h: -------------------------------------------------------------------------------- 1 | #ifndef NeoSWSerial_h 2 | #define NeoSWSerial_h 3 | 4 | #include "Arduino.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | // 8 | // NeoSWSerial 9 | // Copyright (C) 2015-2016, SlashDevin 10 | // 11 | // NeoSWSerial is free software: you can redistribute it and/or modify 12 | // it under the terms of the GNU General Public License as published by 13 | // the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // NeoSWSerial is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | // GNU General Public License for more details: 20 | // 21 | // . 22 | // 23 | // 24 | // This software serial library is intended as a more-efficient replacement 25 | // for SoftwareSerial at baud rates 9600, 19200 and 38400. 26 | // 27 | // Any of the pins supported by SoftwareSerial may be used. Pins (0-19) 28 | // on the Uno may be used. Other boards can use any of the pins 29 | // allowed by digitalPinToPCMSK in pins_arduino.h 30 | // 31 | // This code uses a pin change interrupt on the selected RX pin. 32 | // Transmission on the TX line is done in a loop with interrupts disabled. 33 | // Both RX and TX read timer0 for determining elapsed time. Timer0 itself is 34 | // not reprogrammed; it is assumed to be running with a 4 microsecond step. 35 | // 36 | // By default NeoSWSerial defines handlers for all PCINT interrupts like 37 | // SoftwareSerial. If client code requires own pin change interrupt handlers, 38 | // it's possible to rebuild library with #define NEOSWSERIAL_EXTERNAL_PCINT. 39 | // In such case client code should call NeoSWSerial::rxISR(PINB) (assuming 40 | // that receivePin is on PORT B) 41 | // 42 | // Supported baud rates are 9600 (default), 19200 and 38400. 43 | // The baud rate is selectable at run time. 44 | // 45 | // The size of the RX buffer may be changed by editing the 46 | // accompanying .cpp file. For optimal performance of the interrupt 47 | // service routines, the buffer size should be chosen to be a 48 | // power of 2 (i.e., 2, 4, 8, 16, 32, 64,...). 49 | // 50 | // v1.0 Nov 2014 jboyton - Created 51 | // v1.1 Jun 2015 jboyton - Added support for 8 MHz system clock. Timer 2 had to 52 | // be used since the timer 0 prescaler was inadequate 53 | // for this. The supported baud rates for 8 MHz are 9600 54 | // and 19200. 55 | // v2.0 Nov 2015 SlashDev - Add support for other boards, 56 | // add end() and attach/detachInterrupt 57 | // v2.1 Jun 2016 SlashDev - Add support for all character values 58 | // v2.2 Mar 2017 Dionorgua - Add option to disable pre-defined PCINT ISRs. 59 | // v2.3 Mar 2017 SlashDev - Add GPL 60 | // v3.0.0 May 2017 SlashDev - Convert to new Arduino IDE library 61 | 62 | class NeoSWSerial : public Stream 63 | { 64 | NeoSWSerial( const NeoSWSerial & ); // Not allowed 65 | NeoSWSerial & operator =( const NeoSWSerial & ); // Not allowed 66 | 67 | public: 68 | NeoSWSerial(uint8_t receivePin, uint8_t transmitPin) 69 | { 70 | rxPin = receivePin; 71 | txPin = transmitPin; 72 | _isr = (isr_t) NULL; 73 | } 74 | 75 | void begin(uint16_t baudRate=9600); // initialize, set baudrate, listen 76 | void listen(); // enable RX interrupts 77 | void ignore(); // disable RX interrupts 78 | void setBaudRate(uint16_t baudRate); // 9600 [default], 19200, 38400 79 | virtual int available(); 80 | virtual int read(); 81 | virtual size_t write(uint8_t txChar); 82 | using Stream::write; // make the base class overloads visible 83 | virtual int peek() { return 0; }; 84 | virtual void flush() {}; 85 | void end() { ignore(); } 86 | 87 | typedef void (* isr_t)( uint8_t ); 88 | void attachInterrupt( isr_t fn ); 89 | void detachInterrupt() { attachInterrupt( (isr_t) NULL ); }; 90 | 91 | private: 92 | uint8_t rxPin, txPin; 93 | volatile uint8_t *rxPort; 94 | 95 | uint16_t _baudRate; 96 | isr_t _isr; 97 | 98 | static void rxChar( uint8_t rx ); // buffer or dispatch one received character 99 | 100 | bool checkRxTime(); 101 | 102 | static void startChar(); 103 | 104 | public: 105 | // visible only so the ISRs can call it... 106 | static void rxISR( uint8_t port_input_register ); 107 | 108 | //#define NEOSWSERIAL_EXTERNAL_PCINT // uncomment to use your own PCINT ISRs 109 | }; 110 | #endif 111 | -------------------------------------------------------------------------------- /test/test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | NeoSWSerial nss( 52, 53 ); 4 | 5 | uint16_t baudrate = 9600; 6 | uint8_t rc[520]; 7 | uint16_t received = 0; 8 | uint16_t sent = 0; 9 | 10 | //--------------------------------------------------------------------- 11 | //#define INTER_CHAR_TIME 50 12 | #define INTER_CHAR_TIME 0 13 | 14 | //#define BITDUMP 15 | 16 | #ifdef BITDUMP 17 | extern uint8_t bitTransitionTimes[]; 18 | extern uint8_t bitTransitions; 19 | struct rbd_t { uint8_t loop_bits; uint8_t mul_bits; uint16_t prod; }; 20 | extern rbd_t diffRXbits[]; 21 | extern uint8_t diffRXbitsCount; 22 | 23 | extern uint16_t availCompletions; 24 | extern uint16_t rxStartCompletions; 25 | extern uint8_t rxStartCompletionBits[]; 26 | extern uint16_t checkRxCompletions; 27 | extern uint16_t polledPCI; 28 | extern uint16_t polledPCICompletions; 29 | extern uint16_t stopBitCompletions; 30 | extern uint16_t highBitWaits; 31 | #endif 32 | 33 | //--------------------------------------------------------------------- 34 | 35 | static void dumpBits() 36 | { 37 | #ifdef BITDUMP 38 | for (uint8_t i=0; i 0) { 51 | Serial.print( F(" (") ); 52 | for (uint8_t i=0;;) { 53 | Serial.print( diffRXbits[i].prod ); 54 | Serial.print( ' ' ); 55 | Serial.print( diffRXbits[i].mul_bits ); 56 | Serial.print( F(" != ") ); 57 | Serial.print( diffRXbits[i].loop_bits ); 58 | i++; 59 | if (i >= diffRXbitsCount) 60 | break; 61 | Serial.print( F(", ") ); 62 | } 63 | Serial.print( ')' ); 64 | diffRXbitsCount = 0; 65 | } 66 | #endif 67 | } // dumpDiffs 68 | 69 | //--------------------------------------------------------------------- 70 | 71 | static uint16_t errors = 0; 72 | 73 | void testOne( Stream & ins, Stream & outs, uint8_t c ) 74 | { 75 | uint8_t pair[2]; 76 | pair[0] = c; 77 | pair[1] = c+1; 78 | uint8_t len = 1; 79 | outs.write( pair, len ); 80 | if (INTER_CHAR_TIME < 10) 81 | outs.flush(); 82 | 83 | static bool dotPrinted = false; 84 | 85 | uint8_t received = 0; 86 | bool gotIt = false; 87 | uint32_t start = millis(); 88 | 89 | do { 90 | if (ins.available()) { 91 | uint8_t received_c = ins.read(); 92 | 93 | dotPrinted = false; 94 | 95 | gotIt = (received_c == c); 96 | 97 | if (!gotIt || (INTER_CHAR_TIME > 10)) { 98 | Serial.print( F("rx ") ); 99 | if (received_c < 0x10) Serial.print( '0' ); 100 | Serial.print( received_c, HEX ); 101 | 102 | if (!gotIt) { 103 | Serial.print( F(" != tx ") ); 104 | if (c < 0x10) Serial.print( '0' ); 105 | Serial.print( 0xFF & (c), HEX ); 106 | } 107 | 108 | if (received == 0) { 109 | dumpBits(); 110 | dumpDiffs(); 111 | Serial.print( ' ' ); 112 | } 113 | } 114 | 115 | received++; 116 | c++; 117 | } 118 | } while (millis() - start < INTER_CHAR_TIME); 119 | 120 | if (INTER_CHAR_TIME > 10) { 121 | if (!received) { 122 | Serial.print( '.' ); 123 | dumpBits(); 124 | dumpDiffs(); 125 | Serial.println(); 126 | dotPrinted = true; 127 | } else 128 | Serial.println(); 129 | 130 | Serial.flush(); 131 | } 132 | 133 | } // testOne 134 | 135 | //--------------------------------------------------------------------- 136 | 137 | static void testTwo( uint8_t & c, uint8_t & received_c ) 138 | { 139 | if (nss.available()) { 140 | uint8_t rcActual = nss.read(); 141 | if (rcActual != received_c) { 142 | errors++; 143 | Serial.print( F("rx ") ); 144 | if (received_c < 0x10) Serial.print( '0' ); 145 | Serial.print( 0xFF & rcActual, HEX ); 146 | Serial.print( F(" != tx ") ); 147 | if (received_c < 0x10) Serial.print( '0' ); 148 | Serial.print( 0xFF & (received_c), HEX ); 149 | dumpBits(); 150 | dumpDiffs(); 151 | Serial.println(); 152 | } 153 | received_c++; 154 | } 155 | } // testTwo 156 | 157 | //--------------------------------------------------------------------- 158 | 159 | static void printStats() 160 | { 161 | if (received != sent) { 162 | Serial.print( received ); 163 | Serial.print( F(" received, ") ); 164 | Serial.print( sent ); 165 | Serial.println( F(" sent") ); 166 | } 167 | 168 | Serial.print( errors ); 169 | Serial.println( F(" errors") ); 170 | #ifdef BITDUMP 171 | Serial.print( rxStartCompletions ); 172 | Serial.print( F(" RX start completions:") ); 173 | for (uint16_t i=0; i < rxStartCompletions; i++) { 174 | Serial.print( ' ' ); 175 | Serial.print( rxStartCompletionBits[i] ); 176 | } 177 | Serial.println(); 178 | 179 | Serial.print( availCompletions ); 180 | Serial.println( F(" available() completions") ); 181 | Serial.print( checkRxCompletions ); 182 | Serial.println( F(" checkRxTime completions") ); 183 | Serial.print( polledPCI ); 184 | Serial.println( F(" polled PCI detected") ); 185 | Serial.print( polledPCICompletions ); 186 | Serial.println( F(" polled PCI completions") ); 187 | Serial.print( stopBitCompletions ); 188 | Serial.println( F(" stop bit completions") ); 189 | Serial.print( highBitWaits ); 190 | Serial.println( F(" high bit waits") ); 191 | 192 | rxStartCompletions = 0; 193 | availCompletions = 0; 194 | checkRxCompletions = 0; 195 | polledPCI = 0; 196 | polledPCICompletions = 0; 197 | stopBitCompletions = 0; 198 | highBitWaits = 0; 199 | #endif 200 | 201 | Serial.println( F("-----------------") ); 202 | Serial.flush(); 203 | 204 | received = 0; 205 | sent = 0; 206 | errors = 0; 207 | 208 | } // printStats 209 | 210 | //--------------------------------------------------------------------- 211 | 212 | static void printRC() 213 | { 214 | for (uint16_t i=0; i < received; i++) { 215 | uint8_t c = rc[i]; 216 | if (c < 0x10) Serial.print( '0' ); 217 | Serial.print( 0xFF & (c), HEX ); 218 | Serial.print( ' ' ); 219 | if ((i & 0x1F) == 0x1F) 220 | Serial.println(); 221 | } 222 | Serial.println(); 223 | } // printRC 224 | 225 | //--------------------------------------------------------------------- 226 | 227 | void setup() { 228 | //Initialize serial and wait for port to open: 229 | Serial.begin(9600); 230 | while (!Serial) 231 | ; 232 | 233 | Serial3.begin( baudrate ); 234 | delay( 10 ); 235 | Serial3.print( 'U' ); 236 | Serial3.flush(); 237 | delay( 10 ); 238 | 239 | nss.begin( baudrate ); 240 | 241 | } // setup 242 | 243 | //--------------------------------------------------------------------- 244 | 245 | void loop() 246 | { 247 | Serial.print( F("NeoSWSerial test @ ") ); 248 | Serial.println( baudrate ); 249 | Serial.flush(); 250 | 251 | Serial.println( F("Individual RX test") ); 252 | Serial.flush(); 253 | 254 | uint8_t c=0; 255 | do { 256 | testOne( nss, Serial3, c ); 257 | c++; 258 | } while (c != 0); 259 | testOne( nss, Serial3, c ); 260 | uint32_t start = millis(); 261 | while (millis() - start < 100) { 262 | if (nss.available()) { 263 | nss.read(); 264 | start = millis(); 265 | } 266 | } 267 | 268 | printStats(); 269 | 270 | //===================================== 271 | 272 | Serial.println( F("RX test") ); 273 | Serial.flush(); 274 | 275 | for (uint8_t times=0; times<1; times++) { 276 | do { 277 | Serial3.write( c++ ); 278 | sent++; 279 | if (nss.available()) 280 | rc[ received++ ] = nss.read(); 281 | } while (c != 0); 282 | } 283 | Serial3.write( c++ ); 284 | sent++; 285 | 286 | start = millis(); 287 | while (millis() - start < 100) { 288 | if (nss.available()) { 289 | rc[ received++ ] = nss.read(); 290 | start = millis(); 291 | } 292 | } 293 | 294 | if (received < sent) { 295 | Serial.print( sent-received ); 296 | Serial.println( F(" chars dropped.") ); 297 | } 298 | printRC(); 299 | 300 | printStats(); 301 | 302 | //===================================== 303 | 304 | Serial.println( F("TX test") ); 305 | Serial.flush(); 306 | 307 | for (uint16_t i=0; i<=256; i++) { 308 | nss.write( (uint8_t) (i & 0xFF) ); 309 | if (Serial3.available()) 310 | rc[ received++ ] = Serial3.read(); 311 | } 312 | 313 | start = millis(); 314 | 315 | uint32_t ms; 316 | do { 317 | ms = millis(); 318 | if (Serial3.available()) { 319 | start = ms; 320 | rc[ received++ ] = Serial3.read(); 321 | } 322 | } while (ms - start < 100); 323 | 324 | printRC(); 325 | 326 | printStats(); 327 | 328 | //===================================== 329 | 330 | Serial.println( F("RX and TX test") ); 331 | Serial.flush(); 332 | 333 | for (uint16_t i=0; i<=256; i++) { 334 | Serial3.write( (uint8_t) i & 0xFF ); 335 | if (nss.available()) { 336 | c = nss.read(); 337 | nss.write( c ); 338 | } 339 | if (Serial3.available()) 340 | rc[ received++ ] = Serial3.read(); 341 | } 342 | 343 | start = millis(); 344 | 345 | do { 346 | ms = millis(); 347 | if (nss.available()) { 348 | start = ms; 349 | c = nss.read(); 350 | nss.write( c ); 351 | } 352 | if (Serial3.available()) { 353 | start = ms; 354 | rc[ received++ ] = Serial3.read(); 355 | } 356 | } while (ms - start < 100); 357 | 358 | printRC(); 359 | 360 | printStats(); 361 | 362 | //===================================== 363 | 364 | Serial.println( F("TX and RX test") ); 365 | Serial.flush(); 366 | 367 | for (uint16_t i=0; i<=256; i++) { 368 | nss.write( (uint8_t) i & 0xFF ); 369 | if (Serial3.available()) { 370 | c = Serial3.read(); 371 | Serial3.write( c ); 372 | } 373 | while (nss.available()) 374 | rc[ received++ ] = nss.read(); 375 | } 376 | 377 | start = millis(); 378 | 379 | do { 380 | ms = millis(); 381 | if (Serial3.available()) { 382 | start = ms; 383 | c = Serial3.read(); 384 | Serial3.write( c ); 385 | } 386 | while (nss.available()) { 387 | start = ms; 388 | rc[ received++ ] = nss.read(); 389 | } 390 | } while (ms - start < 100); 391 | 392 | printRC(); 393 | 394 | printStats(); 395 | 396 | //===================================== 397 | 398 | while (!Serial.available()) 399 | ; 400 | 401 | do { 402 | while (Serial.available()) { 403 | Serial.read(); 404 | start = millis(); 405 | } 406 | } while (millis() - start < 20); 407 | 408 | if (baudrate == 38400) 409 | baudrate = 9600; 410 | else 411 | baudrate <<= 1; 412 | nss.setBaudRate( baudrate ); 413 | Serial3.begin( baudrate ); 414 | } 415 | --------------------------------------------------------------------------------