Jump to content
43oh

Help with hardware UART


Recommended Posts

I'm doing a robot arm project for a competition at my school, and I'm having real trouble with getting the g2553 hardware UART to work. For the life of me, I can't figure out why it's not printing anything at all. I compiled the TI code example that I'm modifying and it works just fine on my Linux box with Cutecom, but when I make transmission not interrupt-based, I get nothing! :x

The functions I have in there were what I planned to use with a timer to send strings after a TimerA interrupt, but eventually I stripped it down to just writing to the TX buffer. Still nothing. Shouldn't it give me an 'a"?

 

//******************************************************************************
//   MSP430G2xx3 Demo - USCI_A0, Ultra-Low Pwr UART 9600 String, 32kHz ACLK
//
//   Description: This program demonstrates a full-duplex 9600-baud UART using
//   USCI_A0 and a 32kHz crystal.  The program will wait in LPM3, and will
//   respond to a received 'u' character using 8N1 protocol. The response will
//   be the string 'Hello World'.
//   ACLK = BRCLK = LFXT1 = 32768Hz, MCLK = SMCLK = DCO ~1.2MHz
//   Baud rate divider with 32768Hz XTAL @9600 = 32768Hz/9600 = 3.41
//* An external watch crystal is required on XIN XOUT for ACLK *//
//******************************************************************************
#include 
#include 

void writeChar(char aChar);
void writeWord(char* word);


void main(void)
{
 WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT  
 P1DIR = 0xFF;                             // All P1.x outputs
 P1OUT = 0;                                // All P1.x reset
 P2DIR = 0xFF;                             // All P2.x outputs
 P2OUT = 0;                                // All P2.x reset
 P1SEL = BIT1 + BIT2 ;                     // P1.1 = RXD, P1.2=TXD
 P1SEL2 = BIT1 + BIT2 ;                     // P1.1 = RXD, P1.2=TXD
 P3DIR = 0xFF;                             // All P3.x outputs
 P3OUT = 0;                                // All P3.x reset  

 //CCTL0 = CCIE;                             // CCR0 interrupt enabled
 //CCR0 = 50000;
 //TACTL = TASSEL_1 + MC_2 + TAIE;                  // ACLK, contmode, enable timer interrupt


 UCA0CTL1 |= UCSWRST; // Put state machine in reset
 UCA0CTL1 |= UCSSEL_1;                     // CLK = ACLK
 UCA0BR0 = 0x03;                           // 32kHz/9600 = 3.41
 UCA0BR1 = 0x00;                           //
 UCA0MCTL = UCBRS1 + UCBRS0;               // Modulation UCBRSx = 3
 //UC0IE |= UCA0RXIE | UCA0TXIE;
 UCA0CTL1 &= ~UCSWRST;                     // **Initialize USCI state machine**

 UCA0TXBUF = 'a';

 __bis_SR_register(GIE);

}

void writeChar(char aChar)
{
 while (!(IFG2&UCA0TXIFG));                // USCI_A0 TX buffer ready?
   UCA0TXBUF = aChar;                      // TX next character
 //IFG2 &= ~UCA0TXIFG;
}

void writeWord(char word[])
{
 while(*word)
 //for(unsigned int i = 0; i < sizeof word - 1; i++)
   writeChar(*(word++));
}

 

Here's the makefile, if it helps. I think the only changes are that the mcu flag was set to msp430g2553 to make Uniarch happy and the version of C to c99.

CC=msp430-gcc
CFLAGS=-Os -Wall -g -mmcu=msp430g2553 -std=c99

OBJS=main.o


all: $(OBJS)
$(CC) $(CFLAGS) -o main.elf $(OBJS)

%.o: %.c
$(CC) $(CFLAGS) -c $<

clean:
rm -fr main.elf $(OBJS)

 

I've been stuck on this for days and would be tremendously grateful to anyone who could help.

