beginner-friendly electronics tutorials and projects

Discover the joy of understanding electronics!

Use RS232 to connect your microcontroller to a computer or another controller

September 20, 2019 tutorial

RS232? That sounds so 1990! Maybe so, but in this tutorial I want to show you why I still think RS232 is a good protocol to connect your PIC circuit to a computer, and, perhaps more importantly, to another PIC controller. Many PIC controllers have a built-in USART module that allows you to utilize the full power of the asynchronous data transmission and reception offered by protocols such as RS232. Let's do it!

Before we jump in, let's briefly address the elephant in the room: why not just use USB? It is true, USB might be a better choice for some applications. I am also not an expert on it. However, I am still convinced that RS232 is a viable option in many cases, especially for beginners.

But what do we even need RS232 or USB for? Here are some scenarios:

  • Your microcontroller circuit exchanges with a computer or other device.
  • Your microcontroller circuit communicates with another microcontroller circuit.

RS232 stands for Recommended Standard 232 and was introduced in 1960. It is a communication standard for serial transmission of data (think Morse code, more below). USB, on the other hand, stands for Universal Serial Bus and was introduced in 1996, and is the de facto industry standard for data transmission protocols.

Here is a comparison of RS232 and USB listing their advantages and disadvantages (see also a nice article on on how older communication standards still compete with USB):

RS232: simplicity over performance

  • easy to implement in the software
  • perfect for transferring numerical values or characters
  • only one piece of external hardware required
  • modern computers rarely come with a dedicated RS232 port
  • need USB adapter which can get broken/lost
  • RS232 cables are a bit clunky
  • can be a bit slow for large amounts of data

USB: functionality over simplicity

  • USB ports are everywhere
  • no adapters required
  • USB cables are easy to find
  • can be very fast
  • quite hard to set up for a beginner
  • needs external libraries
  • quite inefficient for transmitting numbers or characters
  • externally required hardware can be quite involved

RS232 basics: BAUD rate and terminal programs

So let's say you are convinced and would like to try to use RS232 in your next project to transmit or receive data. Great! What do you need to know about RS232? As it turns out, not very much.

First, RS232 only uses three wires to communicate. They are called TX (for ”transmit“), RX (for “receive”), and GND (which provides the ground potential reference). The RX and TX wires can carry positive voltage (which stands for a logical 0) and negative voltage (which stands for a logical 1), more on that below.

Second, we need to talk a bit about transmission speed. In the case of RS232, the transmission speed is commonly referred to as the BAUD rate. It counts how many transitions from 0 to 1 and vice versa occur per second, so it can be interpreted as a frequency of some sort. In practice, people tend to just say “1000 Baud” (or “1000 Bd” for short), or something to that effect, and use the unit Baud. There is a simple thing to remember:

  • The faster the Baud rate, the more data you can transmit or receive.
  • The faster the Baud rate, the more errors happen during transmission or reception.

You see: maxing out the Baud rate is not always the best idea. For our concrete case, a Baud rate of a few thousand is typically enough.

In this article I won't go into the details of the RS232 protocol. There are several details including notions of start and stop bits as well as parity bits, and if you want to learn about it I refer you to an excellent article on the RS232 protocol by CircuitDigest. In this tutorial I will rather focus on a practical implementation.

Okay, there is one thing missing. Say you have a circuit that receives data via RS232. How can you send that data to the circuit? How could you debug it? Or, if it is the other way around and you have a circuit that sends data, how can you make sure that the data is sent correctly?

The answer is simple: you need a so-called terminal program. Those programs are simple pieces of software that monitor the RS232 connection of your computer. If your computer does no longer come with a dedicated RS232 connector you can use a USB-RS232 converter as shown here:

These adapters emulate a virtual RS232 serial port via USB and are usually plug-and-play. After plugging those into your USB port, your computer will think that it has a regular serial port. Magic! In included a link to such an adapter in the resources box.

And now it is time to start the terminal program. If you do a quick Google search for “RS232 terminal program” you will find many results, all of them typically work quite well. Here is a list of possible choices:

