// ----------------------------------------------------------------------------------------
//
//  avr/lcd/sfe569/sfe569.cpp
//    
//  Bob Cook Development, Robotics Library
//  http://www.bobcookdev.com/rl/
//    
//  This file implements support for the SparkFun 128x128 Color LCD (#LCD-000569).
//
//  Copyright (C) 2007 Bob Cook
//
//      This program is free software; you can redistribute it and/or modify it
//      under the terms of the GNU General Public License as published by the
//      Free Software Foundation; either version 2 of the License, or (at your
//      option) any later version.
//    
//      This program is distributed in the hope that it will be useful, but 
//      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
//      or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
//      for more details.
//    
//      You should have received a copy of the GNU General Public License along
//      with this program; if not, write to the Free Software Foundation, Inc., 
//      51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
//  Portions copied from "Nokia-like Color LCD Sample" written by Cathy Saxton.
//  See http://www.idleloop.com/robotics/ColorLCD/
//
//  Portions copied from "Nokia 6100 LCD Display Driver" written by James P. Lynch.
//  See http:// ... (todo fill this in from the SFE site)
//
// ----------------------------------------------------------------------------------------


#include "sfe569.h"

#include <avr/io.h>

#include "packages/avr/device/spinwait.h"

#include "project_defs.h"


#if defined( PRJ_SFE569_ENABLE )


// ----------------------------------------------------------------------------------------

#if !defined( PRJ_SFE569_CLOCK_PORT )
#error PRJ_SFE569_CLOCK_PORT is required!
#endif

#if !defined( PRJ_SFE569_CLOCK_PIN )
#error PRJ_SFE569_CLOCK_PIN is required!
#endif

#if !defined( PRJ_SFE569_CLOCK_DDR )
#error PRJ_SFE569_CLOCK_DDR is required!
#endif

#if !defined( PRJ_SFE569_DATA_PORT )
#error PRJ_SFE569_DATA_PORT is required!
#endif

#if !defined( PRJ_SFE569_DATA_PIN )
#error PRJ_SFE569_DATA_PIN is required!
#endif

#if !defined( PRJ_SFE569_DATA_DDR )
#error PRJ_SFE569_DATA_DDR is required!
#endif

#if !defined( PRJ_SFE569_RESET_PORT )
#error PRJ_SFE569_RESET_PORT is required!
#endif

#if !defined( PRJ_SFE569_RESET_PIN )
#error PRJ_SFE569_RESET_PIN is required!
#endif

#if !defined( PRJ_SFE569_RESET_DDR )
#error PRJ_SFE569_RESET_DDR is required!
#endif

#if !defined( PRJ_SFE569_SELECT_PORT )
#error PRJ_SFE569_SELECT_PORT is required!
#endif

#if !defined( PRJ_SFE569_SELECT_PIN )
#error PRJ_SFE569_SELECT_PIN is required!
#endif

#if !defined( PRJ_SFE569_SELECT_DDR )
#error PRJ_SFE569_SELECT_DDR is required!
#endif

#if defined( PRJ_SFE569_INC_WRITE_CSTRINGS ) || defined( PRJ_SFE569_INC_WRITE_PGM_CSTRINGS )
#if !defined( PRJ_SFE569_INC_WRITE_CHAR )
#define PRJ_SFE569_INC_WRITE_CHAR
#endif
#endif


// ----------------------------------------------------------------------------------------

const uint8_t SFE569_MAX_HORIZONTAL_PIXELS   = 128;
const uint8_t SFE569_MAX_VERTICAL_PIXELS     = 128;
const uint8_t SFE569_HORIZONTAL_PIXEL_OFFSET = 1;
const uint8_t SFE569_VERTICAL_PIXEL_OFFSET   = 3;

