/**************************************************************************//**
 * \brief Snootlab Deuligne library
 * \author Copyright (C) 2012  Julien Le Sech - www.idreammicro.com
 * \version 1.0
 * \date 20121026
 *
 * This file is part of the iDreamMicro library.
 *
 * This library is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * This library 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 Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 ******************************************************************************/
/**************************************************************************//**
 * \headerfile deuligne.c
 ******************************************************************************/
/******************************************************************************
 * Header file inclusions.
 ******************************************************************************/
#include "../deuligne.h"
#include <adc/adc.h>
#include <mcp23008/mcp23008.h>
#include <util/delay.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/******************************************************************************
 * Private macro definitions.
 ******************************************************************************/
/**************************************************************************//**
 * \def DEULIGNE__HARDWARE_ADDRESS
 * \brief MCP23008 hardware address. Pins A0 to A3.
 ******************************************************************************/
#define DEULIGNE__HARDWARE_ADDRESS          0x07
#define DEULIGNE__GPIO_E                    4
#define DEULIGNE__GPIO_RW                   5
#define DEULIGNE__GPIO_RS                   6
#define DEULIGNE__GPIO_BACKLIGHT            7
/**************************************************************************//**
 * \def LCD__CMD_CLEAR_DISPLAY
 * \brief Clear display.
 ******************************************************************************/
#define LCD__CMD_CLEAR_DISPLAY              0x01
/**************************************************************************//**
 * \def LCD__CMD_RETURN_HOME
 * \brief Return Home (execution time = 1.52 ms).
 ******************************************************************************/
#define LCD__CMD_RETURN_HOME                0x02
// Function set (execution time = 37 s).
#define LCD__CMD_FUNCTION_SET               0x20
#define LCD__CMD_DATA_LENGTH_4_BIT          0x00
#define LCD__CMD_DATA_LENGTH_8_BIT          0x10
#define LCD__CMD_DISPLAY_LINE_1             0x00
#define LCD__CMD_DISPLAY_LINE_2             0x08
#define LCD__CMD_CHARACTER_FONT_5x8_DOTS    0x00
#define LCD__CMD_CHARACTER_FONT_5x10_DOTS   0x04
// Set entry mode (execution time = 37 s).
#define LCD__CMD_ENTRY_MODE_SET             0x04
#define LCD__CMD_INCREMENT_ADDRESS          0x02
#define LCD__CMD_DECREMENT_ADDRESS          0x00
#define LCD__CMD_DISPLAY_SHIFT_DISABLED     0x00
#define LCD__CMD_DISPLAY_SHIFT_RIGHT        0x01
#define LCD__CMD_DISPLAY_SHIFT_LEFT         0x03
// Display on/off control (execution time = 37 s).
#define LCD__CMD_DISPLAY_CONTROL            0x08
#define LCD__CMD_ENABLE_DISPLAY             0x04
#define LCD__CMD_DISABLE_DISPLAY            0x00
#define LCD__CMD_ENABLE_CURSOR_DISPLAY      0x02
#define LCD__CMD_DISABLE_CURSOR_DISPLAY     0x00
#define LCD__CMD_ENABLE_CURSOR_BLINK        0x01
#define LCD__CMD_DISABLE_CURSOR_BLINK       0x00
// Cursor or display shift (execution time = 37 s).
#define LCD__CMD_CURSOR_OR_DISPLAY_SHIFT    0x10
#define LCD__CMD_SHIFT_CURSOR               0x00
#define LCD__CMD_SHIFT_DISPLAY              0x08
#define LCD__CMD_SHIFT_RIGHT                0x04
#define LCD__CMD_SHIFT_LEFT                 0x00
// CGRAM address.
#define LCD__CMD_SET_CGRAM_ADDRESS          0x40
// DDRAM address.
#define LCD__CMD_SET_DDRAM_ADDRESS          0x80
// Line addresses.
#define LCD__LINE_1_ADDRESS                 0x40
#define LCD__LINE_2_ADDRESS                 0x80
#define LCD__LINE_3_ADDRESS                 0x54
#define LCD__LINE_4_ADDRESS                 0x94
/******************************************************************************
 * Private function prototypes.
 ******************************************************************************/
