// ----------------------------------------------------------------------------------------
//
//  avr/lcd/cfax/cfax.cpp
//    
//  Bob Cook Development, Robotics Library
//  http://www.bobcookdev.com/rl/
//    
//  This file implements support for the Crystal Fontz CFAX 128x64.
//
//  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 "CFAX_LCD_Example.c" provided by Spark Fun Electronics.
//  See http://www.sparkfun.com/datasheets/LCD/CFAX_LCD_Example.c
//
// ----------------------------------------------------------------------------------------


#include "cfax.h"

#include <avr/io.h>

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

#include "project_defs.h"


#if defined( PRJ_CFAX_ENABLE )


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#if defined( PRJ_CFAX_INC_TOUCHSCREEN )

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

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

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

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

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

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

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

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

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

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

#endif // #if defined( PRJ_CFAX_INC_TOUCHSCREEN )

#if defined( PRJ_CFAX_INC_WRITE_CSTRINGS ) || defined( PRJ_CFAX_INC_WRITE_PGM_CSTRINGS )
#if !defined( PRJ_CFAX_INC_WRITE_CHAR )
#define PRJ_CFAX_INC_WRITE_CHAR
#endif
#endif

#if defined( PRJ_CFAX_INC_DRAWING ) || defined( PRJ_CFAX_INC_BITMAPS )
#define PRJ_CFAX_INC_SET_PIXEL
#endif

#if !defined( PRJ_CFAX_DEFAULT_CONTRAST )
#define PRJ_CFAX_DEFAULT_CONTRAST 38
#endif


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

const uint8_t CFAX_MAX_HORIZONTAL_PIXELS   = 128;
const uint8_t CFAX_MAX_VERTICAL_PIXELS     =  64;
const uint8_t CFAX_MAX_VERTICAL_PAGES      = 8;
const uint8_t CFAX_HORIZONTAL_PIXEL_OFFSET = ( 132 - 128 );

const uint8_t CFAX_MAX_HORIZONTAL_CHARS = ( CFAX_MAX_HORIZONTAL_PIXELS / 6 );
const uint8_t CFAX_MAX_VERTICAL_CHARS   = CFAX_MAX_VERTICAL_PAGES;

const uint8_t CFAX_COMMAND_SEL_COLUMN_LSB = 0x00;
const uint8_t CFAX_COMMAND_SEL_COLUMN_MSB = 0x10;
const uint8_t CFAX_COMMAND_SEL_PAGE       = 0xb0;

const uint8_t CFAX_COMMAND_IDL = 0x40;

const uint8_t CFAX_COMMAND_VREF = 0x81;

const uint8_t CFAX_COMMAND_ADC_OFF = 0xa0;
const uint8_t CFAX_COMMAND_ADC_ON  = 0xa1;

const uint8_t CFAX_COMMAND_BAS_OFF = 0xa2;
const uint8_t CFAX_COMMAND_BAS_ON  = 0xa3;

const uint8_t CFAX_COMMAND_DON_OFF = 0xae;
const uint8_t CFAX_COMMAND_DON_ON  = 0xaf;

const uint8_t CFAX_COMMAND_SHL_ON  = 0xc8;
const uint8_t CFAX_COMMAND_SHL_OFF = 0xc0;


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

#if defined( PRJ_CFAX_INC_ICONS )

static const prog_uint8_t cfax_icon_ids[] =
{
    0x07,   // maple leaf
    0x11,   // document
    0x18,   // battery outline
    0x16,   // battery level 1
    0x15,   // battery level 2
    0x17,   // battery levle 3
    0x28,   // terminal / computer
    0x33,   // antenna
    0x3c,   // speaker
    0x39,   // speaker tick top-left
    0x41,   // speaker tick bottom-right
    0x49,   // telephone
    0x54,   // letter
    0x5f,   // bell
    0x6a,   // clock
    0x75,   // caution triangle
    0x7f    // spinner
};

#endif


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

#if defined( PRJ_CFAX_INC_WRITE_CHAR )

typedef prog_uint8_t cfax_glyph_t[ 5 ];