const uint8_t SFE569_COMMAND_CASET    = 0x15;
const uint8_t SFE569_COMMAND_PWRCTR   = 0x20;
const uint8_t SFE569_COMMAND_NOP      = 0x25;
const uint8_t SFE569_COMMAND_PASET    = 0x75;
const uint8_t SFE569_COMMAND_RAMWR    = 0x5c;
const uint8_t SFE569_COMMAND_VOLCTR   = 0x81;
const uint8_t SFE569_COMMAND_TMPGRD   = 0x82;
const uint8_t SFE569_COMMAND_SLPOUT   = 0x94;
const uint8_t SFE569_COMMAND_SLPIN    = 0x95;
const uint8_t SFE569_COMMAND_DISNOR   = 0xa6;
const uint8_t SFE569_COMMAND_DISINV   = 0xa7;
const uint8_t SFE569_COMMAND_PTLIN    = 0xa8;
const uint8_t SFE569_COMMAND_PTLOUT   = 0xa9;
const uint8_t SFE569_COMMAND_DISOFF   = 0xae;
const uint8_t SFE569_COMMAND_DISON    = 0xaf;
const uint8_t SFE569_COMMAND_COMSCN   = 0xbb;
const uint8_t SFE569_COMMAND_DATCTL   = 0xbc;
const uint8_t SFE569_COMMAND_DISCTL   = 0xca;
const uint8_t SFE569_COMMAND_RGBSET8  = 0xce;
const uint8_t SFE569_COMMAND_OSCON    = 0xd1;
const uint8_t SFE569_COMMAND_OSCOFF   = 0xd2;


// ----------------------------------------------------------------------------------------
//
//  This is an 8x6 monospaced font, organized as one byte per horizontal row.
//

#if defined( PRJ_SFE569_INC_WRITE_CHAR )

typedef prog_uint8_t sfe569_glyph_t[ 8 ];

const uint8_t SFE569_GLYPH_HEIGHT = 8;
const uint8_t SFE569_GLYPH_WIDTH  = 6;

const uint8_t SFE569_MAX_HORIZONTAL_CHARS
    = ( SFE569_MAX_HORIZONTAL_PIXELS / SFE569_GLYPH_WIDTH );

const uint8_t SFE569_MAX_VERTICAL_CHARS
    = ( SFE569_MAX_VERTICAL_PIXELS / SFE569_GLYPH_HEIGHT );

