RS232-driven LCD display

01 December 2017
Although not quite a clone, this project is basically a reimplementation of an LCD display & input module I used while working in the UK back in 2010-2012, which in turn has been a long-term ambition I had already completed in parts. Since I had all the major circuit components and test equipment in stock, I decided to finally bring this vanity project of five years to a final close. It was also an opportunity to properly try out the PIC16F88.

The LCD display is controlled via commands sent over an RS232 connection operating at RS232 voltage levels, so this module is basically a display that can be driven from a PC. Button presses are signalled back over the same RS232 connection, but for me this is a relatively minor thing. The only outward functional difference between this and the original module I used back in 2010-2012 is the number of buttons and the size of the display, although I have no idea how the insides compare.

Choice of microcontroller

My previous LCD projects used the PIC16F630, but I wanted to try porting the LCD control code to a different chip, and in any case the PIC16F630 has no hardware support for any serial communications — implementing RS232 in firmware was not something I would do. Although I did have some vague ideas about dual-purposing this project as an I2C-driven LCD display as well as an RS232-driven one, my choice of the PIC16F88 was ultimately down to being the most suitable UART-equipped microcontroller I had in stock — most of the others had insufficient (or far too many) pins or were not supported by gpsim.

At first the different PIC microcontroller seem to be vastly different, which they often are in terms of initial setup, but functionality of modules such as RS232 support is actually much the same — often down to even the bit-structure of control registers. Using a new PIC chip still requires a bit of experimentation, but after trying 3 or 4 different ones you start to notice where the differences tend to be. The PIC16F88 chip was originally my candidate for the single chip I would use for all future project, but its lack of hardware support for I2C master — a non-issue for this particular project — scuppered that intention.


The PIC microcontrollers tend to concentrate pins that are part of the same I/O port towards the ends of the chip rather than along the same side, which in this case worked to my advantage due to the LCD header's location along the side. I used the same prototyping board (Farnell 2768280) I used for my previous LCD timer, which is designed for use with integrated circuits, and the nice thing about it is not having to cut tracks — working out where to drill out tracks and then doing so was particularly time-consumping with previous circuits. It still has some irritations, but on the whole I think it a lot better than my previous strip-board.

At the time I designed the circuit I was undecided whether to include pull-down resisitors for the buttons on the circuit itself — partly because I did not know what type of buttons I would eventually get in stock, and partly because keeping the pull-downs off-circuit means the pins could potentially be used for other things such as I2C. For simplicity I left them out of the original design, because I was undecided whether the buttons would be board-mounted or external. I did the board layout in Fritzing, which is shown below (left) alongside a picture of the built hardware (right) for comparison.


Some minor repositioning is evident, but by and large component connections ended up either in the hole they were intended to go into, or one neighbouring the intended hole. The RS232 driver and the things around it were shifted right a bit, the connections around the voltage regulator were rearranged, and some resisitors were positioned a bit differently, but as a total sum of  “significant” changes it does not amount to much. The only post-design addition to the circuit was connecting up the 6th pin of the flashing header, along with a ~20KΩ pull-down resisitor, to Pin 9 of the microcontroller. This notionally enables low-power programming mode, but wired up this way it can also be used for connecting an external button for testing purposes. In the end I decided to use the flashing header to connect all the buttons, because I felt that there was little to gain from having more than three buttons.

Faults & rework

I have generally had relatively good luck with my more recent circuit boards, having not had to do a scrap-and-restart since my LED row driver board, but that does not mean the process of assembling them went entirely smoothly. I have been a lot more blasé compared to say what went into my DEM16217 LCD display, which includes more adventurous reworking. This section covers some of the pit-falls I had with this project, and how I mostly got around them.

Miswired Vpp/MCLR pin

I actually noticed this in a visual sanity check long before I powered up the circuit, but the Vpp connection from the chip flashing header was routed to Pin 3 rather than Pin 4— the tightness of the yellow wire around the chip recepticle and the angle of the MCLR pull-up resisitor sort of give away the rework. Probably just as well I caught this one early on, as Vpp voltages are a lot higher than what I think the other pins can tolerate, but it shows how easy it is for a little mistake to slip in.

In-circuit flashing problems

I don't really know the details of why, but adding in the RS232 Driver interfered with in-circuit flashing, and simply disconnecting the driver from the Vcc power rail did not solve this problem. My guess is this is to do with capacitance (or much less likley, inductance) on the power rails that is not properly accounted for, but doing so at the moment is beyond my current knowledge of electronic circuits. If I was to redesign the circuit tomorrow, I would probably use the brute-force approch of adding in jumpers to isolate different parts of the circuit.

Garbage RS232 output

When I first connected up the circuit to an external RS232 adapter, I basically got garbage output, and spent a while trying things such as different BAUD rates in PuTTY, and even doing a partial rewiring on solderless breadboard. I eventually worked out it was a problem with the RS232 Driver, and in turn found out that the ground pin on the RS232 header (bottom-right, next to the capacitors) was not connected. It was due to a poor solder joint, shown below under magnification:

To the best of my knowledge the problem was the wire not getting hot enough, so it ended up surrounded by small insulating layer of flux, rather than the solder bonding with it to create an electrical connection. This seems a particular problem on prototyping board where the copper tracks are just on the surface, in contrast to PCBs where the hole itself is lined with metal all the way through. Poor connections seems to be a more common problems than short-circuits, although both problems can be found by checking pins with a multi-meter in resistance mode.

Inputs not working

This one really caught me as I only tested it at a very late stage — only RB7 worked properly when I wired in a load of push-buttons with pull-down resistors. I put this down to damage due to the circuit having a rough time in transit, as although I don't de-bounce the switch inputs, they ought to settle on a value eventually. I decided not to investigate for now, as not having multiple working buttons is not a major issue, particularly as I never got round to finding a suitable case to put the circuit & display into.


This circuit can be represented entirely within gpsim, as shown in the image below, so development of the firmware itself is independent of the hardware — this suits me fine as it means I can write about the hardware & firmware issues in a compartmentalised way. My interests are much more towards circuitry than firmware, but this project brings together information on various microcontroller functionalities, which previously were split between different articles using different chips.

This section will concentrate on the setup of the various PIC16F88 modules, as longer-term these are of interest rather than the overall purpose of the firmware. To enable this I split up the firmware into functions in a way that would probably not be done with a production product.

Chip clock rate

As a rule-of-thumb, if serial communications are required, then PIC microcontrollers have to be run at or close to their maximum clock frequency. The main advantage of running at lower clock speeds is supposedly lower power consumption, but I don't know any figures to quantify the savings, and in any case power efficency is not normally a major concern of mine. I opted for a Fosc of 2MHz as this seemed to be the lowest clock speed that could also sustain 9,600 BAUD RS232 within reasonable error bounds. This clock speed is selected using IRCF=0b101 via the OSCCON register, the details of which are on Page 40 of the data-sheet, and performed by the function below:

void clockSetup(void) { OSCCON = 0b01011000; while( ! (OSCCON & 0b100) ); }

I am not entirely sure how clock switching & stabalisation works, but the checking of the IOFS flag within the OSCCON register is supposed to be done before any timing-critical code, which is something I prefer to get done & dusted as part of start-up code. To my knowledge the code above ought to make sure everything is in the correct state.


As with my count-down timer, I used the Timer0 functionality, which is basically the same on both the PIC16F630 and the PIC16F88. Although originally intended to be a 1.0-1.5ms delay, testing under simulation revealed it to actually be 1.6ms — I stuck with 1.6ms as my experience so far is that hardware runs faster than the nominal speed, so I saw no point in adjusting the delay.

void timerSetup(void) { OPTION_REG = 0b00000111; }

