Richard Parker / EALCD

screen/EALCD.cpp

Committer:
richardparker
Date:
2010-02-11
Revision:
0:839ecbf5cb2a
Child:
1:f04bcaea1d60

File content as of revision 0:839ecbf5cb2a:

// Copyright 2010 Richard Parker

#include "mbed.h"
#include "EALCD.h"

// Register addresses.
#define EALCD_DEVICE_CODE_ADDRESS        0x00
#define EALCD_TOUCH_FREQ                 10000
#define EALCD_LCD_FREQ                   20000000

EALCD::EALCD(   PinName LED_PWM,
                PinName LCD_RS,
                PinName LCD_SO,
                PinName LCD_SI,
                PinName LCD_SCL,
                PinName LCD_CS,
                PinName TCH_CS)
:   _backlight(LED_PWM),
    _comm(LCD_SI, LCD_SO, LCD_SCL),
    _rs(LCD_RS),
    _cs(LCD_CS),
    _tch_cs(TCH_CS)
{
    // Make sure not selecting either chip.
    _cs = 1;
    _tch_cs = 1;
    
    // Set the frequency of the PWM backlight to have a period ~400us.
    _backlight.period_us(400);
    // By default 40% on.
    setBrightness(0.4);
    
    // Set up comms, 8 bit interface.
    _comm.format(8);
    _comm.frequency(EALCD_LCD_FREQ);   
    
    // Now actual initialise the connection.
    _initialise();
}

EALCD::~EALCD()
{
}

void EALCD::setBrightness(float percentage)
{
    // Check the value is within the correct range.
    if (percentage < 0)
    {
        percentage = 0;
    } 
    else if (percentage > 1)
    {
        percentage = 1;
    }
    
    _backlight = 1-percentage;
}

void EALCD::_writeToDisplay(unsigned short data)
{ 
    // Set RS which is connected to DC and so when high is to data.
    _rs = 1;  
    // Activate the chip (active low).
    _cs = 0;
    
    // Write the msb data.
    _comm.write(data >> 8);
    // Write the lsb data.
    _comm.write(data & 0xff);
    
    // Deactivate the chip (active low).
    _cs = 1;   
}

void EALCD::_writeToRegister(unsigned short addr, unsigned short data)
{ 
    // Command phase.
    // Set RS which is connected to DC and so when low is to command.
    _rs = 0;  
    // Activate the chip (active low).
    _cs = 0;
    
    // Write the msb address.
    _comm.write(addr >> 8);
    // Write the lsb address.
    _comm.write(addr & 0xff);
    
    // Deactivate the chip (active low).
    _cs = 1;
    
    //  Data phase.
    // Set RS which is connected to DC and so when high is data.
    _rs = 1;  
    // Activate the chip (active low).
    _cs = 0;
    
    // Write the msb data.
    _comm.write(data >> 8);
    // Write the lsb data.
    _comm.write(data & 0xff);
    
    // Deactivate the chip (active low).
    _cs = 1;
    
    // Restore index to GRAM by accessing GRAM register.
    // Set RS which is connected to DC and so when low is to command.
    _rs = 0;  
    // Activate the chip (active low).
    _cs = 0;
    
    // Write the msb address.
    _comm.write(0x00);
    // Write the lsb address.
    _comm.write(0x22);
    
    // Deactivate the chip (active low).
    _cs = 1;
}

void EALCD::_initialise()
{    
    // Start up sequence. See page 72 of SSD1289 V1.3 datasheet for reasons.  
    _writeToRegister(0x07, 0x0021);
    // Oscillator on.
    _writeToRegister(0x00, 0x0001);
    _writeToRegister(0x07, 0x0723); 
    // Exit sleep mode
    _writeToRegister(0x10, 0x0000);
    // Pause 200ms (need at least 30ms).
    wait(0.2);
    _writeToRegister(0x07, 0x0033);
    // Setup screen orientation and colour mode etc.
    _writeToRegister(0x11, 0x6828);
    // LCD driving waveform.
    _writeToRegister(0x02, 0x0600);
    _writeToRegister(0x0f, 0x0000);
    // Driver output control.
    _writeToRegister(0x01, 0x2b3f);
    _writeToRegister(0x0b, 0x5308);
    // Set refresh rate to 70Hz.
    _writeToRegister(0x25, 0xa000);
  
}

