GPIO / GLCD

In this example we will create a very basic library to drive the NHD-2.4-240320SF-CTXL#-FTN1 which uses the ILI9341 Controller.

Using the following schematic that only shows the connections from ATSAM4S to the LCD as well as power to the LCD. Please note that the typical source voltage for the LCD, LCD IO and backlight are varying voltages, but they are all 3.3v max.



Here are some quick stats, a run down on the pinout for the LCD and how it works.

  • 2.4" diagonal
  • 240x320 pixels (portrait)
  • white LED backlight
  • RGB 5-6-5
  • 4 wire resistive touch panel
  • ILI9341 Controller
  • 8bit or 16bit parallel interface
Pin # Name Description
1 GND
2 YD Touch panel: down
3 XL Touch panel: left
4 YU Touch panel: up
5 XR Touch panel: right
6 NC
7 VDD min: 2.3v | typ: 2.8 | max:3.3v
8 IOVDD min: 1.65v | typ: 2.8 | max:3.3v
9 NC
10 CS Chip select - active low, can tie to ground
11 DC Data(1) or Command(0)
12 WR active low write signal
13 RD active low read signal
14 DB0 Bidirection data bus

8 bit: use DB8-DB15
16 bit: use DB0-DB15
15 DB1
16 DB2
17 DB3
18 DB4
19 DB5
20 DB6
21 DB7
22 DB8
23 DB9
24 DB10
25 DB11
26 DB12
27 DB13
28 DB14
29 DB15
30 RESET Resets the LCD - active low
31 IM0 16 bit: 0, 8bit: 1
32 NC
33 GND
34 LED-K1
35 LED-K2
36 LED-K3
37 LED-K4
38 LED-A Backlight anode: 3.2v
39 GND
40 NC

We will use an 8 bit data bus, so that means we will use DB8-DB15 and set IM0 to vcc.
To send a command to the LCD you set your data pins with the command you want to send then set the write pin low and set the write pin high again.
To send data to the LCD you set your data pins with the data you want to send then set the write pin low and set the write pin high again.
A list of commands can be found in the controller datasheet starting on page 83. A lot of this datasheet can be ignored. We will be using instructions for 16bit per pixel RGB 5-6-5 - 8bit data bus for 8080I found on page 65. To send pixel data to the LCD first you set your column address start and end and page address start and end. This gives us a box to start writing pixel data to. Then once you have your area you send the command to start writing to video memory and then start writing bytes of data to colourize the pixels.

  1. Set lcd pin 11 (data or command) to 0 to accept a command
  2. Set the 8 bit port (LCD DB8-DB15) to the value of the command from the datasheet
  3. Set pin 12 (write) to low, make sure it's low long enough for the LCD to read it (a few ns: see datasheet), set pin 12 high again
  4. Set LCD pin 11 (data or command) to 1 to accept data
  5. In 8 bit mode, set LCD pins DB8-DB15 to the first byte of the two byte data transfer (R,R,R,R,R,G,G,G)
  6. Set pin 12 (write) to low, make sure it's low long enough for the LCD to read it (a few ns: see datasheet), set pin 12 high again
  7. Set the last databyte (G,G,G,B,B,B,B,B)
  8. Set pin 12 (write) to low, make sure it's low long enough for the LCD to read it (a few ns: see datasheet), set pin 12 high again
  9. Repeat from step 5 until you've written all the bytes bounded by the page and column address you set - otherwise the pixels will start writing from the beginning of that box.

Here is an image from the datasheet that shows the command to start writing video memory and bytes in the 5-6-5 format.


Yellow: CMD (start writing memory)
Red: 5 bits for amount of red in a pixel
Green: 6 bits for amount of green in a pixel
Blue: 5 bit for amount of blue in a pixel

The following program is an example of setting up the LCD and then drawing a red screen, green screen and then a blue screen.

main.c


#include "sam.h"
#include "basic_uart.h"
#include "delay.h"
#include "glcd.h"

void clock_init(){
    REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCXTEN;
    while (!(REG_PMC_SR & PMC_SR_MOSCXTS));
    REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCSEL;
    REG_PMC_MCKR |= MC_MCKR_CSS_MAIN_CLK;
    while (!(REG_PMC_SR & PMC_SR_MCKRDY));
    REG_PMC_MCKR |= PMC_MCKR_PRES_CLK_1;
    while (!(REG_PMC_SR & PMC_SR_MCKRDY));
}

