Jump to content
43oh

A higher performing timer UART snippet


Recommended Posts

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
        (1 bit that's transmitted, 1 bit to put in line, and 1 bit to keep xmit_char() synced) */
	int16_t value = c;
	value |= 0x700;

	/* 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;
		}
	}
}
Link to post
Share on other sites

Thanks for the code. However it didn't work for me. I'm running a G2553, but it has the same timers as the G2231

 

I couldn't understand what you were doing in the xmit_char() function, I changed it to this; and now it's working as expected.

/* For some reason static prevents inlining... */
void xmit_char(uint16_t c){
	/* Wait for current xmit to stop. */
	while(tx_char);

	/* Add proper stop bit timing (2 bits since last one is cutoff) */
	int16_t value = c;
	value |= 0x100;

	value <<= 1;
	tx_char = value;

	TA0CCR1 = TAR;
	TA0CCR1 += rx_ticks_per_bit;
	TA0CCTL1 = OUTMOD0 | CCIE;

}

When the interrupt fires, it takes the LSB of value which will the the start bit.

 

 

Also just a bit of constructive critisism

	__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"
	);

In this assembly section you've done some variables in %[readable_name] notation, and some in %(decimal value) notaion? Wouldn't it be nicer just to have them all in the same notaion?

Link to post
Share on other sites

Yes, sorry. I was getting garbled often non-ascii characters when using the loop-back. I don't have access to my oscilloscope until Friday So I can't compare bit patterns yet.

 

You are right, what I posted has 1 bit of dead time. In your original code you shouldn't need to test the last bit of value. Since every UART byte transmission needs a start bit '0' and a stop bit '1'.

 

By shifting value>>1 in the xmit function you are discarding this start-bit. You are also adding a double stop bit with the |=0x300.

Link to post
Share on other sites

I made a correction to the code; my stop bit was terminating too early; for some reason my FTDI USB dongle was just ignoring the early termination of the stop bit.

I made a quick edit to add 0x300 to the value, but the reply is delayed by too much now.

Either I need to double buffer the transmit side (as the rx buffer side) or adjust TA0CCR1 to wait less time on a stop bit (IE account for transmission delay)

 

 In your original code you shouldn't need to test the last bit of value. Since every UART byte transmission needs a start bit '0' and a stop bit '1'.

 


By shifting value>>1 in the xmit function you are discarding this start-bit. You are also adding a double stop bit with the |=0x300.

 

greeeg, your understanding is still incorrect.

 

The point of

	/* Start output ASAP. */
	TA0CCTL1 = OUTMOD_0;
	TA0CCR1 = TAR;
	TA0CCR1 += rx_ticks_per_bit;

Is to immediately begin outputting the start bit and to schedule an interrupt after this start bit completes.

So clearly the start bit is never put into value and therefore is never shifted out of value.

 

The "least significant bit test" is necessary to determine what to output on the first CCIE interrupt.

Link to post
Share on other sites

greeeg, this version should work for you.

The stop bits look good on this one.

I added a waiting transmit buffer so gapless transmission is now possible.

I think the transmit path for data bits is a few clocks faster too now.

(though xmit_char() is a little slower)

#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_wait = 0;
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;

	/* Note OUT is an indication of "idle".
	 * Thankfully OUT is 4, which is covered by the constant generator.
	 * Set CCIFG to indicate stop bit completed. */
	TA0CCTL1 = OUT | CCIFG;

	P1SEL |= P1_RX | P1_TX;
}

/* For some reason static prevents inlining... */
void xmit_char(uint16_t c){
	/* If busy transmitting then populate the wait buffer. */
	__dint();
	if(!(TA0CCTL1 & OUT)){
		register int16_t value = c;
		c |= 0x100;
		c <<= 1;
		tx_wait = c;
		__eint();

		/* Wait for wait buffer to clear. */
		while(tx_wait);
		return;
	}
	__eint();

	/* Wait for stop bit to finish transmitting. */
	while(!(TA0CCTL1 & CCIFG));

	/* Let's get started on the stop bit now.. */
	TA0CCTL1 = OUTMOD_0;

	/* Schedule timer transition. */
	TA0CCR1 = TAR;
	TA0CCR1 += rx_ticks_per_bit;

	/* Add stop bit */
	register int16_t value = c;
	value |= 0x100;

	/* Shift the LSB into the C bit of the SR.  */
	value >>= 1;
	tx_char = value;

	/* This ASM clause defines output bit at CCR1 trigger. */
	__asm__ (
		" mov #176,%[value]\n"
		" jnc 10f\n"
		" xor #128,%[value]\n"
		"10:"
		" mov %[value],%[cctl]\n"
		:
		:
		[cctl] "m" (TA0CCTL1),     /* 3 */
		[value] "r" (value)     /* 3 */
	);
}

__attribute__((interrupt(TIMER0_A1_VECTOR)))
void SoftUART_TX(void){
	__asm__ (
		/* Clear TAIV; only TA0.1 is enabled so no read necessary. */
		" tst &0x012e\n"

		/* Schedule next bit transition. */
		" add %[TICKS],%[ccr]\n"

		/* Assume the next bit is a 1.
		 * Note if the carry bit is 1 then assumption was correct.
		 * Move the next bit to output to the C flag;
		 * If the result is nonzero */
		" mov %[init],%[cctl]\n"
		" rra %[tx_buf]\n"
		" jnz 3f\n"

		/* If the carry bit is nonzero, check the buffer for pending data. */
		" jnc 5f\n"

		/* At this point, scheduled the final stop bit.
		 * Wait buffer may have more stuff.
		 * XOR because nonzero result sets Z flag.
		 * If not then set to idle. */
		" xor %[tx_w], %[tx_buf]\n"
		" mov #0,%[tx_w]\n"
		" jnz 4f\n"

		/* Value is zero, move into the end state. */
		" mov %[end],%[cctl]\n"
		" jmp 4f\n"

		/* Set to idle. (setting to OUT) */
		"5:\n"
		" mov #4,%[cctl]\n"
		" jmp 4f\n"

		/* Otherwise assume we will output 0.
		 * If the stop bit is still in tx_buf go to the end. */
		"3:\n"
		" jc 4f\n"
		" bis %2,%[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 */
		[tx_w] "m" (tx_wait),               /* 5 */
		[init] "i" (OUTMOD_1 | CCIE),      /* 6 */
		[end] "i" (OUTMOD_1 | OUT)      /* 6 */
		:
		"cc"
	);
}

__attribute__((interrupt(TIMER0_A0_VECTOR)))
void SoftUART_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;
		}
	}
}

Link to post
Share on other sites

@@David Bender Thanks this code works perfectly, and does give continuous data transmission, I can paste a file of text in my terminal, and it is all echo'd back.

 

 

 

You're right, I wasn't understanding the execution of the OUTMOD bits. I went back and tried to re-create my original problem but couldn't. your original code ran fine when I tested it just now, except every few byte I would get a dropped/miss-interrpreted byte.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...