Link to post
Share on other sites
I've been stuck on this for days and would be tremendously grateful to anyone who could help.

 

Did you change the jumpers for TX / RX on J3? Is this on a 1.5 Launchpad with the new style jumpers? The old

LP version <= 1.4 board will require you to provide a cross jumper.

 

When you say you were using an interrupt based version and it worked, was that a Timer A based interrupt or USCI

based interrupt? If you were using the TimerA based serial it would work with the original TX/RX jumpers.

Hardware based USCI would require a change, you would need to swap TX/RX on the J3 jumper.

 

-rick

Link to post
Share on other sites
Try putting the write into a loop:

 

  while (1) {
   if (UC0IFG & UCA0TXIFG) {
     UCA0TXBUF = 'a';
   }
 }

 

Sometimes the clocks take a while to stabilize and the first character emitted is garbled.

 

Your code with this change spits a stream of "a"s to the output on my launchpad+g2553.

 

If you do that on a linux box, it is going to overflow the serial ACM buffer and the kill the port. It will just be dead.

On linux, you should be waiting till you receive something before you start spewing.

Link to post
Share on other sites

Thanks for the help, guys! Still don't have it fixed or really know why, though.

 

Sometimes the clocks take a while to stabilize and the first character emitted is garbled.

 

Your code with this change spits a stream of "a"s to the output on my launchpad+g2553.

Does the code without the change output a single 'a'?

 

I'm using a Rev 1.4 launchpad right now, but I do have a 1.5 also. I don't think the problem is with the jumpers because it works with TI's unmodified code example perfectly. Sorry for not being clear, and "interrupt-based" meant hardware USCI (USCIAB0RX_VECTOR, USCIAB0TX_VECTOR, all that good stuff).

 

If hitting the port with a unsolicited continuous stream of 'a's will kill the port, how do I not kill it without sending something first? The goal eventually is to be able to control the arm through a wireless PS2 controller, but I'll need the msp to give me continuous positioning output to my PC before I can hope to even get hardwired servo control right.

Link to post
Share on other sites
If hitting the port with a unsolicited continuous stream of 'a's will kill the port, how do I not kill it without sending something first? The goal eventually is to be able to control the arm through a wireless PS2 controller, but I'll need the msp to give me continuous positioning output to my PC before I can hope to even get hardwired servo control right.

 

Just start by sitting in a loop waiting for a request. Once you receive something you can start sending back your position data.

 

This seems to work:

 

main.cpp

/**
* main.cpp - swserial/timerserial/usciserial Serial Asynch test driver.
*
* To test: use putty and connect to /dev/ttyACM0 at 9600 Baud
*
*/

#include 
#include 
#include "serial.h"

#define MCLK_FREQ 16000000UL /* run @16MHz */

/**
* typedef Serial - start of a simple serial wrapper class
*/
typedef struct {
   /**
    * begin() - initialize serial port, sets registers, port direction, and
    *   alternative port features as required by implementing serial routines.
    *   Depending on the implementing method, you may not be able to use
    *   arbitrary rx/tx pins the software only implementation is the only
    *   version capable of using any arbitrary pin.
    *
    * baudRate - bits/sec  default: 9600
    * rxPin - receive pin  default: 1 (P1.1) ( g2553 defaults )
    * txPin - transmit pin default: 2 (P1.2)
    */
   static void begin(uint32_t baud = 9600, int rxPin = 1, int txPin = 2) {
       init_serial(1 << txPin, 1 << rxPin, MCLK_FREQ / baud, (MCLK_FREQ / baud) >> 8);
   }

   /**
    * puts - writes the string s to the serial output stream without a trailing
    *    newline character. Note: this differs from the ISO standard puts()
    *    function which appends a newline character.
`     */
   static void puts(register const char *s) {
       while(*s) {
           putchar(*s++);
       }
   }
} serial_t;

static serial_t Serial;

