In this article we discuss developing a custom driver, and provide source code for a 20x4 LCD targeted at the Atmel SAMD51 ARM microcontroller. While this article is targeted at the SAMD51 it is written with abstraction in mind for ease of portability between microcontrollers. Commercial use of this driver is allowed provided you attribute this article with a link and send us an email to let us know it was helpful!
This driver was created to support a microcontroller application where an output LCD was needed. Since this application is built upon the Atmel ASF4 platform and targets the SAMD51 ARM microcontroller a driver needed to be written. While there are many drivers available this one was written from the ground up in hopes that its added documentation will aid newcomers to writing drivers and code for microcontrollers outside of the common Arduino platform.
You are free to use this driver for commercial applications with attribution and notification (link to this article, and send me an email so I know it was helpful!), however, it is provided without warranty whether express or implied. User assumes all risk.
Hardware
The targeted device for this application is the SAMD51J19A. It is a small 48QFN 120MHz ARM microcontroller from Atmel. While the driver was written on top of the ASF4 platform it uses minimal ASF code to make it functional.
The LCD is our 20x4 LCD module which is connected to our standard flat-flex break-out connector. This LCD uses the standard Hitachi HD44780 compatible interface that most LCDs use these days.
In addition to the application hardware a Seggger J-Link programmer is used to program the MCU, and a logic analyzer was found to be particularly helpful to debug during initial development.
Note: Please contact us if you want to purchase a bare LCD module board as we have spares available. Schematic drawings, and BOM are also available.
Software
The driver was exclusively developed in C, and documentation added with doxygen tags. It is designed with three layers:
Application Interface Layer
Middleware Layer
Hardware Abstraction Layer
Note: This LCD driver is designed to operate in 4-bit mode only. 8-bit mode is not currently supported. This 4-bit mode has the advantage of requiring fewer connections but is slower. Additionally, pulldown resistors on the LCD DB0->DB4 pins are recommended for better noise immunity.
Application Interface Layer
The application interface layer is the layer that you will use to control the LCD within your program. Simply call lcd_setup() once to initialze the LCD, then you can call the other methods as needed.
Note: While the other methods are callable, you are advised to not to use them after you setup the HAL to support your hardware.
The driver has been designed to accept a single character lcd_write_char() or a string lcd_write_str(). The driver will automatically increment text as it writes, and supports both newline characters /n and carriage returns /r within text. To reset the position to zero you can use either the clear screen command lcd_cls() or the set position command lcd_set_position().
Initialization Example
If you setup everything and connect some logic probes it should look something like this diagram if done properly.
Middleware Layer
The middleware layer are helper functions that perform actions between the application interface layer and the hardware abstraction layer. These include methods that strobe the LCD clock, split a byte into nibbles for the 4-bit interface, and so forth.
Hardware Abstraction Layer
The hardware abstraction layer (HAL) is the layer that interfaces the LCD driver to your particular device. This is the layer that you will need to rewrite for each device. The layering scheme helps support minimizing redesign efforts when transitioning to other hardware.
The HAL has only two methods _lcd_hal_delay() which is the interface to the microcontrollers delay function, and _lcd_hal_set_pin_state() which is the interface to the GPIO.
HAL Delay Method
The delay method is very simple, and simply takes in a delay duration in microseconds. This is then passed to the microcontroller’s method of delay.
In your application you will need to replace delay_us() since this method is part of Atmel’s ASF framework. Simply replace it with an equivalent delay microsecond function that can accept an integer of duration.
The specific delay durations are defined in the header to allow easy customization to suite your hardware. The values can be determined from the hardware datasheet but the values used were experimentally determined for best reliability.
Note: This driver is written with blocking delays. If your application uses a real-time OS (RTOS), or you need higher speed consider replacing this function with a timer or non-blocking method then re-writing some of the driver to form an LCD task.
Hint: If your microcontroller does not have a delay function easily available, look for a method to use the NOP command. In ARM microcontrollers it uses 1 clock cycle per NOP therefore you can calculate the delay time if you know your clock rate.
GPIO Interface Method
The second part of the HAL layer is the GPIO interface method. This is how the driver sets the state of the GPIO pins.
This design uses a switch case to switch between the pins. An Enum was used for ease of use with the design. In the case of the Atmel ASF framework the method to set the state of a pin is gpio_set_pin_level(). For other microcontrollers this is the part that will need to be replaced.
To replace this you simply need a function that will accept a value of 1 or 0 corresponding to the state variable for how your system is wired.
Note: The values LCD_DBx through LCD_EN are #defines created by the ASF tools. These would be the specific pinout to wire the LCD to. Do not include these in your application, only pass the state variable to your custom pin method and assignment.
Source Code
Note: While this code has been initially tested it has not been put full a through regression and unit test. It is possible that bugs exist with it. No warranty expressed or implied shall be provided. User assumes all risk with use.
License Terms
This code may be used for commerical use, however, under all use cases it must maintain attribution to LambdaFox, and include a link to our website or this article. Additionally, I ask that you email us to let us know you used it so we can stay motivated to spend more time on projects like this.