/* SoftwareSerial.cpp (formerly NewSoftSerial.cpp) - Multi-instance software serial library for Arduino/Wiring -- Interrupt-driven receive and other improvements by ladyada (http://ladyada.net) -- Tuning, circular buffer, derivation from class Print/Stream, multi-instance support, porting to 8MHz processors, various optimizations, PROGMEM delay tables, inverse logic and direct port writing by Mikal Hart (http://www.arduiniana.org) -- Pin change interrupt macros by Paul Stoffregen (http://www.pjrc.com) -- 20MHz processor support by Garrett Mace (http://www.macetech.com) -- ATmega1280/2560 support by Brett Hagman (http://www.roguerobotics.com/) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA The latest version of this library can always be found at http://arduiniana.org. */ #ifdef TARGET_LPC1768 // // Includes // //#include #include "../../inc/MarlinConfig.h" #include #include #include "arduino.h" #include "pinmapping.h" #include "fastio.h" #include "SoftwareSerial.h" void GpioEnableInt(uint32_t port, uint32_t pin, uint32_t mode); void GpioDisableInt(uint32_t port, uint32_t pin); // // Statics // SoftwareSerial *SoftwareSerial::active_object = 0; unsigned char SoftwareSerial::_receive_buffer[_SS_MAX_RX_BUFF]; volatile uint8_t SoftwareSerial::_receive_buffer_tail = 0; volatile uint8_t SoftwareSerial::_receive_buffer_head = 0; typedef struct _DELAY_TABLE { long baud; uint16_t rx_delay_centering; uint16_t rx_delay_intrabit; uint16_t rx_delay_stopbit; uint16_t tx_delay; } DELAY_TABLE; // rough delay estimation static const DELAY_TABLE table[] = { //baud |rxcenter|rxintra |rxstop |tx { 250000, 2, 4, 4, 4, }, //Done but not good due to instruction cycle error { 115200, 4, 8, 8, 8, }, //Done but not good due to instruction cycle error //{ 74880, 69, 139, 62, 162, }, // estimation // { 57600, 100, 185, 1, 208, }, // Done but not good due to instruction cycle error //{ 38400, 13, 26, 26, 26, }, // Done //{ 19200, 26, 52, 52, 52, }, // Done { 9600, 52, 104, 104, 104, }, // Done //{ 4800, 104, 208, 208, 208, }, //{ 2400, 208, 417, 417, 417, }, //{ 1200, 416, 833, 833, 833,}, }; // // Private methods // #if 0 /* static */ inline void SoftwareSerial::tunedDelay(uint32_t count) { asm volatile( "mov r3, %[loopsPerMicrosecond] \n\t" //load the initial loop counter "1: \n\t" "sub r3, r3, #1 \n\t" "bne 1b \n\t" ://empty output list :[loopsPerMicrosecond] "r" (count) :"r3", "cc" //clobber list ); } #else inline void SoftwareSerial::tunedDelay(uint32_t count) { delayMicroseconds(count); } #endif // This function sets the current object as the "listening" // one and returns true if it replaces another bool SoftwareSerial::listen() { if (!_rx_delay_stopbit) return false; if (active_object != this) { if (active_object) active_object->stopListening(); _buffer_overflow = false; _receive_buffer_head = _receive_buffer_tail = 0; active_object = this; setRxIntMsk(true); return true; } return false; } // Stop listening. Returns true if we were actually listening. bool SoftwareSerial::stopListening() { if (active_object == this) { setRxIntMsk(false); active_object = NULL; return true; } return false; } // // The receive routine called by the interrupt handler // void SoftwareSerial::recv() { uint8_t d = 0; // If RX line is high, then we don't see any start bit // so interrupt is probably not for us if (_inverse_logic ? rx_pin_read() : !rx_pin_read()) { // Disable further interrupts during reception, this prevents // triggering another interrupt directly after we return, which can // cause problems at higher baudrates. setRxIntMsk(false);//__disable_irq();// // Wait approximately 1/2 of a bit width to "center" the sample tunedDelay(_rx_delay_centering); // Read each of the 8 bits for (uint8_t i=8; i > 0; --i) { tunedDelay(_rx_delay_intrabit); d >>= 1; if (rx_pin_read()) d |= 0x80; } if (_inverse_logic) d = ~d; // if buffer full, set the overflow flag and return uint8_t next = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF; if (next != _receive_buffer_head) { // save new data in buffer: tail points to where byte goes _receive_buffer[_receive_buffer_tail] = d; // save new byte _receive_buffer_tail = next; } else { _buffer_overflow = true; } tunedDelay(_rx_delay_stopbit); // Re-enable interrupts when we're sure to be inside the stop bit setRxIntMsk(true);//__enable_irq();// } } uint32_t SoftwareSerial::rx_pin_read() { return digitalRead(_receivePin); } // // Interrupt handling // /* static */ inline void SoftwareSerial::handle_interrupt() { if (active_object) { active_object->recv(); } } extern "C" void intWrapper() { SoftwareSerial::handle_interrupt(); } // // Constructor // SoftwareSerial::SoftwareSerial(pin_t receivePin, pin_t transmitPin, bool inverse_logic /* = false */) : _rx_delay_centering(0), _rx_delay_intrabit(0), _rx_delay_stopbit(0), _tx_delay(0), _buffer_overflow(false), _inverse_logic(inverse_logic) { setTX(transmitPin); setRX(receivePin); } // // Destructor // SoftwareSerial::~SoftwareSerial() { end(); } void SoftwareSerial::setTX(pin_t tx) { // First write, then set output. If we do this the other way around, // the pin would be output low for a short while before switching to // output hihg. Now, it is input with pullup for a short while, which // is fine. With inverse logic, either order is fine. digitalWrite(tx, _inverse_logic ? LOW : HIGH); pinMode(tx,OUTPUT); _transmitPin = tx; } void SoftwareSerial::setRX(pin_t rx) { pinMode(rx, INPUT_PULLUP); // pullup for normal logic! //if (!_inverse_logic) // digitalWrite(rx, HIGH); _receivePin = rx; _receivePort = LPC1768_PIN_PORT(rx); _receivePortPin = LPC1768_PIN_PIN(rx); /* GPIO_T * rxPort = digitalPinToPort(rx); _receivePortRegister = portInputRegister(rxPort); _receiveBitMask = digitalPinToBitMask(rx);*/ } // // Public methods // void SoftwareSerial::begin(long speed) { _rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0; for(uint8_t i = 0; i < sizeof(table)/sizeof(table[0]); ++i) { long baud = table[i].baud; if(baud == speed) { _rx_delay_centering = table[i].rx_delay_centering; _rx_delay_intrabit = table[i].rx_delay_intrabit; _rx_delay_stopbit = table[i].rx_delay_stopbit; _tx_delay = table[i].tx_delay; break; } } attachInterrupt(_receivePin, intWrapper, CHANGE); //this->handle_interrupt, CHANGE); listen(); tunedDelay(_tx_delay); } void SoftwareSerial::setRxIntMsk(bool enable) { if (enable) GpioEnableInt(_receivePort,_receivePin,CHANGE); else GpioDisableInt(_receivePort,_receivePin); } void SoftwareSerial::end() { stopListening(); } // Read data from buffer int SoftwareSerial::read() { if (!isListening()) return -1; // Empty buffer? if (_receive_buffer_head == _receive_buffer_tail) return -1; // Read from "head" uint8_t d = _receive_buffer[_receive_buffer_head]; // grab next byte _receive_buffer_head = (_receive_buffer_head + 1) % _SS_MAX_RX_BUFF; return d; } int SoftwareSerial::available() { if (!isListening()) return 0; return (_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head) % _SS_MAX_RX_BUFF; } size_t SoftwareSerial::write(uint8_t b) { // By declaring these as local variables, the compiler will put them // in registers _before_ disabling interrupts and entering the // critical timing sections below, which makes it a lot easier to // verify the cycle timings bool inv = _inverse_logic; uint16_t delay = _tx_delay; if(inv) b = ~b; cli(); // turn off interrupts for a clean txmit // Write the start bit if (inv) digitalWrite(_transmitPin, 1); else digitalWrite(_transmitPin, 0); tunedDelay(delay); // Write each of the 8 bits for (uint8_t i = 8; i > 0; --i) { if (b & 1) // choose bit digitalWrite(_transmitPin, 1); // send 1 //(GPIO_Desc[_transmitPin].P)->DOUT |= GPIO_Desc[_transmitPin].bit; else digitalWrite(_transmitPin, 0); // send 0 //(GPIO_Desc[_transmitPin].P)->DOUT &= ~GPIO_Desc[_transmitPin].bit; tunedDelay(delay); b >>= 1; } // restore pin to natural state if (inv) digitalWrite(_transmitPin, 0); else digitalWrite(_transmitPin, 1); sei(); // turn interrupts back on tunedDelay(delay); return 1; } void SoftwareSerial::flush() { if (!isListening()) return; cli(); _receive_buffer_head = _receive_buffer_tail = 0; sei(); } int SoftwareSerial::peek() { if (!isListening()) return -1; // Empty buffer? if (_receive_buffer_head == _receive_buffer_tail) return -1; // Read from "head" return _receive_buffer[_receive_buffer_head]; } #endif // TARGET_LPC1768