static void initMCLK() {

#ifdef __MSP430FR5739
   CSCTL0_H = 0xA5;                   // CS_KEY
   CSCTL1 |= DCOFSEL0 + DCOFSEL1;     // Set max. DCO setting
   CSCTL2 = SELA_3 + SELS_3 + SELM_3; // set ACLK = MCLK = DCO
   CSCTL3 = DIVA_0 + DIVS_0 + DIVM_0; // set all dividers
#else
   // Use 16MHz DCO factory calibration
   DCOCTL = 0;
   BCSCTL1 = CALBC1_16MHZ;
   DCOCTL = CALDCO_16MHZ;
#endif
}

int main(void) {
   WDTCTL = WDTPW | WDTHOLD;  /* Disable watchdog */

   initMCLK();

   // configure serial device using defaults
   Serial.begin(9600);

   //__eint();   // we enable interrupts after user setup() to give them a chance to customize

   // if you are using an FTDI or other reasonable USB-serial chip you can output
   // serial data at reset. If you want to use the Launchpad /dev/ttyACM0 you have
   // to wait for a request before spewing.

#if 0
   printf("\rWelcome to msp430 uNix! %s %s %d, %d:%d %s\r\nlogin: ", "Fri", "Oct", 9, 11, 25, "AM");
   Serial.puts("\r\n# ");
#endif

   int c;
   int nCharCnt = 0;

   for (; {
       c = getchar();

       // do some minimum line control to handle backspace
       if ( c != 127 ) {
           putchar(c);
           nCharCnt++;
       } else {
           if (nCharCnt > 0) {
               putchar(c);
               --nCharCnt;
           }
       }

       // append newline on CR
       if ( c == '\r' ) {
           Serial.puts("\n# ");
           nCharCnt = 0;
       }

       // restart processor on CTRL-D
       if ( c == 0x04 /*CTRL-D*/ ) {
           WDTCTL = WDTHOLD; // restart by setting the WDTCTL without a password
       }
   }

   return 0;
}

 

usci_serial.c

/*
* usci_serial.c - USCI implementations of init_serial(), getchar(), putchar() and puts()
*
*  Created on: Oct 30, 2011
*      Author: kimballr - rick@kimballsoftware.com
*/

#include 
#include 
#include "serial.h"

/**
* init_serial(txPinMask, rxPinMask, bitDuration, durMod)
*
*  Really simple use of the hardware UART so that it will be
*  compatible with the our simple software ones. We setup the
*  hardware UART using the pins and bit duration provided.
*
*  This code doesn't use any interrupts. Just simple polling
*  to decide when to receive or transmit.
*
*/

void init_serial(int txPinMask, int rxPinMask, unsigned duration, unsigned durmod) {
   // Use UART defaults for most things.
   // LSB
   // 8-N-1 ( 8 bit, NO parity, 1 Stop bit)

   UCA0CTL1 |= UCSSEL_2;   // use SMCLK as source for USCI clock
   UCA0BR0 = duration;     // bit duration is MCLK/BAUD
   UCA0BR1 = durmod;       // (MCLK/BAUD) >> 8

#ifdef __MSP430FR5739
   P2SEL1 |= BIT0 | BIT1;
   P2SEL0 &= ~(BIT0 | BIT1);
#else
   P1SEL = rxPinMask | txPinMask; // P1.1=RXD, P1.2=TXD
   P1SEL2 = rxPinMask | txPinMask; // P1.1=RXD, P1.2=TXD
#endif

   UCA0CTL1 &= ~UCSWRST; // Initialize USCI state machine
}

/**
* int getchar() - read next char from serial port rx pin
*
*
*/
int getchar(void) {
   // sit and spin waiting for baudot heh I make myself laugh

   while (!(UC0IFG & UCA0RXIFG)) {
       ; // busywait until USCI_A0 RX buffer is ready
   }

   return UCA0RXBUF; // return RXed character
}

/**
* putchar(int c) - write char to serial port
*
*/
int putchar(int c) {

   // make sure previous character has been sent
   // before trying to send another character
   while (!(UC0IFG & UCA0TXIFG)) {
       ; // busywait until USCIA0TX buffer is ready
   }

   UCA0TXBUF = (uint8_t) c; // TX character

   return 0;
}

 

serial.h

//------------------------------------------------------------------------
// serial.h - function declarations for compact asm serial routines
//------------------------------------------------------------------------

#ifdef __cplusplus
extern "C" {
#endif

   void init_serial(int txPinMask, int rxPinMask, unsigned duration, unsigned durMod);
   int getchar(void);
   int putchar(int);

#ifdef __cplusplus
} /* extern "C" */
#endif

 

To compile just use:

 

msp430-gcc -O0 -g -mmcu=msp430g2553 main.cpp usci_serial.c -o main.elf

Link to post
Share on other sites

Sometimes the clocks take a while to stabilize and the first character emitted is garbled.

 

Your code with this change spits a stream of "a"s to the output on my launchpad+g2553.

Does the code without the change output a single 'a'?

No. Hence the assumption that the first one (or several) are not being correctly decoded by the PC, possibly due to clock differences. I didn't bother sending a different character on each iteration to see which one got through first, and I've shut down my development machine for the night.

 

The spew shouldn't be an issue at 9600, but it was really just a proof of concept (note that I use the HID interface rather than the tty interface, as noted here, which may be is less susceptible to serial port instabilities). If the possibility bothers you, put a delay in the loop; the point is to see whether you do start getting characters out or not. If not, there's something else wrong: your 32kiHz clock isn't, or your jumpers are wrong, or other things. That you said the demo code from TI worked suggests those aren't an issue.

Link to post
Share on other sites

pabigot, thanks for the suggestion. I need to try that tomorrow.

 

Rick, thank you SO much for the code. I'm actually getting output now! I did have to modify your code a little and take out some C++ stuff since mspgcc doesn't want to compile the cpp file. I'll post it tomorrow for reference, but it does work and I understand it for the most part.

The (comparatively small) trouble now is that I don't get this modulation business. Could you explain the UCA0BR1 register? The datasheet goes a little over my head on the first read, and mainly I want to know how to adjust the registers for different clock speeds. The USCI does NOT like me simply redefining MCLK_FREQ to 32768 and choosing ACLK.

 

Again, thanks everyone. I've gotten more done today than the last week because of your help.

Link to post
Share on other sites

The (comparatively small) trouble now is that I don't get this modulation business. Could you explain the UCA0BR1 register? The datasheet goes a little over my head on the first read, and mainly I want to know how to adjust the registers for different clock speeds. The USCI does NOT like me simply redefining MCLK_FREQ to 32768 and choosing ACLK.

 

The easiest way to deal with the baud rate registers is to use this page:

 

http://mspgcc.sourceforge.net/baudrate.html

 

http://mspgcc.sourceforge.net/cgi-bin/msp-uart.pl?clock=32768&baud=9600&submit=calculate

Seems to yield:

 

UBR00=0x03; UBR10=0x00; UMCTL0=0x29; /* uart0 32768Hz 9637bps */

 

I haven't tried the setting above so I can't vouch for it. I'm still trying to understand the proper way to use these registers also.

I've looked at the msp-uart.pl perl code and compared it to another site on the net that uses an Excel spreadsheet

and they produce different results and use different algorithms. If you can set your MCU_FREQ to be a multiple

of the baud rate you want to use you can ignore all the modulation. Using a 1.2288 Mhz clock frequency the cycle count between bits would be an even multiple of 9600 so the UBR00 would be 1228800/9600 = 128.

 

-rick

Link to post
Share on other sites
The easiest way to deal with the baud rate registers is to use this page:

http://mspgcc.sourceforge.net/baudrate.html

While that does work, it was only designed for the 1xx generation of MSP430.

 

As a general practice, I'd recommend instead getting the family user guide for the specific chip (in the case of the MSP430G2553, go to the mcu homepage, and download MSP430x2xx Family User's Guide revision H). In the UART chapter for your chip, there'll usually be a table of "Typical Baud Rates and Errors", which lists the sub-register settings for a specific clock. There are a lot of UART alternatives across the MSP430 line, and looking in the guide rather than using a script will help keep you aware of any inconsistencies between them. (I was surprised to learn that the eUSCI UART on the FRAM parts has a few differences, including recommendation of UCOS16 and a new register field.)

 

