diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c1a20c..6266525 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,8 @@ "*.md": "markdown", "xtr1common": "c", "stdlib.h": "c", - "multicore.h": "c" + "multicore.h": "c", + "stdio.h": "c", + "tms9918.pio.h": "c" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5711eb3..cba7afb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,11 @@ set(PROJECT pico9918) project(${PROJECT} C CXX) -add_definitions(-DPICO_BUILD) +add_definitions(-DPICO_BUILD=1) +add_definitions(-DPICO_DISABLE_SHARED_IRQ_HANDLERS=1) +add_definitions(-DVR_EMU_TMS9918_SINGLE_INSTANCE=1) +add_definitions(-DPICO_PANIC_FUNCTION=) +add_definitions(-DPICO_TIME_DEFAULT_ALARM_POOL_DISABLED=1) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3bf9a56..49b3a20 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,19 +4,20 @@ set(PROGRAM pico9918) add_executable(${PROGRAM}) -target_sources(${PROGRAM} PRIVATE main.c palette.c clocks.pio.h) +target_sources(${PROGRAM} PRIVATE main.c palette.c clocks.pio.h tms9918.pio.h) # generate image array source files from png images visrealm_generate_image_source_ram(${PROGRAM} splash res/splash.png ) # generate header file from pio pico_generate_pio_header(${PROGRAM} ${CMAKE_CURRENT_LIST_DIR}/clocks.pio) +pico_generate_pio_header(${PROGRAM} ${CMAKE_CURRENT_LIST_DIR}/tms9918.pio) pico_add_extra_outputs(${PROGRAM}) pico_enable_stdio_usb(${PROGRAM} 0) pico_enable_stdio_uart(${PROGRAM} 0) -#pico_set_binary_type(${PROGRAM} copy_to_ram) # TOO SLOW TO BOOT +pico_set_binary_type(${PROGRAM} copy_to_ram) # TOO SLOW TO BOOT target_link_libraries(${PROGRAM} PUBLIC pico_stdlib diff --git a/src/clocks.pio b/src/clocks.pio index 4eb31d8..085fc71 100644 --- a/src/clocks.pio +++ b/src/clocks.pio @@ -10,26 +10,7 @@ */ .program clock - pull block .wrap_target set pins, 1 - mov x, osr -onDelay: - jmp x-- onDelay set pins, 0 - mov x, osr -offDelay: - jmp x-- offDelay .wrap - - -% c-sdk { - -void clock_program_init(PIO pio, uint sm, uint offset, uint pin) { - pio_gpio_init(pio, pin); - pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); - pio_sm_config c = clock_program_get_default_config(offset); - sm_config_set_set_pins(&c, pin, 1); - pio_sm_init(pio, sm, offset, &c); -} -%} diff --git a/src/main.c b/src/main.c index 6521595..3e1a6c2 100644 --- a/src/main.c +++ b/src/main.c @@ -13,11 +13,13 @@ #include "vga-modes.h" #include "clocks.pio.h" +#include "tms9918.pio.h" #include "palette.h" #include "splash.h" +#include "impl/vrEmuTms9918Priv.h" #include "vrEmuTms9918Util.h" #include "pico/stdlib.h" @@ -25,11 +27,10 @@ #include "hardware/pio.h" #include "hardware/clocks.h" - -#include +#include "hardware/vreg.h" /* - * Pin mapping + * Pin mapping (PCB v0.3) * * Pin | GPIO | Name | TMS9918A Pin * -----+------+-----------+------------- @@ -53,7 +54,7 @@ * a genuine Raspberry Pi Pico can't be used. * v0.3 of the PCB is designed for the DWEII? * RP2040 USB-C module which exposes these additional - * GPIOs. A future pico9918 revision will do without + * GPIOs. A future pico9918 revision (v0.4+) will do without * an external RP2040 board and use the RP2040 directly. * * Purchase links: @@ -61,150 +62,136 @@ * https://www.aliexpress.com/item/1005007066733934.html */ +#define PCB_MAJOR_VERSION 0 +#define PCB_MINOR_VERSION 3 + #define GPIO_CD0 14 -#define GPIO_CSR 26 -#define GPIO_CSW 27 +#define GPIO_CSR tmsRead_CSR_PIN // defined in tms9918.pio +#define GPIO_CSW tmsWrite_CSW_PIN // defined in tms9918.pio #define GPIO_MODE 28 #define GPIO_INT 22 + +#if PCB_MAJOR_VERSION != 0 +#error "Time traveller?" +#endif + + // pin-mapping for gromclk and cpuclk changed in PCB v0.4 + // in order to have MODE and MODE1 sequential +#if PCB_MINOR_VERSION < 4 #define GPIO_GROMCL 29 #define GPIO_CPUCL 23 -#define GPIO_LED 25 +#else +#define GPIO_GROMCL 25 +#define GPIO_CPUCL 24 +#define GPIO_RESET 23 +#define GPIO_MODE1 29 +#endif + #define GPIO_CD_MASK (0xff << GPIO_CD0) #define GPIO_CSR_MASK (0x01 << GPIO_CSR) #define GPIO_CSW_MASK (0x01 << GPIO_CSW) #define GPIO_MODE_MASK (0x01 << GPIO_MODE) #define GPIO_INT_MASK (0x01 << GPIO_INT) -#define GPIO_LED_MASK (0x01 << GPIO_LED) #define TMS_CRYSTAL_FREQ_HZ 10738635.0f -#define GPIO_CD_REVERSED 0 /* unset for v0.3 PCB */ -#define LED_BLINK_ON_WRITE 0 - +#define PICO_CLOCK_PLL 1260000000 +#define PICO_CLOCK_PLL_DIV1 5 +#define PICO_CLOCK_PLL_DIV2 1 +#define PICO_CLOCK_HZ (PICO_CLOCK_PLL / PICO_CLOCK_PLL_DIV1 / PICO_CLOCK_PLL_DIV2) -#if GPIO_CD_REVERSED +#define TMS_PIO pio1 +#define TMS_IRQ PIO1_IRQ_0 - /* In revision 0.2 of my prototype PCB, CD0 through CD7 are inconveniently reversed into the - Pi Pico GPIO pins. Quickest way to deal with that is this lookup table. - v0.3+ of the pico9918 hardware will have this fix in hardware, so will be removed soon - */ - -static uint8_t __aligned(4) reversed[] = -{ - 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, - 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, - 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, - 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, - 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, - 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, - 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, - 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, - 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, - 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, - 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, - 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, - 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, - 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, - 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, - 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF -}; -#define REVERSE(x) reversed[x] -#else -#define REVERSE(x) x -#endif /* file globals */ -static VrEmuTms9918* tms = NULL; /* our vrEmuTms9918 instance handle */ -static uint32_t nextValue = 0; /* TMS9918A read-ahead value */ -static uint32_t currentInt = GPIO_INT_MASK; /* current interrupt pin state */ +static uint8_t nextValue = 0; /* TMS9918A read-ahead value */ +static bool currentInt = false; /* current interrupt state */ +static uint8_t currentStatus = 0; /* current status register value */ static uint8_t __aligned(4) tmsScanlineBuffer[TMS9918_PIXELS_X]; +const uint tmsWriteSm = 0; +const uint tmsReadSm = 1; + /* - * RP2040 exclusive GPIO interrupt callback for PROC1 - * Called whenever CSR or CSW changes and reads or writes the data - * - * Note: This needs to be extremely responsive. No dilly-dallying here. + * update the value send to the read PIO */ -void __time_critical_func(gpioExclusiveCallbackProc1)() +inline static void updateTmsReadAhead() { - uint32_t gpios = sio_hw->gpio_in; + uint32_t readAhead = 0xff; // pin direction + readAhead |= nextValue << 8; + readAhead |= currentStatus << 16; + pio_sm_put(TMS_PIO, tmsReadSm, readAhead); +} - /* interrupt handled */ - iobank0_hw->intr[GPIO_CSR >> 3u] = iobank0_hw->proc1_irq_ctrl.ints[GPIO_CSR >> 3u]; +/* + * handle interrupts from the TMS9918<->CPU interface + */ +void __not_in_flash_func(pio_irq_handler)() +{ - if ((gpios & GPIO_CSR_MASK) == 0) /* read? */ + if ((TMS_PIO->fstat & (1u << (PIO_FSTAT_RXEMPTY_LSB + tmsWriteSm))) == 0) // write? { - sio_hw->gpio_oe_set = GPIO_CD_MASK; + uint32_t writeVal = TMS_PIO->rxf[tmsWriteSm]; - if (gpios & GPIO_MODE_MASK) /* read status register */ + if (writeVal & (GPIO_MODE_MASK >> GPIO_CD0)) // write reg/addr { - sio_hw->gpio_out = ((uint32_t)REVERSE(vrEmuTms9918ReadStatus(tms)) << GPIO_CD0) | currentInt; - currentInt = GPIO_INT_MASK; + vrEmuTms9918WriteAddrImpl(writeVal & 0xff); + currentInt = vrEmuTms9918InterruptStatusImpl(); + gpio_put(GPIO_INT, !currentInt); } - else /* read data */ + else // write data { - sio_hw->gpio_out = nextValue | currentInt; - vrEmuTms9918ReadData(tms); + vrEmuTms9918WriteDataImpl(writeVal & 0xff); } + + nextValue = vrEmuTms9918ReadDataNoIncImpl(); + updateTmsReadAhead(); } - else if ((gpios & GPIO_CSW_MASK) == 0) /* write? */ + else if ((TMS_PIO->fstat & (1u << (PIO_FSTAT_RXEMPTY_LSB + tmsReadSm))) == 0) // read? { - uint8_t value = REVERSE((sio_hw->gpio_in >> GPIO_CD0) & 0xff); + uint32_t readVal = TMS_PIO->rxf[tmsReadSm]; - if (gpios & GPIO_MODE_MASK) /* write register/address */ + if ((readVal & 0x04) == 0) // read data { - vrEmuTms9918WriteAddr(tms, value); - - currentInt = vrEmuTms9918InterruptStatus(tms) ? 0 : GPIO_INT_MASK; - sio_hw->gpio_out = nextValue | currentInt; + vrEmuTms9918ReadDataImpl(); + nextValue = vrEmuTms9918ReadDataNoIncImpl(); } - else /* write data */ + else // read status { - vrEmuTms9918WriteData(tms, value); - -#if LED_BLINK_ON_WRITE - sio_hw->gpio_out = GPIO_LED_MASK | currentInt; -#endif + currentStatus = 0; + vrEmuTms9918SetStatusImpl(currentStatus); + currentInt = false; + gpio_put(GPIO_INT, !currentInt); } + updateTmsReadAhead(); } - else /* both CSR and CSW are high (inactive). Go High-Z */ - { - sio_hw->gpio_oe_clr = GPIO_CD_MASK; - sio_hw->gpio_out = currentInt; - } - - /* update read-ahead */ - nextValue = REVERSE(vrEmuTms9918ReadDataNoInc(tms)) << GPIO_CD0; } + /* - * 2nd CPU core (proc1) entry + * enable gpio interrupts inline */ -void proc1Entry() +static inline void enableTmsPioInterrupts() { - // set up gpio pins - gpio_init_mask(GPIO_CD_MASK | GPIO_CSR_MASK | GPIO_CSW_MASK | GPIO_MODE_MASK | GPIO_INT_MASK | GPIO_LED_MASK); - gpio_put_all(GPIO_INT_MASK); - gpio_set_dir_all_bits(GPIO_INT_MASK | GPIO_LED_MASK); // int is an output - - // ensure CSR and CSW are high (inactive) - while (!gpio_get(GPIO_CSW) || !gpio_get(GPIO_CSR)) - tight_loop_contents(); - - // set up gpio interrupts - irq_set_exclusive_handler(IO_IRQ_BANK0, gpioExclusiveCallbackProc1); - gpio_set_irq_enabled(GPIO_CSW, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true); - gpio_set_irq_enabled(GPIO_CSR, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true); - irq_set_enabled(IO_IRQ_BANK0, true); + __dmb(); + *((io_rw_32*)(PPB_BASE + M0PLUS_NVIC_ICPR_OFFSET)) = 1u << TMS_IRQ; + *((io_rw_32*)(PPB_BASE + M0PLUS_NVIC_ISER_OFFSET)) = 1u << TMS_IRQ; +} - // wait until everything else is ready, then run the vga loop - multicore_fifo_pop_blocking(); - vgaLoop(); +/* + * disable gpio interrupts inline + */ +static inline void disableTmsPioInterrupts() +{ + *((io_rw_32*)(PPB_BASE + M0PLUS_NVIC_ICER_OFFSET)) = 1u << TMS_IRQ; + __dmb(); } + /* * generate a single VGA scanline (called by vgaLoop(), runs on proc1) */ @@ -212,6 +199,7 @@ static void __time_critical_func(tmsScanline)(uint16_t y, VgaParams* params, uin { #if 1 + // better compile-time optimizations if we hard-code these #define VIRTUAL_PIXELS_X 640 #define VIRTUAL_PIXELS_Y 240 #else @@ -223,7 +211,7 @@ static void __time_critical_func(tmsScanline)(uint16_t y, VgaParams* params, uin const uint32_t vBorder = (VIRTUAL_PIXELS_Y - TMS9918_PIXELS_Y) / 2; const uint32_t hBorder = (VIRTUAL_PIXELS_X - TMS9918_PIXELS_X * 2) / 2; - uint16_t bg = tms9918PaletteBGR12[vrEmuTms9918RegValue(tms, TMS_REG_FG_BG_COLOR) & 0x0f]; + uint16_t bg = tms9918PaletteBGR12[vrEmuTms9918RegValue(TMS_REG_FG_BG_COLOR) & 0x0f]; /*** top and bottom borders ***/ if (y < vBorder || y >= (vBorder + TMS9918_PIXELS_Y)) @@ -233,29 +221,32 @@ static void __time_critical_func(tmsScanline)(uint16_t y, VgaParams* params, uin pixels[x] = bg; } - /* source: C:/Users/troy/OneDrive/Documents/projects/pico9918/src/res/splash.png * size : 172px x 10px - * : 3440 bytes - * format: 16bpp abgr image + * : 430 bytes + * format: 16-bit abgr palette, 2bpp indexed image */ if (y >= vBorder + TMS9918_PIXELS_Y + 12) { y -= vBorder + TMS9918_PIXELS_Y + 12; - if (y < 10) + if (y < splashHeight) { - uint16_t* splashPtr = splash + (y * 172); - for (int x = 4; x < 4 + 172; ++x) + uint8_t* splashPtr = splash + (y * splashWidth / 4); + for (int x = 4; x < 4 + splashWidth; x += 4) { - uint16_t c = *(splashPtr++); - if (c & 0xf000) - { - pixels[x] = c; - } + uint8_t c = *(splashPtr++); + uint8_t p0 = (c & 0xc0); + uint8_t p1 = (c & 0x30); + uint8_t p2 = (c & 0x0c); + uint8_t p3 = (c & 0x03); + + if (p0) { pixels[x] = splash_pal[(p0 >> 6)]; } + if (p1) { pixels[x + 1] = splash_pal[(p1 >> 4)]; } + if (p2) { pixels[x + 2] = splash_pal[(p2 >> 2)]; } + if (p3) { pixels[x + 3] = splash_pal[p3]; } } } } - return; } @@ -268,18 +259,17 @@ static void __time_critical_func(tmsScanline)(uint16_t y, VgaParams* params, uin } /*** main display region ***/ - irq_set_mask_enabled(1u << IO_IRQ_BANK0, false); - uint8_t status = vrEmuTms9918PeekStatus(tms); - irq_set_mask_enabled(1u << IO_IRQ_BANK0, true); /* generate the scanline */ - status = vrEmuTms9918ScanLine(tms, y, tmsScanlineBuffer, status); + uint8_t tempStatus = vrEmuTms9918ScanLine(y, tmsScanlineBuffer); - irq_set_mask_enabled(1u << IO_IRQ_BANK0, false); - vrEmuTms9918SetStatus(tms, (status & 0x7f) | (currentInt ? 0 : 0x80)); - irq_set_mask_enabled(1u << IO_IRQ_BANK0, true); + /*** interrupt signal? ***/ + if (y == TMS9918_PIXELS_Y - 1) + { + tempStatus |= STATUS_INT; + } - /* convert from tms palette to bgr12 */ + /* convert from palette to bgr12 */ int tmsX = 0; if (tmsScanlineBuffer[0] & 0xf0) { @@ -293,7 +283,7 @@ static void __time_critical_func(tmsScanline)(uint16_t y, VgaParams* params, uin { for (int x = hBorder; x < hBorder + TMS9918_PIXELS_X * 2; x += 2, ++tmsX) { - pixels[x] = tms9918PaletteBGR12[tmsScanlineBuffer[tmsX] & 0x0f]; + pixels[x] = tms9918PaletteBGR12[tmsScanlineBuffer[tmsX]]; pixels[x + 1] = pixels[x]; } } @@ -304,15 +294,25 @@ static void __time_critical_func(tmsScanline)(uint16_t y, VgaParams* params, uin pixels[x] = bg; } - /*** interrupt signal? ***/ - if (y == TMS9918_PIXELS_Y - 1) + disableTmsPioInterrupts(); + if ((currentStatus & STATUS_INT) == 0) { - irq_set_mask_enabled(1u << IO_IRQ_BANK0, false); - vrEmuTms9918InterruptSet(tms); - currentInt = vrEmuTms9918InterruptStatus(tms) ? 0 : GPIO_INT_MASK; - gpio_put(GPIO_INT, !!currentInt); - irq_set_mask_enabled(1u << IO_IRQ_BANK0, true); + if ((currentStatus & STATUS_5S) != 0) + { + currentStatus |= tempStatus & 0xe0; + } + else + { + currentStatus |= tempStatus; + } + + vrEmuTms9918SetStatusImpl(currentStatus); + updateTmsReadAhead(); + + currentInt = vrEmuTms9918InterruptStatusImpl(); + gpio_put(GPIO_INT, !currentInt); } + enableTmsPioInterrupts(); } /* @@ -324,22 +324,89 @@ uint initClock(uint gpio, float freqHz) if (clocksPioOffset == -1) { - clocksPioOffset = pio_add_program(pio1, &clock_program); + clocksPioOffset = pio_add_program(pio0, &clock_program); + } + + static uint clkSm = 2; + + pio_gpio_init(pio0, gpio); + pio_sm_set_consecutive_pindirs(pio0, clkSm, gpio, 1, true); + pio_sm_config c = clock_program_get_default_config(clocksPioOffset); + sm_config_set_set_pins(&c, gpio, 1); + + pio_sm_init(pio0, clkSm, clocksPioOffset, &c); + + float clockDiv = (float)PICO_CLOCK_HZ / (freqHz * 2.0f); + pio_sm_set_clkdiv(pio0, clkSm, clockDiv); + pio_sm_set_enabled(pio0, clkSm, true); + + return clkSm++; +} + +/* + * Set up PIOs for TMS9918 <-> CPU interface + */ +void tmsPioInit() +{ + uint tmsWriteProgram = pio_add_program(TMS_PIO, &tmsWrite_program); + + pio_sm_config writeConfig = tmsWrite_program_get_default_config(tmsWriteProgram); + sm_config_set_in_pins(&writeConfig, GPIO_CD0); + sm_config_set_in_shift(&writeConfig, false, true, 16); // L shift, autopush @ 16 bits + sm_config_set_clkdiv(&writeConfig, 4.0f); + + pio_sm_init(TMS_PIO, tmsWriteSm, tmsWriteProgram, &writeConfig); + pio_sm_set_enabled(TMS_PIO, tmsWriteSm, true); + + uint tmsReadProgram = pio_add_program(TMS_PIO, &tmsRead_program); + + for (uint i = 0; i < 8; ++i) + { + pio_gpio_init(TMS_PIO, GPIO_CD0 + i); } - uint clkSm = pio_claim_unused_sm(pio1, true); - clock_program_init(pio1, clkSm, clocksPioOffset, gpio); + pio_sm_config readConfig = tmsRead_program_get_default_config(tmsReadProgram); + sm_config_set_in_pins(&readConfig, GPIO_CSR); + sm_config_set_jmp_pin(&readConfig, GPIO_MODE); + sm_config_set_out_pins(&readConfig, GPIO_CD0, 8); + sm_config_set_in_shift(&readConfig, false, false, 32); // L shift + sm_config_set_out_shift(&readConfig, true, false, 32); // R shift + sm_config_set_clkdiv(&readConfig, 4.0f); + + pio_sm_init(TMS_PIO, tmsReadSm, tmsReadProgram, &readConfig); + pio_sm_set_enabled(TMS_PIO, tmsReadSm, true); + irq_set_exclusive_handler(TMS_IRQ, pio_irq_handler); + irq_set_enabled(TMS_IRQ, true); + pio_set_irq0_source_enabled(TMS_PIO, pis_sm0_rx_fifo_not_empty, true); + pio_set_irq0_source_enabled(TMS_PIO, pis_sm1_rx_fifo_not_empty, true); + + pio_sm_put(TMS_PIO, tmsReadSm, 0x000000ff); +} - float clockDiv = (float)clock_get_hz(clk_sys) / (TMS_CRYSTAL_FREQ_HZ * 10.0f); - pio_sm_set_clkdiv(pio1, clkSm, clockDiv); - pio_sm_set_enabled(pio1, clkSm, true); +/* + * 2nd CPU core (proc1) entry + */ +void proc1Entry() +{ + // set up gpio pins + gpio_init_mask(GPIO_CD_MASK | GPIO_CSR_MASK | GPIO_CSW_MASK | GPIO_MODE_MASK | GPIO_INT_MASK); + gpio_put_all(GPIO_INT_MASK); + gpio_set_dir_all_bits(GPIO_INT_MASK); // int is an output - pio_sm_put(pio1, clkSm, (uint)(clock_get_hz(clk_sys) / clockDiv / (2.0f * freqHz)) - 3.0f); + tmsPioInit(); - return clkSm; + // set up the GROMCLK and CPUCLK signals + initClock(GPIO_GROMCL, TMS_CRYSTAL_FREQ_HZ / 24.0f); + initClock(GPIO_CPUCL, TMS_CRYSTAL_FREQ_HZ / 3.0f); + + // wait until everything else is ready, then run the vga loop + multicore_fifo_pop_blocking(); + vgaLoop(); } + + /* * main entry point */ @@ -349,18 +416,15 @@ int main(void) * that comes close to being divisible by 25.175MHz. 252.0 is close... enough :) * I do have code which sets the best clock baased on the chosen VGA mode, * but this'll do for now. */ - set_sys_clock_khz(252000, false); + + set_sys_clock_pll(PICO_CLOCK_PLL, PICO_CLOCK_PLL_DIV1, PICO_CLOCK_PLL_DIV2); // 252000 /* we need one of these. it's the main guy */ - tms = vrEmuTms9918New(); + vrEmuTms9918Init(); - /* set up the GPIO pins and interrupt handler */ + /* launch core 1 which handles TMS9918<->CPU and rendering scanlines */ multicore_launch_core1(proc1Entry); - /* set up the GROMCLK and CPUCLK signals */ - initClock(GPIO_GROMCL, TMS_CRYSTAL_FREQ_HZ / 24.0f); - initClock(GPIO_CPUCL, TMS_CRYSTAL_FREQ_HZ / 3.0f); - /* then set up VGA output */ VgaInitParams params = { 0 }; params.params = vgaGetParams(VGA_640_480_60HZ); @@ -384,4 +448,4 @@ int main(void) } return 0; -} \ No newline at end of file +} diff --git a/src/pio-utils/pio_utils.c b/src/pio-utils/pio_utils.c index 998d69c..c0b175b 100644 --- a/src/pio-utils/pio_utils.c +++ b/src/pio-utils/pio_utils.c @@ -41,14 +41,6 @@ static void pio_set_xy(PIO pio, uint sm, uint32_t val, enum pio_src_dest dest) pio_sm_exec(pio, sm, pio_encode_mov(dest, pio_isr)); } -/* - * set the pio state machine x register - */ -void pio_set_x(PIO pio, uint sm, uint32_t x) -{ - pio_set_xy(pio, sm, x, pio_x); -} - /* * set the pio state machine y register */ diff --git a/src/pio-utils/pio_utils.h b/src/pio-utils/pio_utils.h index 65f730c..62cae09 100644 --- a/src/pio-utils/pio_utils.h +++ b/src/pio-utils/pio_utils.h @@ -13,5 +13,4 @@ #include "hardware/pio.h" -void pio_set_x(PIO pio, uint sm, uint32_t y); void pio_set_y(PIO pio, uint sm, uint32_t y); \ No newline at end of file diff --git a/src/res/splash.png b/src/res/splash.png index b22e199..0c65d0b 100644 Binary files a/src/res/splash.png and b/src/res/splash.png differ diff --git a/src/tms9918.pio b/src/tms9918.pio new file mode 100644 index 0000000..93aa09c --- /dev/null +++ b/src/tms9918.pio @@ -0,0 +1,78 @@ +/* + * Project: pico9918 + * + * Copyright (c) 2024 Troy Schrapel + * + * This code is licensed under the MIT license + * + * https://github.com/visrealm/pico9918 + * + */ + +; ----------------------------------------------------------------------------- +; tmsRead - monitor the CSR pin and send either status or data value +; +; due to the read-ahead nature of the TMS9918, we have the +; possible read values already. Each tx FIFO word contains +; the current values for status, read data and pin direction +; +; fifo osr 0b|xxxxxxxx|ssssssss|dddddddd|11111111| +; | ignore | status | data | pindir | +; +; fifo isr 0b|m|w|r| +; |o|r|e| +; |d|i|a| +; |e|t|d| +; | |e| | +; + +.program tmsRead +.define public CSR_PIN 26 + + pull block + mov x, osr ; ensure we have a valid fifio value in x + +.wrap_target + wait 1 gpio CSR_PIN ; wait for CSR to go high (inactive) + mov osr, null ; change CD0-7 pindirs to inputs + out pindirs, 8 + +pullLoop: + pull noblock ; continuously empty the tx fifo + mov x, osr ; since we want the latest value + mov isr, null + in pins, 1 ; hacky way to jmp based on the CSR pin + mov y, isr ; since we can only have one jmp pin + jmp y--, pullLoop ; still 1? loop, otherwise, let's read + + out pindirs, 8 ; set up CD0-7 as outputs + jmp pin readStatus ; if 'mode' is high, read status bits +readData: + out pins, 8 ; mode is low, send the data byte + jmp endPart +readStatus: + out null, 8 ; skip data bits + out pins, 8 ; output the status byte +endPart: + in pins, 3 ; push CSR, CSW and MODE back through fifo + push ; push ^^^ back to cpu to process +.wrap + +; ----------------------------------------------------------------------------- +; tmsWrite - monitor the CSW pin and pass on pin state via FIFO +; +; very simple grab the data and send it through... +; +; fifo isr 0b|x|m|w|r|xxxx|dddddddd| +; | |o|r|e| | CD0-7 | +; | |d|i|a| | | +; | |e|t|d| | | +; | | |e| | | | + +.program tmsWrite +.define public CSW_PIN 27 + + wait 0 gpio CSW_PIN [12] ; wait for CSW to go active (low) + in pins, 16 ; grab the data CD0-7 and auto-push + wait 1 gpio CSW_PIN [12] ; wait for CSW high (inactive) +.wrap diff --git a/src/vga/vga-modes.c b/src/vga/vga-modes.c index 4764516..16aa118 100644 --- a/src/vga/vga-modes.c +++ b/src/vga/vga-modes.c @@ -135,7 +135,7 @@ bool setVgaParamsScaleX(VgaParams* params, int pixelScale) if (!params || pixelScale < 1) return false; params->hPixelScale = pixelScale; - params->hVirtualPixels = (params->hSyncParams.displayPixels / params->hPixelScale); + params->hVirtualPixels = (params->hSyncParams.displayPixels / 1);//params->hPixelScale); return true; } @@ -144,7 +144,7 @@ bool setVgaParamsScaleY(VgaParams* params, int pixelScale) if (!params || pixelScale < 1) return false; params->vPixelScale = pixelScale; - params->vVirtualPixels = (params->vSyncParams.displayPixels / params->vPixelScale); + params->vVirtualPixels = (params->vSyncParams.displayPixels / 2);//params->vPixelScale); return true; } diff --git a/src/vga/vga.c b/src/vga/vga.c index 627fbb2..f427788 100644 --- a/src/vga/vga.c +++ b/src/vga/vga.c @@ -13,17 +13,41 @@ #include "vga.pio.h" #include "pio_utils.h" -#include "pico/divider.h" #include "pico/multicore.h" #include "hardware/dma.h" #include "hardware/pio.h" #include "hardware/clocks.h" -#include -#include -#include -#include + // compile options +#define VGA_CRT_EFFECT 1 +#define VGA_SCANLINE_TIME_DEBUG 0 +#define VGA_HARDCODED_640 1 +#define VGA_NO_MALLOC 1 +#define VGA_COMBINE_SYNC 0 + + + // a number of compiile-time optimisations can occur + // if it knows some of the VGA parameters +#if VGA_HARDCODED_640 +#define VIRTUAL_PIXELS_X 640 +#define VIRTUAL_PIXELS_Y 240 +#else +#include "pico/divider.h" +#define VIRTUAL_PIXELS_X vgaParams.params.hVirtualPixels +#define VIRTUAL_PIXELS_Y vgaParams.params.vVirtualPixels +#endif + + +// avoid bringing in math.h +int roundflt(float x) +{ + if (x < 0.0f) + return (int)(x - 0.5f); + else + return (int)(x + 0.5f); +} + #define SYNC_PINS_START 0 // first sync pin gpio number #define SYNC_PINS_COUNT 2 // number of sync pins (h and v) @@ -38,18 +62,21 @@ #define END_OF_SCANLINE_MSG 0x40000000 #define END_OF_FRAME_MSG 0x80000000 -#define CRT_EFFECT 0 -#define SCANLINE_TIME_DEBUG 0 - - /* - * sync pio dma data buffers - */ +/* + * sync pio dma data buffers + */ uint32_t __aligned(4) syncDataActive[4]; // active display area uint32_t __aligned(4) syncDataPorch[4]; // vertical porch uint32_t __aligned(4) syncDataSync[4]; // vertical sync -uint16_t* __aligned(4) rgbDataBuffer[2 + SCANLINE_TIME_DEBUG] = { 0 }; // two scanline buffers (odd and even) +#if VGA_NO_MALLOC +uint16_t __aligned(4) rgbDataBuffer[2 + VGA_SCANLINE_TIME_DEBUG][640 * sizeof(uint16_t)] = { 0 }; // two scanline buffers (odd and even) +#else +#include +uint16_t* __aligned(4) rgbDataBuffer[2 + VGA_SCANLINE_TIME_DEBUG] = { 0 }; // two scanline buffers (odd and even) +#endif + /* * file scope @@ -60,7 +87,7 @@ static uint syncDmaChanMask = 0; static uint rgbDmaChanMask = 0; static VgaInitParams vgaParams = { 0 }; -#if SCANLINE_TIME_DEBUG +#if VGA_SCANLINE_TIME_DEBUG bool hasRenderedNext = false; #endif @@ -89,32 +116,35 @@ static bool buildSyncData() if (sysClockKHz < minClockKHz) { - printf("Error: System clock is %d KHz. Minimum required is %d KHz\n", sysClockKHz, minClockKHz); return false; } - rgbDataBuffer[0] = malloc(vgaParams.params.hVirtualPixels * sizeof(uint16_t)); - rgbDataBuffer[1] = malloc(vgaParams.params.hVirtualPixels * sizeof(uint16_t)); +#if !VGA_NO_MALLOC + rgbDataBuffer[0] = malloc(VIRTUAL_PIXELS_X * sizeof(uint16_t)); + rgbDataBuffer[1] = malloc(VIRTUAL_PIXELS_X * sizeof(uint16_t)); +#endif -#if SCANLINE_TIME_DEBUG - rgbDataBuffer[2] = malloc(vgaParams.params.hVirtualPixels * sizeof(uint16_t)); +#if VGA_SCANLINE_TIME_DEBUG +#if !VGA_NO_MALLOC + rgbDataBuffer[2] = malloc(VIRTUAL_PIXELS_X * sizeof(uint16_t)); +#endif - for (int i = 0; i < vgaParams.params.hVirtualPixels; ++i) + for (int i = 0; i < VIRTUAL_PIXELS_X; ++i) rgbDataBuffer[2][i] = 0x0f00; #endif - vgaParams.params.pioDivider = round(sysClockKHz / (float)minClockKHz); + vgaParams.params.pioDivider = roundflt(sysClockKHz / (float)minClockKHz); vgaParams.params.pioFreqKHz = sysClockKHz / vgaParams.params.pioDivider; vgaParams.params.pioClocksPerPixel = vgaParams.params.pioFreqKHz / (float)vgaParams.params.pixelClockKHz; vgaParams.params.pioClocksPerScaledPixel = vgaParams.params.pioFreqKHz * vgaParams.params.hPixelScale / (float)vgaParams.params.pixelClockKHz; - const uint32_t activeTicks = round(vgaParams.params.pioClocksPerPixel * (float)vgaParams.params.hSyncParams.displayPixels) - vga_sync_SETUP_OVERHEAD; - const uint32_t fPorchTicks = round(vgaParams.params.pioClocksPerPixel * (float)vgaParams.params.hSyncParams.frontPorchPixels) - vga_sync_SETUP_OVERHEAD; - const uint32_t syncTicks = round(vgaParams.params.pioClocksPerPixel * (float)vgaParams.params.hSyncParams.syncPixels) - vga_sync_SETUP_OVERHEAD; - const uint32_t bPorchTicks = round(vgaParams.params.pioClocksPerPixel * (float)vgaParams.params.hSyncParams.backPorchPixels) - vga_sync_SETUP_OVERHEAD; + const uint32_t activeTicks = roundflt(vgaParams.params.pioClocksPerPixel * (float)vgaParams.params.hSyncParams.displayPixels) - vga_sync_SETUP_OVERHEAD; + const uint32_t fPorchTicks = roundflt(vgaParams.params.pioClocksPerPixel * (float)vgaParams.params.hSyncParams.frontPorchPixels) - vga_sync_SETUP_OVERHEAD; + const uint32_t syncTicks = roundflt(vgaParams.params.pioClocksPerPixel * (float)vgaParams.params.hSyncParams.syncPixels) - vga_sync_SETUP_OVERHEAD; + const uint32_t bPorchTicks = roundflt(vgaParams.params.pioClocksPerPixel * (float)vgaParams.params.hSyncParams.backPorchPixels) - vga_sync_SETUP_OVERHEAD; - uint32_t rgbCyclesPerPixel = round(vgaParams.params.pioClocksPerScaledPixel); + uint32_t rgbCyclesPerPixel = roundflt(vgaParams.params.pioClocksPerScaledPixel); // compute sync bits @@ -123,6 +153,18 @@ static bool buildSyncData() const uint32_t vSyncOff = !vgaParams.params.vSyncParams.syncHigh << vga_sync_WORD_VSYNC_OFFSET; const uint32_t vSyncOn = vgaParams.params.vSyncParams.syncHigh << vga_sync_WORD_VSYNC_OFFSET; +#if VGA_COMBINE_SYNC + const uint32_t HoffVoff = hSyncOff | vSyncOff; + const uint32_t HonVoff = hSyncOn | vSyncOn; + const uint32_t HoffVon = hSyncOn | vSyncOn; + const uint32_t HonVon = hSyncOff | vSyncOff; +#else + const uint32_t HoffVoff = hSyncOff | vSyncOff; + const uint32_t HonVoff = hSyncOn | vSyncOff; + const uint32_t HoffVon = hSyncOff | vSyncOn; + const uint32_t HonVon = hSyncOn | vSyncOn; +#endif + // compute exec instructions const uint32_t instIrq = pio_encode_irq_set(false, vga_rgb_RGB_IRQ) << vga_sync_WORD_EXEC_OFFSET; const uint32_t instNop = pio_encode_nop() << vga_sync_WORD_EXEC_OFFSET; @@ -133,22 +175,22 @@ static bool buildSyncData() const int SYNC_LINE_HSYNC = 2; const int SYNC_LINE_BPORCH = 3; - syncDataActive[SYNC_LINE_ACTIVE] = instIrq | vSyncOff | hSyncOff | activeTicks; - syncDataActive[SYNC_LINE_FPORCH] = instNop | vSyncOff | hSyncOff | fPorchTicks; - syncDataActive[SYNC_LINE_HSYNC] = instNop | vSyncOff | hSyncOn | syncTicks; - syncDataActive[SYNC_LINE_BPORCH] = instNop | vSyncOff | hSyncOff | bPorchTicks; + syncDataActive[SYNC_LINE_ACTIVE] = instIrq | HoffVoff | activeTicks; + syncDataActive[SYNC_LINE_FPORCH] = instNop | HoffVoff | fPorchTicks; + syncDataActive[SYNC_LINE_HSYNC] = instNop | HonVoff | syncTicks; + syncDataActive[SYNC_LINE_BPORCH] = instNop | HoffVoff | bPorchTicks; // sync data for a front or back porch scanline - syncDataPorch[SYNC_LINE_ACTIVE] = instNop | vSyncOff | hSyncOff | activeTicks; - syncDataPorch[SYNC_LINE_FPORCH] = instNop | vSyncOff | hSyncOff | fPorchTicks; - syncDataPorch[SYNC_LINE_HSYNC] = instNop | vSyncOff | hSyncOn | syncTicks; - syncDataPorch[SYNC_LINE_BPORCH] = instNop | vSyncOff | hSyncOff | bPorchTicks; + syncDataPorch[SYNC_LINE_ACTIVE] = instNop | HoffVoff | activeTicks; + syncDataPorch[SYNC_LINE_FPORCH] = instNop | HoffVoff | fPorchTicks; + syncDataPorch[SYNC_LINE_HSYNC] = instNop | HonVoff | syncTicks; + syncDataPorch[SYNC_LINE_BPORCH] = instNop | HoffVoff | bPorchTicks; // sync data for a vsync scanline - syncDataSync[SYNC_LINE_ACTIVE] = instNop | vSyncOn | hSyncOff | activeTicks; - syncDataSync[SYNC_LINE_FPORCH] = instNop | vSyncOn | hSyncOff | fPorchTicks; - syncDataSync[SYNC_LINE_HSYNC] = instNop | vSyncOn | hSyncOn | syncTicks; - syncDataSync[SYNC_LINE_BPORCH] = instNop | vSyncOn | hSyncOff | bPorchTicks; + syncDataSync[SYNC_LINE_ACTIVE] = instNop | HoffVon | activeTicks; + syncDataSync[SYNC_LINE_FPORCH] = instNop | HoffVon | fPorchTicks; + syncDataSync[SYNC_LINE_HSYNC] = instNop | HonVon | syncTicks; + syncDataSync[SYNC_LINE_BPORCH] = instNop | HoffVon | bPorchTicks; return true; } @@ -180,7 +222,7 @@ static void vgaInitSync() pio_sm_init(VGA_PIO, SYNC_SM, syncProgOffset, &syncConfig); // initialise sync dma - syncDmaChan = dma_claim_unused_channel(true); + syncDmaChan = 0;//dma_claim_unused_channel(true); syncDmaChanMask = 0x01 << syncDmaChan; dma_channel_config syncDmaChanConfig = dma_channel_get_default_config(syncDmaChan); channel_config_set_transfer_data_size(&syncDmaChanConfig, DMA_SIZE_32); // transfer 32 bits at a time @@ -199,11 +241,14 @@ static void vgaInitSync() */ static void vgaInitRgb() { - const uint32_t rgbCyclesPerPixel = round(vgaParams.params.pioClocksPerScaledPixel); + const uint32_t rgbCyclesPerPixel = roundflt(vgaParams.params.pioClocksPerScaledPixel); // copy the rgb program and set the appropriate pixel delay uint16_t rgbProgramInstr[vga_rgb_program.length]; - memcpy(rgbProgramInstr, vga_rgb_program.instructions, sizeof(rgbProgramInstr)); + for (int i = 0; i < vga_rgb_program.length; ++i) + { + rgbProgramInstr[i] = vga_rgb_program.instructions[i]; + } rgbProgramInstr[vga_rgb_DELAY_INSTR] |= pio_encode_delay(rgbCyclesPerPixel - vga_rgb_LOOP_TICKS); pio_program_t rgbProgram = { @@ -220,7 +265,7 @@ static void vgaInitRgb() // add rgb pio program pio_sm_set_consecutive_pindirs(VGA_PIO, RGB_SM, RGB_PINS_START, RGB_PINS_COUNT, true); - pio_set_y(VGA_PIO, RGB_SM, vgaParams.params.hVirtualPixels - 1); + pio_set_y(VGA_PIO, RGB_SM, VIRTUAL_PIXELS_X - 1); uint rgbProgOffset = pio_add_program(VGA_PIO, &rgbProgram); pio_sm_config rgbConfig = vga_rgb_program_get_default_config(rgbProgOffset); @@ -234,7 +279,7 @@ static void vgaInitRgb() pio_sm_init(VGA_PIO, RGB_SM, rgbProgOffset, &rgbConfig); // initialise rgb dma - rgbDmaChan = dma_claim_unused_channel(true); + rgbDmaChan = 1;//dma_claim_unused_channel(true); rgbDmaChanMask = 0x01 << rgbDmaChan; dma_channel_config rgbDmaChanConfig = dma_channel_get_default_config(rgbDmaChan); channel_config_set_transfer_data_size(&rgbDmaChanConfig, DMA_SIZE_16); // transfer 16 bits at a time @@ -243,14 +288,14 @@ static void vgaInitRgb() channel_config_set_dreq(&rgbDmaChanConfig, pio_get_dreq(VGA_PIO, RGB_SM, true)); // setup the dma channel and set it going - dma_channel_configure(rgbDmaChan, &rgbDmaChanConfig, &VGA_PIO->txf[RGB_SM], rgbDataBuffer[0], vgaParams.params.hVirtualPixels, false); + dma_channel_configure(rgbDmaChan, &rgbDmaChanConfig, &VGA_PIO->txf[RGB_SM], rgbDataBuffer[0], VIRTUAL_PIXELS_X, false); dma_channel_set_irq0_enabled(rgbDmaChan, true); } /* * dma interrupt handler */ -static void __time_critical_func(dmaIrqHandler)(void) +static void __isr __time_critical_func(dmaIrqHandler)(void) { static int currentTimingLine = -1; static int currentDisplayLine = -1; @@ -288,25 +333,33 @@ static void __time_critical_func(dmaIrqHandler)(void) { dma_hw->ints0 = rgbDmaChanMask; - divmod_result_t pxLineVal = divmod_u32u32(currentDisplayLine++, vgaParams.params.vPixelScale); +#if VGA_HARDCODED_640 + uint32_t pxLine = currentDisplayLine >> 1; + uint32_t pxLineRpt = currentDisplayLine & 0x01; +#else + divmod_result_t pxLineVal = divmod_u32u32(currentDisplayLine, vgaParams.params.vPixelScale); uint32_t pxLine = to_quotient_u32(pxLineVal); uint32_t pxLineRpt = to_remainder_u32(pxLineVal); - uint16_t* currentBuffer = rgbDataBuffer[pxLine & 0x01]; +#endif + + uint32_t* currentBuffer = (uint32_t*)rgbDataBuffer[pxLine & 0x01]; + currentDisplayLine++; -#if CRT_EFFECT +#if VGA_CRT_EFFECT if (pxLineRpt != 0) { - for (int i = 0; i < 10; ++i) + for (int i = 0; i < 5; ++i) { - currentBuffer[i] = (currentBuffer[i] >> 1) & 0x0777; + currentBuffer[i] = (currentBuffer[i] >> 1) & 0x07770777; } } #endif -#if SCANLINE_TIME_DEBUG +#if VGA_SCANLINE_TIME_DEBUG + // apply a few darkened values before passing to dma if (pxLineRpt != 0 && hasRenderedNext) { - currentBuffer = rgbDataBuffer[2]; + currentBuffer = (uint32_t*)rgbDataBuffer[2]; } #endif @@ -317,24 +370,25 @@ static void __time_critical_func(dmaIrqHandler)(void) if ((pxLineRpt == 0)) { uint32_t requestLine = pxLine + 1; - if (requestLine >= vgaParams.params.vVirtualPixels) requestLine -= vgaParams.params.vVirtualPixels; + if (requestLine >= VIRTUAL_PIXELS_Y) requestLine -= VIRTUAL_PIXELS_Y; multicore_fifo_push_timeout_us(requestLine, 0); -#if SCANLINE_TIME_DEBUG +#if VGA_SCANLINE_TIME_DEBUG hasRenderedNext = false; #endif - if (requestLine == vgaParams.params.vVirtualPixels - 1) + if (requestLine == VIRTUAL_PIXELS_Y - 1) { multicore_fifo_push_timeout_us(END_OF_FRAME_MSG, 0); } } -#if CRT_EFFECT - else +#if VGA_CRT_EFFECT + else // apply a lame CRT effect, darkening every 2nd scanline { - for (int i = 10; i < vgaParams.params.hVirtualPixels; ++i) + int end = VIRTUAL_PIXELS_X / 2; + for (int i = 5; i < end; ++i) { - currentBuffer[i] = (currentBuffer[i] >> 1) & 0x0777; + currentBuffer[i] = (currentBuffer[i] >> 1) & 0x07770777; } } #endif @@ -353,6 +407,7 @@ static void initDma() dma_channel_start(rgbDmaChan); } + /* * main vga loop */ @@ -363,7 +418,8 @@ void __time_critical_func(vgaLoop)() vgaParams.initFn(); } - uint64_t frameNumber = 0; + + uint32_t frameNumber = 0; while (1) { uint32_t message = multicore_fifo_pop_blocking(); @@ -373,8 +429,8 @@ void __time_critical_func(vgaLoop)() if (vgaParams.endOfFrameFn) { vgaParams.endOfFrameFn(frameNumber); + ++frameNumber; } - ++frameNumber; } else if ((message & END_OF_SCANLINE_MSG) != 0) { @@ -387,7 +443,7 @@ void __time_critical_func(vgaLoop)() { // get the next scanline pixels vgaParams.scanlineFn(message & 0xfff, &vgaParams.params, rgbDataBuffer[message & 0x01]); -#if SCANLINE_TIME_DEBUG +#if VGA_SCANLINE_TIME_DEBUG dma_channel_set_read_addr(rgbDmaChan, rgbDataBuffer[2], true); hasRenderedNext = true; #endif diff --git a/src/vga/vga.h b/src/vga/vga.h index 94be64c..573cdc5 100644 --- a/src/vga/vga.h +++ b/src/vga/vga.h @@ -42,7 +42,7 @@ typedef struct typedef void (*vgaScanlineRgbFn)(uint16_t y, VgaParams* params, uint16_t* pixels); -typedef void (*vgaEndOfFrameFn)(uint64_t frameNumber); +typedef void (*vgaEndOfFrameFn)(uint32_t frameNumber); typedef void (*vgaInitFn)(); typedef void (*vgaEndOfScanlineFn)(); diff --git a/submodules/vrEmuTms9918 b/submodules/vrEmuTms9918 index 98e53a5..49ef229 160000 --- a/submodules/vrEmuTms9918 +++ b/submodules/vrEmuTms9918 @@ -1 +1 @@ -Subproject commit 98e53a51120648c22cb568f4ff6cc2b78ba12cf1 +Subproject commit 49ef229a1aacb37808208d3e12c0ad36fae74679 diff --git a/tools/img2carray.py b/tools/img2carray.py index 9e08c5b..581fb9a 100644 --- a/tools/img2carray.py +++ b/tools/img2carray.py @@ -117,7 +117,7 @@ def getFileHeader(fileName, romFileList, ramFileList, args, isHeaderFile) -> str if isHeaderFile: baseName = args['prefix'] + "_" + os.path.basename(fileName) sanitizedFile = re.sub('[^0-9a-zA-Z]+', '_', baseName.upper()) - hdrText += f"#pragma once\n\n" + hdrText += f"#pragma once\n" else: hdrText += "#include \"pico/platform.h\"\n" hdrText += "#include " @@ -195,6 +195,11 @@ def generateProto(varName, src, inRam, isHeader) -> str: # output the image array prototype proto += typePrefix + dataType + varNamePrefix + varName + "[]" + suffix + if isHeader: + proto += "\nconst int " + varNamePrefix + varName + \ + "Width = " + str(src.width) + ";\n" + proto += "const int " + varNamePrefix + varName + \ + "Height = " + str(src.height) + ";\n" return proto @@ -205,6 +210,7 @@ def imageToArrayContents(src, pix, bpp) -> str: rows = [] for y in range(src.height): row = [] + value = 0 for x in range(src.width): col = pix[x, y] if hasattr(col, "__len__"): @@ -214,12 +220,21 @@ def imageToArrayContents(src, pix, bpp) -> str: row.append(encodeRGBA32ToABGR16Hex(col[0], col[1], col[2], a)) elif bpp == 8: row.append("{0:#0{1}x}".format(col, 4)) - else: - if x & 1: - value = value | col + elif bpp == 4: + value = value | col + if (x & 1): + row.append("{0:#0{1}x}".format(value, 4)) + value = 0 + else: + value <<= bpp + else: # 2bpp + value = value | col + if (x & 3) == 3: row.append("{0:#0{1}x}".format(value, 4)) + value = 0 else: - value = col << 4 + value <<= bpp + rows.append(", ".join(row)) return "\n " + (",\n ".join(rows)) @@ -249,7 +264,10 @@ def processImageFile(infile, srcOutput, hdrOutput, args, inRam) -> None: bpp = 16 if src.palette: - bpp = 4 if len(src.palette.tobytes()) <= (16 * 3) else 8 + if len(src.palette.tobytes()) <= (4 * 3): + bpp = 2 + else: + bpp = 4 if len(src.palette.tobytes()) <= (16 * 3) else 8 comment = generateArrayComment(infile, src, bpp)