Custom characters on HD44780 display (Nerdkit)
It took me really a lot of time to turn my Nerdkit into a Pixel Art Machine.
Because of that I wanted to share my experience with the community.
The following code shows one custom character on your nerdkit display.
Main Program
#define F_CPU 14745600
#include <stdio.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include <inttypes.h>
#include <string.h>
#include <util/delay.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
int main(void) {
// fire up the LCD
lcd_init();
lcd_home();
lcd_set_type_command(); // Change into command mode
lcd_write_byte(0x40); // Change to CGRAM to store custom character - at 0x00
lcd_set_type_data(); // Change into data mode
lcd_write_byte(0x00); // Send Hexa Bytes of Custom Character Begin
lcd_write_byte(0x0E);
lcd_write_byte(0x1F);
lcd_write_byte(0x1F);
lcd_write_byte(0x1F);
lcd_write_byte(0x0E);
lcd_write_byte(0x00);
lcd_write_byte(0x00); // Send Hexa Bytes of Custom Character End
lcd_set_type_command(); // Change into command mode
lcd_write_byte(0x80); // Change to DDRAM
lcd_set_type_data();
lcd_write_byte(0x00); // Show stored custom character on lcd screen
// busy loop
while(1) {
// do nothing
}
return 0;
}
This part was for a better understanding how things have to be written and read from the CGRAM.
Please check your makefile for this project.
You have to change from '-Os' to '-O0' in the makefile, or else your display writes anytime into the first line.
To get a clear declaration of the array, you also have to change the following code into your makefile:
Makefile
avr-objcopy -j .text -j .data -O ihex lnino_eval_board.o lnino_eval_board.hex
I have modified the original nerdkit LCD library to have a more comfortable use of custom characters.
char pattern01[8]={14,17,17,14,4,6,4,6}; // Key Pattern
lcd_createChar(0, pattern01); // Create Char on position 0x00
lcd_loadCharSingle(0x00); // Loads the Char 0x00 on the current Cursor Position
lcd_loadChar(0,19,0x00); // Loads the Char 0x00 on row 0, column 19
You can convert your Images to Numbers on the following Website:
http://www.quinapalus.com/hd44780udg.html
The complete modified nerdkit lcd library is attached here:
lcd.c