static const sfe569_glyph_t glyphs[] =
{
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },     // space, ascii 0x20
    { 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x10, 0x00 },     // !
    { 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00 },     // "
    { 0x28, 0x28, 0x7c, 0x28, 0x7c, 0x28, 0x28, 0x00 },     // #
    { 0x10, 0x3c, 0x50, 0x38, 0x14, 0x78, 0x10, 0x00 },     // $
    { 0x60, 0x64, 0x08, 0x10, 0x20, 0x4c, 0x0c, 0x00 },     // %
    { 0x10, 0x28, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 },     // &
    { 0x30, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00 },     // '
    { 0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00 },     // (
    { 0x10, 0x08, 0x04, 0x04, 0x04, 0x08, 0x10, 0x00 },     // )
    { 0x00, 0x10, 0x54, 0x38, 0x54, 0x10, 0x00, 0x00 },     // *
    { 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00 },     // +
    { 0x00, 0x00, 0x00, 0x00, 0x30, 0x10, 0x20, 0x00 },     // ,
    { 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00 },     // -
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00 },     // .
    { 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00 },     // /
    { 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00 },     // 0
    { 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 },     // 1
    { 0x38, 0x44, 0x04, 0x08, 0x10, 0x20, 0x7c, 0x00 },     // 2
    { 0x7c, 0x08, 0x10, 0x08, 0x04, 0x44, 0x38, 0x00 },     // 3
    { 0x08, 0x18, 0x28, 0x48, 0x7c, 0x08, 0x08, 0x00 },     // 4
    { 0x7c, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00 },     // 5
    { 0x18, 0x20, 0x40, 0x78, 0x44, 0x44, 0x38, 0x00 },     // 6
    { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x00 },     // 7
    { 0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x38, 0x00 },     // 8
    { 0x38, 0x44, 0x44, 0x38, 0x04, 0x04, 0x38, 0x00 },     // 9
    { 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00 },     // :
    { 0x00, 0x30, 0x30, 0x00, 0x30, 0x10, 0x20, 0x00 },     // ;
    { 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x00 },     // <
    { 0x00, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x00, 0x00 },     // =
    { 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00 },     // >
    { 0x38, 0x44, 0x04, 0x08, 0x10, 0x00, 0x10, 0x00 },     // ?
    { 0x38, 0x44, 0x04, 0x34, 0x54, 0x54, 0x38, 0x00 },     // @
    { 0x38, 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x00 },     // A
    { 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x78, 0x00 },     // B
    { 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00 },     // C
    { 0x70, 0x48, 0x44, 0x44, 0x44, 0x48, 0x70, 0x00 },     // D
    { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7c, 0x00 },     // E
    { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00 },     // F
    { 0x38, 0x44, 0x40, 0x5c, 0x44, 0x44, 0x3c, 0x00 },     // G
    { 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x00 },     // H
    { 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 },     // I
    { 0x1c, 0x08, 0x08, 0x08, 0x08, 0x48, 0x30, 0x00 },     // J
    { 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00 },     // K
    { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00 },     // L
    { 0x44, 0x6c, 0x54, 0x54, 0x44, 0x44, 0x44, 0x00 },     // M
    { 0x44, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x00 },     // N
    { 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 },     // O
    { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00 },     // P
    { 0x38, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00 },     // Q
    { 0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x00 },     // R
    { 0x3c, 0x40, 0x40, 0x38, 0x04, 0x04, 0x78, 0x00 },     // S
    { 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 },     // T
    { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 },     // U
    { 0x44, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 },     // V
    { 0x44, 0x44, 0x44, 0x54, 0x54, 0x54, 0x28, 0x00 },     // W
    { 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00 },     // X
    { 0x44, 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x00 },     // Y
    { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00 },     // Z
    { 0x70, 0x40, 0x40, 0x40, 0x40, 0x40, 0x70, 0x00 },     // [
    { 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 },     // \ (backslash)
    { 0x1c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1c, 0x00 },     // ]
    { 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00 },     // ^
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00 },     // _
    { 0x20, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 },     // `
    { 0x00, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00 },     // a
    { 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x78, 0x00 },     // b
    { 0x00, 0x00, 0x38, 0x40, 0x40, 0x44, 0x38, 0x00 },     // c
    { 0x04, 0x04, 0x34, 0x4c, 0x44, 0x44, 0x3c, 0x00 },     // d
    { 0x00, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x38, 0x00 },     // e
    { 0x18, 0x24, 0x20, 0x70, 0x20, 0x20, 0x20, 0x00 },     // f
    { 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x38, 0x00 },     // g
    { 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x44, 0x00 },     // h
    { 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x38, 0x00 },     // i
    { 0x04, 0x00, 0x0c, 0x04, 0x04, 0x24, 0x18, 0x00 },     // j
    { 0x40, 0x40, 0x48, 0x50, 0x60, 0x50, 0x48, 0x00 },     // k
    { 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 },     // l
    { 0x00, 0x00, 0x68, 0x54, 0x54, 0x44, 0x44, 0x00 },     // m
    { 0x00, 0x00, 0x58, 0x64, 0x44, 0x44, 0x44, 0x00 },     // n
    { 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 },     // o
    { 0x00, 0x00, 0x78, 0x44, 0x78, 0x40, 0x40, 0x00 },     // p
    { 0x00, 0x00, 0x34, 0x4c, 0x3c, 0x04, 0x04, 0x00 },     // q
    { 0x00, 0x00, 0x58, 0x64, 0x40, 0x40, 0x40, 0x00 },     // r
    { 0x00, 0x00, 0x38, 0x40, 0x38, 0x04, 0x78, 0x00 },     // s
    { 0x20, 0x20, 0x70, 0x20, 0x20, 0x24, 0x18, 0x00 },     // t
    { 0x00, 0x00, 0x44, 0x44, 0x44, 0x4c, 0x34, 0x00 },     // u
    { 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 },     // v
    { 0x00, 0x00, 0x44, 0x44, 0x54, 0x54, 0x28, 0x00 },     // w
    { 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 },     // x
    { 0x00, 0x00, 0x44, 0x44, 0x3c, 0x04, 0x38, 0x00 },     // y
    { 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00 },     // z
    { 0x10, 0x20, 0x20, 0x40, 0x20, 0x20, 0x10, 0x00 },     // {
    { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 },     // |
    { 0x10, 0x08, 0x08, 0x04, 0x08, 0x08, 0x10, 0x00 },     // }
    { 0x00, 0x00, 0x14, 0x28, 0x00, 0x00, 0x00, 0x00 },     // ~
    { 0x00, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x00 }      // DEL
};


