summaryrefslogtreecommitdiffstats
path: root/lib/msp430/TimerSerial.cpp
blob: e32d902f927b5a5360b940a5cb9594f7034b5f0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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)
        }
    }
}