/*!
* \file alcd.c
* \brief
* A target independent Alpharithmetic LCD driver
*
* Copyright (C) 2014 Houtouridis Christos (http://www.houtouridis.net)
*
* This program 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 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 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 .
*
*/
#include "alcd.h"
static int _inc_x (alcd_t *alcd);
static int _dec_x (alcd_t *alcd);
static void _inc_y (alcd_t *alcd);
static void _dec_y (alcd_t *alcd);
static void _set_bus (alcd_t *alcd, int8_t db);
static void _write_data (alcd_t *alcd, int8_t data);
static void _command (alcd_t *alcd, uint8_t c);
static void _character (alcd_t *alcd, uint8_t c);
static void _set_cursor (alcd_t *alcd, uint8_t x, uint8_t y);
/*!
* \brief
* increase cursor's x position. Positions start from 1.
* If the cursor loops returns 1, else returns 0.
* \param alcd pointer to active alcd.
* \return Change line status
*/
static int _inc_x (alcd_t *alcd) {
int ret=0;
if (++alcd->c.x > alcd->columns) {
alcd->c.x = 1;
ret = 1;
}
return ret;
}
/*!
* \brief
* Decrease cursor's x position. Positions start from 1.
* If the cursor loops returns 1, else returns 0.
* \param alcd pointer to active alcd.
* \return none
*/
static int _dec_x (alcd_t *alcd) {
int ret = 0;
if (--alcd->c.x < 1) {
alcd->c.x = alcd->columns;
ret = 1;
}
return ret;
}
/*!
* \brief
* increase cursor's y position. Positions start from 1.
* \param alcd pointer to active alcd.
* \return none
*/
static void _inc_y (alcd_t *alcd) {
if (++alcd->c.y > alcd->lines) {
alcd->c.y = 1;
}
}
/*!
* \brief
* Decrease cursor's y position. Positions start from 1.
* \param alcd pointer to active alcd.
* \return none
*/
static void _dec_y (alcd_t *alcd) {
if (--alcd->c.y < 1) {
alcd->c.y = alcd->lines;
}
}
/*!
* \brief
* Update the bus I/O and send it to the device.
* \param alcd pointer to active alcd.
* \param db the bus data.
* \return none
*/
static void _set_bus (alcd_t *alcd, int8_t db)
{
alcd->io.db4 (db & 0x01); //Update port
alcd->io.db5 (db & 0x02);
alcd->io.db6 (db & 0x04);
alcd->io.db7 (db & 0x08);
jf_delay_us (10); // Wait to settle
alcd->io.en (1); // Pulse out the data
jf_delay_us (10);
alcd->io.en (0);
jf_delay_us (10); // Data hold
}
/*!
* \brief
* Write the byte date to the bus using _set_bus ()
* \param alcd pointer to active alcd.
* \param data the data byte.
* \return none
*/
static void _write_data (alcd_t *alcd, int8_t data)
{
_set_bus (alcd, data >> 4);
_set_bus (alcd, data & 0x0F);
}
/*!
* \brief
* Send a command to alcd
* \param alcd pointer to active alcd.
* \param c the command byte.
* \return none
*/
static void _command (alcd_t *alcd, uint8_t c)
{
alcd->io.rs(0); // Enter command mode
jf_delay_us (100); // Wait
_write_data (alcd, c); // Send
}
/*!
* \brief
* Send a character to alcd
* \param alcd pointer to active alcd.
* \param c the character byte.
* \return none
*/
static void _character (alcd_t *alcd, uint8_t c)
{
alcd->io.rs(1); // Enter character mode
jf_delay_us (100); // Wait
_write_data (alcd, c); // Send
}
/*!
* \brief
* Set the Cursor to LCD's position line (y), column (x) starts from 1,2,...n
* \param alcd pointer to active alcd.
* \param x the x position.
* \param y the y position.
* \return none
*/
static void _set_cursor (alcd_t *alcd, uint8_t x, uint8_t y)
{
uint8_t cmd;
alcd->c.x = x; // Update alcd data
alcd->c.y = y;
// Calculate address
switch (y) {
default:
case 1: cmd = 0x0; break;
case 2: cmd = 0x40; break;
case 3: cmd = 0x0 + alcd->columns; break;
case 4: cmd = 0x40 + alcd->columns; break;
}
cmd |= (x - 1);
// Calculate command
cmd |= LCD_DDRAMMask;
// Command out the alcd
_command (alcd, cmd);
}
/*
* ============================ Public Functions ============================
*/
/*
* Link and Glue functions
*/
/*!
* \brief
* Link driver's db4 pin function to io struct.
* \param alcd pointer to active alcd.
* \param pfun driver's DB4 pin function
* \return none
*/
inline void alcd_link_db4 (alcd_t *alcd, drv_pinout_ft pfun) { alcd->io.db4 = pfun; }
/*!
* \brief
* Link driver's db4 pin function to io struct.
* \param alcd pointer to active alcd.
* \param pfun driver's DB5 pin function
* \return none
*/
inline void alcd_link_db5 (alcd_t *alcd, drv_pinout_ft pfun) { alcd->io.db5 = pfun; }
/*!
* \brief
* Link driver's db4 pin function to io struct.
* \param alcd pointer to active alcd.
* \param pfun driver's DB6 pin function
* \return none
*/
inline void alcd_link_db6 (alcd_t *alcd, drv_pinout_ft pfun) { alcd->io.db6 = pfun; }
/*!
* \brief
* Link driver's db4 pin function to io struct.
* \param alcd pointer to active alcd.
* \param pfun driver's DB7 pin function
* \return none
*/
inline void alcd_link_db7 (alcd_t *alcd, drv_pinout_ft pfun) { alcd->io.db7 = pfun; }
/*!
* \brief
* Link driver's db4 pin function to io struct.
* \param alcd pointer to active alcd.
* \param pfun driver's RS pin function
* \return none
*/
inline void alcd_link_rs (alcd_t *alcd, drv_pinout_ft pfun) { alcd->io.rs = pfun; }
/*!
* \brief
* Link driver's db4 pin function to io struct.
* \param alcd pointer to active alcd.
* \param pfun driver's EN pin function
* \return none
*/
inline void alcd_link_en (alcd_t *alcd, drv_pinout_ft pfun) { alcd->io.en = pfun; }
/*!
* \brief
* Link driver's db4 pin function to io struct.
* \param alcd pointer to active alcd.
* \param pfun driver's BL pin function
* \return none
*/
inline void alcd_link_bl (alcd_t *alcd, drv_pinout_ft pfun) { alcd->io.bl = pfun; }
/*!
* \brief
* Send an ascii character to alcd.
* \param alcd pointer to active alcd.
* \param ch the character to send
* \return the character send.
*
* \note
* This is the driver's "putchar()" functionality to glue.
* Tailor this function to redirect stdout to alcd.
*/
int alcd_putchar (alcd_t *alcd, int ch)
{
alcd->status = DRV_BUSY;
// LCD Character dispatcher
switch (ch) {
case 0:
// don't send null termination to device
break;
case '\n':
_inc_y (alcd);
//break; This "no break" is intentional
case '\r':
_set_cursor (alcd, 1, alcd->c.y);
break;
case '\v':
alcd->c.x = alcd->c.y = 1;
_command (alcd, LCD_RETHOME);
jf_delay_us(2000);
break;
case '\f':
//alcd->c.x = alcd->c.y = 1;
_set_cursor (alcd, 1, 1);
//_command (alcd, LCD_CLRSCR);
//jf_delay_us(5000);
//_command (alcd, LCD_RETHOME);
//_set_cursor (alcd, alcd->c.x, alcd->c.y);
jf_delay_us(2000);
break;
case '\b':
if (_dec_x (alcd)) _dec_y (alcd);
_set_cursor (alcd, alcd->c.x, alcd->c.y);
_character (alcd, ' ');
_set_cursor (alcd, alcd->c.x, alcd->c.y);
break;
default:
_character (alcd, ch);
// Increase cursor and loop inside the same line
if (_inc_x (alcd)) _set_cursor (alcd, alcd->c.x, alcd->c.y);
break;
}
// Restore status
alcd->status = DRV_READY;
//ANSI C (C99) compatible mode
return ch;
}
/*
* Set functions
*/
/*!
* \brief
* Set the number of lines for the attached lcd display
* \param alcd pointer to active alcd.
* \param lines The number of lines (usually 2 or 4)
* \return None.
*/
void alcd_set_lines (alcd_t *alcd, int lines) {
alcd->lines = lines;
}
/*!
* \brief
* Set the number of columns for the attached lcd display
* \param alcd pointer to active alcd.
* \param lines The number of columns (usually 16 or 20)
* \return None.
*/
void alcd_set_columns (alcd_t *alcd, int columns) {
alcd->columns = columns;
}
/*
* User Functions
*/
/*!
* \brief
* De-initialize the alcd.
* \param alcd pointer to active alcd.
* \return none
*/
void alcd_deinit (alcd_t *alcd)
{
memset ((void*)alcd, 0, sizeof (alcd_t));
/*!<
* This leaves the status DRV_NOINIT
*/
}
/*!
* \brief
* Initialize the alcd.
* \param alcd pointer to active alcd.
* \return Zero on success, non zero on error
*/
drv_status_en alcd_init (alcd_t *alcd, alcd_funset_en fs)
{
#define _lcd_assert(_x) if (!_x) return alcd->status = DRV_ERROR;
drv_status_en st = jf_probe ();
if (st == DRV_NODEV || st == DRV_BUSY)
return alcd->status = DRV_ERROR;
_lcd_assert (alcd->io.db4);
_lcd_assert (alcd->io.db5);
_lcd_assert (alcd->io.db6);
_lcd_assert (alcd->io.db7);
_lcd_assert (alcd->io.rs);
_lcd_assert (alcd->io.en);
//_lcd_assert (alcd->io.bl);
/*
* We are here, so all its OK. We can (re)initialize alcd.
*/
alcd->status = DRV_NOINIT;
alcd->c.x = alcd->c.y = 1;
alcd->io.en (0);
alcd->io.rs (0);
jf_delay_us (100000);
//Pre-Init phase 8bit at this point
_set_bus (alcd, 0x3);
jf_delay_us(50000);
_set_bus (alcd, 0x3);
jf_delay_us(5000);
_set_bus (alcd, 0x3);
jf_delay_us(5000);
_set_bus (alcd, 0x2); //4bit selection
jf_delay_us(10000);
_command (alcd, fs); //4bit selection and Function Set
jf_delay_us(5000);
_command (alcd, LCD_DISP_OFF); //Display Off Control 4bit for now on
jf_delay_us(5000);
_command (alcd, LCD_CLRSCR); //Clear Display
jf_delay_us(5000);
_command (alcd, LCD_ENTRYMODE); //Entry Mode Set
jf_delay_us(5000);
_command (alcd, LCD_RETHOME);
jf_delay_us(10000);
_command (alcd, LCD_DISP_ON);
jf_delay_us(5000);
//alcd_backlight (alcd, 1);
return alcd->status = DRV_READY;
#undef _lcd_assert
}
/*!
* \brief
* Enables and disables the lcd backlight.
* \param alcd pointer to active alcd.
* \param on
* \arg 0 disable the backlight
* \arg 1 enable the backlight
* \return none
*/
void alcd_backlight (alcd_t *alcd, uint8_t on) {
if (alcd->io.bl)
alcd->io.bl ((on)?1:0);
}
/*!
* \brief
* Enables and disables the entire lcd (+backlight).
* \param alcd pointer to active alcd.
* \param on
* \arg 0 disable the backlight
* \arg 1 enable the backlight
* \return none
*/
void alcd_enable (alcd_t *alcd, uint8_t on)
{
if (on) {
_command (alcd, LCD_DISP_ON);
alcd_backlight (alcd, 1);
} else {
_command (alcd, LCD_DISP_OFF);
alcd_backlight (alcd, 0);
}
}
/*!
* \brief
* Clears screen and returns cursor at home position (1,1).
* \param alcd pointer to active alcd.
* \return none
*/
void alcd_cls (alcd_t *alcd)
{
_command(alcd, LCD_CLRSCR);
jf_delay_us(2000);
_command (alcd, LCD_RETHOME);
jf_delay_us(2000);
}
/*!
* \brief
* Shift alcd left or right for a \a pos characters.
* \param alcd pointer to active alcd.
* \param pos The number of position to shift.
* A positive number shifts lcd data to left, so screen shows the data in the right.
* A negative number shifts lcd data to right, so screen shows the data in the left.
* \return none
*/
void alcd_shift (alcd_t *alcd, int pos)
{
uint8_t i, cmd = LCD_SHIFT_LEFT;
if (pos<0) {
pos = -pos;
cmd = LCD_SHIFT_RIGHT;
}
for (i=0 ; i