There are a variety of UART configuration examples for different UART peripherals in the platform support directories of test430. If you use standard DCO rates (like the pre-calibrated ones), and SMCLK or a derivative thereof as the UART clock, calibration is pretty straightforward. Keep in mind that while "32K" in crystal-speak is really 32 kiHz or 32.768 kHz or 2^15 Hz, 1MHz is really 10^6Hz and never 2^22Hz. This can have a significant impact on error rate.

 

One other thing to keep in mind: in a lot of cases, the crystal doesn't contribute to the DCO unless the XIN/XOUT port pins are correctly configured in addition to the relevant clock register settings. demo/clocks in test430 configures and validates the crystal on a launchpad; see the comments. I seem to recall noticing that if this isn't done, a brought-to-GPIO ACLK doesn't actually tick unless it's reconfigured to use VLOCLK, at least on the value-line devices, but I don't know what impact that might have on internal use. I alway use SMCLK to control a UART; the only reason I can think of to use ACLK is if you're doing interrupt-driven I/O in low-power mode using DMA, and don't want to wait for the DCO to re-stabilize.

Link to post
Share on other sites

Today was... a learning experience. I had wanted to use an external crystal to provide a 16MHz MCLK and spent all day trying to figure how, but it looks like the G2553 doesn't even have the capability to use an oscillator above 50kHz! :(

