I had some SP14Q002 lcds for long time now, but it was not possible to make them work.
The reason was that they need a controller like SED1335 for the interface with a microcontroller.
Because the lcd doesn't have any controller you constantly need to refresh it every
13.2ms or 75Hz in order to avoid flickering.
So I start trying to drive the lcd with mbed.
My first try was using the mbed i/o library but the refresh rate was to slow.
Then, I tried accessing the gpio registers direct and I improved the refresh rate. But
still it was not possible to have the 75Hz refresh rate that the lcd needs for not flickering.
Also with the above approach the cpu load is high!
I start looking for a DMA approach in order to reduce the mbed load and also to increase the
refresh rate.
Finally I find out the following post "DMA and fast GPIO" from Burt Hashizume
http://mbed.org/forum/mbed/topic/1138/
With this approach you can get 60ns clock that was super for my application.
So I setup a ticker every 55us for sending 1 line via DMA at a time in order to
have a full lcd refresh every 13.2ms or 75Hz refresh rate.
I have a line update in less than 20us via DMA and a lot of cpu power to do other tasks.
#include "mbed.h"
#include "font.h"
#include "Font16x24.h"
#include <stdio.h>
#include <stdarg.h>
///////////////////////////////////////////////////////////////////////////////
#define DMA_CHANNEL_ENABLE 1
#define DMA_TRANSFER_TYPE_M2M (0UL << 11)
#define DMA_CHANNEL_TCIE (1UL << 31)
#define DMA_CHANNEL_SRC_INC (1UL << 26)
#define DMA_MASK_IE (1UL << 14)
#define DMA_MASK_ITC (1UL << 15)
DigitalOut myled1(LED1);
DigitalOut myled2(LED2);
DigitalOut myled3(LED3);
DigitalOut myled4(LED4);
DigitalOut frame(p26);
DigitalOut ndispoff(p25);
// p9 - P0.0 - CP
// p10 - P0.1 - LOAD
// p30 - P0.4 - D0
// p29 - P0.5 - D1
// p8 - P0.6 - D2
// p7 - P0.7 - D3
Ticker ticker; // irq to prepare pattern for sending one line via DMA
char buffer[500]; // buffer for the line timing pattern
////////////////////////////////////////////////////////////////////////////////
#define LCD_WIDTH 320
#define LCD_HEIGTH 240
char Mem[LCD_WIDTH * LCD_HEIGTH / 8]; // lcd shadow memory
///////////////////////////////////////////////////////////////////////////////
// clear the lcd
void LCDClear(void)
{
int i;
for (i = 0; i < LCD_WIDTH * LCD_HEIGTH / 8; i++)
Mem[i] = 0;
}
////////////////////////////////////////////////////////////////////////////////
// set one pixel (x:0-319, y:0-239)
void SetPixel(int x, int y)
{
int i;
i = y * LCD_WIDTH / 8 + (x / 8);
Mem[i] |= 1 << (8 - (x % 8));
}
////////////////////////////////////////////////////////////////////////////////
// clear one pixel (x:0-319, y:0-239)
void ClearPixel(int x, int y)
{
int i;
i = y * LCD_WIDTH / 8 + (x / 8);
Mem[i] &= ~(1 << (8 - (x % 8)));
}
////////////////////////////////////////////////////////////////////////////////
// display one character 8x8 font on lcd (x:0-39, y:0-29)
void LCD_char(int x, int y, char n)
{
int j, i;
i = y * LCD_WIDTH + x;
for (j = 0; j < 8; j++)
{
Mem[i] = FONT8x8F[n-32][j];
i += LCD_WIDTH / 8;
}
}
////////////////////////////////////////////////////////////////////////////////
// display a string (8x8 font) on the lcd (x:0-39, y:0-29)
void LCD_string(int x, int y, char * str)
{
while(*str)
{
LCD_char(x++, y, *str);
str++;
}
}
////////////////////////////////////////////////////////////////////////////////
char str5[80];
// display a string (8x8 font) via printf on the lcd (x:0-39, y:0-29)
void lcd_printf(int x, int y, char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsprintf(str5, fmt, args);
LCD_string(x, y, str5);
va_end(args);
}
////////////////////////////////////////////////////////////////////////////////
// display one character 16x24 font on lcd (x:0-19, y:0-9)
void LCD_char16x24(int x, int y, char n)
{
unsigned int j, i, tmp;
i = y * LCD_WIDTH * 3 + 2 * x;
for (j = 0; j < 24; j++)
{
tmp = Font_16x24[(n - 32) * 24 + j];
Mem[i] = (char)((tmp & 0xFF00) >> 8);
Mem[i + 1] = (char)(tmp & 0x00FF);
i += LCD_WIDTH / 8;
}
}
////////////////////////////////////////////////////////////////////////////////
// display a string (16x24 font) on the lcd (x:0-19, y:0-9)
void LCD_string16x24(int x, int y, char * str)
{
while(*str)
{
LCD_char16x24(x++, y, *str);
str++;
}
}
////////////////////////////////////////////////////////////////////////////////
// display a string (16x24 font) via printf on the lcd (x:0-19, y:0-9)
void lcd_printf16x24(int x, int y, char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsprintf(str5, fmt, args);
LCD_string16x24(x, y, str5);
va_end(args);
}
///////////////////////////////////////////////////////////////////////////////
// DMA start setup
// copy all buffer pattern (4*80+1 bytes) to the Port0 (byte)
void DMAStart(void)
{
myled2 = 0;
myled3 = 0;
myled4 = 0;
// Prep Channel0 to send the bytes of the buffer to the P0.
LPC_GPDMACH0->DMACCSrcAddr = (uint32_t)buffer;
LPC_GPDMACH0->DMACCDestAddr = (uint32_t)&LPC_GPIO0->FIOPIN;
LPC_GPDMACH0->DMACCLLI = 0;
LPC_GPDMACH0->DMACCControl = DMA_CHANNEL_TCIE | DMA_CHANNEL_SRC_INC | (4 * 80 + 1);
myled2 = 1;
// Fire GPDMA Channel0
LPC_GPDMACH0->DMACCConfig = DMA_CHANNEL_ENABLE | DMA_TRANSFER_TYPE_M2M | DMA_MASK_IE | DMA_MASK_ITC;
}
///////////////////////////////////////////////////////////////////////////////
// prepare the line timing pattern to send via DMA
void PreparePattern(char *buf)
{
char tmp;
int i;
for (i = 0; i < 8 * 39; i += 8)
{
tmp = *buf & 0xF0;
buffer[i] = tmp + 1; // cp set
buffer[i+1] = tmp + 1; // wait for data setup
buffer[i+2] = tmp; // cp low shift data
buffer[i+3] = tmp; // cp low shift data (just a delay)
tmp = *buf & 0x0F;
tmp = tmp << 4; // shift data to P0.4-P0.7
buffer[i+4] = tmp + 1; // cp set
buffer[i+5] = tmp + 1; // wait for data setup
buffer[i+6] = tmp; // cp low shift data
buffer[i+7] = tmp; // cp low shift data (just a delay)
buf++;
}
tmp = *buf & 0xF0;
buffer[i] = tmp + 1; // cp set
buffer[i+1] = tmp + 1; // wait for data setup
buffer[i+2] = tmp; // cp low shift data
buffer[i+3] = tmp; // cp low shift data (just a delay)
tmp = *buf & 0x0F;
tmp = tmp << 4; // shift data to P0.4-P0.7
buffer[i+4] = tmp + 3; // cp set, load set
buffer[i+5] = tmp + 3; // wait for data setup
buffer[i+6] = tmp + 2; // cp low shift data, load set
buffer[i+7] = tmp + 2; // cp low shift data, load set (just a delay)
buffer[i+8] = tmp; // load line frame
}
///////////////////////////////////////////////////////////////////////////////
long ticker_cntr;
// irq every 55us for preparing the pattern and firing the DMA to start the transfer
void ticker_ISR(void)
{
ticker_cntr++; // line counter
if (ticker_cntr >= LCD_HEIGTH)
{
ticker_cntr = 0;
frame = 1; // first line start
}
myled1 = 1;
PreparePattern(&Mem[ticker_cntr * LCD_WIDTH / 8]);
DMAStart();
myled1 = 0;
}
///////////////////////////////////////////////////////////////////////////////
// DMA_IRQHandler on the end of the dma transfer or on error
extern "C" void DMA_IRQHandler(void) __irq
{
frame = 0; // first line end
myled2 = 1;
if (LPC_GPDMA->DMACIntStat & 1)
{
if (LPC_GPDMA->DMACIntTCStat & 1)
{
myled3 = 1;
LPC_GPDMA->DMACIntTCClear = 1;
}
if (LPC_GPDMA->DMACIntErrStat & 1)
{
myled4 = 1;
LPC_GPDMA->DMACIntErrClr = 1;
}
}
}
///////////////////////////////////////////////////////////////////////////////
// get a normalized sine value
int getNormalizedSine(int x, int halfY, int Amplitude, int maxX)
{
float piDouble = 2 * 3.14;
float factor = piDouble / maxX;
int ret;
ret = sin(x * factor) * Amplitude + halfY;
return (ret);
}
///////////////////////////////////////////////////////////////////////////////
int main()
{
int cntr1, cntr2, i, j, ampl;
myled1 = 0;
myled2 = 0;
myled3 = 0;
myled4 = 0;
ndispoff = 1;
frame = 1; // first line start
cntr1 = 0;
cntr2 = 0;
ampl = 0;
LCDClear();
ticker.attach(ticker_ISR, 0.000055); // shift 1 line every 55us ~ 75Hz refresh rate for the complete lcd 320x240
// set outputs for P0.0, P0.1, P0.4, P0.5, P0.6, P0.7 (used from DMA transfer)
LPC_GPIO0->FIODIR |= (1UL << 0) + (1UL << 1) + (1UL << 4) + (1UL << 5) + (1UL << 6) + (1UL << 7);
// set mask only for the outputs
LPC_GPIO0->FIOMASK = ~((1UL << 0) + (1UL << 1) + (1UL << 4) + (1UL << 5) + (1UL << 6) + (1UL << 7));
// Power up the GPDMA.
LPC_SC->PCONP |= (1UL << 29);
LPC_GPDMA->DMACConfig = 1;
LPC_GPDMA->DMACIntTCClear = 0x1;
NVIC_EnableIRQ(DMA_IRQn);
DMAStart();
while(1)
{
// LCDClear();
cntr1++;
if (cntr1 % 50 == 0)
cntr2++;
///////// fill all lcd with the same character
for (j = 0; j < 10; j++)
{
for (i = 0; i < 20; i++)
LCD_char16x24(i, j, (cntr2 & 0x1f) + 'A');
}
///////// display the cntr1
lcd_printf(5, 1, " %d ", cntr1);
lcd_printf16x24(5, 1, " %d ", cntr1);
///////// display a sine wave
if (cntr1 % LCD_HEIGTH < LCD_HEIGTH / 2)
ampl++;
else
ampl--;
for (i = 0; i < LCD_WIDTH; i++)
{
j = getNormalizedSine(i, LCD_HEIGTH / 2, ampl, LCD_WIDTH);
SetPixel(i, j - 3);
SetPixel(i, j - 2);
SetPixel(i, j - 1);
SetPixel(i, j);
SetPixel(i, j + 1);
SetPixel(i, j + 2);
SetPixel(i, j + 3);
}
wait(0.01);
}
}
///////////////////////////////////////////////////////////////////////////////
I had some SP14Q002 lcds for long time now, but it was not possible to make them work. The reason was that they need a controller like SED1335 for the interface with a microcontroller.
Because the lcd doesn't have any controller you constantly need to refresh it every 13.2ms or 75Hz in order to avoid flickering.
So I start trying to drive the lcd with mbed. My first try was using the mbed i/o library but the refresh rate was to slow. Then, I tried accessing the gpio registers direct and I improved the refresh rate. But still it was not possible to have the 75Hz refresh rate that the lcd needs for not flickering. Also with the above approach the cpu load is high!
I start looking for a DMA approach in order to reduce the mbed load and also to increase the refresh rate. Finally I find out the following post "DMA and fast GPIO" from Burt Hashizume http://mbed.org/forum/mbed/topic/1138/
With this approach you can get 60ns clock that was super for my application.
So here is my implementation:
The interface for this lcd is the following:
The timing requirements are:
So I setup a ticker every 55us for sending 1 line via DMA at a time in order to have a full lcd refresh every 13.2ms or 75Hz refresh rate.
I have a line update in less than 20us via DMA and a lot of cpu power to do other tasks.
Here is a small demo that I made:
And here is the code: