avr-lcd-lib/lcdLib.c
Collin J. Doering 3d3b9ab5fb Working backspace and fixed bug with cursor
Before this commit the cursor would be incremented (automatically by
hardware) after printing the last character on the line, which would
leave it the next line in memory, which doesn't necessarily correspond
to the next physical line on the display. This would be corrected when
the next character is received but is incorrect behavior; the cursor
should always be at where the next character is to inserted. This was
due to an off-by-one logical error. This commit solves this bug.

Along with the bug fix, keycodes from the serial terminal are now
deciphered correctly and the corresponding key or keys are sent to the
LCD accordingly.

Further work is required with regards to updating the serial
console (the connected client) so that their serial console looks
exactly like the LCD they are connected to.

Signed-off-by: Collin J. Doering <collin.doering@rekahsoft.ca>
2015-10-07 22:38:57 -04:00

402 lines
11 KiB
C

/**
* (C) Copyright Collin J. Doering 2015
*
* 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 <http://www.gnu.org/licenses/>.
*/
/**
* File: lcdLib.c
* Author: Collin J. Doering <collin.doering@rekahsoft.ca>
* Date: Sep 29, 2015
*/
#include <util/delay.h>
// Include header
#include "lcdLib.h"
// Globals
volatile uint8_t currentLineNum = 0;
volatile uint8_t currentLineChars = 0;
const uint8_t lineBeginnings[LCD_NUMBER_OF_LINES] = { LCD_LINE_BEGINNINGS };
//------------------------------------------------------------------------------------------
// Function definitions
void flashLED(uint8_t times) {
while (times > 0) {
STATUS_LED_PORT |= 1 << STATUS_LED; // turn on status LED
_delay_ms(100);
STATUS_LED_PORT &= ~(1 << STATUS_LED); // turn status LED off
_delay_ms(100);
times--;
}
}
//------------------------------------
void clkLCD(void) {
LCD_ENABLE_PORT |= (1 << LCD_ENABLE);
_delay_us(LCD_ENABLE_HIGH_DELAY);
LCD_ENABLE_PORT &= ~(1 << LCD_ENABLE);
_delay_us(LCD_ENABLE_LOW_DELAY);
}
void loop_until_LCD_BF_clear(void) {
uint8_t bf;
LCD_RS_PORT &= ~(1 << LCD_RS); // RS=0
LCD_RW_PORT |= (1 << LCD_RW); // RW=1
// Set LCD_BF as input
LCD_DBUS7_DDR &= ~(1 << LCD_BF);
STATUS_LED_PORT |= 1 << STATUS_LED; // DEBUG
do {
bf = 0;
LCD_ENABLE_PORT |= (1 << LCD_ENABLE);
_delay_us(1); // 'delay data time' and 'enable pulse width'
bf |= (LCD_DBUS7_PIN & (1 << LCD_BF));
LCD_ENABLE_PORT &= ~(1 << LCD_ENABLE);
_delay_us(1); // 'address hold time', 'data hold time' and 'enable cycle width'
#ifdef FOUR_BIT_MODE
LCD_ENABLE_PORT |= (1 << LCD_ENABLE);
_delay_us(1); // 'delay data time' and 'enable pulse width'
LCD_ENABLE_PORT &= ~(1 << LCD_ENABLE);
_delay_us(1); // 'address hold time', 'data hold time' and 'enable cycle width'
#endif
} while (bf);
STATUS_LED_PORT &= ~(1 << STATUS_LED); // DEBUG
#if defined (FOUR_BIT_MODE) || defined (EIGHT_BIT_ARBITRARY_PIN_MODE)
LCD_DBUS7_DDR |= (1 << LCD_DBUS7);
LCD_DBUS6_DDR |= (1 << LCD_DBUS6);
LCD_DBUS5_DDR |= (1 << LCD_DBUS5);
LCD_DBUS4_DDR |= (1 << LCD_DBUS4);
#ifdef EIGHT_BIT_ARBITRARY_PIN_MODE
LCD_DBUS3_DDR |= (1 << LCD_DBUS3);
LCD_DBUS2_DDR |= (1 << LCD_DBUS2);
LCD_DBUS1_DDR |= (1 << LCD_DBUS1);
LCD_DBUS0_DDR |= (1 << LCD_DBUS0);
#endif
#else
LCD_DBUS_DDR = 0xff; // Reset all LCD_DBUS_PORT pins as outputs
#endif
}
#ifdef FOUR_BIT_MODE
/*
Writes one nibble to the LCD data bus. Does not touch the RS or RW control lines.
Note: the bits that are sent are the four MSBs of the given argument
*/
void writeLCDNibble_(uint8_t b) {
// Reset data lines to zeros
LCD_DBUS7_PORT &= ~(1 << LCD_DBUS7);
LCD_DBUS6_PORT &= ~(1 << LCD_DBUS6);
LCD_DBUS5_PORT &= ~(1 << LCD_DBUS5);
LCD_DBUS4_PORT &= ~(1 << LCD_DBUS4);
// Write 1's where appropriate on data lines
if (b & (1 << 7)) LCD_DBUS7_PORT |= (1 << LCD_DBUS7);
if (b & (1 << 6)) LCD_DBUS6_PORT |= (1 << LCD_DBUS6);
if (b & (1 << 5)) LCD_DBUS5_PORT |= (1 << LCD_DBUS5);
if (b & (1 << 4)) LCD_DBUS4_PORT |= (1 << LCD_DBUS4);
// Pulse the enable line
clkLCD();
}
#endif
/*
Write a byte to the LCD data bus. Does not touch the RS or RW control lines.
*/
void writeLCDByte_(uint8_t b) {
#ifdef FOUR_BIT_MODE
writeLCDNibble_(b);
writeLCDNibble_(b << 4);
#elif defined (EIGHT_BIT_ARBITRARY_PIN_MODE)
// Reset data lines to zeros
LCD_DBUS7_PORT &= ~(1 << LCD_DBUS7);
LCD_DBUS6_PORT &= ~(1 << LCD_DBUS6);
LCD_DBUS5_PORT &= ~(1 << LCD_DBUS5);
LCD_DBUS4_PORT &= ~(1 << LCD_DBUS4);
LCD_DBUS3_PORT &= ~(1 << LCD_DBUS3);
LCD_DBUS2_PORT &= ~(1 << LCD_DBUS2);
LCD_DBUS1_PORT &= ~(1 << LCD_DBUS1);
LCD_DBUS0_PORT &= ~(1 << LCD_DBUS0);
// Write 1's where appropriate on data lines
if (b & (1 << 7)) LCD_DBUS7_PORT |= (1 << LCD_DBUS7);
if (b & (1 << 6)) LCD_DBUS6_PORT |= (1 << LCD_DBUS6);
if (b & (1 << 5)) LCD_DBUS5_PORT |= (1 << LCD_DBUS5);
if (b & (1 << 4)) LCD_DBUS4_PORT |= (1 << LCD_DBUS4);
if (b & (1 << 3)) LCD_DBUS3_PORT |= (1 << LCD_DBUS3);
if (b & (1 << 2)) LCD_DBUS2_PORT |= (1 << LCD_DBUS2);
if (b & (1 << 1)) LCD_DBUS1_PORT |= (1 << LCD_DBUS1);
if (b & (1 << 0)) LCD_DBUS0_PORT |= (1 << LCD_DBUS0);
clkLCD();
#else
LCD_DBUS_PORT = b;
clkLCD();
#endif
}
void writeLCDInstr_(uint8_t instr) {
LCD_RS_PORT &= ~(1 << LCD_RS); // RS=0
LCD_RW_PORT &= ~(1 << LCD_RW); // RW=0
#ifdef FOUR_BIT_MODE
writeLCDNibble_(instr);
writeLCDNibble_(instr << 4);
#else
writeLCDByte_(instr);
#endif
}
void writeLCDInstr(uint8_t instr) {
loop_until_LCD_BF_clear(); // Wait until LCD is ready for new instructions
writeLCDInstr_(instr);
}
void writeCharToLCD_(char c) {
LCD_RS_PORT |= (1 << LCD_RS); // RS=1
LCD_RW_PORT &= ~(1 << LCD_RW); // RW=0
#ifdef FOUR_BIT_MODE
writeLCDNibble_(c);
writeLCDNibble_(c << 4);
#else
writeLCDByte_(c);
#endif
}
void writeCharToLCD(char c) {
switch (c) {
case '\n': // Line feed
if (currentLineNum == LCD_NUMBER_OF_LINES - 1) {
clearDisplay();
} else {
writeLCDInstr(INSTR_DDRAM_ADDR | lineBeginnings[++currentLineNum]);
currentLineChars = 0;
}
break;
;
case '\a': // Alarm
break;
;
case '\b': // Backspace (non-destructive)
if (currentLineChars == 0 && currentLineNum == 0) {
// At first line, first column; there is no where to move; do nothing
break;
} else if (currentLineChars == 0) {
// At beginning of line, need to move the end of previous line
currentLineChars = LCD_CHARACTERS_PER_LINE - 1;
writeLCDInstr(INSTR_DDRAM_ADDR | (lineBeginnings[--currentLineNum] + currentLineChars));
} else {
// OK, simply go back one character
writeLCDInstr(INSTR_DDRAM_ADDR | (lineBeginnings[currentLineNum] + --currentLineChars));
}
break;
;
case '\r': // Carriage return
writeLCDInstr(INSTR_DDRAM_ADDR | lineBeginnings[currentLineNum]);
currentLineChars = 0;
break;
;
case '\f': // Form feed
clearDisplay();
break;
;
default:
if (currentLineChars == LCD_CHARACTERS_PER_LINE - 1 && currentLineNum == LCD_NUMBER_OF_LINES - 1) {
clearDisplay();
} else if (currentLineChars == LCD_CHARACTERS_PER_LINE - 1) {
loop_until_LCD_BF_clear(); // Wait until LCD is ready for new instructions
writeCharToLCD_(c);
currentLineChars = 0;
writeLCDInstr(INSTR_DDRAM_ADDR | lineBeginnings[++currentLineNum]);
} else {
loop_until_LCD_BF_clear(); // Wait until LCD is ready for new instructions
writeCharToLCD_(c);
currentLineChars++;
}
;
}
}
void writeStringToLCD(const char* str) {
while (*str != '\0') {
writeCharToLCD(*str);
str++;
}
}
void clearDisplay(void) {
writeLCDInstr(CMD_CLEAR_DISPLAY);
// Reset line and char number tracking
currentLineNum = 0;
currentLineChars = 0;
}
void returnHome(void) {
writeLCDInstr(CMD_RETURN_HOME);
// Reset line and char number tracking
currentLineNum = 0;
currentLineChars = 0;
}
/* char readCharFromLCD(void) { */
/* loop_until_LCD_BF_clear(); // Wait until LCD is ready for new instructions */
/* LCD_CTRL_PORT |= (1 << LCD_RW) | (1 << LCD_RW); // RS=RW=1 */
/* LCD_DBUS_DDR = 0; // Set all LCD_DBUS_PORT pins as inputs */
/* clkLCD(); */
/* char c = LCD_DBUS_PIN; */
/* LCD_DBUS_DDR = 0xff; // Reset all LCD_DBUS_PORT pins to outputs */
/* return c; */
/* } */
/*
Set all pins of LCD_DBUS, as well as pins LCD_RS, and LCD_RW as outputs
*/
static inline void enableLCDOutput(void) {
LCD_RS_DDR |= (1 << LCD_RS);
LCD_RW_DDR |= (1 << LCD_RW);
LCD_ENABLE_DDR |= (1 << LCD_ENABLE);
#if defined (FOUR_BIT_MODE) || defined (EIGHT_BIT_ARBITRARY_PIN_MODE)
LCD_DBUS7_DDR |= (1 << LCD_DBUS7);
LCD_DBUS6_DDR |= (1 << LCD_DBUS6);
LCD_DBUS5_DDR |= (1 << LCD_DBUS5);
LCD_DBUS4_DDR |= (1 << LCD_DBUS4);
#ifdef EIGHT_BIT_ARBITRARY_PIN_MODE
LCD_DBUS3_DDR |= (1 << LCD_DBUS3);
LCD_DBUS2_DDR |= (1 << LCD_DBUS2);
LCD_DBUS1_DDR |= (1 << LCD_DBUS1);
LCD_DBUS0_DDR |= (1 << LCD_DBUS0);
#endif
#else
LCD_DBUS_DDR = 0xff;
#endif
}
/*
Set all pins of LCD_DBUS as well as LCD_RS, and LCD_RW as inputs (disabling their output)
*/
static inline void disableLCDOutput(void) {
LCD_RS_DDR &= ~(1 << LCD_RS);
LCD_RW_DDR &= ~(1 << LCD_RW);
LCD_ENABLE_DDR &= ~(1 << LCD_ENABLE);
#if defined (FOUR_BIT_MODE) || defined (EIGHT_BIT_ARBITRARY_PIN_MODE)
LCD_DBUS7_DDR &= ~(1 << LCD_DBUS7);
LCD_DBUS6_DDR &= ~(1 << LCD_DBUS6);
LCD_DBUS5_DDR &= ~(1 << LCD_DBUS5);
LCD_DBUS4_DDR &= ~(1 << LCD_DBUS4);
#ifdef EIGHT_BIT_ARBITRARY_PIN_MODE
LCD_DBUS3_DDR &= ~(1 << LCD_DBUS3);
LCD_DBUS2_DDR &= ~(1 << LCD_DBUS2);
LCD_DBUS1_DDR &= ~(1 << LCD_DBUS1);
LCD_DBUS0_DDR &= ~(1 << LCD_DBUS0);
#endif
#else
LCD_DBUS_DDR = 0;
#endif
}
static inline void softwareLCDInitPulse(void) {
enableLCDOutput();
LCD_RS_PORT &= ~(1 << LCD_RS); // RS=0
LCD_RW_PORT &= ~(1 << LCD_RW); // RW=0
#ifdef FOUR_BIT_MODE
writeLCDNibble_(CMD_INIT);
#else
writeLCDByte_(CMD_INIT);
#endif
}
/*
Do software initialization as specified by the datasheet
*/
void initLCD (void) {
enableLCDOutput();
_delay_us(LCD_INIT_DELAY0); // Wait minimum 15ms as per datasheet
softwareLCDInitPulse();
_delay_us(LCD_INIT_DELAY1); // Wait minimum 4.1ms as per datasheet
softwareLCDInitPulse();
_delay_us(LCD_INIT_DELAY2); // Wait minimum 100us as per datasheet
softwareLCDInitPulse();
#if defined (FOUR_BIT_MODE)
// Function Set (4-bit interface; 2 lines with 5x7 dot character font)
writeLCDNibble_(CMD_INIT_FOUR_BIT);
writeLCDInstr_(CMD_INIT_FOUR_BIT | (1 << INSTR_FUNC_SET_N));
#else
// Function set (8-bit interface; 2 lines with 5x7 dot character font)
// RS=RW=0, DBUS=b00111000,0x38
writeLCDInstr_(INSTR_FUNC_SET | (1 << INSTR_FUNC_SET_DL) | (1 << INSTR_FUNC_SET_N));
#endif
/* BF now can be checked */
// Set functions of LCD
writeLCDInstr_(INSTR_DISPLAY); // Display off
_delay_us(LCD_GENERIC_INSTR_DELAY);
// Clear display
writeLCDInstr_(CMD_CLEAR_DISPLAY);
_delay_us(LCD_CLEAR_DISPLAY_DELAY);
// Increment mode, no shift
writeLCDInstr_(INSTR_ENTRY_SET | (1 << INSTR_ENTRY_SET_ID));
_delay_us(LCD_GENERIC_INSTR_DELAY);
// Display on, cursor on, blink off
writeLCDInstr_(INSTR_DISPLAY | (1 << INSTR_DISPLAY_D) | (1 << INSTR_DISPLAY_C));
_delay_us(LCD_GENERIC_INSTR_DELAY);
flashLED(5); // DEBUG
}
/*
Initialize LCD using the internal reset circuitry.
Note: This currently only works with 8 bit modes, but is not recommended. Instead use the
initLCD function which uses the software initialization method and works for 8-bit
modes as well the 4-bit mode.
*/
void initLCDByInternalReset(void) {
enableLCDOutput();
// Function set (8-bit interface; 2 lines with 5x7 dot character font)
writeLCDInstr_(INSTR_FUNC_SET | (1 << INSTR_FUNC_SET_DL) | (1 << INSTR_FUNC_SET_N));
writeLCDInstr_(0x0F);
writeLCDInstr_(0x06);
writeLCDInstr_(CMD_CLEAR_DISPLAY);
_delay_ms(LCD_CLEAR_DISPLAY_DELAY);
}