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