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