├── .editorconfig ├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── main ├── app_main.cpp ├── component.mk ├── my_config.h ├── my_rmt.cpp ├── my_rmt.h ├── my_spi.c ├── my_spi.h ├── my_vga.cpp └── my_vga.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{h,hpp,c,cpp}] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_size = 4 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | sdkconfig 3 | sdkconfig.* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Takayuki MATSUOKA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | PROJECT_NAME := vga_experiment 6 | include $(IDF_PATH)/make/project.mk 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32-vga-experiment 2 | 3 | Experiment for output VGA signal from ESP32. 4 | 5 | 6 | ## How to build and run 7 | 8 | + Check and set `GpioPins{}` and `Peripherals` in `main/app_main.cpp`. You should define and connect at least 3 GPIO pins: V-Sync, H-Sync, Video (single pin). 9 | + Run `make menuconfig` for basic setup. 10 | + Run `make -j flash monitor` for build & run. 11 | -------------------------------------------------------------------------------- /main/app_main.cpp: -------------------------------------------------------------------------------- 1 | // Please edit GpioPins {}, Peripherals {} and connect GPIO pins. 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "my_vga.h" 9 | 10 | 11 | /////////////////////////////////////////////////////////////////////////// 12 | // 13 | // Set, modify and connect the following GpioPins and Peripherals. 14 | // 15 | /////////////////////////////////////////////////////////////////////////// 16 | namespace GpioPins { 17 | const gpio_num_t outHsync = static_cast(GPIO_NUM_23); // OUT: VGA H-SYNC from RMT 18 | const gpio_num_t outVsync = static_cast(GPIO_NUM_22); // OUT: VGA V-SYNC from RMT 19 | const gpio_num_t outVideo = static_cast(GPIO_NUM_25); // OUT: VGA VIDEO from SPI 20 | const gpio_num_t outLedBlink = static_cast(GPIO_NUM_2); // OUT: LED heartbeat 21 | } 22 | 23 | namespace Peripherals { 24 | const spi_host_device_t SpiHost = static_cast(HSPI_HOST); // HSPI or VSPI 25 | const int SpiDmaChannel = static_cast(1); // 1 or 2 26 | const rmt_channel_t RmtChannelHsync = RMT_CHANNEL_0; // RMT Channel for H-SYNC signal 27 | const rmt_channel_t RmtChannelVsync = RMT_CHANNEL_1; // RMT Channel for V-SYNC signal 28 | } 29 | 30 | 31 | /////////////////////////////////////////////////////////////////////////// 32 | enum { 33 | VIDEO_WIDTH = 800, 34 | VIDEO_HEIGHT = 600, 35 | VIDEO_STRIDE = VIDEO_WIDTH / 8, 36 | }; 37 | 38 | static uint8_t* videoBuf = NULL; 39 | static void* vgaBuf = NULL; 40 | 41 | 42 | /////////////////////////////////////////////////////////////////////////// 43 | static void heartBeat(gpio_num_t outLed) { 44 | static int8_t counter = -1; 45 | if(counter < 0) { 46 | counter = 0; 47 | gpio_pad_select_gpio(outLed); 48 | gpio_set_direction(outLed, GPIO_MODE_OUTPUT); 49 | } 50 | 51 | counter = (counter + 1) & 127; 52 | if((counter & 31) == 0) { 53 | gpio_set_level(outLed, (counter & 32) == 0); 54 | } 55 | } 56 | 57 | 58 | /////////////////////////////////////////////////////////////////////////// 59 | // http://www.pcg-random.org/download.html 60 | typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t; 61 | 62 | static uint32_t pcg32_random_r(pcg32_random_t* rng) { 63 | uint64_t oldstate = rng->state; 64 | // Advance internal state 65 | rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1); 66 | // Calculate output function (XSH RR), uses old state for max ILP 67 | uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; 68 | uint32_t rot = oldstate >> 59u; 69 | return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); 70 | } 71 | 72 | static uint32_t rnd(int x) { 73 | static pcg32_random_t rng = { 0, 0 }; 74 | return pcg32_random_r(&rng) % x; 75 | } 76 | 77 | 78 | /////////////////////////////////////////////////////////////////////////// 79 | static void setPixel(int x, int y, int color) { 80 | uint8_t* p = videoBuf + x/8 + y * VIDEO_STRIDE; 81 | if(color) { 82 | const uint8_t mask = 1 << (x & 7); 83 | *p |= mask; 84 | } else { 85 | const uint8_t mask = 0xfe << (x & 7); 86 | *p &= mask; 87 | } 88 | } 89 | 90 | static void reversePixel(int x, int y) { 91 | uint8_t* p = videoBuf + x/8 + y * VIDEO_STRIDE; 92 | const uint8_t mask = 1 << (x & 7); 93 | *p ^= mask; 94 | } 95 | 96 | static void clearAllPixels(int c = 0) { 97 | memset(videoBuf, c, VIDEO_STRIDE * VIDEO_HEIGHT); 98 | } 99 | 100 | 101 | /////////////////////////////////////////////////////////////////////////// 102 | static void onVsync() { 103 | static int cnt = 0; 104 | if(--cnt <= 0) { 105 | cnt = 300; //rnd(150) + 300; 106 | clearAllPixels(); 107 | } 108 | 109 | for(int i = 0; i < 1000; ++i) { 110 | reversePixel(rnd(VIDEO_WIDTH), rnd(VIDEO_HEIGHT)); 111 | } 112 | 113 | for(int x = 0; x < VIDEO_WIDTH; ++x) { 114 | setPixel(x, 0, 1); 115 | setPixel(x, VIDEO_HEIGHT-1, 1); 116 | } 117 | 118 | for(int y = 0; y < VIDEO_HEIGHT; ++y) { 119 | setPixel(0, y, 1); 120 | setPixel(VIDEO_WIDTH-1, y, 1); 121 | } 122 | } 123 | 124 | 125 | /////////////////////////////////////////////////////////////////////////// 126 | static SemaphoreHandle_t vsyncSemaphore = nullptr; 127 | static volatile int32_t onVsyncCount = 0; 128 | 129 | 130 | static void onVsyncIsr(void* userPtr) { 131 | if(vsyncSemaphore == nullptr) { 132 | return; 133 | } 134 | 135 | ++onVsyncCount; 136 | 137 | static BaseType_t xHigherPriorityTaskWoken; 138 | xHigherPriorityTaskWoken = pdFALSE; 139 | xSemaphoreGiveFromISR(vsyncSemaphore, &xHigherPriorityTaskWoken); 140 | portYIELD_FROM_ISR(); 141 | } 142 | 143 | 144 | static void taskVsync(void * pvParameters) { 145 | vsyncSemaphore = xSemaphoreCreateBinary(); 146 | 147 | // Set V-Sync callback (ISR) 148 | myvga_set_vsync_callback_function(onVsyncIsr, NULL); 149 | 150 | for(;;) { 151 | // Wait fror V-Sync 152 | if(xSemaphoreTake(vsyncSemaphore, portMAX_DELAY) != pdTRUE) { 153 | continue; 154 | } 155 | onVsync(); 156 | } 157 | } 158 | 159 | 160 | /////////////////////////////////////////////////////////////////////////// 161 | static void vga_setup() { 162 | myvga_init_params_t params; 163 | myvga_prepare_init_init_params(¶ms); 164 | 165 | const size_t videoBufSizeInBytes = VIDEO_STRIDE * VIDEO_HEIGHT; 166 | videoBuf = (uint8_t*) malloc(videoBufSizeInBytes); 167 | 168 | // Set initialize parameters and allocate memory for own video buffer. 169 | params.video.width = VIDEO_WIDTH; 170 | params.video.height = VIDEO_HEIGHT; 171 | params.video.strideInBytes = VIDEO_STRIDE; 172 | params.video.bufferSizeInBytes = videoBufSizeInBytes; 173 | params.video.buffer = videoBuf; 174 | params.spi.host = Peripherals::SpiHost; 175 | params.spi.dmaChan = Peripherals::SpiDmaChannel; 176 | params.spi.mosiGpioNum = GpioPins::outVideo; 177 | params.rmt.hsyncChannel = Peripherals::RmtChannelHsync; 178 | params.rmt.hsyncGpioNum = GpioPins::outHsync; 179 | params.rmt.vsyncChannel = Peripherals::RmtChannelVsync; 180 | params.rmt.vsyncGpioNum = GpioPins::outVsync; 181 | 182 | // Allocate memory for VGA class. 183 | const size_t vgaBufSize = myvga_prepare_get_memory_size(¶ms); 184 | vgaBuf = malloc(vgaBufSize); 185 | 186 | // Initialize VGA class. 187 | myvga_init(¶ms, vgaBuf, vgaBufSize); 188 | } 189 | 190 | /////////////////////////////////////////////////////////////////////////// 191 | static void setup() { 192 | vga_setup(); 193 | } 194 | 195 | static void loop() { 196 | static int cc = 0; 197 | if(--cc < 0) { 198 | cc = 100; 199 | const int32_t frameCount = myvga_get_frame_count32(); 200 | printf("frameCount=%d, onVsyncCount=%d\n", frameCount, onVsyncCount); 201 | } 202 | } 203 | 204 | extern "C" void app_main() { 205 | setup(); 206 | fflush(stdout); 207 | 208 | TaskHandle_t xHandle = nullptr; 209 | //BaseType_t xReturned = 210 | xTaskCreate( 211 | taskVsync // Function that implements the task. 212 | , "VGA-VSync" // Text name for the task. 213 | , configMINIMAL_STACK_SIZE // Stack size in words, not bytes. 214 | , ( void * ) 1 // pvParameters: Parameter passed into the task. 215 | , tskIDLE_PRIORITY // Priority at which the task is created. 216 | , &xHandle 217 | ); 218 | 219 | for(;;) { 220 | heartBeat(GpioPins::outLedBlink); 221 | vTaskDelay(10 / portTICK_RATE_MS); 222 | loop(); 223 | } 224 | 225 | if(xHandle != NULL) { 226 | vTaskDelete(xHandle); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | -------------------------------------------------------------------------------- /main/my_config.h: -------------------------------------------------------------------------------- 1 | #ifndef MY_CONFIG_H 2 | #define MY_CONFIG_H 3 | 4 | #include 5 | 6 | #define MY_ESP_ERR(base,x) ((esp_err_t) ((base) + (x))) 7 | 8 | #define MY_ESP_ERR_USER_BASE (0x40000000) 9 | #define MY_ESP_ERR_LEDC_BASE (MY_ESP_ERR_USER_BASE + 0x1000 * 0) 10 | #define MY_ESP_ERR_SPI_BASE (MY_ESP_ERR_USER_BASE + 0x1000 * 1) 11 | #define MY_ESP_ERR_VGA_BASE (MY_ESP_ERR_USER_BASE + 0x1000 * 2) 12 | #define MY_ESP_ERR_RMT_BASE (MY_ESP_ERR_USER_BASE + 0x1000 * 3) 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /main/my_rmt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "my_rmt.h" 4 | 5 | namespace { 6 | const int RmtItem32Max = 64; 7 | const int RmtItem16Max = RmtItem32Max * 2; 8 | const int RmtDurationMax = 32767; 9 | } 10 | 11 | static rmt_item16_t* myrmt_writeItem(rmt_item16_t* pItem, rmt_item16_t* pItemEnd, int32_t duration, int level) { 12 | auto d = duration; 13 | const int l = level ? 1 : 0; 14 | while(d > 0 && pItem < pItemEnd) { 15 | auto t = d; 16 | if(t > RmtDurationMax) { 17 | t = RmtDurationMax; 18 | } 19 | pItem->duration = t; 20 | pItem->level = l; 21 | 22 | ++pItem; 23 | d -= t; 24 | } 25 | return pItem; 26 | } 27 | 28 | 29 | static rmt_item16_t* myrmt_writePadding(rmt_item16_t* pItem, rmt_item16_t* pItemTop, rmt_item16_t* pItemEnd) { 30 | const int nItem = static_cast(pItem - pItemTop); 31 | if(nItem & 1) { 32 | if(pItem < pItemEnd) { 33 | pItem->duration = 0; 34 | pItem->level = 0; 35 | ++pItem; 36 | } 37 | } 38 | return pItem; 39 | } 40 | 41 | 42 | esp_err_t myrmt_setup_pulse_output( 43 | rmt_channel_t channel 44 | , gpio_num_t outputGpioNum 45 | , int32_t clkDiv 46 | , int32_t durationA 47 | , int32_t levelA 48 | , int32_t durationB 49 | , int32_t levelB 50 | , int32_t* pNumItems 51 | ) { 52 | std::array items; 53 | memset(items.data(), 0, items.size() * sizeof(items[0])); 54 | 55 | auto* const pItemTop = reinterpret_cast(items.data()); 56 | auto* const pItemEnd = reinterpret_cast(std::end(items)); 57 | 58 | auto* p = pItemTop; 59 | 60 | p = myrmt_writeItem(p, pItemEnd, durationA, levelA); 61 | p = myrmt_writeItem(p, pItemEnd, durationB, levelB); 62 | p = myrmt_writePadding(p, pItemTop, pItemEnd); 63 | 64 | const auto nItems = static_cast(p - pItemTop) / 2; 65 | if(pNumItems != nullptr) { 66 | *pNumItems = nItems; 67 | } 68 | 69 | rmt_config_t c; 70 | memset(&c, 0, sizeof(c)); 71 | auto& tx = c.tx_config; 72 | c.rmt_mode = RMT_MODE_TX; 73 | c.channel = channel; 74 | c.gpio_num = outputGpioNum; 75 | c.mem_block_num = nItems; 76 | c.clk_div = clkDiv; 77 | 78 | tx.loop_en = 1; 79 | 80 | tx.idle_output_en = 1; 81 | tx.idle_level = RMT_IDLE_LEVEL_LOW; 82 | 83 | tx.carrier_en = 0; 84 | tx.carrier_freq_hz = 1; 85 | tx.carrier_level = RMT_CARRIER_LEVEL_LOW; 86 | tx.carrier_duty_percent = 50; 87 | 88 | if(nItems > 16) { 89 | return MY_ESP_ERR_RMT_ITEM_BUFFER_OVERFLOW; 90 | } 91 | ESP_ERROR_CHECK(rmt_driver_install(channel, 0, ESP_INTR_FLAG_SHARED)); 92 | ESP_ERROR_CHECK(rmt_config(&c)); 93 | ESP_ERROR_CHECK(rmt_write_items(channel, reinterpret_cast(pItemTop), nItems, false)); 94 | rmt_tx_stop(channel); 95 | return ESP_OK; 96 | } 97 | -------------------------------------------------------------------------------- /main/my_rmt.h: -------------------------------------------------------------------------------- 1 | #ifndef MY_RMT_H 2 | #define MY_RMT_H 3 | 4 | #include 5 | #include "my_config.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define MY_ESP_ERR_RMT(x) MY_ESP_ERR(MY_ESP_ERR_RMT_BASE, (x)) 12 | #define MY_ESP_ERR_RMT_ITEM_BUFFER_OVERFLOW MY_ESP_ERR_RMT(1) 13 | 14 | esp_err_t myrmt_setup_pulse_output( 15 | rmt_channel_t channel 16 | , gpio_num_t outputGpioNum 17 | , int32_t clkDiv 18 | , int32_t durationA 19 | , int32_t levelA 20 | , int32_t durationB 21 | , int32_t levelB 22 | , int32_t* numItems 23 | ); 24 | 25 | #ifdef __cplusplus 26 | } // extern "C" 27 | #endif 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /main/my_spi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "my_spi.h" 4 | 5 | static const uint8_t InvalidIndex = (uint8_t) -1; 6 | 7 | spi_dev_t *myspi_get_hw_for_host( 8 | spi_host_device_t host 9 | ) { 10 | switch(host) { 11 | case SPI_HOST: return &SPI1; break; 12 | case HSPI_HOST: return &SPI2; break; 13 | case VSPI_HOST: return &SPI3; break; 14 | default: return NULL; break; 15 | } 16 | } 17 | 18 | 19 | static uint8_t getSpidOutByHost( 20 | spi_host_device_t host 21 | ) { 22 | switch(host) { 23 | case SPI_HOST: return SPID_OUT_IDX; break; 24 | case HSPI_HOST: return HSPID_OUT_IDX; break; 25 | case VSPI_HOST: return VSPID_OUT_IDX; break; 26 | default: return InvalidIndex; break; 27 | } 28 | } 29 | 30 | 31 | static uint8_t getSpidInByHost( 32 | spi_host_device_t host 33 | ) { 34 | switch(host) { 35 | case SPI_HOST: return SPID_IN_IDX; break; 36 | case HSPI_HOST: return HSPID_IN_IDX; break; 37 | case VSPI_HOST: return VSPID_IN_IDX; break; 38 | default: return InvalidIndex; break; 39 | } 40 | } 41 | 42 | 43 | esp_err_t myspi_prepare_circular_buffer( 44 | const spi_host_device_t spiHostDevice 45 | , const int dma_chan 46 | , const lldesc_t* lldescs 47 | , const double dmaClockSpeedInHz 48 | , const gpio_num_t mosi_gpio_num 49 | , const int waitCycle 50 | ) { 51 | const bool spi_periph_claimed = spicommon_periph_claim(spiHostDevice); 52 | if(! spi_periph_claimed) { 53 | return MY_ESP_ERR_SPI_HOST_ALREADY_IN_USE; 54 | } 55 | 56 | const bool dma_chan_claimed = spicommon_dma_chan_claim(dma_chan); 57 | if(! dma_chan_claimed) { 58 | spicommon_periph_free(spiHostDevice); 59 | return MY_ESP_ERR_SPI_DMA_ALREADY_IN_USE; 60 | } 61 | 62 | spi_dev_t* const spiHw = myspi_get_hw_for_host(spiHostDevice); 63 | const int Cs = 0; 64 | const int CsMask = 1 << Cs; 65 | 66 | //Use GPIO 67 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[mosi_gpio_num], PIN_FUNC_GPIO); 68 | gpio_set_direction(mosi_gpio_num, GPIO_MODE_INPUT_OUTPUT); 69 | gpio_matrix_out(mosi_gpio_num, getSpidOutByHost(spiHostDevice), false, false); 70 | gpio_matrix_in(mosi_gpio_num, getSpidInByHost(spiHostDevice), false); 71 | 72 | //Select DMA channel. 73 | DPORT_SET_PERI_REG_BITS( 74 | DPORT_SPI_DMA_CHAN_SEL_REG 75 | , 3 76 | , dma_chan 77 | , (spiHostDevice * 2) 78 | ); 79 | 80 | //Reset DMA 81 | spiHw->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; 82 | spiHw->dma_out_link.start = 0; 83 | spiHw->dma_in_link.start = 0; 84 | spiHw->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); 85 | 86 | //Reset timing 87 | spiHw->ctrl2.val = 0; 88 | 89 | //Disable unneeded ints 90 | spiHw->slave.rd_buf_done = 0; 91 | spiHw->slave.wr_buf_done = 0; 92 | spiHw->slave.rd_sta_done = 0; 93 | spiHw->slave.wr_sta_done = 0; 94 | spiHw->slave.rd_buf_inten = 0; 95 | spiHw->slave.wr_buf_inten = 0; 96 | spiHw->slave.rd_sta_inten = 0; 97 | spiHw->slave.wr_sta_inten = 0; 98 | spiHw->slave.trans_inten = 0; 99 | spiHw->slave.trans_done = 0; 100 | 101 | //Set CS pin, CS options 102 | spiHw->pin.master_ck_sel &= ~CsMask; 103 | spiHw->pin.master_cs_pol &= ~CsMask; 104 | 105 | // Set SPI Clock 106 | // Register 7.7: SPI_CLOCK_REG (0x18) 107 | // 108 | // SPI_CLK_EQU_SYSCLK 109 | // In master mode, when this bit is set to 1, spi_clk is equal 110 | // to system clock; when set to 0, spi_clk is divided from system 111 | // clock. 112 | // 113 | // SPI_CLKDIV_PRE 114 | // In master mode, the value of this register field is the 115 | // pre-divider value for spi_clk, minus one. 116 | // 117 | // SPI_CLKCNT_N 118 | // In master mode, this is the divider for spi_clk minus one. 119 | // The spi_clk frequency is 120 | // system_clock/(SPI_CLKDIV_PRE+1)/(SPI_CLKCNT_N+1). 121 | // 122 | // SPI_CLKCNT_H 123 | // For a 50% duty cycle, set this to floor((SPI_CLKCNT_N+1)/2-1) 124 | // 125 | // SPI_CLKCNT_L 126 | // In master mode, this must be equal to SPI_CLKCNT_N. 127 | { 128 | const double preDivider = 1.0; 129 | const double apbClockSpeedInHz = APB_CLK_FREQ; 130 | const double apbClockPerDmaCycle = (apbClockSpeedInHz / preDivider / dmaClockSpeedInHz); 131 | 132 | const int32_t clkdiv_pre = ((int32_t) preDivider) - 1; 133 | const int32_t clkcnt_n = ((int32_t) apbClockPerDmaCycle) - 1; 134 | const int32_t clkcnt_h = (clkcnt_n + 1) / 2 - 1; 135 | const int32_t clkcnt_l = clkcnt_n; 136 | 137 | spiHw->clock.clk_equ_sysclk = 0; 138 | spiHw->clock.clkcnt_n = clkcnt_n; 139 | spiHw->clock.clkdiv_pre = clkdiv_pre; 140 | spiHw->clock.clkcnt_h = clkcnt_h; 141 | spiHw->clock.clkcnt_l = clkcnt_l; 142 | } 143 | 144 | //Configure bit order 145 | spiHw->ctrl.rd_bit_order = 0; // MSB first 146 | spiHw->ctrl.wr_bit_order = 1; // LSB first 147 | 148 | //Configure polarity 149 | spiHw->pin.ck_idle_edge = 0; 150 | spiHw->user.ck_out_edge = 0; 151 | spiHw->ctrl2.miso_delay_mode = 0; 152 | 153 | //configure dummy bits 154 | spiHw->user.usr_dummy = 0; 155 | spiHw->user1.usr_dummy_cyclelen = 0; 156 | 157 | //Configure misc stuff 158 | spiHw->user.doutdin = 0; 159 | spiHw->user.sio = 0; 160 | 161 | spiHw->ctrl2.setup_time = 0; 162 | spiHw->user.cs_setup = 0; 163 | spiHw->ctrl2.hold_time = 0; 164 | spiHw->user.cs_hold = 0; 165 | 166 | //Configure CS pin 167 | spiHw->pin.cs0_dis = (Cs == 0) ? 0 : 1; 168 | spiHw->pin.cs1_dis = (Cs == 1) ? 0 : 1; 169 | spiHw->pin.cs2_dis = (Cs == 2) ? 0 : 1; 170 | 171 | //Reset SPI peripheral 172 | spiHw->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; 173 | spiHw->dma_out_link.start = 0; 174 | spiHw->dma_in_link.start = 0; 175 | spiHw->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); 176 | spiHw->dma_conf.out_data_burst_en = 1; 177 | 178 | //Set up QIO/DIO if needed 179 | spiHw->ctrl.val &= ~(SPI_FREAD_DUAL|SPI_FREAD_QUAD|SPI_FREAD_DIO|SPI_FREAD_QIO); 180 | spiHw->user.val &= ~(SPI_FWRITE_DUAL|SPI_FWRITE_QUAD|SPI_FWRITE_DIO|SPI_FWRITE_QIO); 181 | 182 | //DMA temporary workaround: let RX DMA work somehow to avoid the issue in ESP32 v0/v1 silicon 183 | spiHw->dma_in_link.addr = 0; 184 | spiHw->dma_in_link.start = 1; 185 | 186 | spiHw->user1.usr_addr_bitlen = 0; 187 | spiHw->user2.usr_command_bitlen = 0; 188 | spiHw->user.usr_addr = 0; 189 | spiHw->user.usr_command = 0; 190 | if(waitCycle <= 0) { 191 | spiHw->user.usr_dummy = 0; 192 | spiHw->user1.usr_dummy_cyclelen = 0; 193 | } else { 194 | spiHw->user.usr_dummy = 1; 195 | spiHw->user1.usr_dummy_cyclelen = (uint8_t) (waitCycle-1); 196 | } 197 | 198 | spiHw->user.usr_mosi_highpart = 0; 199 | spiHw->user2.usr_command_value = 0; 200 | spiHw->addr = 0; 201 | spiHw->user.usr_mosi = 1; // Enable MOSI 202 | spiHw->user.usr_miso = 0; 203 | 204 | spiHw->dma_out_link.addr = (int)(lldescs) & 0xFFFFF; 205 | 206 | spiHw->mosi_dlen.usr_mosi_dbitlen = 0; // works great! (there's no glitch in 5 hours) 207 | spiHw->miso_dlen.usr_miso_dbitlen = 0; 208 | 209 | // Set circular mode 210 | // https://www.esp32.com/viewtopic.php?f=2&t=4011#p18107 211 | // > yes, in SPI DMA mode, SPI will alway transmit and receive 212 | // > data when you set the SPI_DMA_CONTINUE(BIT16) of SPI_DMA_CONF_REG. 213 | spiHw->dma_conf.dma_continue = 1; 214 | 215 | return ESP_OK; 216 | } 217 | 218 | 219 | esp_err_t myspi_release_circular_buffer( 220 | const spi_host_device_t spiHostDevice 221 | , const int dma_chan 222 | , const gpio_num_t mosi_gpio_num 223 | ) { 224 | spi_dev_t* const spiHw = myspi_get_hw_for_host(spiHostDevice); 225 | 226 | spiHw->dma_conf.dma_continue = 0; 227 | spiHw->dma_out_link.start = 0; 228 | spiHw->cmd.usr = 0; 229 | 230 | // TODO : Reset GPIO Matrix 231 | 232 | spicommon_dma_chan_free(dma_chan); 233 | spicommon_periph_free(spiHostDevice); 234 | return ESP_OK; 235 | } 236 | -------------------------------------------------------------------------------- /main/my_spi.h: -------------------------------------------------------------------------------- 1 | #ifndef MY_SPI_H 2 | #define MY_SPI_H 3 | 4 | #include 5 | #include 6 | #include "my_config.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #define MY_ESP_ERR_SPI(x) MY_ESP_ERR(MY_ESP_ERR_SPI_BASE, (x)) 13 | #define MY_ESP_ERR_SPI_SPI1_IS_NOT_SUPPORTED MY_ESP_ERR_SPI(1) 14 | #define MY_ESP_ERR_SPI_INVALID_HOST_NUMBER MY_ESP_ERR_SPI(2) 15 | #define MY_ESP_ERR_SPI_INVALID_DMA_CHANNEL MY_ESP_ERR_SPI(3) 16 | #define MY_ESP_ERR_SPI_HOST_ALREADY_IN_USE MY_ESP_ERR_SPI(4) 17 | #define MY_ESP_ERR_SPI_DMA_ALREADY_IN_USE MY_ESP_ERR_SPI(5) 18 | 19 | esp_err_t myspi_prepare_circular_buffer( 20 | spi_host_device_t host // HSPI_HOST or VSPI_HOST 21 | , int dma_chan // 22 | , const lldesc_t* lldesc // lldesc_t which makes circular buffer 23 | , double clock_speed_hz // SPI speed in Hz 24 | , gpio_num_t mosi_gpio_num // GPIO 25 | , int wait_cycle // Number of wait cycle before actual transmission (1 cycle means SPI DMA's single cycle) 26 | ); 27 | 28 | esp_err_t myspi_release_circular_buffer( 29 | const spi_host_device_t host 30 | , const int dma_chan 31 | , const gpio_num_t mosi_gpio_num 32 | ); 33 | 34 | spi_dev_t *myspi_get_hw_for_host(spi_host_device_t host); 35 | 36 | #ifdef __cplusplus 37 | } // extern "C" 38 | #endif 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /main/my_vga.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "my_spi.h" 10 | #include "my_rmt.h" 11 | #include "my_vga.h" 12 | 13 | 14 | namespace { 15 | // 16 | // 800x600@60Hz, PixelFrequency = 40 MHz 17 | // 18 | // http://tinyvga.com/vga-timing/800x600@60Hz 19 | // 20 | // Pixel frequency: 40.0 MHz 21 | // 22 | // Horizontal timing (line) 23 | // 24 | // Polarity of horizontal sync pulse is positive. 25 | // (H-Sync pulse is +V, non-pulse is 0V) 26 | // 27 | // Visible area 800 pixels 20000.0 ns 28 | // Front porch 40 pixels 1000.0 ns 29 | // Sync pulse 128 pixels 3200.0 ns 30 | // Back porch 88 pixels 2200.0 ns 31 | // Whole line 1056 pixels 26400.0 ns 32 | // 33 | // note: All above nano-seconds are exact nano-seconds. 34 | // Thus "Whole line" requires 26400.000...0 nano-seconds. 35 | // 36 | // Vertical timing (frame) 37 | // 38 | // Polarity of vertical sync pulse is positive. 39 | // (V-Sync pulse is +V, non-pulse is 0V) 40 | // 41 | // Visible area 600 lines 15840.00 us 42 | // Front porch 1 line 26.40 us 43 | // Sync pulse 4 lines 105.60 us 44 | // Back porch 23 lines 607.20 us 45 | // Whole frame 628 lines 16579.20 us 46 | // 47 | // note: All above micro-seconds are exact micro-seconds. 48 | // Thus "Whole frame" requires 16579.200...0 micro-seconds. 49 | // 50 | 51 | const double VgaPixelFrequencyInHz = 40.0 * 1000.0 * 1000.0; // 40.0 MHz 52 | const bool VSyncPolarity = true; // true:sync=HI, false:sync=LO 53 | const bool HSyncPolarity = true; // true:sync=HI, false:sync=LO 54 | 55 | const int VgaVideoWidth = 800; 56 | const int VgaVideoHeight = 600; 57 | 58 | const int VgaHSyncFrontPorchInPixels = 40; 59 | const int VgaHSyncSignalInPixels = 128; 60 | const int VgaHSyncBackPorchInPixels = 88; 61 | 62 | const int VgaVSyncFrontPorchInLines = 1; 63 | const int VgaVSyncSignalInLines = 4; 64 | const int VgaVSyncBackPorchInLines = 23; 65 | 66 | const int VgaSignalWidthInPixels = // 1056 pixels / line 67 | VgaHSyncFrontPorchInPixels 68 | + VgaHSyncSignalInPixels 69 | + VgaHSyncBackPorchInPixels 70 | + VgaVideoWidth; 71 | 72 | const int VgaSignalHeightInLines = // 628 lines / frame 73 | VgaVSyncFrontPorchInLines 74 | + VgaVSyncSignalInLines 75 | + VgaVSyncBackPorchInLines 76 | + VgaVideoHeight; 77 | 78 | const int VgaTotalScreenAreaInPixels = VgaSignalWidthInPixels * VgaSignalHeightInLines; 79 | const int VgaTotalScreenAreaForVsyncInPixels = VgaSignalWidthInPixels * VgaVSyncSignalInLines; 80 | const double VgaVSyncDuty = (double) VgaTotalScreenAreaForVsyncInPixels / (double) VgaTotalScreenAreaInPixels; 81 | const double VgaHSyncDuty = (double) VgaHSyncSignalInPixels / (double) VgaSignalWidthInPixels; 82 | const double VgaVSyncFrequencyInHz = (double) VgaPixelFrequencyInHz / (double) VgaTotalScreenAreaInPixels; 83 | const double VgaHSyncFrequencyInHz = (double) VgaPixelFrequencyInHz / (double) VgaSignalWidthInPixels; 84 | 85 | const int SpiHSyncBackporchWaitCycle = 58; // H-Sync back porch : 2.20us 86 | 87 | const int RmtItem32Max = 64; 88 | const int RmtItem16Max = RmtItem32Max * 2; 89 | const int RmtDurationMax = 32767; 90 | 91 | } // anonymous namespace 92 | 93 | 94 | class VgaContext { 95 | public: 96 | using ThisClass = VgaContext; 97 | 98 | static void initInitParams(myvga_init_params_t* initParams) { 99 | memset(initParams, 0, sizeof(*initParams)); 100 | auto& ip = *initParams; 101 | ip.video.flags = 0; 102 | ip.video.width = 800; 103 | ip.video.height = 600; 104 | ip.video.strideInBytes = ip.video.width / 8; 105 | ip.video.bufferSizeInBytes = ip.video.strideInBytes * ip.video.height; 106 | ip.video.buffer = nullptr; 107 | } 108 | 109 | static size_t calcMemorySize(const myvga_init_params_t* initParams) { 110 | size_t extraSize = sizeof(ThisClass); // DRAM 111 | extraSize += blankLineBytes; // DRAM, DMA 112 | extraSize += sizeof(lldesc_t) * 2 * VgaSignalHeightInLines; // DRAM, DMA 113 | return extraSize; 114 | } 115 | 116 | esp_err_t cleanup() { 117 | // TODO : Stop SPI DMA 118 | // TODO : Stop RMT Channels 119 | // TODO : Remove RMT ISRs 120 | // TODO : Disable RMT interrupts 121 | return ESP_OK; 122 | } 123 | 124 | esp_err_t init(const myvga_init_params_t* initParams) { 125 | auto* ap = reinterpret_cast(this); 126 | ap += sizeof(ThisClass); 127 | 128 | { 129 | const auto& ip = *initParams; 130 | userVideo.width = ip.video.width; 131 | userVideo.height = ip.video.height; 132 | userVideo.stride = ip.video.strideInBytes; 133 | userVideo.buffer = static_cast(ip.video.buffer); 134 | 135 | spi.host = ip.spi.host; 136 | spi.dmaChan = ip.spi.dmaChan; 137 | spi.mosiGpioNum = ip.spi.mosiGpioNum; 138 | spi.hw = myspi_get_hw_for_host(ip.spi.host); 139 | 140 | rmt.hsyncChannel = ip.rmt.hsyncChannel; 141 | rmt.vsyncChannel = ip.rmt.vsyncChannel; 142 | rmt.hsyncGpioNum = ip.rmt.hsyncGpioNum; 143 | rmt.vsyncGpioNum = ip.rmt.vsyncGpioNum; 144 | } 145 | 146 | vsyncCallback.callback = nullptr; 147 | 148 | blankLine = reinterpret_cast(ap); 149 | ap += blankLineBytes; 150 | { 151 | memset(blankLine, 0, blankLineBytes); 152 | } 153 | 154 | descs = reinterpret_cast(ap); 155 | ap += sizeof(lldesc_t) * 2 * VgaSignalHeightInLines; 156 | { 157 | for(int y = 0; y < VgaSignalHeightInLines; ++y) { 158 | const int videoY = y - (VgaVSyncSignalInLines + VgaVSyncBackPorchInLines); 159 | const bool isVideoEnable = (videoY >= 0 && videoY < userVideo.height); 160 | const bool isLast = (y == VgaSignalHeightInLines - 1); 161 | { 162 | auto* dd = &descs[y * 2 + 0]; 163 | auto* next = dd + 1; 164 | const int dmaChunkLen = userVideo.width / 8; 165 | dd->size = dmaChunkLen; 166 | dd->length = dmaChunkLen; 167 | uint8_t* buf = nullptr; 168 | if(isVideoEnable) { 169 | buf = &userVideo.buffer[userVideo.stride * videoY]; 170 | } 171 | if(nullptr == buf) { 172 | buf = blankLine; 173 | } 174 | dd->buf = buf; 175 | dd->eof = 0; 176 | dd->sosf = 0; 177 | dd->owner = 1; 178 | dd->qe.stqe_next = next; 179 | } 180 | { 181 | auto* dd = &descs[y * 2 + 1]; 182 | auto* next = dd + 1; 183 | if(isLast) { 184 | next = &descs[0]; 185 | } 186 | const int dmaChunkLen = (VgaSignalWidthInPixels - userVideo.width) / 8; 187 | dd->size = dmaChunkLen; 188 | dd->length = dmaChunkLen; 189 | dd->buf = blankLine; 190 | dd->eof = 0; 191 | dd->sosf = 0; 192 | dd->owner = 1; 193 | dd->qe.stqe_next = next; 194 | } 195 | } 196 | } 197 | 198 | // setup RMT for H-Sync 199 | myrmt_setup_pulse_output( 200 | rmt.hsyncChannel 201 | , rmt.hsyncGpioNum 202 | , 1 203 | , 270 204 | , 1 205 | , 1837 206 | , 0 207 | , nullptr 208 | ); 209 | 210 | // setup RMT for V-Sync 211 | { 212 | int32_t nItems = 0; 213 | myrmt_setup_pulse_output( 214 | rmt.vsyncChannel 215 | , rmt.vsyncGpioNum 216 | , 4 217 | , 878 218 | , 1 219 | , 330704 220 | , 0 221 | , &nItems 222 | ); 223 | } 224 | 225 | const double SpiDmaClockSpeedInHz = VgaPixelFrequencyInHz; 226 | 227 | myspi_prepare_circular_buffer( 228 | spi.host 229 | , spi.dmaChan 230 | , descs 231 | , SpiDmaClockSpeedInHz 232 | , spi.mosiGpioNum 233 | , SpiHSyncBackporchWaitCycle 234 | ); 235 | 236 | intr_handle_t my_rmt_isr_handle; 237 | ESP_ERROR_CHECK(esp_intr_alloc(ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_SHARED, rmtIsr, this, &my_rmt_isr_handle)); 238 | 239 | portDISABLE_INTERRUPTS(); 240 | // Reset timers and begin SPI DMA transfer 241 | spi_dev_t* const spiHw = getSpiHw(); 242 | 243 | // Here, we're waiting for completion of RMT TX. When TX is completed, 244 | // RMT channel's internal counter becomes some constant value (maybe 0?). 245 | // Therefore, we can see stable behaviour of RMT channel. 246 | { 247 | auto& hsyncRmtConf1 = RMT.conf_ch[rmt.hsyncChannel].conf1; 248 | auto& vsyncRmtConf1 = RMT.conf_ch[rmt.vsyncChannel].conf1; 249 | 250 | hsyncRmtConf1.tx_conti_mode = 0; 251 | vsyncRmtConf1.tx_conti_mode = 0; 252 | 253 | const uint32_t mask = BIT(rmt.hsyncChannel * 3 + 0) | BIT(rmt.vsyncChannel * 3 + 0); 254 | for(;;) { 255 | const uint32_t int_raw = RMT.int_raw.val; 256 | if((int_raw & mask) == mask) { 257 | break; 258 | } 259 | } 260 | 261 | hsyncRmtConf1.ref_cnt_rst = 1; // RMT_REF_CNT_RST_CH Setting this bit resets the clock divider of channel n. (R/W) 262 | vsyncRmtConf1.ref_cnt_rst = 1; // RMT_REF_CNT_RST_CH 263 | 264 | hsyncRmtConf1.mem_rd_rst = 1; // RMT_MEM_RD_RST_CHn Set this bit to reset the read-RAM address for channel n by accessing the transmitter. (R/W) 265 | vsyncRmtConf1.mem_rd_rst = 1; // RMT_MEM_RD_RST_CHn 266 | } 267 | 268 | 269 | spiHw->dma_conf.dma_tx_stop = 1; // Stop SPI DMA 270 | spiHw->ctrl2.val = 0; // Reset timing 271 | spiHw->dma_conf.dma_tx_stop = 0; // Disable stop 272 | spiHw->dma_conf.dma_continue = 1; // Set contiguous mode 273 | spiHw->dma_out_link.start = 1; // Start SPI DMA transfer (1) 274 | 275 | ESP_ERROR_CHECK(rmt_set_tx_thr_intr_en(rmt.hsyncChannel, true, 1)); 276 | ESP_ERROR_CHECK(rmt_set_tx_thr_intr_en(rmt.vsyncChannel, true, 7)); 277 | 278 | clearIsrCounters(); 279 | 280 | kickPeripherals(spiHw, rmt.hsyncChannel, rmt.vsyncChannel); 281 | portENABLE_INTERRUPTS(); 282 | 283 | return ESP_OK; 284 | } 285 | 286 | static void IRAM_ATTR kickPeripherals( 287 | spi_dev_t* spiHw 288 | , rmt_channel_t hsyncChannel 289 | , rmt_channel_t vsyncChannel 290 | ) { 291 | auto& hsyncRmtConf1 = RMT.conf_ch[hsyncChannel].conf1; 292 | auto& vsyncRmtConf1 = RMT.conf_ch[vsyncChannel].conf1; 293 | 294 | hsyncRmtConf1.tx_conti_mode = 1; // RMT: Set this bit to start sending data on channel n, in contiguous mode. 295 | vsyncRmtConf1.tx_conti_mode = 1; // RMT: Set this bit to start sending data on channel n, in contiguous mode. 296 | spiHw->cmd.usr = 1; // SPI: Start SPI DMA transfer 297 | } 298 | 299 | lldesc_t* getLldescs() { 300 | return descs; 301 | } 302 | 303 | spi_dev_t* getSpiHw() { 304 | return spi.hw; 305 | } 306 | 307 | int32_t getFrameCount32() const { 308 | return static_cast(getFrameCount64()); 309 | } 310 | 311 | int64_t getFrameCount64() const { 312 | return isrCounters.vcounterEvt; 313 | } 314 | 315 | void onVsync() { 316 | isrCounters.vcounterEvt += 1; 317 | if(vsyncCallback.callback) { 318 | vsyncCallback.callback(vsyncCallback.userPtr); 319 | } 320 | } 321 | 322 | static void IRAM_ATTR vsyncIsr(void* p) { 323 | reinterpret_cast(p)->onVsync(); 324 | } 325 | 326 | void onHsync() { 327 | isrCounters.hcounterEvt += 1; 328 | } 329 | 330 | static void IRAM_ATTR hsyncIsr(void* p) { 331 | reinterpret_cast(p)->onHsync(); 332 | } 333 | 334 | static void rmtIsr(void* p) { 335 | reinterpret_cast(p)->onRmtEvent(); 336 | } 337 | 338 | esp_err_t setVsyncCallback(myvga_vsync_callback callback, void* userPtr) { 339 | vsyncCallback.callback = callback; 340 | vsyncCallback.userPtr = userPtr; 341 | return ESP_OK; 342 | } 343 | 344 | void clearIsrCounters() { 345 | isrCounters.vcounterEvt = 0; 346 | isrCounters.hcounterEvt = 0; 347 | } 348 | 349 | void onRmtEvent() { 350 | const uint32_t intr_st = RMT.int_st.val; 351 | 352 | if(intr_st & BIT(rmt.vsyncChannel + 24)) { 353 | onVsync(); 354 | } 355 | 356 | if(intr_st & BIT(rmt.hsyncChannel + 24)) { 357 | onHsync(); 358 | } 359 | } 360 | 361 | struct { 362 | int16_t width; // width in pixels 363 | int16_t height; // heigt in pixels 364 | int16_t stride; // stride in bytes 365 | uint8_t* buffer; // buffer pointer must be aligned to 4 bytes. 366 | } userVideo; 367 | 368 | struct { 369 | spi_host_device_t host; // HSPI_HOST or VSPI_HOST 370 | int dmaChan; // 0, 1 or 2 371 | gpio_num_t mosiGpioNum; // GPIO 372 | spi_dev_t* hw; 373 | } spi; 374 | 375 | struct { 376 | rmt_channel_t hsyncChannel; // [0,7] 377 | rmt_channel_t vsyncChannel; // [0,7] 378 | gpio_num_t hsyncGpioNum; // GPIO 379 | gpio_num_t vsyncGpioNum; // GPIO 380 | } rmt; 381 | 382 | struct { 383 | myvga_vsync_callback callback; 384 | void* userPtr; 385 | } vsyncCallback; 386 | 387 | struct { 388 | volatile int64_t vcounterEvt; 389 | volatile int32_t hcounterEvt; 390 | } isrCounters; 391 | 392 | static const size_t blankLineBytes = VgaSignalWidthInPixels / 8; 393 | static VgaContext* ctx; 394 | 395 | uint8_t* blankLine; 396 | lldesc_t* descs; 397 | }; 398 | 399 | 400 | //////////////////////////////////////////////////////////////////////////////////////////// 401 | VgaContext* VgaContext::ctx; 402 | 403 | #define CTX VgaContext::ctx 404 | 405 | 406 | void myvga_prepare_init_init_params( 407 | myvga_init_params_t* initParams 408 | ) { 409 | return VgaContext::initInitParams(initParams); 410 | } 411 | 412 | size_t myvga_prepare_get_memory_size( 413 | const myvga_init_params_t* initParams 414 | ) { 415 | return VgaContext::calcMemorySize(initParams); 416 | } 417 | 418 | esp_err_t myvga_init( 419 | const myvga_init_params_t* initParams 420 | , void* dedicatedMemoryForMyvga 421 | , size_t dedicatedMemoryForMyvgaInBytes 422 | ) { 423 | CTX = (VgaContext*) dedicatedMemoryForMyvga; 424 | return CTX->init(initParams); 425 | } 426 | 427 | esp_err_t myvga_cleanup(void) { 428 | const auto result = CTX->cleanup(); 429 | CTX = nullptr; 430 | return result; 431 | } 432 | 433 | int32_t myvga_get_frame_count32(void) { 434 | return CTX->getFrameCount32(); 435 | } 436 | 437 | int64_t myvga_get_frame_count64(void) { 438 | return CTX->getFrameCount64(); 439 | } 440 | 441 | esp_err_t myvga_set_vsync_callback_function(myvga_vsync_callback callback, void* user_ptr) { 442 | return CTX->setVsyncCallback(callback, user_ptr); 443 | } 444 | -------------------------------------------------------------------------------- /main/my_vga.h: -------------------------------------------------------------------------------- 1 | #ifndef MY_VGA_H 2 | #define MY_VGA_H 3 | 4 | #include "my_config.h" 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define MY_ESP_ERR_VGA(x) MY_ESP_ERR(MY_ESP_ERR_VGA_BASE, (x)) 12 | 13 | typedef struct myvga_init_params_t myvga_init_params_t; 14 | 15 | struct myvga_init_params_t { 16 | uint32_t flags; 17 | struct myvga_init_params_video_t { 18 | uint16_t flags; 19 | int16_t width; 20 | int16_t height; 21 | uint16_t strideInBytes; 22 | size_t bufferSizeInBytes; 23 | void* buffer; 24 | } video; 25 | struct myvga_init_params_spi_t { 26 | spi_host_device_t host; // HSPI_HOST or VSPI_HOST 27 | int dmaChan; // [0,2] 28 | gpio_num_t mosiGpioNum; // GPIO 29 | } spi; 30 | struct myvga_init_params_rmt_t { 31 | rmt_channel_t hsyncChannel; // [0,7] 32 | rmt_channel_t vsyncChannel; // [0,7] 33 | gpio_num_t hsyncGpioNum; // GPIO 34 | gpio_num_t vsyncGpioNum; // GPIO 35 | } rmt; 36 | }; 37 | 38 | typedef void (*myvga_vsync_callback)(void* user_ptr); 39 | 40 | void myvga_prepare_init_init_params(myvga_init_params_t* initParams); 41 | size_t myvga_prepare_get_memory_size(const myvga_init_params_t* initParams); 42 | 43 | esp_err_t myvga_init(const myvga_init_params_t* initParams, void* dedicatedMemoryForMyvga, size_t dedicatedMemoryForMyvgaInBytes); 44 | //esp_err_t myvga_cleanup(void); 45 | 46 | esp_err_t myvga_set_vsync_callback_function(myvga_vsync_callback callback, void* user_ptr); 47 | 48 | int32_t myvga_get_frame_count32(void); 49 | int64_t myvga_get_frame_count64(void); 50 | 51 | 52 | #ifdef __cplusplus 53 | } // extern "C" 54 | #endif 55 | 56 | #endif 57 | --------------------------------------------------------------------------------