19 May 2012

Tutorial 16f: The Transmitter

We now have all the tools to build a UART transmitter on the MSP430. We have an accurate clock, which operates at a frequency that gives us the best accuracy on standard transmission rates with the DCO. The best way to control the timing of our serial transmission will be to use the Timer_A peripheral, so let's look at how to configure it.

First, we need to ensure we have our DCO set to our custom calibration. The way I've chosen to do this is to use definitions in the header:
   #define CALDCO_UART  *(char *)0x10BE
   #define CALBC1_UART  *(char *)0x10BF
Then in our code, we can set the DCO just as we would for the 1 MHz calibration:
   BCSCTL1 = CALBC1_UART;
   DCOCTL = CALDCO_UART;
To make this even easier, you can put the #defines in a header file (I've called mine calibrations.h), and then include it in any program where you need to use the custom DCO calibration.

Now to set up our timer. First, we need to choose our transmission rate. The commonly selected rate is 9600 baud, and if you use only the LaunchPad's USB connection it may be the only rate that works.  (It's a little unclear still how the USB connection is set up-- the MSP430 UART connects to the MSP430 in the emulator, which has separate pins to forward the data to the TUSB chip interfacing with the USB port.)  This DCO frequency can reliably do transmission up to a rate of 921,600 baud. Any faster, and there may not be enough time to run the transmission code between bits.  The code you can download at the end of the tutorial is configured for 9600 baud.

The next thing we need is to know how many clock cycles occur during the time to transmit one bit. We'll transmit at 9600 bits per second, and our clock oscillates at 7,372,800 times per second, so there are 7,372,800/9600 = 768 clock cycles in each bit. (Note that this is exact; there's no fractional part being truncated in this division, which is why we chose this DCO frequency to start with.) This period is often called the bit time.
   bit_time = 24576;  // bit time for 300 baud
   bit_time = 768;    // bit time for 9600 baud
   bit_time = 64;     // bit time for 115,200 baud

The LaunchPad is set up to send the transmission over P1.1. Looking in the datasheet, we see that this pin can be used as the TA0.0 outputusing Timer_A, we can toggle this output when the TAR counter reaches the value stored in TACCR0. We have two options for this: we can use the timer's up-mode so that the counter resets after reaching TACCR0 of bit_time, or we can use continuous mode and reset TACCR0 to be bit_time cycles beyond the current value of TAR when it triggers an interrupt. (Doing this automatically accounts for roll-over.) The first would be an easy solution for our transmitter, but as we're working towards a full-duplex receiver/transmitter, and the receiver looks at P1.2 (using TA0.1, based on TACCR1 for timing, of course), it makes more sense to use the second method in anticipation of setting up the receiver next time so that we don't have conflicts in using the timer for both tasks.

The idle state for UART is logic high. When we configure the timer, we'll ensure that the TA0.0 defaults to setting P1.1. There's no need to start the timer until we're ready to transmit, but on the other hand there's no need to stop it unless you're concerned about power consumption. We'll go ahead and let the timer run for now, since as a peripheral it runs independently and won't interfere with any code running. We will, however, wait to enable interrupts until we're ready to transmit.

Once we're ready to transmit a character, we need to enable interrupts, and configure Timer_A to reset the TA0.0 output to signal the start of a character. TACCR0 is incremented to bit_time counts later, so the next interrupt occurs at the timing necessary for our chosen transmission rate. We then step through the bits being sent and tell the MSP430 whether the next bit should be set or reset, depending on our encoding. We'll use the standard 8N1, little-endian encoding, so it is determined by the lsb of the character we're sending. When all 8 bits have been sent, we then set TA0.0 to mark a stop bit, making a total of 10 bits for each character. Interrupts are then disabled until we have another character to send.

Take a look at the code in uartTXG2231.c. Note first of all the routine DCO_init(). This includes a trap–since we're accessing the flash memory directly, we run the risk of setting the DCO to its highest setting if we use a device that hasn't had the DCO calibration written to that memory address! This type of check will prevent that, and is not a bad thing to include even when using the factory calibrations. I've created an LED signal specifically to indicate a DCO calibration error: three flashes of LED1, repeated with a pause between sets. If you try this code with an uncalibrated G2231, it should immediately indicate this error. Note that it cannot tell if a written code is correct or not, only if the addresses we're accessing have been erased.

If you've jumped right in and tried to run the code after calibrating your DCO, you may still encounter the error.  CCS by default erases both the main memory and the information memory segments (except for INFO_A) when you program the chip; so your calibrations in INFO_B have been erased and the code won't run.  Rerun the UART calibration code, and then open the project properties.  (When uartTXG2231 is the [Active -Debug] project, you can find the properties in the File menu, or right-clicking the project name in the Project Explorer tab.)  In the menu on the left, select Debug.  In the window that comes up, select MSP430 Properties.  Here you'll see a list of Download Options; be sure to choose "Erase Main Memory Only" and close the properties window.  Now when you load this project into your device, it will only erase the Main Memory and leave the information memory segments alone.

This code is set up to demonstrate a few ways to transmit data.  Each loop is initiated by pressing the button on P1.3.  First, a string is stepped through and sent over UART character by character.  Individual characters are also sent, as is the value of a variable count, which is set to repeatedly count from 1 through 9.  Single digits can be sent as characters easily as they appear in order in ASCII-- the digit 0 is ascii code 48 = 0 + 48, 1 is 49 = 1 + 48, and so on.  A multiple digit number could be pushed through using code similar to what we used for the LCM.  These are some simple ways of putting data and messages through UART that take very little code space in your device.

