├── .gitignore ├── README ├── SoftwareSerialWithHalfDuplex.cpp ├── SoftwareSerialWithHalfDuplex.h ├── examples ├── SoftwareSerialExample │ └── SoftwareSerialExample.ino ├── SoftwareSerialWithHalfDuplex_test_PartB │ └── SoftwareSerialWithHalfDuplex_test_PartB.ino ├── SoftwareSerialWithHalfDuplex_test_partA │ └── SoftwareSerialWithHalfDuplex_test_partA.ino └── TwoPortReceive │ └── TwoPortReceive.ino └── keywords.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | SoftwareSerialWithHalfDuplex (formerly SoftwareSerial) - 2 | Multi-instance software serial with half duplex library for Arduino/Wiring 3 | 4 | By default the library works the same as the SoftwareSerial library, 5 | but by adding a couple of additional arguments it can be configured for 6 | half-duplex. In that case, the transmit pin is set by default to an input, 7 | with the pull-up set. When transmitting, the pin temporarily switches to 8 | an output until the byte is sent, then flips back to input. When a module 9 | is receiving it should not be able to transmit, and vice-versa. 10 | This library probably won't work as is if you need inverted-logic. 11 | 12 | This is a first draft of the library and test programs. It appears to work, 13 | but has only been tested on a limited basis. The library also works with 14 | Robotis Bioloid AX-12 motors. Seems fairly reliable up to 57600 baud. 15 | As with all serial neither error checking, nor addressing are implemented, 16 | so it is likely that you will need to do this yourself. Also, you can make 17 | use of other protocols such as i2c. I am looking for any feedback, advice 18 | and help at this stage. Changes from SoftwareSerial have been noted with a 19 | comment of "//NS" for your review. Only a few were required. 20 | Contact me at n.stedman@steddyrobots.com, or on the arduino forum. 21 | 22 | This library is free software; you can redistribute it and/or 23 | modify it under the terms of the GNU Lesser General Public 24 | License as published by the Free Software Foundation; either 25 | version 2.1 of the License, or (at your option) any later version. 26 | 27 | This library is distributed in the hope that it will be useful, 28 | but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 30 | Lesser General Public License for more details. 31 | 32 | You should have received a copy of the GNU Lesser General Public 33 | License along with this library; if not, write to the Free Software 34 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -------------------------------------------------------------------------------- /SoftwareSerialWithHalfDuplex.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SoftwareSerialWithHalfDuplex.cpp (formerly SoftwareSerial.cpp) - 3 | Multi-instance software serial with half duplex library for Arduino/Wiring 4 | 5 | By default the library works the same as the SoftwareSerial library, but by adding a couple of additional arguments 6 | it can be configured for half-duplex. In that case, the transmit pin is set by default to an input, with the pull-up set. 7 | When transmitting, the pin temporarily switches to an output until the byte is sent, then flips back to input. When a module is 8 | receiving it should not be able to transmit, and vice-versa. This library probably won't work as is if you need inverted-logic. 9 | 10 | This is a first draft of the library and test programs. It appears to work, but has only been tested on a limited basis. 11 | The library also works to communicate with Robotis Bioloid AX-12 motors. 12 | Seems fairly reliable up to 57600 baud. As with all serial neither error checking, nor addressing are implemented, 13 | so it is likely that you will need to do this yourself. Also, you can make use of other protocols such as i2c. 14 | I am looking for any feedback, advice and help at this stage. 15 | Changes from SoftwareSerial have been noted with a comment of "//NS" for your review. Only a few were required. 16 | Contact me at n.stedman@steddyrobots.com, or on the arduino forum. 17 | ---- 18 | SoftwareSerial.cpp (formerly NewSoftSerial.cpp) - 19 | Multi-instance software serial library for Arduino/Wiring 20 | -- Interrupt-driven receive and other improvements by ladyada 21 | (http://ladyada.net) 22 | -- Tuning, circular buffer, derivation from class Print/Stream, 23 | multi-instance support, porting to 8MHz processors, 24 | various optimizations, PROGMEM delay tables, inverse logic and 25 | direct port writing by Mikal Hart (http://www.arduiniana.org) 26 | -- Pin change interrupt macros by Paul Stoffregen (http://www.pjrc.com) 27 | -- 20MHz processor support by Garrett Mace (http://www.macetech.com) 28 | -- ATmega1280/2560 support by Brett Hagman (http://www.roguerobotics.com/) 29 | 30 | This library is free software; you can redistribute it and/or 31 | modify it under the terms of the GNU Lesser General Public 32 | License as published by the Free Software Foundation; either 33 | version 2.1 of the License, or (at your option) any later version. 34 | 35 | This library is distributed in the hope that it will be useful, 36 | but WITHOUT ANY WARRANTY; without even the implied warranty of 37 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 38 | Lesser General Public License for more details. 39 | 40 | You should have received a copy of the GNU Lesser General Public 41 | License along with this library; if not, write to the Free Software 42 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 43 | */ 44 | 45 | 46 | // When set, _DEBUG co-opts pins 11 and 13 for debugging with an 47 | // oscilloscope or logic analyzer. Beware: it also slightly modifies 48 | // the bit times, so don't rely on it too much at high baud rates 49 | #define _DEBUG 0 50 | #define _DEBUG_PIN1 11 51 | #define _DEBUG_PIN2 13 52 | // 53 | // Includes 54 | // 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | // 62 | // Statics 63 | // 64 | SoftwareSerialWithHalfDuplex *SoftwareSerialWithHalfDuplex::active_object = 0; 65 | char SoftwareSerialWithHalfDuplex::_receive_buffer[_SS_MAX_RX_BUFF]; 66 | volatile uint8_t SoftwareSerialWithHalfDuplex::_receive_buffer_tail = 0; 67 | volatile uint8_t SoftwareSerialWithHalfDuplex::_receive_buffer_head = 0; 68 | 69 | // 70 | // Debugging 71 | // 72 | // This function generates a brief pulse 73 | // for debugging or measuring on an oscilloscope. 74 | inline void DebugPulse(uint8_t pin, uint8_t count) 75 | { 76 | #if _DEBUG 77 | volatile uint8_t *pport = portOutputRegister(digitalPinToPort(pin)); 78 | 79 | uint8_t val = *pport; 80 | while (count--) 81 | { 82 | *pport = val | digitalPinToBitMask(pin); 83 | *pport = val; 84 | } 85 | #endif 86 | } 87 | 88 | // 89 | // Private methods 90 | // 91 | 92 | /* static */ 93 | inline void SoftwareSerialWithHalfDuplex::tunedDelay(uint16_t delay) { 94 | _delay_loop_2(delay); 95 | } 96 | 97 | // This function sets the current object as the "listening" 98 | // one and returns true if it replaces another 99 | bool SoftwareSerialWithHalfDuplex::listen() 100 | { 101 | if (!_rx_delay_stopbit) 102 | return false; 103 | 104 | if (active_object != this) 105 | { 106 | if (active_object) 107 | active_object->stopListening(); 108 | 109 | _buffer_overflow = false; 110 | _receive_buffer_head = _receive_buffer_tail = 0; 111 | active_object = this; 112 | 113 | setRxIntMsk(true); 114 | return true; 115 | } 116 | 117 | return false; 118 | } 119 | 120 | // Stop listening. Returns true if we were actually listening. 121 | bool SoftwareSerialWithHalfDuplex::stopListening() 122 | { 123 | if (active_object == this) 124 | { 125 | setRxIntMsk(false); 126 | active_object = NULL; 127 | return true; 128 | } 129 | return false; 130 | } 131 | 132 | // 133 | // The receive routine called by the interrupt handler 134 | // 135 | void SoftwareSerialWithHalfDuplex::recv() 136 | { 137 | 138 | #if GCC_VERSION < 40302 139 | // Work-around for avr-gcc 4.3.0 OSX version bug 140 | // Preserve the registers that the compiler misses 141 | // (courtesy of Arduino forum user *etracer*) 142 | asm volatile( 143 | "push r18 \n\t" 144 | "push r19 \n\t" 145 | "push r20 \n\t" 146 | "push r21 \n\t" 147 | "push r22 \n\t" 148 | "push r23 \n\t" 149 | "push r26 \n\t" 150 | "push r27 \n\t" 151 | ::); 152 | #endif 153 | 154 | uint8_t d = 0; 155 | 156 | // If RX line is high, then we don't see any start bit 157 | // so interrupt is probably not for us 158 | if (_inverse_logic ? rx_pin_read() : !rx_pin_read()) 159 | { 160 | // Disable further interrupts during reception, this prevents 161 | // triggering another interrupt directly after we return, which can 162 | // cause problems at higher baudrates. 163 | setRxIntMsk(false); 164 | 165 | // Wait approximately 1/2 of a bit width to "center" the sample 166 | tunedDelay(_rx_delay_centering); 167 | DebugPulse(_DEBUG_PIN2, 1); 168 | 169 | // Read each of the 8 bits 170 | for (uint8_t i=8; i > 0; --i) 171 | { 172 | tunedDelay(_rx_delay_intrabit); 173 | d >>= 1; 174 | DebugPulse(_DEBUG_PIN2, 1); 175 | if (rx_pin_read()) 176 | d |= 0x80; 177 | } 178 | 179 | if (_inverse_logic) 180 | d = ~d; 181 | 182 | // if buffer full, set the overflow flag and return 183 | uint8_t next = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF; 184 | if (next != _receive_buffer_head) 185 | { 186 | // save new data in buffer: tail points to where byte goes 187 | _receive_buffer[_receive_buffer_tail] = d; // save new byte 188 | _receive_buffer_tail = next; 189 | } 190 | else 191 | { 192 | DebugPulse(_DEBUG_PIN1, 1); 193 | _buffer_overflow = true; 194 | } 195 | 196 | // skip the stop bit 197 | tunedDelay(_rx_delay_stopbit); 198 | DebugPulse(_DEBUG_PIN1, 1); 199 | 200 | // Re-enable interrupts when we're sure to be inside the stop bit 201 | setRxIntMsk(true); 202 | 203 | } 204 | 205 | #if GCC_VERSION < 40302 206 | // Work-around for avr-gcc 4.3.0 OSX version bug 207 | // Restore the registers that the compiler misses 208 | asm volatile( 209 | "pop r27 \n\t" 210 | "pop r26 \n\t" 211 | "pop r23 \n\t" 212 | "pop r22 \n\t" 213 | "pop r21 \n\t" 214 | "pop r20 \n\t" 215 | "pop r19 \n\t" 216 | "pop r18 \n\t" 217 | ::); 218 | #endif 219 | } 220 | 221 | uint8_t SoftwareSerialWithHalfDuplex::rx_pin_read() 222 | { 223 | return *_receivePortRegister & _receiveBitMask; 224 | } 225 | 226 | // 227 | // Interrupt handling 228 | // 229 | 230 | /* static */ 231 | inline void SoftwareSerialWithHalfDuplex::handle_interrupt() 232 | { 233 | if (active_object) 234 | { 235 | active_object->recv(); 236 | } 237 | } 238 | 239 | #if defined(PCINT0_vect) 240 | ISR(PCINT0_vect) 241 | { 242 | SoftwareSerialWithHalfDuplex::handle_interrupt(); 243 | } 244 | #endif 245 | 246 | #if defined(PCINT1_vect) 247 | ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect)); 248 | #endif 249 | 250 | #if defined(PCINT2_vect) 251 | ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect)); 252 | #endif 253 | 254 | #if defined(PCINT3_vect) 255 | ISR(PCINT3_vect, ISR_ALIASOF(PCINT0_vect)); 256 | #endif 257 | 258 | // 259 | // Constructor 260 | // 261 | SoftwareSerialWithHalfDuplex::SoftwareSerialWithHalfDuplex(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic /* = false */, bool full_duplex /* = true */) : 262 | _rx_delay_centering(0), 263 | _rx_delay_intrabit(0), 264 | _rx_delay_stopbit(0), 265 | _tx_delay(0), 266 | _buffer_overflow(false), 267 | _inverse_logic(inverse_logic) 268 | { 269 | // @micooke - passing half_duplex is fairly pointless as you can determine it from the tx and rx pin chose. 270 | // Im inclined to remove full_duplex as an argument. 271 | // This change allows the user to test half-duplex with different or the same pins 272 | _full_duplex = (transmitPin == receivePin)?false:full_duplex; //NS Added 273 | setTX(transmitPin); 274 | setRX(receivePin); 275 | } 276 | 277 | // 278 | // Destructor 279 | // 280 | SoftwareSerialWithHalfDuplex::~SoftwareSerialWithHalfDuplex() 281 | { 282 | end(); 283 | } 284 | 285 | void SoftwareSerialWithHalfDuplex::setTX(uint8_t tx) 286 | { 287 | // First write, then set output. If we do this the other way around, 288 | // the pin would be output low for a short while before switching to 289 | // output high. Now, it is input with pullup for a short while, which 290 | // is fine. With inverse logic, either order is fine. 291 | digitalWrite(tx, _inverse_logic ? LOW : HIGH); 292 | if(_full_duplex) pinMode(tx, OUTPUT); //NS Added 293 | else pinMode(tx, INPUT); //NS Added 294 | _transmitPin = tx; //NS Added 295 | 296 | _transmitBitMask = digitalPinToBitMask(tx); 297 | uint8_t port = digitalPinToPort(tx); 298 | _transmitPortRegister = portOutputRegister(port); 299 | } 300 | 301 | void SoftwareSerialWithHalfDuplex::setRX(uint8_t rx) 302 | { 303 | pinMode(rx, INPUT); 304 | if (!_inverse_logic) 305 | digitalWrite(rx, HIGH); // pullup for normal logic! 306 | _receivePin = rx; 307 | _receiveBitMask = digitalPinToBitMask(rx); 308 | uint8_t port = digitalPinToPort(rx); 309 | _receivePortRegister = portInputRegister(port); 310 | } 311 | 312 | uint16_t SoftwareSerialWithHalfDuplex::subtract_cap(uint16_t num, uint16_t sub) { 313 | if (num > sub) 314 | return num - sub; 315 | else 316 | return 1; 317 | } 318 | 319 | // 320 | // Public methods 321 | // 322 | 323 | void SoftwareSerialWithHalfDuplex::begin(long speed) 324 | { 325 | _rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0; 326 | 327 | // Precalculate the various delays, in number of 4-cycle delays 328 | uint16_t bit_delay = (F_CPU / speed) / 4; 329 | 330 | // 12 (gcc 4.8.2) or 13 (gcc 4.3.2) cycles from start bit to first bit, 331 | // 15 (gcc 4.8.2) or 16 (gcc 4.3.2) cycles between bits, 332 | // 12 (gcc 4.8.2) or 14 (gcc 4.3.2) cycles from last bit to stop bit 333 | // These are all close enough to just use 15 cycles, since the inter-bit 334 | // timings are the most critical (deviations stack 8 times) 335 | _tx_delay = subtract_cap(bit_delay, 15 / 4); 336 | 337 | // Only setup rx when we have a valid PCINT for this pin 338 | if (digitalPinToPCICR(_receivePin)) { 339 | #if GCC_VERSION > 40800 340 | // Timings counted from gcc 4.8.2 output. This works up to 115200 on 341 | // 16Mhz and 57600 on 8Mhz. 342 | // 343 | // When the start bit occurs, there are 3 or 4 cycles before the 344 | // interrupt flag is set, 4 cycles before the PC is set to the right 345 | // interrupt vector address and the old PC is pushed on the stack, 346 | // and then 75 cycles of instructions (including the RJMP in the 347 | // ISR vector table) until the first delay. After the delay, there 348 | // are 17 more cycles until the pin value is read (excluding the 349 | // delay in the loop). 350 | // We want to have a total delay of 1.5 bit time. Inside the loop, 351 | // we already wait for 1 bit time - 23 cycles, so here we wait for 352 | // 0.5 bit time - (71 + 18 - 22) cycles. 353 | _rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 75 + 17 - 23) / 4); 354 | 355 | // There are 23 cycles in each loop iteration (excluding the delay) 356 | _rx_delay_intrabit = subtract_cap(bit_delay, 23 / 4); 357 | 358 | // There are 37 cycles from the last bit read to the start of 359 | // stopbit delay and 11 cycles from the delay until the interrupt 360 | // mask is enabled again (which _must_ happen during the stopbit). 361 | // This delay aims at 3/4 of a bit time, meaning the end of the 362 | // delay will be at 1/4th of the stopbit. This allows some extra 363 | // time for ISR cleanup, which makes 115200 baud at 16Mhz work more 364 | // reliably 365 | _rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (37 + 11) / 4); 366 | #else // Timings counted from gcc 4.3.2 output 367 | // Note that this code is a _lot_ slower, mostly due to bad register 368 | // allocation choices of gcc. This works up to 57600 on 16Mhz and 369 | // 38400 on 8Mhz. 370 | _rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 97 + 29 - 11) / 4); 371 | _rx_delay_intrabit = subtract_cap(bit_delay, 11 / 4); 372 | _rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (44 + 17) / 4); 373 | #endif 374 | 375 | 376 | // Enable the PCINT for the entire port here, but never disable it 377 | // (others might also need it, so we disable the interrupt by using 378 | // the per-pin PCMSK register). 379 | *digitalPinToPCICR(_receivePin) |= _BV(digitalPinToPCICRbit(_receivePin)); 380 | // Precalculate the pcint mask register and value, so setRxIntMask 381 | // can be used inside the ISR without costing too much time. 382 | _pcint_maskreg = digitalPinToPCMSK(_receivePin); 383 | _pcint_maskvalue = _BV(digitalPinToPCMSKbit(_receivePin)); 384 | 385 | tunedDelay(_tx_delay); // if we were low this establishes the end 386 | } 387 | 388 | #if _DEBUG 389 | pinMode(_DEBUG_PIN1, OUTPUT); 390 | pinMode(_DEBUG_PIN2, OUTPUT); 391 | #endif 392 | 393 | listen(); 394 | } 395 | 396 | void SoftwareSerialWithHalfDuplex::setRxIntMsk(bool enable) 397 | { 398 | if (enable) 399 | *_pcint_maskreg |= _pcint_maskvalue; 400 | else 401 | *_pcint_maskreg &= ~_pcint_maskvalue; 402 | } 403 | 404 | void SoftwareSerialWithHalfDuplex::end() 405 | { 406 | stopListening(); 407 | } 408 | 409 | 410 | // Read data from buffer 411 | int SoftwareSerialWithHalfDuplex::read() 412 | { 413 | if (!isListening()) 414 | return -1; 415 | 416 | // Empty buffer? 417 | if (_receive_buffer_head == _receive_buffer_tail) 418 | return -1; 419 | 420 | // Read from "head" 421 | uint8_t d = _receive_buffer[_receive_buffer_head]; // grab next byte 422 | _receive_buffer_head = (_receive_buffer_head + 1) % _SS_MAX_RX_BUFF; 423 | return d; 424 | } 425 | 426 | int SoftwareSerialWithHalfDuplex::available() 427 | { 428 | if (!isListening()) 429 | return 0; 430 | 431 | return (_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head) % _SS_MAX_RX_BUFF; 432 | } 433 | 434 | size_t SoftwareSerialWithHalfDuplex::write(uint8_t b) 435 | { 436 | if (_tx_delay == 0) { 437 | setWriteError(); 438 | return 0; 439 | } 440 | 441 | // By declaring these as local variables, the compiler will put them 442 | // in registers _before_ disabling interrupts and entering the 443 | // critical timing sections below, which makes it a lot easier to 444 | // verify the cycle timings 445 | volatile uint8_t *reg = _transmitPortRegister; 446 | uint8_t reg_mask = _transmitBitMask; 447 | uint8_t inv_mask = ~_transmitBitMask; 448 | uint8_t oldSREG = SREG; 449 | bool inv = _inverse_logic; 450 | uint16_t delay = _tx_delay; 451 | 452 | if (inv) 453 | b = ~b; 454 | 455 | cli(); // turn off interrupts for a clean txmit 456 | 457 | // NS - Set Pin to Output 458 | if(!_full_duplex) //NS Added 459 | pinMode(_transmitPin, OUTPUT); //NS Added 460 | 461 | // Write the start bit 462 | if (inv) 463 | *reg |= reg_mask; 464 | else 465 | *reg &= inv_mask; 466 | 467 | tunedDelay(delay); 468 | 469 | // Write each of the 8 bits 470 | for (uint8_t i = 8; i > 0; --i) 471 | { 472 | if (b & 1) // choose bit 473 | *reg |= reg_mask; // send 1 474 | else 475 | *reg &= inv_mask; // send 0 476 | 477 | tunedDelay(delay); 478 | b >>= 1; 479 | } 480 | 481 | // restore pin to natural state 482 | if (inv) 483 | *reg &= inv_mask; 484 | else 485 | *reg |= reg_mask; 486 | 487 | // NS - Set Pin back to Input 488 | if(!_full_duplex){ 489 | pinMode(_transmitPin, INPUT); //NS Added 490 | *reg |= reg_mask; //pull _transmitPin HIGH 491 | } 492 | SREG = oldSREG; // turn interrupts back on 493 | tunedDelay(_tx_delay); 494 | 495 | return 1; 496 | } 497 | 498 | void SoftwareSerialWithHalfDuplex::flush() 499 | { 500 | if (!isListening()) 501 | return; 502 | 503 | uint8_t oldSREG = SREG; 504 | cli(); 505 | _receive_buffer_head = _receive_buffer_tail = 0; 506 | SREG = oldSREG; 507 | } 508 | 509 | int SoftwareSerialWithHalfDuplex::peek() 510 | { 511 | if (!isListening()) 512 | return -1; 513 | 514 | // Empty buffer? 515 | if (_receive_buffer_head == _receive_buffer_tail) 516 | return -1; 517 | 518 | // Read from "head" 519 | return _receive_buffer[_receive_buffer_head]; 520 | } 521 | -------------------------------------------------------------------------------- /SoftwareSerialWithHalfDuplex.h: -------------------------------------------------------------------------------- 1 | /* 2 | SoftwareSerialWithHalfDuplex.h (formerly SoftwareSerial.h) - 3 | Multi-instance software serial with half duplex library for Arduino/Wiring 4 | 5 | By default the library works the same as the SoftwareSerial library, 6 | but by adding a couple of additional arguments it can be configured for 7 | half-duplex. In that case, the transmit pin is set by default to an input, 8 | with the pull-up set. When transmitting, the pin temporarily switches to 9 | an output until the byte is sent, then flips back to input. When a module 10 | is receiving it should not be able to transmit, and vice-versa. 11 | This library probably won't work as is if you need inverted-logic. 12 | 13 | This is a first draft of the library and test programs. It appears to work, 14 | but has only been tested on a limited basis. The library also works with 15 | Robotis Bioloid AX-12 motors. Seems fairly reliable up to 57600 baud. 16 | As with all serial neither error checking, nor addressing are implemented, 17 | so it is likely that you will need to do this yourself. Also, you can make 18 | use of other protocols such as i2c. I am looking for any feedback, advice 19 | and help at this stage. Changes from SoftwareSerial have been noted with a 20 | comment of "//NS" for your review. Only a few were required. 21 | Contact me at n.stedman@steddyrobots.com, or on the arduino forum. 22 | ---- 23 | SoftwareSerial.cpp (formerly NewSoftSerial.cpp) - 24 | Multi-instance software serial library for Arduino/Wiring 25 | -- Interrupt-driven receive and other improvements by ladyada 26 | (http://ladyada.net) 27 | -- Tuning, circular buffer, derivation from class Print/Stream, 28 | multi-instance support, porting to 8MHz processors, 29 | various optimizations, PROGMEM delay tables, inverse logic and 30 | direct port writing by Mikal Hart (http://www.arduiniana.org) 31 | -- Pin change interrupt macros by Paul Stoffregen (http://www.pjrc.com) 32 | -- 20MHz processor support by Garrett Mace (http://www.macetech.com) 33 | -- ATmega1280/2560 support by Brett Hagman (http://www.roguerobotics.com/) 34 | 35 | This library is free software; you can redistribute it and/or 36 | modify it under the terms of the GNU Lesser General Public 37 | License as published by the Free Software Foundation; either 38 | version 2.1 of the License, or (at your option) any later version. 39 | 40 | This library is distributed in the hope that it will be useful, 41 | but WITHOUT ANY WARRANTY; without even the implied warranty of 42 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 43 | Lesser General Public License for more details. 44 | 45 | You should have received a copy of the GNU Lesser General Public 46 | License along with this library; if not, write to the Free Software 47 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 48 | */ 49 | 50 | #ifndef SoftwareSerialWithHalfDuplex_h 51 | #define SoftwareSerialWithHalfDuplex_h 52 | 53 | #include 54 | #include 55 | 56 | /****************************************************************************** 57 | * Definitions 58 | ******************************************************************************/ 59 | 60 | #define _SS_MAX_RX_BUFF 64 // RX buffer size 61 | #ifndef GCC_VERSION 62 | #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) 63 | #endif 64 | 65 | class SoftwareSerialWithHalfDuplex : public Stream 66 | { 67 | private: 68 | // per object data 69 | uint8_t _receivePin; 70 | uint8_t _receiveBitMask; 71 | volatile uint8_t *_receivePortRegister; 72 | uint8_t _transmitPin; //NS Added 73 | uint8_t _transmitBitMask; 74 | volatile uint8_t *_transmitPortRegister; 75 | volatile uint8_t *_pcint_maskreg; 76 | uint8_t _pcint_maskvalue; 77 | 78 | // Expressed as 4-cycle delays (must never be 0!) 79 | uint16_t _rx_delay_centering; 80 | uint16_t _rx_delay_intrabit; 81 | uint16_t _rx_delay_stopbit; 82 | uint16_t _tx_delay; 83 | 84 | bool _buffer_overflow; 85 | bool _inverse_logic; 86 | bool _full_duplex; //NS Added 87 | 88 | // static data 89 | static char _receive_buffer[_SS_MAX_RX_BUFF]; 90 | static volatile uint8_t _receive_buffer_tail; 91 | static volatile uint8_t _receive_buffer_head; 92 | static SoftwareSerialWithHalfDuplex *active_object; 93 | 94 | // private methods 95 | void recv() __attribute__((__always_inline__)); 96 | uint8_t rx_pin_read(); 97 | //void tx_pin_write(uint8_t pin_state) __attribute__((__always_inline__)); 98 | void setTX(uint8_t transmitPin); 99 | void setRX(uint8_t receivePin); 100 | void setRxIntMsk(bool enable) __attribute__((__always_inline__)); 101 | 102 | // Return num - sub, or 1 if the result would be < 1 103 | static uint16_t subtract_cap(uint16_t num, uint16_t sub); 104 | 105 | // private static method for timing 106 | static inline void tunedDelay(uint16_t delay); 107 | 108 | public: 109 | // public methods 110 | SoftwareSerialWithHalfDuplex(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic = false, bool full_duplex = true); 111 | ~SoftwareSerialWithHalfDuplex(); 112 | void begin(long speed); 113 | bool listen(); 114 | void end(); 115 | bool isListening() { return this == active_object; } 116 | bool stopListening(); 117 | bool overflow() { bool ret = _buffer_overflow; if (ret) _buffer_overflow = false; return ret; } 118 | int peek(); 119 | 120 | virtual size_t write(uint8_t byte); 121 | virtual int read(); 122 | virtual int available(); 123 | virtual void flush(); 124 | operator bool() { return true; } 125 | 126 | using Print::write; 127 | 128 | // public only for easy access by interrupt handlers 129 | static inline void handle_interrupt() __attribute__((__always_inline__)); 130 | }; 131 | 132 | // Arduino 0012 workaround 133 | #ifdef int 134 | #undef int 135 | #undef char 136 | #undef long 137 | #undef byte 138 | #undef float 139 | #undef abs 140 | #undef round 141 | #endif 142 | #endif 143 | -------------------------------------------------------------------------------- /examples/SoftwareSerialExample/SoftwareSerialExample.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Software serial multple serial test 3 | 4 | Receives from the hardware serial, sends to software serial. 5 | Receives from software serial, sends to hardware serial. 6 | 7 | The circuit: 8 | * RX is digital pin 2 (connect to TX of other device) 9 | * TX is digital pin 3 (connect to RX of other device) 10 | 11 | created back in the mists of time 12 | modified 9 Apr 2012 13 | by Tom Igoe 14 | based on Mikal Hart's example 15 | modified for SoftwareSerialWithHalfDuplex 16 | by Nick Stedman 15 July 2012 17 | 18 | This example code is in the public domain. 19 | 20 | */ 21 | #include 22 | 23 | SoftwareSerialWithHalfDuplex mySerial(2, 3); // RX, TX 24 | 25 | void setup() 26 | { 27 | // Open serial communications and wait for port to open: 28 | Serial.begin(57600); 29 | while (!Serial) { 30 | ; // wait for serial port to connect. Needed for Leonardo only 31 | } 32 | 33 | 34 | Serial.println("Goodnight moon!"); 35 | 36 | // set the data rate for the SoftwareSerial port 37 | mySerial.begin(4800); 38 | mySerial.println("Hello, world?"); 39 | } 40 | 41 | void loop() // run over and over 42 | { 43 | if (mySerial.available()) 44 | Serial.write(mySerial.read()); 45 | if (Serial.available()) 46 | mySerial.write(Serial.read()); 47 | } 48 | -------------------------------------------------------------------------------- /examples/SoftwareSerialWithHalfDuplex_test_PartB/SoftwareSerialWithHalfDuplex_test_PartB.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This example program can be used to test the "SoftwareSerialWithHalfDuplex" library, adapted from the SoftwareSerial library. 3 | The value of half-duplex is that one pin can be used for both transmitting and receiving data. 4 | Also many devices can be daisy-chained to the same line. RS485 still commonly uses half-duplex. 5 | 6 | By default the library works the same as the SoftwareSerial library, but by adding a couple of additional arguments 7 | it can be configured for half-duplex. In that case, the transmit pin is set by default to an input, with the pull-up set. 8 | When transmitting the pin temporarily switches to an output until the byte is sent, then flips back to input. When a module is 9 | receiving it should not be able to transmit, and vice-versa. This library probably won't work as is if you need inverted-logic. 10 | 11 | To use this test example, upload SoftwareSerialWithHalfDuplex_test_partA to as many arduinos as you like. Be sure to change 12 | "myID" for each arduino loaded with partA. Upload SoftwareSerialWithHalfDuplex_test_partB to a different arduino. All arduinos 13 | should be connected to each other by the same communications pin, and by ground. Open up the serial monitor pointing to partB. 14 | When you type in the id number of one of the devices it should respond. 15 | 16 | This is a first draft of the library and test programs. It appears to work, but has only been tested on a limited basis, 17 | and hasn't yet been tested with any native half-duplex devices (like the bioloid ax12 robot servo). 18 | Seems fairly reliable up to 57600 baud. As with all serial neither error checking, nor addressing are implemented, 19 | so it is likely that you will need to do this yourself. Also, you can make use of other protocols such as i2c. 20 | I am looking for any feedback, advice and help at this stage. 21 | Contact me at n.stedman@steddyrobots.com or on the arduino forums. 22 | */ 23 | 24 | 25 | 26 | #include 27 | 28 | #define comPin 4 29 | 30 | // add arguments of false (for inverse_logic?), false (for full-duplex?) for half duplex. 31 | // you can then use the same pin for both transmit and receive. 32 | SoftwareSerialWithHalfDuplex debugPort(comPin,comPin,false,false); 33 | 34 | void setup(){ 35 | debugPort.begin(9600); // to connect with other devices 36 | Serial.begin(9600); // to see who's connected 37 | } 38 | 39 | void loop(){ 40 | if( debugPort.available() ){ 41 | char c = debugPort.read(); 42 | Serial.write(c); 43 | } 44 | if( Serial.available()){ 45 | int i = int(Serial.read())-48; 46 | debugPort.write(i); 47 | } 48 | } 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/SoftwareSerialWithHalfDuplex_test_partA/SoftwareSerialWithHalfDuplex_test_partA.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This example program can be used to test the "SoftwareSerialWithHalfDuplex" library, adapted from the SoftwareSerial library. 3 | The value of half-duplex is that one pin can be used for both transmitting and receiving data. 4 | Also many devices can be daisy-chained to the same line. RS485 still commonly uses half-duplex. 5 | 6 | By default the library works the same as the SoftwareSerial library, but by adding a couple of additional arguments 7 | it can be configured for half-duplex. In that case, the transmit pin is set by default to an input, with the pull-up set. 8 | When transmitting the pin temporarily switches to an output until the byte is sent, then flips back to input. When a module is 9 | receiving it should not be able to transmit, and vice-versa. This library probably won't work as is if you need inverted-logic. 10 | 11 | To use this test example, upload SoftwareSerialWithHalfDuplex_test_partA to as many arduinos as you like. Be sure to change 12 | "myID" for each arduino loaded with partA. Upload SoftwareSerialWithHalfDuplex_test_partB to a different arduino. All arduinos 13 | should be connected to each other by the same communications pin, and by ground. Open up the serial monitor pointing to partB. 14 | When you type in the id number of one of the devices it should respond. 15 | 16 | This is a first draft of the library and test programs. It appears to work, but has only been tested on a limited basis, 17 | and hasn't yet been tested with any native half-duplex devices (like the bioloid ax12 robot servo). 18 | Seems fairly reliable up to 57600 baud. As with all serial neither error checking, nor addressing are implemented, 19 | so it is likely that you will need to do this yourself. Also, you can make use of other protocols such as i2c. 20 | I am looking for any feedback, advice and help at this stage. 21 | Contact me at n.stedman@steddyrobots.com or on the arduino forums. 22 | */ 23 | 24 | 25 | #include 26 | 27 | #define comPin 4 28 | 29 | SoftwareSerialWithHalfDuplex debugPort(comPin,comPin,false,false); 30 | 31 | const int myID = 1; 32 | 33 | void setup(){ 34 | debugPort.begin(9600); 35 | } 36 | 37 | void loop(){ 38 | if( debugPort.available() ){ 39 | int i = int(debugPort.read()); 40 | if( i == myID ){ 41 | debugPort.print("I am "); 42 | debugPort.println(myID); 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /examples/TwoPortReceive/TwoPortReceive.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Software serial multple serial test 3 | 4 | Receives from the two software serial ports, 5 | sends to the hardware serial port. 6 | 7 | In order to listen on a software port, you call port.listen(). 8 | When using two software serial ports, you have to switch ports 9 | by listen()ing on each one in turn. Pick a logical time to switch 10 | ports, like the end of an expected transmission, or when the 11 | buffer is empty. This example switches ports when there is nothing 12 | more to read from a port 13 | 14 | The circuit: 15 | Two devices which communicate serially are needed. 16 | * First serial device's TX attached to digital pin 2, RX to pin 3 17 | * Second serial device's TX attached to digital pin 4, RX to pin 5 18 | 19 | created 18 Apr. 2011 20 | modified 9 Apr 2012 21 | by Tom Igoe 22 | based on Mikal Hart's twoPortRXExample 23 | adapted by Nick Stedman 15 July 2012 24 | 25 | This example code is in the public domain. 26 | 27 | */ 28 | 29 | #include 30 | // software serial #1: TX = digital pin 2, RX = digital pin 3 31 | SoftwareSerialWithHalfDuplex mySerial(2, 3); // RX, TX 32 | 33 | // software serial #2: TX = digital pin 4, RX = digital pin 5 34 | SoftwareSerialWithHalfDuplex portTwo(4, 5); 35 | 36 | void setup() 37 | { 38 | // Open serial communications and wait for port to open: 39 | Serial.begin(9600); 40 | while (!Serial) { 41 | ; // wait for serial port to connect. Needed for Leonardo only 42 | } 43 | 44 | 45 | // Start each software serial port 46 | portOne.begin(9600); 47 | portTwo.begin(9600); 48 | } 49 | 50 | void loop() 51 | { 52 | // By default, the last intialized port is listening. 53 | // when you want to listen on a port, explicitly select it: 54 | portOne.listen(); 55 | Serial.println("Data from port one:"); 56 | // while there is data coming in, read it 57 | // and send to the hardware serial port: 58 | while (portOne.available() > 0) { 59 | char inByte = portOne.read(); 60 | Serial.write(inByte); 61 | } 62 | 63 | // blank line to separate data from the two ports: 64 | Serial.println(); 65 | 66 | // Now listen on the second port 67 | portTwo.listen(); 68 | // while there is data coming in, read it 69 | // and send to the hardware serial port: 70 | Serial.println("Data from port two:"); 71 | while (portTwo.available() > 0) { 72 | char inByte = portTwo.read(); 73 | Serial.write(inByte); 74 | } 75 | 76 | // blank line to separate data from the two ports: 77 | Serial.println(); 78 | } 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map for SoftwareSerialWithHalfDuplex 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | SoftwareSerialWithHalfDuplex KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | begin KEYWORD2 16 | listen KEYWORD2 17 | end KEYWORD2 18 | isListening KEYWORD2 19 | stopListening KEYWORD2 20 | overflow KEYWORD2 21 | peek KEYWORD2 22 | write KEYWORD2 23 | read KEYWORD2 24 | available KEYWORD2 25 | flush KEYWORD2 26 | handle_interrupt KEYWORD2 27 | 28 | ####################################### 29 | # Constants (LITERAL1) 30 | ####################################### 31 | 32 | --------------------------------------------------------------------------------