/**************************************************************************//**
 * \fn static void deuligne__write_command(uint8_t command)
 *
 * \brief Write a command on LCD port.
 *
 * \param command   Command to write.
 ******************************************************************************/
static
void
deuligne__write_command
(
    uint8_t command
);
 /**************************************************************************//**
  * \fn static void deuligne__write_byte(uint8_t data)
  *
  * \brief Write a byte on LCD port.
  *
  * \param data  Byte to write.
  ******************************************************************************/
static
void
deuligne__write_byte
(
    uint8_t data
);
/**************************************************************************//**
 * \fn static void deuligne__write_high_nibble(uint8_t data)
 *
 * \brief Write the high nibble of a byte on LCD port.
 *
 * \param data  Byte which write high nibble.
 ******************************************************************************/
static
void
deuligne__write_high_nibble
(
    uint8_t data
);
/**************************************************************************//**
 * \fn static void deuligne__enable_data(void)
 *
 * \brief Enable data on LCD port.
 ******************************************************************************/
static
void
deuligne__enable_data
(
    void
);
/******************************************************************************
 * Private constant definitions.
 ******************************************************************************/
/**************************************************************************//**
 *
 ******************************************************************************/
static const uint16_t deuligne__adc_values
[] =
{
    [DEULIGNE__KEY__RIGHT
]  = 50,
    [DEULIGNE__KEY__UP
]     = 190,
    [DEULIGNE__KEY__DOWN
]   = 400,
    [DEULIGNE__KEY__LEFT
]   = 540,
    [DEULIGNE__KEY__SELECT
] = 770,
    [DEULIGNE__KEY__NONE
]   = 1024
};
/******************************************************************************
 * Public function definitions.
 ******************************************************************************/
/**************************************************************************//**
 * \fn void deuligne__initialize(void)
 *
 * \brief Initialize Deuligne.
 ******************************************************************************/
void
deuligne__initialize
(
    void
){
    // Initialize MCP23008.
    mcp23008__initialize
(DEULIGNE__HARDWARE_ADDRESS
);
    // Configure all GPIOs in output.
    mcp23008__configure_port
(0x00);
    mcp23008__set_pin_level
(DEULIGNE__GPIO_RW
, MCP23008__LEVEL__LOW
);
    // Wait for LCD display power-up initialization.
    _delay_ms
(45);
    // Initialize LCD display.
    // Set interface.
    deuligne__write_high_nibble
(LCD__CMD_FUNCTION_SET 
| LCD__CMD_DATA_LENGTH_8_BIT
);
    deuligne__write_high_nibble
(LCD__CMD_FUNCTION_SET 
| LCD__CMD_DATA_LENGTH_8_BIT
);
    deuligne__write_high_nibble
(LCD__CMD_FUNCTION_SET 
| LCD__CMD_DATA_LENGTH_8_BIT
);
    deuligne__write_high_nibble
(LCD__CMD_FUNCTION_SET 
| LCD__CMD_DATA_LENGTH_4_BIT
);
    // Set function.
    deuligne__set_function
(DEULIGNE__DISPLAY_LINE_2
, DEULIGNE__CHARACTER_FONT_5x8_DOTS
);
    // Set entry mode.
    deuligne__set_entry_mode
(DEULIGNE__INCREMENT_ADDRESS
, DEULIGNE__DISPLAY_SHIFT_DISABLED
);
    // Clear display.
    deuligne__clear_display
();
    // Initialize ADC.
    adc__single_channel_initialize
(ADC__CHANNEL_0
);
}
/**************************************************************************//**
 * \fn void deuligne__switch_on_backlight(void)
 *
 * \brief Switch on backlight.
 ******************************************************************************/
void
deuligne__switch_on_backlight
(
    void
){
    mcp23008__set_pin_level
(DEULIGNE__GPIO_BACKLIGHT
, MCP23008__LEVEL__HIGH
);
}
/**************************************************************************//**
 * \fn void deuligne__switch_off_backlight(void)
 *
 * \brief Switch off backlight.
 ******************************************************************************/