// lcd.c
// for NerdKits
// This email address is being protected from spambots. You need JavaScript enabled to view it.
//
// PIN DEFINITIONS:
//
//
// PD2-5 -- LCD DB4-7 (pins 11-14) (was PB3-6)
// PD6 -- LCD E (pin 6) (was PA5)
// PD7 -- LCD RS (pin 4) (was PA4)
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include <stdio.h>
#include "lcd.h"
#include "delay.h"
// lcd_set_type_data()
void lcd_set_type_data() {
PORTD |= (1<<PD7);
}
// lcd_set_type_command()
void lcd_set_type_command() {
PORTD &= ~(1<<PD7);
}
// lcd_createChar()
void lcd_createChar(uint8_t location, char charmap[]) {
int i = 0;
location &= 0x7; // 8 possible characters regarding to 1Byte availability
lcd_set_type_command(); // Change into command mode
lcd_write_byte(0x40 | (location << 3));
lcd_set_type_data(); // Change into data mode
for (i=0; i<8; i++) {
lcd_write_byte(charmap[i]);
}
lcd_set_type_command(); // Change into command mode
lcd_write_byte(0x80); // Change to DDRAM
lcd_set_type_data(); // Change into data mode
}
// lcd_loadChar()
void lcd_loadChar(uint8_t row_position, uint8_t column_position, char mem_location) {
lcd_goto_position(row_position, column_position);
lcd_write_byte_data(mem_location);
}
// lcd_loadCharSingle()
void lcd_loadCharSingle(char mem_location) {
lcd_write_byte_data(mem_location);
}
// lcd_write_nibble(...)
void lcd_write_nibble(char c) {
// NOTE: only 2 or 3 work in the delays here.
// set data
PORTD &= ~(0x0f << 2);
PORTD |= (c&0x0f) << 2;
// E high
PORTD |= (1<<PD6);
delay_us(1);
// E low
PORTD &= ~(1<<PD6);
delay_us(1);
}
void lcd_write_byte_data(char c) {
lcd_set_type_data();
lcd_write_nibble( (c >> 4) & 0x0f );
lcd_write_nibble( c & 0x0f );
delay_us(80);
}
void lcd_write_byte_command(char c) {
lcd_set_type_command();
lcd_write_nibble( (c >> 4) & 0x0f );
lcd_write_nibble( c & 0x0f );
delay_us(80);
}
void lcd_write_byte(char c) {
lcd_write_nibble( (c >> 4) & 0x0f );
lcd_write_nibble( c & 0x0f );
delay_us(80);
}
void lcd_clear_and_home() {
lcd_set_type_command();
lcd_write_byte(0x01);
delay_ms(50);
lcd_write_byte(0x02);
delay_ms(50);
}
void lcd_home() {
lcd_set_type_command();
lcd_write_byte(0x02);
delay_ms(50);
}
void lcd_write_data(char c) {
lcd_set_type_data();
lcd_write_byte(c);
}
// lcd_write_int16
void lcd_write_int16(int16_t in) {
uint8_t started = 0;
uint16_t pow = 10000;
if(in < 0) {
lcd_write_data('-');
in = -in;
}
while(pow >= 1) {
if(in / pow > 0 || started || pow==1) {
lcd_write_data((uint8_t) (in/pow) + '0');
started = 1;
in = in % pow;
}
pow = pow / 10;
}
}
// lcd_write_int16_centi
// assumes that its measured in centi-whatevers
void lcd_write_int16_centi(int16_t in) {
uint8_t started = 0;
uint16_t pow = 10000;
if(in < 0) {
lcd_write_data('-');
in = -in;
}
while(pow >= 1) {
if(in / pow > 0 || started || pow==1) {
lcd_write_data((uint8_t) (in/pow) + '0');
started = 1;
in = in % pow;
}
if(pow == 100) {
if(!started) {
lcd_write_data('0');
}
lcd_write_data('.');
started = 1;
}
pow = pow / 10;
}
}
void lcd_write_string(const char *x) {
// assumes x is in program memory
while(pgm_read_byte(x) != 0x00)
lcd_write_data(pgm_read_byte(x++));
}
void lcd_goto_position(uint8_t row, uint8_t col) {
lcd_set_type_command();
// 20x4 LCD: offsets 0, 0x40, 20, 0x40+20
uint8_t row_offset = 0;
switch(row) {
case 0: row_offset = 0; break;
case 1: row_offset = 0x40; break;
case 2: row_offset = 20; break;
case 3: row_offset = 0x40+20; break;
}
lcd_write_byte(0x80 | (row_offset + col));
}
void lcd_line_one() { lcd_goto_position(0, 0); }
void lcd_line_two() { lcd_goto_position(1, 0); }
void lcd_line_three() { lcd_goto_position(2, 0); }
void lcd_line_four() { lcd_goto_position(3, 0); }
// lcd_init()
void lcd_init() {
// set pin driver directions
// (output on PD7,PD6, and PD3-6)
DDRD |= 0xfc;
// wait 100msec
delay_ms(100);
lcd_set_type_command();
// do reset
lcd_write_nibble(0x03);
delay_ms(6);
lcd_write_nibble(0x03);
delay_us(250);
lcd_write_nibble(0x03);
delay_us(250);
// write 0010 (data length 4 bits)
lcd_write_nibble(0x02);
// set to 2 lines, font 5x8
lcd_write_byte(0x28);
// disable LCD
//lcd_write_byte(0x08);
// enable LCD
lcd_write_byte(0x0c);
// clear display
lcd_write_byte(0x01);
delay_ms(5);
// enable LCD
lcd_write_byte(0x0c);
// set entry mode
lcd_write_byte(0x06);
// set cursor/display shift
lcd_write_byte(0x14);
// clear and home
lcd_clear_and_home();
}
int lcd_putchar(char c, FILE *stream) {
lcd_write_data(c);
return 0;
}
lcd.h