The real magic happens in the tx_byte() and CCR0_ISR() routines.  When tx_byte is called, it first polls the UART_FG flag and waits for the TXbit to clear.  The byte being sent is loaded into a buffer and formatted to include the stop bit. The TXbit is set in UART_FG, an LED turned on to indicate transmission, and the timer is configured to reset at the next interrupt.  A character cannot be transmitted until at least bit_time after the previous transmission; since the MSP430 clock is so much faster than the data rate, you could potentially run into a problem of initiating a new character send microseconds after the last, rather than leaving at least one bit_time space between.  To prevent this, any present interrupt flag is cleared and the next interrupt is set for one bit_time after the current time.  This configuration leaves just slightly more than one bit time between characters.  The interrupts run the CCR0_ISR routine, which sets the next interrupt to be bit_time later, configures TA0.0 to the next bit state for the next interrupt, and shifts the transmission buffer down to pull the next bit to the front.

To see all of this happen, you'll need a serial terminal, such as RealTerm or PuTTY.  When your terminal is directed to watch the port your LaunchPad is on at the 9600 baud rate, you should see the message display every time you push the button.

Next time we'll use Timer_A to receive data, getting ready for a complete UART transceiver.

10 comments:

Tony K said...

Hi David, I've enjoyed your posts so far and I'm excited that you're posting new tutorials again.

Regarding the Launchpad "Applications UART" it actually uses a "software" UART implemented on the TUSB3410 not dissimilar to the Timer_A UART; the hardware UART on the TUSB3410 is already taken up to communicate between the host and the FET circuitry. The 9600 baud rate is probably hard coded into the firmware, and in any case the 8052 on the TUSB3410 would be too busy servicing the USB and the FET for a higher baud rate.

Tony

Yaseen Khan said...

Hi David,

May be I am doing something silly and dumb. I am using Launchpad with MSP430G2553.

Initially, the code was not compiling due to error with the declaration of timerA0 interrupt vector. So, I made this change -

#pragma vector = TIMER0_A0_VECTOR

So, it started to compile. I also ran your UART_calibration code first and made sure the INFO_B calibration data is correct i.e. I don't see rapid red led flashing.

I have included calibrations.h as follows -

/*
* calibrations.h
*
* Created on: Sep 1, 2012
*
*/

#ifndef CALIBRATIONS_H_
#define CALIBRATIONS_H_

#define CALDCO_UART *(char *)0x10BE
#define CALBC1_UART *(char *)0x10BF

#endif /* CALIBRATIONS_H_ */

But for some reason when I try send data over 9600-N-1, I get nothing on RealTerm.

I know you are pretty busy person, but I would appreciate any pointer or silly thing I am doing.

Thanks a bunch.
YK

ogden said...

I advise to use timer periodic interrupt mode - to use CCR. Only then you will get accurate frequency. Problem in your code is that CPU instructions in the ISR routine to set next interrupt takes CPU cycles, thus time, meaning that actual interrupt frequency is lower than you expect. What's worse - your code is compiler dependent.

David Olson said...

Thanks for pointing that out! The reason I used the ISR to set the next interrupt makes more sense when taken in context of both a receiver and a transmitter-- the only way to use a set CCR on the timer in this case is to have a separate timer for each-- which this particular device does not have. By setting the next interrupt manually,you can use a single timer for both without having to worry about whether a receive is triggered near the edge of a clock cycle or not. This method is certainly not the best way (ideally you'd be using USCI!), but it works, particularly for slow transmission rates. The CPU is clocking at such a high rate compared to the transmission speed, that even a fistful of clock cycles won't introduce too much error.

For a stand-alone software transmitter (or receiver, for that matter), using CCR in Timer_A is certainly a better choice!

Sri said...

If i want to to glow an LED usuing the signal from TX pin is sending "1"(char) equal to a digital high?

David Olson said...

Sri,

I'm not exactly sure what you mean. A single character will be an 8-bit pattern, for the character "1", that corresponds to 0x31. If you use the value itself in a truth test, it will evaluate to logic high, or true, because it is non-zero. But the character "0" is 0x30, which is also non-zero.. the only way a UART character will evaluate as False is by sending the null value 0x00.

You can, however, examine the received byte and turn on an LED if it matches a particular value, and turn off the LED if it matches another.

Does that help at all?

Sri said...

Thanks for your reply David,
I just wanted to know how to send the value 0x00 over the TX pin,so that when i connect a LED to the TX pin and i send ox00 it should not glow.I am using uart.h lib from the net.

David Olson said...

In that case, you should be able to push the value 0x00 directly via UART. You don't need to pass a specific character, as those are just encodings of 8-bit int values anyway. In my code, sending tx_byte(0x00); should turn off your LED, while sending any other value should turn it on during the moments when the bit value is high. (Some bytes will only light your led briefly, while others will have it on most of the time.)

Anonymous said...

Hello David,

Great Blog! But the link you gave the c code is not working, I think it's expired. Can you please renew it?

Thank you,
Gunay

David Olson said...

I'm not sure why the links aren't working for you; they appear to be good from my end. Let me know if you're still having trouble with the links.