-
Notifications
You must be signed in to change notification settings - Fork 16
/
lcd-display.cc
194 lines (161 loc) · 6.17 KB
/
lcd-display.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// This file is part of UPnP LCD Display
//
// Copyright (C) 2013 Henner Zeller <[email protected]>
//
// 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/>.
#include "lcd-display.h"
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "gpio.h"
#include "font-data.h"
#include "utf8.h"
GPIO gpio;
// According to datasheet, basic ops are typically ~37usec
#define LCD_DISPLAY_OPERATION_WAIT_USEC 50
// Time between sending two nibbles.
#define LCD_ENABLE_PULSE_TIME_NSEC 400
// The following GPIO mapping allows to have all wiring in one row to
// accomodate simpling wiring.
#define LCD_E (1<<18)
#define LCD_RS (1<<14)
#define LCD_D0_BIT (1<<23)
#define LCD_D1_BIT (1<<24)
#define LCD_D2_BIT (1<<25)
#define LCD_D3_BIT (1<<8)
static void WriteNibble(bool is_command, uint8_t b) {
uint32_t out = is_command ? 0 : LCD_RS;
out |= (b & 0x1) ? LCD_D0_BIT : 0;
out |= (b & 0x2) ? LCD_D1_BIT : 0;
out |= (b & 0x4) ? LCD_D2_BIT : 0;
out |= (b & 0x8) ? LCD_D3_BIT : 0;
out |= LCD_E;
gpio.Write(out);
// We don't want to do a sleep because we don't want to risk a context
// switch - Linux might come back way to late (the LCD display times out
// between two Nibble-writes and ends up in a broken state).
gpio.busy_nano_sleep(LCD_ENABLE_PULSE_TIME_NSEC);
gpio.ClearBits(LCD_E);
}
// Write data to display. Differentiates if this is a command byte or data
// byte.
static void WriteByte(bool is_command, uint8_t b) {
WriteNibble(is_command, (b >> 4) & 0xf);
WriteNibble(is_command, b & 0xf);
usleep(LCD_DISPLAY_OPERATION_WAIT_USEC);
}
static int compare_glyph(const void *key, const void *array_member) {
return *((const uint32_t*)key) - ((const Font5x8*)array_member)->codepoint;
}
// Find font for codepoint from our sorted compiled-in table.
static const Font5x8 *findGlyph(const uint32_t codepoint) {
return (const Font5x8*)bsearch(&codepoint, kFontData, kFontDataSize,
sizeof(Font5x8), compare_glyph);
}
static void LCDStoreGlyph(uint8_t num, const Font5x8 *glyph) {
assert(glyph);
assert(num < 8);
WriteByte(true, 0x40 + (num << 3));
for (int i = 0; i < 8; ++i) {
WriteByte(false, glyph->bitmap[i] >> 3); // font is left aligned in data.
}
}
uint8_t LCDDisplay::FindCharacterFor(Codepoint cp, bool *register_new) {
*register_new = false;
if (cp < 0x80) return cp; // Everything regular ASCII is used as-is.
// Conceptually, we need a map codepoint -> char; but this is a really small
// list, so this is faster to iterate than having a bulky map.
for (int i = 0; i < 8; ++i) {
if (special_characters_[i] == cp) return i;
}
// Ok, not there, just use the next free. We're doing a very simple
// round-robin approach here, potentially re-using characters that are
// still in use. For now, we just assume that the active number of different
// characters is small enough to fit.
const Font5x8 *glyph = findGlyph(cp);
if (glyph == NULL) return '?'; // unicode without glyph for codepoint.
const uint8_t new_char = (next_free_special_++ % 8);
LCDStoreGlyph(new_char, glyph);
*register_new = true;
special_characters_[new_char] = cp;
return new_char;
}
LCDDisplay::LCDDisplay(int width) : width_(width), initialized_(false),
next_free_special_(0) {
memset(special_characters_, 0, sizeof(special_characters_));
}
bool LCDDisplay::Init() {
if (!gpio.Init())
return false;
gpio.InitOutputs(LCD_E | LCD_RS |
LCD_D0_BIT | LCD_D1_BIT | LCD_D2_BIT | LCD_D3_BIT);
gpio.Write(0);
usleep(100000);
// -- This seems to be a reliable initialization sequence:
// Start with 8 bit mode, then instruct to switch to 4 bit mode.
WriteNibble(true, 0x03);
usleep(5000); // If we were in 4 bit mode, timeout makes this 0x30
WriteNibble(true, 0x03);
usleep(5000);
// Transition to 4 bit mode.
WriteNibble(true, 0x02); // Interpreted as 0x20: 8-bit cmd to switch to 4-bit.
usleep(LCD_DISPLAY_OPERATION_WAIT_USEC);
// From now on, we can write full bytes that we transfer in nibbles.
WriteByte(true, 0x28); // Function set: 4-bit mode, two lines, 5x8 font
WriteByte(true, 0x06); // Entry mode: increment, no shift
WriteByte(true, 0x0c); // Display control: on, no cursor
WriteByte(true, 0x01); // Clear display
usleep(2000); // ... which takes up to 1.6ms
initialized_ = true;
display_is_on_ = true;
return true;
}
void LCDDisplay::SaveScreen() {
if (!display_is_on_) return;
Print(0, "");
Print(1, "");
WriteByte(true, 0x08);
display_is_on_ = false;
}
void LCDDisplay::Print(int row, const std::string &text) {
assert(initialized_); // call Init() first.
assert(row < 2); // uh, out of range.
if (last_line_[row] == text)
return; // nothing to update.
if (!display_is_on_) {
WriteByte(true, 0x0c);
display_is_on_ = true;
}
// Set address to write to; line 2 starts at 0x40
WriteByte(true, 0x80 + ((row > 0) ? 0x40 : 0));
std::string::const_iterator it = text.begin();
int screen_pos = 0;
for (screen_pos = 0; screen_pos < width_ && it != text.end(); ++screen_pos) {
const Codepoint codepoint = utf8_next_codepoint(it);
bool ddram_dirty = false;
uint8_t char_to_print = FindCharacterFor(codepoint, &ddram_dirty);
if (ddram_dirty) {
WriteByte(true, 0x80 + ((row > 0) ? 0x40 : 0) + screen_pos);
}
WriteByte(false, char_to_print);
}
// Fill rest with spaces.
for (int i = screen_pos; i < width_; ++i) {
WriteByte(false, ' ');
}
last_line_[row] = text;
};