int main(void)
{
    /* Initialize the SAM system */
    SystemInit();
	clock_init();
	UART_Init();	
	timerInit();
	printString("wait");
	delay_ms(1000);
	printString("done");
	init_LCD();	
	
    /* Replace with your application code */
	drawTestPattern();
	printString("draw test");
    while (1) 
    {
		
			
    }
}

glcd.c

#include "sam.h"
#include "delay.h"
#include "glcd.h"
#include "basic_uart.h"

void init_LCD(void){
	//----SETUP PINS----//
	//set pins controllable by PIO controller
	REG_PIOA_PER |= (dc_pin|rd_pin|wr_pin|res_pin|db8_pin|db9_pin|db10_pin|db11_pin);
	REG_PIOB_PER |= (db12_pin|db13_pin|db14_pin|db15_pin);
	//set pins as output
	REG_PIOA_OER |= (dc_pin|rd_pin|wr_pin|res_pin|db8_pin|db9_pin|db10_pin|db11_pin);
	REG_PIOB_OER |= (db12_pin|db13_pin|db14_pin|db15_pin);
	//set read/write/reset pins high since they are active low
	REG_PIOA_SODR |= (rd_pin|wr_pin|res_pin);
	//set REG_PIOA/B_OWER so that is a write mask for pins
	REG_PIOA_OWER |= (db8_pin|db9_pin|db10_pin|db11_pin);
	REG_PIOB_OWER |= (db12_pin|db13_pin|db14_pin|db15_pin);
	
	//----SETUP LCD----//
	REG_PIOA_SODR |= rd_pin;
	REG_PIOA_CODR |= wr_pin;
	LCD_reset();
	
	SendCommand(0x28); //display off
	SendCommand(0x11); //sleep out
	//power control A start up
	SendCommand(0xCB); // vcore 1.6, vbc 5.6v
	SendCommandData(0x39);
	SendCommandData(0x2C);
	SendCommandData(0x00);
	SendCommandData(0x34);  
	SendCommandData(0x02);
	//power control B start up
	SendCommand(0xCF);  //power control B
	SendCommandData(0x00);
	SendCommandData(0x81);
	SendCommandData(0x30);
  
	SendCommand(0xC0); 
	SendCommandData(0x26);  //power control 1
	SendCommandData(0x04); //second parameter for ILI9340 (ignored by ILI9341)
  
	SendCommand(0xC1); 
	SendCommandData(0x11);  //power control 2
  
	SendCommand(0xC5); 
	SendCommandData(0x35); 
	SendCommandData(0x3E);  //VCOM control 1
  
	SendCommand(0x36); 
	SendCommandData(0x88);  //memory access control = BGR
  
	SendCommand(0xB1); 
	SendCommandData(0x00); 
	SendCommandData(0x18);  //frame rate control
  
	SendCommand(0xB6); 
	SendCommandData(0x0A); 
	SendCommandData(0xA2);  //display function control
  
	SendCommand(0xC7); 
	SendCommandData(0xBE); //VCOM control 2
  
	SendCommand(0x3A); 
	SendCommandData(0x55); //pixel format = 16 bit per pixel
  
  /*SendCommand(0xE0); 
  SendData(0x1F); //positive gamma correction
  SendData(0x1B);
  SendData(0x18);
  SendData(0x0B);
  SendData(0x0F);
  SendData(0x09);
  SendData(0x46);
  SendData(0xB5);
  SendData(0x37);
  SendData(0x0A);
  SendData(0x0C);
  SendData(0x07);
  SendData(0x07);
  SendData(0x05);
  SendData(0x00);
  
  SendCommand(0xE1); 
  SendData(0x00);    //negative gamma correction
  SendData(0x24);
  SendData(0x27);
  SendData(0x04);
  SendData(0x10);
  SendData(0x06);
  SendData(0x39);
  SendData(0x74);
  SendData(0x48);
  SendData(0x05);
  SendData(0x13);
  SendData(0x38);
  SendData(0x38);
  SendData(0x3A);
  SendData(0x1F);*/
  
  SendCommand(0xF2);//3g damma control
  SendCommandData(0x02);   //off
  
  SendCommand(0x26);     //gamma curve 3
  SendCommandData(0x01);
  
  SendCommand(0x2A); 
  SendCommandData(0x00);   //column address set
  SendCommandData(0x00);  //start 0x0000
  SendCommandData(0x00);
  SendCommandData(0xEF);  //end 0x00EF
  
  SendCommand(0x2B); 
  SendCommandData(0x00);  /page address set
  SendCommandData(0x00);   //start 0x0000
  SendCommandData(0x01);
  SendCommandData(0x3F);   //end 0x003F
  
  SendCommand(0x29);  //display ON

  //delay_ms(1000);
}