static const cfax_glyph_t glyphs[] =
{
    { 0x00, 0x00, 0x00, 0x00, 0x00 },   // space, ascii 0x20
    { 0x00, 0x00, 0x4f, 0x00, 0x00 },   // !
    { 0x00, 0x07, 0x00, 0x07, 0x00 },   // "
    { 0x14, 0x7f, 0x14, 0x7f, 0x14 },   // #
    { 0x24, 0x2a, 0x7f, 0x2a, 0x12 },   // $
    { 0x23, 0x13, 0x08, 0x64, 0x62 },   // %
    { 0x00, 0x02, 0x05, 0x02, 0x00 },   // &
    { 0x00, 0x05, 0x03, 0x00, 0x00 },   // '
    { 0x1C, 0x22, 0x41, 0x00, 0x00 },   // (
    { 0x00, 0x00, 0x41, 0x22, 0x1C },   // )
    { 0x14, 0x08, 0x3E, 0x08, 0x14 },   // *
    { 0x08, 0x08, 0x3E, 0x08, 0x08 },   // +
    { 0x00, 0x50, 0x30, 0x00, 0x00 },   // ,
    { 0x08, 0x08, 0x08, 0x08, 0x08 },   // -
    { 0x00, 0x60, 0x60, 0x00, 0x00 },   // .
    { 0x20, 0x10, 0x08, 0x04, 0x02 },   // /
    { 0x3e, 0x51, 0x49, 0x45, 0x3e },   // 0
    { 0x00, 0x42, 0x7f, 0x40, 0x00 },   // 1
    { 0x42, 0x61, 0x51, 0x49, 0x46 },   // 2
    { 0x21, 0x41, 0x45, 0x4B, 0x31 },   // 3
    { 0x18, 0x14, 0x12, 0x7f, 0x10 },   // 4
    { 0x27, 0x45, 0x45, 0x45, 0x39 },   // 5
    { 0x3c, 0x4a, 0x49, 0x49, 0x30 },   // 6
    { 0x01, 0x71, 0x09, 0x05, 0x03 },   // 7
    { 0x36, 0x49, 0x49, 0x49, 0x36 },   // 8
    { 0x06, 0x49, 0x49, 0x49, 0x36 },   // 9
    { 0x00, 0x36, 0x36, 0x00, 0x00 },   // :
    { 0x00, 0x56, 0x36, 0x00, 0x00 },   // ;
    { 0x08, 0x14, 0x22, 0x41, 0x00 },   // <
    { 0x14, 0x14, 0x14, 0x14, 0x14 },   // =
    { 0x00, 0x41, 0x22, 0x14, 0x08 },   // >
    { 0x02, 0x01, 0x51, 0x09, 0x06 },   // ?
    { 0x32, 0x49, 0x79, 0x41, 0x3e },   // @
    { 0x7e, 0x11, 0x11, 0x11, 0x7e },   // A
    { 0x7f, 0x49, 0x49, 0x49, 0x36 },   // B
    { 0x3e, 0x41, 0x41, 0x41, 0x22 },   // C
    { 0x7f, 0x41, 0x41, 0x22, 0x1c },   // D
    { 0x7f, 0x49, 0x49, 0x49, 0x41 },   // E
    { 0x7f, 0x09, 0x09, 0x09, 0x01 },   // F
    { 0x3e, 0x41, 0x49, 0x49, 0x7A },   // G
    { 0x7f, 0x08, 0x08, 0x08, 0x7f },   // H
    { 0x00, 0x41, 0x7f, 0x41, 0x00 },   // I
    { 0x20, 0x40, 0x41, 0x3f, 0x01 },   // J
    { 0x7f, 0x08, 0x14, 0x22, 0x41 },   // K
    { 0x7f, 0x40, 0x40, 0x40, 0x40 },   // L
    { 0x7f, 0x02, 0x0c, 0x02, 0x7f },   // M
    { 0x7f, 0x04, 0x08, 0x10, 0x7f },   // N
    { 0x3e, 0x41, 0x41, 0x41, 0x3e },   // O
    { 0x7f, 0x09, 0x09, 0x09, 0x06 },   // P
    { 0x3e, 0x41, 0x51, 0x21, 0x5e },   // Q
    { 0x7f, 0x09, 0x19, 0x29, 0x46 },   // R
    { 0x46, 0x49, 0x49, 0x49, 0x31 },   // S
    { 0x01, 0x01, 0x7f, 0x01, 0x01 },   // T
    { 0x3f, 0x40, 0x40, 0x40, 0x3f },   // U
    { 0x1f, 0x20, 0x40, 0x20, 0x1f },   // V
    { 0x3f, 0x40, 0x38, 0x40, 0x3f },   // W
    { 0x63, 0x14, 0x08, 0x14, 0x63 },   // X
    { 0x07, 0x08, 0x70, 0x08, 0x07 },   // Y
    { 0x61, 0x51, 0x49, 0x45, 0x43 },   // Z
    { 0x7f, 0x41, 0x41, 0x00, 0x00 },   // [
    { 0x02, 0x04, 0x08, 0x10, 0x20 },   // \ (backslash)
    { 0x00, 0x00, 0x41, 0x41, 0x7f },   // ]
    { 0x04, 0x02, 0x01, 0x02, 0x04 },   // ^
    { 0x40, 0x40, 0x40, 0x40, 0x40 },   // _
    { 0x00, 0x01, 0x02, 0x04, 0x00 },   // `
    { 0x20, 0x54, 0x54, 0x54, 0x78 },   // a
    { 0x7f, 0x48, 0x44, 0x44, 0x38 },   // b
    { 0x38, 0x44, 0x44, 0x44, 0x20 },   // c
    { 0x38, 0x44, 0x44, 0x48, 0x7f },   // d
    { 0x38, 0x54, 0x54, 0x54, 0x18 },   // e
    { 0x08, 0x7e, 0x09, 0x01, 0x02 },   // f
    { 0x0c, 0x52, 0x52, 0x52, 0x3e },   // g
    { 0x7f, 0x08, 0x04, 0x04, 0x78 },   // h
    { 0x00, 0x44, 0x7d, 0x40, 0x00 },   // i
    { 0x00, 0x20, 0x40, 0x44, 0x3d },   // j
    { 0x7f, 0x10, 0x28, 0x44, 0x00 },   // k
    { 0x00, 0x41, 0x7f, 0x40, 0x00 },   // l
    { 0x7c, 0x04, 0x18, 0x04, 0x78 },   // m
    { 0x7c, 0x08, 0x04, 0x04, 0x78 },   // n
    { 0x38, 0x44, 0x44, 0x44, 0x38 },   // o
    { 0x7c, 0x14, 0x14, 0x14, 0x08 },   // p
    { 0x08, 0x14, 0x14, 0x18, 0x7c },   // q
    { 0x7c, 0x08, 0x04, 0x04, 0x08 },   // r
    { 0x48, 0x54, 0x54, 0x54, 0x20 },   // s
    { 0x04, 0x3f, 0x44, 0x40, 0x20 },   // t
    { 0x3c, 0x40, 0x40, 0x20, 0x7c },   // u
    { 0x1c, 0x20, 0x40, 0x20, 0x1c },   // v
    { 0x3c, 0x40, 0x30, 0x40, 0x3c },   // w
    { 0x44, 0x28, 0x10, 0x28, 0x44 },   // x
    { 0x0c, 0x50, 0x50, 0x50, 0x3c },   // y
    { 0x44, 0x64, 0x54, 0x4C, 0x44 },   // z
    { 0x08, 0x36, 0x41, 0x00, 0x00 },   // {
    { 0x00, 0x00, 0x7f, 0x00, 0x00 },   // |
    { 0x00, 0x00, 0x41, 0x36, 0x08 },   // }
    { 0x00, 0x08, 0x04, 0x08, 0x04 },   // ~
    { 0x00, 0x7e, 0x7e, 0x7e, 0x7e }    // DEL
};

#endif


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

inline static void pin_rstb_on()
{
    PRJ_CFAX_CONTROL_RSTB_PORT |= ( 1 << PRJ_CFAX_CONTROL_RSTB_PIN );
}

inline static void pin_rstb_off()
{
    PRJ_CFAX_CONTROL_RSTB_PORT &= ~( 1 << PRJ_CFAX_CONTROL_RSTB_PIN );
}

inline static void pin_rs_on()
{
    PRJ_CFAX_CONTROL_RS_PORT |= ( 1 << PRJ_CFAX_CONTROL_RS_PIN );
}

inline static void pin_rs_off()
{
    PRJ_CFAX_CONTROL_RS_PORT &= ~( 1 << PRJ_CFAX_CONTROL_RS_PIN );
}

inline static void pin_rd_on()
{
    PRJ_CFAX_CONTROL_RD_PORT |= ( 1 << PRJ_CFAX_CONTROL_RD_PIN );
}

inline static void pin_rd_off()
{
    PRJ_CFAX_CONTROL_RD_PORT &= ~( 1 << PRJ_CFAX_CONTROL_RD_PIN );
}

inline static void pin_rw_on()
{
    PRJ_CFAX_CONTROL_RW_PORT |= ( 1 << PRJ_CFAX_CONTROL_RW_PIN );
}

inline static void pin_rw_off()
{
    PRJ_CFAX_CONTROL_RW_PORT &= ~( 1 << PRJ_CFAX_CONTROL_RW_PIN );
}


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

static void send_command( uint8_t cmd )
{
    PRJ_CFAX_DATA_DDR  = 0xff;
    PRJ_CFAX_DATA_PORT = cmd;
    
    pin_rs_off();
    pin_rw_off();
    pin_rd_on();
    pin_rd_off();
}


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

static void write_lcd_page( uint8_t page, uint8_t column, uint8_t data )
{
    send_command( CFAX_COMMAND_SEL_COLUMN_MSB | ( column >> 4 ) );
    send_command( CFAX_COMMAND_SEL_COLUMN_LSB | ( column & 0x0f ) );
    send_command( CFAX_COMMAND_SEL_PAGE | page );
    
    PRJ_CFAX_DATA_DDR  = 0xff;
    PRJ_CFAX_DATA_PORT = data;

    pin_rs_on();
    pin_rw_off();
    pin_rd_on();
    pin_rd_off();
}


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

static uint8_t read_lcd_page( uint8_t page, uint8_t column )
{
    send_command( CFAX_COMMAND_SEL_COLUMN_MSB | ( column >> 4 ) );
    send_command( CFAX_COMMAND_SEL_COLUMN_LSB | ( column & 0x0f ) );
    send_command( CFAX_COMMAND_SEL_PAGE | page );
    
    PRJ_CFAX_DATA_DDR  = 0x00;
    PRJ_CFAX_DATA_PORT = 0xff;

    pin_rs_on();
    pin_rw_on();
    pin_rd_on();
    pin_rd_off();
    pin_rd_on();

    uint8_t data = PRJ_CFAX_DATA_PORT_PINS;

    pin_rd_off();

    return data;
}


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

void cfax_init()
{
    //--    Initialize the data port and control pins.

    PRJ_CFAX_DATA_DDR = 0xff;

    PRJ_CFAX_CONTROL_RSTB_DDR |= ( 1 << PRJ_CFAX_CONTROL_RSTB_PIN );
    PRJ_CFAX_CONTROL_RS_DDR   |= ( 1 << PRJ_CFAX_CONTROL_RS_PIN );
    PRJ_CFAX_CONTROL_RD_DDR   |= ( 1 << PRJ_CFAX_CONTROL_RD_PIN );
    PRJ_CFAX_CONTROL_RW_DDR   |= ( 1 << PRJ_CFAX_CONTROL_RW_PIN );

    //--    Reset the display.

    pin_rstb_off();
    spinwait_delay_ms( 10 );

    pin_rstb_on();
    spinwait_delay_ms( 10 );

    //--    Set up the hardware.

    send_command( CFAX_COMMAND_IDL | 0x00 );
    send_command( CFAX_COMMAND_BAS_ON );

    send_command( CFAX_COMMAND_ADC_ON );    // flip the horizontal pixels

    send_command(0x2F); //Turn on internal power control

    send_command(0x26); //Pseudo-Contrast 7 = dark 6 = OK - First Choice

    //--    Default contrast setting.

    cfax_set_contrast( PRJ_CFAX_DEFAULT_CONTRAST );

    //--    Clear the screen memory.

    cfax_clear_screen();

#if defined( CFAX_INC_ICONS )

    cfax_clear_icons();
    
#endif

    //--    Turn on the display.

    send_command( CFAX_COMMAND_DON_ON );
}


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

