UART is the traditional serial communication protocol used. It offers only two pins RX and TX for sending and receiving characters serially. A microcontroller can have more than one UART. For instance MSP430F5418 microcontroller has two UART’s (UART and enhanced UART). Just read the desired controller datasheet for getting the info.
There are two methods of using UART.
-
Polling mode
-
Interrupt mode
Commonly interrupt will be used for reception and polling is used for transmission. Here, I will use both methods. Code used in this article is for MSP430F5418 microcontroller. But, this can be ported to any microcontroller series with minor adjustments. Changes had to be made in the registers and interrupt handlers.
The USCI (Universal Serial Communication Interface) module in MSP430F5418 is responsible for serial communications. It has support for both synchronous protocols (SPI, I2C) and asynchronous protocols (UART, eUART). This controller comes with two USCI modules A and B.
-
USCI A controls UART, IrDA, SPI
-
USCI B controls SPI, I2C
IrDA is Infrared encoder/decoder and eUART is Enhanced UART which supports auto baud rate detection.
Prerequisites
Before sending/transmitting through UART there are various things which needs to be done. It includes selecting the RX/TX pins, USCI reset, selecting clock source, determining baud rate and finally enabling the USCI module…Let us look into the software part for more detail.
Configure the Clock source
Select the pins where the crystal is connected, using the PSEL instruction. After that, select the clock source. !!!yep I can your voice, what is the use of clock in asynchronous mode? The reason is that clock source is necessary for the generation of baud rate to synchronize both transmitter and receiver. I had selected SMCLK. You can select as per your wish. The available clocks are SMCLK, ACLK, UCAxCLK.
void clk_config(void) { WDTCTL = WDTPW + WDTHOLD; // Stop WDT P7SEL |= 0x03; // Port select XT1 UCSCTL3 |= SELREF_2; // FLL Ref = REFO UCSCTL6 &= ~XT1OFF; // Set XT1 On UCSCTL6 |= XT1DRIVE_0 + XTS; // Max drive strength, adjust // according to crystal frequency. // LFXT1 HF mode delay_ms(10); // Loop until XT1,XT2 & DCO stabilizes do { UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + XT1HFOFFG + DCOFFG); // Clear XT2,XT1,DCO fault flags SFRIFG1 &= ~OFIFG; // Clear fault flags }while (SFRIFG1&OFIFG); // Test oscillator fault flag UCSCTL4 = SELA_0 + SELS_0 + SELM_0; // Select ACLK, SMCLK, MCLK as LFXT1 }
Here, the clock source is selected as the high frequency crystal oscillator connected to PORT7 pins 0 and 1. As you can see from the code above, the drive strength is selected as per the crystal oscillator frequency. I had used 4MHz crystal, but you can connect up to the maximum allowed frequency.
Note: While using the external crystal it is necessary to check the oscillator fault flags. This will avoid you a lot of troubles. If your fault flag is set even you had connected the crystal properly, do check the soldering point of the crystal. A small dry soldering will spoil your day.
Initializing UART
First step is to select the RX/TX pin on Port3. Using PSEL enables us to select the UART pins as they are also used as general purpose I/O pins. Then, clearing UCSWRST bit in UCA0CTL1 register resets UART. Baud rate can be generated using external clock source (UCAxCLK) and internal clock sources (SMCLK, ACLK) depending upon the selection of UCSSELx bit. Then, the baud rate generator is configured for 9600 baud from 4MHz SMCLK. UCA0MCTL register is used to provide the necessary modulation. Finally, the UCSWRST is reset to initialize the UART module with the selected configuration.
void uart_init_9600(void) { P3SEL = 0x30; // Enable TX/RX pins UCA0CTL1 |= UCSWRST; // Reset USCI UCA0CTL1 |= UCSSEL_2; // SMCLK UCA0BR0 = 0xA0; // Baud rate setting (4MHz 9600) UCA0BR1 = 0x01; // UCA0MCTL |= UCBRS_1 + UCBRF_0; // Modulation UCBRSx=1, UCBRFx=0 UCA0CTL1 &= ~UCSWRST; // Enable UART // UCA0IE |= UCRXIE; // Enable USCI_A0 RX interrupt // __bis_SR_register(GIE); // interrupts enabled }
Note: The UCSWRST bit should be set before configuring the USCI registers. Finally, it should be reset to enable UART.
Polling mode
In this mode the interrupts are not used. Instead the interrupt flags are polled in order to transmit and receive. The interrupt flags are always set during TX and RX irrespective of enabling or disabling interrupts.
Transmitting Characters
Transmission can be enabled by writing to UCA0TXBUF. If the buffer is empty UCTXIFG flag will be set, when it is full it will bet reset. Here, the flag is polled untill it is set. Once, it is set the data will be written to the buffer and the data will be transferred to the Transmit shift register then transmitted via TX pin. The TX flag will automatically reset once the data is loaded into buffer and will be set when it is transmitted.
void uart_put_char(char data) { while (!(UCA0IFG&UCTXIFG)); // Is USCI_A0 TX buffer ready? UCA0TXBUF = data; // Transmit the data return; }
Receiving Characters
Method used for transmission is used for reception also. The UCRXIFG is polled until it is set. When there is data in UCA0RXBUF the flag will be set, once the data is read it will be automatically reset.
char uart_get_char(void) { unsigned char rec_data; while (!(UCA0IFG&UCRXIFG)); // Is USCI_A0 RX buffer ready? rec_data = UCA0RXBUF; // Read the data return rec_data; }
Interrupt mode
Using interrupts is the recommended way over polling, because polling causes the time of the controller to be wasted. Hence, interrupts are used to save the processor time. Also, if you were not aware of the timing of the receiving character, using interrupt will be handy. MSP430F5418 uses same interrupt handler for both transmission and reception. If the TX and RX interrupt are enabled the interrupt request will be generated during transmission and reception.
Uncomment the last two lines in uart_init_9600() function to enable interrupt. It is to be noted that the interrupt is enabled only for reception.
Interrupt handler
#pragma vector=USCI_A0_VECTOR __interrupt void USCI_A0_ISR(void) { switch(__even_in_range(UCA0IV,4)) { case 0:break; // Vector 0 - no interrupt case 2: // Vector 2 - RXIFG buffer = UCA0RXBUF; // Read the data break; case 4:break; // Vector 4 - TXIFG default: break; } }
When a byte is arrived it will be stored in the UCA0RXBUF buffer, then the interrupt flag will be set and the RX ISR will be called. Inside the ISR, the data byte will be read, then the UCRXIFG flag will be automatically reset.
Delay function
Using the accurate delay in your code is recommended. When it comes to delay, many of us will use the for loop for generating the delay. But, that will not be accurate. TI doesn’t provides any delay function in their libraries. So, I had written a delay function, which will be far more accurate than the delay generated using for loop.
I used for loop too :_)
inline void delay_ms(unsigned int k) { unsigned int j; for(j=0;j<k;j++) { _delay_cycles(4000); } }
No Comment