33#include "freertos/FreeRTOS.h"
34#include "freertos/task.h"
36#include "soc/i2s_struct.h"
37#include "soc/i2s_reg.h"
38#include "driver/periph_ctrl.h"
46#pragma GCC optimize ("O2")
52#if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
53 volatile uint64_t s_vgapalctrlcycles = 0;
58VGABaseController::VGABaseController()
63void VGABaseController::init()
65 m_DMABuffers =
nullptr;
66 m_DMABuffersCount = 0;
67 m_DMABuffersHead =
nullptr;
68 m_DMABuffersVisible =
nullptr;
69 m_primitiveProcessingSuspended = 1;
70 m_isr_handle =
nullptr;
71 m_doubleBufferOverDMA =
false;
73 m_viewPortMemoryPool[0] =
nullptr;
80void VGABaseController::begin(gpio_num_t redGPIO, gpio_num_t greenGPIO, gpio_num_t blueGPIO, gpio_num_t HSyncGPIO, gpio_num_t VSyncGPIO)
85 setupGPIO(redGPIO, VGA_RED_BIT, GPIO_MODE_OUTPUT);
86 setupGPIO(greenGPIO, VGA_GREEN_BIT, GPIO_MODE_OUTPUT);
87 setupGPIO(blueGPIO, VGA_BLUE_BIT, GPIO_MODE_OUTPUT);
90 setupGPIO(HSyncGPIO, VGA_HSYNC_BIT, GPIO_MODE_OUTPUT);
91 setupGPIO(VSyncGPIO, VGA_VSYNC_BIT, GPIO_MODE_OUTPUT);
93 RGB222::lowBitOnly =
true;
99void VGABaseController::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)
101 begin(red0GPIO, green0GPIO, blue0GPIO, HSyncGPIO, VSyncGPIO);
104 setupGPIO(red1GPIO, VGA_RED_BIT + 1, GPIO_MODE_OUTPUT);
105 setupGPIO(green1GPIO, VGA_GREEN_BIT + 1, GPIO_MODE_OUTPUT);
106 setupGPIO(blue1GPIO, VGA_BLUE_BIT + 1, GPIO_MODE_OUTPUT);
108 RGB222::lowBitOnly =
false;
109 m_bitsPerChannel = 2;
114void VGABaseController::begin()
116 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);
120void VGABaseController::end()
123 suspendBackgroundPrimitiveExecution();
124 vTaskDelay(50 / portTICK_PERIOD_MS);
126 vTaskDelay(10 / portTICK_PERIOD_MS);
128 esp_intr_free(m_isr_handle);
129 m_isr_handle =
nullptr;
136void VGABaseController::setupGPIO(gpio_num_t gpio,
int bit, gpio_mode_t mode)
138 configureGPIO(gpio, mode);
139 gpio_matrix_out(gpio, I2S1O_DATA_OUT0_IDX + bit,
false,
false);
143void VGABaseController::freeBuffers()
145 if (m_DMABuffersCount > 0) {
146 heap_caps_free((
void*)m_HBlankLine_withVSync);
147 heap_caps_free((
void*)m_HBlankLine);
151 setDMABuffersCount(0);
156void VGABaseController::freeViewPort()
158 for (uint8_t * * poolPtr = m_viewPortMemoryPool; *poolPtr; ++poolPtr) {
159 heap_caps_free((
void*) *poolPtr);
163 heap_caps_free(m_viewPort);
164 m_viewPort =
nullptr;
166 if (isDoubleBuffered())
167 heap_caps_free(m_viewPortVisible);
168 m_viewPortVisible =
nullptr;
175bool VGABaseController::setDMABuffersCount(
int buffersCount)
177 if (buffersCount == 0) {
178 if (m_DMABuffersVisible && m_DMABuffersVisible != m_DMABuffers)
179 heap_caps_free( (
void*) m_DMABuffersVisible );
180 heap_caps_free( (
void*) m_DMABuffers );
181 m_DMABuffers =
nullptr;
182 m_DMABuffersVisible =
nullptr;
183 m_DMABuffersCount = 0;
187 if (buffersCount != m_DMABuffersCount) {
190 if (m_doubleBufferOverDMA && m_DMABuffersHead ==
nullptr) {
191 m_DMABuffersHead = (lldesc_t*) heap_caps_malloc(
sizeof(lldesc_t), MALLOC_CAP_DMA);
192 m_DMABuffersHead->eof = m_DMABuffersHead->sosf = m_DMABuffersHead->offset = 0;
193 m_DMABuffersHead->owner = 1;
194 m_DMABuffersHead->size = 0;
195 m_DMABuffersHead->length = 0;
196 m_DMABuffersHead->buf = m_HBlankLine;
197 m_DMABuffersHead->qe.stqe_next =
nullptr;
201 m_DMABuffers = (lldesc_t*) heap_caps_realloc((
void*)m_DMABuffers, buffersCount *
sizeof(lldesc_t), MALLOC_CAP_DMA);
202 if (m_doubleBufferOverDMA && isDoubleBuffered())
203 m_DMABuffersVisible = (lldesc_t*) heap_caps_realloc((
void*)m_DMABuffersVisible, buffersCount *
sizeof(lldesc_t), MALLOC_CAP_DMA);
205 m_DMABuffersVisible = m_DMABuffers;
206 if (!m_DMABuffers || !m_DMABuffersVisible)
209 auto buffersHead = m_DMABuffersHead ? m_DMABuffersHead : &m_DMABuffers[0];
211 for (
int i = 0; i < buffersCount; ++i) {
212 m_DMABuffers[i].eof = 0;
213 m_DMABuffers[i].sosf = 0;
214 m_DMABuffers[i].offset = 0;
215 m_DMABuffers[i].owner = 1;
216 m_DMABuffers[i].qe.stqe_next = (lldesc_t*) (i == buffersCount - 1 ? buffersHead : &m_DMABuffers[i + 1]);
217 if (m_doubleBufferOverDMA && isDoubleBuffered()) {
218 m_DMABuffersVisible[i].eof = 0;
219 m_DMABuffersVisible[i].sosf = 0;
220 m_DMABuffersVisible[i].offset = 0;
221 m_DMABuffersVisible[i].owner = 1;
222 m_DMABuffersVisible[i].qe.stqe_next = (lldesc_t*) (i == buffersCount - 1 ? buffersHead : &m_DMABuffersVisible[i + 1]);
226 m_DMABuffersCount = buffersCount;
235bool VGABaseController::convertModelineToTimings(
char const * modeline, VGATimings * timings)
238 int hdisp, hsyncstart, hsyncend, htotal, vdisp, vsyncstart, vsyncend, vtotal;
239 char HSyncPol = 0, VSyncPol = 0;
242 int count = sscanf(modeline,
"\"%[^\"]\" %g %d %d %d %d %d %d %d %d %n", timings->label, &freq, &hdisp, &hsyncstart, &hsyncend, &htotal, &vdisp, &vsyncstart, &vsyncend, &vtotal, &pos);
244 if (count == 10 && pos > 0) {
246 timings->frequency = freq * 1000000;
247 timings->HVisibleArea = hdisp;
248 timings->HFrontPorch = hsyncstart - hdisp;
249 timings->HSyncPulse = hsyncend - hsyncstart;
250 timings->HBackPorch = htotal - hsyncend;
251 timings->VVisibleArea = vdisp;
252 timings->VFrontPorch = vsyncstart - vdisp;
253 timings->VSyncPulse = vsyncend - vsyncstart;
254 timings->VBackPorch = vtotal - vsyncend;
255 timings->HSyncLogic =
'-';
256 timings->VSyncLogic =
'-';
257 timings->scanCount = 1;
258 timings->multiScanBlack = 0;
262 char const * pc = modeline + pos;
264 if (*pc ==
'+' || *pc ==
'-') {
266 timings->HSyncLogic = HSyncPol = *pc;
267 else if (!VSyncPol) {
268 timings->VSyncLogic = VSyncPol = *pc;
269 while (*pc && *pc !=
' ')
273 }
else if (*pc !=
' ')
283 timings->scanCount = 2;
287 timings->scanCount = 4;
307 timings->multiScanBlack = 1;
314 while (*pc && *pc !=
' ')
329void VGABaseController::suspendBackgroundPrimitiveExecution()
331 ++m_primitiveProcessingSuspended;
337void VGABaseController::resumeBackgroundPrimitiveExecution()
339 m_primitiveProcessingSuspended = tmax(0, m_primitiveProcessingSuspended - 1);
343void VGABaseController::startGPIOStream()
345 m_GPIOStream.play(m_timings.frequency, m_DMABuffers);
349void VGABaseController::setResolution(
char const * modeline,
int viewPortWidth,
int viewPortHeight,
bool doubleBuffered)
352 if (convertModelineToTimings(modeline, &timings))
353 setResolution(timings, viewPortWidth, viewPortHeight, doubleBuffered);
357void VGABaseController::setResolution(VGATimings
const& timings,
int viewPortWidth,
int viewPortHeight,
bool doubleBuffered)
365 setScreenSize(m_timings.HVisibleArea, m_timings.VVisibleArea);
367 setDoubleBuffered(doubleBuffered);
369 m_HVSync = packHVSync(
false,
false);
371 m_HLineSize = m_timings.HFrontPorch + m_timings.HSyncPulse + m_timings.HBackPorch + m_timings.HVisibleArea;
373 m_HBlankLine_withVSync = (uint8_t*) heap_caps_malloc(m_HLineSize, MALLOC_CAP_DMA);
374 m_HBlankLine = (uint8_t*) heap_caps_malloc(m_HLineSize, MALLOC_CAP_DMA);
376 m_viewPortWidth = ~3 & (viewPortWidth <= 0 || viewPortWidth >= m_timings.HVisibleArea ? m_timings.HVisibleArea : viewPortWidth);
377 m_viewPortHeight = viewPortHeight <= 0 || viewPortHeight >= m_timings.VVisibleArea ? m_timings.VVisibleArea : viewPortHeight;
383 m_viewPortCol = (m_timings.HVisibleArea - m_viewPortWidth) / 2;
384 m_viewPortRow = (m_timings.VVisibleArea - m_viewPortHeight) / 2;
387 m_viewPortCol = m_viewPortCol & ~3;
388 m_viewPortRow = m_viewPortRow & ~3;
390 m_rawFrameHeight = m_timings.VVisibleArea + m_timings.VFrontPorch + m_timings.VSyncPulse + m_timings.VBackPorch;
393 setDMABuffersCount(calcRequiredDMABuffersCount(m_viewPortHeight));
402 setDMABuffersCount(calcRequiredDMABuffersCount(m_viewPortHeight));
410 if (m_doubleBufferOverDMA)
411 m_DMABuffersHead->qe.stqe_next = (lldesc_t*) &m_DMABuffersVisible[0];
417void VGABaseController::allocateViewPort(uint32_t allocCaps,
int rowlen)
421 int remainingLines = m_viewPortHeight;
422 m_viewPortHeight = 0;
424 if (isDoubleBuffered())
429 int largestBlock = heap_caps_get_largest_free_block(allocCaps);
430 linesCount[poolsCount] = tmin(remainingLines, largestBlock / rowlen);
431 if (linesCount[poolsCount] == 0)
433 m_viewPortMemoryPool[poolsCount] = (uint8_t*) heap_caps_malloc(linesCount[poolsCount] * rowlen, allocCaps);
434 remainingLines -= linesCount[poolsCount];
435 m_viewPortHeight += linesCount[poolsCount];
438 m_viewPortMemoryPool[poolsCount] =
nullptr;
441 if (isDoubleBuffered()) {
442 m_viewPortHeight /= 2;
443 m_viewPortVisible = (
volatile uint8_t * *) heap_caps_malloc(
sizeof(uint8_t*) * m_viewPortHeight, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
445 m_viewPort = (
volatile uint8_t * *) heap_caps_malloc(
sizeof(uint8_t*) * m_viewPortHeight, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
446 if (!isDoubleBuffered())
447 m_viewPortVisible = m_viewPort;
448 for (
int p = 0, l = 0; p < poolsCount; ++p) {
449 uint8_t * pool = m_viewPortMemoryPool[p];
450 for (
int i = 0; i < linesCount[p]; ++i) {
451 if (l + i < m_viewPortHeight)
452 m_viewPort[l + i] = pool;
454 m_viewPortVisible[l + i - m_viewPortHeight] = pool;
462uint8_t IRAM_ATTR VGABaseController::packHVSync(
bool HSync,
bool VSync)
464 uint8_t hsync_value = (m_timings.HSyncLogic ==
'+' ? (HSync ? 1 : 0) : (HSync ? 0 : 1));
465 uint8_t vsync_value = (m_timings.VSyncLogic ==
'+' ? (VSync ? 1 : 0) : (VSync ? 0 : 1));
466 return (vsync_value << VGA_VSYNC_BIT) | (hsync_value << VGA_HSYNC_BIT);
470uint8_t IRAM_ATTR VGABaseController::preparePixelWithSync(RGB222 rgb,
bool HSync,
bool VSync)
472 return packHVSync(HSync, VSync) | (rgb.B << VGA_BLUE_BIT) | (rgb.G << VGA_GREEN_BIT) | (rgb.R << VGA_RED_BIT);
476int VGABaseController::calcRequiredDMABuffersCount(
int viewPortHeight)
478 int rightPadSize = m_timings.HVisibleArea - m_viewPortWidth - m_viewPortCol;
479 int buffersCount = m_timings.scanCount * (m_rawFrameHeight + viewPortHeight);
481 switch (m_timings.HStartingBlock) {
484 buffersCount += m_timings.scanCount * (rightPadSize > 0 ? viewPortHeight : 0);
488 buffersCount += m_timings.scanCount * viewPortHeight;
492 buffersCount += m_timings.scanCount * viewPortHeight;
496 buffersCount += m_timings.scanCount * (m_viewPortCol > 0 ? viewPortHeight : 0);
506void VGABaseController::fillHorizBuffers(
int offsetX)
510 fill(m_HBlankLine, 0, m_HLineSize, 0, 0, 0,
false,
false);
511 fill(m_HBlankLine_withVSync, 0, m_HLineSize, 0, 0, 0,
false,
true);
515 int16_t porchSum = m_timings.HFrontPorch + m_timings.HBackPorch;
516 m_timings.HFrontPorch = tmax(8, (int16_t)m_timings.HFrontPorch - offsetX);
517 m_timings.HBackPorch = tmax(8, porchSum - m_timings.HFrontPorch);
518 m_timings.HFrontPorch = porchSum - m_timings.HBackPorch;
522 switch (m_timings.HStartingBlock) {
525 syncPos = m_timings.HFrontPorch;
533 syncPos = m_timings.HBackPorch + m_timings.HVisibleArea + m_timings.HFrontPorch;
537 syncPos = m_timings.HVisibleArea + m_timings.HFrontPorch;
541 fill(m_HBlankLine, syncPos, m_timings.HSyncPulse, 0, 0, 0,
true,
false);
542 fill(m_HBlankLine_withVSync, syncPos, m_timings.HSyncPulse, 0, 0, 0,
true,
true);
546void VGABaseController::fillVertBuffers(
int offsetY)
548 int16_t porchSum = m_timings.VFrontPorch + m_timings.VBackPorch;
549 m_timings.VFrontPorch = tmax(1, (int16_t)m_timings.VFrontPorch - offsetY);
550 m_timings.VBackPorch = tmax(1, porchSum - m_timings.VFrontPorch);
551 m_timings.VFrontPorch = porchSum - m_timings.VBackPorch;
561 int VVisibleArea_pos = 0;
562 int VFrontPorch_pos = VVisibleArea_pos + m_timings.VVisibleArea;
563 int VSync_pos = VFrontPorch_pos + m_timings.VFrontPorch;
564 int VBackPorch_pos = VSync_pos + m_timings.VSyncPulse;
566 int rightPadSize = m_timings.HVisibleArea - m_viewPortWidth - m_viewPortCol;
568 for (
int line = 0, DMABufIdx = 0; line < m_rawFrameHeight; ++line) {
570 bool isVVisibleArea = (line < VFrontPorch_pos);
571 bool isVFrontPorch = (line >= VFrontPorch_pos && line < VSync_pos);
572 bool isVSync = (line >= VSync_pos && line < VBackPorch_pos);
573 bool isVBackPorch = (line >= VBackPorch_pos);
575 for (
int scan = 0; scan < m_timings.scanCount; ++scan) {
577 bool isStartOfVertFrontPorch = (line == VFrontPorch_pos && scan == 0);
581 setDMABufferBlank(DMABufIdx++, m_HBlankLine_withVSync, m_HLineSize, scan, isStartOfVertFrontPorch);
583 }
else if (isVFrontPorch || isVBackPorch) {
585 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_HLineSize, scan, isStartOfVertFrontPorch);
587 }
else if (isVVisibleArea) {
589 int visibleAreaLine = line - VVisibleArea_pos;
590 bool isViewport = visibleAreaLine >= m_viewPortRow && visibleAreaLine < m_viewPortRow + m_viewPortHeight;
591 int HInvisibleAreaSize = m_HLineSize - m_timings.HVisibleArea;
595 switch (m_timings.HStartingBlock) {
598 setDMABufferBlank(DMABufIdx++, m_HBlankLine, HInvisibleAreaSize + m_viewPortCol, scan, isStartOfVertFrontPorch);
599 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
600 if (rightPadSize > 0)
601 setDMABufferBlank(DMABufIdx++, m_HBlankLine + HInvisibleAreaSize, rightPadSize, scan, isStartOfVertFrontPorch);
605 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_timings.HSyncPulse + m_timings.HBackPorch + m_viewPortCol, scan, isStartOfVertFrontPorch);
606 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
607 setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_HLineSize - m_timings.HFrontPorch - rightPadSize, m_timings.HFrontPorch + rightPadSize, scan, isStartOfVertFrontPorch);
611 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_timings.HBackPorch + m_viewPortCol, scan, isStartOfVertFrontPorch);
612 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
613 setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_HLineSize - m_timings.HFrontPorch - m_timings.HSyncPulse - rightPadSize, m_timings.HFrontPorch + m_timings.HSyncPulse + rightPadSize, scan, isStartOfVertFrontPorch);
617 if (m_viewPortCol > 0)
618 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_viewPortCol, scan, isStartOfVertFrontPorch);
619 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
620 setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_timings.HVisibleArea - rightPadSize, HInvisibleAreaSize + rightPadSize, scan, isStartOfVertFrontPorch);
626 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_HLineSize, scan, isStartOfVertFrontPorch);
640void VGABaseController::setDMABufferBlank(
int index,
void volatile * address,
int length,
int scan,
bool isStartOfVertFrontPorch)
642 int size = (length + 3) & (~3);
643 m_DMABuffers[index].eof = 0;
644 m_DMABuffers[index].size = size;
645 m_DMABuffers[index].length = length;
646 m_DMABuffers[index].buf = (uint8_t*) address;
647 onSetupDMABuffer(&m_DMABuffers[index], isStartOfVertFrontPorch, scan,
false, 0);
648 if (m_doubleBufferOverDMA && isDoubleBuffered()) {
649 m_DMABuffersVisible[index].eof = 0;
650 m_DMABuffersVisible[index].size = size;
651 m_DMABuffersVisible[index].length = length;
652 m_DMABuffersVisible[index].buf = (uint8_t*) address;
653 onSetupDMABuffer(&m_DMABuffersVisible[index], isStartOfVertFrontPorch, scan,
false, 0);
658bool VGABaseController::isMultiScanBlackLine(
int scan)
660 return (scan > 0 && m_timings.multiScanBlack == 1 && m_timings.HStartingBlock ==
FrontPorch);
667void VGABaseController::setDMABufferView(
int index,
int row,
int scan,
volatile uint8_t * * viewPort,
bool onVisibleDMA)
669 uint8_t * bufferPtr =
nullptr;
670 if (isMultiScanBlackLine(scan))
671 bufferPtr = (uint8_t *) (m_HBlankLine + m_HLineSize - m_timings.HVisibleArea);
673 bufferPtr = (uint8_t *) viewPort[row];
674 lldesc_t
volatile * DMABuffers = onVisibleDMA ? m_DMABuffersVisible : m_DMABuffers;
675 DMABuffers[index].size = (m_viewPortWidth + 3) & (~3);
676 DMABuffers[index].length = m_viewPortWidth;
677 DMABuffers[index].buf = bufferPtr;
684void VGABaseController::setDMABufferView(
int index,
int row,
int scan,
bool isStartOfVertFrontPorch)
686 setDMABufferView(index, row, scan, m_viewPort,
false);
687 if (!isMultiScanBlackLine(scan))
688 onSetupDMABuffer(&m_DMABuffers[index], isStartOfVertFrontPorch, scan,
true, row);
689 if (isDoubleBuffered()) {
690 setDMABufferView(index, row, scan, m_viewPortVisible,
true);
691 if (!isMultiScanBlackLine(scan))
692 onSetupDMABuffer(&m_DMABuffersVisible[index], isStartOfVertFrontPorch, scan,
true, row);
697void volatile * VGABaseController::getDMABuffer(
int index,
int * length)
699 *length = m_DMABuffers[index].length;
700 return m_DMABuffers[index].buf;
709int VGABaseController::fill(uint8_t
volatile * buffer,
int startPos,
int length, uint8_t red, uint8_t green, uint8_t blue,
bool HSync,
bool VSync)
711 uint8_t pattern = preparePixelWithSync((RGB222){red, green, blue}, HSync, VSync);
712 for (
int i = 0; i < length; ++i, ++startPos)
713 VGA_PIXELINROW(buffer, startPos) = pattern;
718void VGABaseController::moveScreen(
int offsetX,
int offsetY)
720 suspendBackgroundPrimitiveExecution();
721 fillVertBuffers(offsetY);
722 fillHorizBuffers(offsetX);
723 resumeBackgroundPrimitiveExecution();
727void VGABaseController::shrinkScreen(
int shrinkX,
int shrinkY)
729 VGATimings * currTimings = getResolutionTimings();
731 currTimings->HBackPorch = tmax(currTimings->HBackPorch + 4 * shrinkX, 4);
732 currTimings->HFrontPorch = tmax(currTimings->HFrontPorch + 4 * shrinkX, 4);
734 currTimings->VBackPorch = tmax(currTimings->VBackPorch + shrinkY, 1);
735 currTimings->VFrontPorch = tmax(currTimings->VFrontPorch + shrinkY, 1);
737 setResolution(*currTimings, m_viewPortWidth, m_viewPortHeight, isDoubleBuffered());
741void IRAM_ATTR VGABaseController::swapBuffers()
743 tswap(m_viewPort, m_viewPortVisible);
744 if (m_doubleBufferOverDMA) {
745 tswap(m_DMABuffers, m_DMABuffersVisible);
746 m_DMABuffersHead->qe.stqe_next = (lldesc_t*) &m_DMABuffersVisible[0];
#define FABGLIB_VIEWPORT_MEMORY_POOL_COUNT
This file contains some utility classes and functions.
This file contains fabgl::GPIOStream definition.
This file contains fabgl::VGABaseController definition.