#endif


// ----------------------------------------------------------------------------------------

void send_nine_bits( uint8_t ninth_bit, uint8_t low_eight_bits )
{
    //--    Set the ninth bit.

    if ( ninth_bit )
    {
        PRJ_SFE569_DATA_PORT |= ( 1 << PRJ_SFE569_DATA_PIN );
    }
    else
    {
        PRJ_SFE569_DATA_PORT &= ~( 1 << PRJ_SFE569_DATA_PIN );
    }

    //--    Clock line goes low to signal data ready, delay, then raise it again.

    PRJ_SFE569_CLOCK_PORT &= ~( 1 << PRJ_SFE569_CLOCK_PIN );

    asm( "nop" );
    asm( "nop" );
    
    PRJ_SFE569_CLOCK_PORT |= ( 1 << PRJ_SFE569_CLOCK_PIN );

    asm( "nop" );
    asm( "nop" );

    for ( uint8_t i = 0; i < 8; i++ )
    {
        if ( low_eight_bits & 0x80 )
        {
            PRJ_SFE569_DATA_PORT |= ( 1 << PRJ_SFE569_DATA_PIN );
        }
        else
        {
            PRJ_SFE569_DATA_PORT &= ~( 1 << PRJ_SFE569_DATA_PIN );
        }

        low_eight_bits <<= 1;

        //--    Clock line goes low to signal data ready, delay, then raise it again.

        PRJ_SFE569_CLOCK_PORT &= ~( 1 << PRJ_SFE569_CLOCK_PIN );
        
        asm( "nop" );
        asm( "nop" );

        PRJ_SFE569_CLOCK_PORT |= ( 1 << PRJ_SFE569_CLOCK_PIN );

        asm( "nop" );
        asm( "nop" );
    }
}

inline void send_command( uint8_t cmd )
{
    send_nine_bits( 0x00, cmd );
}

inline void send_data( uint8_t data )
{
    send_nine_bits( 0xff, data );
}


// ----------------------------------------------------------------------------------------

static void select_controller()
{
    PRJ_SFE569_SELECT_PORT &= ~( 1 << PRJ_SFE569_SELECT_PIN );
    asm( "nop" );
    asm( "nop" );
}

static void deselect_controller()
{
    PRJ_SFE569_SELECT_PORT |= ( 1 << PRJ_SFE569_SELECT_PIN );
    asm( "nop" );
    asm( "nop" );
}


// ----------------------------------------------------------------------------------------

