Skip to content

Commit d2a1999

Browse files
author
PanKleszcz
committed
feat<display refactor> implement initial LCD abstraction
1 parent 9e71e27 commit d2a1999

2 files changed

Lines changed: 491 additions & 0 deletions

File tree

source/Core/Drivers/LCD.cpp

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
/*
2+
* OLED.cpp
3+
*
4+
* Created on: 29Aug.,2017
5+
* Author: Ben V. Brown
6+
*/
7+
#include "LCD.hpp"
8+
9+
#include "Settings.h"
10+
#include "Translation.h"
11+
#include "cmsis_os.h"
12+
#include "configuration.h"
13+
#include <stdint.h>
14+
#include <stdlib.h>
15+
#include <string.h>
16+
17+
// rendering to the buffer
18+
uint8_t *LCD::stripPointers[4]; // Pointers to the strips to allow for buffer having extra content
19+
20+
alignas(uint32_t) uint8_t LCD::screenBuffer[LCD_WIDTH * (LCD_HEIGHT / 8)]; // The data buffer
21+
alignas(uint32_t) uint8_t LCD::secondFrameBuffer[LCD_WIDTH * (LCD_HEIGHT / 8)];
22+
uint32_t LCD::displayChecksum;
23+
24+
const FRToSSPI::SPI_CMD lcdInitCmds[] = {
25+
{ST7735_SWRESET, FRToSSPI::SPI_CMD_DELAY_MS, 150, NULL},
26+
{ ST7735_SLPOUT, FRToSSPI::SPI_CMD_DELAY_MS, 200, NULL},
27+
28+
{ST7735_FRMCTR1, FRToSSPI::SPI_CMD_PAYLOAD, 3, (uint8_t[]){0x05, 0x3A, 0x3A}},
29+
{ST7735_FRMCTR2, FRToSSPI::SPI_CMD_PAYLOAD, 3, (uint8_t[]){0x05, 0x3A, 0x3A}},
30+
{ST7735_FRMCTR3, FRToSSPI::SPI_CMD_PAYLOAD, 6, (uint8_t[]){0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A}},
31+
32+
{ ST7735_PWCTR1, FRToSSPI::SPI_CMD_PAYLOAD, 3, (uint8_t[]){0x62, 0x02, 0x04}},
33+
{ ST7735_PWCTR2, FRToSSPI::SPI_CMD_PAYLOAD, 1, (uint8_t[]){0xC0}},
34+
{ ST7735_PWCTR3, FRToSSPI::SPI_CMD_PAYLOAD, 2, (uint8_t[]){0x0D, 0x00}},
35+
{ ST7735_PWCTR4, FRToSSPI::SPI_CMD_PAYLOAD, 2, (uint8_t[]){0x8D, 0x6A}},
36+
{ ST7735_PWCTR5, FRToSSPI::SPI_CMD_PAYLOAD, 2, (uint8_t[]){0x8D, 0xEE}},
37+
38+
{ST7735_GMCTRP1, FRToSSPI::SPI_CMD_PAYLOAD, 16, (uint8_t[]){0x10, 0x0E, 0x02, 0x03, 0x0E, 0x07, 0x02, 0x07, 0x0A, 0x12, 0x27, 0x37, 0x00, 0x0D, 0x0E, 0x10}},
39+
{ST7735_GMCTRN1, FRToSSPI::SPI_CMD_PAYLOAD, 16, (uint8_t[]){0x10, 0x0E, 0x03, 0x03, 0x0F, 0x06, 0x02, 0x08, 0x0A, 0x13, 0x26, 0x36, 0x00, 0x0D, 0x0E, 0x10}},
40+
41+
{ ST7735_INVCTR, FRToSSPI::SPI_CMD_PAYLOAD, 1, (uint8_t[]){0x03}},
42+
{ ST7735_INVON, FRToSSPI::SPI_CMD_PAYLOAD, 0, NULL},
43+
{ ST7735_VMCTR1, FRToSSPI::SPI_CMD_PAYLOAD, 1, (uint8_t[]){0x0E}},
44+
{ ST7735_MADCTL, FRToSSPI::SPI_CMD_PAYLOAD, 1, (uint8_t[]){0x88}},
45+
{ ST7735_COLMOD, FRToSSPI::SPI_CMD_PAYLOAD, 1, (uint8_t[]){0x05}},
46+
47+
{ ST7735_NORON, FRToSSPI::SPI_CMD_DELAY_MS, 10, NULL},
48+
{ ST7735_DISPON, FRToSSPI::SPI_CMD_DELAY_MS, 100, NULL},
49+
};
50+
51+
FRToSSPI::SPI_CMD lcdSetAreaCmds[] = {
52+
{ST7735_RASET, FRToSSPI::SPI_CMD_PAYLOAD, 4, (uint8_t[]){0, 16 + ST7735_XOFFSET, 0, 16 + LCD_WIDTH + ST7735_XOFFSET - 1}},
53+
{ST7735_CASET, FRToSSPI::SPI_CMD_PAYLOAD, 4, (uint8_t[]){0, 24 + ST7735_YOFFSET, 0, 24 + LCD_HEIGHT + ST7735_YOFFSET - 1}},
54+
{ST7735_RAMWR, FRToSSPI::SPI_CMD_PAYLOAD, 0, NULL},
55+
};
56+
57+
void LCD::setDrawingWindow(uint8_t x, uint8_t y, uint8_t w, uint8_t h) {
58+
lcdSetAreaCmds[0].data[1] = x + ST7735_XOFFSET;
59+
lcdSetAreaCmds[0].data[3] = x + w + ST7735_XOFFSET - 1;
60+
lcdSetAreaCmds[1].data[1] = y + ST7735_YOFFSET;
61+
lcdSetAreaCmds[1].data[3] = y + h + ST7735_YOFFSET - 1;
62+
63+
FRToSSPI::sendCmdChain(lcdSetAreaCmds, sizeof(lcdSetAreaCmds) / sizeof(*lcdSetAreaCmds));
64+
}
65+
66+
/*
67+
* Animation timing function that follows a bezier curve.
68+
* @param t A given percentage value [0..<100]
69+
* Returns a new percentage value with ease in and ease out.
70+
* Original floating point formula: t * t * (3.0f - 2.0f * t);
71+
*/
72+
static uint16_t easeInOutTiming(uint16_t t) { return t * t * (300 - 2 * t) / 10000; }
73+
74+
/*
75+
* Returns the value between a and b, using a percentage value t.
76+
* @param a The value associated with 0%
77+
* @param b The value associated with 100%
78+
* @param t The percentage [0..<100]
79+
*/
80+
static uint16_t lerp(uint16_t a, uint16_t b, uint16_t t) { return a + t * (b - a) / 100; }
81+
82+
void LCD::initialize() {
83+
stripPointers[0] = &screenBuffer[0 * LCD_WIDTH];
84+
stripPointers[1] = &screenBuffer[1 * LCD_WIDTH];
85+
stripPointers[2] = &screenBuffer[2 * LCD_WIDTH];
86+
stripPointers[3] = &screenBuffer[3 * LCD_WIDTH];
87+
88+
FRToSSPI::init();
89+
FRToSSPI::sendCmdChain(lcdInitCmds, sizeof(lcdInitCmds) / sizeof(*lcdInitCmds));
90+
91+
// Erase background
92+
setDrawingWindow(0, 0, 160, 80);
93+
FRToSSPI::sendByteMutiple(0x00, 2 * 160 * 80);
94+
95+
// Draw a nice frame for the emulated OLED display
96+
setDrawingWindow(12, 20, 136, 40);
97+
FRToSSPI::sendByteMutiple(0xFF, 2 * 136 * 40);
98+
setDrawingWindow(14, 22, 132, 36);
99+
FRToSSPI::sendByteMutiple(0x00, 2 * 132 * 36);
100+
}
101+
102+
void LCD::setFramebuffer(uint8_t *buffer) {
103+
stripPointers[0] = &buffer[0 * LCD_WIDTH];
104+
stripPointers[1] = &buffer[1 * LCD_WIDTH];
105+
stripPointers[2] = &buffer[2 * LCD_WIDTH];
106+
stripPointers[3] = &buffer[3 * LCD_WIDTH];
107+
}
108+
109+
/**
110+
* Plays a transition animation between two framebuffers.
111+
*
112+
* If forward is true, this displays a forward navigation to the second framebuffer contents.
113+
* Otherwise a rewinding navigation animation is shown to the second framebuffer contents.
114+
*/
115+
bool LCD::scrollHorizontal(const bool dirForward, uint16_t progress, uint8_t offset) {
116+
uint8_t *stripBackPointers[4];
117+
stripBackPointers[0] = &secondFrameBuffer[0 * LCD_WIDTH];
118+
stripBackPointers[1] = &secondFrameBuffer[1 * LCD_WIDTH];
119+
stripBackPointers[2] = &secondFrameBuffer[2 * LCD_WIDTH];
120+
stripBackPointers[3] = &secondFrameBuffer[3 * LCD_WIDTH];
121+
122+
// When forward, current contents move to the left out.
123+
// Otherwise the contents move to the right out.
124+
uint8_t oldStart = dirForward ? 0 : progress;
125+
uint8_t oldPrevious = dirForward ? progress - offset : offset;
126+
127+
// Content from the second framebuffer moves in from the right (forward)
128+
// or from the left (not forward).
129+
uint8_t newStart = dirForward ? LCD_WIDTH - progress : 0;
130+
uint8_t newEnd = dirForward ? 0 : LCD_WIDTH - progress;
131+
132+
offset = progress;
133+
134+
memmove(&stripPointers[0][oldStart], &stripPointers[0][oldPrevious], LCD_WIDTH - progress);
135+
memmove(&stripPointers[1][oldStart], &stripPointers[1][oldPrevious], LCD_WIDTH - progress);
136+
memmove(&stripPointers[2][oldStart], &stripPointers[2][oldPrevious], LCD_WIDTH - progress);
137+
memmove(&stripPointers[3][oldStart], &stripPointers[3][oldPrevious], LCD_WIDTH - progress);
138+
139+
memmove(&stripPointers[0][newStart], &stripBackPointers[0][newEnd], progress);
140+
memmove(&stripPointers[1][newStart], &stripBackPointers[1][newEnd], progress);
141+
memmove(&stripPointers[2][newStart], &stripBackPointers[2][newEnd], progress);
142+
memmove(&stripPointers[3][newStart], &stripBackPointers[3][newEnd], progress);
143+
144+
return true;
145+
}
146+
147+
void LCD::useSecondaryFramebuffer(bool useSecondary) {
148+
if (useSecondary) {
149+
setFramebuffer(secondFrameBuffer);
150+
} else {
151+
setFramebuffer(screenBuffer);
152+
}
153+
}
154+
155+
/**
156+
* This assumes that the current display output buffer has the current on screen contents
157+
* Then the secondary buffer has the "new" contents to be slid up onto the screen
158+
* Sadly we cant use the hardware scroll as some devices with the 128x32 screens dont have the GRAM for holding both screens at once
159+
*
160+
* **This function blocks until the transition has completed or user presses button**
161+
*/
162+
bool LCD::scrollDown(uint8_t pos) {
163+
164+
// For each line, we shuffle all bits up a row
165+
for (uint8_t xPos = 0; xPos < LCD_WIDTH; xPos++) {
166+
const uint16_t firstStripPos = xPos;
167+
const uint16_t secondStripPos = firstStripPos + LCD_WIDTH;
168+
const uint16_t thirdStripPos = secondStripPos + LCD_WIDTH;
169+
const uint16_t fourthStripPos = thirdStripPos + LCD_WIDTH;
170+
171+
// Move the MSB off the first strip, and pop MSB from second strip onto the first strip
172+
screenBuffer[firstStripPos] = (screenBuffer[firstStripPos] >> 1) | ((screenBuffer[secondStripPos] & 0x01) << 7);
173+
// Now shuffle off the second strip
174+
screenBuffer[secondStripPos] = (screenBuffer[secondStripPos] >> 1) | ((screenBuffer[thirdStripPos] & 0x01) << 7);
175+
// Now shuffle off the third strip
176+
screenBuffer[thirdStripPos] = (screenBuffer[thirdStripPos] >> 1) | ((screenBuffer[fourthStripPos] & 0x01) << 7);
177+
// Now forth strip gets the start of the new buffer
178+
screenBuffer[fourthStripPos] = (screenBuffer[fourthStripPos] >> 1) | ((secondFrameBuffer[firstStripPos] & 0x01) << 7);
179+
// Now cycle all the secondary buffers
180+
181+
secondFrameBuffer[firstStripPos] = (secondFrameBuffer[firstStripPos] >> 1) | ((secondFrameBuffer[secondStripPos] & 0x01) << 7);
182+
secondFrameBuffer[secondStripPos] = (secondFrameBuffer[secondStripPos] >> 1) | ((secondFrameBuffer[thirdStripPos] & 0x01) << 7);
183+
secondFrameBuffer[thirdStripPos] = (secondFrameBuffer[thirdStripPos] >> 1) | ((secondFrameBuffer[fourthStripPos] & 0x01) << 7);
184+
// Finally on the bottom row; we shuffle it up ready
185+
secondFrameBuffer[fourthStripPos] >>= 1;
186+
}
187+
188+
return true;
189+
}
190+
/**
191+
* This assumes that the current display output buffer has the current on screen contents
192+
* Then the secondary buffer has the "new" contents to be slid down onto the screen
193+
* Sadly we cant use the hardware scroll as some devices with the 128x32 screens dont have the GRAM for holding both screens at once
194+
*
195+
* **This function blocks until the transition has completed or user presses button**
196+
*/
197+
bool LCD::scrollUp(uint8_t pos) {
198+
// For each line, we shuffle all bits down a row
199+
for (uint8_t xPos = 0; xPos < LCD_WIDTH; xPos++) {
200+
const uint16_t firstStripPos = xPos;
201+
const uint16_t secondStripPos = firstStripPos + LCD_WIDTH;
202+
const uint16_t thirdStripPos = secondStripPos + LCD_WIDTH;
203+
const uint16_t fourthStripPos = thirdStripPos + LCD_WIDTH;
204+
205+
// We are shffling LSB's off the end and pushing bits down
206+
screenBuffer[fourthStripPos] = (screenBuffer[fourthStripPos] << 1) | ((screenBuffer[thirdStripPos] & 0x80) >> 7);
207+
screenBuffer[thirdStripPos] = (screenBuffer[thirdStripPos] << 1) | ((screenBuffer[secondStripPos] & 0x80) >> 7);
208+
screenBuffer[secondStripPos] = (screenBuffer[secondStripPos] << 1) | ((screenBuffer[firstStripPos] & 0x80) >> 7);
209+
screenBuffer[firstStripPos] = (screenBuffer[firstStripPos] << 1) | ((secondFrameBuffer[fourthStripPos] & 0x80) >> 7);
210+
211+
secondFrameBuffer[fourthStripPos] = (secondFrameBuffer[fourthStripPos] << 1) | ((secondFrameBuffer[thirdStripPos] & 0x80) >> 7);
212+
secondFrameBuffer[thirdStripPos] = (secondFrameBuffer[thirdStripPos] << 1) | ((secondFrameBuffer[secondStripPos] & 0x80) >> 7);
213+
secondFrameBuffer[secondStripPos] = (secondFrameBuffer[secondStripPos] << 1) | ((secondFrameBuffer[firstStripPos] & 0x80) >> 7);
214+
// Finally on the bottom row; we shuffle it up ready
215+
secondFrameBuffer[firstStripPos] <<= 1;
216+
}
217+
218+
return true;
219+
}
220+
221+
void LCD::setRotation(bool leftHanded) {
222+
// TODO implement
223+
}
224+
225+
void LCD::setBrightness(uint8_t brightness) {
226+
// TODO implement
227+
}
228+
229+
void LCD::setInverse(bool inverse) {
230+
// TODO implement
231+
}
232+
233+
void LCD::flushSecondBuffer(void) {
234+
memcpy(screenBuffer, secondFrameBuffer, sizeof(screenBuffer));
235+
}
236+
237+
// Draw an area, but y must be aligned on 0/8 offset
238+
void LCD::drawArea(int16_t x, int8_t y, uint8_t width, uint8_t height, const uint8_t *ptr) {
239+
// Splat this from x->x+width in two strides
240+
if (x <= -width) {
241+
return; // cutoffleft
242+
}
243+
if (x > LCD_WIDTH) {
244+
return; // cutoff right
245+
}
246+
247+
uint8_t visibleStart = 0;
248+
uint8_t visibleEnd = width;
249+
250+
// trimming to draw partials
251+
if (x < 0) {
252+
visibleStart -= x; // subtract negative value == add absolute value
253+
}
254+
if (x + width > LCD_WIDTH) {
255+
visibleEnd = LCD_WIDTH - x;
256+
}
257+
uint8_t rowsDrawn = 0;
258+
while (height > 0) {
259+
for (uint8_t xx = visibleStart; xx < visibleEnd; xx++) {
260+
stripPointers[(y / 8) + rowsDrawn][x + xx] = ptr[xx + (rowsDrawn * width)];
261+
}
262+
height -= 8;
263+
rowsDrawn++;
264+
}
265+
}
266+
267+
// Draw an area, but y must be aligned on 0/8 offset
268+
// For data which has octets swapped in a 16-bit word.
269+
void LCD::drawAreaSwapped(int16_t x, int8_t y, uint8_t width, uint8_t height, const uint8_t *ptr) {
270+
// Splat this from x->x+width in two strides
271+
if (x <= -width) {
272+
return; // cutoffleft
273+
}
274+
if (x > LCD_WIDTH) {
275+
return; // cutoff right
276+
}
277+
278+
uint8_t visibleStart = 0;
279+
uint8_t visibleEnd = width;
280+
281+
// trimming to draw partials
282+
if (x < 0) {
283+
visibleStart -= x; // subtract negative value == add absolute value
284+
}
285+
if (x + width > LCD_WIDTH) {
286+
visibleEnd = LCD_WIDTH - x;
287+
}
288+
289+
uint8_t rowsDrawn = 0;
290+
while (height > 0) {
291+
for (uint8_t xx = visibleStart; xx < visibleEnd; xx += 2) {
292+
stripPointers[(y / 8) + rowsDrawn][x + xx] = ptr[xx + 1 + (rowsDrawn * width)];
293+
stripPointers[(y / 8) + rowsDrawn][x + xx + 1] = ptr[xx + (rowsDrawn * width)];
294+
}
295+
height -= 8;
296+
rowsDrawn++;
297+
}
298+
}
299+
300+
void LCD::fillArea(int16_t x, int8_t y, uint8_t wide, uint8_t height, const uint8_t value) {
301+
// Splat this from x->x+wide in two strides
302+
if (x <= -wide) {
303+
return; // cutoffleft
304+
}
305+
if (x > LCD_WIDTH) {
306+
return; // cutoff right
307+
}
308+
309+
uint8_t visibleStart = 0;
310+
uint8_t visibleEnd = wide;
311+
312+
// trimming to draw partials
313+
if (x < 0) {
314+
visibleStart -= x; // subtract negative value == add absolute value
315+
}
316+
if (x + wide > LCD_WIDTH) {
317+
visibleEnd = LCD_WIDTH - x;
318+
}
319+
320+
uint8_t rowsDrawn = 0;
321+
while (height > 0) {
322+
for (uint8_t xx = visibleStart; xx < visibleEnd; xx++) {
323+
stripPointers[(y / 8) + rowsDrawn][x + xx] = value;
324+
}
325+
height -= 8;
326+
rowsDrawn++;
327+
}
328+
}
329+
330+
void LCD::drawFilledRect(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, bool clear) {
331+
// Ensure coordinates are within bounds
332+
if (x0 >= LCD_WIDTH || y0 >= LCD_HEIGHT || x1 >= LCD_WIDTH || y1 >= LCD_HEIGHT) {
333+
return;
334+
}
335+
336+
// Calculate the height in rows
337+
uint8_t startRow = y0 / 8;
338+
uint8_t endRow = y1 / 8;
339+
uint8_t startMask = 0xFF << (y0 % 8);
340+
uint8_t endMask = 0xFF >> (7 - (y1 % 8));
341+
342+
for (uint8_t row = startRow; row <= endRow; row++) {
343+
uint8_t mask = 0xFF;
344+
if (row == startRow) {
345+
mask &= startMask;
346+
}
347+
if (row == endRow) {
348+
mask &= endMask;
349+
}
350+
351+
for (uint8_t x = x0; x <= x1; x++) {
352+
if (clear) {
353+
stripPointers[row][x] &= ~mask;
354+
} else {
355+
stripPointers[row][x] |= mask;
356+
}
357+
}
358+
}
359+
}

0 commit comments

Comments
 (0)