summaryrefslogtreecommitdiffstats
path: root/lib/msp430/TimerSerial.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/msp430/TimerSerial.cpp')
-rw-r--r--lib/msp430/TimerSerial.cpp272
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)
+ }
+ }
+}