void
deuligne__switch_off_backlight
(
    void
){
    mcp23008__set_pin_level
(DEULIGNE__GPIO_BACKLIGHT
, MCP23008__LEVEL__LOW
);
}
/**************************************************************************//**
 * \fn void deuligne__set_function(
 * deuligne__display_line_t     display_line,
 * deuligne__character_font_t   character_font)
 *
 * \brief Set the number of lines available on LCD display and the character font.
 *
 * \param display_line number of lines
 * \param character_font character font
 ******************************************************************************/
void
deuligne__set_function
(
    deuligne__display_line_t     display_line
,
    deuligne__character_font_t   character_font
){
    uint8_t command 
= LCD__CMD_FUNCTION_SET
;
    switch (display_line
)
    {
        case DEULIGNE__DISPLAY_LINE_1
:
        {
            command 
|= LCD__CMD_DISPLAY_LINE_1
;
        }
        break;
        case DEULIGNE__DISPLAY_LINE_2
:
        {
            command 
|= LCD__CMD_DISPLAY_LINE_2
;
        }
        break;
        default:
        break;
    }
    switch (character_font
)
    {
        case DEULIGNE__CHARACTER_FONT_5x8_DOTS
:
        {
            command 
|= LCD__CMD_CHARACTER_FONT_5x8_DOTS
;
        }
        break;
        case DEULIGNE__CHARACTER_FONT_5x10_DOTS
:
        {
            command 
|= LCD__CMD_CHARACTER_FONT_5x10_DOTS
;
        }
        break;
        default:
        break;
    }
    deuligne__write_command
(command
);
}
/**************************************************************************//**
 * \fn void deuligne__set_entry_mode(
 * deuligne__address_mode_t     address_mode,
 * deuligne__display_shift_t    display_shift)
 *
 * \brief Set LCD display entry mode.
 *
 * \param address_mode specify if DDRAM address is incremented or decremented
 * when a character code is written
 * \param display_shift set the display shift when a character code is written
 ******************************************************************************/
void
deuligne__set_entry_mode
(
    deuligne__address_mode_t     address_mode
,
    deuligne__display_shift_t    display_shift
){
    uint8_t command 
= LCD__CMD_ENTRY_MODE_SET
;
    switch (address_mode
)
    {
        case DEULIGNE__INCREMENT_ADDRESS
:
        {
            command 
|= LCD__CMD_INCREMENT_ADDRESS
;
        }
        break;
        case DEULIGNE__DECREMENT_ADDRESS
:
        {
            command 
|= LCD__CMD_DECREMENT_ADDRESS
;
        }
        break;
        default:
        break;
    }
    switch (display_shift
)
    {
        case DEULIGNE__DISPLAY_SHIFT_DISABLED
:
        {
            command 
|= LCD__CMD_DISPLAY_SHIFT_DISABLED
;
        }
        break;
        case DEULIGNE__DISPLAY_SHIFT_RIGHT
:
        {
            command 
|= LCD__CMD_DISPLAY_SHIFT_RIGHT
;
        }
        break;
        case DEULIGNE__DISPLAY_SHIFT_LEFT
:
        {
            command 
|= LCD__CMD_DISPLAY_SHIFT_LEFT
;
        }
        break;
        default:
        break;
    }
    deuligne__write_command
(command
);
}
/**************************************************************************//**
 * \fn void deuligne__set_display(
 * bool enable_display,
 * bool enable_cursor_display,
 * bool enable_cursor_blink)
 *
 * \brief Set LCD display.
 * Enable or disable display, cursor display and cursor blink.
 *
 * \param enable_display true to enable display or false in contrary case
 * \param enable_cursor_display true to enable cursor display or false in
 * contrary case
 * \param enable_cursor_blink true to enable cursor blink or false in contrary
 * case
 ******************************************************************************/
