Subversion Repositories idreammicro-avr

Rev

Blame | Last modification | View Log | Download | RSS feed

/**************************************************************************//**
 * \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);
}