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()
67 m_DMABuffers =
nullptr;
68 m_DMABuffersCount = 0;
69 m_DMABuffersHead =
nullptr;
70 m_DMABuffersVisible =
nullptr;
71 m_primitiveProcessingSuspended = 1;
72 m_isr_handle =
nullptr;
73 m_doubleBufferOverDMA =
false;
75 m_viewPortMemoryPool =
nullptr;
82void VGABaseController::begin(gpio_num_t redGPIO, gpio_num_t greenGPIO, gpio_num_t blueGPIO, gpio_num_t HSyncGPIO, gpio_num_t VSyncGPIO)
87 setupGPIO(redGPIO, VGA_RED_BIT, GPIO_MODE_OUTPUT);
88 setupGPIO(greenGPIO, VGA_GREEN_BIT, GPIO_MODE_OUTPUT);
89 setupGPIO(blueGPIO, VGA_BLUE_BIT, GPIO_MODE_OUTPUT);
92 setupGPIO(HSyncGPIO, VGA_HSYNC_BIT, GPIO_MODE_OUTPUT);
93 setupGPIO(VSyncGPIO, VGA_VSYNC_BIT, GPIO_MODE_OUTPUT);
95 RGB222::lowBitOnly =
true;
101void 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)
103 begin(red0GPIO, green0GPIO, blue0GPIO, HSyncGPIO, VSyncGPIO);
106 setupGPIO(red1GPIO, VGA_RED_BIT + 1, GPIO_MODE_OUTPUT);
107 setupGPIO(green1GPIO, VGA_GREEN_BIT + 1, GPIO_MODE_OUTPUT);
108 setupGPIO(blue1GPIO, VGA_BLUE_BIT + 1, GPIO_MODE_OUTPUT);
110 RGB222::lowBitOnly =
false;
111 m_bitsPerChannel = 2;
116void VGABaseController::begin()
118 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);
122void VGABaseController::end()
125 suspendBackgroundPrimitiveExecution();
126 vTaskDelay(50 / portTICK_PERIOD_MS);
128 vTaskDelay(10 / portTICK_PERIOD_MS);
130 esp_intr_free(m_isr_handle);
131 m_isr_handle =
nullptr;
138void VGABaseController::setupGPIO(gpio_num_t gpio,
int bit, gpio_mode_t mode)
140 configureGPIO(gpio, mode);
141 gpio_matrix_out(gpio, I2S1O_DATA_OUT0_IDX + bit,
false,
false);
145void VGABaseController::freeBuffers()
147 if (m_DMABuffersCount > 0) {
148 heap_caps_free((
void*)m_HBlankLine_withVSync);
149 heap_caps_free((
void*)m_HBlankLine);
153 setDMABuffersCount(0);
158void VGABaseController::freeViewPort()
160 if (m_viewPortMemoryPool) {
161 for (
auto poolPtr = m_viewPortMemoryPool; *poolPtr; ++poolPtr)
162 heap_caps_free((
void*) *poolPtr);
163 heap_caps_free(m_viewPortMemoryPool);
164 m_viewPortMemoryPool =
nullptr;
167 heap_caps_free(m_viewPort);
168 m_viewPort =
nullptr;
170 if (isDoubleBuffered())
171 heap_caps_free(m_viewPortVisible);
172 m_viewPortVisible =
nullptr;
179bool VGABaseController::setDMABuffersCount(
int buffersCount)
181 if (buffersCount == 0) {
182 if (m_DMABuffersVisible && m_DMABuffersVisible != m_DMABuffers)
183 heap_caps_free( (
void*) m_DMABuffersVisible );
184 heap_caps_free( (
void*) m_DMABuffers );
185 m_DMABuffers =
nullptr;
186 m_DMABuffersVisible =
nullptr;
187 m_DMABuffersCount = 0;
191 if (buffersCount != m_DMABuffersCount) {
194 if (m_doubleBufferOverDMA && m_DMABuffersHead ==
nullptr) {
195 m_DMABuffersHead = (lldesc_t*) heap_caps_malloc(
sizeof(lldesc_t), MALLOC_CAP_DMA);
196 m_DMABuffersHead->eof = m_DMABuffersHead->sosf = m_DMABuffersHead->offset = 0;
197 m_DMABuffersHead->owner = 1;
198 m_DMABuffersHead->size = 0;
199 m_DMABuffersHead->length = 0;
200 m_DMABuffersHead->buf = m_HBlankLine;
201 m_DMABuffersHead->qe.stqe_next =
nullptr;
205 m_DMABuffers = (lldesc_t*) heap_caps_realloc((
void*)m_DMABuffers, buffersCount *
sizeof(lldesc_t), MALLOC_CAP_DMA);
206 if (m_doubleBufferOverDMA && isDoubleBuffered())
207 m_DMABuffersVisible = (lldesc_t*) heap_caps_realloc((
void*)m_DMABuffersVisible, buffersCount *
sizeof(lldesc_t), MALLOC_CAP_DMA);
209 m_DMABuffersVisible = m_DMABuffers;
210 if (!m_DMABuffers || !m_DMABuffersVisible)
213 auto buffersHead = m_DMABuffersHead ? m_DMABuffersHead : &m_DMABuffers[0];
215 for (
int i = 0; i < buffersCount; ++i) {
216 m_DMABuffers[i].eof = 0;
217 m_DMABuffers[i].sosf = 0;
218 m_DMABuffers[i].offset = 0;
219 m_DMABuffers[i].owner = 1;
220 m_DMABuffers[i].qe.stqe_next = (lldesc_t*) (i == buffersCount - 1 ? buffersHead : &m_DMABuffers[i + 1]);
221 if (m_doubleBufferOverDMA && isDoubleBuffered()) {
222 m_DMABuffersVisible[i].eof = 0;
223 m_DMABuffersVisible[i].sosf = 0;
224 m_DMABuffersVisible[i].offset = 0;
225 m_DMABuffersVisible[i].owner = 1;
226 m_DMABuffersVisible[i].qe.stqe_next = (lldesc_t*) (i == buffersCount - 1 ? buffersHead : &m_DMABuffersVisible[i + 1]);
230 m_DMABuffersCount = buffersCount;
239bool VGABaseController::convertModelineToTimings(
char const * modeline, VGATimings * timings)
242 int hdisp, hsyncstart, hsyncend, htotal, vdisp, vsyncstart, vsyncend, vtotal;
243 char HSyncPol = 0, VSyncPol = 0;
246 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);
248 if (count == 10 && pos > 0) {
250 timings->frequency = freq * 1000000;
251 timings->HVisibleArea = hdisp;
252 timings->HFrontPorch = hsyncstart - hdisp;
253 timings->HSyncPulse = hsyncend - hsyncstart;
254 timings->HBackPorch = htotal - hsyncend;
255 timings->VVisibleArea = vdisp;
256 timings->VFrontPorch = vsyncstart - vdisp;
257 timings->VSyncPulse = vsyncend - vsyncstart;
258 timings->VBackPorch = vtotal - vsyncend;
259 timings->HSyncLogic =
'-';
260 timings->VSyncLogic =
'-';
261 timings->scanCount = 1;
262 timings->multiScanBlack = 0;
266 char const * pc = modeline + pos;
268 if (*pc ==
'+' || *pc ==
'-') {
270 timings->HSyncLogic = HSyncPol = *pc;
271 }
else if (!VSyncPol) {
272 timings->VSyncLogic = VSyncPol = *pc;
284 timings->scanCount = 2;
288 timings->scanCount = 4;
308 timings->multiScanBlack = 1;
315 while (*pc && *pc !=
' ')
330void VGABaseController::suspendBackgroundPrimitiveExecution()
332 ++m_primitiveProcessingSuspended;
338void VGABaseController::resumeBackgroundPrimitiveExecution()
340 m_primitiveProcessingSuspended = tmax(0, m_primitiveProcessingSuspended - 1);
344void VGABaseController::startGPIOStream()
346 m_GPIOStream.play(m_timings.frequency, m_DMABuffers);
350void VGABaseController::setResolution(
char const * modeline,
int viewPortWidth,
int viewPortHeight,
bool doubleBuffered)
353 if (convertModelineToTimings(modeline, &timings))
354 setResolution(timings, viewPortWidth, viewPortHeight, doubleBuffered);
358void VGABaseController::setResolution(VGATimings
const& timings,
int viewPortWidth,
int viewPortHeight,
bool doubleBuffered)
366 setScreenSize(m_timings.HVisibleArea, m_timings.VVisibleArea);
368 setDoubleBuffered(doubleBuffered);
370 m_HVSync = packHVSync(
false,
false);
372 m_HLineSize = m_timings.HFrontPorch + m_timings.HSyncPulse + m_timings.HBackPorch + m_timings.HVisibleArea;
374 m_HBlankLine_withVSync = (uint8_t*) heap_caps_malloc(m_HLineSize, MALLOC_CAP_DMA);
375 m_HBlankLine = (uint8_t*) heap_caps_malloc(m_HLineSize, MALLOC_CAP_DMA);
377 m_viewPortWidth = ~3 & (viewPortWidth <= 0 || viewPortWidth >= m_timings.HVisibleArea ? m_timings.HVisibleArea : viewPortWidth);
378 m_viewPortHeight = viewPortHeight <= 0 || viewPortHeight >= m_timings.VVisibleArea ? m_timings.VVisibleArea : viewPortHeight;
384 m_viewPortCol = (m_timings.HVisibleArea - m_viewPortWidth) / 2;
385 m_viewPortRow = (m_timings.VVisibleArea - m_viewPortHeight) / 2;
388 m_viewPortCol = m_viewPortCol & ~3;
389 m_viewPortRow = m_viewPortRow & ~3;
391 m_rawFrameHeight = m_timings.VVisibleArea + m_timings.VFrontPorch + m_timings.VSyncPulse + m_timings.VBackPorch;
394 setDMABuffersCount(calcRequiredDMABuffersCount(m_viewPortHeight));
403 setDMABuffersCount(calcRequiredDMABuffersCount(m_viewPortHeight));
411 if (m_doubleBufferOverDMA)
412 m_DMABuffersHead->qe.stqe_next = (lldesc_t*) &m_DMABuffersVisible[0];
418void VGABaseController::allocateViewPort(uint32_t allocCaps,
int rowlen)
422 int remainingLines = m_viewPortHeight;
423 m_viewPortHeight = 0;
425 if (isDoubleBuffered())
431 int largestBlock = heap_caps_get_largest_free_block(allocCaps);
435 m_viewPortMemoryPool[poolsCount] = (uint8_t*) heap_caps_malloc(linesCount[poolsCount] * rowlen, allocCaps);
436 if (m_viewPortMemoryPool[poolsCount] ==
nullptr)
438 remainingLines -= linesCount[poolsCount];
439 m_viewPortHeight += linesCount[poolsCount];
442 m_viewPortMemoryPool[poolsCount] =
nullptr;
445 if (isDoubleBuffered()) {
446 m_viewPortHeight /= 2;
447 m_viewPortVisible = (
volatile uint8_t * *) heap_caps_malloc(
sizeof(uint8_t*) * m_viewPortHeight, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
449 m_viewPort = (
volatile uint8_t * *) heap_caps_malloc(
sizeof(uint8_t*) * m_viewPortHeight, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
450 if (!isDoubleBuffered())
451 m_viewPortVisible = m_viewPort;
452 for (
int p = 0, l = 0; p < poolsCount; ++p) {
453 uint8_t * pool = m_viewPortMemoryPool[p];
454 for (
int i = 0; i < linesCount[p]; ++i) {
455 if (l + i < m_viewPortHeight)
456 m_viewPort[l + i] = pool;
458 m_viewPortVisible[l + i - m_viewPortHeight] = pool;
466uint8_t IRAM_ATTR VGABaseController::packHVSync(
bool HSync,
bool VSync)
468 uint8_t hsync_value = (m_timings.HSyncLogic ==
'+' ? (HSync ? 1 : 0) : (HSync ? 0 : 1));
469 uint8_t vsync_value = (m_timings.VSyncLogic ==
'+' ? (VSync ? 1 : 0) : (VSync ? 0 : 1));
470 return (vsync_value << VGA_VSYNC_BIT) | (hsync_value << VGA_HSYNC_BIT);
474uint8_t IRAM_ATTR VGABaseController::preparePixelWithSync(RGB222 rgb,
bool HSync,
bool VSync)
476 return packHVSync(HSync, VSync) | (rgb.B << VGA_BLUE_BIT) | (rgb.G << VGA_GREEN_BIT) | (rgb.R << VGA_RED_BIT);
480int VGABaseController::calcRequiredDMABuffersCount(
int viewPortHeight)
482 int rightPadSize = m_timings.HVisibleArea - m_viewPortWidth - m_viewPortCol;
483 int buffersCount = m_timings.scanCount * (m_rawFrameHeight + viewPortHeight);
485 switch (m_timings.HStartingBlock) {
488 buffersCount += m_timings.scanCount * (rightPadSize > 0 ? viewPortHeight : 0);
492 buffersCount += m_timings.scanCount * viewPortHeight;
496 buffersCount += m_timings.scanCount * viewPortHeight;
500 buffersCount += m_timings.scanCount * (m_viewPortCol > 0 ? viewPortHeight : 0);
510void VGABaseController::fillHorizBuffers(
int offsetX)
514 fill(m_HBlankLine, 0, m_HLineSize, 0, 0, 0,
false,
false);
515 fill(m_HBlankLine_withVSync, 0, m_HLineSize, 0, 0, 0,
false,
true);
519 int16_t porchSum = m_timings.HFrontPorch + m_timings.HBackPorch;
520 m_timings.HFrontPorch = tmax(8, (int16_t)m_timings.HFrontPorch - offsetX);
521 m_timings.HBackPorch = tmax(8, porchSum - m_timings.HFrontPorch);
522 m_timings.HFrontPorch = porchSum - m_timings.HBackPorch;
526 switch (m_timings.HStartingBlock) {
529 syncPos = m_timings.HFrontPorch;
537 syncPos = m_timings.HBackPorch + m_timings.HVisibleArea + m_timings.HFrontPorch;
541 syncPos = m_timings.HVisibleArea + m_timings.HFrontPorch;
545 fill(m_HBlankLine, syncPos, m_timings.HSyncPulse, 0, 0, 0,
true,
false);
546 fill(m_HBlankLine_withVSync, syncPos, m_timings.HSyncPulse, 0, 0, 0,
true,
true);
550void VGABaseController::fillVertBuffers(
int offsetY)
552 int16_t porchSum = m_timings.VFrontPorch + m_timings.VBackPorch;
553 m_timings.VFrontPorch = tmax(1, (int16_t)m_timings.VFrontPorch - offsetY);
554 m_timings.VBackPorch = tmax(1, porchSum - m_timings.VFrontPorch);
555 m_timings.VFrontPorch = porchSum - m_timings.VBackPorch;
565 int VVisibleArea_pos = 0;
566 int VFrontPorch_pos = VVisibleArea_pos + m_timings.VVisibleArea;
567 int VSync_pos = VFrontPorch_pos + m_timings.VFrontPorch;
568 int VBackPorch_pos = VSync_pos + m_timings.VSyncPulse;
570 int rightPadSize = m_timings.HVisibleArea - m_viewPortWidth - m_viewPortCol;
572 for (
int line = 0, DMABufIdx = 0; line < m_rawFrameHeight; ++line) {
574 bool isVVisibleArea = (line < VFrontPorch_pos);
575 bool isVFrontPorch = (line >= VFrontPorch_pos && line < VSync_pos);
576 bool isVSync = (line >= VSync_pos && line < VBackPorch_pos);
577 bool isVBackPorch = (line >= VBackPorch_pos);
579 for (
int scan = 0; scan < m_timings.scanCount; ++scan) {
581 bool isStartOfVertFrontPorch = (line == VFrontPorch_pos && scan == 0);
585 setDMABufferBlank(DMABufIdx++, m_HBlankLine_withVSync, m_HLineSize, scan, isStartOfVertFrontPorch);
587 }
else if (isVFrontPorch || isVBackPorch) {
589 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_HLineSize, scan, isStartOfVertFrontPorch);
591 }
else if (isVVisibleArea) {
593 int visibleAreaLine = line - VVisibleArea_pos;
594 bool isViewport = visibleAreaLine >= m_viewPortRow && visibleAreaLine < m_viewPortRow + m_viewPortHeight;
595 int HInvisibleAreaSize = m_HLineSize - m_timings.HVisibleArea;
599 switch (m_timings.HStartingBlock) {
602 setDMABufferBlank(DMABufIdx++, m_HBlankLine, HInvisibleAreaSize + m_viewPortCol, scan, isStartOfVertFrontPorch);
603 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
604 if (rightPadSize > 0)
605 setDMABufferBlank(DMABufIdx++, m_HBlankLine + HInvisibleAreaSize, rightPadSize, scan, isStartOfVertFrontPorch);
609 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_timings.HSyncPulse + m_timings.HBackPorch + m_viewPortCol, scan, isStartOfVertFrontPorch);
610 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
611 setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_HLineSize - m_timings.HFrontPorch - rightPadSize, m_timings.HFrontPorch + rightPadSize, scan, isStartOfVertFrontPorch);
615 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_timings.HBackPorch + m_viewPortCol, scan, isStartOfVertFrontPorch);
616 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
617 setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_HLineSize - m_timings.HFrontPorch - m_timings.HSyncPulse - rightPadSize, m_timings.HFrontPorch + m_timings.HSyncPulse + rightPadSize, scan, isStartOfVertFrontPorch);
621 if (m_viewPortCol > 0)
622 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_viewPortCol, scan, isStartOfVertFrontPorch);
623 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
624 setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_timings.HVisibleArea - rightPadSize, HInvisibleAreaSize + rightPadSize, scan, isStartOfVertFrontPorch);
630 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_HLineSize, scan, isStartOfVertFrontPorch);
644void VGABaseController::setDMABufferBlank(
int index,
void volatile * address,
int length,
int scan,
bool isStartOfVertFrontPorch)
646 int size = (length + 3) & (~3);
647 m_DMABuffers[index].eof = 0;
648 m_DMABuffers[index].size = size;
649 m_DMABuffers[index].length = length;
650 m_DMABuffers[index].buf = (uint8_t*) address;
651 onSetupDMABuffer(&m_DMABuffers[index], isStartOfVertFrontPorch, scan,
false, 0);
652 if (m_doubleBufferOverDMA && isDoubleBuffered()) {
653 m_DMABuffersVisible[index].eof = 0;
654 m_DMABuffersVisible[index].size = size;
655 m_DMABuffersVisible[index].length = length;
656 m_DMABuffersVisible[index].buf = (uint8_t*) address;
657 onSetupDMABuffer(&m_DMABuffersVisible[index], isStartOfVertFrontPorch, scan,
false, 0);
662bool VGABaseController::isMultiScanBlackLine(
int scan)
664 return (scan > 0 && m_timings.multiScanBlack == 1 && m_timings.HStartingBlock ==
FrontPorch);
671void VGABaseController::setDMABufferView(
int index,
int row,
int scan,
volatile uint8_t * * viewPort,
bool onVisibleDMA)
673 uint8_t * bufferPtr =
nullptr;
674 if (isMultiScanBlackLine(scan))
675 bufferPtr = (uint8_t *) (m_HBlankLine + m_HLineSize - m_timings.HVisibleArea);
677 bufferPtr = (uint8_t *) viewPort[row];
678 lldesc_t
volatile * DMABuffers = onVisibleDMA ? m_DMABuffersVisible : m_DMABuffers;
679 DMABuffers[index].size = (m_viewPortWidth + 3) & (~3);
680 DMABuffers[index].length = m_viewPortWidth;
681 DMABuffers[index].buf = bufferPtr;
688void VGABaseController::setDMABufferView(
int index,
int row,
int scan,
bool isStartOfVertFrontPorch)
690 setDMABufferView(index, row, scan, m_viewPort,
false);
691 if (!isMultiScanBlackLine(scan))
692 onSetupDMABuffer(&m_DMABuffers[index], isStartOfVertFrontPorch, scan,
true, row);
693 if (isDoubleBuffered()) {
694 setDMABufferView(index, row, scan, m_viewPortVisible,
true);
695 if (!isMultiScanBlackLine(scan))
696 onSetupDMABuffer(&m_DMABuffersVisible[index], isStartOfVertFrontPorch, scan,
true, row);
701void volatile * VGABaseController::getDMABuffer(
int index,
int * length)
703 *length = m_DMABuffers[index].length;
704 return m_DMABuffers[index].buf;
713int VGABaseController::fill(uint8_t
volatile * buffer,
int startPos,
int length, uint8_t red, uint8_t green, uint8_t blue,
bool HSync,
bool VSync)
715 uint8_t pattern = preparePixelWithSync((RGB222){red, green, blue}, HSync, VSync);
716 for (
int i = 0; i < length; ++i, ++startPos)
717 VGA_PIXELINROW(buffer, startPos) = pattern;
722void VGABaseController::moveScreen(
int offsetX,
int offsetY)
724 suspendBackgroundPrimitiveExecution();
725 fillVertBuffers(offsetY);
726 fillHorizBuffers(offsetX);
727 resumeBackgroundPrimitiveExecution();
731void VGABaseController::shrinkScreen(
int shrinkX,
int shrinkY)
733 VGATimings * currTimings = getResolutionTimings();
735 currTimings->HBackPorch = tmax(currTimings->HBackPorch + 4 * shrinkX, 4);
736 currTimings->HFrontPorch = tmax(currTimings->HFrontPorch + 4 * shrinkX, 4);
738 currTimings->VBackPorch = tmax(currTimings->VBackPorch + shrinkY, 1);
739 currTimings->VFrontPorch = tmax(currTimings->VFrontPorch + shrinkY, 1);
741 setResolution(*currTimings, m_viewPortWidth, m_viewPortHeight, isDoubleBuffered());
745void IRAM_ATTR VGABaseController::swapBuffers()
747 tswap(m_viewPort, m_viewPortVisible);
748 if (m_doubleBufferOverDMA) {
749 tswap(m_DMABuffers, m_DMABuffersVisible);
750 m_DMABuffersHead->qe.stqe_next = (lldesc_t*) &m_DMABuffersVisible[0];
#define FABGLIB_VIEWPORT_MEMORY_POOL_COUNT
#define FABGLIB_MINFREELARGESTBLOCK
This file contains some utility classes and functions.
This file contains fabgl::GPIOStream definition.
This file contains fabgl::VGABaseController definition.