Fool-proof C library for HD44780 LCDs for MSP430 or others

By Dennis Eichmann

>> Fool-proof C library for HD44780 LCDs of all usual sizes <<

For use with MSP430, MSP432 and other types of microcontrollers and processors

V 1.00

 

1) Description

This library offers an easy and flexible way to connect a HD44780 (or compatible) display to a microcontroller like the MSP430 or MSP432 - of course it can be used on other microcontrollers or processors as well.

The reason for offering this library is simple: The HD44780 is the most used display by any beginner and I started with this type of display myself. Compared to other LCDs, the HD44780 based ones are very simple, but you will only hear this from someone that knows how to use them - for somebody who has never worked with it, getting something on the screen might be difficult and frustrating. This library is not meant to be a guide on how to properly configure and use a HD44780 LCD, it is more for the ones that only want to connect the display to the microcontroller, write a minimum amount of code lines and see a working result on the screen - let's say it is for the lazy ones ;) In the MSP Low Power Microcontroller Forum I often see people having problems with this display and in most cases the issues are timing violations, a wrong initialization or a combination of both. Another thing I frequently see is the common workaround for it - add as many delay cycles until it finally works. But as soon as the same code is used at a different clock speed, tweaking the delay cycles starts again.

The given library doesn't use any delay cycles and is based on a continuous running timer - a module that is included in every microcontroller. The user only has to write an interrupt service routine that is called every 1ms to 10ms and place a single function of this library inside of the interrupt so that it is called in the desired intervals. There is no need to care for any timings and the initialization of the display is automatically handled as well. The library provides one basic function to write a text string at any position on the screen and it additionally comes with a few basic functions for the most common beginner applications like printing an ADC value, a measured voltage or a plain number. For more sophisticated text formatting I would recommend to use the C standard library sprintf and pass the text string to the basic function of this library, but there will be a few examples later.

Here are a few features of the library:

  • Supports 1x8, 1x16, 1x20, 2x8, 2x16, 2x20, 4x16 and 4x20 HD44780 (and compatible) LCDs
  • Supports full 8 bit mode or 4 bit nibble mode
  • Support for normal parallel connection as well as for using a shift register like the 74HCT595 or 74HCT164 to save pins
  • Use any port and any pin for any connection of the display - no need for consecutive pins on a single port
  • Q outputs of a shift register can be connected in any order and unused outputs can be used to drive other hardware
  • All pins can be inverted in order to compensate for external inverting logic
  • Various hardware setups down to only four pins of the microcontroller while still providing three outputs of a shift register for other hardware
  • Connection with only three pins of the microcontroller when driving the display from a shift register without additional hardware
  • Clock speed independent without program blocking delay cycles
  • Critical timing handled by the library
  • Automatic initialization based on the selected display type and hardware connection
  • Easy configuration by a few #defines in the header file
  • Pre-inserted definitions for typical denominations of MSP43x controllers

One side note: In German there is a proverb saying "Mit Kanonen auf Spatzen schießen.", a translation program gives me "To break a butterfly on a wheel." - I don't get the meaning behind this translation, but in other words: Driving a HD44780 display by a timer might be a bit overstated, but on the other hand it also provides the mentioned benefits like the platform independent timing. In combination with the flexibility regarding the hardware connections, this library will help you to get your LCD running within a few minutes. Regardless if you use a 4 bit interface, a 8 bit interface, a parallel connection, a serial connection, mixed or straight pin order, processor A, processor B, or whatever, the display output is the part of the project that is finished right from the start. The only things you have to do for each project are setting up the timer interrupt, configuring the used pins to be outputs and making the project specific definitions in the header file.

Depending on the configuration, the given features of this library result in a few drawbacks compared to a program that was written for one specific hardware setup. They are negligible, but I will mention them anyway: The flexibility for the mixed hardware connections will result in a few more clock cycles to set all the bits compared to a byte that is written completely to a single port. The timer based method will somehow keep the microcontroller active - at least it will wake the controller up in the set interval in order to write data to the display. This means that the working principle is not the most power saving one. The display content is updated even if there were no changes, so there is unnecessary data written to the display that would normally not be required. Furthermore the library works with a buffer that holds the screen image - for a 4x20 LCD this will result in 80 bytes of RAM only for the buffer, so this library is not meant to run on very tiny processors like the MSP430G2102, for example. I would not recommend to use it on a processor with less than 0.5k of RAM and 4k of non-volatile memory, just to have some headroom for the application.

The datasheet for the HD44780 controller can be found here.

 

2) Supported LCD sizes

 

