This code snippet demonstrates a timer based UART running on a 1MHz G2231.
The code starts by outputting "RESET\n" and going into full duplex loopback mode.
Both the transmit and receive interrupts are processed fast enough for bidirectional communication.
Thanks to @Rickta59 for giving me a starting point for the assembler in SoftUART_TX.
(A few clock cycles could be shaved off SoftUART_TX
(NOTE the pin mapping for RX/TX follows the G2553 convention!)
/* If you use this code in your project or product make sure to credit the authors. */
#include <stdint.h>
#include <msp430g2231.h>
#define BAUD 9600
enum {
/* P1 */
P1_STATUS_LED_BIT = BIT0
,P1_TX = BIT2
,P1_RX = BIT1
};
volatile uint16_t tx_char = 0;
volatile uint16_t rx_ready = 0;
/* TODO Could use constants here for efficiency..
* but it would be nice to design for runtime set baud rates. */
uint16_t rx_ticks_per_bit = F_CPU / BAUD;
uint16_t rx_ticks_per_bit_3_2 = 3 * F_CPU / BAUD / 2;
inline void ser_init(void){
TA0CTL = TASSEL_2 | MC_2 | TACLR;
TA0CCTL0 = SCS | CM_2 | CAP | CCIE;
TA0CCTL1 = OUTMOD_0 | OUT;
P1SEL |= P1_RX | P1_TX;
}
/* For some reason static prevents inlining... */
void xmit_char(uint16_t c){
/* Wait for current xmit to stop. */
while(tx_char);
/* Start output ASAP. */
TA0CCTL1 = OUTMOD_0;
TA0CCR1 = TAR;
TA0CCR1 += rx_ticks_per_bit;
/* Add proper stop bit timing (2 bits since last one is cutoff) */
int16_t value = c;
value |= 0x300;
/* Assume next bit is 0; * toggle if not.*/
TA0CCTL1 = OUTMOD_5 | CCIE;
if(value & 0x1)
TA0CCTL1 ^= OUTMOD2;
value >>= 1;
tx_char = value;
}
__attribute__((interrupt(TIMER0_A1_VECTOR)))
void TimerUART_TX(void){
__asm__ (
/* Clear TAIV; only TA0.1 is enabled so no read necessary. */
" tst &0x012e\n"
" bis %2,%[cctl]\n"
" add %[TICKS],%[ccr]\n"
" rra %[tx_buf]\n"
" jnc 3f\n"
" bic %2,%[cctl]\n"
"3:\n"
" jnz 4f\n"
" and %5,%[cctl]\n"
"4:\n"
:
:
[TICKS] "m" (rx_ticks_per_bit), /* 0 */
[ccr] "m" (TA0CCR1), /* 1 */
[mod2] "i" (OUTMOD2), /* 2 */
[cctl] "m" (TA0CCTL1), /* 3 */
[tx_buf] "m" (tx_char), /* 4 */
"i" (~CCIE) /* 5 */
:
"cc"
);
}
__attribute__((interrupt(TIMER0_A0_VECTOR)))
void TimerUART_RX(void){
static uint16_t rx_buffer;
register int16_t tmp = TA0CCTL0;
if(tmp & CAP){
TA0CCR0 += rx_ticks_per_bit_3_2;
tmp = 0x0100;
TA0CCTL0 &= ~CAP;
}
else{
TA0CCR0 += rx_ticks_per_bit;
tmp &= SCCI;
tmp |= rx_buffer;
tmp >>= 1;
if(tmp & 0x1){
TA0CCTL0 |= CAP;
__bic_SR_register_on_exit(CPUOFF);
rx_ready = tmp;
return;
}
}
rx_buffer = tmp;
}
int main(void){
WDTCTL = WDTPW | WDTHOLD;
/* Set to 1 MHz. */
BCSCTL1 = CALBC1_1MHZ;
DCOCTL = CALDCO_1MHZ;
/* SMCLK = DCO / DIVS = nMHz */
/* ACLK = VLO = ~ 12 KHz */
BCSCTL2 &= ~(DIVS_0);
BCSCTL3 |= LFXT1S_2;
/* Disable external crystal. */
P2SEL = 0;
/* Enable RX pullup */
P1REN = BIT1;
P1OUT = P1_TX | P1_RX;
P1DIR = P1_STATUS_LED_BIT | P1_TX;
/* Set up serial. */
ser_init();
__eint();
/* Test sending multiple in a row */
xmit_char('R');
xmit_char('E');
xmit_char('S');
xmit_char('E');
xmit_char('T');
xmit_char('\n');
while(1){
LPM0;
/* Handle rx. */
int16_t tmp = rx_ready;
if(tmp){
/* Shift to align with byte. */
tmp >>= 2;
tmp &= 0xFF;
/* Send back received character. */
xmit_char(tmp);
rx_ready = 0;
/* Toggle LED on each transmit. */
P1OUT ^= P1_STATUS_LED_BIT;
}
}
}