display.cpp
11.3 KB
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
#include <ion.h>
#include "display.h"
#include "regs/regs.h"
extern "C" {
#include <assert.h>
}
/* This driver interfaces with the ST7789V LCD controller.
* This chip keeps a whole frame in SRAM memory and feeds it to the LCD panel as
* needed. We use the STM32's FSMC to drive the bus between the ST7789V. Once
* configured, we only need to write in the address space of the MCU to actually
* send some data to the LCD controller. */
#define USE_DMA_FOR_PUSH_PIXELS 0
#define USE_DMA_FOR_PUSH_COLOR 0
#define USE_DMA (USE_DMA_FOR_PUSH_PIXELS|USE_DMA_FOR_PUSH_COLOR)
// Public Ion::Display methods
namespace Ion {
namespace Display {
void pushRect(KDRect r, const KDColor * pixels) {
#if USE_DMA
Device::waitForPendingDMAUploadCompletion();
#endif
Device::setDrawingArea(r, Device::Orientation::Landscape);
Device::pushPixels(pixels, r.width()*r.height());
}
void pushRectUniform(KDRect r, KDColor c) {
#if USE_DMA
Device::waitForPendingDMAUploadCompletion();
#endif
Device::setDrawingArea(r, Device::Orientation::Portrait);
Device::pushColor(c, r.width()*r.height());
}
void pullRect(KDRect r, KDColor * pixels) {
#if USE_DMA
Device::waitForPendingDMAUploadCompletion();
#endif
Device::setDrawingArea(r, Device::Orientation::Landscape);
Device::pullPixels(pixels, r.width()*r.height());
}
void waitForVBlank() {
// We want to return as soon as the TE line is transitionning from "DOWN" to "UP"
while (Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) {
// Loop while high, exit when low
// Wait for zero
}
while (!Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) {
// Loop while low, exit when high
}
}
}
}
// Private Ion::Display::Device methods
namespace Ion {
namespace Display {
namespace Device {
static inline void send_data(uint16_t d) {
*DataAddress = d;
}
static inline uint16_t receive_data() {
return *DataAddress;
}
template<typename... Args>
static inline void send_data(uint16_t d, Args... other) {
send_data(d);
send_data(other...);
}
static inline void send_command(Command c) {
*CommandAddress = c;
}
template<typename... Args>
static inline void send_command(Command c, Args... d) {
send_command(c);
send_data(d...);
}
void init() {
#if USE_DMA
initDMA();
#endif
initGPIO();
initFSMC();
initPanel();
}
void shutdown() {
shutdownPanel();
shutdownFSMC();
shutdownGPIO();
}
#if USE_DMA
void initDMA() {
// Only DMA2 can perform memory-to-memory transfers
//assert(DMAEngine == DMA2);
/* In memory-to-memory transfers, the "peripheral" is the source and the
* "memory" is the destination. In other words, memory is copied from address
* DMA_SxPAR to address DMA_SxM0AR. */
DMAEngine.SCR(DMAStream)->setDIR(DMA::SCR::Direction::MemoryToMemory);
DMAEngine.SM0AR(DMAStream)->set((uint32_t)DataAddress);
DMAEngine.SCR(DMAStream)->setMSIZE(DMA::SCR::DataSize::HalfWord);
DMAEngine.SCR(DMAStream)->setPSIZE(DMA::SCR::DataSize::HalfWord);
DMAEngine.SCR(DMAStream)->setMBURST(DMA::SCR::Burst::Incremental4);
DMAEngine.SCR(DMAStream)->setPBURST(DMA::SCR::Burst::Incremental4);
DMAEngine.SCR(DMAStream)->setMINC(false);
}
void waitForPendingDMAUploadCompletion() {
// Loop until DMA engine available
while (DMAEngine.SCR(DMAStream)->getEN()) {
}
}
static inline void startDMAUpload(const KDColor * src, bool incrementSrc, uint16_t length) {
// Reset interruption markers
DMAEngine.LIFCR()->set(0xF7D0F7D);
DMAEngine.SNDTR(DMAStream)->set(length);
DMAEngine.SPAR(DMAStream)->set((uint32_t)src);
DMAEngine.SCR(DMAStream)->setPINC(incrementSrc);
DMAEngine.SCR(DMAStream)->setEN(true);
}
#endif
void initGPIO() {
// All the FSMC GPIO pins use the alternate function number 12
for(const GPIOPin & g : FSMCPins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction);
g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF12);
}
// Turn on the power
PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Output);
PowerPin.group().ODR()->set(PowerPin.pin(), true);
// Turn on the reset pin
ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Output);
ResetPin.group().ODR()->set(ResetPin.pin(), true);
// Turn on the extended command pin
ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Output);
ExtendedCommandPin.group().ODR()->set(ExtendedCommandPin.pin(), true);
// Turn on the Tearing Effect pin
TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Input);
TearingEffectPin.group().PUPDR()->setPull(TearingEffectPin.pin(), GPIO::PUPDR::Pull::None);
msleep(120);
}
void shutdownGPIO() {
// All the FSMC GPIO pins use the alternate function number 12
for(const GPIOPin & g : FSMCPins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog);
g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None);
}
ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Analog);
ResetPin.group().PUPDR()->setPull(ResetPin.pin(), GPIO::PUPDR::Pull::None);
PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Analog);
PowerPin.group().PUPDR()->setPull(PowerPin.pin(), GPIO::PUPDR::Pull::None);
ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Analog);
ExtendedCommandPin.group().PUPDR()->setPull(ExtendedCommandPin.pin(), GPIO::PUPDR::Pull::None);
TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Analog);
}
void initFSMC() {
/* Set up the FSMC control registers.
* We address the LCD panel as if it were an SRAM module, using a 16bits wide
* bus, non-multiplexed.
* The STM32 FSMC supports two kinds of memory access modes :
* - Base modes (1 and 2), which use the same timings for reads and writes
* - Extended modes (named A to D), which can be customized further.
* The LCD panel can be written to faster than it can be read from, therefore
* we want to use one of the extended modes. */
FSMC.BCR(FSMCMemoryBank)->setEXTMOD(true);
FSMC.BCR(FSMCMemoryBank)->setWREN(true);
FSMC.BCR(FSMCMemoryBank)->setMWID(FSMC::BCR::MWID::SIXTEEN_BITS);
FSMC.BCR(FSMCMemoryBank)->setMTYP(FSMC::BCR::MTYP::SRAM);
FSMC.BCR(FSMCMemoryBank)->setMUXEN(false);
FSMC.BCR(FSMCMemoryBank)->setMBKEN(true);
/* We now need to set the actual timings. First, the FSMC and LCD specs don't
* use the same names. Here's the mapping:
*
* FSMC | LCD
* -----+-----
* NOE | RDX
* NWE | WRX
* NE1 | CSX
* A16 | D/CX
* Dn | Dn
*
* We need to set the values of the BTR and BWTR which gives the timings for
* reading and writing. Note that the STM32 datasheet doesn't take into
* account the time needed to actually switch from one logic state to another,
* whereas the ST7789V one does, so we'll add T(R) and T(F) as needed.
* Last but not least, timings on the STM32 have to be expressed in terms of
* HCLK = 1/96MHz = 10.42ns.
* - We'll pick Mode A which corresponds to SRAM with OE toggling
* - ADDSET = T(AST) + T(F) = 0ns + 15ns = 2 HCLK
* - ADDHLD is unused in this mode, set to 0
* - DATAST(read) = T(RDLFM) + T(R) = 355ns + 15ns = 36 HCLK
* DATAST(write) = T(WRL) + T(R) = 15ns + 15ns = 3 HCLK
* - BUSTURN(read) = T(RDHFM) + T(F) = 90ns + 15ns = 10 HCLK
* BUSTURN(write) = T(RDHFM) + T(F) = 15ns + 15ns = 3 HCLK
*/
// Read timing from the LCD
FSMC.BTR(FSMCMemoryBank)->setADDSET(2);
FSMC.BTR(FSMCMemoryBank)->setADDHLD(0);
FSMC.BTR(FSMCMemoryBank)->setDATAST(36);
FSMC.BTR(FSMCMemoryBank)->setBUSTURN(10);
FSMC.BTR(FSMCMemoryBank)->setACCMOD(FSMC::BTR::ACCMOD::A);
// Write timings for the LCD
FSMC.BWTR(FSMCMemoryBank)->setADDSET(2);
FSMC.BWTR(FSMCMemoryBank)->setADDHLD(0);
FSMC.BWTR(FSMCMemoryBank)->setDATAST(3);
FSMC.BWTR(FSMCMemoryBank)->setBUSTURN(3);
FSMC.BWTR(FSMCMemoryBank)->setACCMOD(FSMC::BWTR::ACCMOD::A);
}
void shutdownFSMC() {
}
void initPanel() {
send_command(Command::Reset);
msleep(5);
send_command(Command::SleepOut);
msleep(5);
send_command(Command::PixelFormatSet, 0x05);
send_command(Command::TearingEffectLineOn, 0x00);
send_command(Command::FrameRateControl, 0x1E); // 40 Hz frame rate
send_command(Command::DisplayOn);
}
void shutdownPanel() {
send_command(Command::DisplayOff);
send_command(Command::SleepIn);
msleep(5);
}
void setDrawingArea(KDRect r, Orientation o) {
uint16_t x_start, x_end, y_start, y_end;
if (o == Orientation::Landscape) {
send_command(Command::MemoryAccessControl, 0xA0);
x_start = r.x();
x_end = r.x() + r.width() - 1;
y_start = r.y();
y_end = r.y() + r.height() - 1;
} else {
send_command(Command::MemoryAccessControl, 0x00);
x_start = r.y();
x_end = r.y() + r.height() - 1;
y_start = Ion::Display::Width - (r.x() + r.width());
y_end = Ion::Display::Width - r.x() - 1;
}
send_command(
Command::ColumnAddressSet,
(x_start >> 8),
(x_start & 0xFF),
(x_end >> 8),
(x_end & 0xFF)
);
send_command(
Command::PageAddressSet,
(y_start >> 8),
(y_start & 0xFF),
(y_end >> 8),
(y_end & 0xFF)
);
}
void pushPixels(const KDColor * pixels, size_t numberOfPixels) {
send_command(Command::MemoryWrite);
/* Theoretically, we should not be able to use DMA here. Indeed, we have no
* guarantee that the content at "pixels" will remain valid once we exit this
* function call. In practice, we might be able to use DMA here because most
* of the time we push pixels from static locations. */
#if USE_DMA_FOR_PUSH_PIXELS
startDMAUpload(pixels, true, numberOfPixels);
#else
while (numberOfPixels > 8) {
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
send_data(*pixels++);
numberOfPixels -= 8;
}
while (numberOfPixels--) {
send_data(*pixels++);
}
#endif
}
void pushColor(KDColor color, size_t numberOfPixels) {
send_command(Command::MemoryWrite);
#if USE_DMA_FOR_PUSH_COLOR
/* The "color" variable lives on the stack. We cannot take its address because
* it will stop being valid as soon as we return. An easy workaround is to
* duplicate the content in a static variable, whose value is guaranteed to be
* kept until the next pushColor call. */
static KDColor staticColor;
staticColor = color;
startDMAUpload(&staticColor, false, (numberOfPixels > 64000 ? 64000 : numberOfPixels));
#else
while (numberOfPixels--) {
send_data(color);
}
#endif
}
void pullPixels(KDColor * pixels, size_t numberOfPixels) {
if (numberOfPixels == 0) {
return;
}
send_command(Command::PixelFormatSet, 0x06);
send_command(Command::MemoryRead);
receive_data(); // First read is dummy data, per datasheet
while (true) {
if (numberOfPixels == 0) {
break;
}
uint16_t one = receive_data();
uint16_t two = receive_data();
uint16_t firstPixel = (one & 0xF800) | (one & 0xFC) << 3 | (two & 0xF800) >> 11;
*pixels++ = KDColor::RGB16(firstPixel);
numberOfPixels--;
if (numberOfPixels == 0) {
break;
}
uint16_t three = receive_data();
uint16_t secondPixel = (two & 0xF8) << 8 | (three & 0xFC00) >> 5 | (three & 0xF8) >> 3;
*pixels++ = KDColor::RGB16(secondPixel);
numberOfPixels--;
}
send_command(Command::PixelFormatSet, 0x05);
}
}
}
}