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
54static const struct CVBS_I_PAL_B : CVBSParams {
55 CVBS_I_PAL_B() {
56 desc = "I-PAL-B";
57
58 //sampleRate_hz = 4433618.75*4.;
59 sampleRate_hz = 19250000.0; // = 1136/64*1000000
60
61 subcarrierFreq_hz = 4433618.75;
62 line_us = 64.0;
63 hline_us = 32.0;
64 hsync_us = 4.7;
65 backPorch_us = 5.7;
66 frontPorch_us = 1.65;
67 hblank_us = 1.0;
68 burstCycles = 10.0;
69 burstStart_us = 0.9;
70 fieldLines = 312.5;
71 longPulse_us = 27.3;
72 shortPulse_us = 2.35;
73 hsyncEdge_us = 0.3;
74 vsyncEdge_us = 0.2;
75 blankLines = 19;
76 frameGroupCount = 4;
77 preEqualizingPulseCount = 0;
78 vsyncPulseCount = 5;
79 postEqualizingPulseCount = 5;
80 endFieldEqualizingPulseCount = 5;
81 syncLevel = 0;
82 blackLevel = 25;
83 whiteLevel = 79;
84 burstAmp = 12;
85 defaultVisibleSamples = 640;
86 defaultVisibleLines = 240;
87 fieldStartingLine[0] = 1;
88 fieldStartingLine[1] = 2;
89 fields = 2;
90 interlaceFactor = 2;
91 }
92
93 double getComposite(bool oddLine, double phase, double red, double green, double blue, double * Y) const {
94 *Y = red * .299 + green * .587 + blue * .114;
95 double U = 0.493 * (blue - *Y);
96 double V = 0.877 * (red - *Y);
97 return U * sin(phase) + (oddLine ? 1. : -1.) * V * cos(phase);
98 }
99 double getColorBurst(bool oddLine, double phase) const {
100 // color burst is still composed by V and U signals, but U is permanently inverted (-sin...). This results in +135/-135 degrees swinging burst!
101 return -sin(phase) + (oddLine ? 1. : -1.) * cos(phase);
102 }
103 // to support burst-blanking (Bruch blanking, Bruch sequence)... and to make my Tektronix VM700 happy!
104 bool lineHasColorBurst(int frame, int frameLine) const {
105 if (((frame == 1 || frame == 3) && (frameLine < 7 || (frameLine > 309 && frameLine < 319) || frameLine > 621)) ||
106 ((frame == 2 || frame == 4) && (frameLine < 6 || (frameLine > 310 && frameLine < 320) || frameLine > 622)))
107 return false; // no color burst
108 return true;
109 }
110
111} CVBS_I_PAL_B;
112
113
114// interlaced PAL-B wide
115static const struct CVBS_I_PAL_B_WIDE : CVBS_I_PAL_B {
116 CVBS_I_PAL_B_WIDE() : CVBS_I_PAL_B() {
117 desc = "I-PAL-B-WIDE";
118 defaultVisibleSamples = 768;
119 defaultVisibleLines = 240;
120 }
121} CVBS_I_PAL_B_WIDE;
122
123
124// progressive PAL-B
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// interlaced NTSC-M
136static const struct CVBS_I_NTSC_M : CVBSParams {
137 CVBS_I_NTSC_M() {
138 desc = "I-NTSC-M";
139 sampleRate_hz = 14223774; // =904/63.555564*1000000
140 subcarrierFreq_hz = 3579545.45;
141 line_us = 63.555564;
142 hline_us = 31.777782;
143 hsync_us = 4.7;
144 backPorch_us = 4.5;
145 frontPorch_us = 1.5;
146 hblank_us = 1.5;
147 burstCycles = 9.0;
148 burstStart_us = 0.6;
149 fieldLines = 262.5;
150 longPulse_us = 27.3;
151 shortPulse_us = 2.3;
152 hsyncEdge_us = 0.3;
153 vsyncEdge_us = 0.2;
154 blankLines = 30;
155 frameGroupCount = 2;
156 preEqualizingPulseCount = 6;
157 vsyncPulseCount = 6;
158 postEqualizingPulseCount = 6;
159 endFieldEqualizingPulseCount = 0;
160 syncLevel = 0;
161 blackLevel = 25;
162 whiteLevel = 70;
163 burstAmp = 15;
164 defaultVisibleSamples = 640;
165 defaultVisibleLines = 200;
166 fieldStartingLine[0] = 1;
167 fieldStartingLine[1] = 2;
168 fields = 2;
169 interlaceFactor = 2;
170 };
171
172 double getComposite(bool oddLine, double phase, double red, double green, double blue, double * Y) const {
173 *Y = red * .299 + green * .587 + blue * .114;
174 double Q = .413 * (blue - *Y) + .478 * (red - *Y);
175 double I = -.269 * (blue - *Y) + .736 * (red - *Y);
176 return Q * sin(phase + TORAD(33.)) + I * cos(phase + TORAD(33.));
177 }
178 double getColorBurst(bool oddLine, double phase) const {
179 // burst is 180˚ on subcarrier
180 return sin(phase + TORAD(180.));
181 }
182 bool lineHasColorBurst(int frame, int frameLine) const {
183 return true;
184 }
185
186} CVBS_I_NTSC_M;
187
188
189// interlaced NTSC-M wide
190static const struct CVBS_I_NTSC_M_WIDE : CVBS_I_NTSC_M {
191 CVBS_I_NTSC_M_WIDE() : CVBS_I_NTSC_M() {
192 desc = "I-NTSC-M-WIDE";
193 defaultVisibleSamples = 768;
194 defaultVisibleLines = 200;
195 };
196} CVBS_I_NTSC_M_WIDE;
197
198
199// progressive NTSC-M
200static const struct CVBS_P_NTSC_M : CVBS_I_NTSC_M {
201 CVBS_P_NTSC_M() : CVBS_I_NTSC_M() {
202 desc = "P-NTSC-M";
203 fieldStartingLine[0] = 1;
204 fieldStartingLine[1] = 1;
205 interlaceFactor = 1;
206 };
207} CVBS_P_NTSC_M;
208
209
210static CVBSParams const * CVBS_Standards[] = {
211 &CVBS_I_PAL_B,
212 &CVBS_P_PAL_B,
213 &CVBS_I_PAL_B_WIDE,
214 &CVBS_I_NTSC_M,
215 &CVBS_P_NTSC_M,
216 &CVBS_I_NTSC_M_WIDE,
217};
218
219
220
223
224
225
226volatile int CVBSGenerator::s_scanLine;
227volatile bool CVBSGenerator::s_VSync;
228volatile int CVBSGenerator::s_field;
229volatile int CVBSGenerator::s_frame;
230volatile int CVBSGenerator::s_frameLine;
231volatile int CVBSGenerator::s_activeLineIndex;
232volatile int CVBSGenerator::s_interFrameLine;
233volatile scPhases_t * CVBSGenerator::s_subCarrierPhase;
234volatile scPhases_t * CVBSGenerator::s_lineSampleToSubCarrierSample;
235volatile int16_t CVBSGenerator::s_firstVisibleSample;
236volatile int16_t CVBSGenerator::s_visibleSamplesCount;
237
238
239#if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
240 volatile uint64_t s_compctrlcycles = 0;
241#endif
242
243
244CVBSGenerator::CVBSGenerator() :
245 m_DMAStarted(false),
246 m_DMAChain(nullptr),
247 m_lsyncBuf(nullptr),
248 m_ssyncBuf(nullptr),
249 m_lineBuf(nullptr),
250 m_isr_handle(nullptr),
251 m_drawScanlineCallback(nullptr),
252 m_subCarrierPhases(),
253 m_params(nullptr)
254{
255 s_lineSampleToSubCarrierSample = nullptr;
256}
257
258
259// gpio can be:
260// GPIO_NUM_25 : gpio 25 DAC1 connected to DMA, gpio 26 set using setConstDAC()
261// GPIO_NUM_26 : gpio 26 DAC2 connected to DMA, gpio 25 set using setConstDAC()
262void CVBSGenerator::setVideoGPIO(gpio_num_t gpio)
263{
264 m_gpio = gpio;
265}
266
267
268// only I2S0 can control DAC channels
269void CVBSGenerator::runDMA(lldesc_t volatile * dmaBuffers)
270{
271 if (!m_DMAStarted) {
272 periph_module_enable(PERIPH_I2S0_MODULE);
273
274 // Initialize I2S device
275 I2S0.conf.tx_reset = 1;
276 I2S0.conf.tx_reset = 0;
277
278 // Reset DMA
279 I2S0.lc_conf.in_rst = 1;
280 I2S0.lc_conf.in_rst = 0;
281
282 // Reset FIFO
283 I2S0.conf.rx_fifo_reset = 1;
284 I2S0.conf.rx_fifo_reset = 0;
285
286 // false = use APLL, true use PLL_D2 clock
287 bool usePLL = m_params->sampleRate_hz == 16000000 || m_params->sampleRate_hz == 13333333.3334;
288
289 if (usePLL)
290 I2S0.conf_chan.tx_chan_mod = (m_gpio == GPIO_NUM_25 ? 3 : 4);
291 else
292 I2S0.conf_chan.tx_chan_mod = (m_gpio == GPIO_NUM_25 ? 1 : 2);
293
294 I2S0.fifo_conf.tx_fifo_mod_force_en = 1;
295 I2S0.fifo_conf.tx_fifo_mod = 1;
296 I2S0.fifo_conf.dscr_en = 1;
297
298 I2S0.conf.tx_mono = 1; // =0?
299 I2S0.conf.tx_start = 0;
300 I2S0.conf.tx_msb_right = 1;
301 I2S0.conf.tx_right_first = 1;
302 I2S0.conf.tx_slave_mod = 0;
303 I2S0.conf.tx_short_sync = 0;
304 I2S0.conf.tx_msb_shift = 0;
305
306 I2S0.conf2.lcd_en = 1;
307 I2S0.conf2.camera_en = 0;
308
309 if (usePLL) {
310 // valid just for 16MHz and 13.333Mhz
311 I2S0.clkm_conf.clka_en = 0;
312 I2S0.clkm_conf.clkm_div_a = m_params->sampleRate_hz == 16000000 ? 2 : 1;
313 I2S0.clkm_conf.clkm_div_b = 1;
314 I2S0.clkm_conf.clkm_div_num = 2;
315 I2S0.sample_rate_conf.tx_bck_div_num = 2;
316 } else {
317 // valid for all other sample rates
318 APLLParams p = { 0, 0, 0, 0 };
319 double error, out_freq;
320 uint8_t a = 1, b = 0;
321 APLLCalcParams(m_params->sampleRate_hz * 2., &p, &a, &b, &out_freq, &error);
322 I2S0.clkm_conf.val = 0;
323 I2S0.clkm_conf.clkm_div_b = b;
324 I2S0.clkm_conf.clkm_div_a = a;
325 I2S0.clkm_conf.clkm_div_num = 2; // not less than 2
326 I2S0.sample_rate_conf.tx_bck_div_num = 1; // this makes I2S0O_BCK = I2S0_CLK
327 rtc_clk_apll_enable(true, p.sdm0, p.sdm1, p.sdm2, p.o_div);
328 I2S0.clkm_conf.clka_en = 1;
329 }
330
331 I2S0.sample_rate_conf.tx_bits_mod = 16;
332
333 // prepares for first frame and field
334 s_field = m_params->fields - 1;
335 s_frame = m_params->frameGroupCount - 1;
336 s_VSync = false;
337
338 // ESP_INTR_FLAG_LEVEL1: should be less than PS2Controller interrupt level, necessary when running on the same core
339 if (m_isr_handle == nullptr) {
340 CoreUsage::setBusiestCore(FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE);
341 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);
342 I2S0.int_clr.val = 0xFFFFFFFF;
343 I2S0.int_ena.out_eof = 1;
344 }
345
346 I2S0.out_link.addr = (uint32_t) &dmaBuffers[0];
347 I2S0.out_link.start = 1;
348 I2S0.conf.tx_start = 1;
349
350 dac_i2s_enable();
351 if (usePLL) {
352 // enable both DACs
353 dac_output_enable(DAC_CHANNEL_1); // GPIO25: DAC1, right channel
354 dac_output_enable(DAC_CHANNEL_2); // GPIO26: DAC2, left channel
355 } else {
356 // enable just used DAC
357 dac_output_enable(m_gpio == GPIO_NUM_25 ? DAC_CHANNEL_1 : DAC_CHANNEL_2);
358 }
359
360 m_DMAStarted = true;
361 }
362}
363
364
365volatile lldesc_t * CVBSGenerator::setDMANode(int index, volatile uint16_t * buf, int len)
366{
367 m_DMAChain[index].eof = 0;
368 m_DMAChain[index].sosf = 0;
369 m_DMAChain[index].owner = 1;
370 m_DMAChain[index].qe.stqe_next = (lldesc_t *) (m_DMAChain + index + 1);
371 m_DMAChain[index].offset = 0;
372 m_DMAChain[index].size = len * sizeof(uint16_t);
373 m_DMAChain[index].length = len * sizeof(uint16_t);
374 m_DMAChain[index].buf = (uint8_t*) buf;
375 return &m_DMAChain[index];
376}
377
378
379void CVBSGenerator::closeDMAChain(int index)
380{
381 m_DMAChain[index].qe.stqe_next = (lldesc_t *) m_DMAChain;
382}
383
384
385// align samples, incrementing or decrementing value
386static int bestAlignValue(int value)
387{
388 // 2 samples
389 return value & ~1;
390
391 /*
392 // 4 samples (round up or down)
393 int up = (value + 3) & ~3;
394 int down = (value & ~3);
395 return abs(value - up) < abs(value - down) ? up : down;
396 */
397
398 // 4 samples round down
399 //return value & ~3;
400
401 // 4 samples round up
402 //return (value + 3) & ~3;
403}
404
405
406void CVBSGenerator::buildDMAChain()
407{
408 int lineSamplesCount = round(m_params->line_us / m_sample_us);
409 int hlineSamplesCount = round(m_params->hline_us / m_sample_us);
410
411 printf("lineSamplesCount = %d\n", lineSamplesCount);
412 printf("hlineSamplesCount = %d\n\n", hlineSamplesCount);
413
414 // make sizes aligned
415 lineSamplesCount = bestAlignValue(lineSamplesCount);
416 hlineSamplesCount = bestAlignValue(hlineSamplesCount);
417
418 m_actualLine_us = lineSamplesCount * m_sample_us;
419 m_actualHLine_us = hlineSamplesCount * m_sample_us;
420
421 printf("adj lineSamplesCount = %d\n", lineSamplesCount);
422 printf("adj hlineSamplesCount = %d\n", hlineSamplesCount);
423 printf("actual line duration = %.2f us\n", m_actualLine_us);
424
425 // setup long sync pulse buffer
426 m_lsyncBuf = (volatile uint16_t *) heap_caps_malloc(hlineSamplesCount * sizeof(uint16_t), MALLOC_CAP_DMA);
427 int lsyncStart = 0;
428 int lsyncEnd = m_params->longPulse_us / m_sample_us;
429 int vedgeLen = ceil(m_params->vsyncEdge_us / m_sample_us);
430 for (int s = 0, fallCount = vedgeLen, riseCount = 0; s < hlineSamplesCount; ++s) {
431 if (s < lsyncStart + vedgeLen)
432 m_lsyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * --fallCount / vedgeLen) << 8; // falling edge
433 else if (s <= lsyncEnd - vedgeLen)
434 m_lsyncBuf[s ^ 1] = m_params->syncLevel << 8; // sync
435 else if (s < lsyncEnd)
436 m_lsyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * ++riseCount / vedgeLen) << 8; // rising edge
437 else
438 m_lsyncBuf[s ^ 1] = m_params->blackLevel << 8; // black level
439 }
440
441 // setup short sync pulse buffer and black buffer
442 m_blackBuffer = nullptr;
443 m_ssyncBuf = (volatile uint16_t *) heap_caps_malloc(hlineSamplesCount * sizeof(uint16_t), MALLOC_CAP_DMA);
444 int ssyncStart = 0;
445 int ssyncEnd = m_params->shortPulse_us / m_sample_us;
446 for (int s = 0, fallCount = vedgeLen, riseCount = 0; s < hlineSamplesCount; ++s) {
447 if (s < ssyncStart + vedgeLen)
448 m_ssyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * --fallCount / vedgeLen) << 8; // falling edge
449 else if (s <= ssyncEnd - vedgeLen)
450 m_ssyncBuf[s ^ 1] = m_params->syncLevel << 8; // sync
451 else if (s < ssyncEnd)
452 m_ssyncBuf[s ^ 1] = (m_params->syncLevel + m_params->blackLevel * ++riseCount / vedgeLen) << 8; // rising edge
453 else {
454 m_ssyncBuf[s ^ 1] = m_params->blackLevel << 8; // black level
455 if (!m_blackBuffer && !(s & 3)) {
456 m_blackBuffer = &m_ssyncBuf[s ^ 1];
457 m_blackBufferLength = hlineSamplesCount - s;
458 }
459 }
460 }
461
462 // setup line buffer
463 m_lineBuf = (volatile uint16_t * *) malloc(CVBS_ALLOCATED_LINES * sizeof(uint16_t*));
464 int hsyncStart = 0;
465 int hsyncEnd = (m_params->hsync_us + m_params->hsyncEdge_us) / m_sample_us;
466 int hedgeLen = ceil(m_params->hsyncEdge_us / m_sample_us);
467 for (int l = 0; l < CVBS_ALLOCATED_LINES; ++l) {
468 m_lineBuf[l] = (volatile uint16_t *) heap_caps_malloc(lineSamplesCount * sizeof(uint16_t), MALLOC_CAP_DMA);
469 for (int s = 0, fallCount = hedgeLen, riseCount = 0; s < lineSamplesCount; ++s) {
470 if (s < hsyncStart + hedgeLen)
471 m_lineBuf[l][s ^ 1] = (m_params->syncLevel + m_params->blackLevel * --fallCount / hedgeLen) << 8; // falling edge
472 else if (s <= hsyncEnd - hedgeLen)
473 m_lineBuf[l][s ^ 1] = m_params->syncLevel << 8; // sync
474 else if (s < hsyncEnd)
475 m_lineBuf[l][s ^ 1] = (m_params->syncLevel + m_params->blackLevel * ++riseCount / hedgeLen) << 8; // rising edge
476 else
477 m_lineBuf[l][s ^ 1] = m_params->blackLevel << 8; // back porch, active line, front porch
478 }
479 }
480
481 // line sample (full line, from hsync to front porch) to m_colorBurstLUT[] item
482 s_lineSampleToSubCarrierSample = (volatile scPhases_t *) heap_caps_malloc(lineSamplesCount * sizeof(scPhases_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
483 double K = m_params->subcarrierFreq_hz * CVBS_SUBCARRIERPHASES / m_params->sampleRate_hz;
484 for (int s = 0; s < lineSamplesCount; ++s)
485 s_lineSampleToSubCarrierSample[s] = round(fmod(K * s, CVBS_SUBCARRIERPHASES));
486
487 // setup burst LUT
488 for (int line = 0; line < 2; ++line) {
489 for (int sample = 0; sample < CVBS_SUBCARRIERPHASES * 2; ++sample) {
490 double phase = 2. * M_PI * sample / CVBS_SUBCARRIERPHASES;
491 double burst = m_params->getColorBurst(line == 0, phase);
492 m_colorBurstLUT[line][sample] = (uint16_t)(m_params->blackLevel + m_params->burstAmp * burst) << 8;
493 }
494 }
495
496 // calculates nodes count
497 int DMAChainLength = m_params->preEqualizingPulseCount +
498 m_params->vsyncPulseCount +
499 m_params->postEqualizingPulseCount +
500 m_params->endFieldEqualizingPulseCount +
501 ceil(m_params->fieldLines) * m_params->fields + 2;
502
503 m_DMAChain = (volatile lldesc_t *) heap_caps_malloc(DMAChainLength * sizeof(lldesc_t), MALLOC_CAP_DMA);
504
505 //printf("m_DMAChain size: %d bytes (%d items)\n", DMAChainLength * sizeof(lldesc_t), DMAChainLength);
506
507 // associate DMA chain nodes to buffers
508
509 #define SHOWDMADETAILS 0
510
511 int node = 0;
512 volatile lldesc_t * nodeptr = nullptr;
513
514 // microseconds since start of frame sequence
515 double us = 0; // @TODO: should we offset by half of hsync falling edge??
516
517 // microseconds since start of frame sequence: actual value, sample size rounded
518 double aus = us;
519
520 for (int frame = 1; frame <= m_params->frameGroupCount; ++frame) {
521
522 #if SHOWDMADETAILS
523 printf("frame = %d\n", frame);
524 #endif
525
526 // setup subcarrier phases buffer
527 m_subCarrierPhases[frame - 1] = (volatile scPhases_t *) heap_caps_malloc(m_linesPerFrame * sizeof(scPhases_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
528
529 double frameLine = 1.0;
530
531 for (int field = 1; field <= m_params->fields; ++field) {
532
533 #if SHOWDMADETAILS
534 printf(" field = %d\n", field);
535 #endif
536
537 m_startingScanLine[field - 1] = m_params->fieldStartingLine[field - 1] - 1;
538
539 double fieldLine = 1.0;
540 bool startOfFieldISR = false;
541 bool firstActiveLine = true;
542 int activeLineIndex = 0;
543
544 while (fieldLine < m_params->fieldLines + 1.0) {
545
546 #if SHOWDMADETAILS
547 printf(" frameLine = %f fieldLine = %f ", frameLine, fieldLine);
548 #endif
549
550 double ipart;
551 double subCarrierPhase = modf(m_params->subcarrierFreq_hz * aus / 1000000., &ipart); // 0.0 = 0˚ ... 1.0 = 360˚
552
553 if (m_params->lineHasColorBurst(frame, frameLine)) {
554 // stores subcarrier phase (in samples) for this line
555 m_subCarrierPhases[frame - 1][(int)frameLine - 1] = subCarrierPhase * CVBS_SUBCARRIERPHASES;
556 } else {
557 // no burst for this line
558 m_subCarrierPhases[frame - 1][(int)frameLine - 1] = CVBS_NOBURSTFLAG;
559 }
560 //printf("frame=%d frameLine=%d scPhase=%.1f\n", frame, (int)frameLine, subCarrierPhase * 360.);
561 //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]);
562
563 #if SHOWDMADETAILS
564 int interFrameLine = m_params->fieldLines * m_params->fields * (frame - 1) + frameLine - 1; // range: 0...
565 bool oddLine = ((interFrameLine & 1) == 0);
566 printf("SCPhase = %.2f ", subCarrierPhase * 360.);
567 printf("BurstPhase = %.2f ", fmod(subCarrierPhase * 2. * M_PI + (oddLine ? 1 : -1) * 3. * M_PI / 4., 2. * M_PI) / M_PI * 180.);
568 oddLine = !oddLine;
569 #endif
570
571 if (fieldLine < m_params->preEqualizingPulseCount * .5 + 1.0) {
572
573 // pre-equalizing short pulse (half line)
574 #if SHOWDMADETAILS
575 printf("node = %d - pre-equalizing pulse\n", node);
576 #endif
577 if (frame == 1)
578 nodeptr = setDMANode(node++, m_ssyncBuf, hlineSamplesCount);
579 frameLine += .5;
580 fieldLine += .5;
581 us += m_params->hline_us;
582 aus += m_actualHLine_us;
583
584 } else if (fieldLine < (m_params->preEqualizingPulseCount + m_params->vsyncPulseCount) * .5 + 1.0) {
585
586 // vsync long pulse (half line)
587 #if SHOWDMADETAILS
588 printf("node = %d - vsync pulse", node);
589 #endif
590 if (frame == 1) {
591 nodeptr = setDMANode(node++, m_lsyncBuf, hlineSamplesCount);
592 if (!startOfFieldISR) {
593 // generate interrupt at the first vsync, this will start drawing first lines
594 nodeptr->eof = 1;
595 nodeptr->sosf = 1; // internal flag to signal beginning of field
596 startOfFieldISR = true;
597 #if SHOWDMADETAILS
598 printf(" EOF SOSF");
599 #endif
600 }
601 }
602 #if SHOWDMADETAILS
603 printf("\n");
604 #endif
605 frameLine += .5;
606 fieldLine += .5;
607 us += m_params->hline_us;
608 aus += m_actualHLine_us;
609
610 } else if (fieldLine < (m_params->preEqualizingPulseCount + m_params->vsyncPulseCount + m_params->postEqualizingPulseCount) * .5 + 1.0) {
611
612 // post-equalizing short pulse (half line)
613 #if SHOWDMADETAILS
614 printf("node = %d - post-equalizing pulse\n", node);
615 #endif
616 if (frame == 1)
617 nodeptr = setDMANode(node++, m_ssyncBuf, hlineSamplesCount);
618 frameLine += .5;
619 fieldLine += .5;
620 us += m_params->hline_us;
621 aus += m_actualHLine_us;
622
623 } else if (fieldLine < m_params->fieldLines - m_params->endFieldEqualizingPulseCount * .5 + 1.0) {
624
625 // active line
626
627 #if SHOWDMADETAILS
628 printf("node = %d - active line, ", node);
629 #endif
630
631 if (firstActiveLine) {
632 m_firstActiveFrameLine[field - 1] = (int)frameLine - 1;
633 firstActiveLine = false;
634 activeLineIndex = 0;
635 #if SHOWDMADETAILS
636 printf("FIRST ACTIVE, ");
637 #endif
638 } else
639 ++activeLineIndex;
640
641 if ((int)fieldLine == m_firstVisibleFieldLine) {
642 m_firstVisibleFrameLine[field - 1] = (int)frameLine - 1;
643 #if SHOWDMADETAILS
644 printf("FIRST VISIBLE, ");
645 #endif
646 } else if ((int)fieldLine == m_lastVisibleFieldLine) {
647 m_lastVisibleFrameLine[field - 1] = (int)frameLine - 1;
648 #if SHOWDMADETAILS
649 printf("LAST VISIBLE, ");
650 #endif
651 }
652
653 double ipart;
654 if (modf(frameLine, &ipart) == .5) {
655 // ending half of line (half line)
656 #if SHOWDMADETAILS
657 printf("ending half");
658 #endif
659 if (frame == 1)
660 nodeptr = setDMANode(node++, m_lineBuf[activeLineIndex % CVBS_ALLOCATED_LINES] + hlineSamplesCount, hlineSamplesCount);
661 frameLine += .5;
662 fieldLine += .5;
663 us += m_params->hline_us;
664 aus += m_actualHLine_us;
665 } else if (fieldLine + 1.0 > m_params->fieldLines + 1.0 - m_params->endFieldEqualizingPulseCount * .5) {
666 // beginning half of line (half line)
667 #if SHOWDMADETAILS
668 printf("beginning half");
669 #endif
670 if (frame == 1)
671 nodeptr = setDMANode(node++, m_lineBuf[activeLineIndex % CVBS_ALLOCATED_LINES], hlineSamplesCount);
672 frameLine += .5;
673 fieldLine += .5;
674 us += m_params->hline_us;
675 aus += m_actualHLine_us;
676 } else {
677 // full line
678 #if SHOWDMADETAILS
679 printf("full");
680 #endif
681 if (frame == 1)
682 nodeptr = setDMANode(node++, m_lineBuf[activeLineIndex % CVBS_ALLOCATED_LINES], lineSamplesCount);
683 frameLine += 1.0;
684 fieldLine += 1.0;
685 us += m_params->line_us;
686 aus += m_actualLine_us;
687 }
688
689 // generate interrupt every half CVBS_ALLOCATED_LINES
690 if (frame == 1 && (activeLineIndex % (CVBS_ALLOCATED_LINES / 2)) == 0) {
691 nodeptr->eof = 1;
692 #if SHOWDMADETAILS
693 printf(" EOF");
694 #endif
695 }
696 #if SHOWDMADETAILS
697 printf("\n");
698 #endif
699
700
701 } else {
702
703 // end-field equalizing short pulse (half line)
704 #if SHOWDMADETAILS
705 printf("node = %d - end-field equalizing pulse\n", node);
706 #endif
707 if (frame == 1)
708 nodeptr = setDMANode(node++, m_ssyncBuf, hlineSamplesCount);
709 frameLine += .5;
710 fieldLine += .5;
711 us += m_params->hline_us;
712 aus += m_actualHLine_us;
713
714 }
715
716 /*
717 int extra_samples = imin(((us - aus) / m_sample_us), m_blackBufferLength) & ~1;
718 //if (extra_samples > 0 && frame == 1 && ((int)fieldLine < m_firstVisibleFieldLine-1 || (int)fieldLine > m_lastVisibleFieldLine+1)) {
719 if (extra_samples > 0 && frame == 1) {
720 setDMANode(node++, m_blackBuffer, extra_samples);
721 aus += extra_samples * m_sample_us;
722 printf("line %d, added %d extra samples\n", (int)fieldLine, extra_samples);
723 }
724 */
725
726
727 } // field-line loop
728
729 } // field loop
730
731 } // frame loop
732
733 //printf("total us = %f\n", us);
734
735 closeDMAChain(node - 1);
736
737 //printf("DMAChainLength = %d node = %d\n", DMAChainLength, node);
738}
739
740
741void CVBSGenerator::buildDMAChain_subCarrierOnly()
742{
743 double fsamplesPerCycle = 1000000. / m_params->subcarrierFreq_hz / m_sample_us;
744 int samplesPerCycle = 1000000. / m_params->subcarrierFreq_hz / m_sample_us;
745
746 int cycles = 10;
747 while ( (fsamplesPerCycle * cycles) - (int)(fsamplesPerCycle * cycles) > 0.5)
748 cycles += 1;
749
750 samplesPerCycle = fsamplesPerCycle;
751
752 int count = (samplesPerCycle * cycles) & ~1;
753
754 //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);
755
756 m_lsyncBuf = (volatile uint16_t *) heap_caps_malloc(count * sizeof(uint16_t), MALLOC_CAP_DMA);
757
758 auto sinLUT = new uint16_t[CVBS_SUBCARRIERPHASES * 2];
759
760 for (int sample = 0; sample < CVBS_SUBCARRIERPHASES * 2; ++sample) {
761 double phase = 2. * M_PI * sample / CVBS_SUBCARRIERPHASES;
762 double value = sin(phase);
763 sinLUT[sample] = (uint16_t)(m_params->blackLevel + m_params->burstAmp * value) << 8;
764 }
765
766 double K = m_params->subcarrierFreq_hz * CVBS_SUBCARRIERPHASES / m_params->sampleRate_hz;
767
768 for (int sample = 0; sample < count; ++sample) {
769 auto idx = (int)(K * sample) % CVBS_SUBCARRIERPHASES;
770 m_lsyncBuf[sample ^ 1] = sinLUT[idx];
771 //printf("%d = %d\n", sample, m_lsyncBuf[sample ^ 1] >> 8);
772 }
773
774 delete[] sinLUT;
775
776 m_DMAChain = (volatile lldesc_t *) heap_caps_malloc(1 * sizeof(lldesc_t), MALLOC_CAP_DMA);
777 setDMANode(0, m_lsyncBuf, count);
778 closeDMAChain(0);
779}
780
781
782CVBSParams const * CVBSGenerator::getParamsFromDesc(char const * desc)
783{
784 for (auto std : CVBS_Standards)
785 if (strcmp(std->desc, desc) == 0)
786 return std;
787 return nullptr;
788}
789
790
791void CVBSGenerator::setup(char const * desc)
792{
793 auto params = getParamsFromDesc(desc);
794 setup(params ? params : CVBS_Standards[0]);
795}
796
797
798void CVBSGenerator::setup(CVBSParams const * params)
799{
800 m_params = params;
801
802 m_sample_us = 1000000. / m_params->sampleRate_hz;
803
804 double activeLine_us = m_params->line_us - m_params->hsync_us - m_params->backPorch_us - m_params->frontPorch_us;
805 int maxVisibleSamples = activeLine_us / m_sample_us;
806 m_linesPerFrame = m_params->fieldLines * m_params->fields;
807
808 int usableFieldLines = m_params->fieldLines - m_params->blankLines;
809 m_visibleLines = imin(usableFieldLines, m_params->defaultVisibleLines);
810
811 // make sure m_visibleLines is divisible by CVBS_ALLOCATED_LINES
812 m_visibleLines -= m_visibleLines % CVBS_ALLOCATED_LINES;
813
814 m_firstVisibleFieldLine = m_params->blankLines + ceil((usableFieldLines - m_visibleLines) / 2.0);
815 m_lastVisibleFieldLine = m_firstVisibleFieldLine + m_visibleLines - 1;
816
817 //printf("m_visibleLines = %d m_firstVisibleFieldLine = %d m_lastVisibleFieldLine = %d\n", m_visibleLines, m_firstVisibleFieldLine, m_lastVisibleFieldLine);
818
819 int blankSamples = m_params->hblank_us / m_sample_us;
820 int hsyncSamples = m_params->hsync_us / m_sample_us;
821 int backPorchSamples = m_params->backPorch_us / m_sample_us;
822 int usableVisibleSamples = maxVisibleSamples - blankSamples;
823 s_visibleSamplesCount = imin(usableVisibleSamples, m_params->defaultVisibleSamples);
824 s_firstVisibleSample = (hsyncSamples + backPorchSamples + blankSamples + (usableVisibleSamples - s_visibleSamplesCount) / 2) & ~1; // aligned to 2
825
826 double subcarrierCycle_us = 1000000. / m_params->subcarrierFreq_hz; // duration in microseconds of a subcarrier cycle
827
828 m_firstColorBurstSample = (m_params->hsync_us + m_params->burstStart_us) / m_sample_us;
829 m_lastColorBurstSample = m_firstColorBurstSample + (subcarrierCycle_us * m_params->burstCycles) / m_sample_us - 1;
830}
831
832
833void CVBSGenerator::run(bool subCarrierOnly)
834{
835 if (subCarrierOnly)
836 buildDMAChain_subCarrierOnly();
837 else
838 buildDMAChain();
839 runDMA(m_DMAChain);
840}
841
842
843void CVBSGenerator::stop()
844{
845 if (m_DMAStarted) {
846
847 periph_module_disable(PERIPH_I2S0_MODULE);
848 m_DMAStarted = false;
849
850 if (m_isr_handle) {
851 esp_intr_free(m_isr_handle);
852 m_isr_handle = nullptr;
853 }
854
855 // cleanup DMA chain and buffers
856 if (m_DMAChain) {
857
858 heap_caps_free((void*)m_DMAChain);
859 m_DMAChain = nullptr;
860
861 if (m_ssyncBuf) {
862 heap_caps_free((void*)m_ssyncBuf);
863 m_ssyncBuf = nullptr;
864 }
865
866 if (m_lsyncBuf) {
867 heap_caps_free((void*)m_lsyncBuf);
868 m_lsyncBuf = nullptr;
869 }
870
871 if (m_lineBuf) {
872 for (int i = 0; i < CVBS_ALLOCATED_LINES; ++i)
873 heap_caps_free((void*)m_lineBuf[i]);
874 free(m_lineBuf);
875 m_lineBuf = nullptr;
876 }
877
878 for (int frame = 0; frame < m_params->frameGroupCount; ++frame)
879 if (m_subCarrierPhases[frame]) {
880 heap_caps_free((void*)m_subCarrierPhases[frame]);
881 m_subCarrierPhases[frame] = nullptr;
882 }
883
884 if (s_lineSampleToSubCarrierSample) {
885 heap_caps_free((void*)s_lineSampleToSubCarrierSample);
886 s_lineSampleToSubCarrierSample = nullptr;
887 }
888
889 }
890
891 }
892}
893
894
895void CVBSGenerator::setDrawScanlineCallback(CVBSDrawScanlineCallback drawScanlineCallback, void * arg)
896{
897 m_drawScanlineCallback = drawScanlineCallback;
898 m_drawScanlineArg = arg;
899}
900
901
902void IRAM_ATTR CVBSGenerator::ISRHandler(void * arg)
903{
904 #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
905 auto s1 = getCycleCount();
906 #endif
907
908 if (I2S0.int_st.out_eof) {
909
910 auto ctrl = (CVBSGenerator *) arg;
911 auto desc = (volatile lldesc_t*) I2S0.out_eof_des_addr;
912
913 // begin of field?
914 if (desc->sosf) {
915 s_field = (s_field + 1) % ctrl->m_params->fields;
916 if (s_field == 0)
917 s_frame = (s_frame + 1) % ctrl->m_params->frameGroupCount; // first field
918 s_interFrameLine = ctrl->m_linesPerFrame * s_frame + ctrl->m_firstActiveFrameLine[s_field];
919 s_frameLine = ctrl->m_firstActiveFrameLine[s_field];
920 s_subCarrierPhase = &(ctrl->m_subCarrierPhases[s_frame][s_frameLine]);
921 s_activeLineIndex = 0;
922 s_scanLine = ctrl->m_startingScanLine[s_field];
923 s_VSync = false;
924 }
925
926 auto drawScanlineCallback = ctrl->m_drawScanlineCallback;
927 auto drawScanlineArg = ctrl->m_drawScanlineArg;
928 auto lineBuf = ctrl->m_lineBuf;
929 auto firstVisibleFrameLine = ctrl->m_firstVisibleFrameLine[s_field];
930 auto lastVisibleFrameLine = ctrl->m_lastVisibleFrameLine[s_field];
931 auto firstColorBurstSample = ctrl->m_firstColorBurstSample;
932 auto lastColorBurstSample = ctrl->m_lastColorBurstSample;
933 auto interlaceFactor = ctrl->m_params->interlaceFactor;
934
935 for (int i = 0; i < CVBS_ALLOCATED_LINES / 2; ++i) {
936
937 auto fullLineBuf = (uint16_t*) lineBuf[s_activeLineIndex % CVBS_ALLOCATED_LINES];
938
939 if (*s_subCarrierPhase == CVBS_NOBURSTFLAG) {
940 // no burst for this line
941 uint16_t blk = ctrl->m_params->blackLevel << 8;
942 for (int s = firstColorBurstSample; s <= lastColorBurstSample; ++s)
943 fullLineBuf[s ^ 1] = blk;
944 } else {
945 // fill color burst
946 auto colorBurstLUT = ctrl->m_colorBurstLUT[s_interFrameLine & 1];
947 auto sampleLUT = CVBSGenerator::lineSampleToSubCarrierSample() + firstColorBurstSample;
948 for (int s = firstColorBurstSample; s <= lastColorBurstSample; ++s)
949 fullLineBuf[s ^ 1] = colorBurstLUT[*sampleLUT++ + *s_subCarrierPhase];
950 }
951
952 // fill active area
953 auto visibleBuf = fullLineBuf + s_firstVisibleSample;
954 if (s_frameLine >= firstVisibleFrameLine && s_frameLine <= lastVisibleFrameLine) {
955 // visible lines
956 drawScanlineCallback(drawScanlineArg, visibleBuf, s_scanLine);
957 s_scanLine += interlaceFactor; // +2 if interlaced, +1 if progressive
958 } else {
959 // blank lines
960 uint32_t blackFillX2 = (ctrl->m_params->blackLevel << 8) | (ctrl->m_params->blackLevel << (8 + 16));
961 for (int col = 0; col < s_visibleSamplesCount; col += 2, visibleBuf += 2)
962 *((uint32_t*)(visibleBuf)) = blackFillX2;
963 }
964
965 ++s_activeLineIndex;
966 ++s_interFrameLine;
967 ++s_frameLine;
968 ++s_subCarrierPhase;
969
970 }
971
972 if (s_frameLine >= lastVisibleFrameLine)
973 s_VSync = true;
974 }
975
976 #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
977 s_compctrlcycles += getCycleCount() - s1;
978 #endif
979
980 I2S0.int_clr.val = I2S0.int_st.val;
981}
982
983
984
985} // namespace fabgl
int16_t Y
#define FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE
Definition: fabglconf.h:138
This file contains some utility classes and functions.