/*
* LCDi2c4bit.cpp: part of the "VoluMaster(tm)" system
*
* An open-source arduino controller-based digital volume control and input/output selector
*
*
* LICENSE
* -------
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "LCDi2c4bit.h"
#include
#include // not needed yet
#include // needed for strlen()
#include
#include "WConstants.h" // all things wiring / arduino
#include "config.h"
extern uint8_t lcd_in_use_flag;
#define OUTLINE_TOP 159
#define NO_OUTLINE_TOP 128
const uint8_t graph_horiz_line_pre[] = { NO_OUTLINE_TOP,128,159,159,159,159,128,NO_OUTLINE_TOP };
const uint8_t graph_horiz_line_post[] = { NO_OUTLINE_TOP,128,128,149,149,128,128,NO_OUTLINE_TOP };
// special start-char
const uint8_t graph_start[] = { NO_OUTLINE_TOP,128,128,149,149,128,128,NO_OUTLINE_TOP };
const uint8_t graph_end[] = { NO_OUTLINE_TOP,128,159,159,159,159,128,NO_OUTLINE_TOP };
const uint8_t graph_b1[] = { NO_OUTLINE_TOP,128,144,149,149,144,128,NO_OUTLINE_TOP };
const uint8_t graph_b2[] = { NO_OUTLINE_TOP,128,152,157,157,152,128,NO_OUTLINE_TOP };
const uint8_t graph_b3[] = { NO_OUTLINE_TOP,128,156,157,157,156,128,NO_OUTLINE_TOP };
const uint8_t graph_b4[] = { NO_OUTLINE_TOP,128,158,159,159,158,128,NO_OUTLINE_TOP };
const uint8_t graph_b5[] = { NO_OUTLINE_TOP,128,159,159,159,159,128,NO_OUTLINE_TOP };
// these 2 can share the same 'cell' since only 1 is on at a time
const uint8_t graph_play[] = { 136,140,142,143,142,140,136,128 };
const uint8_t graph_pause[] = { 155,155,155,155,155,155,155,128 };
/*
* LCD graphics for bar-graphs is 'boxed' style
*/
// empty box
const uint8_t boxed_graph_b0[] = { OUTLINE_TOP,128,128,128,128,128,128,OUTLINE_TOP };
// the graph values, inside a char-cell
const uint8_t boxed_graph_b1[] = { OUTLINE_TOP,144,144,144,144,144,144,OUTLINE_TOP };
const uint8_t boxed_graph_b2[] = { OUTLINE_TOP,136,136,136,136,136,136,OUTLINE_TOP };
const uint8_t boxed_graph_b3[] = { OUTLINE_TOP,132,132,132,132,132,132,OUTLINE_TOP };
const uint8_t boxed_graph_b4[] = { OUTLINE_TOP,130,130,130,130,130,130,OUTLINE_TOP };
const uint8_t boxed_graph_b5[] = { OUTLINE_TOP,129,129,129,129,129,129,OUTLINE_TOP };
LCDI2C4Bit::LCDI2C4Bit( uint8_t devI2CAddress, uint8_t num_lines, uint8_t lcdwidth, uint8_t backlightPin )
{
myNumLines = num_lines;
myWidth = lcdwidth;
myAddress = lcd_i2c_address = devI2CAddress;
myBacklightPin = backlightPin;
myInputKeysMask = 0x00; // default
myBacklight_max = DEFAULT_BL_LEVEL;
myBacklight_min = MIN_BL_LEVEL;
backlight_currently_on = 1; // boolean
one_second_counter_ts = 0;
seconds = 0;
lcd_inactivity_timeout = 4; // in seconds: how long do we keep the 'bright' setting before we fade
backlight_admin = 0; // administratively set (enable auto timeout; normal mode)
/*
* setup and define our pins. usually a good idea toward the top of each program.
*/
pinMode(myBacklightPin, OUTPUT); // pwm backlight
}
void
LCDI2C4Bit::SetInputKeysMask( uint8_t input_keys_mask )
{
myInputKeysMask = input_keys_mask;
}
// TODO: use the separate 'mcp23xx' class
void
LCDI2C4Bit::SetMCPReg( uint8_t reg, uint8_t val )
{
Wire.beginTransmission(lcd_i2c_address);
Wire.send(reg);
Wire.send(val);
Wire.endTransmission();
}
uint8_t
LCDI2C4Bit::GetMCPReg( uint8_t reg )
{
uint8_t val = 0;
Wire.beginTransmission(lcd_i2c_address);
Wire.send(reg);
Wire.endTransmission();
val = Wire.requestFrom((uint8_t)lcd_i2c_address, (uint8_t)1); // read 1 byte
if (Wire.available()) {
val = Wire.receive();
}
return val;
}
uint8_t
LCDI2C4Bit::ReadInputKeys( void )
{
uint8_t data;
data = GetMCPReg(MCP_REG_GPIO);
return data;
}
void
LCDI2C4Bit::SendToLCD( uint8_t data )
{
data |= dataPlusMask;
SetMCPReg(MCP_REG_OLAT, data);
delayMicroseconds(6);
data ^= 0x80; // Enable bit ON on LCD
SetMCPReg(MCP_REG_OLAT, data);
delayMicroseconds(6);
data ^= 0x80; // Enable bit OFF on LCD
SetMCPReg(MCP_REG_OLAT, data);
delayMicroseconds(6);
}
void
LCDI2C4Bit::WriteLCDByte( uint8_t bdata )
{
lcd_in_use_flag = 1; // set a mutex so that the IR isr won't do anything while we are doing lcd i/o
SendToLCD( bdata >> 4 );
delayMicroseconds(6);
SendToLCD( bdata & 0x0F );
delayMicroseconds(6);
lcd_in_use_flag = 0; // clear that mutex (so that the IR isr CAN now do things)
}
void
LCDI2C4Bit::init( void )
{
lcd_in_use_flag = 1; // set a mutex so that the IR isr won't do anything while we are doing lcd i/o
dataPlusMask = 0;
SetMCPReg(MCP_REG_IOCON, 0x0C);
delay(50);
SetMCPReg(MCP_REG_IODIR, myInputKeysMask);
delay(50);
SetMCPReg(MCP_REG_GPPU, myInputKeysMask);
delay(50);
SendToLCD(0x03);
delay(5);
SendToLCD(0x03);
delayMicroseconds(100);
SendToLCD(0x03);
delay(5);
SendToLCD(0x02);
WriteLCDByte(0x28);
WriteLCDByte(0x08);
WriteLCDByte(0x0C); // turn on, cursor off, no blinking
delayMicroseconds(60);
WriteLCDByte(0x01); // clear display
delay(5);
lcd_in_use_flag = 0; // clear that mutex (so that the IR isr CAN now do things)
}
void
LCDI2C4Bit::backLight( uint8_t value )
{
analogWrite(myBacklightPin, value); // 0..255 gets us 'dark' to 'very bright'
}
void
LCDI2C4Bit::write( uint8_t value )
{
dataPlusMask |= 0x10; // RS
WriteLCDByte(value);
dataPlusMask ^= 0x10; // RS
}
void
LCDI2C4Bit::print( char value[] )
{
for ( char *p = value; *p != 0; p++ ) {
write(*p);
}
}
void
LCDI2C4Bit::clear( void )
{
command(CMD_CLR);
}
void
LCDI2C4Bit::cursorTo( uint8_t row, uint8_t col )
{
int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
command(0x80 | (col + row_offsets[row]));
}
void
LCDI2C4Bit::command( uint8_t command )
{
// RS - leave low
WriteLCDByte(command);
//delayMicroseconds(500);
delayMicroseconds(700);
}
void
LCDI2C4Bit::LCD_send_string( const char *str, const uint8_t addr )
{
// Send string at addr, if addr <> 0, or cursor position if addr == 0
if (addr != 0) {
command(addr);
}
uint8_t i = 0;
while (str[i] != 0) {
write(str[i++]);
}
}
void
LCDI2C4Bit::LCD_cgram_load_normal_bargraph( void )
{
uint8_t i;
/*
* load special graphic chars into 'cgram'
*/
command(0x40); // start off writing to CG RAM char 0
// slot-0
for (i=0; i<8; i++) {
write(graph_play[i]);
}
// slot-1
for (i=0; i<8; i++) {
write(graph_horiz_line_pre[i]);
}
// slot-2
for (i=0; i<8; i++) {
write(graph_horiz_line_post[i]);
}
/*
* 5 data point (graphic) bitmaps
*/
// slot-3
for (i=0; i<8; i++) {
write(graph_b1[i]);
}
// slot-4
for (i=0; i<8; i++) {
write(graph_b2[i]);
}
// slot-5
for (i=0; i<8; i++) {
write(graph_b3[i]);
}
// slot-6
for (i=0; i<8; i++) {
write(graph_b4[i]);
}
// slot-7
for (i=0; i<8; i++) {
write(graph_b5[i]);
}
}
void
LCDI2C4Bit::LCD_cgram_load_boxed_bargraph( void )
{
uint8_t i;
/*
* load special graphic chars into 'cgram'
*/
command(0x40); // start off writing to CG RAM char 0
// empty box
for (i=0; i<8; i++) {
write(boxed_graph_b0[i]);
}
// 5 data point bitmaps
for (i=0; i<8; i++) {
write(boxed_graph_b1[i]);
}
for (i=0; i<8; i++) {
write(boxed_graph_b2[i]);
}
for (i=0; i<8; i++) {
write(boxed_graph_b3[i]);
}
for (i=0; i<8; i++) {
write(boxed_graph_b4[i]);
}
for (i=0; i<8; i++) {
write(boxed_graph_b5[i]);
}
}
void
LCDI2C4Bit::clear_line( uint8_t line_num )
{
command(line_num);
for (uint8_t i=0; imyBacklight_min; bl--) {
backLight(bl); // temporarily turn backlight entirely off (or go to its lowest 'low' setting)
delay(2);
}
backlight_currently_on = 0; // flag it as now off
}
void
LCDI2C4Bit::lcd_fade_backlight_complete_off( void )
{
for (int bl=myBacklight_max; bl>0; bl--) {
backLight(bl); // temporarily turn backlight entirely off (or go to its lowest 'low' setting)
delay(2);
}
backlight_currently_on = 0; // flag it as now off
}
#if 0
// value is a float from (0.0 .. 1.0) (ie, 0-100% of fullscale)
void
draw_graphic_bar(char *dest_buf, float value, int total_bargraph_size)
{
byte i;
int scaled_bit_pos;
int scaled_char_pos;
byte byte_graph_code;
byte dest_buf_idx=0;
float scaled_temp;
// which char position gets the graph point?
scaled_temp = value * (float)total_bargraph_size;
scaled_char_pos = (int) scaled_temp; // truncate the fraction and keep the int value
scaled_bit_pos = (int) (scaled_temp * (float)HORIZ_PIXELS_PER_CHAR);
// (1) draw from 0 up to user 'lower limit'
if (scaled_char_pos > 0) {
for (i=0; ; i++) {
if (i < scaled_char_pos) {
dest_buf[dest_buf_idx++] = 1; // 'small dots' line style
//lcd.write(1); // 'small dots' line style
}
else
break;
}
}
// (2) we're at the cell that needs the 'graphic'
// which graphic (0..5) should be used?
if (i < total_bargraph_size) {
byte_graph_code = (scaled_bit_pos - (scaled_char_pos * HORIZ_PIXELS_PER_CHAR));
dest_buf[dest_buf_idx++] = byte_graph_code+3; // skipping over the first 2+1 magic chars
//lcd.write(byte_graph_code+3); // skipping over the first 2+1 magic chars
}
// (3) draw postamble, if any
if (i < total_bargraph_size) {
for (i=scaled_char_pos+1; i < total_bargraph_size; i++) {
dest_buf[dest_buf_idx++] = 2; // continue drawing the horiz_line, to the end, in 'small dots'
//lcd.write(2); // continue drawing the horiz_line, to the end, in 'small dots'
}
}
dest_buf[total_bargraph_size+1] = '\0';
}
#endif