3) Possible hardware setups

  • Parallel 8 bit mode with having D0 to D7 of the LCD consecutively connected to Px.0 to Px.7 of one microcontroller port plus RS and E connected to any other ports / pins - uses 10 pins of the microcontroller
  • Parallel 8 bit mode with having D0 to D7 of the LCD connected to any mixed ports / pins of the microcontroller plus RS and E connected to any ports / pins - uses 10 pins of the microcontroller
  • Parallel 4 bit mode with having D4 to D7 of the LCD consecutively connected to Px.0 to Px.3 of one microcontroller port plus RS and E connected to any ports / pins - uses 6 pins of the microcontroller
  • Parallel 4 bit mode with having D4 to D7 of the LCD consecutively connected to Px.4 to Px.7 of one microcontroller port plus RS and E connected to any ports / pins - uses 6 pins of the microcontroller
  • Parallel 4 bit mode with having D4 to D7 of the LCD connected to any mixed ports / pins of the microcontroller plus RS and E connected to any ports / pins - uses 6 pins of the microcontroller
  • Serial 8 bit mode with having D0 to D7 of the LCD consecutively connected to Q0 to Q7 of one 74HCT595 or 74HCT164 shift register plus RS and E connected to any other ports / pins of the microcontroller - uses 5 pins of the microcontroller for 74HCT595 and 4 pins for 74HCT164
  • Serial 8 bit mode with having D0 to D7 of the LCD connected to any Q output of one 74HCT595 or 74HCT164 shift register plus RS and E connected to any other ports / pins of the microcontroller - uses 5 pins of the microcontroller for 74HCT595 and 4 pins for 74HCT164
  • Serial 4 bit mode with having D4 to D7 of the LCD consecutively connected to Q0 to Q3 of one 74HCT595 or 74HCT164 shift register plus RS and E connected to any other ports / pins of the microcontroller - unused  four Q outputs can be used to drive other hardware - uses 5 pins of the microcontroller for 74HCT595 and 4 pins for 74HCT164
  • Serial 4 bit mode with having D4 to D7 of the LCD consecutively connected to Q4 to Q7 of one 74HCT595 or 74HCT164 shift register plus RS and E connected to any other ports / pins of the microcontroller - unused  four Q outputs can be used to drive other hardware - uses 5 pins of the microcontroller for 74HCT595 and 4 pins for 74HCT164
  • Serial 4 bit mode with having D0 to D7 of the LCD connected to any Q output of one 74HCT595 or 74HCT164 shift register plus RS and E connected to any other ports / pins of the microcontroller - unused  four Q outputs can be used to drive other hardware - uses 5 pins of the microcontroller for 74HCT595 and 4 pins for 74HCT164
  • Serial 4 bit mode with having D4 to D7 of the LCD consecutively connected to Q0 to Q3 of one 74HCT595 or 74HCT164 shift register plus RS connected to any other Q output of the shift register plus E connected to any other port / pin of the microcontroller - unused  three Q outputs can be used to drive other hardware - uses 4 pins of the microcontroller for 74HCT595 and 3 pins for 74HCT164
  • Serial 4 bit mode with having D4 to D7 of the LCD consecutively connected to Q4 to Q7 of one 74HCT595 or 74HCT164 shift register plus RS connected to any other Q output of the shift register plus E connected to any other port / pin of the microcontroller - unused  three Q outputs can be used to drive other hardware - uses 4 pins of the microcontroller for 74HCT595 and 3 pins for 74HCT164
  • Serial 4 bit mode with having D4 to D7 of the LCD connected to any Q output of one 74HCT595 or 74HCT164 shift register plus RS connected to any other Q output of the shift register plus E connected to any other port / pin of the microcontroller - unused  three Q outputs can be used to drive other hardware - uses 4 pins of the microcontroller for 74HCT595 and 3 pins for 74HCT164

 

4) Pinout of the HD44780 display

The following pinout applies to 99.9% of the HD44780 (or compatible) displays - there are a few that have pin 1 and 2 or pin 15 and 16 switched.

  1. GND
  2. Vcc (5V typical)
  3. Vee (contrast voltage)
  4. RS (register select - 0 for command, 1 for display data)
  5. R/W (read/write - 0 for write, 1 for read - permanently tied to GND for this library)
  6. E (enable - high to low transition executes data)
  7. D0
  8. D1
  9. D2
  10. D3
  11. D4
  12. D5
  13. D6
  14. D7
  15. LED backlight +
  16. LED backlight -

 

5) Typical hardware connections

The two most used hardware setups are shown below. The LCD is typically driven by 5V, has it's contrast voltage VEE connected to the midpoint of a 5k to 10k potentiometer that is connectd between 5V and GND and the backlight is also connected to 5V - almost every display has a series resistor for the backlight onboard. You may want to add a transistor at the cathode of the backlight to switch it on and off or to apply a PWM signal in order to regulate the brightness, but this is up to the user. The data lines D0 to D7, the RS signal (register select) and the E (enable) signal are usually directly connected to the microcontroller. The RW (read/write) pin is often shorted to GND because reading from the display is not necessary and a low level sets it to write mode permanently.