void EALCD::_moveTo(short x, short y)
{
    // Limit movement.
    if (x < 0)
    {
        x = 0;
    }
    
    if (y < 0)
    {
        y = 0;
    }
    
    if (x >= width())
    {
        x = width()-1;
    }
    
    if (y >= height())
    {
        y = height()-1;
    }

    // Masking values to prevent too large.
    x = x & 0x1ff;
    _writeToRegister(0x4f, x);    
    
    // Mask to get down to a sensible number.
    y = y & 0xff;
    // -1 because pixel at top of screen is 239 (as 0 indexed therefore 240 high).
    y = height() - 1 - y;
    _writeToRegister(0x4e, y);
}

void EALCD::_window(short x, short y, unsigned short w, unsigned short h)
{
    // If start off screen then no need to even try drawing.  
    if ((x >= width()) || (y >= height()))
    {
        return;
    }
    
    // Limit to not go off screen.
    if ((x + w) > width())
    {
        w = width()-x;
    }

    if ((y + h) > height())
    {
        h = height()-y;
    }
    
    // Move x and y to correct location.
    if (x < 0)
    {
        x = 0;
    }
    
    if (y < 0)
    {
        y = 0;
    }
    
    y = height() - 1 - y;
    
    // Vertical.
    unsigned short hsa = y - h + 1;
    unsigned short hea = y;
    hea = hea << 8;
    
    _writeToRegister(0x44, hsa | hea);

    // Horizontal
    unsigned short vsa = x;
    unsigned short vea = x + w - 1;
    
    _writeToRegister(0x45, vsa);
    _writeToRegister(0x46, vea);
}

void EALCD::_getTouch(unsigned short& x, unsigned short& y, unsigned short& z1, unsigned short& z2)
{  
    // As about to change clock freq and so data is not valid for screen disable.
    _cs = 1;

    x = 0;
    y = 0;
    z1 = 0;
    z2 = 0;

    // Set comm frequency to access touch screen.
    _comm.frequency(EALCD_TOUCH_FREQ);

    // Read the x value.   
    // Activate the chip (active low).
    _tch_cs = 0;
    
    // 0x83 = Set start bit high, power mode is 2'b11 (always on).
    // 0x50 = Set address to 3'b101
    _comm.write(0x83 | 0x50 | 0x00);
    // Read two bytes in, the last 3 bits are uninteresting (12 bit reading).
    x = _comm.write(0x00) << 8;
    x = x | _comm.write(0x00);
    x = x << 1;
    x = x >> 4;
    
    // Deactivate the chip (active low).
    _tch_cs = 1;

    // Read the y value.   
    // Activate the chip (active low).
    _tch_cs = 0;
    
    // 0x83 = Set start bit high, power mode is 2'b11 (always on).
    // 0x50 = Set address to 3'b001
    _comm.write(0x83 | 0x10 | 0x00);
    // Read two bytes in, the last 3 bits are uninteresting (12 bit reading).
    y = _comm.write(0x00) << 8;
    y = y | _comm.write(0x00);
    y = y << 1;
    y = y >> 4;
    
    // Deactivate the chip (active low).
    _tch_cs = 1;

    // Read the z1 value.   
    // Activate the chip (active low).
    _tch_cs = 0;
    
    // 0x83 = Set start bit high, power mode is 2'b11 (always on).
    // 0x50 = Set address to 3'b011
    _comm.write(0x83 | 0x30 | 0x00);
    // Read two bytes in, the last 3 bits are uninteresting (12 bit reading).
    z1 = _comm.write(0x00) << 8;
    z1 = z1 | _comm.write(0x00);
    z1 = z1 << 1;
    z1 = z1 >> 4;
    
    // Deactivate the chip (active low).
    _tch_cs = 1;

    // Read the z2 value.   
    // Activate the chip (active low).
    _tch_cs = 0;
    
    // 0x83 = Set start bit high, power mode is 2'b11 (always on).
    // 0x50 = Set address to 3'b100
    _comm.write(0x83 | 0x40 | 0x00);
    // Read two bytes in, the last 3 bits are uninteresting (12 bit reading).
    z2 = _comm.write(0x00) << 8;
    z2 = z2 | _comm.write(0x00);
    z2 = z2 << 1;
    z2 = z2 >> 4;
    
    // Deactivate the chip (active low).
    _tch_cs = 1;

    // Set comm frequency to access lcd.
    _comm.frequency(EALCD_LCD_FREQ);  
}

