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