In the left picture the display is connected via 8 bit parallel interface to one complete port of the microcontroller - this is the most efficient way because a data byte can be sent to the display in one cycle. The right picture shows a 4 bit interface. This saves four pins of the microcontroller, but one byte has to be transmitted in two 4 bit packages (nibbles) now - this is a bit more complex and takes twice as long, but speed isn't an issue for this type of display. All interfaces need the E and RS signal. Of course any port could be used for the data lines. But if you want to keep it simple, use consecutive pins of one port because no bit order has to be switched before the transmission. The 4 bit interface in the right picture could be P1.0 to P1.3 as well, of course, or four other consecutive bits when not wanting to rearrange the bit order. But D4 to D7 of the display have to be used in 4 bit mode - it does not work with D0 to D3.

 

 

6) Glue logic for signal level conversion

So why is there a red flash next to the 3.3V of the MSP43x? Driving the display with 3.3V logic levels from the microcontroller is out of the HD44780's specifications. If you have a look into the datasheet you will find the following information:

The minimum input high level is 0.7xVcc which is 3.6V for a supply of 5V, so 3.3V does not meet this minimum requirement. Note that the display also works with 3.3V supply voltage, but then you would need a negative contrast voltage.

I have never experienced any problems with driving these displays at 3.3V logic levels while it is powered from a 5V source, but one should keep this information in mind.

There is a difference if you build something for hobby use or if you are designing an industrial product - industrial products should never violate any specifications because they might be used at various different temperatures where suddenly the violation has an effect. So if you care about the violation or not is up to you - if you don't, you can skip all the level translating glue logic stuff.

The following picture shows a few parts that can be used as glue logic - they will translate your 3.3V to proper 5V levels. Inverting and non-inverting solutions are shown. Keep in mind that the library can invert every single signal independently. You will notice that all parts (except from the discrete transistor solutions) are HCT types - this is important because HC types would have the same input requirements as the display itself. HCT types are more flexible regarding the input thresholds, but they cannot be used for a wide supply voltage range - this is a benefit of the HC types, but since we have a fixed 5V supply level, the HCT types are perfect.

 

7) Parallel interface with glue logic

A parallel display connection with voltage level translation could look like one of the following setups. The left one uses a 8 bit interface, so a total of ten connections are required. You could use a transistor for each of the pins, but I doubt anyone would go this way - it is just to show a possible variant. The left one is a reasonable setup - it uses a 4 bit interface and a 74HCT14 hex inverter. For both setups you would have to tell the library that all pins are inverted by enabling the respective definitions. And as mentioned before, there is no need to have the pins in a consecutive order - if this does not fit your application you can mix them up as you like - simply make your individual settings in the library.

 

8) Serial interface with shift register and N-FET as glue logic

A serial display connection with the shift register acting as voltage level translation is shown below. In this case a 74HCT595 type is used. The E signal is driven by a N-FET which inverts the signal, but this can be set in the library. This setup uses only four pins of the microcontroller. The example shows totally mixed up ports and pins and three additional LEDs are driven by the shift register. You can, of course, connect anything here. The possibility of these mixed connections can offer you some flexibility in your board layout, for example. Note that a 74HCT164 shift register is not the part to be used when wanting to drive additional hardware with the unused outputs of the shift register because it does not have a latch. This means that when shifting the data byte in, the outputs will not remain at a fixed state until the byte was transmitted completely. If you don't want to drive anything else through the shift register you can move over to the 74HCT164 type and save the latch pin which results in three pins only to drive the HD44780 LCD.

 

9) How to use this library

Using this library is very simple - it is based on two major functions. The only thing you have to provide is a contiuously running timer that generates interrupts in an interval of 1 ms to 10 ms. This timer interval depends on the display size, the used type of hardware interface, the amount of data that is updated on the screen and your individual feelings. For the slowest possible hardware connection, which is a serial interface in 4 bit mode, with the largest type of display (4x20) you want to go down to 1 ms, but when only using a 2x16 display with 8 bit parallel interface you may want to use 10 ms. Just do some experiments here - you don't want to see the display content written row by row.

The HD44780.h file is based on a few #define values you have to set for your individual setup. Depending on your definitions, the IDE will gray out (at least CCS does) not required parts in this file. Start at the top and don't skip any selection that is not grayed out.

And don't forget to set your used pins to output direction in your main() function.

The two major functions of this library:

  • void hd44780_timer_isr( void )

