29#include "freertos/FreeRTOS.h"
30#include "freertos/task.h"
32#include "driver/dac.h"
33#include "soc/i2s_reg.h"
34#include "driver/periph_ctrl.h"
36#include <soc/sens_reg.h>
39#include "devdrivers/cvbsgenerator.h"
42#pragma GCC optimize ("O2")
54static const struct CVBS_I_PAL_B : CVBSParams {
60 sampleRate_hz = 17500000.0;
62 subcarrierFreq_hz = 4433618.75;
78 preEqualizingPulseCount = 0;
80 postEqualizingPulseCount = 5;
81 endFieldEqualizingPulseCount = 5;
86 defaultVisibleSamples = 640;
87 defaultVisibleLines = 240;
88 fieldStartingLine[0] = 1;
89 fieldStartingLine[1] = 2;
94 double getComposite(
bool oddLine,
double phase,
double red,
double green,
double blue,
double *
Y)
const {
95 *
Y = red * .299 + green * .587 + blue * .114;
96 double U = 0.493 * (blue - *
Y);
97 double V = 0.877 * (red - *
Y);
98 return U * sin(phase) + (oddLine ? 1. : -1.) * V * cos(phase);
100 double getColorBurst(
bool oddLine,
double phase)
const {
102 return -sin(phase) + (oddLine ? 1. : -1.) * cos(phase);
105 bool lineHasColorBurst(
int frame,
int frameLine)
const {
106 if (((frame == 1 || frame == 3) && (frameLine < 7 || (frameLine > 309 && frameLine < 319) || frameLine > 621)) ||
107 ((frame == 2 || frame == 4) && (frameLine < 6 || (frameLine > 310 && frameLine < 320) || frameLine > 622)))
116static const struct CVBS_I_PAL_B_WIDE : CVBS_I_PAL_B {
117 CVBS_I_PAL_B_WIDE() : CVBS_I_PAL_B() {
118 desc =
"I-PAL-B-WIDE";
119 defaultVisibleSamples = 768;
125static const struct CVBS_P_PAL_B : CVBS_I_PAL_B {
126 CVBS_P_PAL_B() : CVBS_I_PAL_B() {
128 fieldStartingLine[0] = 1;
129 fieldStartingLine[1] = 1;
136static const struct CVBS_P_PAL_B_WIDE : CVBS_P_PAL_B {
137 CVBS_P_PAL_B_WIDE() : CVBS_P_PAL_B() {
138 desc =
"P-PAL-B-WIDE";
139 defaultVisibleSamples = 768;
145static const struct CVBS_I_NTSC_M : CVBSParams {
148 sampleRate_hz = 14223774;
149 subcarrierFreq_hz = 3579545.45;
151 hline_us = 31.777782;
165 preEqualizingPulseCount = 6;
167 postEqualizingPulseCount = 6;
168 endFieldEqualizingPulseCount = 0;
173 defaultVisibleSamples = 640;
174 defaultVisibleLines = 200;
175 fieldStartingLine[0] = 1;
176 fieldStartingLine[1] = 2;
181 double getComposite(
bool oddLine,
double phase,
double red,
double green,
double blue,
double *
Y)
const {
182 *
Y = red * .299 + green * .587 + blue * .114;
183 double Q = .413 * (blue - *
Y) + .478 * (red - *
Y);
184 double I = -.269 * (blue - *
Y) + .736 * (red - *
Y);
185 return Q * sin(phase + TORAD(33.)) + I * cos(phase + TORAD(33.));
187 double getColorBurst(
bool oddLine,
double phase)
const {
189 return sin(phase + TORAD(180.));
191 bool lineHasColorBurst(
int frame,
int frameLine)
const {
199static const struct CVBS_I_NTSC_M_WIDE : CVBS_I_NTSC_M {
200 CVBS_I_NTSC_M_WIDE() : CVBS_I_NTSC_M() {
201 desc =
"I-NTSC-M-WIDE";
202 defaultVisibleSamples = 768;
208static const struct CVBS_P_NTSC_M : CVBS_I_NTSC_M {
209 CVBS_P_NTSC_M() : CVBS_I_NTSC_M() {
211 fieldStartingLine[0] = 1;
212 fieldStartingLine[1] = 1;
219static const struct CVBS_P_NTSC_M_WIDE : CVBS_P_NTSC_M {
220 CVBS_P_NTSC_M_WIDE() : CVBS_P_NTSC_M() {
221 desc =
"P-NTSC-M-WIDE";
222 defaultVisibleSamples = 768;
228static const struct CVBS_P_NTSC_M_EXT : CVBS_P_NTSC_M_WIDE {
229 CVBS_P_NTSC_M_EXT() : CVBS_P_NTSC_M_WIDE() {
230 desc =
"P-NTSC-M-EXT";
231 defaultVisibleLines = 240;
238static CVBSParams
const * CVBS_Standards[] = {
257volatile int CVBSGenerator::s_scanLine;
258volatile bool CVBSGenerator::s_VSync;
259volatile int CVBSGenerator::s_field;
260volatile int CVBSGenerator::s_frame;
261volatile int CVBSGenerator::s_frameLine;
262volatile int CVBSGenerator::s_activeLineIndex;
263volatile scPhases_t * CVBSGenerator::s_subCarrierPhase;
264volatile scPhases_t * CVBSGenerator::s_lineSampleToSubCarrierSample;
265volatile int16_t CVBSGenerator::s_firstVisibleSample;
266volatile int16_t CVBSGenerator::s_visibleSamplesCount;
267volatile bool CVBSGenerator::s_lineSwitch;
270#if FABGLIB_CVBSCONTROLLER_PERFORMANCE_CHECK
271 volatile uint64_t s_cvbsctrlcycles = 0;
275CVBSGenerator::CVBSGenerator() :
281 m_isr_handle(nullptr),
282 m_drawScanlineCallback(nullptr),
283 m_subCarrierPhases(),
286 s_lineSampleToSubCarrierSample =
nullptr;
293void CVBSGenerator::setVideoGPIO(gpio_num_t gpio)
300void CVBSGenerator::runDMA(lldesc_t
volatile * dmaBuffers)
303 periph_module_enable(PERIPH_I2S0_MODULE);
306 I2S0.conf.tx_reset = 1;
307 I2S0.conf.tx_reset = 0;
310 I2S0.lc_conf.in_rst = 1;
311 I2S0.lc_conf.in_rst = 0;
314 I2S0.conf.rx_fifo_reset = 1;
315 I2S0.conf.rx_fifo_reset = 0;
318 bool usePLL = m_params->sampleRate_hz == 16000000 || m_params->sampleRate_hz == 13333333.3334;
321 I2S0.conf_chan.tx_chan_mod = (m_gpio == GPIO_NUM_25 ? 3 : 4);
323 I2S0.conf_chan.tx_chan_mod = (m_gpio == GPIO_NUM_25 ? 1 : 2);
325 I2S0.fifo_conf.tx_fifo_mod_force_en = 1;
326 I2S0.fifo_conf.tx_fifo_mod = 1;
327 I2S0.fifo_conf.dscr_en = 1;
329 I2S0.conf.tx_mono = 1;
330 I2S0.conf.tx_start = 0;
331 I2S0.conf.tx_msb_right = 1;
332 I2S0.conf.tx_right_first = 1;
333 I2S0.conf.tx_slave_mod = 0;
334 I2S0.conf.tx_short_sync = 0;
335 I2S0.conf.tx_msb_shift = 0;
337 I2S0.conf2.lcd_en = 1;
338 I2S0.conf2.camera_en = 0;
342 I2S0.clkm_conf.clka_en = 0;
343 I2S0.clkm_conf.clkm_div_a = m_params->sampleRate_hz == 16000000 ? 2 : 1;
344 I2S0.clkm_conf.clkm_div_b = 1;
345 I2S0.clkm_conf.clkm_div_num = 2;
346 I2S0.sample_rate_conf.tx_bck_div_num = 2;
349 APLLParams p = { 0, 0, 0, 0 };
350 double error, out_freq;
351 uint8_t a = 1, b = 0;
352 APLLCalcParams(m_params->sampleRate_hz * 2., &p, &a, &b, &out_freq, &error);
353 I2S0.clkm_conf.val = 0;
354 I2S0.clkm_conf.clkm_div_b = b;
355 I2S0.clkm_conf.clkm_div_a = a;
356 I2S0.clkm_conf.clkm_div_num = 2;
357 I2S0.sample_rate_conf.tx_bck_div_num = 1;
358 rtc_clk_apll_enable(
true, p.sdm0, p.sdm1, p.sdm2, p.o_div);
359 I2S0.clkm_conf.clka_en = 1;
362 I2S0.sample_rate_conf.tx_bits_mod = 16;
365 s_field = m_params->fields - 1;
366 s_frame = m_params->frameGroupCount - 1;
370 if (m_isr_handle ==
nullptr) {
373 I2S0.int_clr.val = 0xFFFFFFFF;
374 I2S0.int_ena.out_eof = 1;
377 I2S0.out_link.addr = (uint32_t) &dmaBuffers[0];
378 I2S0.out_link.start = 1;
379 I2S0.conf.tx_start = 1;
384 dac_output_enable(DAC_CHANNEL_1);
385 dac_output_enable(DAC_CHANNEL_2);
388 dac_output_enable(m_gpio == GPIO_NUM_25 ? DAC_CHANNEL_1 : DAC_CHANNEL_2);
396volatile lldesc_t * CVBSGenerator::setDMANode(
int index,
volatile uint16_t * buf,
int len)
398 m_DMAChain[index].eof = 0;
399 m_DMAChain[index].sosf = 0;
400 m_DMAChain[index].owner = 1;
401 m_DMAChain[index].qe.stqe_next = (lldesc_t *) (m_DMAChain + index + 1);
402 m_DMAChain[index].offset = 0;
403 m_DMAChain[index].size = len *
sizeof(uint16_t);
404 m_DMAChain[index].length = len *
sizeof(uint16_t);
405 m_DMAChain[index].buf = (uint8_t*) buf;
406 return &m_DMAChain[index];
410void CVBSGenerator::closeDMAChain(
int index)
412 m_DMAChain[index].qe.stqe_next = (lldesc_t *) m_DMAChain;
416void CVBSGenerator::addExtraSamples(
double us,
double * aus,
int * node)
418 int extra_samples = imin((us - *aus) / m_sample_us, m_blackBufferLength) & ~1;
419 if (extra_samples > 0) {
420 setDMANode((*node)++, m_blackBuffer, extra_samples);
421 *aus += extra_samples * m_sample_us;
422 printf(
"added %d extra samples\n", extra_samples);
428static int bestAlignValue(
int value)
435 int up = (value + 3) & ~3;
436 int down = (value & ~3);
437 return abs(value - up) < abs(value - down) ? up : down;
442void CVBSGenerator::buildDMAChain()
444 int lineSamplesCount = round(m_params->line_us / m_sample_us);
445 int hlineSamplesCount = round(m_params->hline_us / m_sample_us);
451 lineSamplesCount = bestAlignValue(lineSamplesCount);
452 hlineSamplesCount = bestAlignValue(hlineSamplesCount);
454 m_actualLine_us = lineSamplesCount * m_sample_us;
455 m_actualHLine_us = hlineSamplesCount * m_sample_us;
463 m_lsyncBuf = (
volatile uint16_t *) heap_caps_malloc(hlineSamplesCount *
sizeof(uint16_t), MALLOC_CAP_DMA);
465 int lsyncEnd = m_params->longPulse_us / m_sample_us;
466 int vedgeLen = ceil(m_params->vsyncEdge_us / m_sample_us);
467 for (
int s = 0, fallCount = vedgeLen, riseCount = 0; s < hlineSamplesCount; ++s) {
468 if (s < lsyncStart + vedgeLen)
469 m_lsyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * --fallCount / vedgeLen) << 8;
470 else if (s <= lsyncEnd - vedgeLen)
471 m_lsyncBuf[s ^ 1] = m_params->syncLevel << 8;
472 else if (s < lsyncEnd)
473 m_lsyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * ++riseCount / vedgeLen) << 8;
475 m_lsyncBuf[s ^ 1] = m_params->blackLevel << 8;
479 m_blackBuffer =
nullptr;
480 m_ssyncBuf = (
volatile uint16_t *) heap_caps_malloc(hlineSamplesCount *
sizeof(uint16_t), MALLOC_CAP_DMA);
482 int ssyncEnd = m_params->shortPulse_us / m_sample_us;
483 for (
int s = 0, fallCount = vedgeLen, riseCount = 0; s < hlineSamplesCount; ++s) {
484 if (s < ssyncStart + vedgeLen)
485 m_ssyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * --fallCount / vedgeLen) << 8;
486 else if (s <= ssyncEnd - vedgeLen)
487 m_ssyncBuf[s ^ 1] = m_params->syncLevel << 8;
488 else if (s < ssyncEnd)
489 m_ssyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * ++riseCount / vedgeLen) << 8;
491 m_ssyncBuf[s ^ 1] = m_params->blackLevel << 8;
492 if (!m_blackBuffer && !(s & 3)) {
493 m_blackBuffer = &m_ssyncBuf[s ^ 1];
494 m_blackBufferLength = hlineSamplesCount - s;
500 m_lineBuf = (
volatile uint16_t * *) malloc(CVBS_ALLOCATED_LINES *
sizeof(uint16_t*));
502 int hsyncEnd = (m_params->hsync_us + m_params->hsyncEdge_us) / m_sample_us;
503 int hedgeLen = ceil(m_params->hsyncEdge_us / m_sample_us);
504 for (
int l = 0; l < CVBS_ALLOCATED_LINES; ++l) {
505 m_lineBuf[l] = (
volatile uint16_t *) heap_caps_malloc(lineSamplesCount *
sizeof(uint16_t), MALLOC_CAP_DMA);
506 for (
int s = 0, fallCount = hedgeLen, riseCount = 0; s < lineSamplesCount; ++s) {
507 if (s < hsyncStart + hedgeLen)
508 m_lineBuf[l][s ^ 1] = (m_params->syncLevel + m_params->blackLevel * --fallCount / hedgeLen) << 8;
509 else if (s <= hsyncEnd - hedgeLen)
510 m_lineBuf[l][s ^ 1] = m_params->syncLevel << 8;
511 else if (s < hsyncEnd)
512 m_lineBuf[l][s ^ 1] = (m_params->syncLevel + m_params->blackLevel * ++riseCount / hedgeLen) << 8;
514 m_lineBuf[l][s ^ 1] = m_params->blackLevel << 8;
519 s_lineSampleToSubCarrierSample = (
volatile scPhases_t *) heap_caps_malloc(lineSamplesCount *
sizeof(scPhases_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
520 double K = m_params->subcarrierFreq_hz * CVBS_SUBCARRIERPHASES / m_params->sampleRate_hz;
521 for (
int s = 0; s < lineSamplesCount; ++s)
522 s_lineSampleToSubCarrierSample[s] = round(fmod(K * s, CVBS_SUBCARRIERPHASES));
525 for (
int line = 0; line < 2; ++line) {
526 for (
int sample = 0; sample < CVBS_SUBCARRIERPHASES * 2; ++sample) {
527 double phase = 2. * M_PI * sample / CVBS_SUBCARRIERPHASES;
528 double burst = m_params->getColorBurst(line == 0, phase);
529 m_colorBurstLUT[line][sample] = (uint16_t)(m_params->blackLevel + m_params->burstAmp * burst) << 8;
534 int DMAChainLength = m_params->preEqualizingPulseCount +
535 m_params->vsyncPulseCount +
536 m_params->postEqualizingPulseCount +
537 m_params->endFieldEqualizingPulseCount +
538 ceil(m_params->fieldLines) * m_params->fields + 2;
540 m_DMAChain = (
volatile lldesc_t *) heap_caps_malloc(DMAChainLength *
sizeof(lldesc_t), MALLOC_CAP_DMA);
546 #define SHOWDMADETAILS 0
549 volatile lldesc_t * nodeptr =
nullptr;
552 double us = m_params->hsyncEdge_us / 2.;
557 bool lineSwitch =
false;
559 for (
int frame = 1; frame <= m_params->frameGroupCount; ++frame) {
562 printf(
"frame = %d\n", frame);
566 m_subCarrierPhases[frame - 1] = (
volatile scPhases_t *) heap_caps_malloc(m_linesPerFrame *
sizeof(scPhases_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
568 double frameLine = 1.0;
570 for (
int field = 1; field <= m_params->fields; ++field) {
573 printf(
" field = %d\n", field);
576 m_startingScanLine[field - 1] = m_params->fieldStartingLine[field - 1] - 1;
578 double fieldLine = 1.0;
579 bool startOfFieldISR =
false;
580 bool firstActiveLine =
true;
581 int activeLineIndex = 0;
583 while (fieldLine < m_params->fieldLines + 1.0) {
586 printf(
" frameLine = %f fieldLine = %f ", frameLine, fieldLine);
590 double subCarrierPhase = modf(m_params->subcarrierFreq_hz * aus / 1000000., &ipart);
592 if (m_params->lineHasColorBurst(frame, frameLine)) {
594 m_subCarrierPhases[frame - 1][(int)frameLine - 1] = subCarrierPhase * CVBS_SUBCARRIERPHASES;
597 m_subCarrierPhases[frame - 1][(int)frameLine - 1] = CVBS_NOBURSTFLAG;
603 printf(
"SCPhase = %.2f ", subCarrierPhase * 360.);
604 printf(
"BurstPhase = %.2f (%c) ", fmod(subCarrierPhase * 2. * M_PI + (lineSwitch ? 1 : -1) * 3. * M_PI / 4., 2. * M_PI) / M_PI * 180., lineSwitch ?
'O' :
'E');
607 if (fieldLine < m_params->preEqualizingPulseCount * .5 + 1.0) {
611 printf(
"node = %d - pre-equalizing pulse\n", node);
615 nodeptr = setDMANode(node++, m_ssyncBuf, hlineSamplesCount);
619 us += m_params->hline_us;
620 aus += m_actualHLine_us;
622 }
else if (fieldLine < (m_params->preEqualizingPulseCount + m_params->vsyncPulseCount) * .5 + 1.0) {
626 printf(
"node = %d - vsync pulse", node);
630 nodeptr = setDMANode(node++, m_lsyncBuf, hlineSamplesCount);
631 if (!startOfFieldISR) {
635 startOfFieldISR =
true;
646 us += m_params->hline_us;
647 aus += m_actualHLine_us;
649 }
else if (fieldLine < (m_params->preEqualizingPulseCount + m_params->vsyncPulseCount + m_params->postEqualizingPulseCount) * .5 + 1.0) {
653 printf(
"node = %d - post-equalizing pulse\n", node);
657 nodeptr = setDMANode(node++, m_ssyncBuf, hlineSamplesCount);
661 us += m_params->hline_us;
662 aus += m_actualHLine_us;
664 }
else if (fieldLine < m_params->fieldLines - m_params->endFieldEqualizingPulseCount * .5 + 1.0) {
669 printf(
"node = %d - active line, ", node);
672 if (firstActiveLine) {
673 m_firstActiveFrameLine[field - 1] = (int)frameLine - 1;
674 m_firstActiveFieldLineSwitch[frame - 1][field - 1] = lineSwitch;
675 firstActiveLine =
false;
678 printf(
"FIRST ACTIVE, ");
683 if ((
int)fieldLine == m_firstVisibleFieldLine) {
684 m_firstVisibleFrameLine[field - 1] = (int)frameLine - 1;
686 printf(
"FIRST VISIBLE, ");
688 }
else if ((
int)fieldLine == m_lastVisibleFieldLine) {
689 m_lastVisibleFrameLine[field - 1] = (int)frameLine - 1;
691 printf(
"LAST VISIBLE, ");
696 if (modf(frameLine, &ipart) == .5) {
699 printf(
"ending half");
703 nodeptr = setDMANode(node++, m_lineBuf[activeLineIndex % CVBS_ALLOCATED_LINES] + hlineSamplesCount, hlineSamplesCount);
707 us += m_params->hline_us;
708 aus += m_actualHLine_us;
709 }
else if (fieldLine + 1.0 > m_params->fieldLines + 1.0 - m_params->endFieldEqualizingPulseCount * .5) {
712 printf(
"beginning half");
716 nodeptr = setDMANode(node++, m_lineBuf[activeLineIndex % CVBS_ALLOCATED_LINES], hlineSamplesCount);
720 us += m_params->hline_us;
721 aus += m_actualHLine_us;
728 int l = activeLineIndex % CVBS_ALLOCATED_LINES;
729 nodeptr = setDMANode(node++, m_lineBuf[l], lineSamplesCount);
733 us += m_params->line_us;
734 aus += m_actualLine_us;
738 if (frame == 1 && (activeLineIndex % (CVBS_ALLOCATED_LINES / 2)) == 0) {
753 printf(
"node = %d - end-field equalizing pulse\n", node);
757 nodeptr = setDMANode(node++, m_ssyncBuf, hlineSamplesCount);
761 us += m_params->hline_us;
762 aus += m_actualHLine_us;
766 if (frameLine == (
int)frameLine)
767 lineSwitch = !lineSwitch;
780 closeDMAChain(node - 1);
786void CVBSGenerator::buildDMAChain_subCarrierOnly()
788 double fsamplesPerCycle = 1000000. / m_params->subcarrierFreq_hz / m_sample_us;
789 int samplesPerCycle = 1000000. / m_params->subcarrierFreq_hz / m_sample_us;
792 while ( (fsamplesPerCycle * cycles) - (
int)(fsamplesPerCycle * cycles) > 0.5)
795 samplesPerCycle = fsamplesPerCycle;
797 int count = (samplesPerCycle * cycles) & ~1;
801 m_lsyncBuf = (
volatile uint16_t *) heap_caps_malloc(count *
sizeof(uint16_t), MALLOC_CAP_DMA);
803 auto sinLUT =
new uint16_t[CVBS_SUBCARRIERPHASES * 2];
805 for (
int sample = 0; sample < CVBS_SUBCARRIERPHASES * 2; ++sample) {
806 double phase = 2. * M_PI * sample / CVBS_SUBCARRIERPHASES;
807 double value = sin(phase);
808 sinLUT[sample] = (uint16_t)(m_params->blackLevel + m_params->burstAmp * value) << 8;
811 double K = m_params->subcarrierFreq_hz * CVBS_SUBCARRIERPHASES / m_params->sampleRate_hz;
813 for (
int sample = 0; sample < count; ++sample) {
814 auto idx = (int)(K * sample) % CVBS_SUBCARRIERPHASES;
815 m_lsyncBuf[sample ^ 1] = sinLUT[idx];
821 m_DMAChain = (
volatile lldesc_t *) heap_caps_malloc(1 *
sizeof(lldesc_t), MALLOC_CAP_DMA);
822 setDMANode(0, m_lsyncBuf, count);
827CVBSParams
const * CVBSGenerator::getParamsFromDesc(
char const * desc)
829 for (
auto std : CVBS_Standards)
830 if (strcmp(std->desc, desc) == 0)
836void CVBSGenerator::setup(
char const * desc)
838 auto params = getParamsFromDesc(desc);
839 setup(params ? params : CVBS_Standards[0]);
843void CVBSGenerator::setup(CVBSParams
const * params)
847 m_sample_us = 1000000. / m_params->sampleRate_hz;
849 double activeLine_us = m_params->line_us - m_params->hsync_us - m_params->backPorch_us - m_params->frontPorch_us;
850 int maxVisibleSamples = activeLine_us / m_sample_us;
851 m_linesPerFrame = m_params->fieldLines * m_params->fields;
853 int usableFieldLines = m_params->fieldLines - m_params->blankLines;
854 m_visibleLines = imin(usableFieldLines, m_params->defaultVisibleLines);
857 m_visibleLines -= m_visibleLines % CVBS_ALLOCATED_LINES;
859 m_firstVisibleFieldLine = m_params->blankLines + ceil((usableFieldLines - m_visibleLines) / 2.0);
860 m_lastVisibleFieldLine = m_firstVisibleFieldLine + m_visibleLines - 1;
864 int blankSamples = m_params->hblank_us / m_sample_us;
865 int hsyncSamples = m_params->hsync_us / m_sample_us;
866 int backPorchSamples = m_params->backPorch_us / m_sample_us;
867 int usableVisibleSamples = maxVisibleSamples - blankSamples;
868 s_visibleSamplesCount = imin(usableVisibleSamples, m_params->defaultVisibleSamples);
869 s_firstVisibleSample = (hsyncSamples + backPorchSamples + blankSamples + (usableVisibleSamples - s_visibleSamplesCount) / 2) & ~1;
871 double subcarrierCycle_us = 1000000. / m_params->subcarrierFreq_hz;
873 m_firstColorBurstSample = (m_params->hsync_us + m_params->hsyncEdge_us / 2. + m_params->burstStart_us) / m_sample_us;
874 m_lastColorBurstSample = m_firstColorBurstSample + (subcarrierCycle_us * m_params->burstCycles) / m_sample_us - 1;
878void CVBSGenerator::run(
bool subCarrierOnly)
881 buildDMAChain_subCarrierOnly();
888void CVBSGenerator::stop()
892 periph_module_disable(PERIPH_I2S0_MODULE);
893 m_DMAStarted =
false;
896 esp_intr_free(m_isr_handle);
897 m_isr_handle =
nullptr;
903 heap_caps_free((
void*)m_DMAChain);
904 m_DMAChain =
nullptr;
907 heap_caps_free((
void*)m_ssyncBuf);
908 m_ssyncBuf =
nullptr;
912 heap_caps_free((
void*)m_lsyncBuf);
913 m_lsyncBuf =
nullptr;
917 for (
int i = 0; i < CVBS_ALLOCATED_LINES; ++i)
918 heap_caps_free((
void*)m_lineBuf[i]);
923 for (
int frame = 0; frame < m_params->frameGroupCount; ++frame)
924 if (m_subCarrierPhases[frame]) {
925 heap_caps_free((
void*)m_subCarrierPhases[frame]);
926 m_subCarrierPhases[frame] =
nullptr;
929 if (s_lineSampleToSubCarrierSample) {
930 heap_caps_free((
void*)s_lineSampleToSubCarrierSample);
931 s_lineSampleToSubCarrierSample =
nullptr;
940void CVBSGenerator::setDrawScanlineCallback(CVBSDrawScanlineCallback drawScanlineCallback,
void * arg)
942 m_drawScanlineCallback = drawScanlineCallback;
943 m_drawScanlineArg = arg;
947void IRAM_ATTR CVBSGenerator::ISRHandler(
void * arg)
949 #if FABGLIB_CVBSCONTROLLER_PERFORMANCE_CHECK
950 auto s1 = getCycleCount();
953 if (I2S0.int_st.out_eof) {
955 auto ctrl = (CVBSGenerator *) arg;
956 auto desc = (
volatile lldesc_t*) I2S0.out_eof_des_addr;
960 s_field = (s_field + 1) % ctrl->m_params->fields;
962 s_frame = (s_frame + 1) % ctrl->m_params->frameGroupCount;
963 s_frameLine = ctrl->m_firstActiveFrameLine[s_field];
964 s_subCarrierPhase = &(ctrl->m_subCarrierPhases[s_frame][s_frameLine]);
965 s_activeLineIndex = 0;
966 s_scanLine = ctrl->m_startingScanLine[s_field];
967 s_lineSwitch = ctrl->m_firstActiveFieldLineSwitch[s_frame][s_field];
971 auto drawScanlineCallback = ctrl->m_drawScanlineCallback;
972 auto drawScanlineArg = ctrl->m_drawScanlineArg;
973 auto lineBuf = ctrl->m_lineBuf;
974 auto firstVisibleFrameLine = ctrl->m_firstVisibleFrameLine[s_field];
975 auto lastVisibleFrameLine = ctrl->m_lastVisibleFrameLine[s_field];
976 auto firstColorBurstSample = ctrl->m_firstColorBurstSample;
977 auto lastColorBurstSample = ctrl->m_lastColorBurstSample;
978 auto interlaceFactor = ctrl->m_params->interlaceFactor;
980 for (
int i = 0; i < CVBS_ALLOCATED_LINES / 2; ++i) {
982 auto fullLineBuf = (uint16_t*)lineBuf[s_activeLineIndex % CVBS_ALLOCATED_LINES];
984 if (*s_subCarrierPhase == CVBS_NOBURSTFLAG) {
986 uint16_t blk = ctrl->m_params->blackLevel << 8;
987 for (
int s = firstColorBurstSample; s <= lastColorBurstSample; ++s)
988 fullLineBuf[s ^ 1] = blk;
991 auto colorBurstLUT = (uint16_t *) ctrl->m_colorBurstLUT[s_lineSwitch];
992 auto sampleLUT = CVBSGenerator::lineSampleToSubCarrierSample() + firstColorBurstSample;
993 for (
int s = firstColorBurstSample; s <= lastColorBurstSample; ++s)
994 fullLineBuf[s ^ 1] = colorBurstLUT[*sampleLUT++ + *s_subCarrierPhase];
998 if (s_frameLine >= firstVisibleFrameLine && s_frameLine <= lastVisibleFrameLine) {
1000 drawScanlineCallback(drawScanlineArg, fullLineBuf, s_firstVisibleSample, s_scanLine);
1001 s_scanLine += interlaceFactor;
1004 auto visibleBuf = fullLineBuf + s_firstVisibleSample;
1005 uint32_t blackFillX2 = (ctrl->m_params->blackLevel << 8) | (ctrl->m_params->blackLevel << (8 + 16));
1006 for (
int col = 0; col < s_visibleSamplesCount; col += 2, visibleBuf += 2)
1007 *((uint32_t*)(visibleBuf)) = blackFillX2;
1010 ++s_activeLineIndex;
1012 ++s_subCarrierPhase;
1013 s_lineSwitch = !s_lineSwitch;
1017 if (s_frameLine >= lastVisibleFrameLine)
1021 #if FABGLIB_CVBSCONTROLLER_PERFORMANCE_CHECK
1022 s_cvbsctrlcycles += getCycleCount() - s1;
1025 I2S0.int_clr.val = I2S0.int_st.val;
#define FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE
This file contains some utility classes and functions.