void cfax_clear_screen()
{
    //--    Clear all the pixels.

    for ( uint8_t page = 0; page < CFAX_MAX_VERTICAL_PAGES; page++ )
    {
        for ( uint8_t column = CFAX_HORIZONTAL_PIXEL_OFFSET;
             column < ( CFAX_MAX_HORIZONTAL_PIXELS + CFAX_HORIZONTAL_PIXEL_OFFSET );
             column++ )
        {
            write_lcd_page( page, column, 0x00 );
        }
    }

    //--    Clear the icons too, unless there is explicit support for that feature.

#if !defined( PRJ_CFAX_INC_ICONS )

    for ( uint8_t column = CFAX_HORIZONTAL_PIXEL_OFFSET;
         column < ( CFAX_MAX_HORIZONTAL_PIXELS + CFAX_HORIZONTAL_PIXEL_OFFSET );
         column++ )
    {
        write_lcd_page( CFAX_MAX_VERTICAL_PAGES, column, 0x00 );
    }

#endif

}


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

#if defined( PRJ_CFAX_INC_ICONS )

void cfax_clear_icons()
{
    for ( uint8_t column = CFAX_HORIZONTAL_PIXEL_OFFSET;
         column < ( CFAX_MAX_HORIZONTAL_PIXELS + CFAX_HORIZONTAL_PIXEL_OFFSET );
         column++ )
    {
        write_lcd_page( CFAX_MAX_VERTICAL_PAGES, column, 0x00 );
    }
}

#endif


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

void cfax_set_display( bool state )
{
    send_command( state ? CFAX_COMMAND_DON_ON :  CFAX_COMMAND_DON_OFF );
}


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

void cfax_set_contrast( uint8_t contrast )
{
    send_command( CFAX_COMMAND_VREF );  // contrast
    send_command( contrast ); 
}


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

#if defined( PRJ_CFAX_INC_ICONS )

void cfax_set_icon( cfax_icon_t icon_id, bool state )
{
    uint8_t icon_index
        = pgm_read_byte_near( &cfax_icon_ids[ static_cast< uint8_t >( icon_id ) ] );

    write_lcd_page( 8, icon_index, ( state ? 0x01 : 0x00 ) );
}

#endif


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

#if defined( PRJ_CFAX_INC_WRITE_CHAR )

void cfax_write( char c, uint8_t x, uint8_t y )
{
    x %= ( CFAX_MAX_HORIZONTAL_PIXELS / ( sizeof( cfax_glyph_t ) + 1 ) );
    y %= CFAX_MAX_VERTICAL_PAGES;

    // 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 ) ] );

    // the glyph data does not store the blank column on the right edge, so pad by one

    uint8_t pixel_column = x * ( sizeof( cfax_glyph_t ) + 1 );

    // offset due to the flipped horizontal orientation
    pixel_column += CFAX_HORIZONTAL_PIXEL_OFFSET;

    for ( uint8_t i = 0; i < sizeof( cfax_glyph_t ); i++ )
    {
        uint8_t data = pgm_read_byte_near( this_glyph++ );
        write_lcd_page( y, pixel_column++, data );
    }

    write_lcd_page( y, pixel_column, 0x00 );
}

#endif


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

#if defined( PRJ_CFAX_INC_WRITE_CSTRINGS )

void cfax_write( const char* str, uint8_t x, uint8_t y )
{
    while ( *str )
    {
        cfax_write( *str++, x++, y );
    }
}

#endif


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

#if defined( PRJ_CFAX_INC_WRITE_PGM_CSTRINGS )

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

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

        cfax_write( c, x++, y );
    }
}

#endif


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

#if defined( PRJ_CFAX_INC_SET_PIXEL )

void cfax_set_pixel( uint8_t x, uint8_t y, bool state )
{
    x %= CFAX_MAX_HORIZONTAL_PIXELS;
    y %= CFAX_MAX_VERTICAL_PIXELS;

    // offset due to the flipped horizontal orientation
    x += CFAX_HORIZONTAL_PIXEL_OFFSET;

    uint8_t page  = y / 8;
    uint8_t pixel = y % 8;

    uint8_t data = read_lcd_page( page, x );

    if ( state )
    {
        data |= ( 1 << pixel );
    }
    else
    {
        data &= ~( 1 << pixel );
    }

    write_lcd_page( page, x, data );
}

#endif


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

#if defined( PRJ_CFAX_INC_DRAWING )

void cfax_line_h( uint8_t x, uint8_t y, uint8_t len, bool state )
{
    while ( len-- )
    {
        cfax_set_pixel( x++, y, state );
    }
}

#endif


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

#if defined( PRJ_CFAX_INC_DRAWING )

void cfax_line_v( uint8_t x, uint8_t y, uint8_t len, bool state )
{
    while ( len-- )
    {
        cfax_set_pixel( x, y++, state );
    }
}

