22 May 2012

Tutorial 17: The Receiver

Once you've built a UART transmitter, the receiver is not much different!  The way we'll build the receiver here will be done in anticipation of putting both the transmitter and receiver on the same device, and we'll look at a few new things.

What's the Same

We'll use Timer_A to work with the timings, using the same bit_time value as the transmitter.  The timer will need an accurate clock, and we'll use the calibrated UART frequency from last time as well.  The interrupt service routine for the timer will handle all of the work for reading the serial data as it's transmitted to the device.

What's Different

There are a number of differences in how the receiver is handled, but most of them are quite minor.  We'll use interrupts on the GPIO to trigger receiving, for one.  Once we've seen a falling edge on the GPIO (signalling a start bit being received), we need to wait one half bit_time so that we're sampling in more or less the center of each bit, thus we need a definition for a half bit_time as well.  (We could just divide bit_time by 2, but that's actually kind of slow-- it's probably more conducive to just define another variable with the exact value we want.)  All of this will be handled by an interrupt service routine for the GPIO port.

In addition, we want to use TA1 instead of TA0 for the timing-- this will allow us to send and receive simultaneously, as each has its own independent timer.  TA1 has a slightly different way of handling the ISR, however.  It's worth noting that there's nothing special about TA0 for transmitting nor TA1 for receiving-- we could just as easily have reversed the two.  Which one you use depends on which pins you connect for transmit and receive.  On the LaunchPad (the earlier versions, anyway), Tx is connected to P1.1 and Rx to P1.2.  Looking at the datasheet for the devices that connect to the LaunchPad, P1.1 is connected to TA0.0, letting us use the output feature of Timer_A to change the bit automatically.  P1.5 can also be connected to the TA0.0 output, so that is another option.  P1.2, P1.6, and P2.6 can all be connected to the TA0.1 output, and are all possibilities for the transmit pin if you change from CCR0 to CCR1 for the transmitter's timer.  The receiver will be reading a GPIO, and could in principle be used with any GPIO pin.  (We'll look in a short while to how we can do transmitting on any GPIO as well.)

The Specifics

Alright, let's dig into the code.  First, we need to define our bit_time again, as well as a half_bit_time.  For 9600 baud at the UART calibrated frequency (7.3728 MHz), these correspond to 768 and 384 respectively.  We'll make use of the UART_FG flag variable again, leaving BIT0 for a transmitting flag and using BIT1 for a receiving flag.  The data will be loaded into a buffer again before being saved, using the int value RXBuffer.  The bit_count variable comes into play again, and will be used to count down the bits as they are received.  When we combine the transmitter with the receiver, we'll need one of these for each of the parts so they can count independently.  For now, I've just used the same variable name bit_count.

The DCO and Timer are initialized identically to the transmitter-- the differences in the code happen in the interrupt routines.  P1 is configured to read from our chosen receive pin (P1.2), enabling interrupts on a falling edge.  When the interrupt occurs (presumably at the start bit of a transmission), CCR1 is initialized to a half bit-time later, CCR interrupts are enabled, and P1 interrupts temporarily disabled until the data has been received.  This method precludes having to wait for the receive flag to clear before the next P1 interrupt, so no while loop is needed here.

The CCR1 interrupt is handled differently than the CCR0 interrupt.  For one, CCR0 triggers the interrupt flag TAIFG in the TACTL register.  All other capture/compare registers in a device will trigger flags in the TAIV register.  There is a single interrupt for this register, and so TAIV must be read to know which CCR caused the interrupt.  (Though in the LaunchPad devices there is only CCR1, the interrupt must be handled the same way as though there were further CCR's in the device.)  I've done this by using a C switch statement, looking specifically for a CCR1 flag, which is marked by bit 1 in TAIV.  TAIFG also appears in TAIV, but as the value 0xA rather than 0x2, so a case 2: is sufficient for identifying a CCR1 interrupt flag.  The flag is automatically cleared with the interrupt is serviced.  The bits are then read in bit_time intervals from the GPIO input.  When all 10 bits have been read, the timer interrupts are disabled and P1 interrupts re-enabled.

Now that we've covered essentially how the code works, you can test it out with uartRXG2231.c.  (Don't forget to configure it to not erase the information segments!)  Look at the main function here-- it's set up to look for single-character commands, recognizing the characters 'r' to toggle the red LED, 'g' to toggle the green LED, and 'z' to do nothing but go into a low power mode and wait for a command.  If an invalid command is sent, the code holds on to the current state of P1OUT and flashes the LEDs to signal an error and returns to the saved state.  After each command, the device returns to a sleep mode.  To use the code, be sure you have at least exited the debugger-- you may find that serial terminals don't like trying to communicate through the same port that is opened in CCS in debug mode; exiting debug mode releases the serial port for use. When connected to the proper port (at 9600 baud, of course), you should be able to send single character commands to toggle the red and green LEDs. Try sending an invalid character to see the error signal.

This is the simplest way of sending commands to the MSP430.  Obviously there are uses for multiple-character commands, or sending data directly.  We'll look at those techniques at some point in the future, but next time we'll combine the two parts into a full-duplex transceiver.

Reader Exercise: Add a command to clear both LEDs. See if you can come up with another function to add a command for.

1 comment:

Rastislav said...

I've found this blog after disasembling code for receiving uart on pin2 inspired by Your blog.

I needed to switch input from pin2 to pin6, this required reading datasheet and discovering CCIS bit is used for switching.

Long story short, there is CCIS1 keyword which has nothing to do with input selection. Instead CCIS_1 should be used to select pin6 for TimerA0.1...

Anyway, great blog. I'll read rest of it. Particulary interested in using flash as eeprom and block write.