Numworks Epsilon  1.4.1
Graphing Calculator Operating System
display.cpp
Go to the documentation of this file.
1 #include <ion.h>
2 #include "display.h"
3 #include "regs/regs.h"
4 extern "C" {
5 #include <assert.h>
6 }
7 
8 /* This driver interfaces with the ST7789V LCD controller.
9  * This chip keeps a whole frame in SRAM memory and feeds it to the LCD panel as
10  * needed. We use the STM32's FSMC to drive the bus between the ST7789V. Once
11  * configured, we only need to write in the address space of the MCU to actually
12  * send some data to the LCD controller. */
13 
14 #define USE_DMA_FOR_PUSH_PIXELS 0
15 #define USE_DMA_FOR_PUSH_COLOR 0
16 
17 #define USE_DMA (USE_DMA_FOR_PUSH_PIXELS|USE_DMA_FOR_PUSH_COLOR)
18 
19 // Public Ion::Display methods
20 
21 namespace Ion {
22 namespace Display {
23 
24 void pushRect(KDRect r, const KDColor * pixels) {
25 #if USE_DMA
27 #endif
29  Device::pushPixels(pixels, r.width()*r.height());
30 }
31 
33 #if USE_DMA
35 #endif
37  Device::pushColor(c, r.width()*r.height());
38 }
39 
40 void pullRect(KDRect r, KDColor * pixels) {
41 #if USE_DMA
43 #endif
45  Device::pullPixels(pixels, r.width()*r.height());
46 }
47 
48 void waitForVBlank() {
49  // We want to return as soon as the TE line is transitionning from "DOWN" to "UP"
50  while (Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) {
51  // Loop while high, exit when low
52  // Wait for zero
53  }
54  while (!Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) {
55  // Loop while low, exit when high
56  }
57 }
58 
59 }
60 }
61 
62 // Private Ion::Display::Device methods
63 
64 namespace Ion {
65 namespace Display {
66 namespace Device {
67 
68 #define SEND_COMMAND(c, ...) {*CommandAddress = Command::c; uint8_t data[] = {__VA_ARGS__}; for (unsigned int i=0;i<sizeof(data);i++) { *DataAddress = data[i];};}
69 
70 void init() {
71 #if USE_DMA
72  initDMA();
73 #endif
74  initGPIO();
75  initFSMC();
76  initPanel();
77 }
78 
79 void shutdown() {
80  shutdownPanel();
81  shutdownFSMC();
82  shutdownGPIO();
83 }
84 
85 #if USE_DMA
86 void initDMA() {
87  // Only DMA2 can perform memory-to-memory transfers
88  //assert(DMAEngine == DMA2);
89 
90  /* In memory-to-memory transfers, the "peripheral" is the source and the
91  * "memory" is the destination. In other words, memory is copied from address
92  * DMA_SxPAR to address DMA_SxM0AR. */
93 
94  DMAEngine.SCR(DMAStream)->setDIR(DMA::SCR::Direction::MemoryToMemory);
95  DMAEngine.SM0AR(DMAStream)->set((uint32_t)DataAddress);
96  DMAEngine.SCR(DMAStream)->setMSIZE(DMA::SCR::DataSize::HalfWord);
97  DMAEngine.SCR(DMAStream)->setPSIZE(DMA::SCR::DataSize::HalfWord);
98  DMAEngine.SCR(DMAStream)->setMBURST(DMA::SCR::Burst::Incremental4);
99  DMAEngine.SCR(DMAStream)->setPBURST(DMA::SCR::Burst::Incremental4);
100  DMAEngine.SCR(DMAStream)->setMINC(false);
101 }
102 
104  // Loop until DMA engine available
105  while (DMAEngine.SCR(DMAStream)->getEN()) {
106  }
107 }
108 
109 static inline void startDMAUpload(const KDColor * src, bool incrementSrc, uint16_t length) {
110  // Reset interruption markers
111  DMAEngine.LIFCR()->set(0xF7D0F7D);
112 
113  DMAEngine.SNDTR(DMAStream)->set(length);
114  DMAEngine.SPAR(DMAStream)->set((uint32_t)src);
115  DMAEngine.SCR(DMAStream)->setPINC(incrementSrc);
116  DMAEngine.SCR(DMAStream)->setEN(true);
117 }
118 #endif
119 
120 void initGPIO() {
121  // All the FSMC GPIO pins use the alternate function number 12
122  for(const GPIOPin & g : FSMCPins) {
123  g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction);
124  g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF12);
125  }
126 
127  // Turn on the power
128  PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Output);
129  PowerPin.group().ODR()->set(PowerPin.pin(), true);
130 
131  // Turn on the reset pin
132  ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Output);
133  ResetPin.group().ODR()->set(ResetPin.pin(), true);
134 
135  // Turn on the extended command pin
136  ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Output);
137  ExtendedCommandPin.group().ODR()->set(ExtendedCommandPin.pin(), true);
138 
139  // Turn on the Tearing Effect pin
140  TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Input);
141  TearingEffectPin.group().PUPDR()->setPull(TearingEffectPin.pin(), GPIO::PUPDR::Pull::None);
142 
143  msleep(120);
144 }
145 
146 
147 void shutdownGPIO() {
148  // All the FSMC GPIO pins use the alternate function number 12
149  for(const GPIOPin & g : FSMCPins) {
150  g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog);
151  g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None);
152  }
153 
154  ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Analog);
155  ResetPin.group().PUPDR()->setPull(ResetPin.pin(), GPIO::PUPDR::Pull::None);
156 
157  PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Analog);
158  PowerPin.group().PUPDR()->setPull(PowerPin.pin(), GPIO::PUPDR::Pull::None);
159 
160  ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Analog);
161  ExtendedCommandPin.group().PUPDR()->setPull(ExtendedCommandPin.pin(), GPIO::PUPDR::Pull::None);
162 
163  TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Analog);
164 }
165 
166 void initFSMC() {
167  /* Set up the FSMC control registers.
168  * We address the LCD panel as if it were an SRAM module, using a 16bits wide
169  * bus, non-multiplexed.
170  * The STM32 FSMC supports two kinds of memory access modes :
171  * - Base modes (1 and 2), which use the same timings for reads and writes
172  * - Extended modes (named A to D), which can be customized further.
173  * The LCD panel can be written to faster than it can be read from, therefore
174  * we want to use one of the extended modes. */
175  FSMC.BCR(FSMCMemoryBank)->setEXTMOD(true);
176  FSMC.BCR(FSMCMemoryBank)->setWREN(true);
177  FSMC.BCR(FSMCMemoryBank)->setMWID(FSMC::BCR::MWID::SIXTEEN_BITS);
178  FSMC.BCR(FSMCMemoryBank)->setMTYP(FSMC::BCR::MTYP::SRAM);
179  FSMC.BCR(FSMCMemoryBank)->setMUXEN(false);
180  FSMC.BCR(FSMCMemoryBank)->setMBKEN(true);
181 
182  /* We now need to set the actual timings. First, the FSMC and LCD specs don't
183  * use the same names. Here's the mapping:
184  *
185  * FSMC | LCD
186  * -----+-----
187  * NOE | RDX
188  * NWE | WRX
189  * NE1 | CSX
190  * A16 | D/CX
191  * Dn | Dn
192  *
193  * We need to set the values of the BTR and BWTR which gives the timings for
194  * reading and writing. Note that the STM32 datasheet doesn't take into
195  * account the time needed to actually switch from one logic state to another,
196  * whereas the ST7789V one does, so we'll add T(R) and T(F) as needed.
197  * Last but not least, timings on the STM32 have to be expressed in terms of
198  * HCLK = 1/96MHz = 10.42ns.
199  * - We'll pick Mode A which corresponds to SRAM with OE toggling
200  * - ADDSET = T(AST) + T(F) = 0ns + 15ns = 2 HCLK
201  * - ADDHLD is unused in this mode, set to 0
202  * - DATAST(read) = T(RDLFM) + T(R) = 355ns + 15ns = 36 HCLK
203  * DATAST(write) = T(WRL) + T(R) = 15ns + 15ns = 3 HCLK
204  * - BUSTURN(read) = T(RDHFM) + T(F) = 90ns + 15ns = 10 HCLK
205  * BUSTURN(write) = T(RDHFM) + T(F) = 15ns + 15ns = 3 HCLK
206  */
207 
208  // Read timing from the LCD
209  FSMC.BTR(FSMCMemoryBank)->setADDSET(2);
210  FSMC.BTR(FSMCMemoryBank)->setADDHLD(0);
211  FSMC.BTR(FSMCMemoryBank)->setDATAST(36);
212  FSMC.BTR(FSMCMemoryBank)->setBUSTURN(10);
213  FSMC.BTR(FSMCMemoryBank)->setACCMOD(FSMC::BTR::ACCMOD::A);
214 
215  // Write timings for the LCD
216  FSMC.BWTR(FSMCMemoryBank)->setADDSET(2);
217  FSMC.BWTR(FSMCMemoryBank)->setADDHLD(0);
218  FSMC.BWTR(FSMCMemoryBank)->setDATAST(3);
219  FSMC.BWTR(FSMCMemoryBank)->setBUSTURN(3);
220  FSMC.BWTR(FSMCMemoryBank)->setACCMOD(FSMC::BWTR::ACCMOD::A);
221 }
222 
223 void shutdownFSMC() {
224 }
225 
226 void initPanel() {
227  *CommandAddress = Command::Reset;
228  msleep(5);
229 
230  *CommandAddress = Command::SleepOut;
231  msleep(5);
232 
234  //SEND_COMMAND(MemoryAccessControl, 0xA0);
236  SEND_COMMAND(FrameRateControl, 0x1E); // 40 Hz frame rate
237 
238  *CommandAddress = Command::DisplayOn;
239 }
240 
242  *CommandAddress = Command::DisplayOff;
243  *CommandAddress = Command::SleepIn;
244  msleep(5);
245 }
246 
248  uint16_t x_start, x_end, y_start, y_end;
249 
250  if (o == Orientation::Landscape) {
252  x_start = r.x();
253  x_end = r.x() + r.width() - 1;
254  y_start = r.y();
255  y_end = r.y() + r.height() - 1;
256  } else {
258  x_start = r.y();
259  x_end = r.y() + r.height() - 1;
260  y_start = Ion::Display::Width - (r.x() + r.width());
261  y_end = Ion::Display::Width - r.x() - 1;
262  }
263 
264  *CommandAddress = Command::ColumnAddressSet;
265  *DataAddress = (x_start >> 8);
266  *DataAddress = (x_start & 0xFF);
267  *DataAddress = (x_end >> 8);
268  *DataAddress = (x_end & 0xFF);
269 
270  *CommandAddress = Command::PageAddressSet;
271  *DataAddress = (y_start >> 8);
272  *DataAddress = (y_start & 0xFF);
273  *DataAddress = (y_end >> 8);
274  *DataAddress = (y_end & 0xFF);
275 }
276 
277 void pushPixels(const KDColor * pixels, size_t numberOfPixels) {
278  *CommandAddress = Command::MemoryWrite;
279  /* Theoretically, we should not be able to use DMA here. Indeed, we have no
280  * guarantee that the content at "pixels" will remain valid once we exit this
281  * function call. In practice, we might be able to use DMA here because most
282  * of the time we push pixels from static locations. */
283 #if USE_DMA_FOR_PUSH_PIXELS
284  startDMAUpload(pixels, true, numberOfPixels);
285 #else
286  while (numberOfPixels > 8) {
287  *DataAddress = *pixels++;
288  *DataAddress = *pixels++;
289  *DataAddress = *pixels++;
290  *DataAddress = *pixels++;
291  *DataAddress = *pixels++;
292  *DataAddress = *pixels++;
293  *DataAddress = *pixels++;
294  *DataAddress = *pixels++;
295  numberOfPixels -= 8;
296  }
297  while (numberOfPixels--) {
298  *DataAddress = *pixels++;
299  }
300 #endif
301 }
302 
303 void pushColor(KDColor color, size_t numberOfPixels) {
304  *CommandAddress = Command::MemoryWrite;
305 #if USE_DMA_FOR_PUSH_COLOR
306  /* The "color" variable lives on the stack. We cannot take its address because
307  * it will stop being valid as soon as we return. An easy workaround is to
308  * duplicate the content in a static variable, whose value is guaranteed to be
309  * kept until the next pushColor call. */
310  static KDColor staticColor;
311  staticColor = color;
312  startDMAUpload(&staticColor, false, (numberOfPixels > 64000 ? 64000 : numberOfPixels));
313 #else
314  while (numberOfPixels--) {
315  *DataAddress = color;
316  }
317 #endif
318 }
319 
320 void pullPixels(KDColor * pixels, size_t numberOfPixels) {
321  if (numberOfPixels == 0) {
322  return;
323  }
325  *CommandAddress = Command::MemoryRead;
326  *DataAddress; // First read is dummy data, per datasheet
327  while (true) {
328  if (numberOfPixels == 0) {
329  break;
330  }
331  uint16_t one = *DataAddress;
332  uint16_t two = *DataAddress;
333  uint16_t firstPixel = (one & 0xF800) | (one & 0xFC) << 3 | (two & 0xF800) >> 11;
334  *pixels++ = KDColor::RGB16(firstPixel);
335  numberOfPixels--;
336 
337  if (numberOfPixels == 0) {
338  break;
339  }
340  uint16_t three = *DataAddress;
341  uint16_t secondPixel = (two & 0xF8) << 8 | (three & 0xFC00) >> 5 | (three & 0xF8) >> 3;
342  *pixels++ = KDColor::RGB16(secondPixel);
343  numberOfPixels--;
344  }
346 }
347 
348 }
349 }
350 }
Definition: fsmc.h:6
KDCoordinate x() const
Definition: rect.h:36
constexpr int Width
Definition: display.h:26
void Display(const char *input)
Definition: display.cpp:11
void pullPixels(KDColor *pixels, size_t numberOfPixels)
Definition: display.cpp:320
void setDrawingArea(KDRect r, Orientation o)
Definition: display.cpp:247
void pullRect(KDRect r, KDColor *pixels)
Definition: display.cpp:26
void msleep(long ms)
Definition: ion.cpp:4
#define one
Definition: k_tan.c:68
void pushPixels(const KDColor *pixels, size_t numberOfPixels)
Definition: display.cpp:277
unsigned short uint16_t
Definition: stdint.h:5
void pushRect(KDRect r, const KDColor *pixels)
Definition: display.cpp:14
#define SEND_COMMAND(c,...)
Definition: display.cpp:68
c(generic_all_nodes)
void pushColor(KDColor color, size_t numberOfPixels)
Definition: display.cpp:303
static constexpr KDColor RGB16(uint16_t rgb)
Definition: color.h:10
unsigned int uint32_t
Definition: stdint.h:6
volatile BCR * BCR(int index) const
Definition: fsmc.h:62
volatile BTR * BTR(int index) const
Definition: fsmc.h:65
void waitForPendingDMAUploadCompletion()
KDCoordinate y() const
Definition: rect.h:37
Definition: rect.h:26
Definition: color.h:6
KDCoordinate width() const
Definition: rect.h:39
Definition: gpio.h:95
volatile BWTR * BWTR(int index) const
Definition: fsmc.h:68
Definition: backlight.h:6
void pushRectUniform(KDRect r, KDColor c)
Definition: display.cpp:20
void waitForVBlank()
Definition: display.cpp:32
KDCoordinate height() const
Definition: rect.h:40