This is the first important function and it has to be called in the above explained interval, so simply call hd44780_timer_isr() inside your timer interrupt service routine. Don't worry about this function call inside an interrupt - there is very little code executed. The function does not expect anything to be passed to it and it does not return anything - it simply runs the state machine for the HD44780 display. This includes all of the initial initialization as well as the display data and control data transmission for your specific type of hardware interface.


  • uint8_t hd44780_write_string( char * ch__string, uint8_t u8__row, uint8_t u8__column, uint8_t u8__cr_lf )

This second important function is used to place a text string at a desired location somewhere on the screen. This function has a few function arguments you have to pass to it and it also returns a value.

The function arguments:

char * ch__string is the text string you want to write to the display. It is a pointer to a char array. You can pass the address of a string to it or write a string directly into the function (which is the same from the machine side of view). But keep in mind that it it expects a string that is terminated with 0 (or '\0'), so passing a single character by writing 'A' is not valid. You have to pass it as a string by writing "A".

uint8_t u8__row is the row you want the text to appear in. For a 4x20 display you can choose 1, 2, 3 or 4. A 2x16 display only has 1 and 2, of course. A row that is not present for a used display will be ignored and no data is written to the display. The function returns 0 in this case. A single row display always needs this argument to be 1.

uint8_t u8__column is the column you want the text to start in. Similar to the row, the argument can only be as large as the maximum number of columns of your display. The index starts with 1. So for a 4x20 display you can pass a value ranging from 1 to 20. A too large value will be ignored and 0 is returned.

uint8_t u8__cr_lf stands for carriage return and line feed. If set to 1, text that does not fit into one row will be continued in the next one or, if the last column in the last row was reached, in the first one. For a single row display the text will continue at the first column, of course. There is a definition for better readability named CR_LF you can use instead of passing the number 1. The function returns the last column of the current row it has written to - this is useful if you want to add additional text behind the already written one. If this argument is passed as 0 or NO_CR_LF, printing text stops at the last column of the row and the function returns this last column.

Return value:

The function returns the last written column in a row or it returns 0 if the passed row or column was outside the display's range.

 

A function call could look like this:

hd44780_write_string( "Hello world!", 1, 1, NO_CR_LF );

It would print "Hello world!" at the upper left corner of the display (first row, first column).

 

10) Explanation of the library's additional functions

 

  • void hd44780_clear_row( uint8_t u8__row )

This function clears a row. It has one function argument and does not return anything.

Function argument:

uint8_t u8__row is the row you want to clear. If the passed value is larger than your display's number of rows, the command will be ignored.


  • void hd44780_clear_screen( void )

This function clears the whole screen. There is no function argument and no return value.


  • void hd44780_blank_out_remaining_row( uint8_t u8__row, uint8_t u8__column )

This function can be used to clear the remaining columns of a row when the text itself does not fill the complete row. It is useful when a row is updated with a new text string that is shorter than the string that was written to this line before. If the new string does not cover all columns of the old string, the remaining columns still contain the old text. By calling this functions you can blank out the remaining columns after your last written character. It is useful to work with the return value of the function for writing strings because you can pass the return value + 1 to this function. There are two function arguments and no return value.

Function arguments:

uint8_t u8__row is the row you want to blank out. Pass a value that is within the number of rows of your display. Any other number will be ignored.

uint8_t u8__column is the column you want to start with blanking out. Pass a value that is within the number of comuns of your display. Any other number will be ignored. The function starts with the given column and continues until it reaches the last column of the row. It will never move to the next row.


  • void hd44780_write_shared_shift_register_bits( uint8_t u8__sr_bitmap )

If a serial hardware connection via a shift register is used and the display is connected in 4 bit mode, the remaining outputs of the shift register can be used to control other hardware. This function is used to update the additional shift register outputs. HD44780_SR_SHARES_OTHER_FUNCTION needs to be enabled in the HD44780.h file in order to tell the state machine that it has to pay attention to these bit settings. Bits required for display communication will be overwritten. The update rate is limited by the timer interrupt interval. The function has one argument and no return value.

Function arguments:

uint8_t u8__sr_bitmap is holds the logic states for the shift register outputs that are not used for display communication.


  • uint8_t hd44780_output_adc_value_mv( uint32_t u32__adc_value, uint16_t u16__adc_reference_mv, uint8_t u8__adc_resolution_bits, uint8_t u8__leading_zero_handling, uint8_t u8__row, uint8_t u8__column, uint8_t u8__cr_lf )

This function is used to output a voltage that is sampled by the ADC - one of the most common things beginners start with. The function also does the conversion from ADC counts to voltage in mV and it will output the value at the desired location on the screen. You can decide whether you want to see leading zeroes, if you want to have them blanked out or if you want to delete them. There are seven function arguments and a return value.

Function arguments:

uint8_t u32__adc_value is the result from your ADC.

uint16_t u16__adc_reference_mv is the ADC's reference voltage in mV.

