diff options
Diffstat (limited to 'lib/msp430/TimerSerial.cpp')
-rw-r--r-- | lib/msp430/TimerSerial.cpp | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/lib/msp430/TimerSerial.cpp b/lib/msp430/TimerSerial.cpp new file mode 100644 index 0000000..e32d902 --- /dev/null +++ b/lib/msp430/TimerSerial.cpp @@ -0,0 +1,272 @@ +/* + ************************************************************************ + * TimerSerial.cpp + * + * Arduino core files for MSP430 + * Copyright (c) 2012 Robert Wessels. All right reserved. + * + * + *********************************************************************** + Derived from: + HardwareSerial.cpp - Hardware serial library for Wiring + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + and + msp430softserial by Rick Kimball + https://github.com/RickKimball + + 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 +*/ + +#include "Energia.h" +#include "TimerSerial.h" + +#define SERIAL_BUFFER_SIZE 64 + +#ifndef TIMERA0_VECTOR + #define TIMERA0_VECTOR TIMER0_A0_VECTOR +#endif /* TIMER0_A0_VECTOR */ + +#ifndef TIMERA1_VECTOR + #define TIMERA1_VECTOR TIMER0_A1_VECTOR +#endif /* TIMERA1_VECTOR */ + +struct ring_buffer_ts +{ + volatile unsigned int head; + volatile unsigned int tail; + unsigned char buffer[SERIAL_BUFFER_SIZE]; +}; + +/** + * uint8x2_t - optimized structure storage for ISR. Fits our static variables in one register + * This tweak allows the ISR to use one less register saving a push and pop + * We also save a couple of instructions being able to write to both values with + * one mov.w instruction. + */ +typedef union uint8x2_t { + //---------- word access + uint16_t mask_data; // access both as a word: mask is low byte, data is high byte + //--- or --- individual byte access + struct { + uint8_t mask:8; // bit mask to set data bits. Also used as a loop end flag + uint8_t data:8; // working value for bits received + } b; +} uint8x2_t; + +// --- --- +static volatile unsigned int USARTTXBUF; +static uint16_t TICKS_PER_BIT; +static uint16_t TICKS_PER_BIT_DIV2; +static ring_buffer_ts rx_buffer; + +#if NEEDS_BUFF_PTR + static ring_buffer_ts tx_buffer; // required for the g2231, without it we get garbage +#endif + +#if !defined(__MSP430_HAS_USCI__) && !defined(__MSP430_HAS_EUSCI_A0__) +TimerSerial Serial; +#endif + +void serialEvent() __attribute__((weak)); +void serialEvent() {} + +void serialEventRun(void) +{ + if (Serial.available()) serialEvent(); +} + +TimerSerial::TimerSerial() +{ +#if NEEDS_BUFF_PTR + _rx_buffer = &rx_buffer; + _tx_buffer = &tx_buffer; +#endif +} + +void TimerSerial::begin(register unsigned long baud) +{ + pinMode_int(UARTRXD, UARTRXD_SET_MODE); + pinMode_int(UARTTXD, UARTTXD_SET_MODE); + + TA0CCTL0 = OUT; // Set TXD Idle state as Mark = '1', +3.3 volts normal + TA0CCTL1 = SCS | CM1 | CAP | CCIE; // Sync TACLK and MCLK, Detect Neg Edge, Enable Capture mode and RX Interrupt + TA0CTL = TASSEL_2 | MC_2 | TACLR; // Clock TIMERA from SMCLK, run in continuous mode counting from to 0-0xFFFF + +#if F_CPU == 1000000 + baud = (baud<=4800) ? baud : 4800; // force 4800 for slow F_CPU +#endif + + TICKS_PER_BIT = F_CPU / baud; + TICKS_PER_BIT_DIV2 = TICKS_PER_BIT >> 1; +} + +void TimerSerial::end() +{ + while (TA0CCTL0 & CCIE) { + ; // wait for previous xmit to finish + } + pinMode(UARTTXD, INPUT); +} + +int TimerSerial::read() +{ + register uint16_t temp_tail=rx_buffer.tail; + + if (rx_buffer.head != temp_tail) { + uint8_t c = rx_buffer.buffer[temp_tail++]; + rx_buffer.tail = temp_tail % SERIAL_BUFFER_SIZE; + return c; + } + else { + return -1; + } +} + +int TimerSerial::available() +{ + unsigned cnt = (rx_buffer.head - rx_buffer.tail) % SERIAL_BUFFER_SIZE; + + return cnt; +} + +void TimerSerial::flush() +{ + while (TA0CCTL0 & CCIE) { + ; // wait for previous xmit to finish + } +} + +int TimerSerial::peek() +{ + register uint16_t temp_tail=rx_buffer.tail; + + if (rx_buffer.head != temp_tail) { + return rx_buffer.buffer[temp_tail]; + } + else { + return -1; + } +} + +size_t TimerSerial::write(uint8_t c) +{ + // TIMERA0 disables the interrupt flag when it has sent + // the final stop bit. While a transmit is in progress the + // interrupt is enabled + while (TA0CCTL0 & CCIE) { + ; // wait for previous xmit to finish + } + + // make the next output at least TICKS_PER_BIT in the future + // so we don't stomp on the the stop bit from our previous xmt + + TA0CCR0 = TA0R; // resync with current TimerA clock + TA0CCR0 += TICKS_PER_BIT; // setup the next timer tick + TA0CCTL0 = OUTMOD0 + CCIE; // set TX_PIN HIGH and reenable interrupts + + // now that we have set the next interrupt in motion + // we quickly need to set the TX data. Hopefully the + // next 2 lines happens before the next timer tick. + + // Note: This code makes great use of multiple peripherals + // + // In the code above, we start with a busy wait on the CCIE + // interrupt flag. As soon as it is available, we setup the next + // send time and then enable the interrupt. Until that time happens, + // we have a few free cycles available to stuff the start and stop bits + // into the data buffer before the timer ISR kicks in and handles + // the event. Note: if you are using a really slow clock or a really + // fast baud rate you could run into problems if the interrupt is + // triggered before you have finished with the USARTTXBUF + + register unsigned value = c | 0x100; // add stop bit '1' + value <<= 1; // Add the start bit '0' + USARTTXBUF=value; // queue up the byte for xmit + return 1; +} + + +#ifndef TIMER0_A0_VECTOR +#define TIMER0_A0_VECTOR TIMERA0_VECTOR +#endif /* TIMER0_A0_VECTOR */ + +#ifndef __GNUC__ +#pragma vector = TIMER0_A0_VECTOR +__interrupt +#else +__attribute__((interrupt(TIMER0_A0_VECTOR))) +#endif +//Timer0 A0 interrupt service routine +static void TimerSerial__TxIsr(void) +{ + TA0CCR0 += TICKS_PER_BIT; // setup next time to send a bit, OUT will be set then + + TA0CCTL0 |= OUTMOD2; // reset OUT (set to 0) OUTMOD2|OUTMOD0 (0b101) + if ( USARTTXBUF & 0x01 ) { // look at LSB if 1 then set OUT high + TA0CCTL0 &= ~OUTMOD2; // set OUT (set to 1) OUTMOD0 (0b001) + } + + if (!(USARTTXBUF >>= 1)) { // All bits transmitted ? + TA0CCTL0 &= ~CCIE; // disable interrupt, indicates we are done + } +} + +#define store_rxchar(c) { \ + register unsigned int next_head;\ + next_head = rx_buffer.head;\ + rx_buffer.buffer[next_head++]=c; \ + next_head %= SERIAL_BUFFER_SIZE; \ + if ( next_head != rx_buffer.tail ) { \ + rx_buffer.head = next_head; \ + } \ +} + +#ifndef TIMER0_A1_VECTOR +#define TIMER0_A1_VECTOR TIMERA1_VECTOR +#endif /* TIMER0_A0_VECTOR */ + +#ifndef __GNUC__ +#pragma vector = TIMER0_A1_VECTOR +__interrupt +#else +__attribute__((interrupt(TIMER0_A1_VECTOR))) +#endif +//Timer A1 interrupt service routine +static void TimerSerial__RxIsr(void) +{ + static uint8x2_t rx_bits; // persistent storage for data and mask. fits in one 16 bit register + volatile uint16_t resetTAIVIFG; // just reading TAIV will reset the interrupt flag + resetTAIVIFG=TA0IV;(void)resetTAIVIFG; + + register uint16_t regCCTL1=TA0CCTL1; // using a temp register provides a slight performance improvement + + TA0CCR1 += TICKS_PER_BIT; // Setup next time to sample + + if (regCCTL1 & CAP) { // Are we in capture mode? If so, this is a start bit + TA0CCR1 += TICKS_PER_BIT_DIV2; // adjust sample time, so next sample is in the middle of the bit width + rx_bits.mask_data = 0x0001; // initialize both values, set data to 0x00 and mask to 0x01 + TA0CCTL1 = regCCTL1 & ~CAP; // Switch from capture mode to compare mode + } + else { + if (regCCTL1 & SCCI) { // sampled bit value from receive latch + rx_bits.b.data|=rx_bits.b.mask; // if latch is high, then set the bit using the sliding mask + } + + if (!(rx_bits.b.mask <<= 1)) { // Are all bits received? Use the mask to end loop + store_rxchar(rx_bits.b.data); // Store the bits into the rx_buffer + TA0CCTL1 = regCCTL1 | CAP; // Switch back to capture mode and wait for next start bit (HI->LOW) + } + } +} |