I hope they provide that in a later G-series generation, but in the meantime, I'll have to use the DCO like in Rick's code. Plan is now a 1MHz MCLK, 500kHz SMCLK for the controller's SPI bus, and 32.768kHz for the UART.

 

All the advice and links you gave will definitely help with fully understanding UART functionality, but for now I'll leave good enough alone and hope I don't break anything. Scheduled deadline's in a week.

 

By the way, what license is the code under?

 

Here's Rick's code modified to conform to boring, old C.

    
   /**
   * main.c - swserial/timerserial/usciserial Serial Asynch test driver.
   *
   * To test: use putty and connect to /dev/ttyACM0 at 9600 Baud
   *
   */

   #include 
   #include 
   #include 
   #include "serial.h"

   #define MCLK_FREQ 16000000UL /* run @16MHz */


/**
* typedef Serial - start of a simple serial wrapper class
*/
//typedef struct {
   /**
    * begin() - initialize serial port, sets registers, port direction, and
    *   alternative port features as required by implementing serial routines.
    *   Depending on the implementing method, you may not be able to use
    *   arbitrary rx/tx pins the software only implementation is the only
    *   version capable of using any arbitrary pin.
    *
    * baudRate - bits/sec  default: 9600
    * rxPin - receive pin  default: 1 (P1.1) ( g2553 defaults )
    * txPin - transmit pin default: 2 (P1.2)
    */