In what follows, I will focus on using HTerm (which comes from a German developer but whose GUI is entirely in English, so don't be afraid). You can download the software for both Windows and Linux in the resources box. Here is how it looks like:

In the context of microcontroller communication, I find the following settings useful:

  • Baud rate 1200. This is quite slow but fast enough for our purposes. It corresponds to around 120 bytes per second which I consider plenty.
  • 8 data bits. I chose this option because it is helpful for transmitting characters in the ASCII format. In this format, each number from 0 to 255 corresponds to a certain symbol. For example, the letter “A” is 65, and the letter “a” is 97.
  • 1 stop bit. I won't go into detail here, but this is a fairly standard setting.
  • Parity: None. Parity can be used to make sure the transmitted data does not contain any errors. We don't need this extra level of complication here because our Baud rate is very low and therefore it is quite unlikely to get transmission errors.
  • These settings can be abbreviated by the shorthand notation b:1200 d:8 s:1 p:None.

And that is all we need! Now we can click on Connect and establish a connection to the RS232 port (which is called “COM1” in my case and may have a different name in yours).

Then you can simply type a sequence of letters (and this works well because we selected 8 data bits):

Then click on ASend to send the text via the RS232 port:

A window will pop up that allows for repetition and delay settings. We don't need to change any of that if we only want to send the text once, so click on Start.

Congrats! Your text has just been sent! But now what? Let us now finally talk about how to configure your PIC controller so that it can listen to the RS232 data that we just sent!

Configuring your PIC's USART module

As we said above: now it's time to choose a PIC microcontroller and configure it so that it can transmit and receive RS232. For the sake of this tutorial we will focus on the PIC16F627A microcontroller whose datasheet you can find in the resources box, but all of these steps have their counterpart with other PIC microcontrollers.

All microcontrollers you ask? No, that's technically not true. Only microcontrollers with a so-called USART module can make use of RS232. USART stands for Universal Synchronous-Asynchronous Receiver Transmitter. Yeah, that's a mouthful.

Well, we understand the receiver/transmitter part, and the “universal” means that it can also be used with slightly different protocols from RS232. The “Synchronous” and “Asynchronous” refers to the way the data is sent and received. We will focus here on the asynchronous mode. This is convenient because we can just send some data to the USART module and it will send it automatically (like a magic black box), and we can receive data similarly. I hope it will become clear below!

Overall, there are the following registers associated with the USART module of the PIC16F627A:

  • BRGH: 1 for high Baud rates, 0 for lower Baud rates
  • SPBRG : set the Baud rate
  • SYNC: 1 for synchronous, and 0 for asynchronous mode
  • SPEN: enables the serial port
  • RCIE: triggers and interrupt when something is received
  • CREN: enable receiver mode
  • RCIF: interrupt flag for reception of data
  • RCREG: contains the received data
  • TXREG: contains the data you want to send
  • TXEN: send data

As you can see, it is a bunch of stuff. Instead of going into a lot of detail on all the possible configurations let us focus on a concrete application. We want to do the following:

  1. Initialize the USART in asynchronous mode with a Baud rate of, say, 1200.
  2. Create a software routine that checks if some data has been received.
  3. Create a software routine that can send some data.

Let's start simple and set up the USART module. Here is the code:

BRGH = 0;
SPBRG = 51;
SYNC = 0;
SPEN = 1;

In the first line we tell the controller that our Baud rate is rather low (1200 is very low indeed). Then, in line 2, we set the Baud rate to 1200. The value of SPBRG is related to the Baud rate as follows:

Given a target Baud rate of, say, 1200, we can recase this formula and solve for SPBRG:

If we run the PIC with its internal oscillator, the oscillator frequency fosc is equal to 4MHz. If you use a crystal, the value of fosc will be given by the oscillation frequency of the crystal. At any rate, inserting 4000000 (which is the oscillation frequency in Hertz) into the above formula gives a value of 51 for SPBRG. The Baud rate is not exactly 1200 this way, but it is close enough since the tolerance of the RS232 protocol is 5%. In line 3 we set the USART module to the asynchronous mode, and in line 4 we finally turn the USART module ON.

Okay, now what happens if we want to react to data that was sent to us? This is how we can do it:

RCIE = 1;
GIE = 1;
PEIE = 1;
CREN = 1;

In line 5 we tell the controller to trigger an interrupt whenever a character is received. If you want to learn more about interrupts check out the article on interrupts here. In lines 6 and 7 we turn on the global interrupts and peripheral interrupts. This is required, because if these bits are not set then the PIC will simply ignore line 5. Last, in line 8, we turn the receiver module ON.

And then we also need to specify the interrupt service routine (isr) that is called whenever a character is received. Here it is:

void __interrupt () isr (void) {

  // received some data?
  if (RCIF) {

    // retrieve value
    value = RCREG;

    // react to possible errors (we skip that here)
    CREN = 0;
    CREN = 1;
    OERR = 0;
    FERR = 0;



In line 4 we check if we have indeed received data via RS232. This is necessary because the interrupt service routine is called for any interrupt, and if our software becomes more complicated and uses multiple interrupts we need to make sure that the correct interrupt flag is set. In this case, we need to check if data has been received, which is exactly what line 4 accomplishes.

In line 7 we can retrieve the value that was sent, and in lines 10–13 we reset all errors. This is potentially bad programming style, since a reasonable software should check if there are any reception errors. Again, since we are using very low Baud rates, I never encountered any problems. If you use higher Baud rates, you might want to refer to the datasheet to check for specific errors. And that's it!

And last, but not least: How can we send data? It is very simple! Because we sent the mode to asynchronous, all we have to do is write the value to the TXREG register:

TXREG = value;
TXEN = 1;

And then, in line 2, we just set the transmission enable bit to send the data.

And that's it! Overall I find it quite remarkable how simple it is to send and receive data. Because the PIC16F627A contains an USART module, it all works like a black box without us having to implement the RS232 protocol by hand, which is quite convenient. At the same time I find this approach low-level enough that we still understand, in principle, what is going on. If we were to use USB, the level of abstraction would be a lot higher.

Now we can think about the circuitry a bit more. How to we connect a RS232 cable to a PIC controller?

Talk RS232 to me! The MAX232 level shifter

We learned at the very beginning of this article that in the RS232 protocol a logical HIGH is represented by a negative voltage, and a logical LOW is represented by a positive voltage. It is clear that this is very different from the logic levels used by PIC controllers which interpret a logical HIGH as +5V and a logical LOW as 0V.

To translate between these two “interpretations” we need a so-called level-shifter. A very common choice is the MAX232 integrated circuit:

It is very simple to connect the MAX232 to external circuitry. All it needs are four external capacitors of 1μF:

How does it work? It uses the capacitors as a so-called “charge pump:” The capacitors C1 and C2 get charged and GND is applied between them. This means that the MAX232 now has -5V at its disposal that it can use for transmitting data. For receiving data it is easier to shift it from negative values to positive ones since it only requires some resistors.

On the left you can connect microcontroller circuits, and on the right you can connect the RS232 connectors. Also, as you have probably seen already, the MAX232 offers two TX and two RX lines, respectively. In our example we only need one, but in principle two PICs could share one MAX232 to connect to an RS232 connection.

The symbol on the left just denotes the power supply for the MAX232. If you want to learn more about reading schematics check out our article here.

A simple example: dim your LED via RS232!

Now we have all that we need to build a very simple example circuit: dimming an LED (see our article on pulse-width modulation) via RS232! The functionality is as follows:

  • Sending 0 sets the brightness to zero, and sending 255 maximizes the brightness. Values in between control the brightness in 255 steps.
  • As soon as the PIC16F627A receives a number between 0 and 255, it adjusts the LED's brightness and sends the value back via RS232. If we connect a terminal software we can use it to verify that the circuit is indeed working properly.

Here is the schematic for this simple project:

You can see the PIC16F627A microcontroller connected to the MAX232 level shifter IC. Pins RB1 and RB2 work as RX and TX, respectively. X1 is a SUB-D connector that is used to connect an RS232 cable. And, last but not least, the LED is connected to pin RB3 (which is the PIC16F627A's dedicated PWM pin). And that's it :)

The software evolves around what we already talked about. I won't list it here, but you can find it in the appendix. You can also download the source code as well as the readily compiled .hex file in the resources box.

Of course this little application is not necessarily the most useful implementation of RS232, but I decided to include it because it demonstrates that there is not a lot of secret knowledge to using the USART module of a PIC microcontroller via the RS232 protocol.

Beyond RS232: RS422 for larger distances

Before wrapping up I wanted to mention one last thing: we haven't really talked about how long the RS232 cable can be before data is being lost. The answer is: not very! Already a few meters can lead to substantial data loss. Why is that? RS232, as a protocol, is not tailored for large distances, but rather for tabletop connections between several devices.

The reason for this loss of signal integrity is simple: RS232 only uses three wires. Two signal wires and one ground wire. This means that after a surprisingly short distance, the voltages can drop simply due to the internal resistance of the wires, which consecutively leads to undefined signal levels and data loss.

If you want to use the USART module to send and receive data over potentially a hundred or a thousand (!) meters worth of cable, RS232 is not a good idea. Rather, you should use a different protocol, for example the RS422 protocol. This protocol uses four wires instead of three: both the RX and the TX signal are defined as differential signals which significantly improves the signal robustness.

I don't want to go into a ton of detail about RS422 in this already somewhat lengthy article, but let me mention one important thing: you can still use the USART module! Okay, but then what's the difference? You just need a different level shifter IC. In this case, this level shifter IC is called MAX488. Just as before it has two

Here you can see the main idea of the MAX488 level shifter IC:

Under resources you can download the datasheet for the MAX488. You can also see that both pairs of cables are intertwined for improved signal integrity. Also, there is a resistor called Rt at the receiving channel, a typical value is around 120Ω, to match the impedance. Tuning this value depends on the cable that you use for the connection as well as the cable length.

Final thoughts

Oof! That was a lot of information, I know. I still hope that I could convince you that using the RS232 interface can be very rewarding. To put it in a nutshell:

  • Using RS232 is great because you can understand every single detail about it!

Even though we make use of the USART module, which is a bit of a black box, we can still understand all the steps involved. For USB this is a lot more complicated. There is nothing wrong with USB, of course, but it is a bit overkill for a beginner who is just learning how to use microcontrollers.

I am sure there are some details in this article that deserve some more explanation, and if you made it this far and found a piece that dows not make sense: please get in touch and let me know and I will do my best to improve this article! Thanks for reading :)

Appendix: the complete source code

 * File:   main.c
 * Author: boos
 * Created on September 20, 2019, 10:04 PM

#pragma config FOSC = XT        // Oscillator Selection bits (XT oscillator: Crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = ON       // Brown-out Detect Enable bit (BOD enabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#include <xc.h>

// global variable that stores the value
unsigned char value = 0;

void main (void) {
	// set tristate registers
    // (B1 = RX, B2 = TX, B3 = LED)
	TRISB1 = 1;
	TRISB2 = 0;
	TRISB3 = 0;
	// configure PWM
    // (see article )
	PR2 = 0xff;
	T2CON = 0b100;
	CCP1CON = 0b1100;
	CCPR1L = value;

	// configure USART
	// slow Baud rate
	BRGH = 0;

	// set Baud rate to 1200
	// f_osc = 4MHz, Baud rate = f_osc / 64 / (SPBRG + 1)
	SPBRG = 51;

	// set to asynchronous
	SYNC = 0;

	// enable serial port
	SPEN = 1;

	// enable interrupts for receiving data
	RCIE = 1;

	// enable receiving module
	CREN = 1;

	// enable global and peripheral interrupts
	GIE = 1;
	PEIE = 1;

	// main loop
	while (1) {


// interrupt service routine
void __interrupt () isr (void) {

  // received some data?
  if (RCIF) {

    // retrieve value
    value = RCREG;

    // react to possible errors (we skip that here)
    CREN = 0;
    CREN = 1;
    OERR = 0;
    FERR = 0;



About FriendlyWire

Beginner-friendly electronics tutorials and projects. Discover the joy of electronics! Keep reading.

Let's build a community

How did you get interested in electronics? What do you want to learn? Connect and share your story!

Tag Cloud

  • RS232
  • serial port
  • RS422
  • MAX232
  • level shifter
  • data transmission
  • BAUD rate
  • tutorial