uint8_t u8__adc_resolution_bits is your ADC's resolution in bits. Valid values are 8, 10, 12, 14 and 16 - other values will be ignored.

uint8_t u8__leading_zero_handling let's you control the handling of leading zeroes. If you pass 0 or SHOW_ZEROES, leading zeroes will be shown. If you pass 1 or BLANK_ZEROES, leading zeroes will be blanked out and if you pass 2 or DELETE_ZEROES, only significant numbers will be shown and leading zeroes will be deleted. Any other value will have the same effect as deleting zeroes.

uint8_t u8__row is the row you want the value to appear in. A row outside the display's range will be ignored, nothing is written and 0 is returned. A single row display always needs this argument to be 1.

uint8_t u8__column is the column you want to start in. A column outside the display's range will be ignored, nothing is written and 0 is returned.

uint8_t u8__cr_lf determines whether printing the value is continued in the next row (or from the beginning for single row displays) if the end of the actual row was reached. Pass 1 or CR_LF to continue or 0 or NO_CR_LF to stop at the end of row.

Return value:

The function returns the last written column in a row or it returns 0 if the passed row or column was outside the display's range.


  • uint8_t hd44780_output_unsigned_16bit_value( uint16_t u16__value, uint8_t u8__leading_zero_handling, uint8_t u8__row, uint8_t u8__column, uint8_t u8__cr_lf )

This function is used to output an unsigned 16 bit integer value to the desired location on the screen and it offers different options for leading zero handling. It has five function arguments and a return value.

Function arguments:

uint16_t u16__value is the 16 bit unsigned integer value you want to display.

uint8_t u8__leading_zero_handling let's you control the handling of leading zeroes. If you pass 0 or SHOW_ZEROES, leading zeroes will be shown. If you pass 1 or BLANK_ZEROES, leading zeroes will be blanked out and if you pass 2 or DELETE_ZEROES, only significant numbers will be shown and leading zeroes will be deleted. Any other value will have the same effect as deleting zeroes.

uint8_t u8__row is the row you want the value to appear in. A row outside the display's range will be ignored, nothing is written and 0 is returned. A single row display always needs this argument to be 1.

uint8_t u8__column is the column you want to start in. A column outside the display's range will be ignored, nothing is written and 0 is returned.

uint8_t u8__cr_lf determines whether printing the value is continued in the next row (or from the beginning for single row displays) if the end of the actual row was reached. Pass 1 or CR_LF to continue or 0 or NO_CR_LF to stop at the end of row.

Return value:

The function returns the last written column in a row or it returns 0 if the passed row or column was outside the display's range.

 

11) Minimum example program

The following example program shows the minimum code that is necessary to use the library - the Text "Hello world!" is displayed on the screen. The HD44780 display is connected via 8 bit interface in consecutive pin mode, having D0 to D7 wired to P1.0 to P1.7 of a MSP430G2553 on the MSP-EXP430G2 LaunchPad. E is connected to P2.0 and RS is connected to P2.1. I did not use any logic level conversion since the G2 LaunchPad works at 3.6V instead of 3.3V, so this just matches the minimum required input high level of the HD44780 controller when operating at 5V. But as said before, it will also work at 3.3V when not caring about the datasheet specification. The 5V are taken from TP1 of the LaunchPad - this is a solder hole right next to the USB connector where you can solder in a wire to use the USB's 5V rail - be careful not to short it since there is no extra protection except the polyfuse inside your PC or your hub.

Here is a picture of the display output:

This is the configuration in the hd44780.h file:

  • #define HD44780_TYPE_4x20
  • #define HD44780_PARALLEL_MODE
  • #define HD44780_8BIT_MODE
  • #define HD44780_CONSECUTIVE_PINS_MODE
  • #define HD44780_ENABLE_MCU_OUT_PORT
    • P2OUT
  • #define HD44780_ENABLE_MCU_OUT_PIN
    • BIT0
  • #define HD44780_RS_MCU_OUT_PORT
    • P2OUT
  • #define HD44780_RS_MCU_OUT_PIN
    • BIT1
  • #define HD44780_MCU_OUT_PORT
    • P1OUT

And this is the code - copy paste it to your IDE:

/*
 * main.c
 *
 *  Created on: 31.03.2017
 *      Author: Dennis Eichmann
 */


#include "msp430g2553.h"                                    // Microcontroller specific header file
#include "hd44780.h"                                        // HD44780 library