void sfe569_init()
{
    //--    Initialize the various pins.

    PRJ_SFE569_CLOCK_DDR |= ( 1 << PRJ_SFE569_CLOCK_PIN );
    PRJ_SFE569_CLOCK_PORT |= ( 1 << PRJ_SFE569_CLOCK_PIN );

    PRJ_SFE569_DATA_DDR |= ( 1 << PRJ_SFE569_DATA_PIN );
    PRJ_SFE569_DATA_PORT &= ~( 1 << PRJ_SFE569_DATA_PIN );

    PRJ_SFE569_RESET_DDR |= ( 1 << PRJ_SFE569_RESET_PIN );
    PRJ_SFE569_RESET_PORT |= ( 1 << PRJ_SFE569_RESET_PIN );

    PRJ_SFE569_SELECT_DDR |= ( 1 << PRJ_SFE569_SELECT_PIN );
    PRJ_SFE569_SELECT_PORT |= ( 1 << PRJ_SFE569_SELECT_PIN );

    //--    Reset the display.

    spinwait_delay_us( 5 );
    PRJ_SFE569_RESET_PORT &= ~( 1 << PRJ_SFE569_RESET_PIN );
    spinwait_delay_us( 5 );
    PRJ_SFE569_RESET_PORT |= ( 1 << PRJ_SFE569_RESET_PIN );
    spinwait_delay_ms( 2 );

    //--    Configure the display controller. Mostly copied from example code and reading
    //      the EPSON S1D15G00 data sheet from SparkFun.

    select_controller();

    send_command( SFE569_COMMAND_DISCTL );
    send_data( 0x00 );    // CL div ratio 2; F1, F2 switching 8H (default)
    send_data( 0x20 );    // 132 / 4 - 1 = 32 (0x20)
    send_data( 0x0a );    // Inverse highlight (default)

    send_command( SFE569_COMMAND_COMSCN );
    send_data( 0x01 );    // COM1-80: 1->80; COM81-160: 160<-81

    send_command( SFE569_COMMAND_OSCON );
    send_command( SFE569_COMMAND_SLPOUT );

    send_command( SFE569_COMMAND_VOLCTR );
    send_data( 28 );
    send_data( 3 );

    send_command( SFE569_COMMAND_TMPGRD );
    send_data( 0 ); // default

    send_command( SFE569_COMMAND_PWRCTR );
    send_data( 0x0f );  // full power control set enabled

    send_command( SFE569_COMMAND_DISINV );
    send_command( SFE569_COMMAND_PTLOUT );
    
    send_command( SFE569_COMMAND_DATCTL );
    send_data( 0x00 );    // normal orientation; scan across cols, then rows
    send_data( 0x00 );    // RGB arrangement (RGB all rows/cols)
    send_data( 0x01 );    // 8 bit-color display
    
    send_command( SFE569_COMMAND_RGBSET8 );
    send_data( 0x00 );    // 000 RED
    send_data( 0x02 );    // 001  
    send_data( 0x04 );    // 010
    send_data( 0x06 );    // 011
    send_data( 0x08 );    // 100
    send_data( 0x0a );    // 101
    send_data( 0x0c );    // 110
    send_data( 0x0f );    // 111
    send_data( 0x00 );    // 000 GREEN
    send_data( 0x02 );    // 001  
    send_data( 0x04 );    // 010
    send_data( 0x06 );    // 011
    send_data( 0x08 );    // 100
    send_data( 0x0a );    // 101
    send_data( 0x0c );    // 110
    send_data( 0x0f );    // 111
    send_data( 0x00 );    //  00 BLUE
    send_data( 0x06 );    //  01
    send_data( 0x09 );    //  10
    send_data( 0x0f );    //  11

    deselect_controller();
    spinwait_delay_ms( 100 );

    sfe569_fill_screen( sfe569_color_black );

    select_controller();
    send_command( SFE569_COMMAND_DISON );
    deselect_controller();
}


// ----------------------------------------------------------------------------------------
//  Prepare to write to the display memory to a given rectangle.
//

static void prep_page_col_address( uint8_t x, uint8_t dx, uint8_t y, uint8_t dy )
{
    send_command( SFE569_COMMAND_PASET );
    send_data( y );
    send_data( y + dy );

    send_command( SFE569_COMMAND_CASET );
    send_data( x );
    send_data( x + dx );

    send_command( SFE569_COMMAND_RAMWR );
}


// ----------------------------------------------------------------------------------------

void sfe569_fill_screen( sfe569_color_t color )
{
    select_controller();

    //--    Set the page and column addressing to 0 - 131.

    prep_page_col_address( 0, ( 132 - 1 ), 0, ( 132 - 1 ) );

    for ( uint16_t i = 0; i < ( 132 * 132 ); i++ ) 
    {
        send_data( color );
    }

    deselect_controller();
}


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_WRITE_CHAR )

void sfe569_write
    (
        char           c, 
        uint8_t        x, 
        uint8_t        y, 
        sfe569_color_t fg_color,
        sfe569_color_t bg_color 
    )
{
    //--    Convert the glyph coordinates to pixel coordinates.

    x %= SFE569_MAX_HORIZONTAL_CHARS;
    x *= SFE569_GLYPH_WIDTH;
    x += SFE569_HORIZONTAL_PIXEL_OFFSET;

    y %= SFE569_MAX_VERTICAL_CHARS;
    y *= SFE569_GLYPH_HEIGHT;
    y += SFE569_VERTICAL_PIXEL_OFFSET;

    //--    Glyphs are stored starting with the space character: ASCII 32 = 0x20.

    if ( c < 0x20 )
    {
        c = 0x7f;   // generic character glyph
    }

    c -= 0x20;
    
    const prog_uint8_t* this_glyph
      = reinterpret_cast< const prog_uint8_t* >( &glyphs[ static_cast< uint8_t >( c ) ] );

    //--    Write the glyph pixels at the specified location.

    select_controller();
    
    prep_page_col_address( x, SFE569_GLYPH_WIDTH - 1, y, SFE569_GLYPH_HEIGHT - 1 );

    for ( uint8_t i = 0; i < SFE569_GLYPH_HEIGHT; i++ )
    {
        uint8_t data = pgm_read_byte_near( this_glyph++ );

        for ( uint8_t j = 0; j < SFE569_GLYPH_WIDTH; j++ )
        {
            if ( data & 0x80 )
            {
                send_data( fg_color );
            }
            else
            {
                send_data( bg_color );
            }
            data <<= 1;
        }
    }

    deselect_controller();
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_WRITE_CSTRINGS )

