FabGL
ESP32 Display Controller and Graphics Library
vgatextcontroller.cpp
1 
2 #include <stdint.h>
3 #include <stddef.h>
4 #include <string.h>
5 
6 #include "soc/i2s_struct.h"
7 #include "soc/i2s_reg.h"
8 #include "driver/periph_ctrl.h"
9 #include "soc/rtc.h"
10 
11 #include "fabutils.h"
12 #include "vgatextcontroller.h"
13 #include "devdrivers/swgenerator.h"
14 
15 
16 
17 #pragma GCC optimize ("O2")
18 
19 
20 namespace fabgl {
21 
22 
23 // statics
24 
25 volatile int VGATextController::s_scanLine;
26 uint32_t VGATextController::s_blankPatternDWord;
27 uint32_t * VGATextController::s_fgbgPattern = nullptr;
28 int VGATextController::s_textRow;
29 bool VGATextController::s_upperRow;
30 lldesc_t volatile * VGATextController::s_frameResetDesc;
31 
32 #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
33  volatile uint64_t s_vgatxtcycles = 0;
34 #endif
35 
36 
37 
38 
39 
40 VGATextController::VGATextController()
41  : m_charData(nullptr),
42  m_map(nullptr),
43  m_cursorEnabled(false),
44  m_cursorCounter(0),
45  m_cursorSpeed(20),
46  m_cursorRow(0),
47  m_cursorCol(0),
48  m_cursorForeground(0),
49  m_cursorBackground(15)
50 {
51 }
52 
53 
54 VGATextController::~VGATextController()
55 {
56  free((void*)m_charData);
57 }
58 
59 
60 void VGATextController::setTextMap(uint32_t const * map, int rows)
61 {
62  // wait for the end of frame
63  while (m_map != nullptr && s_scanLine < m_timings.VVisibleArea)
64  ;
65  m_rows = rows;
66  m_map = map;
67 }
68 
69 
70 void VGATextController::adjustMapSize(int * columns, int * rows)
71 {
72  if (*columns > 0)
73  *columns = VGATextController_COLUMNS;
74  if (*rows > VGATextController_ROWS)
75  *rows = VGATextController_ROWS;
76 
77 }
78 
79 
80 void VGATextController::init(gpio_num_t VSyncGPIO)
81 {
82  m_DMABuffers = nullptr;
83 
84  m_GPIOStream.begin();
85 
86  // load font into RAM
87  auto font = getFont();
88  int charDataSize = 256 * font->height * ((font->width + 7) / 8);
89  m_charData = (uint8_t*) heap_caps_malloc(charDataSize, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
90  memcpy(m_charData, font->data, charDataSize);
91 }
92 
93 
94 // initializer for 8 colors configuration
95 void VGATextController::begin(gpio_num_t redGPIO, gpio_num_t greenGPIO, gpio_num_t blueGPIO, gpio_num_t HSyncGPIO, gpio_num_t VSyncGPIO)
96 {
97  init(VSyncGPIO);
98 
99  // GPIO configuration for bit 0
100  setupGPIO(redGPIO, VGA_RED_BIT, GPIO_MODE_OUTPUT);
101  setupGPIO(greenGPIO, VGA_GREEN_BIT, GPIO_MODE_OUTPUT);
102  setupGPIO(blueGPIO, VGA_BLUE_BIT, GPIO_MODE_OUTPUT);
103 
104  // GPIO configuration for VSync and HSync
105  setupGPIO(HSyncGPIO, VGA_HSYNC_BIT, GPIO_MODE_OUTPUT);
106  setupGPIO(VSyncGPIO, VGA_VSYNC_BIT, GPIO_MODE_INPUT_OUTPUT); // input/output so can be generated interrupt on falling/rising edge
107 
108  RGB222::lowBitOnly = true;
109  m_bitsPerChannel = 1;
110 }
111 
112 
113 // initializer for 64 colors configuration
114 void VGATextController::begin(gpio_num_t red1GPIO, gpio_num_t red0GPIO, gpio_num_t green1GPIO, gpio_num_t green0GPIO, gpio_num_t blue1GPIO, gpio_num_t blue0GPIO, gpio_num_t HSyncGPIO, gpio_num_t VSyncGPIO)
115 {
116  begin(red0GPIO, green0GPIO, blue0GPIO, HSyncGPIO, VSyncGPIO);
117 
118  // GPIO configuration for bit 1
119  setupGPIO(red1GPIO, VGA_RED_BIT + 1, GPIO_MODE_OUTPUT);
120  setupGPIO(green1GPIO, VGA_GREEN_BIT + 1, GPIO_MODE_OUTPUT);
121  setupGPIO(blue1GPIO, VGA_BLUE_BIT + 1, GPIO_MODE_OUTPUT);
122 
123  RGB222::lowBitOnly = false;
124  m_bitsPerChannel = 2;
125 }
126 
127 
128 // initializer for default configuration
130 {
131  begin(GPIO_NUM_22, GPIO_NUM_21, GPIO_NUM_19, GPIO_NUM_18, GPIO_NUM_5, GPIO_NUM_4, GPIO_NUM_23, GPIO_NUM_15);
132 }
133 
134 
135 void VGATextController::setCursorForeground(Color value)
136 {
137  m_cursorForeground = (int) value;
138 }
139 
140 
141 void VGATextController::setCursorBackground(Color value)
142 {
143  m_cursorBackground = (int) value;
144 }
145 
146 
147 void VGATextController::setupGPIO(gpio_num_t gpio, int bit, gpio_mode_t mode)
148 {
149  configureGPIO(gpio, mode);
150  gpio_matrix_out(gpio, I2S1O_DATA_OUT0_IDX + bit, false, false);
151 }
152 
153 
154 void VGATextController::setResolution(char const * modeline, int viewPortWidth, int viewPortHeight, bool doubleBuffered)
155 {
156  VGATimings timings;
157  if (VGABaseController::convertModelineToTimings(VGATextController_MODELINE, &timings))
158  setResolution(timings);
159 }
160 
161 
162 void VGATextController::setResolution(VGATimings const& timings)
163 {
164  // setResolution() already called? If so, stop and free buffers
165  if (m_DMABuffers) {
166  m_GPIOStream.stop();
167  freeBuffers();
168  }
169 
170  m_timings = timings;
171 
172  // inform base class about screen size
173  setScreenSize(m_timings.HVisibleArea, m_timings.VVisibleArea);
174 
175  m_HVSync = packHVSync(false, false);
176 
177  m_DMABuffersCount = 2 * m_timings.VVisibleArea + m_timings.VFrontPorch + m_timings.VSyncPulse + m_timings.VBackPorch;
178 
179  m_DMABuffers = (lldesc_t*) heap_caps_malloc(m_DMABuffersCount * sizeof(lldesc_t), MALLOC_CAP_DMA);
180 
181  m_lines = (uint32_t*) heap_caps_malloc(VGATextController_CHARHEIGHT * VGATextController_WIDTH, MALLOC_CAP_DMA);
182 
183  int rawLineWidth = m_timings.HFrontPorch + m_timings.HSyncPulse + m_timings.HBackPorch + m_timings.HVisibleArea;
184  m_blankLine = (uint8_t*) heap_caps_malloc(rawLineWidth, MALLOC_CAP_DMA);
185  m_syncLine = (uint8_t*) heap_caps_malloc(rawLineWidth, MALLOC_CAP_DMA);
186 
187  // horiz: FRONTPORCH -> SYNC -> BACKPORCH -> VISIBLEAREA
188  //
189  // vert: VISIBLEAREA
190  // FRONTPORCH
191  // SYNC
192  // BACKPORCH
193 
194  for (int i = 0, visLine = 0, invLine = 0; i < m_DMABuffersCount; ++i) {
195 
196  if (i < m_timings.VVisibleArea * 2) {
197 
198  // first part is the same of a blank line
199  m_DMABuffers[i].eof = (visLine == 0 || visLine == VGATextController_CHARHEIGHT / 2 ? 1 : 0);
200  m_DMABuffers[i].sosf = 0;
201  m_DMABuffers[i].offset = 0;
202  m_DMABuffers[i].owner = 1;
203  m_DMABuffers[i].qe.stqe_next = (lldesc_t*) &m_DMABuffers[i + 1];
204  m_DMABuffers[i].length = m_timings.HFrontPorch + m_timings.HSyncPulse + m_timings.HBackPorch;
205  m_DMABuffers[i].size = (m_DMABuffers[i].length + 3) & (~3);
206  m_DMABuffers[i].buf = (uint8_t*) m_blankLine;
207 
208  ++i;
209 
210  // second part is the visible line
211  m_DMABuffers[i].eof = 0;
212  m_DMABuffers[i].sosf = 0;
213  m_DMABuffers[i].offset = 0;
214  m_DMABuffers[i].owner = 1;
215  m_DMABuffers[i].qe.stqe_next = (lldesc_t*) &m_DMABuffers[i + 1];
216  m_DMABuffers[i].length = m_timings.HVisibleArea;
217  m_DMABuffers[i].size = (m_DMABuffers[i].length + 3) & (~3);
218  m_DMABuffers[i].buf = (uint8_t*)(m_lines) + visLine * VGATextController_WIDTH;
219 
220  ++visLine;
221  if (visLine == VGATextController_CHARHEIGHT)
222  visLine = 0;
223 
224  } else {
225 
226  // vertical porchs and sync
227 
228  bool frameResetDesc = (invLine == 0);
229 
230  if (frameResetDesc)
231  s_frameResetDesc = &m_DMABuffers[i];
232 
233  m_DMABuffers[i].eof = (frameResetDesc ? 1 : 0); // prepare for next frame
234  m_DMABuffers[i].sosf = 0;
235  m_DMABuffers[i].offset = 0;
236  m_DMABuffers[i].owner = 1;
237  m_DMABuffers[i].qe.stqe_next = (lldesc_t*) (i == m_DMABuffersCount - 1 ? &m_DMABuffers[0] : &m_DMABuffers[i + 1]);
238  m_DMABuffers[i].length = rawLineWidth;
239  m_DMABuffers[i].size = (m_DMABuffers[i].length + 3) & (~3);
240 
241  if (invLine < m_timings.VFrontPorch || invLine >= m_timings.VFrontPorch + m_timings.VSyncPulse)
242  m_DMABuffers[i].buf = (uint8_t*) m_blankLine;
243  else
244  m_DMABuffers[i].buf = (uint8_t*) m_syncLine;
245 
246  ++invLine;
247 
248  }
249  }
250 
251  fillDMABuffers();
252 
253  s_scanLine = 0;
254 
255  s_blankPatternDWord = m_HVSync | (m_HVSync << 8) | (m_HVSync << 16) | (m_HVSync << 24);
256 
257  if (s_fgbgPattern == nullptr) {
258  s_fgbgPattern = (uint32_t*) heap_caps_malloc(16384, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
259  for (int i = 0; i < 16; ++i)
260  for (int fg = 0; fg < 16; ++fg)
261  for (int bg = 0; bg < 16; ++bg) {
262  uint8_t fg_pat = preparePixel(RGB222((Color)fg));
263  uint8_t bg_pat = preparePixel(RGB222((Color)bg));
264  s_fgbgPattern[i | (bg << 4) | (fg << 8)] = (i & 0b1000 ? (fg_pat << 16) : (bg_pat << 16)) |
265  (i & 0b0100 ? (fg_pat << 24) : (bg_pat << 24)) |
266  (i & 0b0010 ? (fg_pat << 0) : (bg_pat << 0)) |
267  (i & 0b0001 ? (fg_pat << 8) : (bg_pat << 8));
268  }
269  }
270 
271 
272  // ESP_INTR_FLAG_LEVEL1: should be less than PS2Controller interrupt level, necessary when running on the same core
273  CoreUsage::setBusiestCore(FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE);
274  esp_intr_alloc_pinnedToCore(ETS_I2S1_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, ISRHandler, this, &m_isr_handle, FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE);
275 
276  m_GPIOStream.play(m_timings.frequency, m_DMABuffers);
277 
278  I2S1.int_clr.val = 0xFFFFFFFF;
279  I2S1.int_ena.out_eof = 1;
280 }
281 
282 
283 void VGATextController::freeBuffers()
284 {
285  heap_caps_free( (void*) m_DMABuffers );
286  heap_caps_free((void*) m_lines);
287  m_DMABuffers = nullptr;
288  heap_caps_free((void*) m_blankLine);
289  heap_caps_free((void*) m_syncLine);
290 }
291 
292 
293 uint8_t IRAM_ATTR VGATextController::packHVSync(bool HSync, bool VSync)
294 {
295  uint8_t hsync_value = (m_timings.HSyncLogic == '+' ? (HSync ? 1 : 0) : (HSync ? 0 : 1));
296  uint8_t vsync_value = (m_timings.VSyncLogic == '+' ? (VSync ? 1 : 0) : (VSync ? 0 : 1));
297  return (vsync_value << VGA_VSYNC_BIT) | (hsync_value << VGA_HSYNC_BIT);
298 }
299 
300 
301 uint8_t IRAM_ATTR VGATextController::preparePixelWithSync(RGB222 rgb, bool HSync, bool VSync)
302 {
303  return packHVSync(HSync, VSync) | (rgb.B << VGA_BLUE_BIT) | (rgb.G << VGA_GREEN_BIT) | (rgb.R << VGA_RED_BIT);
304 }
305 
306 
307 void VGATextController::fillDMABuffers()
308 {
309  int x = 0;
310  for (; x < m_timings.HFrontPorch; ++x) {
311  VGA_PIXELINROW(m_blankLine, x) = preparePixelWithSync((RGB222){0, 0, 0}, false, false);
312  VGA_PIXELINROW(m_syncLine, x) = preparePixelWithSync((RGB222){0, 0, 0}, false, true);
313  }
314  for (; x < m_timings.HFrontPorch + m_timings.HSyncPulse; ++x) {
315  VGA_PIXELINROW(m_blankLine, x) = preparePixelWithSync((RGB222){0, 0, 0}, true, false);
316  VGA_PIXELINROW(m_syncLine, x) = preparePixelWithSync((RGB222){0, 0, 0}, true, true);
317  }
318  for (; x < m_timings.HFrontPorch + m_timings.HSyncPulse + m_timings.HBackPorch; ++x) {
319  VGA_PIXELINROW(m_blankLine, x) = preparePixelWithSync((RGB222){0, 0, 0}, false, false);
320  VGA_PIXELINROW(m_syncLine, x) = preparePixelWithSync((RGB222){0, 0, 0}, false, true);
321  }
322  int rawLineWidth = m_timings.HFrontPorch + m_timings.HSyncPulse + m_timings.HBackPorch + m_timings.HVisibleArea;
323  for (int rx = 0; x < rawLineWidth; ++x, ++rx) {
324  VGA_PIXELINROW(m_blankLine, x) = preparePixelWithSync((RGB222){0, 0, 0}, false, false);
325  VGA_PIXELINROW(m_syncLine, x) = preparePixelWithSync((RGB222){0, 0, 0}, false, true);
326  for (int i = 0; i < VGATextController_CHARHEIGHT; ++i)
327  VGA_PIXELINROW( ((uint8_t*)(m_lines) + i * VGATextController_WIDTH), rx) = preparePixelWithSync((RGB222){0, 0, 0}, false, false);
328  }
329 }
330 
331 
332 void IRAM_ATTR VGATextController::ISRHandler(void * arg)
333 {
334  #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
335  auto s1 = getCycleCount();
336  #endif
337 
338  VGATextController * ctrl = (VGATextController *) arg;
339 
340  if (I2S1.int_st.out_eof && ctrl->m_charData != nullptr) {
341 
342  auto desc = (volatile lldesc_t*) I2S1.out_eof_des_addr;
343 
344  if (desc == s_frameResetDesc) {
345 
346  s_scanLine = 0;
347  s_textRow = 0;
348  s_upperRow = true;
349 
350  if (ctrl->m_cursorEnabled) {
351  ++ctrl->m_cursorCounter;
352  if (ctrl->m_cursorCounter >= ctrl->m_cursorSpeed)
353  ctrl->m_cursorCounter = -ctrl->m_cursorSpeed;
354  }
355 
356  if (ctrl->m_map == nullptr) {
357  I2S1.int_clr.val = I2S1.int_st.val;
358  #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
359  s_vgatxtcycles += getCycleCount() - s1;
360  #endif
361  return;
362  }
363 
364  } else if (s_scanLine == 0) {
365  // out of sync, wait for next frame
366  I2S1.int_clr.val = I2S1.int_st.val;
367  #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
368  s_vgatxtcycles += getCycleCount() - s1;
369  #endif
370  return;
371  }
372 
373  int scanLine = s_scanLine;
374 
375  const int lineIndex = scanLine % VGATextController_CHARHEIGHT;
376 
377  auto lines = ctrl->m_lines;
378 
379  if (s_textRow < ctrl->m_rows) {
380 
381  int cursorCol = 0;
382  int cursorFGBG = 0;
383  const auto cursorVisible = (ctrl->m_cursorEnabled && ctrl->m_cursorCounter >= 0 && s_textRow == ctrl->m_cursorRow);
384  if (cursorVisible) {
385  cursorCol = ctrl->m_cursorCol;
386  cursorFGBG = (ctrl->m_cursorForeground << 4) | (ctrl->m_cursorBackground << 8);
387  }
388 
389  const auto charData = ctrl->m_charData + (s_upperRow ? 0 : VGATextController_CHARHEIGHT / 2);
390  auto mapItemPtr = ctrl->m_map + s_textRow * VGATextController_COLUMNS;
391 
392  for (int col = 0; col < VGATextController_COLUMNS; ++col, ++mapItemPtr) {
393 
394  const auto mapItem = *mapItemPtr;
395 
396  int fgbg = (mapItem >> 4) & 0b111111110000;
397 
398  const auto options = glyphMapItem_getOptions(mapItem);
399 
400  // invert?
401  if (options.invert)
402  fgbg = ((fgbg >> 4) & 0b11110000) | ((fgbg << 4) & 0b111100000000);
403 
404  // cursor?
405  if (cursorVisible && col == cursorCol)
406  fgbg = cursorFGBG;
407 
408  uint32_t * dest = lines + lineIndex * VGATextController_WIDTH / sizeof(uint32_t) + col * VGATextController_CHARWIDTHBYTES * 2;
409 
410  // blank?
411  if (options.blank) {
412 
413  for (int rowInChar = 0; rowInChar < VGATextController_CHARHEIGHT / 2; ++rowInChar) {
414  int v = s_fgbgPattern[fgbg];
415  *dest = v;
416  *(dest + 1) = v;
417  dest += VGATextController_WIDTH / sizeof(uint32_t);
418  }
419 
420  } else {
421 
422  const bool underline = (s_upperRow == false && options.underline);
423  const bool bold = options.bold;
424 
425  auto charRowPtr = charData + glyphMapItem_getIndex(mapItem) * VGATextController_CHARHEIGHT * VGATextController_CHARWIDTHBYTES;
426 
427  for (int rowInChar = 0; rowInChar < VGATextController_CHARHEIGHT / 2; ++rowInChar) {
428  auto charRowData = *charRowPtr;
429 
430  // bold?
431  if (bold)
432  charRowData |= charRowData >> 1;
433 
434  *dest = s_fgbgPattern[(charRowData >> 4) | fgbg];
435  *(dest + 1) = s_fgbgPattern[(charRowData & 0xF) | fgbg];
436 
437  dest += VGATextController_WIDTH / sizeof(uint32_t);
438  charRowPtr += VGATextController_CHARWIDTHBYTES;
439  }
440 
441  // underline?
442  if (underline) {
443  dest -= VGATextController_WIDTH / sizeof(uint32_t);
444  uint32_t v = s_fgbgPattern[0xF | fgbg];
445  *dest = v;
446  *(dest + 1) = v;
447  }
448 
449  }
450 
451  }
452 
453  if (s_upperRow) {
454  s_upperRow = false;
455  } else {
456  s_upperRow = true;
457  ++s_textRow;
458  }
459 
460  } else {
461  for (int i = 0; i < VGATextController_CHARHEIGHT / 2; ++i) {
462  auto dest = lines + ((scanLine + i) % VGATextController_CHARHEIGHT) * VGATextController_WIDTH / sizeof(uint32_t);
463  for (int i = 0; i < VGATextController_COLUMNS; ++i) {
464  *dest++ = s_blankPatternDWord;
465  *dest++ = s_blankPatternDWord;
466  }
467  }
468  }
469 
470  scanLine += VGATextController_CHARHEIGHT / 2;
471 
472  s_scanLine = scanLine;
473 
474  }
475 
476  #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
477  s_vgatxtcycles += getCycleCount() - s1;
478  #endif
479 
480  I2S1.int_clr.val = I2S1.int_st.val;
481 }
482 
483 
484 
485 
486 
487 }
void begin()
This is the 64 colors (8 GPIOs) initializer using default pinout.
This file contains fabgl::GPIOStream definition.
Color
This enum defines named colors.
void adjustMapSize(int *columns, int *rows)
Adjust columns and rows to the controller limits.
This file contains fabgl::VGATextController definition.
uint16_t underline
#define FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE
Definition: fabglconf.h:138
void setResolution(char const *modeline=nullptr, int viewPortWidth=-1, int viewPortHeight=-1, bool doubleBuffered=false)
Sets fixed resolution.
Specifies the VGA timings. This is a modeline decoded.
This file contains some utility classes and functions.
Definition: canvas.cpp:36
void setTextMap(uint32_t const *map, int rows)
Sets text map to display.
uint16_t bold