void lcdDelayMS(void) { // 1.6ms INTCON &= ~0b100000; TMR0=253; INTCON &= ~0b100; INTCON |= 0b100000; while(!(INTCON & 0b100)); INTCON &= ~0b100000; }

For shorter delays I simply reused an assembly-language delay loop, adjusted for the different clock rate, for reasons I covered in the article linked to. Simulated in gpsim, using 3 came out at 38ms for the whole function and 4 came out at 46ms. I was going to go with 6 (notionally 62ms) to provide some slack for the hardware, but in the end this was not required, as the HD44780-based devices seem to be forgiving in practice to the delays needed for display updates.

unsigned char iTic; void lcdDelay(void) { __asm MOVLW 4 MOVWF _iTic tic: NOP DECFSZ _iTic,1 GOTO tic __endasm; }

Pin setup

This function sets whether the various pins are input or output — the mnemonic TRIS refers to the tri-state mode the pins use, as input pins typically use high-impedence. The pins are also all set to be digital I/O rather than using any analogue functionality, and interrupts are enabled for pin state changes on Port B.

void pinSetup(void) { TRISA = 0b00100000; TRISB = 0b11001111; ANSEL=0; INTCON &= ~0b1; INTCON |= 0b1000; }

The input/output direction of RB2 & RB5 are actually overridden when the USART is enabled, but for consistency I prefer to set the direction of those pins to what they are expected to be. For the PIC16F88 interrupt-on-change is only for the PORTB pins on the higher-numbered side of the chip, and the set of pins included in interrupt-on-change is fixed. This is a bit of a let-down given that the PIC16F630 via the IOCA register has full-flexibility of which pins can drive an interrupt, and it means pin state change cannot use the following for the pins I am interested in:

if( INTCON & 0b1 ) { INTCON &= ~0b1; iButtons = PORTB; // Other processing }

Instead of using code like the following to check for pin changes, I resorted to polling the whole of PORTB constantly, and reacting if the polled state differed from the polled state.


One thing I did not quite get the hang of last time I looked at RS232 BAUD rates was the purpose of the BRGH flag, which dictates whether the clock speed is divided by 16 or 64 within the USART module. Here I will set the flag to get division-by-16, which allows 9,600 BAUD to be supported at a clock speed of 2MHz. For a 2MHz clock, the SPBRG value of 12, which is obtained from Table 11-6 (Page 101) of the data-sheet.

void rs232Setup(void) { TXSTA = 0b00100100; RCSTA = 0b10010000; SPBRG = 12; }

It has to be pointed out that the data-sheet for the PIC12F88 gives a selection of pre-calculated SPBRG values, whereas that for the PIC12F1822 used in the I2C-RS232 bridge only gave a single worked-out value — 9,600 BAUD at 16MHz clock speed. This is partly responsible for my incorrect belief that PIC chips could not support higher BAUD rates.

LCD functions

Access to the LCD display is done using functions from my LCD timer, modified to take account of the control lines of the LCD display being wired to different microcontroller pins.

void lcdBangNibble(unsigned char bits) { PORTA=0b10000000 | bits; lcdDelayMS(); PORTA=0b00000000 | bits; lcdDelayMS(); }

void lcdWrite4(unsigned char letter) { unsigned char lo = letter & 0b1111; unsigned char hi = letter >> 4; PORTA=0b11000000 | hi; lcdDelay(); PORTA=0b01000000 | hi; lcdDelay(); PORTA=0b11000000 | lo; lcdDelay(); PORTA=0b01000000 | lo; lcdDelay(); }

Using the above functions, setting up the LCD display can be setup using the same procedure I used in previous articles — the details of the latter are beyond the scope of this article.

Watchdog reset

Somewhat redundant in this firmware as the hardware watchdog is disabled, but it nevertheless shows the sort of place it would be used if enabled.

void resetWatch(void) { __asm CLRWDT __endasm; }

Firmware entrypoint

For (almost) completeness the main function of the firmware is shown below, but this is purely to show the context of the functions detailed above, with the serial interface protocol being chosen for expedience rather than sophistication — all I will say about the detail is that it is significantly different from what I remember of my old company's protocol.

#define NO_BIT_DEFINES #include "pic16f88.h" __code short __at (_CONFIG1) cfg0 = _FOSC_INTOSCIO & _WDT_OFF & _MCLR_OFF & _PWRTE_OFF & _CP_OFF & _CPD_OFF; void main(void) { unsigned char iByte; unsigned char iButtons; unsigned char iPresses; unsigned char iPrevButtons = 0; clockSetup(); pinSetup(); rs232Setup(); timerSetup(); lcdSetup(); iPrevButtons = PORTB & 0b11001000; while(1) { iButtons = PORTB & 0b11001000; if( iButtons != iPrevButtons) { iPresses = 0; iPresses |= (iButtons & 8 ) ? 0b001 : 0; iPresses |= (iButtons & 64) ? 0b010 : 0; iPresses |= (iButtons & 128) ? 0b100 : 0; rs232Write('\r'); rs232Write('B'); rs232Write(':'); rs232Write( '0' + iPresses); iPrevButtons = iButtons; } if( PIR1 & 0b100000 ) { iByte = RCREG; PIR1 &= ~0b100000; if( iByte == '\r' ) { lcdBangNibble(0b0000); lcdBangNibble(0b0010); continue; } if( iByte == '$' ) { lcdBangNibble(0b0000); lcdBangNibble(0b0001); continue; } lcdWrite4(iByte); } resetWatch(); } }

Although my memories are are little hazy, my overall recollection is that this module is substantially more responsive than the one I used in my previous company — I suspect this is down to actually knowing what the underlying latencies are actually caused by, and making a protocol that exposes more of how the LCD panel is operated electronically.


It had many faults but the core part of the circuit — displaying text sent over an RS232 link — worked fine. This project was really about process than outcome, particularly as it was a vanity proof-of-concept excercise that the constituent parts I already had tried out. Below are some remarks of the experience.
Electronics experience
While it is nice that I am at a stage where I have mastered LCD displays enough to freely rewire them, and in the process put to rest a long-term ambition, the problems with in-circuit flashing highlight a limit in my understanding of electronic circuits. This currently does not bother me, but it will do if I start to build surface-mount circuits with microcontrollers that cannot be pulled out for reprogramming.
PIC16F88 disappointments
I have very mixed feeling about the PIC16F88 microcontroller. I first picked it as a possible use-this-for-all-projects chip, but that got derailed by not having hardware support for I2C Master. Then quite late on in this project I noticed its inflexibility with pin interrupt-on-change, although the latter was easily worked around. Other PIC chips are substantially more complex, but they did not have any comparable pit-falls.
Although not without its faults, the breadboard used in this project required less planning than the 10cm-by-10cm stripboard I used a lot in the past, and allowed a higher density of components with less effort. A common theme is finding out late in the day components & consumables that would have made earlier projects less of a headache.
This particular project was more about filling in time and past ambitions, whereas previous ones were more investigative, and I am starting to wonder what what my longer-term goal is with doing all these electronics projects. Maybe like my C# projects in 2013 and 2014, the real focus is providing material for technical articles.