void sfe569_write
    ( 
        const char*    str, 
        uint8_t        x, 
        uint8_t        y, 
        sfe569_color_t fg_color,
        sfe569_color_t bg_color 
    )
{
    while ( *str )
    {
        sfe569_write( *str++, x++, y, fg_color, bg_color );
    }
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_WRITE_PGM_CSTRINGS )

void sfe569_write_pgm
    ( 
        const prog_char* str, 
        uint8_t          x, 
        uint8_t          y, 
        sfe569_color_t   fg_color,
        sfe569_color_t   bg_color 
    )
{
    for ( ;; )
    {
        char c = static_cast< char >( pgm_read_byte_near( str++ ) );

        if ( c == '\0' )
        {
            return;
        }

        sfe569_write( c, x++, y, fg_color, bg_color );
    }
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_SET_PIXEL )

void sfe569_set_pixel( uint8_t x, uint8_t y, sfe569_color_t color )
{
    x %= SFE569_MAX_HORIZONTAL_PIXELS;
    x += SFE569_HORIZONTAL_PIXEL_OFFSET;
    y %= SFE569_MAX_VERTICAL_PIXELS;
    y += SFE569_VERTICAL_PIXEL_OFFSET;

    select_controller();
    
    prep_page_col_address( x, 0, y, 0 );
    send_data( color );
    
    deselect_controller();
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_DRAWING )

void sfe569_line_h( uint8_t x, uint8_t y, uint8_t len, sfe569_color_t color )
{
    x %= SFE569_MAX_HORIZONTAL_PIXELS;
    y %= SFE569_MAX_VERTICAL_PIXELS;
    
    if ( len + x > SFE569_MAX_HORIZONTAL_PIXELS )
    {
        len = SFE569_MAX_HORIZONTAL_PIXELS - x;
    }

    x += SFE569_HORIZONTAL_PIXEL_OFFSET;
    y += SFE569_VERTICAL_PIXEL_OFFSET;

    select_controller();
    
    prep_page_col_address( x, len - 1, y, 0 );
    
    while ( len-- )
    {
        send_data( color );
    }

    deselect_controller();
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_DRAWING )

void sfe569_line_v( uint8_t x, uint8_t y, uint8_t len, sfe569_color_t color )
{
    x %= SFE569_MAX_HORIZONTAL_PIXELS;
    y %= SFE569_MAX_VERTICAL_PIXELS;
    
    if ( len + y > SFE569_MAX_VERTICAL_PIXELS )
    {
        len = SFE569_MAX_VERTICAL_PIXELS - y;
    }

    x += SFE569_HORIZONTAL_PIXEL_OFFSET;
    y += SFE569_VERTICAL_PIXEL_OFFSET;

    select_controller();
    
    prep_page_col_address( x, 0, y, len - 1 );
    
    while ( len-- )
    {
        send_data( color );
    }

    deselect_controller();
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_DRAWING )

void sfe569_rect( uint8_t x, uint8_t y, uint8_t w, uint8_t h, sfe569_color_t color )
{
    sfe569_line_h( x, y, w, color );
    sfe569_line_h( x, y + h - 1, w, color );
    sfe569_line_v( x, y, h - 1, color );
    sfe569_line_v( x + w - 1, y, h - 1, color );
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_DRAWING )

