forked from MakerDyne/Memory-LCD-for-Raspberry-Pi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MemoryLCD.cpp
296 lines (238 loc) · 8.94 KB
/
MemoryLCD.cpp
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#include "MemoryLCD.h"
#include <bcm2835.h>
using namespace std;
MemoryLCD::MemoryLCD(char SCSpin, char DISPpin, char EXTCOMINpin, bool useEXTCOMIN) {
bcm2835_init();
// Initialise private vars (constructor args)
// Set DISPpin = 255, when you call the constructor if you want to save a pin
// and not have hardware control of _DISP
SCS = SCSpin;
DISP = DISPpin;
EXTCOMIN = EXTCOMINpin;
enablePWM = useEXTCOMIN;
bcm2835_gpio_fsel(SCS, BCM2835_GPIO_FSEL_OUTP);
bcm2835_gpio_fsel(DISP, BCM2835_GPIO_FSEL_OUTP);
bcm2835_gpio_fsel(EXTCOMIN, BCM2835_GPIO_FSEL_OUTP);
// initialise private vars (others)
commandByte = 0b10000000;
vcomByte = 0b01000000;
clearByte = 0b00100000;
paddingByte = 0b00000000;
// Introduce delay to allow MemoryLCD to reach 5V
// (probably redundant here as Pi's boot is much longer than Arduino's power-on time)
bcm2835_delayMicroseconds(800); // minimum 750us
// setup separate thread to continuously output the EXTCOMIN signal for as long as the parent runs.
// NB: this leaves the Memory LCD vulnerable if an image is left displayed after the program stops.
pthread_t threadId;
if(enablePWM) {
if(pthread_create(&threadId, NULL, &hardToggleVCOM, (void *)EXTCOMIN)) {
cout << "Error creating EXTCOMIN thread" << endl;
} else {
//cout << "PWM thread started successfully" << endl;
}
} else {
// TODO: setup timer driven interrupt instead?
}
// SETUP SPI
// Datasheet says SPI clock must have <1MHz frequency (BCM2835_SPI_CLOCK_DIVIDER_256)
// but it may work up to 4MHz (BCM2835_SPI_CLOCK_DIVIDER_128, BCM2835_SPI_CLOCK_DIVIDER_64)
/*
* The Raspberry Pi GPIO pins reserved for SPI once bcm2835_spi_begin() is called are:
* P1-19 (MOSI)
* P1-21 (MISO)
* P1-23 (CLK)
* P1-24 (CE0)
* P1-26 (CE1)
*/
bcm2835_spi_begin();
// set MSB here - setting to LSB elsewhere doesn't work. So I'm manually reversing lineAddress bit order instead.
bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST);
bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_1024); // this is the 2 MHz/8 setting
bcm2835_spi_setDataMode(BCM2835_SPI_MODE0);
bcm2835_spi_chipSelect(BCM2835_SPI_CS_NONE);
// Not sure if I can use the built-in bcm2835 Chip Select functions as the docs suggest it only
// affects bcm2835_spi_transfer() calls so I'm setting it to inactive and setting up my own CS pin
// as I want to use bcm2835_spi_writenb() to send data over SPI instead.
// Set pin modes
bcm2835_gpio_write(SCS, LOW);
if(DISP != 255) {
bcm2835_gpio_write(DISP, LOW);
}
if(enablePWM) {
bcm2835_gpio_write(EXTCOMIN, LOW);
}
// Memory LCD startup sequence with recommended timings
bcm2835_gpio_write(DISP, HIGH);
bcm2835_delayMicroseconds(PWRUP_DISP_DELAY);
bcm2835_gpio_write(EXTCOMIN, LOW);
bcm2835_delayMicroseconds(PWRUP_EXTCOMIN_DELAY);
clearLineBuffer();
}
MemoryLCD::~MemoryLCD(void) {
bcm2835_spi_end();
bcm2835_close();
}
void MemoryLCD::writeLineToDisplay(char lineNumber, char *line) {
writeMultipleLinesToDisplay(lineNumber, 1, line);
}
void MemoryLCD::writeMultipleLinesToDisplay(char lineNumber, char numLines, char *lines) {
// this implementation writes multiple lines that are CONSECUTIVE (although they don't
// have to be, as an address is given for every line, not just the first in the sequence)
// data for all lines should be stored in a single array
char * linePtr = lines;
bcm2835_gpio_write(SCS, HIGH);
bcm2835_delayMicroseconds(SCS_HIGH_DELAY);
bcm2835_spi_writenb(&commandByte, 1);
for(char x=0; x<numLines; x++) {
char reversedLineNumber = reverseByte(lineNumber);
bcm2835_spi_writenb(&reversedLineNumber, 1);
bcm2835_spi_writenb(linePtr++, LCDWIDTH/8); // Transfers a whole line of data at once
// pointer arithmetic assumes an array of lines - TODO: change this?
bcm2835_spi_writenb(&paddingByte, 1);
lineNumber++;
}
bcm2835_spi_writenb(&paddingByte, 1); // trailing paddings
bcm2835_delayMicroseconds(SCS_LOW_DELAY);
bcm2835_gpio_write(SCS, LOW);
bcm2835_delayMicroseconds(INTERFRAME_DELAY); // can I delete this delay?
}
void MemoryLCD::writePixelToLineBuffer(unsigned int pixel, bool isWhite) {
// pixel location expected in the fn args follows the scheme defined in the datasheet.
// NB: the datasheet defines pixel addresses starting from 1, NOT 0
if((pixel <= LCDWIDTH) && (pixel != 0)) {
pixel = pixel - 1;
if(isWhite)
lineBuffer[pixel/8] |= (1 << (7 - pixel%8));
else
lineBuffer[pixel/8] &= ~(1 << (7 - pixel%8));
}
}
void MemoryLCD::writeByteToLineBuffer(char byteNumber, char byteToWrite) {
// char location expected in the fn args has been extrapolated from the pixel location
// format (see above), so chars go from 1 to LCDWIDTH/8, not from 0
if(byteNumber <= LCDWIDTH/8 && byteNumber != 0) {
byteNumber -= 1;
lineBuffer[byteNumber] = byteToWrite;
}
}
void MemoryLCD::copyByteWithinLineBuffer(char sourceByte, char destinationByte) {
if(sourceByte <= LCDWIDTH/8 && destinationByte <= LCDWIDTH/8) {
sourceByte -= 1;
destinationByte -= 1;
lineBuffer[destinationByte] = lineBuffer[sourceByte];
}
}
void MemoryLCD::setLineBufferBlack(void) {
for(char i=0; i<LCDWIDTH/8; i++) {
lineBuffer[i] = 0x00;
}
}
void MemoryLCD::setLineBufferWhite(void) {
for(char i=0; i<LCDWIDTH/8; i++) {
lineBuffer[i] = 0xFF;
}
}
void MemoryLCD::writeLineBufferToDisplay(char lineNumber) {
writeMultipleLinesToDisplay(lineNumber, 1, lineBuffer);
}
void MemoryLCD::writeLineBufferToDisplayRepeatedly(char lineNumber, char numLines) {
writeMultipleLinesToDisplay(lineNumber, numLines, lineBuffer);
}
void MemoryLCD::writePixelToFrameBuffer(unsigned int pixel, char lineNumber, bool isWhite) {
// pixel location expected in the fn args follows the scheme defined in the datasheet.
// NB: the datasheet defines pixel addresses starting from 1, NOT 0
if((pixel <= LCDWIDTH) && (pixel != 0) && (lineNumber <=LCDHEIGHT) & (lineNumber != 0)) {
pixel -= 1;
lineNumber -= 1;
if(isWhite)
frameBuffer[(lineNumber*LCDWIDTH/8)+(pixel/8)] |= (1 << (7 - pixel%8));
else
frameBuffer[(lineNumber*LCDWIDTH/8)+(pixel/8)] &= ~(1 << (7 - pixel%8));
}
}
void MemoryLCD::writeByteToFrameBuffer(char byteNumber, char lineNumber, char byteToWrite) {
// char location expected in the fn args has been extrapolated from the pixel location
// format (see above), so chars go from 1 to LCDWIDTH/8, not from 0
if((byteNumber <= LCDWIDTH/8) && (byteNumber != 0) && (lineNumber <=LCDHEIGHT) & (lineNumber != 0)) {
byteNumber -= 1;
lineNumber -= 1;
frameBuffer[(lineNumber*LCDWIDTH/8)+byteNumber] = byteToWrite;
}
}
void MemoryLCD::setFrameBufferBlack() {
for(char i=0; i<LCDWIDTH*LCDHEIGHT/8; i++) {
frameBuffer[i] = 0x00;
}
}
void MemoryLCD::setFrameBufferWhite() {
for(char i=0; i<LCDWIDTH*LCDHEIGHT/8; i++) {
frameBuffer[i] = 0xFF;
}
}
void MemoryLCD::writeFrameBufferToDisplay() {
writeMultipleLinesToDisplay(1, LCDHEIGHT, frameBuffer);
}
void MemoryLCD::clearLineBuffer() {
setLineBufferWhite();
}
void MemoryLCD::clearFrameBuffer() {
setFrameBufferWhite();
}
void MemoryLCD::clearDisplay() {
bcm2835_gpio_write(SCS, HIGH);
bcm2835_delayMicroseconds(SCS_HIGH_DELAY);
bcm2835_spi_writenb(&clearByte, 1);
bcm2835_spi_writenb(&paddingByte, 1);
bcm2835_delayMicroseconds(SCS_LOW_DELAY);
bcm2835_gpio_write(SCS, LOW);
bcm2835_delayMicroseconds(INTERFRAME_DELAY);
}
// won't work if DISP pin is not used
void MemoryLCD::turnOff() {
if(DISP != 255)
bcm2835_gpio_write(DISP, HIGH);
}
// won't work if DISP pin is not used
void MemoryLCD::turnOn() {
if(DISP != 255)
bcm2835_gpio_write(DISP, LOW);
}
unsigned int MemoryLCD::getDisplayWidth() {
return LCDWIDTH;
}
unsigned int MemoryLCD::getDisplayHeight() {
return LCDHEIGHT;
}
void MemoryLCD::softToggleVCOM() {
vcomByte ^= 0b01000000;
bcm2835_gpio_write(SCS, HIGH);
bcm2835_delayMicroseconds(SCS_HIGH_DELAY);
bcm2835_spi_writenb(&vcomByte, 1);
bcm2835_spi_writenb(&paddingByte, 1);
bcm2835_delayMicroseconds(SCS_LOW_DELAY);
bcm2835_gpio_write(SCS, LOW);
bcm2835_delayMicroseconds(10);
}
void *MemoryLCD::hardToggleVCOM(void *arg) {
//char extcomin = (char)arg;
char extcomin = *((char*)(&arg));
//cout << "Value of extcomin in hardToggleVCOM is " << (unsigned int)extcomin << endl;
// intended to be run as a separate thread. Do not execute in main loop!
while(1) {
bcm2835_delay(250);
bcm2835_gpio_write(extcomin, HIGH);
bcm2835_delay(250);
bcm2835_gpio_write(extcomin, LOW);
}
pthread_exit(NULL);
}
// reverses the bit order of an unsigned char
// needed to reverse the bit order of the Memory LCD lineAddress sent over SPI
// I can't get the bcm2835 bcm2835_spi_setBitOrder to work!
// (found this snippet on StackOverflow)
char MemoryLCD::reverseByte(char b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}