defragster
Senior Member+
Millis is real - it is micros() that is a fictionmillis() has long been a fiction
The Faux interrupt in that as configured it won't wake from asm(" wfi"); that sleeps the MCU until the next interrupt
Millis is real - it is micros() that is a fictionmillis() has long been a fiction
I don't want to dissuade you, but I may be working on something more "modern" than VGA...I'm laying out a prototype board and thought I'd make sure.
06:08:52.641 -> FL2000<0x20004c80>: HDMI VendorID: 4954 DeviceID: 1612
06:08:53.782 -> FL2000<0x20004c80>: ITE chip powered up
06:08:53.782 -> FL2000<0x20004c80>: Monitor is not connected
06:08:53.782 -> FL2000<0x20004c80>: Ignoring MonitorPlugout event
06:08:54.334 -> FL2000<0x20004c80>: Interrupt result: 1
06:08:54.334 -> FL2000<0x20004c80>: Interrupt!
06:08:54.334 -> FL2000<0x20004c80>: Monitor is connected
06:08:54.334 -> FL2000<0x20004c80>: External monitor status: 1
06:08:54.334 -> FL2000<0x20004c80>: EDID status: 0
06:08:57.971 -> EDID DUMP:
06:08:57.971 -> 00 FF FF FF FF FF FF 00 4C 2D 1A 0C 55 46 5A 5A
06:08:57.971 -> 05 1C 01 03 80 34 1D 78 2A 97 91 A5 56 54 9D 25
06:08:57.971 -> 0E 50 54 BF EF 80 71 4F 81 C0 81 00 81 80 95 00
06:08:57.971 -> A9 C0 B3 00 01 01 02 3A 80 18 71 38 2D 40 58 2C
06:08:57.971 -> 45 00 09 25 21 00 00 1E 01 1D 00 72 51 D0 1E 20
06:08:57.971 -> 6E 28 55 00 09 25 21 00 00 1E 00 00 00 FD 00 32
06:08:57.971 -> 4B 1E 51 11 00 0A 20 20 20 20 20 20 00 00 00 FC
06:08:57.971 -> 00 53 32 34 45 33 39 30 0A 20 20 20 20 20 01 C5
06:09:01.638 -> 02 03 1A F1 46 90 04 1F 13 12 03 23 09 07 07 83
06:09:01.638 -> 01 00 00 66 03 0C 00 10 00 80 01 1D 00 BC 52 D0
06:09:01.638 -> 1E 20 B8 28 55 40 09 25 21 00 00 1E 8C 0A D0 90
06:09:01.638 -> 20 40 31 20 0C 40 55 00 09 25 21 00 00 18 8C 0A
06:09:01.638 -> D0 8A 20 E0 2D 10 10 3E 96 00 09 25 21 00 00 18
06:09:01.638 -> 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
06:09:01.638 -> 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
06:09:01.638 -> 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C9
06:09:01.638 -> FL2000<0x20004c80>: 1 EDID extensions found
16bpp is possible at 640x480 but compression is required which can degrade the quality.Fantastic work @jmarsh!!
Will it support 16 or 24 bit color depth as well as similar or smaller resolutions?
Awesome work jmarsh! What about SDRAM at 227MHz for framebuffer, still to slow? It would be pretty wild to take this concept and make a board. With the USB to HDMI "thingy" built onto the board. And use USB Host instead so that the ordinary USB is free for flashing and such.16bpp is possible at 640x480 but compression is required which can degrade the quality.
The limit is the USB bandwidth which maxes out at roughly 30MB/s. 60 frames a second of 640x480 using 16bpp = 60*640*480*2 = ~37MB/s. That's why these adapters are intended to be used with USB3 hosts with higher bandwidth.
Memory is also an issue, a 16bpp framebuffer for 640x480 is ~600KB which is too large for the default Teensy4 memory. EXTMEM/PSRAM cannot be used because it's too slow for USB streaming.
8bpp paletted mode uses 24bpp colors, you're just limited to only using 256 individual colors in each frame.
SDRAM just means you can have spare render buffers besides the active one, it doesn't really enable more resolutions or bitdepths because the USB bandwidth is still the limiting factor.Awesome work jmarsh! What about SDRAM at 227MHz for framebuffer, still to slow? It would be pretty wild to take this concept and make a board. With the USB to HDMI "thingy" built onto the board. And use USB Host instead so that the ordinary USB is free for flashing and such.
Even though it took using all 8 timer registers, it turns out it is possible to use one FlexIO module (FlexIO2 in this case) to generate VSYNC, HSYNC and drive 4 data lines to get 16-color VGA output. All the framebuffer data is fetched using DMA (one channel for regular mode, three channels for line-doubling) leaving the CPU free for other tasks. Mono/1bpp mode is also supported if low memory usage is required, using two shift buffers as state registers to broadcast the single pixel bit to all four output pins.
Code:#include <DMAChannel.h> /* R2R ladder: * * GROUND <------------- 536R ----*---- 270R ---*-----------> VGA PIN: R=1/G=2/B=3 * | | * INTENSITY (13) <---536R -------/ | * | * COLOR: R=11/G=12/B=10 <-----536R------------/ * * VSYNC (34) <---------------68R---------------------------> VGA PIN 14 * * HSYNC (35) <---------------68R---------------------------> VGA PIN 13 */ // horizontal values must be divisible by 8 for correct operation typedef struct { uint32_t height; uint32_t vfp; uint32_t vsw; uint32_t vbp; uint32_t width; uint32_t hfp; uint32_t hsw; uint32_t hbp; uint32_t clk_num; uint32_t clk_den; // sync polarities: 0 = active high, 1 = active low uint32_t vsync_pol; uint32_t hsync_pol; } vga_timing; class FlexIO2VGA { public: FlexIO2VGA(const vga_timing& mode, bool half_height=false, bool half_width=false, unsigned int bpp=4); void stop(void); // wait parameter: // TRUE = wait until previous frame ends and source is "current" // FALSE = queue it as the next framebuffer to draw, return immediately void set_next_buffer(const void* source, size_t pitch, bool wait); void wait_for_frame(void) { unsigned int count = frameCount; while (count == frameCount) yield(); } private: void set_clk(int num, int den); static void ISR(void); void TimerInterrupt(void); uint8_t dma_chans[2]; DMAChannel dma1,dma2,dmaswitcher; DMASetting dma_params; bool double_height; int32_t widthxbpp; volatile unsigned int frameCount; }; FLASHMEM FlexIO2VGA::FlexIO2VGA(const vga_timing& mode, bool half_height, bool half_width, unsigned int bpp) { frameCount = 0; *(portConfigRegister(11)) = 4; // FLEXIO2_D2 RED *(portConfigRegister(12)) = 4; // FLEXIO2_D1 GREEN *(portConfigRegister(10)) = 4; // FLEXIO2_D0 BLUE *(portConfigRegister(13)) = 4; // FLEXIO2_D3 INTENSITY *(portConfigRegister(34)) = 4; // FLEXIO2_D29 VSYNC *(portConfigRegister(35)) = 4; // FLEXIO2_D30 HSYNC dma_chans[0] = dma2.channel; dma_chans[1] = dma1.channel; memset(dma_params.TCD, 0, sizeof(*dma_params.TCD)); dma_params.TCD->DOFF = 4; dma_params.TCD->ATTR = DMA_TCD_ATTR_DMOD(3) | DMA_TCD_ATTR_DSIZE(2); dma_params.TCD->NBYTES = 8; dma_params.TCD->DADDR = &FLEXIO2_SHIFTBUF0; dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_FLEXIO2_REQUEST0); dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_FLEXIO2_REQUEST0); dmaswitcher.TCD->SADDR = dma_chans; dmaswitcher.TCD->SOFF = 1; dmaswitcher.TCD->DADDR = &DMA_SERQ; dmaswitcher.TCD->DOFF = 0; dmaswitcher.TCD->ATTR = DMA_TCD_ATTR_SMOD(1); dmaswitcher.TCD->NBYTES = 1; dmaswitcher.TCD->BITER = dmaswitcher.TCD->CITER = 1; double_height = half_height; widthxbpp = (mode.width * bpp) / (half_width ? 2 : 1); set_clk(4*mode.clk_num, mode.clk_den); FLEXIO2_CTRL = FLEXIO_CTRL_SWRST; asm volatile("dsb"); FLEXIO2_CTRL = FLEXIO_CTRL_FASTACC | FLEXIO_CTRL_FLEXEN; // wait for reset to clear while (FLEXIO2_CTRL & FLEXIO_CTRL_SWRST); // timer 0: divide pixel clock by 8 FLEXIO2_TIMCFG0 = 0; FLEXIO2_TIMCMP0 = (4*8)-1; // timer 1: generate HSYNC FLEXIO2_TIMCFG1 = FLEXIO_TIMCFG_TIMDEC(1); // on = HSW, off = rest of line FLEXIO2_TIMCMP1 = ((((mode.width+mode.hbp+mode.hfp)/8)-1)<<8) | ((mode.hsw/8)-1); // trigger = timer0, HSYNC=D28 FLEXIO2_TIMCTL1 = FLEXIO_TIMCTL_TRGSEL(4*0+3) | FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_PINCFG(3) | FLEXIO_TIMCTL_PINSEL(28) | FLEXIO_TIMCTL_TIMOD(2) | (mode.hsync_pol*FLEXIO_TIMCTL_PINPOL); // timer 2: frame counter // tick on HSYNC FLEXIO2_TIMCFG2 = FLEXIO_TIMCFG_TIMDEC(1); FLEXIO2_TIMCMP2 = ((mode.height+mode.vbp+mode.vfp+mode.vsw)*2)-1; // trigger = HYSNC pin FLEXIO2_TIMCTL2 = FLEXIO_TIMCTL_TRGSEL(2*28) | (mode.hsync_pol * FLEXIO_TIMCTL_TRGPOL) | FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_TIMOD(3); // timer 3: generate VSYNC FLEXIO2_TIMCFG3 = FLEXIO_TIMCFG_TIMDIS(2) | FLEXIO_TIMCFG_TIMENA(7); // active for VSW lines. 4*total horizontal pixels*vertical sync loength must be <= 65536 to not overflow this timer FLEXIO2_TIMCMP3 = (4*mode.vsw*(mode.width+mode.hbp+mode.hsw+mode.hfp))-1; // trigger = frame counter, VSYNC=D29 FLEXIO2_TIMCTL3 = FLEXIO_TIMCTL_TRGSEL(4*2+3) | FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_PINCFG(3) | FLEXIO_TIMCTL_PINSEL(29) | FLEXIO_TIMCTL_TIMOD(3) | (mode.vsync_pol*FLEXIO_TIMCTL_PINPOL); // timer4: count VSYNC and back porch // enable on VSYNC start, disable after (VSW+VBP)*2 edges of HSYNC FLEXIO2_TIMCFG4 = FLEXIO_TIMCFG_TIMDEC(2) | FLEXIO_TIMCFG_TIMDIS(2) | FLEXIO_TIMCFG_TIMENA(6); FLEXIO2_TIMCMP4 = ((mode.vsw+mode.vbp)*2)-1; // trigger = VSYNC pin, pin = HSYNC FLEXIO2_TIMCTL4 = FLEXIO_TIMCTL_TRGSEL(2*29) | FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_PINSEL(28) | FLEXIO_TIMCTL_TIMOD(3) | (mode.vsync_pol*FLEXIO_TIMCTL_TRGPOL) | (mode.hsync_pol*FLEXIO_TIMCTL_PINPOL); // timer 5: vertical active region // enable when previous timer finishes, disable after height*2 edges of HSYNC FLEXIO2_TIMCFG5 = FLEXIO_TIMCFG_TIMDEC(2) | FLEXIO_TIMCFG_TIMDIS(2) | FLEXIO_TIMCFG_TIMENA(6); FLEXIO2_TIMCMP5 = (mode.height*2)-1; // trigger = timer4 negative, pin = HSYNC FLEXIO2_TIMCTL5 = FLEXIO_TIMCTL_TRGSEL(4*4+3) | FLEXIO_TIMCTL_TRGPOL | FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_PINSEL(28) | FLEXIO_TIMCTL_TIMOD(3) | (mode.vsync_pol*FLEXIO_TIMCTL_PINPOL); // timer 6: horizontal active region // configured as PWM: OFF for HSYNC+HBP, ON for active region, reset (to off state) when HSYNC occurs (off state covers HFP then resets) FLEXIO2_TIMCFG6 = FLEXIO_TIMCFG_TIMOUT(1) | FLEXIO_TIMCFG_TIMDEC(1) | FLEXIO_TIMCFG_TIMRST(4) | FLEXIO_TIMCFG_TIMDIS(1) | FLEXIO_TIMCFG_TIMENA(1); FLEXIO2_TIMCMP6 = ((((mode.hsw+mode.hbp)/8)-1)<<8) | ((mode.width/8)-1); // trigger = timer0, pin = HSYNC FLEXIO2_TIMCTL6 = FLEXIO_TIMCTL_TRGSEL(4*0+3) | FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_PINSEL(28) | FLEXIO_TIMCTL_TIMOD(2) | (mode.hsync_pol*FLEXIO_TIMCTL_PINPOL); // timer 7: output pixels from shifter, runs only when trigger is ON FLEXIO2_TIMCFG7 = FLEXIO_TIMCFG_TIMDIS(6) | FLEXIO_TIMCFG_TIMENA(6) | FLEXIO_TIMCFG_TSTOP(2); FLEXIO2_TIMCMP7 = ((((64/bpp)*2)-1)<<8) | ((half_width ? 4:2)-1); // trigger = timer 6 FLEXIO2_TIMCTL7 = FLEXIO_TIMCTL_TRGSEL(4*6+3) | FLEXIO_TIMCTL_TRGSRC | FLEXIO_TIMCTL_TIMOD(1); // start blank FLEXIO2_SHIFTBUF1 = FLEXIO2_SHIFTBUF2 = 0; if (bpp == 4) { FLEXIO2_SHIFTCFG1 = FLEXIO_SHIFTCFG_PWIDTH(3); FLEXIO2_SHIFTCTL1 = FLEXIO_SHIFTCTL_TIMSEL(7) | FLEXIO_SHIFTCTL_SMOD(2); // output stop bit when timer disables - ensures black output/zero outside active window FLEXIO2_SHIFTCFG0 = FLEXIO_SHIFTCFG_PWIDTH(3) | FLEXIO_SHIFTCFG_INSRC | FLEXIO_SHIFTCFG_SSTOP(2); FLEXIO2_SHIFTCTL0 = FLEXIO_SHIFTCTL_TIMSEL(7) | FLEXIO_SHIFTCTL_PINCFG(3) | FLEXIO_SHIFTCTL_PINSEL(0) | FLEXIO_SHIFTCTL_SMOD(2); FLEXIO2_SHIFTSTATE = 0; } else { // bpp==1 FLEXIO2_SHIFTCFG1 = FLEXIO_SHIFTCFG_PWIDTH(0); FLEXIO2_SHIFTCTL1 = FLEXIO_SHIFTCTL_TIMSEL(7) | FLEXIO_SHIFTCTL_SMOD(2); FLEXIO2_SHIFTCFG0 = FLEXIO_SHIFTCFG_PWIDTH(0) | FLEXIO_SHIFTCFG_INSRC | FLEXIO_SHIFTCFG_SSTOP(2); FLEXIO2_SHIFTCTL0 = FLEXIO_SHIFTCTL_TIMSEL(7) | FLEXIO_SHIFTCTL_PINCFG(3) | FLEXIO_SHIFTCTL_PINSEL(8) | FLEXIO_SHIFTCTL_SMOD(2); // D8 clear = use state 2, D8 set = use state 3 // note that PWIDTH does not seem to mask D4-7 outputs as documented! FLEXIO2_SHIFTBUF2 = 0x0069A69A; FLEXIO2_SHIFTCFG2 = FLEXIO_SHIFTCFG_PWIDTH(15); FLEXIO2_SHIFTCTL2 = FLEXIO_SHIFTCTL_TIMSEL(7) | FLEXIO_SHIFTCTL_PINCFG(3) | FLEXIO_SHIFTCTL_PINSEL(8) | FLEXIO_SHIFTCTL_SMOD(6) | FLEXIO_SHIFTCTL_TIMPOL; FLEXIO2_SHIFTBUF3 = 0x0F69A69A; FLEXIO2_SHIFTCFG3 = FLEXIO_SHIFTCFG_PWIDTH(15); FLEXIO2_SHIFTCTL3 = FLEXIO_SHIFTCTL_TIMSEL(7) | FLEXIO_SHIFTCTL_PINCFG(3) | FLEXIO_SHIFTCTL_PINSEL(8) | FLEXIO_SHIFTCTL_SMOD(6) | FLEXIO_SHIFTCTL_TIMPOL; FLEXIO2_SHIFTSTATE = 2; } // clear timer 5 status FLEXIO2_TIMSTAT = 1<<5; // make sure no other FlexIO interrupts are enabled FLEXIO2_SHIFTSIEN = 0; FLEXIO2_SHIFTEIEN = 0; // enable timer 5 interrupt FLEXIO2_TIMIEN = 1<<5; attachInterruptVector(IRQ_FLEXIO2, ISR); NVIC_SET_PRIORITY(IRQ_FLEXIO2, 32); NVIC_ENABLE_IRQ(IRQ_FLEXIO2); // start everything! FLEXIO2_TIMCTL0 = FLEXIO_TIMCTL_TIMOD(3); } FLASHMEM void FlexIO2VGA::stop(void) { NVIC_DISABLE_IRQ(IRQ_FLEXIO2); // FlexIO2 registers don't work if they have no clock if (CCM_CCGR3 & CCM_CCGR3_FLEXIO2(CCM_CCGR_ON)) { FLEXIO2_CTRL &= ~FLEXIO_CTRL_FLEXEN; FLEXIO2_TIMIEN = 0; FLEXIO2_SHIFTSDEN = 0; } dma1.disable(); dma2.disable(); asm volatile("dsb"); } FLASHMEM void FlexIO2VGA::set_clk(int num, int den) { int post_divide = 0; while (num < 27*den) num <<= 1, ++post_divide; int div_select = num / den; num -= div_select * den; // valid range for div_select: 27-54 // switch video PLL to bypass, enable, set div_select CCM_ANALOG_PLL_VIDEO = CCM_ANALOG_PLL_VIDEO_BYPASS | CCM_ANALOG_PLL_VIDEO_ENABLE | CCM_ANALOG_PLL_VIDEO_DIV_SELECT(div_select); // clear misc2 vid post-divider CCM_ANALOG_MISC2_CLR = CCM_ANALOG_MISC2_VIDEO_DIV(3); switch (post_divide) { case 0: // div by 1 CCM_ANALOG_PLL_VIDEO_SET = CCM_ANALOG_PLL_VIDEO_POST_DIV_SELECT(2); break; case 1: // div by 2 CCM_ANALOG_PLL_VIDEO_SET = CCM_ANALOG_PLL_VIDEO_POST_DIV_SELECT(1); break; // div by 4 // case 2: PLL_VIDEO pos_div_select already set to 0 case 3: // div by 8 (4*2) CCM_ANALOG_MISC2_SET = CCM_ANALOG_MISC2_VIDEO_DIV(1); break; case 4: // div by 16 (4*4) CCM_ANALOG_MISC2_SET = CCM_ANALOG_MISC2_VIDEO_DIV(3); break; } CCM_ANALOG_PLL_VIDEO_NUM = num; CCM_ANALOG_PLL_VIDEO_DENOM = den; // ensure PLL is powered CCM_ANALOG_PLL_VIDEO_CLR = CCM_ANALOG_PLL_VIDEO_POWERDOWN; // wait for lock while (!(CCM_ANALOG_PLL_VIDEO & CCM_ANALOG_PLL_VIDEO_LOCK)); // deactivate bypass CCM_ANALOG_PLL_VIDEO_CLR = CCM_ANALOG_PLL_VIDEO_BYPASS; // gate clock CCM_CCGR3 &= ~CCM_CCGR3_FLEXIO2(CCM_CCGR_ON); // FlexIO2 use vid clock (PLL5) uint32_t t = CCM_CSCMR2; t &= ~CCM_CSCMR2_FLEXIO2_CLK_SEL(3); t |= CCM_CSCMR2_FLEXIO2_CLK_SEL(2); CCM_CSCMR2 = t; // flex gets 1:1 clock, no dividing CCM_CS1CDR &= ~(CCM_CS1CDR_FLEXIO2_CLK_PODF(7) | CCM_CS1CDR_FLEXIO2_CLK_PRED(7)); asm volatile("dsb"); // disable clock gate CCM_CCGR3 |= CCM_CCGR3_FLEXIO2(CCM_CCGR_ON); } void FlexIO2VGA::TimerInterrupt(void) { if (dma_params.TCD->SADDR) { dma1 = dma_params; if (double_height) { dma1.disableOnCompletion(); dmaswitcher.triggerAtCompletionOf(dma1); dma2 = dma_params; dma2.disableOnCompletion(); dmaswitcher.triggerAtCompletionOf(dma2); } dma1.enable(); // push first pixels into shiftbuf registers dma1.triggerManual(); FLEXIO2_SHIFTSDEN = 1<<0; } frameCount++; } void FlexIO2VGA::set_next_buffer(const void* source, size_t pitch, bool wait) { // find worst alignment combo of source and pitch size_t log_read; switch (((size_t)source | pitch) & 7) { case 0: // 8 byte alignment log_read = 3; break; case 2: // 2 byte alignment case 6: log_read = 1; break; case 4: // 4 byte alignment log_read = 2; break; default: // 1 byte alignment, this will be slow... log_read = 0; } uint16_t major = (widthxbpp+63)/64; dma_params.TCD->SOFF = 1 << log_read; dma_params.TCD->ATTR_SRC = log_read; dma_params.TCD->SADDR = source; dma_params.TCD->SLAST = pitch - (major*8); dma_params.TCD->CITER = dma_params.TCD->BITER = major; if (wait) wait_for_frame(); } extern FlexIO2VGA FLEXIOVGA; void FlexIO2VGA::ISR(void) { uint32_t timStatus = FLEXIO2_TIMSTAT & 0xFF; FLEXIO2_TIMSTAT = timStatus; if (timStatus & (1<<5)) { FLEXIOVGA.TimerInterrupt(); } asm volatile("dsb"); } /* END VGA driver code */ static void FillFrameBuffer(uint8_t *fb, int height, int width, int bpp, size_t pitch) { const int radius = height/6 - 10; static int xoff = radius; static int yoff = radius; static int xdir = 4; static int ydir = 2; static uint8_t bg = 8; static uint8_t fg = 8; const int limit = radius*radius; bool hit = false; if (xoff >= (int)width-radius) { hit = true; xdir = -xdir; xoff = (int)width-radius; } if (xoff <= radius) { hit = true; xdir = -xdir; xoff = radius; } if (yoff >= (int)height-radius) { hit = true; ydir = -ydir; yoff = (int)height-radius; } if (yoff <= radius) { hit = true; ydir = -ydir; yoff = radius; } if (hit) { if (++fg == 16) fg = 8; if (++bg == 24) bg = 0; } for (int y=0; y < height; y++) { uint8_t *p = fb+y*pitch; for (int x=0; x < width;) { uint8_t c=0; if (bpp == 1) { for (int i=0; i < 8; i++) { int xdiff = x-xoff+i; int ydiff = y-yoff; if ((xdiff*xdiff + ydiff*ydiff) <= limit) c |= 1 << i; } x += 8; } else { // bpp=4 for (int i=1; i >= 0; i--) { c <<= 4; int xdiff = x-xoff+i; int ydiff = y-yoff; c |= ((xdiff*xdiff + ydiff*ydiff) <= limit) ? fg : (bg/3); } x += 2; } *p++ = c; } } xoff += xdir; yoff += ydir; } PROGMEM static const vga_timing t1280x720x60 = { .height=720, .vfp=13, .vsw=5, .vbp=12, .width=1280, .hfp=80, .hsw=40, .hbp=248, .clk_num=7425, .clk_den=2400, .vsync_pol=0, .hsync_pol=0 }; PROGMEM static const vga_timing t1024x768x60 = { .height=768, .vfp=3, .vsw=6, .vbp=29, .width=1024, .hfp=24, .hsw=136, .hbp=160, .clk_num=65, .clk_den=24, .vsync_pol=1, .hsync_pol=1 }; PROGMEM static const vga_timing t800x600x100 = { .height=600, .vfp=1, .vsw=3, .vbp=32, .width=800, .hfp=48, .hsw=88, .hbp=136, .clk_num=6818, .clk_den=2400, .vsync_pol=0, .hsync_pol=1 }; PROGMEM static const vga_timing t800x600x60 = { .height=600, .vfp=1, .vsw=4, .vbp=23, .width=800, .hfp=40, .hsw=128, .hbp=88, .clk_num=40, .clk_den=24, .vsync_pol=0, .hsync_pol=0 }; PROGMEM static const vga_timing t640x480x60 = { .height=480, .vfp=10, .vsw=2, .vbp=33, .width=640, .hfp=16, .hsw=96, .hbp=48, .clk_num=150, .clk_den=143, .vsync_pol=1, .hsync_pol=1 }; PROGMEM static const vga_timing t640x400x70 = { .height=400, .vfp=12, .vsw=2, .vbp=35, .width=640, .hfp=16, .hsw=96, .hbp=48, .clk_num=150, .clk_den=143, .vsync_pol=0, .hsync_pol=1 }; PROGMEM static const vga_timing t640x350x70 = { .height=350, .vfp=37, .vsw=2, .vbp=60, .width=640, .hfp=16, .hsw=96, .hbp=48, .clk_num=150, .clk_den=143, .vsync_pol=1, .hsync_pol=0 }; // memory restrictions #define MAX_WIDTH (1280/2) #define MAX_HEIGHT 720 #define STRIDE_PADDING 16 static uint8_t frameBuffer0[(MAX_HEIGHT+1)*(MAX_WIDTH+STRIDE_PADDING)]; DMAMEM static uint8_t frameBuffer1[(MAX_HEIGHT+1)*(MAX_WIDTH+STRIDE_PADDING)]; static uint8_t* const s_frameBuffer[2] = {frameBuffer0, frameBuffer1}; const vga_timing *timing = &t640x400x70; FlexIO2VGA FLEXIOVGA(*timing); void setup() { Serial.begin(9600); } void loop() { static uint32_t frameBufferIndex; static int double_height = false; static int double_width = false; 静态int bpp = 4; int 高度 = 时间->高度 / (double_height?2:1); int 宽度 = 计时->宽度 / (double_width?2:1); size_t 间距 = 宽度 * bpp/8 + STRIDE_PADDING; int c = 串行.读取(); 如果 (c >= 0) { 开关(c){ 案例‘0’: 时间=&t1280x720x60; Serial.println("新模式:1280x720x60"); 休息; 案例‘1’: 时间=&t1024x768x60; Serial.println("新模式:1024x768x60"); 休息; 案例 ‘2’: 时间 = &t800x600x100; Serial.println("新模式:800x600x100"); 休息; 案例 ‘3’: 时间 = &t800x600x60; Serial.println("新模式:800x600x60"); 休息; 案例 ‘4’: 时间=&t640x480x60; Serial.println("新模式:640x480x60"); 休息; 案例‘5’: 时间=&t640x400x70; Serial.println("新模式:640x400x70"); 休息; 案例 ‘6’: 时间=&t640x350x70; Serial.println("新模式:640x350x70"); 休息; 案例‘h’: 案例‘H’: 双倍高度 = !双倍高度; Serial.print("高度加倍是"); 串行.println(double_height?“开”:“关”); 休息; 案例‘w’: 案例‘W’: 双倍宽度 = !双倍宽度; Serial.print("宽度加倍是"); 串行.println(double_width?“开”:“关”); 休息; 案例‘b’: 案例‘B’: bpp = (bpp==1)?4:1; 串行打印(“使用BPP =”); 串行.println(bpp); 休息; 默认: 串行.println(“0:1280x720x60”); 串行.println(“1:1024x768x60”); 串行.println(“2:800x600x100”); 串行.println(“3:800x600x60”); 串行.println(“4:640x480x60”); 串行.println(“5:640x400x70”); 串行打印(“6:640x350x70”); Serial.println("H:切换高度加倍"); Serial.println("W:切换宽度加倍"); Serial.println("B: 切换位深度(4/1)"); 案例‘\n’: 返回; } FLEXIOVGA.停止(); FLEXIOVGA = FlexIO2VGA(*timing,double_height,double_width,bpp); } FillFrameBuffer(s_frameBuffer[frameBufferIndex], 高度, 宽度, bpp, 间距); // DMAMEM 帧缓冲区必须从缓存中刷新 如果 (帧缓冲区索引) arm_dcache_flush_delete(s_frameBuffer[frameBufferIndex],高度*间距); FLEXIOVGA.set_next_buffer(s_frameBuffer[frameBufferIndex],pitch,true); 帧缓冲区索引^= 1; } [/代码] [/QUOTE] [URL='https://www.zouser.com/']你好,也许这个链接对你有帮助[/URL]