void readStrobe(void){
	REG_PIOA_CODR |= rd_pin;
	//create delay if needed
	REG_PIOA_SODR |= rd_pin;
}

void writeStrobe(void){
	REG_PIOA_CODR |= wr_pin;
	//create delay if needed
	REG_PIOA_SODR |= wr_pin;
}

void dataOrCmd(uint8_t data){ 
	//0:cmd, 1:data
	if (data == 0){
		REG_PIOA_CODR |= dc_pin;
	}
	else if (data == 1){
		REG_PIOA_SODR |= dc_pin;
	}
}

void SendCommand(uint8_t databyte){
	dataOrCmd(0); //cmd 
	uint8_t upperNibble = databyte >> 4;
	uint8_t lowerNibble = databyte & 0x0f;
	//there is a write mask on these registers, so we can use = instead of |= and masking for clear or set
	REG_PIOA_ODSR = upperNibble << 15;
	REG_PIOB_ODSR = lowerNibble;
	writeStrobe();
	/*unsigned int mask;

	for (mask = 0x80; mask != 0; mask >>= 1) {
		if (databyte & mask) {
			// bit is 1			
		}
		else {
			// bit is 0
		}
	}*/
	
}



void SendCommandData(uint8_t databyte){
	dataOrCmd(1); //data	
	uint8_t upperNibble = databyte >> 4;
	uint8_t lowerNibble = databyte & 0xf;
	//there is a write mask on these registers, so we can use = instead of |= and masking for clear or set
	REG_PIOA_ODSR = upperNibble << 15;
	REG_PIOB_ODSR = lowerNibble;
	writeStrobe();
}

void SendData(uint8_t databyte){
	dataOrCmd(1); //data
	uint8_t upperNibble = databyte >> 4;
	uint8_t lowerNibble = databyte & 0xf;
	//there is a write mask on these registers, so we can use = instead of |= and masking for clear or set
	REG_PIOA_ODSR = upperNibble << 15;
	REG_PIOB_ODSR = lowerNibble;
	writeStrobe();
}

void PageAddressSet(uint16_t startrow, uint16_t endrow){
	SendCommand(0x2B);
	uint8_t parameter1 = (startrow >> 8);
	uint8_t parameter2 = startrow; 
	uint8_t parameter3 = endrow >> 8;
	uint8_t parameter4 = endrow;
	SendCommandData(parameter1);
	SendCommandData(parameter2);
	SendCommandData(parameter3);
	SendCommandData(parameter4);
}
void ColumnAddressSet(uint16_t startcolumn, uint16_t endcolumn){
	SendCommand(0x2A);
	uint8_t parameter1 = (startcolumn >> 8);
	uint8_t parameter2 = startcolumn; //(should take low byte?)
	uint8_t parameter3 = endcolumn >> 8;
	uint8_t parameter4 = endcolumn;
	SendCommandDataparameter1);
	SendCommandData(parameter2);
	SendCommandData(parameter3);
	SendCommandData(parameter4);
}
void MemoryWriteStart(){
	SendCommand(0x2C);
}

void DrawDot(uint16_t x,uint16_t y,uint16_t colour){//colour is in RGB 5-6-5 format
	uint8_t MSB;
	uint8_t LSB;
	PageAddressSet(y,y+1);
	ColumnAddressSet(x,x+1);
	MemoryWriteStart();	
	MSB = colour >> 8;
	LSB = colour & 0xff;
	SendData(MSB);
	SendData(LSB);
}

void LCD_reset(){
	REG_PIOA_CODR |= res_pin;
	delay_ms(250);			
	REG_PIOA_SODR |= res_pin;		
	delay_ms(250);
}

void drawTestPattern(){
	//drawscreen a solid colour
	SendCommand(0x2C);
	//red
	for (uint16_t y=0;y<320; y+=1){
		for (uint16_t x=0;x<240;x+=1){
			SendData(0xf0);
			SendData(0x0e);
		}
	}
	
	for (uint16_t y=0;y<320; y+=1){
		for (uint16_t x=0;x<240;x+=1){
			SendData(0x07);
			SendData(0xe0);
		}
	}
	
	for (uint16_t y=0;y<320; y+=1){
		for (uint16_t x=0;x<240;x+=1){
			SendData(0x00);
			SendData(0x1f);
		}
	}
	
}

