12 April 2008

8. Serial ports

If you had to generalise about what’s the best feature and the worst feature of pic microcontrollers, I’d have to say the best thing is that any decent model of chip has a serial port (UART) built into the hardware. The worst thing is that the PWM modules suck (normally, there’s just one. And even if they have an “enhanced” PWM module, it’s design for driving stepper motor H-bridges, meaning that the “four” outputs are really just two with the second one driven the opposite way).

Anyway, the serial port rocks. It means you can print things out, find out what your code is doing, debugging the old fashioned “printf” way. You can even issue commands to the code and have it respond, very easily. It really does make writing programs for the pic a pleasure. Sure, you can get into the hardware debugging stuff, and I’ve got an ICD2 on it’s way to have a play with – I’ll let you know what it’s like – which allows you to step through code, put in break points, monitor variables etc. Still, with ICD you lose two pins, a chunk of memory and stack space. I’m sure for some nasty hardware problems it could be very handy, in fact, when I was developing the serial library it could have been very useful. Still, now that it’s done, it makes debugging very easy. The routines are well tested and very solid. They ran “out of the box” when I moved on to 18f chips as well. And best of all, you can print something out, it goes into the fifo buffer and gets shunted out the serial port at the appropriate rate. Meanwhile, your program continues on executing and full speed.

So, onto using the serial port. Firstly, it’s a TTL serial port – meaning that you can’t whack the tx and rx pins of the pic straight into serial port of your PC. You need to adapt the polarity and voltage levels. There’s a couple of easy ways to do that. I use the sparkful serial module – I’ve used the RS232 Shifter SMD module, but the other rs232 modules will work just as well. These are great for breadboards. Alternatively, you can use the Olimex boards that have a proper MAX232 chip on board with all the associated capacitors. These are used to bump up the serial port voltages to +/- 12v, which is the standard. Most serial ports these days will work perfectly well at 5v however. On the Olimex board you’ll need to wire the serial port output / input to the tx / rx pins on the pic. Don’t worry about the RTS/DTR pin, it’s not necessary.

You should have no problems with any of these serial port connections with USB serial port adapters. So on to the software.

The serial port on pics, like most microcontrollers, is driven by the clock that the pic is running at. As such, there’s not really the concept of “baud rate” or “bits per second”, or bps, at least, at the hardware level. Everything is divided down from the clock rate. The 16f range of pics have an 8 bit clock divider, and a “fast” and “not so fast” setting. Have a look at the datasheet for your pic – the section is titled “Addressable universal synchronous asynchronous receiver transmitter”. And you wonder why I stick to calling it “serial port”. We don’t care about synchronous serial ports, they require a clock, and serial ports on PCs are asynchronous anyway. Notice that there’s a setting for High speed and Low speed, based on the BRGH bit of the TXSTA register. Hunt down the baud rate formula. You’ll see that you can calculate the baud rate based on the BRGH setting, the SPBRG divider and the clock frequency.

When BRGH = 0 (Low Speed) the baud rate = FOSC/(64(SPBRG + 1))

When BRGH = 1 (High Speed) the baud rate = FOSC/(16(SPBRG + 1))

Thankfully the picpack serial port routines take away a lot of the pain. Who wants to calculate these values anyway? For all the popular serial port values and clock frequencies, the work has been done for you.

Load up the serial_demo project and have a look at the config.h file. You’ll start to see that most things you configure in a picpack project are done through the config.h file.

// - - - - - - - - - - - - - - - - - - - -
// pic_serial defines
// - - - - - - - - - - - - - - - - - - - -


#define SERIAL_TX_BUFFER_SIZE 16
#define SERIAL_RX_BUFFER_SIZE 4
//#define SERIAL_DEBUG_ON

// - - - - - - - - - - - - - - -
// General platform definitions
#define PLATFORM_TYPE breadboard
#define PLATFORM_CLOCK 12000000

The pic_serial defines are pretty obvious – you set the fifo buffer sizes for transmitted and received characters with the SERIAL_TX_BUFFER_SIZE and the SERIAL_RX_BUFFER_SIZE. Since the picpack serial routines are completely interrupt driven, we need a buffer to store things on the way in or way out, until your routines get around the handling them. Remember you need a TX buffer big enough that everything can sit there until it gets transmitted – which only occurs while interrupts are running. If you print out lots of stuff while in an interrupt service routine (and this means interrupts are off), it will pile up here until interrupts get turned back on and the characters can be pumped out. All this means is that you need a big enough buffer. Otherwise the pic will appear to hang, when really what it’s doing is waiting for the buffer to empty, but it’s never going to since it’s only going to get serviced when (you guessed it) interrupts are running.

It’s nothing you need to worry about until several projects down the track you’ve defined a really small buffer and are wondering why things stop working! For the moment, and generally, it won’t be a problem.

Set your platform type and clock speed in cycles per second – the example here is 12Mhz, but if you’re using a 16f88 with internal clock, for example, you’ll need to set this to 8000000, of course.

Have a look at serial_demo.c

// configure_system
//
// Get the pic ready for everything we want to do

void configure_system() {

kill_interrupts(); // turn off interrupts just in case
turn_analog_inputs_off(); // kill those pesky analogue inputs
serial_setup(BRGH_HIGH_SPEED, SPBRG_9600);
turn_peripheral_ints_on();
turn_global_ints_on();
}

In the configure_system routine, we set things up. I always turn off interrupts at the start, just in case, along with turning analog inputs off. In this demo, it’s not strictly necessary, but always good practice unless you need them. See the serial_setup routine? It makes it easy. Always use the BRGH_HIGH_SPEED setting if you’re using the pic_serial defines for the baud rate. Then just pop in the baud rate, and if you’ve set your PLATFORM_CLOCK rate correctly, you’re all set to go with no calculations!

After that, we turn on peripheral interrupts (which includes the serial port module), and global interrupts (which is the overarching control on whether interrupts are on or off).

Our interrupt routine is as easy as this:

void interrupt() {

serial_handle_tx_isr();
serial_handle_rx_isr();

}

Picpack does all the hard work for you.

The process_rx routine is where we respond to typed commands – in this case, we can type something in and press enter to get the pic to respond. Very simple commands are used here, but you can see how you could query anything, at any time, just by typing a request.

Now we’re ready to start getting some serial port action.

serial_print_str("\n\nPIC terminal\n");

Is as easy as it gets for printing out strings. Use serial_print_int for 16 bit unsigned integers, and serial_print_int_hex for printing 8 bit hex numbers.

So, compile the program, bootload it into your pic and start trying out some of those commands.

No comments: