UART Say a!

One of the most basic tasks in getting started with any embedded system is getting serial communications working. In particular getting a UART setup is often the first program written after blinking an LED.

In this document we compare several popular microcontrollers as we coded for them over the past few years.

We say 'a' rather than “Hello World” because a string would require either a loop or a printf like library call which just adds uniform complication to the code.

We aim to show how StellarisWare makes setting up hardware on LM3S microcontrollers a lot more friendly than it might otherwise be. (If you want to skip to the last section feel free!)

One of the main reasons development boards (Cygni, Eridani, Procyon) are built on LM3S microcontrollers is StellarisWare. From our point of view the hardware setup is easier than most common microcontroller platforms other than the very high level Arduino because of the StellarisWare libraries.

Arduino

Arduino is a very simple platform for beginners or absolutely rapid prototyping. We actually don't use it at all, but it is popular. A quick search of the net shows that all that is needed to print 'a' is:

Serial.begin(115200);
Serial.print('a');

Of course this is short because it relies on a nice library for serial communications.

AVR 8-Bit (ATMEGA168)

Arduino is really an AVR 8-bit micro with a nice library and bootloader. The base code for doing the same thing on an AVR directly is a bit more complex but still pretty simple:

#include <avr/io.h> 

#define USART_BAUDRATE 115200 
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1) 

int main (void) 
{ 
	UCSRB |= (1 << RXEN) | (1 << TXEN);			// Enable Tx and Rx
	UCSRC |= (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1); 	// set 8-1-N

	UBRRH = (BAUD_PRESCALE >> 8);	//set baud rate
	UBRRL = BAUD_PRESCALE;

	std_putChar('a');		//print a
}

void std_putChar(uint8_t theChar)
{
	while ((UCSRA & (1 << UDRE)) == 0); 	//Wait for ready
	UDR = theChar; 				//Tx Character
}

We see in the code that we have to turn on the right pins and set the type of UART we want including the baudrate magic numbers which are calculated with help from the datasheet. The function std_putChar just puts a character and we will use it in our other examples. It waits for the UART to be ready to send and then outputs the requested character in all cases.

MSP430 (MSP430G2553)

MSP430 is popular also though it is 16-bit it can be a bit more work to setup some hardware than AVR but really the routine is similar:

#include <msp430.h>

void main(void)
{
	WDTCTL = WDTPW + WDTHOLD;               // Stop WDT
	BCSCTL1 = CALBC1_1MHZ;                  // Set DCO
	DCOCTL = CALDCO_1MHZ;
	P1SEL = BIT1 + BIT2 ;                   // Enable Rx and Tx
	P1SEL2 = BIT1 + BIT2;                      
	UCA0CTL1 |= UCSSEL_2;                   // SMCLK
	UCA0BR0 = 8;                            // 1MHz 115200
	UCA0BR1 = 0;                            // 1MHz 115200
	UCA0MCTL = UCBRS2 + UCBRS0;            	// Modulation UCBRSx = 5
	UCA0CTL1 &= ~UCSWRST;          		// Initialize USCI state

	std_putChar('a');			//print a

}

void std_putChar(uint8_t theChar)
{
	while (!(IFG2&UCA0TXIFG));              // Wait for buffer ready
	UCA0TXBUF = theChar;                    // TX character
}

Here we see some code to setup the clock before we do anything to the UART, it might be nice to neglect that in comparing how hard it is to setup MSP430 vs AVR, because the AVR is code is just making use that the default clock setting is acceptable.

In the code we see a feature of the MSP430 that is different and that is that there is a need to specify the source of the clock for the peripheral in question. Also since the pins are shared across multiple modules we see another common requirement in microcontrollers: to set what module should be attached to the pins in question (P1SEL).

So far the big picture message is that there are a lot of magic numbers to look up from the datasheet to get the job done and the number is going up as the hardware becomes more complex!

STM32 Cortex M3 (STM32F103)

STM32 is a popular and inexpensive ARM Cortex M3 chip that is a competitor of the LM3S series. Maple is an Ardunio port to this chip which no doubt makes it easier to use. We will look at it in the native form though:

#include "stm32f10x.h"

void main(void)
{
	//***** SETUP USART	
	//Enable USART2 clock; GPIOA clock (where the pins are) and Alt Function clock
	RCC_APB1ENR |= (1<<17); //USART2 is bit 17 (6.3.8)
	RCC_APB2ENR |= (1<<2);  //Turn on GPIOA (6.3.7)
	RCC_APB2ENR |= (1<<0);  //Turn on AFIO (6.3.7)

	GPIOA_CRL = 0x00004B00; //Turn on Pins Tx(2)/Rx(3) (Alt Fn push-pull/floating Table 22)
	USART2_CR1 |= (1<<13);	//Enable USART2

	//Default is 8-1-N COM, otherwise change here...

	//Set Baud rate = 57.6 Kbps for 36 MHz clock div by 39.0625
	//DIVFraction = 16*0.0625 = 0x1
	//DIVMantissa = 39 = 0x27
	USART2_BRR = 0x271;
	
	USART2_CR1 |= (1<<3);	//Send idle frame as first transmission (TE bit)
	USART2_CR1 |= (1<<2);	//Enable receiveing (RE bit)
	//***** End UART config

	std_putChar('a');	//print a
	
}