/*    static void begin(uint32_t baud = 9600, int rxPin = 1, int txPin = 2) {
       init_serial(1 << txPin, 1 << rxPin, MCLK_FREQ / baud, (MCLK_FREQ / baud) >> 8);
   }
*/
   /**
    * puts - writes the string s to the serial output stream without a trailing
    *    newline character. Note: this differs from the ISO standard puts()
    *    function which appends a newline character.
`     */
/*    static void puts(register const char *s) {
       while(*s) {
           putchar(*s++);
       }
   }
} serial_t;

static serial_t Serial;
*/

   //void begin(uint32_t baud = 9600, int rxPin = 1, int txPin = 2) {
   void begin(uint32_t baud, int rxPin, int txPin);

   //void puts(register const char *s) {
   void putS(const char *s);

   //static void initMCLK()
   void initMCLK() {

   #ifdef __MSP430FR5739
       CSCTL0_H = 0xA5;                   // CS_KEY
       CSCTL1 |= DCOFSEL0 + DCOFSEL1;     // Set max. DCO setting
       CSCTL2 = SELA_3 + SELS_3 + SELM_3; // set ACLK = MCLK = DCO
       CSCTL3 = DIVA_0 + DIVS_0 + DIVM_0; // set all dividers
   #else	      
       // Use 16MHz DCO factory calibration
       DCOCTL = 0;
       BCSCTL1 = CALBC1_16MHZ;
       DCOCTL = CALDCO_16MHZ;
   #endif
   }

   int main(void) {
       WDTCTL = WDTPW | WDTHOLD;  /* Disable watchdog */

       initMCLK();

       // configure serial device using defaults
       //Serial.begin(9600);
begin(9600, 1, 2);

       //__eint();   // we enable interrupts after user setup() to give them a chance to customize

       // if you are using an FTDI or other reasonable USB-serial chip you can output
       // serial data at reset. If you want to use the Launchpad /dev/ttyACM0 you have
       // to wait for a request before spewing.

       int c;
       int nCharCnt = 0;

       while(1){    
    c = getchar();

           // do some minimum line control to handle backspace
           if ( c != 127 ) {
               putchar(c);
               nCharCnt++;
           } else {
               if (nCharCnt > 0) {
                   putchar(c);
                   --nCharCnt;
               }
           }

           // append newline on CR
           if ( c == '\r' ) {
               //Serial.puts("\n# ");
               putS("\n# ");
               nCharCnt = 0;
           }

           // restart processor on CTRL-D
           if ( c == 0x04 /*CTRL-D*/ ) {
               WDTCTL = WDTHOLD; // restart by setting the WDTCTL without a password
           }
       }

       return 0;
   }


   /**
        * begin() - initialize serial port, sets registers, port direction, and
        *   alternative port features as required by implementing serial routines.
        *   Depending on the implementing method, you may not be able to use
        *   arbitrary rx/tx pins the software only implementation is the only
        *   version capable of using any arbitrary pin.
        *
        * baudRate - bits/sec  default: 9600
        * rxPin - receive pin  default: 1 (P1.1) ( g2553 defaults )
        * txPin - transmit pin default: 2 (P1.2)
        */
   //static void begin(uint32_t baud = 9600, int rxPin = 1, int txPin = 2) {
   void begin(uint32_t baud, int rxPin, int txPin) {
     init_serial(1 << txPin, 1 << rxPin, MCLK_FREQ / baud, (MCLK_FREQ / baud) >> 8);
   }

   /**
        * putS - writes the string s to the serial output stream without a trailing
        *    newline character. Note: this differs from the ISO standard puts()
        *    function which appends a newline character.
        */
   //static void puts(register const char *s) {
   void putS(const char *s) {
      while(*s)
        putchar(*s++);
   }

 

Makefile:

CC=msp430-gcc
CFLAGS=-Os -Wall -g -mmcu=msp430g2553 usci_serial.c #no, that's not really the proper way to do it, but it works!

OBJS=main.o


all: $(OBJS)
$(CC) $(CFLAGS) -o main.elf $(OBJS)

%.o: %.c 
$(CC) $(CFLAGS) -c $<

clean:
rm -fr main.elf $(OBJS)

Link to post
Share on other sites
By the way, what license is the code under?

 

This code snippet is part of a bigger library I'm working on that will be a GPL compatible license if I ever release it.

 

As far as running from a more accurate clock, you might find a post on here from oPossum that details how to

steal the 12MHz clock signal from the LP FET and use it as an external clock.

 

-rick

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...