/*
void init_LCD(void);
void SendCommand(uint8_t databyte);
void SendCommandData(uint8_t databyte);
void SendData(uint8_t data_MSB,uint8_t data_LSB);

void PageAddressSet(uint16_t startrow, uint16_t endrow);
void ColumnAddressSet(>uint16_t startcolumn, uint16_t endcolumn);
void MemoryWriteStart();
void MemoryWrite(uint16_t memorybytes);



uint8_t fontSize;
void PutChar(uint16_t x, uint16_t y, uint8_t character);
uint8_t GetData(void);
void Display(void); //draw background colour

void PrintNumber(uint16_t x,uint16_t y,uint8_t number,uint16_t colour);
*/

glcd.h

/* USED PINS ON ATSAM4S
	DC		PA20
	WR		PA23
	RD		PA22
	RES		PA19
	
	DB0		
	DB1		
	DB2		
	DB3		
	DB4		
	DB5		
	DB6		
	DB7		
	DB8		PA15
	DB9		PA16
	DB10	PA17
	DB11	PA18
	DB12	PB0
	DB13	PB1
	DB14	PB2
	DB15	PB3
	
	IM0		1 (8bit parallel databus)
	CS		GND
	
*/
//pins are PIOA
#define dc_pin		PIO_PER_P20
#define wr_pin		PIO_PER_P23
#define rd_pin		PIO_PER_P22
#define res_pin		PIO_PER_P19
//lower nibble is PIOA
#define db8_pin		PIO_PER_P15
#define db9_pin		PIO_PER_P16
#define db10_pin	PIO_PER_P17
#define db11_pin	PIO_PER_P18
//upper nibble is PIOB
#define db12_pin	PIO_PER_P0
#define db13_pin	PIO_PER_P1
#define db14_pin	PIO_PER_P2
#define db15_pin	PIO_PER_P3


void init_LCD(void);
void SendCommand(int8_t databyte);
void SendCommandData(uint8_t databyte);
void SendData(uint8_t databyte);

void PageAddressSet(uint16_t startrow, uint16_t endrow);
void ColumnAddressSet(uint16_t startcolumn, uint16_t endcolumn);
void MemoryWriteStart();
void MemoryWrite(uint16_t memorybytes);

void LCD_reset();
void drawTestPattern();

uint8_t fontSize;
void PutChar(uint16_t x, uint16_t y, uint8_t character);
uint8_t GetData(void);
void Display(void); //draw background colour
void DrawDot(uint16_t x,uint16_t y,uint16_t colour);
voidPrintNumber(uint16_t x,uint16_t y,uint8_t number,uint16_t colour);

delay.c

#include "sam.h"
#include "basic_uart.h"
#include "delay.h"

//recalculate REG_TC0_RC0 value if clock changes

volatile uint32_t counter = 0; // global variable for delay

void timerInit(){
	//Setup for TC0 - ID 23,
	
	//enable interrupts in NVIC for TC0
	NVIC_EnableIRQ(TC0_IRQn);
	
	//PMC setup
	REG_PMC_PCER0 |= PMC_PCER0_PID23; 	//enable peripheral clock	for timer counter channel0
	
	//Interrupt Setup
	REG_TC0_CMR0 |= TC_CMR_TCCLKS_TIMER_CLOCK1; //mainclock div 2
	//REG_TC0_IER0 |= TC_IER_COVFS;//enable couter overflow interrupt
	REG_TC0_IER0 |= TC_IER_CPCS; //enable RC compare interrupt
	REG_TC0_CMR0 |= TC_CMR_CPCTRG; //compare resets counter and clock
	REG_TC0_CCR0 |= TC_CCR_CLKEN; //enable tc clock
	
	//PIO setup (not neccessary) because we won't use the pins
	
}

void delay_ms(uint32_t delayInMs){
	
	REG_TC0_RC0 = 10000; //1 second / 10mhz = 1ms
	//enable tc clock
	REG_TC0_CCR0 |= TC_CCR_CLKEN;
	//start timer
	REG_TC0_CCR0 |= TC_CCR_SWTRG;

	while (counter <= delayInMs){
		//printWord(counter);
		//printString("\r\n");
	}

	//disable tc clock
	REG_TC0_CCR0 |=TC_CCR_CLKDIS;
	//reset counter
	counter = 0;
}