void EALCD::_drawPoint(short x, short y, unsigned short c)
{
    // No need to draw if off screen.
    if ((x >= width()) 
        || 
        (y >= height())
        ||
        (x < 0)
        ||
        (y < 0))
    {
        return;
    }

    // Move to the position on the screen to place the pixel.
    _moveTo(x, y);
    
    // Draw the pixel with the current pen value.
    _writeToDisplay(c);
}

void EALCD::_swap(short& i, short& j)
{
    short temp = i;
    i = j;
    j = temp;
}

void EALCD::_draw4EllipsePoints(short cX, short cY, short x, short y, bool filled)
{
    if (filled == true)
    {
        // line in quadrant 2 to 1.
        _drawLine(cX-x, cY+y, cX+x, cY+y, _brush.color().rawValue());
        // Point in quadrant 3 to 4.
        _drawLine(cX-x, cY-y, cX+x, cY-y, _brush.color().rawValue());
    } else {
        // Point in quadrant 1.
        _drawPoint(cX+x, cY+y, _pen.color().rawValue()); 
        // Point in quadrant 2.
        _drawPoint(cX-x, cY+y, _pen.color().rawValue());
        // Point in quadrant 3.
        _drawPoint(cX-x, cY-y, _pen.color().rawValue());
        // Point in quadrant 4.
        _drawPoint(cX+x, cY-y, _pen.color().rawValue());
    }
}

void EALCD::_drawEllipse(short x, short y, unsigned short w, unsigned short h, bool filled)
{
    // Algorithm taken from http://homepage.smc.edu/kennedy_john/belipse.pdf
    short xRadius = w/2;
    short yRadius = h/2;
    short cX = x + xRadius;
    short cY = y + yRadius;
    
    int xChange = yRadius*yRadius*(1 - 2*xRadius);
    int yChange = xRadius*xRadius;
    int ellipseError = 0;
    int twoASquare = 2*xRadius*xRadius;
    int twoBSquare = 2*yRadius*yRadius;
    int stoppingX = twoBSquare*xRadius;
    int stoppingY = 0;
    
    x = xRadius;
    y = 0;

    while (stoppingX >= stoppingY)
    {
        // 1st set of points, y' > -1.
        _draw4EllipsePoints(cX, cY, x, y, filled);
        y += 1;
        stoppingY += twoASquare;        
        ellipseError += yChange;
        yChange += twoASquare;
        if ((2*ellipseError + yChange) > 0) 
        {
            x -= 1;
            stoppingX -= twoBSquare;
            ellipseError += xChange;
            xChange += twoBSquare;
        }
    }
    
    // 1st point set is done; start the 2nd set of points.
    x = 0;
    y = yRadius-1;
    xChange = yRadius*yRadius;
    yChange = xRadius*xRadius*(1 - 2*yRadius);    
    ellipseError = 0;
    stoppingX = 0;
    stoppingY = twoASquare*yRadius;
    
    while (stoppingX <= stoppingY) 
    {
        // 2nd set of points, y' < -1.
        _draw4EllipsePoints(cX, cY, x, y, filled);
        x += 1;
        stoppingX += twoBSquare;
        ellipseError += xChange;
        xChange += twoBSquare;
        
        if ((2*ellipseError + yChange) > 0)
        {
            y -= 1;
            stoppingY -= twoASquare;
            ellipseError += yChange;
            yChange += twoASquare;
        }
    }    
}