void main( void )
{
  WDTCTL = (WDTPW | WDTHOLD);                               // Stop watchdog timer

  BCSCTL1 = CALBC1_1MHZ;                                    // Set range to calibrated 1MHz
  DCOCTL  = CALDCO_1MHZ;                                    // Set DCO step and modulation to calibrated 1MHz

  P1DIR = 0xFF;                                             // Set P1.0 (D0) to P1.7 (D7) to output (HD44780 data lines)
  P2DIR = (0x01 | 0x02);                                    // Set P2.0 (E) and P2.1 (RS) to output (HD44780 control lines - R/W is tied to GND)

  TA0CCR1  = 1000;                                          // Set CCR1 value for 1 ms interrupt (1000 / 1 MHz) = 0.001
  TA0CCTL1 = CCIE;                                          // Compare interrupt enable
  TA0CTL   = (TASSEL_2 | MC_2 | TACLR);                     // SMCLK as clock source, continuous mode, timer clear

  __bis_SR_register( GIE );                                 // Enable global interrupts

  hd44780_clear_screen();                                   // Clear display content

  while( 1 )                                                // Endless loop - main program
  {
    hd44780_write_string( "Hello world!", 1, 1, NO_CR_LF ); // Write text string to first row and first column of display
  }
}


// Directive for timer interrupt
#pragma vector = TIMER0_A1_VECTOR
__interrupt void timer_0_a1_isr( void )                     // Timer 0 A1 interrupt service routine
{
  switch( TA0IV )                                           // Determine interrupt source
  {
    case 2:                                                 // CCR1 caused the interrupt
    {
      TA0CCR1 += 1000;                                      // Add CCR1 value for next interrupt in 1 ms

      hd44780_timer_isr();                                  // Call HD44780 state machine

      break;                                                // CCR1 interrupt handling done
    }
  }
}

 

12) Example program with more functionality

I have written a little bit larger example program - you can copy paste it to your IDE. It was also made for the MSP430G2553 on the MSP-EXP430G2 LaunchPad and it samples a 5k potentiometer at A0 (P1.0) that is connected between the internally generated 2.5V reference voltage (output at P1.4) of the MSP and GND. It then displays the measured ADC value, calculates the sampled voltage in mV and also displays it. Additionally it outputs the resolution of the ADC, which is just a #define in the source code as well as the reference voltage in mV, also just a #define after measuring it's real value with a DMM. If you press button S2 on the LaunchPad, it outputs the text from the banner at the top of this project. The HD44780 is connected via a 4 bit interface in mixed pins mode, having D4 connected to P1.1, D5 connected to P1.2, D6 connected to P1.6 and D7 connected to P1.7. Furthermore E is connected to P2.0 and RS is connected to P2.1.

This example is only to show how you could use the given functions from the library. All my comments make it look quite large, but it isn't, just read through it. The display output looks like this:

The configuration in the hd44780.h file is as following:

  • #define HD44780_TYPE_4x20
  • #define HD44780_PARALLEL_MODE
  • #define HD44780_4BIT_MODE
  • #define HD44780_MIXED_PINS_MODE
  • #define HD44780_ENABLE_MCU_OUT_PORT
    • P2OUT
  • #define HD44780_ENABLE_MCU_OUT_PIN
    • BIT0
  • #define HD44780_RS_MCU_OUT_PORT
    • P2OUT
  • #define HD44780_RS_MCU_OUT_PIN
    • BIT1
  • #define HD44780_D4_MCU_OUT_PORT
    • P1OUT
  • #define HD44780_D4_MCU_OUT_PIN
    • BIT1

  • #define HD44780_D5_MCU_OUT_PORT
    • P1OUT
  • #define HD44780_D5_MCU_OUT_PIN
    • BIT2
  • #define HD44780_D6_MCU_OUT_PORT
    • P1OUT
  • #define HD44780_D6_MCU_OUT_PIN
    • BIT6
  • #define HD44780_D7_MCU_OUT_PORT
    • P1OUT
  • #define HD44780_D7_MCU_OUT_PIN
    • BIT7

Here are the code lines:

/*
 * main.c
 *
 *  Created on: 31.03.2017
 *      Author: Dennis Eichmann
 */


#include "msp430g2553.h" // Microcontroller specific header file
#include "hd44780.h" // HD44780 library


#define ADC10_AVERAGE_SAMPLE_COUNT   64 // Moving average over last 64 samples (breadboard is quite noisy due to bad GND)
#define ADC10_REFERENCE_VOLTAGE_MV   2543 // Measured (with DMM) reference voltage in mV at reference output pin P1.4
#define ADC10_RESOLUTION_BITS        10 // MSP430G2553 ADC has 10 bits of resolution


volatile uint8_t  u8__adc10_result_available = 0; // Flag to signal main() that ADC result is available
volatile uint16_t u16__adc10_sample_buffer[ADC10_AVERAGE_SAMPLE_COUNT]; // ADC sample buffer for averaging
         uint16_t u16__adc10_result; // Calculated ADC result after averaging


void main( void )
{
  // Watchdog timer stopped in _system_pre_init

  BCSCTL1 = CALBC1_8MHZ; // Set range to calibrated 8MHz
  DCOCTL  = CALDCO_8MHZ; // Set DCO step and modulation to calibrated 8MHz

  P1DIR = (0x02 | 0x04 | 0x40 | 0x80); // Set P1.1 (D4), P1.2 (D5), P1.6 (D6) and P1.7 (D7) to output (HD44780 data lines)
  P1OUT = 0x08; // Set output for P1.3 high in order to enable pull-up resistor
  P1REN = 0x08; // Enable pull resistor on P1.3
  P2DIR = (0x01 | 0x02); // Set P2.0 (E) and P2.1 (RS) to output (HD44780 control lines - R/W is tied to GND)

  // Vr+ = VREF+, Vr- = Vss, S&H 64 ADC10 clock cycles, reference output on, 2.5V reference, ADC10 on, ADC10 interrupt enable, enable conversions
  ADC10CTL0 = (SREF_1 | ADC10SHT_3 | REFOUT | REF2_5V | REFON | ADC10ON | ADC10IE | ENC);
  ADC10CTL1 = INCH_0; // Select channel A0 on P1.0
  ADC10AE0  = 0x01; // Analog input enable for A0

  TA0CCR1  = 1000; // Set CCR1 value for 1 ms interrupt (1000 / (8 MHz / 8)) = 0.001
  TA0CCTL1 = CCIE; // Compare interrupt enable
  TA0CTL   = (TASSEL_2 | ID_3 | MC_2 | TACLR); // SMCLK as clock source, divider 8, continuous mode, timer clear

  for( u16__adc10_result = 0; u16__adc10_result < ADC10_AVERAGE_SAMPLE_COUNT; u16__adc10_result++ ) // Borrow u16__adc10_result to initialize ADC sample buffer
  {
    u16__adc10_sample_buffer[u16__adc10_result] = 0; // Set all buffer values to 0
  }

  u16__adc10_result = 0; // Set u16__adc10_result to 0

  ADC10CTL0 |= ADC10SC; // Start first ADC conversion - interrupt will be triggered when done

  __bis_SR_register( GIE ); // Enable global interrupts

  while( 1 ) // Endless loop - main program
  {
    uint8_t u8__last_written_column; // Variable to store last written column position in display row in order to place additional text dynamically behind it

    if( !(P1IN & 0x08) ) // Button S2 pressed (on MSP-EXP430G2 LaunchPad)
    {
      hd44780_write_string( "Fool-proof C library", 1, 1, NO_CR_LF ); // Write a 20 character text string to first row of display
      hd44780_write_string( "  for HD44780 LCDs  ", 2, 1, NO_CR_LF ); // Write a 20 character text string to second row of display
      hd44780_write_string( " of all usual sizes ", 3, 1, NO_CR_LF ); // Write a 20 character text string to third row of display
      hd44780_write_string( "for MSP43x or others", 4, 1, NO_CR_LF ); // Write a 20 character text string to fourth row of display
    }
    else // Button S2 released
    {
      // Write text string to first row and first column of display, function returns last written column
      u8__last_written_column = hd44780_write_string( "ADC value  : ", 1, 1, NO_CR_LF );
      // Write ADC value to first row after last written column,  function returns new last written column
      u8__last_written_column = hd44780_output_unsigned_16bit_value( u16__adc10_result, DELETE_ZEROES, 1, (u8__last_written_column + 1), CR_LF );
      // Blank out remaining first row after last written column
      hd44780_blank_out_remaining_row( 1, (u8__last_written_column + 1) );

      // Write text string to second row and first column of display, function returns last written column
      u8__last_written_column = hd44780_write_string( "ADC voltage: ", 2, 1, NO_CR_LF );
      // Write ADC voltage to second row after last written column, function returns new last written column
      u8__last_written_column = hd44780_output_adc_value_mv( u16__adc10_result, ADC10_REFERENCE_VOLTAGE_MV, ADC10_RESOLUTION_BITS, DELETE_ZEROES, 2, (u8__last_written_column + 1), NO_CR_LF );
      // Write text string to second row after last written column, function returns new last written column
      u8__last_written_column = hd44780_write_string( "mV", 2, (u8__last_written_column + 1), NO_CR_LF );
      // Blank out remaining second row after last written column
      hd44780_blank_out_remaining_row( 2, (u8__last_written_column + 1) );

      // Write text string to third row and first column of display, function returns last written column
      u8__last_written_column = hd44780_write_string( "ADC bits   : ", 3, 1, NO_CR_LF );
      // Write ADC resolution to third row after last written column,  function returns new last written column
      u8__last_written_column = hd44780_output_unsigned_16bit_value( ADC10_RESOLUTION_BITS, DELETE_ZEROES, 3, (u8__last_written_column + 1), CR_LF );
      // Blank out remaining third row after last written column
      hd44780_blank_out_remaining_row( 3, (u8__last_written_column + 1) );

      // Write text string to fourth row and first column of display, function returns last written column
      u8__last_written_column = hd44780_write_string( "ADC Vref   : ", 4, 1, NO_CR_LF );
      // Write ADC reference voltage to fourth row after last written column, function returns new last written column
      u8__last_written_column = hd44780_output_unsigned_16bit_value( ADC10_REFERENCE_VOLTAGE_MV, DELETE_ZEROES, 4, (u8__last_written_column + 1), CR_LF );
      // Write text string to fourth row after last written column, function returns new last written column
      u8__last_written_column = hd44780_write_string( "mV", 4, (u8__last_written_column + 1), NO_CR_LF );
      // Blank out remaining fourth row after last written column
      hd44780_blank_out_remaining_row( 4, (u8__last_written_column + 1) );
    }

    if( u8__adc10_result_available ) // Check if flag for new ADC result is set
    {
      uint8_t  u8__counter; // Counter for summing up the buffer values
      uint32_t u32__adc10_average_sum = 0; // Sum of all buffer values

      u8__adc10_result_available = 0; // Reset flag for signaling new result

      for( u8__counter = 0; u8__counter < ADC10_AVERAGE_SAMPLE_COUNT; u8__counter++ ) // Loop for adding up the buffer values
      {
        u32__adc10_average_sum += u16__adc10_sample_buffer[u8__counter]; // Add actual value to buffer sum
      }

      u16__adc10_result = (u32__adc10_average_sum / ADC10_AVERAGE_SAMPLE_COUNT); // Divide sum by number of values

      ADC10CTL0 |= ADC10SC; // Start new ADC conversion
    }
  }
}


