Quantcast
Channel: MSP430 Technical Forums
Viewing all articles
Browse latest Browse all 2077

A higher performing timer UART snippet

$
0
0

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;
		}
	}
}

Viewing all articles
Browse latest Browse all 2077

Trending Articles