// Copyright 2009 Olivier Gillet. // // Author: Olivier Gillet (ol.gillet@gmail.com) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program 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 General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // ----------------------------------------------------------------------------- // // BufferedSoftwareSerialOutput, the name says it all: // - It is software serial. // - Contrary to the other Software Serial implementations out there which use // fine-tuned busy loop to delay between bits, this implementation bangs bits // in a timer interrupt ; while writes are done in an asynchronous, non-blocking // way to a buffer. // // Caveats: // - The timer rate must be a multiple of the baud rate otherwise this will // miserably FAIL. // - Tested only for 2400 (main timer at 31250 Hz) and 4800 (main timer at // 62500 Hz) baud per seconds. // - Please check the timing tolerance of your device's UART! // // SoftwareSerialOutput is a Vanilla blocking implementation, taken from the // Arduino libs ; Copyright (c) 2006 David A. Mellis. #ifndef AVRLIB_SOFTWARE_SERIAL_H_ #define AVRLIB_SOFTWARE_SERIAL_H_ #include "avrlib/avrlib.h" #include "avrlib/gpio.h" #include "avrlib/ring_buffer.h" namespace avrlib { enum SoftwareSerialState { START_BIT = 0, END_BIT = 9, NEXT_BYTE = 10 }; // Parameters: // TxPin: digital pin used for transmission. // timer_rate: frequency (Hz) of the timer which will drive the output. // baud_rate: target baud rate. must be a divisor of timer_rate. // buffer_size: prefered buffer size. template class BufferedSoftwareSerialOutput { typedef BufferedSoftwareSerialOutput Me; typedef RingBuffer OutputBuffer; public: typedef uint8_t Value; enum { prescaler_reset_value = timer_rate / baud_rate, buffer_size = buffer_size_, data_size = 8 }; static void Init() { prescaler_counter_ = prescaler_reset_value; tx_state_ = NEXT_BYTE; TxPin::set_mode(DIGITAL_OUTPUT); TxPin::High(); } static inline void Write(Value v) { OutputBuffer::Write(v); } static inline uint8_t writable() { return OutputBuffer::writable(); } static inline uint8_t NonBlockingWrite(Value v ) { return OutputBuffer::NonBlockingWrite(v); } static inline void Overwrite(Value v) { OutputBuffer::Overwrite(v); } static inline void Tick() { --prescaler_counter_; if (prescaler_counter_ > 0) { return; } prescaler_counter_ = prescaler_reset_value; // Check in which stage of the transmission we are. if (tx_state_ == NEXT_BYTE) { // Attempt to start the transmission of the next byte in the buffer, (if // there is any). int16_t next_byte = OutputBuffer::NonBlockingRead(); if (next_byte >= 0) { tx_byte_ = next_byte; tx_state_ = START_BIT; return; } else { return; } } else if (tx_state_ == START_BIT) { TxPin::Low(); tx_symbol_mask_ = 1; } else if (tx_state_ == END_BIT) { TxPin::High(); } else { TxPin::set_value(tx_byte_ & tx_symbol_mask_); tx_symbol_mask_ <<= 1; } ++tx_state_; } private: static uint8_t prescaler_counter_; // Mask shifted every symbol, used to extract bits from the current character // to transmit. static uint8_t tx_symbol_mask_; static uint8_t tx_state_; static uint8_t tx_byte_; DISALLOW_COPY_AND_ASSIGN(BufferedSoftwareSerialOutput); }; template uint8_t BufferedSoftwareSerialOutput::prescaler_counter_; template uint8_t BufferedSoftwareSerialOutput::tx_symbol_mask_; template uint8_t BufferedSoftwareSerialOutput::tx_state_; template uint8_t BufferedSoftwareSerialOutput::tx_byte_; // Parameters: // TxPin: digital pin used for transmission. // baud_rate: target baud rate. must be a divisor of timer_rate. // Following code from NewSoftSerial, Copyright (c) 2006 David A. Mellis. template struct SoftwareSerialOutput { static void Init() { TxPin::set_mode(DIGITAL_OUTPUT); } static void Write(uint8_t tx_byte) { uint8_t oldSREG = SREG; cli(); uint16_t delay = (F_CPU / baud_rate) / 7; uint16_t tx_delay = delay - 5; TxPin::Low(); TunedDelay(delay); for (uint8_t mask = 1; mask; mask <<= 1) { TxPin::set_value(tx_byte & mask); TunedDelay(tx_delay); } TxPin::High(); SREG = oldSREG; TunedDelay(delay); } static inline void TunedDelay(uint16_t delay) { uint8_t tmp = 0; asm volatile( "sbiw %0, 0x01" "\n\t" "ldi %1, 0xff" "\n\t" "cpi %A0, 0xff" "\n\t" "cpc %B0, %1" "\n\t" "brne .-10" "\n\t" : "+r" (delay), "+a" (tmp) : "0" (delay) ); } }; } // namespace avrlib #endif // AVRLIB_SOFTWARE_SERIAL_H_