void EALCD::_drawLine(short x0, short y0, short x1, short y1, unsigned short c)
{
    // Check if can be done faster.
    if (y0 == y1)
    {
        // Not on screen so don't draw.
        if ((y0 >= height()) || (y0 < 0))
        {
            return;
        }
    
        // Horizontal line.
        unsigned short sx = (x0 < x1) ? x0 : x1;
        unsigned short length = abs(x1 - x0);
        
        // Make sure not going off edge of screen.
        if (sx+length > width())
        {
            length = width()-sx;
        }
        
        // Move to start of line and just draw.
        _moveTo(sx, y0);
        for (int i = 0; i < length; i++)
        {
            _writeToDisplay(c);
        }
        
        return;
    }

    // This code is from http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm - the fast section i.e.
    // relies on only integer maths.
    
    bool steep = (abs(y1 - y0) > abs(x1 - x0));
    
    if (steep == true) 
    {
         _swap(x0, y0);
         _swap(x1, y1);
    }
    
    if (x0 > x1)
    {
         _swap(x0, x1);
         _swap(y0, y1);
    }
    
    int deltaX = x1 - x0;
    int deltaY = abs(y1 - y0);
    int error = deltaX / 2;
    int yStep;
    int y = y0;
    int x = 0;
    
    if (y0 < y1)
    {
        yStep = 1;
    } else {
        yStep = -1;
    }
  
    // Now actually loop and draw the line.  
    for (x = x0; x < x1; x++)
    {
        // Channge drawing if drawing horizontal or vertical.
        if (steep == true)
        {
            _drawPoint(y, x, c);
        } else { 
            _drawPoint(x, y, c);
        }
        
        // Calculate the error to decide to step or not.
        error = error - deltaY;
 
        if (error < 0)
        {
            y = y + yStep;
            error = error + deltaX;
        } 
    }
}

void EALCD::clearScreen()
{
    // First move to the top left.
    _window(0, 0, width(), height());
    _moveTo(0, 0);

    // Now fill in relying on the addresses to be automatically updated.
    for(int i = 0; i < (width()*height()); i++)
    {
        _writeToDisplay(_brush.color().rawValue());
    }
}

void EALCD::drawPoint(short x, short y)
{
    // Draw the pixel with the current pen value.
    _drawPoint(x, y, _pen.color().rawValue());
}

void EALCD::drawLine(short x0, short y0, short x1, short y1)
{  
    _drawLine(x0, y0, x1, y1, _pen.color().rawValue()); 
}

void EALCD::drawRect(short x, short y, unsigned short w, unsigned short h)
{
    // Draw the top horizontal line.
    drawLine(x, y, x + w - 1, y);
    
    // Draw the bottom horizontal line.
    drawLine(x, y + h - 1, x + w - 1, y + h - 1);
    
    // Draw the left vertical line.
    drawLine(x, y, x, y + h - 1);
    
    // Draw the right vertical line.
    drawLine(x + w - 1, y, x + w - 1, y + h);
}

void EALCD::drawFilledRect(short x, short y, unsigned short w, unsigned short h)
{
    if (x < 0)
    {
        x = 0;
    }
    
    if (y < 0)
    {
        y = 0;
    }

    // Set the window so that automatically wrap and go inside the box.
    _window(x, y, w, h);
    _moveTo(x, y);
    
    for (int i = 0; i < (w*h); i++)
    {
        _writeToDisplay(_brush.color().rawValue());
    }
    
    drawRect(x, y, w, h);
}


void EALCD::drawEllipse(short x, short y, unsigned short w, unsigned short h)
{
    _drawEllipse(x, y, w, h, false);
}

void EALCD::drawFilledEllipse(short x, short y, unsigned short w, unsigned short h)
{
    _drawEllipse(x, y, w, h, true);
    _drawEllipse(x, y, w, h, false);
}

void EALCD::drawImage(unsigned short x, unsigned short y, EAImage& img)
{
    // Don't try painting an invalid image.
    if (img.isValid() == false)
    {
        return;
    }

    // Set the position to draw.
    img.setX(x);
    img.setY(y);
    
    // Draw the image.
    img.paint(*this);
}

void EALCD::drawText(unsigned short x, unsigned short y, char* text)
{
    // Don't try painting with an invalid font or no text
    if ((_font.isValid() == false) || (text == NULL))
    {
        return;
    }
    
    EAImage img;
    EAFont::EACharacter detail;
      
    img.load(_font.path());

    for (int i = 0; i < strlen(text); i++)
    {
        if (_font.getCharacter(text[i], detail) == true)
        {
            img.setX(x+detail.xOffset);
            img.setY(y+detail.yOffset);
            img.paint(*this, detail.x, detail.y, detail.width, detail.height);       
            
            x += detail.xAdvance;
        } else {
            // Character is unrecognised.
        }      
    }  
}