void std_putChar(uint8_t theChar)
{
	while((USART2_SR & (1<<7)) == 0); 	// Wait for send register empty (TXE)
	USART2_DR = theChar;			// Tx theChar
}

In the code above we have left out the startup code. ARM microcontrollers have priorities on their interrupts and have a table called the NVIC that points to the functions to handle each interrupt request. This code changes very little across projects so we have not included it.

This code is pretty opaque. It was written with extensive reference to the datasheet (most lines are magic numbers) and is minimal using a lot of default values in registers, which in some sense makes it less safe.

At the time it was written there were no good header files for STM32 there may well be now making the code look more like the MSP430 with shifts for each register.

However, we see an additional item here is that on top of the setting up of pins and turning on peripherals there is also a clock tree. This is a bit similar to the clock source selection of the MSP430 but more complex as the clocks actually cascade off one another, and they all have to be set correctly!

The code isn't much longer than the MSP430 but it isn't as clear which is why the comments reference the sections of the manual that show why the value is set as it is!

The big picture story remains the same, either magic number or magic register bit names that aren't terribly intuitive.

LM3S Cortex M3 with StellarisWare

You can use LM3S chips just like STM32 but they come with a powerful driver library (StellarisWare) that removes the magic numbers and saves you from looking in the datasheet every time you need to change a hardware peripheral configuration.

Just think about that. The only other thing on this list that really does that is Arduino. Let's take a look at the code:

#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/gpio.h"
#include "driverlib/rom.h"
#include "driverlib/sysctl.h"
#include "driverlib/uart.h"

void main(void)
{
	//16 MHz Crystal with PLL at 50 MHz
	ROM_SysCtlClockSet(SYSCTL_SYSDIV_4  | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);

	//**** SETUP USART0
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);		//Turn on GPIOA, UART0 is on A0/A1
	ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);	//Assign USART pins
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);		//Enable USART 0
	// Setup USART0 for 115K, 8-1-N
	ROM_UARTConfigSetExpClk(UART0_BASE, ROM_SysCtlClockGet(), 115200, (UART_CONFIG_PAR_NONE | UART_CONFIG_STOP_ONE | UART_CONFIG_WLEN_8)); 
	//**** End UART config
	
	std_putChar('a');	//print a
}


void std_putchar(uint8_t c) 
{
	while(HWREG(STDIO_BASE + UART_O_FR) & UART_FR_TXFF);	// Wait until FIFO has space
	HWREG(STDIO_BASE + UART_O_DR) = c;			// Send the character
}

Like STM32 we have left out the generic startup code for the NVIC.

We see a bunch of includes at the top these are the files needed for the StellarisWare libraries. It is rather a long list for the simple task we want to do, but the list doesn't get a lot longer for more complex programs basically one extra file per peripheral type.

Looking at the code we see it is infinitely more clear what it does. Even without comments it could be understood. Even though some of the defined symbols must be looked up the human readable nature makes learning from other code much easier.

After the clock is configured, the logical order to turning on a peripheral is:

  1. Turn on the port/pins that the peripheral is one (SysCtlPeripheralEnable)
  2. Tell it what module to attach the pins to (GPIOPinTypeUART)
  3. Turn on the peripheral module (SysCtlPeripheralEnable)
  4. Configure the peripheral module (UARTConfigSetExpClk)

Quite logical. If you wish to change the baud rate there is no table to reference for a high and low register magic number, just the actual human readable number or setting.

Of course this ease and clarity masks procedures that are nearly identical to those in MSP430, STM32, etc. It could be argued this is an extra overhead then. However, LM3S stores these functions in ROM for the most part so they don't take up extra flash. So program size overhead isn't a consideration. Performance isn't negatively impacted either because these functions are called probably once at the start of the program or infrequently, so any impact on code performance should be small.

In sum you get really easy hardware setup with LM3S that lets you get on with writing the application rather than reading the datasheet for the magic numbers to put in registers. This is one of the main reasons our development boards (Cygni, Eridani, Procyon) are built on LM3S microcontrollers. StellarisWare also makes most code portable across all LM3S parts. This combined with the readable code style and tons of examples from Ti, makes learning LM3S very easy.

knowledge/uartsaya.txt · Last modified: 2014/08/01 20:14 (external edit)
 
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki