Collin J. Doering
3d3b9ab5fb
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>
402 lines
11 KiB
C
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);
|
|
}
|