FabGL
ESP32 Display Controller and Graphics Library
cvbsgenerator.cpp
1/*
2 Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3 Copyright (c) 2019-2022 Fabrizio Di Vittorio.
4 All rights reserved.
5
6
7* Please contact fdivitto2013@gmail.com if you need a commercial license.
8
9
10* This library and related software is available under GPL v3.
11
12 FabGL is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 FabGL is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27#include <string.h>
28
29#include "freertos/FreeRTOS.h"
30#include "freertos/task.h"
31
32#include "driver/dac.h"
33#include "soc/i2s_reg.h"
34#include "driver/periph_ctrl.h"
35#include "soc/rtc.h"
36#include <soc/sens_reg.h>
37
38#include "fabutils.h"
39#include "devdrivers/cvbsgenerator.h"
40
41
42#pragma GCC optimize ("O2")
43
44
45namespace fabgl {
46
47
50// CVBS Standards
51
52
53// interlaced PAL-B (max 640x480)
54static const struct CVBS_I_PAL_B : CVBSParams {
55 CVBS_I_PAL_B() {
56 desc = "I-PAL-B";
57
58 //sampleRate_hz = 17750000.0; // = 1136/64*1000000
59 //sampleRate_hz = 4433618.75*4; // = 1136/64*1000000
60 sampleRate_hz = 17500000.0; // 1120/64*1000000
61
62 subcarrierFreq_hz = 4433618.75;
63 line_us = 64.0;
64 hline_us = 32.0;
65 hsync_us = 4.7;
66 backPorch_us = 5.7;
67 frontPorch_us = 1.65;
68 hblank_us = 1.0;
69 burstCycles = 10.0;
70 burstStart_us = 0.9;
71 fieldLines = 312.5;
72 longPulse_us = 27.3;
73 shortPulse_us = 2.35;
74 hsyncEdge_us = 0.3;
75 vsyncEdge_us = 0.2;
76 blankLines = 19;
77 frameGroupCount = 4;
78 preEqualizingPulseCount = 0;
79 vsyncPulseCount = 5;
80 postEqualizingPulseCount = 5;
81 endFieldEqualizingPulseCount = 5;
82 syncLevel = 0;
83 blackLevel = 25;
84 whiteLevel = 79;
85 burstAmp = 12;
86 defaultVisibleSamples = 640;
87 defaultVisibleLines = 240;
88 fieldStartingLine[0] = 1;
89 fieldStartingLine[1] = 2;
90 fields = 2;
91 interlaceFactor = 2;
92 }
93
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);
99 }
100 double getColorBurst(bool oddLine, double phase) const {
101 // color burst is still composed by V and U signals, but U is permanently inverted (-sin...). This results in +135/-135 degrees swinging burst!
102 return -sin(phase) + (oddLine ? 1. : -1.) * cos(phase);
103 }
104 // to support burst-blanking (Bruch blanking, Bruch sequence)... and to make my Tektronix VM700 happy!
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)))
108 return false; // no color burst
109 return true;
110 }
111
112} CVBS_I_PAL_B;
113
114
115// interlaced PAL-B wide (max 768x480)
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;
120 }
121} CVBS_I_PAL_B_WIDE;
122
123
124// progressive PAL-B (max 640x240)
125static const struct CVBS_P_PAL_B : CVBS_I_PAL_B {
126 CVBS_P_PAL_B() : CVBS_I_PAL_B() {
127 desc = "P-PAL-B";
128 fieldStartingLine[0] = 1;
129 fieldStartingLine[1] = 1;
130 interlaceFactor = 1;
131 }
132} CVBS_P_PAL_B;
133
134
135// progressive PAL-B wide (max 768x240)
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;
140 }
141} CVBS_P_PAL_B_WIDE;
142
143
144// interlaced NTSC-M (max 640x200)
145static const struct CVBS_I_NTSC_M : CVBSParams {
146 CVBS_I_NTSC_M() {
147 desc = "I-NTSC-M";
148 sampleRate_hz = 14223774; // =904/63.555564*1000000
149 subcarrierFreq_hz = 3579545.45;
150 line_us = 63.555564;
151 hline_us = 31.777782;
152 hsync_us = 4.7;
153 backPorch_us = 4.5;
154 frontPorch_us = 1.5;
155 hblank_us = 1.5;
156 burstCycles = 9.0;
157 burstStart_us = 0.6;
158 fieldLines = 262.5;
159 longPulse_us = 27.3;
160 shortPulse_us = 2.3;
161 hsyncEdge_us = 0.3;
162 vsyncEdge_us = 0.2;
163 blankLines = 30;
164 frameGroupCount = 2;
165 preEqualizingPulseCount = 6;
166 vsyncPulseCount = 6;
167 postEqualizingPulseCount = 6;
168 endFieldEqualizingPulseCount = 0;
169 syncLevel = 0;
170 blackLevel = 25;
171 whiteLevel = 70;
172 burstAmp = 15;
173 defaultVisibleSamples = 640;
174 defaultVisibleLines = 200;
175 fieldStartingLine[0] = 1;
176 fieldStartingLine[1] = 2;
177 fields = 2;
178 interlaceFactor = 2;
179 };
180
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.));
186 }
187 double getColorBurst(bool oddLine, double phase) const {
188 // burst is 180˚ on subcarrier
189 return sin(phase + TORAD(180.));
190 }
191 bool lineHasColorBurst(int frame, int frameLine) const {
192 return true;
193 }
194
195} CVBS_I_NTSC_M;
196
197
198// interlaced NTSC-M wide (max 768x200)
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;
203 };
204} CVBS_I_NTSC_M_WIDE;
205
206
207// progressive NTSC-M (max 640x200)
208static const struct CVBS_P_NTSC_M : CVBS_I_NTSC_M {
209 CVBS_P_NTSC_M() : CVBS_I_NTSC_M() {
210 desc = "P-NTSC-M";
211 fieldStartingLine[0] = 1;
212 fieldStartingLine[1] = 1;
213 interlaceFactor = 1;
214 };
215} CVBS_P_NTSC_M;
216
217
218// progressive NTSC-M wide (max 768x200)
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;
223 }
224} CVBS_P_NTSC_M_WIDE;
225
226
227// progressive NTSC-M extended (max 768x240)
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;
232 blankLines = 17;
233 }
234} CVBS_P_NTSC_M_EXT;
235
236
237
238static CVBSParams const * CVBS_Standards[] = {
239 &CVBS_I_PAL_B,
240 &CVBS_P_PAL_B,
241 &CVBS_I_PAL_B_WIDE,
242 &CVBS_P_PAL_B_WIDE,
243 &CVBS_I_NTSC_M,
244 &CVBS_P_NTSC_M,
245 &CVBS_I_NTSC_M_WIDE,
246 &CVBS_P_NTSC_M_WIDE,
247 &CVBS_P_NTSC_M_EXT,
248};
249
250
251
254
255
256
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;
268
269
270#if FABGLIB_CVBSCONTROLLER_PERFORMANCE_CHECK
271 volatile uint64_t s_cvbsctrlcycles = 0;
272#endif
273
274
275CVBSGenerator::CVBSGenerator() :
276 m_DMAStarted(false),
277 m_DMAChain(nullptr),
278 m_lsyncBuf(nullptr),
279 m_ssyncBuf(nullptr),
280 m_lineBuf(nullptr),
281 m_isr_handle(nullptr),
282 m_drawScanlineCallback(nullptr),
283 m_subCarrierPhases(),
284 m_params(nullptr)
285{
286 s_lineSampleToSubCarrierSample = nullptr;
287}
288
289
290// gpio can be:
291// GPIO_NUM_25 : gpio 25 DAC1 connected to DMA, gpio 26 set using setConstDAC()
292// GPIO_NUM_26 : gpio 26 DAC2 connected to DMA, gpio 25 set using setConstDAC()
293void CVBSGenerator::setVideoGPIO(gpio_num_t gpio)
294{
295 m_gpio = gpio;
296}
297
298
299// only I2S0 can control DAC channels
300void CVBSGenerator::runDMA(lldesc_t volatile * dmaBuffers)
301{
302 if (!m_DMAStarted) {
303 periph_module_enable(PERIPH_I2S0_MODULE);
304
305 // Initialize I2S device
306 I2S0.conf.tx_reset = 1;
307 I2S0.conf.tx_reset = 0;
308
309 // Reset DMA
310 I2S0.lc_conf.in_rst = 1;
311 I2S0.lc_conf.in_rst = 0;
312
313 // Reset FIFO
314 I2S0.conf.rx_fifo_reset = 1;
315 I2S0.conf.rx_fifo_reset = 0;
316
317 // false = use APLL, true use PLL_D2 clock
318 bool usePLL = m_params->sampleRate_hz == 16000000 || m_params->sampleRate_hz == 13333333.3334;
319
320 if (usePLL)
321 I2S0.conf_chan.tx_chan_mod = (m_gpio == GPIO_NUM_25 ? 3 : 4);
322 else
323 I2S0.conf_chan.tx_chan_mod = (m_gpio == GPIO_NUM_25 ? 1 : 2);
324
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;
328
329 I2S0.conf.tx_mono = 1; // =0?
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;
336
337 I2S0.conf2.lcd_en = 1;
338 I2S0.conf2.camera_en = 0;
339
340 if (usePLL) {
341 // valid just for 16MHz and 13.333Mhz
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;
347 } else {
348 // valid for all other sample rates
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; // not less than 2
357 I2S0.sample_rate_conf.tx_bck_div_num = 1; // this makes I2S0O_BCK = I2S0_CLK
358 rtc_clk_apll_enable(true, p.sdm0, p.sdm1, p.sdm2, p.o_div);
359 I2S0.clkm_conf.clka_en = 1;
360 }
361
362 I2S0.sample_rate_conf.tx_bits_mod = 16;
363
364 // prepares for first frame and field
365 s_field = m_params->fields - 1;
366 s_frame = m_params->frameGroupCount - 1;
367 s_VSync = false;
368
369 // ESP_INTR_FLAG_LEVEL1: should be less than PS2Controller interrupt level, necessary when running on the same core
370 if (m_isr_handle == nullptr) {
371 CoreUsage::setBusiestCore(FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE);
372 esp_intr_alloc_pinnedToCore(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, ISRHandler, this, &m_isr_handle, FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE);
373 I2S0.int_clr.val = 0xFFFFFFFF;
374 I2S0.int_ena.out_eof = 1;
375 }
376
377 I2S0.out_link.addr = (uint32_t) &dmaBuffers[0];
378 I2S0.out_link.start = 1;
379 I2S0.conf.tx_start = 1;
380
381 dac_i2s_enable();
382 if (usePLL) {
383 // enable both DACs
384 dac_output_enable(DAC_CHANNEL_1); // GPIO25: DAC1, right channel
385 dac_output_enable(DAC_CHANNEL_2); // GPIO26: DAC2, left channel
386 } else {
387 // enable just used DAC
388 dac_output_enable(m_gpio == GPIO_NUM_25 ? DAC_CHANNEL_1 : DAC_CHANNEL_2);
389 }
390
391 m_DMAStarted = true;
392 }
393}
394
395
396volatile lldesc_t * CVBSGenerator::setDMANode(int index, volatile uint16_t * buf, int len)
397{
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];
407}
408
409
410void CVBSGenerator::closeDMAChain(int index)
411{
412 m_DMAChain[index].qe.stqe_next = (lldesc_t *) m_DMAChain;
413}
414
415
416void CVBSGenerator::addExtraSamples(double us, double * aus, int * node)
417{
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);
423 }
424}
425
426
427// align samples, incrementing or decrementing value
428static int bestAlignValue(int value)
429{
430 // 2 samples
431 //return value & ~1;
432
434 // 4 samples (round up or down)
435 int up = (value + 3) & ~3;
436 int down = (value & ~3);
437 return abs(value - up) < abs(value - down) ? up : down;
438 //*/
439}
440
441
442void CVBSGenerator::buildDMAChain()
443{
444 int lineSamplesCount = round(m_params->line_us / m_sample_us);
445 int hlineSamplesCount = round(m_params->hline_us / m_sample_us);
446
447 //printf("lineSamplesCount = %d\n", lineSamplesCount);
448 //printf("hlineSamplesCount = %d\n\n", hlineSamplesCount);
449
450 // make sizes aligned
451 lineSamplesCount = bestAlignValue(lineSamplesCount);
452 hlineSamplesCount = bestAlignValue(hlineSamplesCount);
453
454 m_actualLine_us = lineSamplesCount * m_sample_us;
455 m_actualHLine_us = hlineSamplesCount * m_sample_us;
456
457 //printf("adj lineSamplesCount = %d\n", lineSamplesCount);
458 //printf("adj hlineSamplesCount = %d\n", hlineSamplesCount);
459 //printf("actual line duration = %.6f us\n", m_actualLine_us);
460 //printf("actual half line duration = %.6f us\n", m_actualHLine_us);
461
462 // setup long sync pulse buffer
463 m_lsyncBuf = (volatile uint16_t *) heap_caps_malloc(hlineSamplesCount * sizeof(uint16_t), MALLOC_CAP_DMA);
464 int lsyncStart = 0;
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; // falling edge
470 else if (s <= lsyncEnd - vedgeLen)
471 m_lsyncBuf[s ^ 1] = m_params->syncLevel << 8; // sync
472 else if (s < lsyncEnd)
473 m_lsyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * ++riseCount / vedgeLen) << 8; // rising edge
474 else
475 m_lsyncBuf[s ^ 1] = m_params->blackLevel << 8; // black level
476 }
477
478 // setup short sync pulse buffer and black buffer
479 m_blackBuffer = nullptr;
480 m_ssyncBuf = (volatile uint16_t *) heap_caps_malloc(hlineSamplesCount * sizeof(uint16_t), MALLOC_CAP_DMA);
481 int ssyncStart = 0;
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; // falling edge
486 else if (s <= ssyncEnd - vedgeLen)
487 m_ssyncBuf[s ^ 1] = m_params->syncLevel << 8; // sync
488 else if (s < ssyncEnd)
489 m_ssyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * ++riseCount / vedgeLen) << 8; // rising edge
490 else {
491 m_ssyncBuf[s ^ 1] = m_params->blackLevel << 8; // black level
492 if (!m_blackBuffer && !(s & 3)) {
493 m_blackBuffer = &m_ssyncBuf[s ^ 1];
494 m_blackBufferLength = hlineSamplesCount - s;
495 }
496 }
497 }
498
499 // setup line buffer
500 m_lineBuf = (volatile uint16_t * *) malloc(CVBS_ALLOCATED_LINES * sizeof(uint16_t*));
501 int hsyncStart = 0;
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; // falling edge
509 else if (s <= hsyncEnd - hedgeLen)
510 m_lineBuf[l][s ^ 1] = m_params->syncLevel << 8; // sync
511 else if (s < hsyncEnd)
512 m_lineBuf[l][s ^ 1] = (m_params->syncLevel + m_params->blackLevel * ++riseCount / hedgeLen) << 8; // rising edge
513 else
514 m_lineBuf[l][s ^ 1] = m_params->blackLevel << 8; // back porch, active line, front porch
515 }
516 }
517
518 // line sample (full line, from hsync to front porch) to m_colorBurstLUT[] item
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));
523
524 // setup burst LUT
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;
530 }
531 }
532
533 // calculates nodes count
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;
539
540 m_DMAChain = (volatile lldesc_t *) heap_caps_malloc(DMAChainLength * sizeof(lldesc_t), MALLOC_CAP_DMA);
541
542 //printf("m_DMAChain size: %d bytes (%d items)\n", DMAChainLength * sizeof(lldesc_t), DMAChainLength);
543
544 // associate DMA chain nodes to buffers
545
546 #define SHOWDMADETAILS 0
547
548 int node = 0;
549 volatile lldesc_t * nodeptr = nullptr;
550
551 // microseconds since start of frame sequence
552 double us = m_params->hsyncEdge_us / 2.;
553
554 // microseconds since start of frame sequence: actual value, sample size rounded
555 double aus = us;
556
557 bool lineSwitch = false;
558
559 for (int frame = 1; frame <= m_params->frameGroupCount; ++frame) {
560
561 #if SHOWDMADETAILS
562 printf("frame = %d\n", frame);
563 #endif
564
565 // setup subcarrier phases buffer
566 m_subCarrierPhases[frame - 1] = (volatile scPhases_t *) heap_caps_malloc(m_linesPerFrame * sizeof(scPhases_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
567
568 double frameLine = 1.0;
569
570 for (int field = 1; field <= m_params->fields; ++field) {
571
572 #if SHOWDMADETAILS
573 printf(" field = %d\n", field);
574 #endif
575
576 m_startingScanLine[field - 1] = m_params->fieldStartingLine[field - 1] - 1;
577
578 double fieldLine = 1.0;
579 bool startOfFieldISR = false;
580 bool firstActiveLine = true;
581 int activeLineIndex = 0;
582
583 while (fieldLine < m_params->fieldLines + 1.0) {
584
585 #if SHOWDMADETAILS
586 printf(" frameLine = %f fieldLine = %f ", frameLine, fieldLine);
587 #endif
588
589 double ipart;
590 double subCarrierPhase = modf(m_params->subcarrierFreq_hz * aus / 1000000., &ipart); // 0.0 = 0˚ ... 1.0 = 360˚
591
592 if (m_params->lineHasColorBurst(frame, frameLine)) {
593 // stores subcarrier phase (in samples) for this line
594 m_subCarrierPhases[frame - 1][(int)frameLine - 1] = subCarrierPhase * CVBS_SUBCARRIERPHASES;
595 } else {
596 // no burst for this line
597 m_subCarrierPhases[frame - 1][(int)frameLine - 1] = CVBS_NOBURSTFLAG;
598 }
599 //printf("frame=%d frameLine=%d scPhase=%.1f\n", frame, (int)frameLine, subCarrierPhase * 360.);
600 //printf("frame(0)=%d frameLine(0)=%d scPhase=%.1f scSample=%d\n", frame-1, (int)frameLine-1, subCarrierPhase * 360., m_subCarrierPhases[frame - 1][(int)frameLine - 1]);
601
602 #if SHOWDMADETAILS
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');
605 #endif
606
607 if (fieldLine < m_params->preEqualizingPulseCount * .5 + 1.0) {
608
609 // pre-equalizing short pulse (half line)
610 #if SHOWDMADETAILS
611 printf("node = %d - pre-equalizing pulse\n", node);
612 #endif
613 if (frame == 1) {
614 //addExtraSamples(us, &aus, &node);
615 nodeptr = setDMANode(node++, m_ssyncBuf, hlineSamplesCount);
616 }
617 frameLine += .5;
618 fieldLine += .5;
619 us += m_params->hline_us;
620 aus += m_actualHLine_us;
621
622 } else if (fieldLine < (m_params->preEqualizingPulseCount + m_params->vsyncPulseCount) * .5 + 1.0) {
623
624 // vsync long pulse (half line)
625 #if SHOWDMADETAILS
626 printf("node = %d - vsync pulse", node);
627 #endif
628 if (frame == 1) {
629 //addExtraSamples(us, &aus, &node);
630 nodeptr = setDMANode(node++, m_lsyncBuf, hlineSamplesCount);
631 if (!startOfFieldISR) {
632 // generate interrupt at the first vsync, this will start drawing first lines
633 nodeptr->eof = 1;
634 nodeptr->sosf = 1; // internal flag to signal beginning of field
635 startOfFieldISR = true;
636 #if SHOWDMADETAILS
637 printf(" EOF SOSF");
638 #endif
639 }
640 }
641 #if SHOWDMADETAILS
642 printf("\n");
643 #endif
644 frameLine += .5;
645 fieldLine += .5;
646 us += m_params->hline_us;
647 aus += m_actualHLine_us;
648
649 } else if (fieldLine < (m_params->preEqualizingPulseCount + m_params->vsyncPulseCount + m_params->postEqualizingPulseCount) * .5 + 1.0) {
650
651 // post-equalizing short pulse (half line)
652 #if SHOWDMADETAILS
653 printf("node = %d - post-equalizing pulse\n", node);
654 #endif
655 if (frame == 1) {
656 //addExtraSamples(us, &aus, &node);
657 nodeptr = setDMANode(node++, m_ssyncBuf, hlineSamplesCount);
658 }
659 frameLine += .5;
660 fieldLine += .5;
661 us += m_params->hline_us;
662 aus += m_actualHLine_us;
663
664 } else if (fieldLine < m_params->fieldLines - m_params->endFieldEqualizingPulseCount * .5 + 1.0) {
665
666 // active line
667
668 #if SHOWDMADETAILS
669 printf("node = %d - active line, ", node);
670 #endif
671
672 if (firstActiveLine) {
673 m_firstActiveFrameLine[field - 1] = (int)frameLine - 1;
674 m_firstActiveFieldLineSwitch[frame - 1][field - 1] = lineSwitch;
675 firstActiveLine = false;
676 activeLineIndex = 0;
677 #if SHOWDMADETAILS
678 printf("FIRST ACTIVE, ");
679 #endif
680 } else
681 ++activeLineIndex;
682
683 if ((int)fieldLine == m_firstVisibleFieldLine) {
684 m_firstVisibleFrameLine[field - 1] = (int)frameLine - 1;
685 #if SHOWDMADETAILS
686 printf("FIRST VISIBLE, ");
687 #endif
688 } else if ((int)fieldLine == m_lastVisibleFieldLine) {
689 m_lastVisibleFrameLine[field - 1] = (int)frameLine - 1;
690 #if SHOWDMADETAILS
691 printf("LAST VISIBLE, ");
692 #endif
693 }
694
695 double ipart;
696 if (modf(frameLine, &ipart) == .5) {
697 // ending half of line (half line)
698 #if SHOWDMADETAILS
699 printf("ending half");
700 #endif
701 if (frame == 1) {
702 //addExtraSamples(us, &aus, &node);
703 nodeptr = setDMANode(node++, m_lineBuf[activeLineIndex % CVBS_ALLOCATED_LINES] + hlineSamplesCount, hlineSamplesCount);
704 }
705 frameLine += .5;
706 fieldLine += .5;
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) {
710 // beginning half of line (half line)
711 #if SHOWDMADETAILS
712 printf("beginning half");
713 #endif
714 if (frame == 1) {
715 //addExtraSamples(us, &aus, &node);
716 nodeptr = setDMANode(node++, m_lineBuf[activeLineIndex % CVBS_ALLOCATED_LINES], hlineSamplesCount);
717 }
718 frameLine += .5;
719 fieldLine += .5;
720 us += m_params->hline_us;
721 aus += m_actualHLine_us;
722 } else {
723 // full line
724 #if SHOWDMADETAILS
725 printf("full");
726 #endif
727 if (frame == 1) {
728 int l = activeLineIndex % CVBS_ALLOCATED_LINES;
729 nodeptr = setDMANode(node++, m_lineBuf[l], lineSamplesCount);
730 }
731 frameLine += 1.0;
732 fieldLine += 1.0;
733 us += m_params->line_us;
734 aus += m_actualLine_us;
735 }
736
737 // generate interrupt every half CVBS_ALLOCATED_LINES
738 if (frame == 1 && (activeLineIndex % (CVBS_ALLOCATED_LINES / 2)) == 0) {
739 nodeptr->eof = 1;
740 #if SHOWDMADETAILS
741 printf(" EOF");
742 #endif
743 }
744 #if SHOWDMADETAILS
745 printf("\n");
746 #endif
747
748
749 } else {
750
751 // end-field equalizing short pulse (half line)
752 #if SHOWDMADETAILS
753 printf("node = %d - end-field equalizing pulse\n", node);
754 #endif
755 if (frame == 1) {
756 //addExtraSamples(us, &aus, &node);
757 nodeptr = setDMANode(node++, m_ssyncBuf, hlineSamplesCount);
758 }
759 frameLine += .5;
760 fieldLine += .5;
761 us += m_params->hline_us;
762 aus += m_actualHLine_us;
763
764 }
765
766 if (frameLine == (int)frameLine)
767 lineSwitch = !lineSwitch;
768
769 } // field-line loop
770
771 //if (frame == 1)
772 // addExtraSamples(us, &aus, &node);
773
774 } // field loop
775
776 } // frame loop
777
778 //printf("total us = %f\n", us);
779
780 closeDMAChain(node - 1);
781
782 //printf("DMAChainLength = %d node = %d\n", DMAChainLength, node);
783}
784
785
786void CVBSGenerator::buildDMAChain_subCarrierOnly()
787{
788 double fsamplesPerCycle = 1000000. / m_params->subcarrierFreq_hz / m_sample_us;
789 int samplesPerCycle = 1000000. / m_params->subcarrierFreq_hz / m_sample_us;
790
791 int cycles = 10;
792 while ( (fsamplesPerCycle * cycles) - (int)(fsamplesPerCycle * cycles) > 0.5)
793 cycles += 1;
794
795 samplesPerCycle = fsamplesPerCycle;
796
797 int count = (samplesPerCycle * cycles) & ~1;
798
799 //printf("samplesPerCycle=%d m_sample_us=%f subcarrierFreq_hz=%f cycles=%d count=%d\n", samplesPerCycle, m_sample_us, m_params->subcarrierFreq_hz, cycles, count);
800
801 m_lsyncBuf = (volatile uint16_t *) heap_caps_malloc(count * sizeof(uint16_t), MALLOC_CAP_DMA);
802
803 auto sinLUT = new uint16_t[CVBS_SUBCARRIERPHASES * 2];
804
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;
809 }
810
811 double K = m_params->subcarrierFreq_hz * CVBS_SUBCARRIERPHASES / m_params->sampleRate_hz;
812
813 for (int sample = 0; sample < count; ++sample) {
814 auto idx = (int)(K * sample) % CVBS_SUBCARRIERPHASES;
815 m_lsyncBuf[sample ^ 1] = sinLUT[idx];
816 //printf("%d = %d\n", sample, m_lsyncBuf[sample ^ 1] >> 8);
817 }
818
819 delete[] sinLUT;
820
821 m_DMAChain = (volatile lldesc_t *) heap_caps_malloc(1 * sizeof(lldesc_t), MALLOC_CAP_DMA);
822 setDMANode(0, m_lsyncBuf, count);
823 closeDMAChain(0);
824}
825
826
827CVBSParams const * CVBSGenerator::getParamsFromDesc(char const * desc)
828{
829 for (auto std : CVBS_Standards)
830 if (strcmp(std->desc, desc) == 0)
831 return std;
832 return nullptr;
833}
834
835
836void CVBSGenerator::setup(char const * desc)
837{
838 auto params = getParamsFromDesc(desc);
839 setup(params ? params : CVBS_Standards[0]);
840}
841
842
843void CVBSGenerator::setup(CVBSParams const * params)
844{
845 m_params = params;
846
847 m_sample_us = 1000000. / m_params->sampleRate_hz;
848
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;
852
853 int usableFieldLines = m_params->fieldLines - m_params->blankLines;
854 m_visibleLines = imin(usableFieldLines, m_params->defaultVisibleLines);
855
856 // make sure m_visibleLines is divisible by CVBS_ALLOCATED_LINES
857 m_visibleLines -= m_visibleLines % CVBS_ALLOCATED_LINES;
858
859 m_firstVisibleFieldLine = m_params->blankLines + ceil((usableFieldLines - m_visibleLines) / 2.0);
860 m_lastVisibleFieldLine = m_firstVisibleFieldLine + m_visibleLines - 1;
861
862 //printf("m_visibleLines = %d m_firstVisibleFieldLine = %d m_lastVisibleFieldLine = %d\n", m_visibleLines, m_firstVisibleFieldLine, m_lastVisibleFieldLine);
863
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; // aligned to 2
870
871 double subcarrierCycle_us = 1000000. / m_params->subcarrierFreq_hz; // duration in microseconds of a subcarrier cycle
872
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;
875}
876
877
878void CVBSGenerator::run(bool subCarrierOnly)
879{
880 if (subCarrierOnly)
881 buildDMAChain_subCarrierOnly();
882 else
883 buildDMAChain();
884 runDMA(m_DMAChain);
885}
886
887
888void CVBSGenerator::stop()
889{
890 if (m_DMAStarted) {
891
892 periph_module_disable(PERIPH_I2S0_MODULE);
893 m_DMAStarted = false;
894
895 if (m_isr_handle) {
896 esp_intr_free(m_isr_handle);
897 m_isr_handle = nullptr;
898 }
899
900 // cleanup DMA chain and buffers
901 if (m_DMAChain) {
902
903 heap_caps_free((void*)m_DMAChain);
904 m_DMAChain = nullptr;
905
906 if (m_ssyncBuf) {
907 heap_caps_free((void*)m_ssyncBuf);
908 m_ssyncBuf = nullptr;
909 }
910
911 if (m_lsyncBuf) {
912 heap_caps_free((void*)m_lsyncBuf);
913 m_lsyncBuf = nullptr;
914 }
915
916 if (m_lineBuf) {
917 for (int i = 0; i < CVBS_ALLOCATED_LINES; ++i)
918 heap_caps_free((void*)m_lineBuf[i]);
919 free(m_lineBuf);
920 m_lineBuf = nullptr;
921 }
922
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;
927 }
928
929 if (s_lineSampleToSubCarrierSample) {
930 heap_caps_free((void*)s_lineSampleToSubCarrierSample);
931 s_lineSampleToSubCarrierSample = nullptr;
932 }
933
934 }
935
936 }
937}
938
939
940void CVBSGenerator::setDrawScanlineCallback(CVBSDrawScanlineCallback drawScanlineCallback, void * arg)
941{
942 m_drawScanlineCallback = drawScanlineCallback;
943 m_drawScanlineArg = arg;
944}
945
946
947void IRAM_ATTR CVBSGenerator::ISRHandler(void * arg)
948{
949 #if FABGLIB_CVBSCONTROLLER_PERFORMANCE_CHECK
950 auto s1 = getCycleCount();
951 #endif
952
953 if (I2S0.int_st.out_eof) {
954
955 auto ctrl = (CVBSGenerator *) arg;
956 auto desc = (volatile lldesc_t*) I2S0.out_eof_des_addr;
957
958 // begin of field?
959 if (desc->sosf) {
960 s_field = (s_field + 1) % ctrl->m_params->fields;
961 if (s_field == 0)
962 s_frame = (s_frame + 1) % ctrl->m_params->frameGroupCount; // first field
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];
968 s_VSync = false;
969 }
970
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;
979
980 for (int i = 0; i < CVBS_ALLOCATED_LINES / 2; ++i) {
981
982 auto fullLineBuf = (uint16_t*)lineBuf[s_activeLineIndex % CVBS_ALLOCATED_LINES];
983
984 if (*s_subCarrierPhase == CVBS_NOBURSTFLAG) {
985 // no burst for this line
986 uint16_t blk = ctrl->m_params->blackLevel << 8;
987 for (int s = firstColorBurstSample; s <= lastColorBurstSample; ++s)
988 fullLineBuf[s ^ 1] = blk;
989 } else {
990 // fill color burst
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];
995 }
996
997 // fill active area
998 if (s_frameLine >= firstVisibleFrameLine && s_frameLine <= lastVisibleFrameLine) {
999 // visible lines
1000 drawScanlineCallback(drawScanlineArg, fullLineBuf, s_firstVisibleSample, s_scanLine);
1001 s_scanLine += interlaceFactor; // +2 if interlaced, +1 if progressive
1002 } else {
1003 // blank lines
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;
1008 }
1009
1010 ++s_activeLineIndex;
1011 ++s_frameLine;
1012 ++s_subCarrierPhase;
1013 s_lineSwitch = !s_lineSwitch;
1014
1015 }
1016
1017 if (s_frameLine >= lastVisibleFrameLine)
1018 s_VSync = true;
1019 }
1020
1021 #if FABGLIB_CVBSCONTROLLER_PERFORMANCE_CHECK
1022 s_cvbsctrlcycles += getCycleCount() - s1;
1023 #endif
1024
1025 I2S0.int_clr.val = I2S0.int_st.val;
1026}
1027
1028
1029
1030} // namespace fabgl
int16_t Y
#define FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE
Definition: fabglconf.h:142
This file contains some utility classes and functions.