/* An Alternative Software Serial Library * http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html * Copyright (c) 2014 PJRC.COM, LLC, Paul Stoffregen, paul@pjrc.com * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ // Revisions are now tracked on GitHub // https://github.com/PaulStoffregen/AltSoftSerial // // Version 1.2: Support Teensy 3.x // // Version 1.1: Improve performance in receiver code // // Version 1.0: Initial Release #include "AltSoftSerial.h" #include "config/AltSoftSerial_Boards.h" #include "config/AltSoftSerial_Timers.h" /****************************************/ /** Initialization **/ /****************************************/ static uint16_t ticks_per_bit=0; bool AltSoftSerial::timing_error=false; static uint8_t rx_state; static uint8_t rx_byte; static uint8_t rx_bit = 0; static uint16_t rx_target; static uint16_t rx_stop_ticks=0; static volatile uint8_t rx_buffer_head; static volatile uint8_t rx_buffer_tail; #define RX_BUFFER_SIZE 80 static volatile uint8_t rx_buffer[RX_BUFFER_SIZE]; static volatile uint8_t tx_state=0; static uint8_t tx_byte; static uint8_t tx_bit; static volatile uint8_t tx_buffer_head; static volatile uint8_t tx_buffer_tail; #define TX_BUFFER_SIZE 68 static volatile uint8_t tx_buffer[TX_BUFFER_SIZE]; #ifndef INPUT_PULLUP #define INPUT_PULLUP INPUT #endif #define MAX_COUNTS_PER_BIT 6241 // 65536 / 10.5 void AltSoftSerial::init(uint32_t cycles_per_bit) { //Serial.printf("cycles_per_bit = %d\n", cycles_per_bit); if (cycles_per_bit < MAX_COUNTS_PER_BIT) { CONFIG_TIMER_NOPRESCALE(); } else { cycles_per_bit /= 8; //Serial.printf("cycles_per_bit/8 = %d\n", cycles_per_bit); if (cycles_per_bit < MAX_COUNTS_PER_BIT) { CONFIG_TIMER_PRESCALE_8(); } else { #if defined(CONFIG_TIMER_PRESCALE_256) cycles_per_bit /= 32; //Serial.printf("cycles_per_bit/256 = %d\n", cycles_per_bit); if (cycles_per_bit < MAX_COUNTS_PER_BIT) { CONFIG_TIMER_PRESCALE_256(); } else { return; // baud rate too low for AltSoftSerial } #elif defined(CONFIG_TIMER_PRESCALE_128) cycles_per_bit /= 16; //Serial.printf("cycles_per_bit/128 = %d\n", cycles_per_bit); if (cycles_per_bit < MAX_COUNTS_PER_BIT) { CONFIG_TIMER_PRESCALE_128(); } else { return; // baud rate too low for AltSoftSerial } #else return; // baud rate too low for AltSoftSerial #endif } } ticks_per_bit = cycles_per_bit; rx_stop_ticks = cycles_per_bit * 37 / 4; pinMode(INPUT_CAPTURE_PIN, INPUT_PULLUP); digitalWrite(OUTPUT_COMPARE_A_PIN, HIGH); pinMode(OUTPUT_COMPARE_A_PIN, OUTPUT); rx_state = 0; rx_buffer_head = 0; rx_buffer_tail = 0; tx_state = 0; tx_buffer_head = 0; tx_buffer_tail = 0; ENABLE_INT_INPUT_CAPTURE(); } void AltSoftSerial::end(void) { DISABLE_INT_COMPARE_B(); DISABLE_INT_INPUT_CAPTURE(); flushInput(); flushOutput(); DISABLE_INT_COMPARE_A(); // TODO: restore timer to original settings? } /****************************************/ /** Transmission **/ /****************************************/ void AltSoftSerial::writeByte(uint8_t b) { uint8_t intr_state, head; head = tx_buffer_head + 1; if (head >= TX_BUFFER_SIZE) head = 0; while (tx_buffer_tail == head) ; // wait until space in buffer intr_state = SREG; cli(); if (tx_state) { tx_buffer[head] = b; tx_buffer_head = head; } else { tx_state = 1; tx_byte = b; tx_bit = 0; ENABLE_INT_COMPARE_A(); CONFIG_MATCH_CLEAR(); SET_COMPARE_A(GET_TIMER_COUNT() + 16); } SREG = intr_state; } ISR(COMPARE_A_INTERRUPT) { uint8_t state, byte, bit, head, tail; uint16_t target; state = tx_state; byte = tx_byte; target = GET_COMPARE_A(); while (state < 10) { target += ticks_per_bit; if (state < 9) bit = byte & 1; else bit = 1; // stopbit byte >>= 1; state++; if (bit != tx_bit) { if (bit) { CONFIG_MATCH_SET(); } else { CONFIG_MATCH_CLEAR(); } SET_COMPARE_A(target); tx_bit = bit; tx_byte = byte; tx_state = state; // TODO: how to detect timing_error? return; } } head = tx_buffer_head; tail = tx_buffer_tail; if (head == tail) { if (state == 10) { // Wait for final stop bit to finish tx_state = 11; SET_COMPARE_A(target + ticks_per_bit); } else { tx_state = 0; CONFIG_MATCH_NORMAL(); DISABLE_INT_COMPARE_A(); } } else { if (++tail >= TX_BUFFER_SIZE) tail = 0; tx_buffer_tail = tail; tx_byte = tx_buffer[tail]; tx_bit = 0; CONFIG_MATCH_CLEAR(); if (state == 10) SET_COMPARE_A(target + ticks_per_bit); else SET_COMPARE_A(GET_TIMER_COUNT() + 16); tx_state = 1; // TODO: how to detect timing_error? } } void AltSoftSerial::flushOutput(void) { while (tx_state) /* wait */ ; } /****************************************/ /** Reception **/ /****************************************/ ISR(CAPTURE_INTERRUPT) { uint8_t state, bit, head; uint16_t capture, target; uint16_t offset, offset_overflow; capture = GET_INPUT_CAPTURE(); bit = rx_bit; if (bit) { CONFIG_CAPTURE_FALLING_EDGE(); rx_bit = 0; } else { CONFIG_CAPTURE_RISING_EDGE(); rx_bit = 0x80; } state = rx_state; if (state == 0) { if (!bit) { uint16_t end = capture + rx_stop_ticks; SET_COMPARE_B(end); ENABLE_INT_COMPARE_B(); rx_target = capture + ticks_per_bit + ticks_per_bit/2; rx_state = 1; } } else { target = rx_target; offset_overflow = 65535 - ticks_per_bit; while (1) { offset = capture - target; if (offset > offset_overflow) break; rx_byte = (rx_byte >> 1) | rx_bit; target += ticks_per_bit; state++; if (state >= 9) { DISABLE_INT_COMPARE_B(); head = rx_buffer_head + 1; if (head >= RX_BUFFER_SIZE) head = 0; if (head != rx_buffer_tail) { rx_buffer[head] = rx_byte; rx_buffer_head = head; } CONFIG_CAPTURE_FALLING_EDGE(); rx_bit = 0; rx_state = 0; return; } } rx_target = target; rx_state = state; } //if (GET_TIMER_COUNT() - capture > ticks_per_bit) AltSoftSerial::timing_error = true; } ISR(COMPARE_B_INTERRUPT) { uint8_t head, state, bit; DISABLE_INT_COMPARE_B(); CONFIG_CAPTURE_FALLING_EDGE(); state = rx_state; bit = rx_bit ^ 0x80; while (state < 9) { rx_byte = (rx_byte >> 1) | bit; state++; } head = rx_buffer_head + 1; if (head >= RX_BUFFER_SIZE) head = 0; if (head != rx_buffer_tail) { rx_buffer[head] = rx_byte; rx_buffer_head = head; } rx_state = 0; CONFIG_CAPTURE_FALLING_EDGE(); rx_bit = 0; } int AltSoftSerial::read(void) { uint8_t head, tail, out; head = rx_buffer_head; tail = rx_buffer_tail; if (head == tail) return -1; if (++tail >= RX_BUFFER_SIZE) tail = 0; out = rx_buffer[tail]; rx_buffer_tail = tail; return out; } int AltSoftSerial::peek(void) { uint8_t head, tail; head = rx_buffer_head; tail = rx_buffer_tail; if (head == tail) return -1; if (++tail >= RX_BUFFER_SIZE) tail = 0; return rx_buffer[tail]; } int AltSoftSerial::available(void) { uint8_t head, tail; head = rx_buffer_head; tail = rx_buffer_tail; if (head >= tail) return head - tail; return RX_BUFFER_SIZE + head - tail; } int AltSoftSerial::availableForWrite(void) { uint8_t head, tail; head = tx_buffer_head; tail = tx_buffer_tail; if (tail > head) return tail - head; return TX_BUFFER_SIZE + tail - head; }; void AltSoftSerial::flushInput(void) { rx_buffer_head = rx_buffer_tail; } #ifdef ALTSS_USE_FTM0 void ftm0_isr(void) { uint32_t flags = FTM0_STATUS; FTM0_STATUS = 0; if (flags & (1<<0) && (FTM0_C0SC & 0x40)) altss_compare_b_interrupt(); if (flags & (1<<5)) altss_capture_interrupt(); if (flags & (1<<6) && (FTM0_C6SC & 0x40)) altss_compare_a_interrupt(); } #endif