#endif


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

#if defined( PRJ_CFAX_INC_DRAWING )

void cfax_rect( uint8_t x, uint8_t y, uint8_t w, uint8_t h )
{
    cfax_line_h( x, y, w, true );
    --h;

    uint8_t right_x = x + w - 1;

    while ( h-- )
    {
        cfax_set_pixel( x, y, true );
        cfax_set_pixel( right_x, y, true );
        y++;
    }

    cfax_line_h( x, y, w, true );
}

#endif


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

#if defined( PRJ_CFAX_INC_DRAWING )

void cfax_fill( uint8_t x, uint8_t y, uint8_t w, uint8_t h )
{
    while ( h-- )
    {
        cfax_line_h( x, y++, w, true );
    }
}

#endif


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

#if defined( PRJ_CFAX_INC_DRAWING )

void cfax_erase( uint8_t x, uint8_t y, uint8_t w, uint8_t h )
{
    while ( h-- )
    {
        cfax_line_h( x, y++, w, false );
    }
}

#endif


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

#if defined( PRJ_CFAX_INC_DRAWING )

void cfax_line( uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, bool state )
{
    //--    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;

    cfax_set_pixel( x0, y0, state );

    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;

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

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

            y0 += step_y;
            fraction += dx;

            cfax_set_pixel( x0, y0, state );
        }
    }
}

#endif


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

#if defined( PRJ_CFAX_INC_BITMAPS )

void cfax_bitmap
    (
        uint8_t x,
        uint8_t y,
        uint8_t w,
        uint8_t h,
        const prog_uint8_t* pixels
    )
{
    while ( h-- )
    {
        uint8_t right_x = x + w - 1;
        uint8_t pix_bit = 0x00;
        uint8_t data    = 0x00;

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

            cfax_set_pixel( i, y, ( ( data & pix_bit ) != 0x00 ) );
            pix_bit >>= 1;
        }

        y++;
    }
}

#endif


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

#if defined( PRJ_CFAX_INC_TOUCHSCREEN )

bool cfax_touchscreen( uint8_t* x, uint8_t* y )
{
    //--    Read the x value by flipping TS1 and TS3 to outputs and driving TS3 high.
    //      The x value can be sampled on TS2.

    PRJ_CFAX_TOUCHSCREEN_DDR &=
        ~( ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_2 ) | ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_4 ) );

    PRJ_CFAX_TOUCHSCREEN_DDR |=
        ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_1 ) | ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_3 );

    PRJ_CFAX_TOUCHSCREEN_PORT |= ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_1 );

    PRJ_CFAX_TOUCHSCREEN_PORT &=
        ~( ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_2 )
         | ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_3 )
         | ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_4 ) );

    uint16_t valueX = 0;

    for ( uint8_t i = 0; i < 8; i++ )
    {
        valueX += adc_read( PRJ_CFAX_TOUCHSCREEN_ADC_CH_2 );
    }

    valueX /= 30;   // scaling determined experimentally

    //--    Read the y value by flipping TS2 and TS4 to outputs and driving TS2 high.
    //      The y value can be sampled on TS1.

    PRJ_CFAX_TOUCHSCREEN_DDR &=
        ~( ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_1 ) | ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_3 ) );

    PRJ_CFAX_TOUCHSCREEN_DDR |=
        ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_2 ) | ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_4 );

    PRJ_CFAX_TOUCHSCREEN_PORT |= ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_2 );

    PRJ_CFAX_TOUCHSCREEN_PORT &=
        ~( ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_1 ) 
         | ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_3 ) 
         | ( 1 << PRJ_CFAX_TOUCHSCREEN_PIN_4 ) );

    uint16_t valueY = 0;

    for ( uint8_t i = 0; i < 8; i++ )
    {
        valueY += adc_read( PRJ_CFAX_TOUCHSCREEN_ADC_CH_1 );
    }

    valueY /= 27;   // scaling determined experimentally

    *x = valueX;
    *y = valueY;

    if ( ( valueX < 5 ) && ( valueY < 5 ) )
    {
        return false;
    }

    return true;
}

#endif


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

#endif  // PRJ_CFAX_ENABLE


// ----------------------------------------------------------------------------------------
//  end of cfax.cpp
// ----------------------------------------------------------------------------------------