void sfe569_fill( uint8_t x, uint8_t y, uint8_t w, uint8_t h, sfe569_color_t color )
{
    x %= SFE569_MAX_HORIZONTAL_PIXELS;
    y %= SFE569_MAX_VERTICAL_PIXELS;
    
    if ( w + x > SFE569_MAX_HORIZONTAL_PIXELS )
    {
        w = SFE569_MAX_HORIZONTAL_PIXELS - x;
    }

    if ( h + y > SFE569_MAX_VERTICAL_PIXELS )
    {
        h = SFE569_MAX_VERTICAL_PIXELS - y;
    }

    x += SFE569_HORIZONTAL_PIXEL_OFFSET;
    y += SFE569_VERTICAL_PIXEL_OFFSET;

    select_controller();
    
    prep_page_col_address( x, w - 1, y, h - 1 );
    
    for ( uint16_t i = 0; i < static_cast< uint16_t >( h * w ); i++ )
    {
        send_data( color );
    }

    deselect_controller();
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_DRAWING )

void sfe569_line( uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, sfe569_color_t color )
{
    //--    Bresenham's line rasterization algorithm: 
    //      http://www.cs.unc.edu/~mcmillan/comp136/Lecture6/Lines.html

    int16_t dy = y1 - y0;
    int16_t dx = x1 - x0;
    int8_t  step_x = 1;
    int8_t  step_y = 1;

    if ( dy < 0 )
    {
        dy *= -1;
        step_y = -1;
    }

    if ( dx < 0 )
    {
        dx *= -1;
        step_x = -1;
    }

    dy <<= 1;
    dx <<= 1;

    sfe569_set_pixel( x0, y0, color );

    if ( dx > dy )
    {
        int16_t fraction = dy - ( dx >> 1 );

        while ( x0 != x1 )
        {
            if ( fraction >= 0 ) 
            {
                y0 += step_y;
                fraction -= dx;
            }

            x0 += step_x;
            fraction += dy;

            sfe569_set_pixel( x0, y0, color );
        }
    }
    else
    {
        int16_t fraction = dx - ( dy >> 1 );

        while ( y0 != y1 )
        {
            if ( fraction >= 0 )
            {
                x0 += step_x;
                fraction -= dy;
            }

            y0 += step_y;
            fraction += dx;

            sfe569_set_pixel( x0, y0, color );
        }
    }
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_IMAGEMASK )

void sfe569_imagemask
    (
        uint8_t             x,
        uint8_t             y,
        uint8_t             w,
        uint8_t             h,
        sfe569_color_t      fg_color,
        sfe569_color_t      bg_color,
        const prog_uint8_t* pixels
    )
{
    x %= SFE569_MAX_HORIZONTAL_PIXELS;
    x += SFE569_HORIZONTAL_PIXEL_OFFSET;
    
    y %= SFE569_MAX_VERTICAL_PIXELS;
    y += SFE569_VERTICAL_PIXEL_OFFSET;

    select_controller();
    
    prep_page_col_address( x, w - 1, y, h - 1 );
    
    while ( h-- )
    {
        uint8_t pix_bit = 0x00;
        uint8_t data    = 0x00;

        for ( uint8_t i = 0; i < w; i++ )
        {
            if ( pix_bit == 0x00 )
            {
                data = pgm_read_byte_near( pixels++ );
                pix_bit = 0x80;
            }

            if ( data & pix_bit )
            {
                send_data( fg_color );
            }
            else
            {
                send_data( bg_color );
            }

            pix_bit >>= 1;
        }
    }

    deselect_controller();
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_SFE569_INC_COLORIMAGE )

void sfe569_colorimage
    (
        uint8_t x,
        uint8_t y,
        uint8_t w,
        uint8_t h,
        const prog_uint8_t* pixels
    )
{
    x %= SFE569_MAX_HORIZONTAL_PIXELS;
    x += SFE569_HORIZONTAL_PIXEL_OFFSET;
    
    y %= SFE569_MAX_VERTICAL_PIXELS;
    y += SFE569_VERTICAL_PIXEL_OFFSET;

    select_controller();
    
    prep_page_col_address( x, w - 1, y, h - 1 );
    
    while ( h-- )
    {
        for ( uint8_t i = 0; i < w; i++ )
        {
            send_data( pgm_read_byte_near( pixels++ ) );
        }
    }

    deselect_controller();
}

#endif


// ----------------------------------------------------------------------------------------

#endif  // #if defined( PRJ_SFE569_ENABLE )


// ----------------------------------------------------------------------------------------
//  end of sfe569.cpp
// ----------------------------------------------------------------------------------------