// lcd.c
// for NerdKits
// This email address is being protected from spambots. You need JavaScript enabled to view it.
//
// PIN DEFINITIONS:
//
//
// PD2-5 -- LCD DB4-7 (pins 11-14) (was PB3-6)
// PD6 -- LCD E (pin 6) (was PA5)
// PD7 -- LCD RS (pin 4) (was PA4)
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include <stdio.h>
#include "lcd.h"
#include "delay.h"
// lcd_set_type_data()
void lcd_set_type_data() {
PORTD |= (1<<PD7);
}
// lcd_set_type_command()
void lcd_set_type_command() {
PORTD &= ~(1<<PD7);
}
// lcd_createChar()
void lcd_createChar(uint8_t location, char charmap[]) {
int i = 0;
location &= 0x7; // 8 possible characters regarding to 1Byte availability
lcd_set_type_command(); // Change into command mode
lcd_write_byte(0x40 | (location << 3));
lcd_set_type_data(); // Change into data mode
for (i=0; i<8; i++) {
lcd_write_byte(charmap[i]);
}
lcd_set_type_command(); // Change into command mode
lcd_write_byte(0x80); // Change to DDRAM
lcd_set_type_data(); // Change into data mode
}
// lcd_loadChar()
void lcd_loadChar(uint8_t row_position, uint8_t column_position, char mem_location) {
lcd_goto_position(row_position, column_position);
lcd_write_byte_data(mem_location);
}
// lcd_loadCharSingle()
void lcd_loadCharSingle(char mem_location) {
lcd_write_byte_data(mem_location);
}
// lcd_write_nibble(...)
void lcd_write_nibble(char c) {
// NOTE: only 2 or 3 work in the delays here.
// set data
PORTD &= ~(0x0f << 2);
PORTD |= (c&0x0f) << 2;
// E high
PORTD |= (1<<PD6);
delay_us(1);
// E low
PORTD &= ~(1<<PD6);
delay_us(1);
}
void lcd_write_byte_data(char c) {
lcd_set_type_data();
lcd_write_nibble( (c >> 4) & 0x0f );
lcd_write_nibble( c & 0x0f );
delay_us(80);
}
void lcd_write_byte_command(char c) {
lcd_set_type_command();
lcd_write_nibble( (c >> 4) & 0x0f );
lcd_write_nibble( c & 0x0f );
delay_us(80);
}
void lcd_write_byte(char c) {
lcd_write_nibble( (c >> 4) & 0x0f );
lcd_write_nibble( c & 0x0f );
delay_us(80);
}
void lcd_clear_and_home() {
lcd_set_type_command();
lcd_write_byte(0x01);
delay_ms(50);
lcd_write_byte(0x02);
delay_ms(50);
}
void lcd_home() {
lcd_set_type_command();
lcd_write_byte(0x02);
delay_ms(50);
}
void lcd_write_data(char c) {
lcd_set_type_data();
lcd_write_byte(c);
}
// lcd_write_int16
void lcd_write_int16(int16_t in) {
uint8_t started = 0;
uint16_t pow = 10000;
if(in < 0) {
lcd_write_data('-');
in = -in;
}
while(pow >= 1) {
if(in / pow > 0 || started || pow==1) {
lcd_write_data((uint8_t) (in/pow) + '0');
started = 1;
in = in % pow;
}
pow = pow / 10;
}
}
// lcd_write_int16_centi
// assumes that its measured in centi-whatevers
void lcd_write_int16_centi(int16_t in) {
uint8_t started = 0;
uint16_t pow = 10000;
if(in < 0) {
lcd_write_data('-');
in = -in;
}
while(pow >= 1) {
if(in / pow > 0 || started || pow==1) {
lcd_write_data((uint8_t) (in/pow) + '0');
started = 1;
in = in % pow;
}
if(pow == 100) {
if(!started) {
lcd_write_data('0');
}
lcd_write_data('.');
started = 1;
}
pow = pow / 10;
}
}
void lcd_write_string(const char *x) {
// assumes x is in program memory
while(pgm_read_byte(x) != 0x00)
lcd_write_data(pgm_read_byte(x++));
}
void lcd_goto_position(uint8_t row, uint8_t col) {
lcd_set_type_command();
// 20x4 LCD: offsets 0, 0x40, 20, 0x40+20
uint8_t row_offset = 0;
switch(row) {
case 0: row_offset = 0; break;
case 1: row_offset = 0x40; break;
case 2: row_offset = 20; break;
case 3: row_offset = 0x40+20; break;
}
lcd_write_byte(0x80 | (row_offset + col));
}
void lcd_line_one() { lcd_goto_position(0, 0); }
void lcd_line_two() { lcd_goto_position(1, 0); }
void lcd_line_three() { lcd_goto_position(2, 0); }
void lcd_line_four() { lcd_goto_position(3, 0); }
// lcd_init()
void lcd_init() {
// set pin driver directions
// (output on PD7,PD6, and PD3-6)
DDRD |= 0xfc;
// wait 100msec
delay_ms(100);
lcd_set_type_command();
// do reset
lcd_write_nibble(0x03);
delay_ms(6);
lcd_write_nibble(0x03);
delay_us(250);
lcd_write_nibble(0x03);
delay_us(250);
// write 0010 (data length 4 bits)
lcd_write_nibble(0x02);
// set to 2 lines, font 5x8
lcd_write_byte(0x28);
// disable LCD
//lcd_write_byte(0x08);
// enable LCD
lcd_write_byte(0x0c);
// clear display
lcd_write_byte(0x01);
delay_ms(5);
// enable LCD
lcd_write_byte(0x0c);
// set entry mode
lcd_write_byte(0x06);
// set cursor/display shift
lcd_write_byte(0x14);
// clear and home
lcd_clear_and_home();
}
int lcd_putchar(char c, FILE *stream) {
lcd_write_data(c);
return 0;
}
Maybe you can use this little tutorial for your Nerdkit Projects.
Just a little hint:
You can only save a Maximum of 8 custom characters in the HD44780 display.
But of course you can overwrite and shift them anytime, so really can use a lot of custom characters and moving animations.
Have fun with your own Pixel Art on your Nerdkit.