/* 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. */ // When set, _DEBUG co-opts pins 11 and 13 for debugging with an // oscilloscope or logic analyzer. Beware: it also slightly modifies // the bit times, so don't rely on it too much at high baud rates #define _DEBUG 0 #define _DEBUG_PIN1 11 #define _DEBUG_PIN2 13 #define MAX_SOFT_SERIAL_SPEED 115200 #define MIN_SOFT_SERIAL_SPEED 300 // // Includes // // #include #include #include #include "mik32_hal_irq.h" #include "mik32_hal_gpio.h" #include "mik32_hal_scr1_timer.h" // // Statics // SoftwareSerial *SoftwareSerial::active_object = 0; uint8_t SoftwareSerial::_receive_buffer[_SS_MAX_RX_BUFF]; volatile uint8_t SoftwareSerial::_receive_buffer_tail = 0; volatile uint8_t SoftwareSerial::_receive_buffer_head = 0; // // Debugging // // This function generates a brief pulse // for debugging or measuring on an oscilloscope. // #if _DEBUG // inline void DebugPulse(uint8_t pin, uint8_t count) // { // volatile uint8_t *pport = portOutputRegister(digitalPinToPort(pin)); // uint8_t val = *pport; // while (count--) // { // *pport = val | digitalPinToBitMask(pin); // *pport = val; // } // } // #else // inline void DebugPulse(uint8_t, uint8_t) {} // #endif // // Private methods // // get ticks from systick #define CURRENT_TICKS ((uint64_t)(SCR1_TIMER->MTIMEH)<<32 | SCR1_TIMER->MTIME) // function placed in ram. Should not call other function placed in flash void SoftwareSerial::tunedDelay(uint32_t delayTicks) { uint64_t startTicks = CURRENT_TICKS; while ((CURRENT_TICKS - startTicks) < delayTicks) ; } // 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() { // if (active_object) // { // uint8_t data = 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); // // Wait approximately 1/2 of a bit width to "center" the sample // tunedDelay(_rx_delay_centering); // // DebugPulse(_DEBUG_PIN2, 1); // // Read each of the 8 bits // for (uint8_t i=8; i > 0; --i) // { // tunedDelay(_rx_delay_intrabit); // data >>= 1; // // DebugPulse(_DEBUG_PIN2, 1); // if (rx_pin_read()) // data |= 0x80; // } // if (_inverse_logic) // data = ~data; // // 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] = data; // save new byte // _receive_buffer_tail = next; // } // else // { // // DebugPulse(_DEBUG_PIN1, 1); // _buffer_overflow = true; // } // // skip the stop bit // tunedDelay(_rx_delay_stopbit); // // DebugPulse(_DEBUG_PIN1, 1); // // Re-enable interrupts when we're sure to be inside the stop bit // setRxIntMsk(true); // } // } } uint8_t SoftwareSerial::rx_pin_read() { return (uint8_t)HAL_GPIO_ReadPin((GPIO_TypeDef *)_receivePortRegister, (HAL_PinsTypeDef)_receiveBitMask); } // // Interrupt handling // /* static */ inline void SoftwareSerial::handle_interrupt() // todo унести в обработчик прерываний { if (active_object) { active_object->recv(); } } // #if defined(PCINT0_vect) // ISR(PCINT0_vect) // { // SoftwareSerial::handle_interrupt(); // } // #endif // // Constructor // SoftwareSerial::SoftwareSerial(uint8_t receivePin, uint8_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) { _transmitPin = transmitPin; _receivePin = receivePin; } // // Destructor // SoftwareSerial::~SoftwareSerial() { end(); } void SoftwareSerial::setTX(uint8_t tx) { // First configure then write because writing pin didn't work until config is applied pinMode(tx, OUTPUT); digitalWrite(tx, _inverse_logic ? LOW : HIGH); _transmitBitMask = (uint16_t)digitalPinToBitMask(tx); // save tx bit mask _transmitPortRegister = (uint32_t)digitalPinToPort(tx); // save tx port adress } void SoftwareSerial::setRX(uint8_t rx) { _receiveBitMask = (uint16_t)digitalPinToBitMask(rx); // save rx bit mask _receivePortRegister = (uint32_t)digitalPinToPort(rx); // save rx port adress // attachInterrupt(digitalPinToInterrupt(rx), [this]() { this->recv(); }, _inverse_logic ? RISING : FALLING); if (!_inverse_logic) pinMode(rx, INPUT_PULLUP); // pullup for normal logic // HAL_GPIO_PinConfig((GPIO_TypeDef*)_receivePortRegister, (HAL_PinsTypeDef)_receiveBitMask, HAL_GPIO_MODE_GPIO_INPUT, HAL_GPIO_PULL_UP, HAL_GPIO_DS_2MA); else pinMode(rx, INPUT); } uint32_t SoftwareSerial::subtract_cap(uint32_t num, uint16_t sub) { if (num > sub) return num - sub; else return 1; } // // Public methods // void SoftwareSerial::begin(long speed) { // set pins config setRX(_receivePin); setTX(_transmitPin); _rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0; // limit speed if (speed > MAX_SOFT_SERIAL_SPEED) speed = MAX_SOFT_SERIAL_SPEED; if (speed < MIN_SOFT_SERIAL_SPEED) speed = MIN_SOFT_SERIAL_SPEED; // Precalculate the various delays in number of ticks uint32_t bit_delay = F_CPU / speed; // 12 (gcc 4.8.2) or 13 (gcc 4.3.2) cycles from start bit to first bit, // 15 (gcc 4.8.2) or 16 (gcc 4.3.2) cycles between bits, // 12 (gcc 4.8.2) or 14 (gcc 4.3.2) cycles from last bit to stop bit // These are all close enough to just use 15 cycles, since the inter-bit // timings are the most critical (deviations stack 8 times) _tx_delay = subtract_cap(bit_delay, 35); // 15 // Only setup rx when we have a valid INT for this pin if (digitalPinToInterrupt(_receivePin) != NOT_AN_INTERRUPT) { // #if GCC_VERSION > 40800 // // Timings counted from gcc 4.8.2 output. This works up to 115200 on // // 16Mhz and 57600 on 8Mhz. // // // // When the start bit occurs, there are 3 or 4 cycles before the // // interrupt flag is set, 4 cycles before the PC is set to the right // // interrupt vector address and the old PC is pushed on the stack, // // and then 75 cycles of instructions (including the RJMP in the // // ISR vector table) until the first delay. After the delay, there // // are 17 more cycles until the pin value is read (excluding the // // delay in the loop). // // We want to have a total delay of 1.5 bit time. Inside the loop, // // we already wait for 1 bit time - 23 cycles, so here we wait for // // 0.5 bit time - (71 + 18 - 22) cycles. // _rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 75 + 17 - 23)); // // There are 23 cycles in each loop iteration (excluding the delay) // _rx_delay_intrabit = subtract_cap(bit_delay, 23); // // There are 37 cycles from the last bit read to the start of // // stopbit delay and 11 cycles from the delay until the interrupt // // mask is enabled again (which _must_ happen during the stopbit). // // This delay aims at 3/4 of a bit time, meaning the end of the // // delay will be at 1/4th of the stopbit. This allows some extra // // time for ISR cleanup, which makes 115200 baud at 16Mhz work more // // reliably // _rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (37 + 11)); // #else // Timings counted from gcc 4.3.2 output // // Note that this code is a _lot_ slower, mostly due to bad register // // allocation choices of gcc. This works up to 57600 on 16Mhz and // // 38400 on 8Mhz. // _rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 97 + 29 - 11)); // _rx_delay_intrabit = subtract_cap(bit_delay, 11); // _rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (44 + 17)); // #endif // Enable the INT for the entire port here, but never disable it // (others might also need it, so we disable the interrupt by using // the per-pin PCMSK register). // if (_inverse_logic) // _int_mode = GPIO_INT_MODE_RISING; // start bit: low to high // else // _int_mode = GPIO_INT_MODE_FALLING; // start bit: high to low // // Save gpio_mux and gpio_line for on/off interrupts // _int_maskMux = digitalPinToGpioIntMux(_receivePin); // gpio mux value // _int_maskLine = digitalPinToGpioIntLine(_receivePin); // gpio irq line value // HAL_EPIC_MaskEdgeSet(HAL_EPIC_GPIO_IRQ_MASK); // turn on the mask in EPIC // *digitalPinToPCICR((int8_t)_receivePin) |= _BV(digitalPinToPCICRbit(_receivePin)); // _pcint_maskreg = digitalPinToPCMSK(_receivePin); // _pcint_maskvalue = _BV(digitalPinToPCMSKbit(_receivePin)); } else ErrorMsgHandler("SoftwareSerial.begin(): Rx pin does not support interrupts, use different pin"); tunedDelay(_tx_delay); // if we were low this establishes the end // #if _DEBUG // pinMode(_DEBUG_PIN1, OUTPUT); // pinMode(_DEBUG_PIN2, OUTPUT); // #endif listen(); } void SoftwareSerial::setRxIntMsk(bool enable) { if (enable) HAL_GPIO_InitInterruptLine((HAL_GPIO_Line_Config)_int_maskMux, (HAL_GPIO_InterruptMode)_int_mode); // *_pcint_maskreg |= _pcint_maskvalue; else HAL_GPIO_DeInitInterruptLine((HAL_GPIO_Line)_int_maskLine); // *_pcint_maskreg &= ~_pcint_maskvalue; } void SoftwareSerial::end() { stopListening(); } // function placed in ram. Should not call other function placed in flash size_t SoftwareSerial::write(uint8_t byte) { if (_tx_delay == 0) return 0; if (_inverse_logic) byte = ~byte; clear_csr(mie, MIE_MEIE); // turn off interrupts for a clean txmit // Write the start bit if (_inverse_logic) ((GPIO_TypeDef *)_transmitPortRegister)->SET = (HAL_PinsTypeDef)_transmitBitMask; else ((GPIO_TypeDef *)_transmitPortRegister)->CLEAR = (HAL_PinsTypeDef)_transmitBitMask; tunedDelay(_tx_delay); // wait start bit // Write each of the 8 bits in LSB mode for (uint8_t i = 8; i > 0; --i) { if (byte & 1) // choose bit ((GPIO_TypeDef *)_transmitPortRegister)->SET = (HAL_PinsTypeDef)_transmitBitMask; else ((GPIO_TypeDef *)_transmitPortRegister)->CLEAR = (HAL_PinsTypeDef)_transmitBitMask; tunedDelay(_tx_delay); byte >>= 1; } // restore pin to natural state - stop bit if (_inverse_logic) ((GPIO_TypeDef *)_transmitPortRegister)->CLEAR = (HAL_PinsTypeDef)_transmitBitMask; else ((GPIO_TypeDef *)_transmitPortRegister)->SET = (HAL_PinsTypeDef)_transmitBitMask; // enable interrupts set_csr(mstatus, MSTATUS_MIE); set_csr(mie, MIE_MEIE); tunedDelay(_tx_delay); // wait stop bit return 1; } // 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 data = _receive_buffer[_receive_buffer_head]; // grab next byte _receive_buffer_head = (_receive_buffer_head + 1) % _SS_MAX_RX_BUFF; return data; } int SoftwareSerial::available() { if (!isListening()) return 0; return ((unsigned int)(_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head)) % _SS_MAX_RX_BUFF; } void SoftwareSerial::flush() { // There is no tx buffering, simply return } 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]; }