void blockingdelay(uint32_t counts){
	volatile uint32_t x;
	for (x=0; x < counts; x++){
		
	}
}

void TC0_Handler(void){
	
	uint32_t status = REG_TC0_SR0; //read status register - this clears interrupt flags
	if ((status & TC_SR_CPCS)>=1){
		//increment counter
		counter+=1;
	}
	
}

delay.h


void timerInit();
void delay_ms(uint32_t delayInMs);
void blockingdelay(uint32_t counts);

basic_uart.c


#include "sam.h"
#include "basic_uart.h"

void UART_Init(){	
	NVIC_EnableIRQ(UART0_IRQn);
	
	//configure PIO controller A  - disable means enable peripheral on pins		
	REG_PIOA_PDR |= PIO_PDR_P9; //disable PIOA control of PA9 and enable peripheral on pin
	REG_PIOA_PDR |= PIO_PDR_P10; //disable PIOA control of PA9 and enable peripheral on pin
	REG_PIOA_ABCDSR &=  ~(PIO_ABCDSR_P9);
	REG_PIOA_ABCDSR &=  ~(PIO_ABCDSR_P10);	
		
	//configure PMC UART Clock	
	REG_PMC_PCER0 |= PMC_PCER0_PID8; //enable UART0 clock
		
	//configure buad rate
	REG_UART0_BRGR |= 130;
	//fcpu/16xBR 20,000,000/(16x9600)	
		
	//parity
	REG_UART0_MR |= UART_MR_PAR_NO;
		
	//mode
	//normal mode default
	
	//enable transmit/receive
	REG_UART0_CR |= UART_CR_TXEN;
	REG_UART0_CR |= UART_CR_RXEN;
	
	//enable interrupt on receive
	REG_UART0_IER |= UART_IER_RXRDY;
	
	
}

void transmitByte(uint8_t data){
	//wait for ready
	while (!(REG_UART0_SR & UART_SR_TXRDY));
	while (!(REG_UART0_SR & UART_SR_TXEMPTY));
	REG_UART0_THR |= data;	
}

void printString(const char myString[]) {
	uint8_t i = 0;
	while (myString[i]) {
		transmitByte(myString[i]);
		i++;
	}
}

void printByte(uint8_t byte) {
	transmitByte('0' + (byte / 100));                   
	transmitByte('0' + ((byte / 10) % 10));             
	transmitByte('0' + (byte % 10));                    
}

void printWord(uint16_t word) {
	
	
	transmitByte('0' + (word / 10000));        
	transmitByte('0' + ((word / 1000) % 10));  
	transmitByte('0' + ((word / 100) % 10));   
	transmitByte('0' + ((word / 10) % 10));    
	transmitByte('0' + (word % 10));           
}



void printBinaryByte(uint8_t byte) {
	/* Prints out a byte as a series of 1's and 0's */
	uint8_t bit;
	for (bit = 7; bit < 255; bit--) {
		//if (bit_is_set(byte, bit))
		if (((byte & 1<= 1)){
		transmitByte('1');
		}
		else{
		transmitByte('0');
		}
	}
}

char nibbleToHexCharacter(uint8_t nibble) {
	/* Converts 4 bits into hexadecimal */
	if (nibble < 10) {
		return ('0' + nibble);
	}
	else {
		return ('A' + nibble - 10);
	}
}

void printHexByte(uint8_t byte) {
	/* Prints a byte as its hexadecimal equivalent */
	uint8_t nibble;
	nibble = (byte & 0b11110000) >> 4;
	transmitByte(nibbleToHexCharacter(nibble));
	nibble = byte & 0b00001111;
	transmitByte(nibbleToHexCharacter(nibble));
}

void UART0_Handler( void) {
	// when we receive a byte, transmit that byte back
	uint32_t status = REG_UART0_SR;
	if ((status & UART_SR_RXRDY)){
		//read receive holding register
		uint8_t readByte = REG_UART0_RHR;
		//transmit that byte back
		transmitByte(readByte);
	}
}

basic_uart.h


void UART_Init();

void transmitByte(uint8_t data);

void printString(const char myString[]);

void printByte(uint8_t byte);

void printWord(uint16_t word);


void printBinaryByte(uint8_t byte);

char nibbleToHexCharacter(uint8_t nibble);

void printHexByte(uint8_t byte);

void UART0_Handler( void);