Tutorials in this section:
One of the easiest ways of getting information in and out of your microcontroller, including giving it commands, is to use the built in serial module.
There are a number of different types of serial communication methods (that is, transmitting one bit after another), but what we’re talking about here is asynchronous serial communication. That means that we agree beforehand what speed we will communicate at and then we wiggle the output at the correct rate. There are a few more things to agree on though – asynchronous serial communications use a dummy bit at the start of a byte to signify the fact that something is coming, and a bit at the end to say that the byte has finished. Why do we need this if there is always 8 bits in a byte? In asynchronous communications, the byte can start at any time and indeed, we may have started listening half way through a byte. Start and stop bits allow the other end to understand what’s happening on the line.
The challenge of course is timing everything. Transmitting is no problem, you can “bit bang” a serial output just by changing the output at the correct time. It’s more challenging to receive, since you need to sample the input at the right time (each bit).
The serial module in the PIC microcontroller takes care of the hard part of receiving bytes, simply telling you when a complete byte has arrived. It also handles the byte transmission by allowing you to give the module an entire byte and then you can sit back while the hardware takes care of sending the right bit at the right time.
The PicPack library adds to the PIC hardware by giving you a buffer for transmitting and receiving. This means you can send a bunch of bytes at once, and the circular buffer will pump out each byte one after another automatically. It’s interrupt driven, so your program will continue at full speed and the serial port will continue to transmit in the background. It also provides a buffer for receiving, so if your program doesn’t get to service the serial port in time, the bytes are not lost. So this gives you the ability to print out strings and do an incredible amount of debugging, simply by being able to display information on a serial terminal.
Building on that, the PicPack library also gives you some simple terminal functions that make it easy to build programs that respond to user commands. That gives you the ability to provide a menu over the serial terminal whereby you can ask the PIC to execute particular pieces of code.
So, onto using the serial port. Firstly, it’s a TTL serial port – meaning that you can’t whack the transmit (tx) and receive (rx) pins of the PIC straight into serial port of your PC. “TTL” serial ports, or UART ports as they are also known, use a high signal (Vcc) to signify a ‘1’ bit and a low signal to signify a ‘0’ bit. True RS-232, on the other hand uses -12V for ‘1’ and +12V for ‘0’. Modern RS-232 ports often skimp by using +5v for ‘0’ and 0v for ‘1’.
In either case, you can see immediately that we need to adapt the polarity and voltage levels for a UART serial port to talk to an RS-232 serial port. There’s a couple of easy ways to do that. Sparkfun have a 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. 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 we 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))
FOSC is the frequency of the clock running your PIC.
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 20 #define SERIAL_RX_BUFFER_SIZE 4 // Use this define if you want fine-grained control of what happens in the serial port //#define SERIAL_DEBUG_ON // Use this define if you are debugging in the IDE simulator and don't want it to hang // waiting for serial interrupts that will never come... //#define SERIAL_IDE_DEBUG // Use this define if you want to drop a character if the TX buffer is full, // rather than the default behaviour, which is to wait until the TX buffer has // a spare spot. //#define SERIAL_DISCARD_ON_TX_FULL_DURING_INT // - - - - - - - - - - - - - - - // General platform definitions #define PLATFORM_TYPE breadboard #define PLATFORM_CLOCK 12000000
The pic_serial defines are pretty obvious – you set the 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), the library will wait until there’s a spot in the buffer if the buffer is full. Nothing will be lost, but everything will slow down while it waits for the serial port to flush characters from the buffer. The alternative here is to increase your buffer size, or uncomment the line:
#define SERIAL_DISCARD_ON_TX_FULL_DURING_INT
…in which case if the buffer is full, and you’re trying to transmit while in an interrupt service routine, the byte will be discarded. This can be useful if you don’t want to slow down the proceedings to wait for the serial port, but this will be at the expense of lost characters.
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_analog_inputs_off(); serial_setup(SPBRG_9600); term_init(); turn_peripheral_ints_on(); turn_global_ints_on(); }
In the configure_system routine, we set things up. Generally, when you start projects, it can be handy to turn off interrupts at the start, just in case, along with turning analog inputs off (that is, making sure all pins are acting as digital I/O). 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. Just pop in the baud rate, and if you’ve set your PLATFORM_CLOCK rate correctly, you’re all set to go without requiring you to do any 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.
Now we’re ready to start getting some serial port action.
serial_print_str("\n\nPIC terminal\n");
That’s 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.
This demo also uses the pic_term library, which makes it easier to do serial debugging on the PIC. All you need is a:
term_init();
in your configure_system() routine, some parameters in config.h:
// - - - - - - - - - - - - - - - - - - - - // pic_term defines // - - - - - - - - - - - - - - - - - - - - #define TERM_BUFFER_SIZE 10 // Reset (go to bootloader) if magic character received #define TERM_ALLOW_BOOSTBLOADER // Echo typing so user can see what they're doing #define TERM_ECHO_INPUT
And in your main program loop, a routine that allows you to respond to the user while in the main program, as opposed to an interrupt:
for(;;) { term_process(); }
The routine term_entry_callback will be called when the user presses enter. It will be passed the string of characters the user has entered (if any). From there you can choose how you respond to the user. You can print out variable values, execute commands and so on. In this example you can set a variable to a value by typing:
s7e
to set “my_var” to 7e”, and then you can print the value you have set it to by typing
a
to get the result in decimal or
x
to get the result in hex.
So, compile the program, bootload it into your pic and start trying out some of those commands.