void
deuligne__set_display
(
    bool enable_display
,
    bool enable_cursor_display
,
    bool enable_cursor_blink
){
    uint8_t command 
= LCD__CMD_DISPLAY_CONTROL
;
    if (enable_display
)
    {
        command 
|= LCD__CMD_ENABLE_DISPLAY
;
    }
    if (enable_cursor_display
)
    {
        command 
|= LCD__CMD_ENABLE_CURSOR_DISPLAY
;
    }
    if (enable_cursor_blink
)
    {
        command 
|= LCD__CMD_ENABLE_CURSOR_BLINK
;
    }
    deuligne__write_command
(command
);
}
/**************************************************************************//**
 * \fn void deuligne__clear_display(void)
 *
 * \brief Clear LCD display.
 * Wait time of 2 ms.
 ******************************************************************************/
void
deuligne__clear_display
(
    void
){
    deuligne__write_command
(LCD__CMD_CLEAR_DISPLAY
);
    _delay_ms
(2);
}
/**************************************************************************//**
 * \fn void deuligne__return_home(void)
 *
 * \brief Set cursor on the first column of the first line.
 * Wait time of 2 ms.
 ******************************************************************************/
void
deuligne__return_home
(
    void
){
    deuligne__write_command
(LCD__CMD_RETURN_HOME
);
    _delay_ms
(2);
}
/**************************************************************************//**
 *\fn void deuligne__set_cursor_position(
 * deuligne__line_t line,
 * uint8_t          column)
 *
 * \brief Set cursor position.
 *
 * \param line destination line
 * \param column destination column
 ******************************************************************************/
void
deuligne__set_cursor_position
(
    deuligne__line_t    line
,
    uint8_t             column
){
    uint8_t address 
= 0;
    switch (line
)
    {
        case DEULIGNE__LINE_1
:
        {
            address 
= LCD__LINE_1_ADDRESS
;
        }
        break;
        case DEULIGNE__LINE_2
:
        {
            address 
= LCD__LINE_2_ADDRESS
;
        }
        break;
        case DEULIGNE__LINE_3
:
        {
            address 
= LCD__LINE_3_ADDRESS
;
        }
        break;
        case DEULIGNE__LINE_4
:
        {
            address 
= LCD__LINE_4_ADDRESS
;
        }
        break;
        default:
        {
            address 
= LCD__LINE_1_ADDRESS
;
        }
        break;
    }
    address 
+= column
;
    deuligne__write_command
(LCD__CMD_SET_CGRAM_ADDRESS 
+ address
);
}
/**************************************************************************//**
 * \fn void deuligne__shift_display(deuligne__shift_direction_t shift_direction)
 *
 * \brief Shift the display on the right or on the left.
 *
 * \param shift_direction display shift direction
 ******************************************************************************/
void
deuligne__shift_display
(
    deuligne__shift_direction_t shift_direction
){
    uint8_t command 
= LCD__CMD_CURSOR_OR_DISPLAY_SHIFT 
| LCD__CMD_SHIFT_DISPLAY
;
    switch (shift_direction
)
    {
        case DEULIGNE__SHIFT_DIRECTION_RIGHT
:
        {
            command 
|= LCD__CMD_SHIFT_RIGHT
;
        }
        break;
        case DEULIGNE__SHIFT_DIRECTION_LEFT
:
        {
            command 
|= LCD__CMD_SHIFT_LEFT
;
        }
        break;
        default:
        break;
    }
    deuligne__write_command
(command
);
}
/**************************************************************************//**
 * \fn void deuligne__shift_cursor(deuligne__shift_direction_t shift_direction)
 *
 * \brief Shift the cursor on the right or on the left.
 *
 * \param shift_direction cursor shift direction
 ******************************************************************************/
void
deuligne__shift_cursor
(
    deuligne__shift_direction_t shift_direction
){
    uint8_t command 
= LCD__CMD_CURSOR_OR_DISPLAY_SHIFT 
| LCD__CMD_SHIFT_CURSOR
;
    switch (shift_direction
)
    {
        case DEULIGNE__SHIFT_DIRECTION_RIGHT
:
        {
            command 
|= LCD__CMD_SHIFT_RIGHT
;
        }
        break;
        case DEULIGNE__SHIFT_DIRECTION_LEFT
:
        {
            command 
|= LCD__CMD_SHIFT_LEFT
;
        }
        break;
        default:
        break;
    }
    deuligne__write_command
(command
);
}
/**************************************************************************//**
 * \fn void deuligne__write_char(char data)
 *
 * \brief Write a character on LCD.
 *
 * \param data  Character to write.
 ******************************************************************************/