// Directive for timer interrupt
#pragma vector = TIMER0_A1_VECTOR
__interrupt void timer_0_a1_isr( void ) // Timer 0 A1 interrupt service routine
{
  switch( TA0IV ) // Determine interrupt source
  {
    case 2: // CCR1 caused the interrupt
    {
      TA0CCR1 += 1000; // Add CCR1 value for next interrupt in 1 ms

      hd44780_timer_isr(); // Call HD44780 state machine

      break; // CCR1 interrupt handling done
    }
  }
}


// Directive for ADC10 interrupt
#pragma vector = ADC10_VECTOR
__interrupt void adc10_isr( void ) // ADC10 interrupt service routine
{
  static uint8_t u8__adc10_sample_counter = 0; // Counter to fill ADC buffer

  u16__adc10_sample_buffer[u8__adc10_sample_counter] = ADC10MEM; // Add new adc result to buffer

  if( ++u8__adc10_sample_counter >= ADC10_AVERAGE_SAMPLE_COUNT ) // Increment counter and test if maximum count is reached
  {
    u8__adc10_sample_counter = 0; // Reset counter
  }

  u8__adc10_result_available = 1; // Set flag to signal main that new result is available
}


int _system_pre_init( void ) // This function is called before anything else is done
{
  WDTCTL = (WDTPW | WDTHOLD); // Stop watchdog timer

  return 1; // Return 1
}

 

13) Have fun!

 

Come back from time to time to have a look if further material was added!

  • Nice package !!!  Thanks to you, Dennis.

    Some points around the hardware of LCDs in the case of direct MSP <-> LCD connection:

    - Using a 5 volt supplied LCD interfaced to a 3 volt MSP will not harm the MSP pins IF THE LCD IS WIRED TO WRITE MODE  (R/W = ground), thus making all LCD pins INPUT MODE.

    The reason is to be found in the LCD controller data sheet : the input pins are pulled up by 'weak pull up', means they source only a few microamps into the MSP Px.x pin.  These few microamps are easily handled with the Port pin spec of the MSP.    And this can be confirmed with a DVM current measurement.

    - If there is no 5 volt supply for the LCD  -or-  if both the LCD and MSP are run off the same 3 volt supply, like a Lithium button cell - here is a cool way to have the MSP generate a negative voltage supply for the LCD pin 3 Vee  :

    forum.43oh.com/.../

  • Hi Otto!

    Glad to hear you like it. Regarding the 5V powered LCD in combination with a 3.3V powered MSP: The problem ist not located at the MSP's pins - as you said yourself, the LCD's pins are all inputs in write mode (the library does not use read mode), so the LCD does not drive the connections to the MSP actively with 5V levels. But you will violate the minimum high input threshold level for the display controller. This would be 3.6V when running at 5V supply voltage.

    Using a charge pump for generating a negative contrast voltage is indeed a simple way to have your LCD running from the 3.3V supply - I could add this information at a later time.

    Dennis

  • Thank you so much dennis sir....

    5***** stars

Related
Recommended