void
deuligne__write_char
(
    char data
){
    mcp23008__set_pin_level
(DEULIGNE__GPIO_RS
, MCP23008__LEVEL__HIGH
);
    deuligne__write_byte
(data
);
}
/**************************************************************************//**
 * \fn void deuligne__write_string(const char* string_to_write)
 *
 * \brief Write a string on LCD display from current cursor position.
 *
 * \param string_to_write string to write
 ******************************************************************************/
void
deuligne__write_string
(
    const char* string_to_write
){
    // Check the preconditions.
    assert(NULL 
!= string_to_write
);
    while (*string_to_write 
!= '\0')
    {
        deuligne__write_char
(*string_to_write
++);
    }
}
/**************************************************************************//**
 * \fn void deuligne__get_key(void)
 *
 * \brief Get key.
 *
 * \return Key.
 ******************************************************************************/
deuligne__key_t
deuligne__get_key
(
    void
){
    deuligne__key_t key 
= DEULIGNE__KEY__NONE
;
    // Read ADC0 value.
    uint16_t value 
= adc__single_channel_read
();
    // Find key.
    for (deuligne__key_t i 
= DEULIGNE__KEY__RIGHT
; i 
<= DEULIGNE__KEY__NONE
; i
++)
    {
        if (value 
< deuligne__adc_values
[i
])
        {
            key 
= i
;
            break;
        }
    }
    return key
;
}
/******************************************************************************
 * Private function definitions.
 ******************************************************************************/
/**************************************************************************//**
 * \fn static void deuligne__write_command(uint8_t command)
 *
 * \brief Write a command on LCD port.
 *
 * \param command   Command to write.
 ******************************************************************************/
static
void
deuligne__write_command
(
    uint8_t command
){
    mcp23008__set_pin_level
(DEULIGNE__GPIO_RS
, MCP23008__LEVEL__LOW
);
    deuligne__write_byte
(command
);
}
/**************************************************************************//**
 * \fn static void deuligne__write_byte(uint8_t data)
 *
 * \brief Write a byte on LCD port.
 *
 * \param data  Byte to write.
 ******************************************************************************/
static
void
deuligne__write_byte
(
    uint8_t data
){
    // Get current port value.
    uint8_t port_value 
= mcp23008__get_port_value
() & 0xF0;
    // Write high nibble.
    port_value 
|= data 
>> 4;
    mcp23008__set_port_value
(port_value
);
    deuligne__enable_data
();
    _delay_ms
(1);
    // Write low nibble.
    port_value 
&= 0xF0;
    port_value 
|= data 
& 0x0F;
    mcp23008__set_port_value
(port_value
);
    deuligne__enable_data
();
    _delay_ms
(1);
}
/**************************************************************************//**
 * \fn static void deuligne__write_high_nibble(uint8_t data)
 *
 * \brief Write the high nibble of a byte on LCD port.
 *
 * \param data  Byte which write high nibble.
 ******************************************************************************/
static
void
deuligne__write_high_nibble
(
    uint8_t data
){
    // Get current port value.
    uint8_t port_value 
= mcp23008__get_port_value
() & 0xF0;
    // Write high nibble.
    port_value 
|= data 
>> 4;
    mcp23008__set_port_value
(port_value
);
    deuligne__enable_data
();
    _delay_ms
(1);
}
/**************************************************************************//**
 * \fn static void deuligne__enable_data(void)
 *
 * \brief Enable data on LCD port.
 ******************************************************************************/
static
void
deuligne__enable_data
(
    void
){
    mcp23008__set_pin_level
(DEULIGNE__GPIO_E
, MCP23008__LEVEL__HIGH
);
    _delay_ms
(1);
    mcp23008__set_pin_level
(DEULIGNE__GPIO_E
, MCP23008__LEVEL__LOW
);
    _delay_ms
(1);
}