Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
blog:commodore_c65_dtv_programming [2023/01/23 22:14] – [Basic DTV Functions] john | blog:commodore_c65_dtv_programming [2023/01/25 17:21] (current) – [Extended DTV Functions] john | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== DTV (and C64) Programming - Using the CC65 toolchain ====== | ====== DTV (and C64) Programming - Using the CC65 toolchain ====== | ||
+ | ~~TOC_HERE 2-5~~ | ||
+ | |||
+ | From [[https:// | ||
+ | |||
+ | > The C64 Direct-to-TV, | ||
+ | > joystick), with 30 built-in games. The design is similar to the Atari Classics 10-in-1 TV Game. The circuitry of the C64DTV was designed by Jeri Ellsworth, a computer chip designer who | ||
+ | > had previously designed the C-One. | ||
+ | |||
+ | These are my notes on programming for the C64 DTV and making use of some of the extended functionality of the hardware from within C, rather than 6502 assembly. I hope you find these useful - whilst I came to the DTV more than a decade after its popularity peaked, I've found it a very capable little system! | ||
+ | |||
+ | {{: | ||
===== Basic use of CC65 ===== | ===== Basic use of CC65 ===== | ||
Line 205: | Line 216: | ||
---- | ---- | ||
+ | |||
+ | ==== Extended DTV Functions ==== | ||
+ | |||
+ | These functions raise the DTV above a simple C64 clone and add substantially improved functionality to the system: increased colours, digital sound and a dedicated image blitter. | ||
=== Enabling 320x200 Linear Framebuffer === | === Enabling 320x200 Linear Framebuffer === | ||
Line 298: | Line 313: | ||
<code C> | <code C> | ||
// DTV registers which can toggle extended features on/off | // DTV registers which can toggle extended features on/off | ||
- | #define DTV_VIC_REG_CONTROL_1 0xD011 | + | #define DTV_VIC_REG_CONTROL_1 0xD011 |
- | #define DTV_VIC_REG_CONTROL_2 0xD016 | + | #define DTV_VIC_REG_CONTROL_2 0xD016 |
- | #define DTV_VIC_REG_BORDER 0xD020 // Border pen colour | + | #define DTV_VIC_REG_BORDER 0xD020 // Border pen colour |
- | #define DTV_VIC_REG_BG_0 0xD021 // BG pen colour | + | #define DTV_VIC_REG_BG_0 0xD021 // BG pen colour |
- | #define DTV_VIC_REG_CFG 0xD03C // Controls linear framebuffer mode amongst other settings | + | #define DTV_VIC_REG_CFG 0xD03C // Controls linear framebuffer mode amongst other settings |
- | #define DTV_VIC_REG_EXTENDED 0xD03F // Enables/ | + | #define DTV_VIC_REG_EXTENDED 0xD03F // Enables/ |
- | #define DTV_VIC_REG_LFB_START_LOW 0xD049 // Linear framebuffer address low byte | + | #define DTV_VIC_REG_LFB_START_LOW 0xD049 // Linear framebuffer address low byte |
- | #define DTV_VIC_REG_LFB_START_MED 0xD04A // Linear framebuffer address middle byte | + | #define DTV_VIC_REG_LFB_START_MED 0xD04A // Linear framebuffer address middle byte |
- | #define DTV_VIC_REG_LFB_START_HI 0xD04B // Linear framebuffer address high byte | + | #define DTV_VIC_REG_LFB_START_HI 0xD04B // Linear framebuffer address high byte |
- | #define DTV_VIC_REG_LFB_STEPSIZE 0xD04C // Linear framebuffer chunk size, 8 == 8bpp | + | #define DTV_VIC_REG_LFB_STEPSIZE 0xD04C // Linear framebuffer chunk size, 8 == 8bpp |
- | #define DTV_VIC_PALETTE 0xD200 // Start address of palette entries | + | #define DTV_VIC_PALETTE 0xD200 // Start address of palette entries |
- | #define DMA_PORT_SOURCE_LOW 0xD300 // DMA transfer source address low byte | + | // DMA registers |
- | #define DMA_PORT_SOURCE_MED 0xD301 // DMA transfer source address middle byte | + | #define DMA_PORT_SOURCE_LOW 0xD300 // DMA transfer source address low byte |
- | #define DMA_PORT_SOURCE_HI 0xD302 // DMA transfer source address high byte | + | #define DMA_PORT_SOURCE_MED 0xD301 // DMA transfer source address middle byte |
- | #define DMA_PORT_DEST_LOW 0xD303 // DMA transfer destination address low byte | + | #define DMA_PORT_SOURCE_HI 0xD302 // DMA transfer source address high byte |
- | #define DMA_PORT_DEST_MED 0xD304 // DMA transfer destination address middle byte | + | #define DMA_PORT_DEST_LOW 0xD303 // DMA transfer destination address low byte |
- | #define DMA_PORT_DEST_HI 0xD305 // DMA transfer destination address high byte | + | #define DMA_PORT_DEST_MED 0xD304 // DMA transfer destination address middle byte |
- | #define DMA_PORT_SOURCE_STEP_LOW 0xD306 // DMA transfer step size low byte | + | #define DMA_PORT_DEST_HI 0xD305 // DMA transfer destination address high byte |
- | #define DMA_PORT_SOURCE_STEP_HI 0xD307 // DMA transfer step size high byte | + | #define DMA_PORT_SOURCE_STEP_LOW 0xD306 // DMA transfer step size low byte |
- | #define DMA_PORT_DEST_STEP_LOW 0xD308 // DMA transfer step size low byte | + | #define DMA_PORT_SOURCE_STEP_HI 0xD307 // DMA transfer step size high byte |
- | #define DMA_PORT_DEST_STEP_HI 0xD309 // DMA transfer step size high byte | + | #define DMA_PORT_DEST_STEP_LOW 0xD308 // DMA transfer step size low byte |
- | #define DMA_PORT_SIZE_LOW 0xD30A // DMA transfer | + | #define DMA_PORT_DEST_STEP_HI 0xD309 // DMA transfer step size high byte |
- | #define DMA_PORT_SIZE_HI 0xD30B // DMA transfer | + | #define DMA_PORT_SIZE_LOW 0xD30A // DMA transfer |
- | # | + | #define DMA_PORT_SIZE_HI 0xD30B // DMA transfer |
+ | # | ||
+ | #define DMA_PORT_SOURCE_MODULO_HI 0xD30D | ||
+ | #define DMA_PORT_DEST_MODULO_LOW 0xD30E | ||
+ | #define DMA_PORT_DEST_MODULO_HI 0xD30F | ||
+ | #define DMA_PORT_SOURCE_LENGTH_LOW 0xD310 | ||
+ | #define DMA_PORT_SOURCE_LENGTH_HI 0xD311 | ||
+ | #define DMA_PORT_DEST_LENGTH_LOW 0xD312 | ||
+ | #define DMA_PORT_DEST_LENGTH_HI 0xD313 | ||
+ | #define DMA_PORT_CLEAR_IRQ 0xD31D | ||
+ | #define DMA_PORT_MODULO_ENABLE 0xD31E | ||
+ | #define DMA_PORT_STATUS 0xD31F // DMA status & control register | ||
+ | // Blitter registers | ||
+ | #define BLITTER_SOURCE_A_LOW 0xD320 // | ||
+ | #define BLITTER_SOURCE_A_MED 0xD321 // | ||
+ | #define BLITTER_SOURCE_A_HI 0xD322 // | ||
+ | #define BLITTER_SOURCE_A_MODULO_LOW 0xD323 // | ||
+ | #define BLITTER_SOURCE_A_MODULO_HI 0xD324 // | ||
+ | #define BLITTER_SOURCE_A_LENGTH_LOW 0xD325 // | ||
+ | #define BLITTER_SOURCE_A_LENGTH_HI 0xD326 // | ||
+ | #define BLITTER_SOURCE_A_STEP 0xD327 | ||
+ | #define BLITTER_SOURCE_B_LOW 0xD328 // | ||
+ | #define BLITTER_SOURCE_B_MED 0xD329 // | ||
+ | #define BLITTER_SOURCE_B_HI 0xD32A // | ||
+ | #define BLITTER_SOURCE_B_MODULO_LOW 0xD32B // | ||
+ | #define BLITTER_SOURCE_B_MODULO_HI 0xD32C // | ||
+ | #define BLITTER_SOURCE_B_LENGTH_LOW 0xD32D // | ||
+ | #define BLITTER_SOURCE_B_LENGTH_HI 0xD32E // | ||
+ | #define BLITTER_SOURCE_B_STEP 0xD32F | ||
+ | #define BLITTER_DEST_LOW 0xD330 // | ||
+ | #define BLITTER_DEST_MED 0xD331 // | ||
+ | #define BLITTER_DEST_HI 0xD332 // | ||
+ | #define BLITTER_DEST_MODULO_LOW 0xD333 // | ||
+ | #define BLITTER_DEST_MODULO_HI 0xD334 // | ||
+ | #define BLITTER_DEST_LENGTH_LOW 0xD335 // | ||
+ | #define BLITTER_DEST_LENGTH_HI 0xD336 // | ||
+ | #define BLITTER_DEST_STEP 0xD337 | ||
+ | #define BLITTER_SIZE_LOW 0xD338 // | ||
+ | #define BLITTER_SIZE_HI 0xD339 // | ||
+ | #define BLITTER_START 0xD33A | ||
+ | #define BLITTER_CFG 0xD33B // | ||
+ | #define BLITTER_MINTERM_CFG 0xD33E // | ||
+ | #define BLITTER_STATUS 0xD33F | ||
</ | </ | ||
Line 391: | Line 448: | ||
</ | </ | ||
- | Here are a couple of functions which fill an off-screen scratchpad with a solid colour, and then copies the entire 320x200 scratchpad to the linear framebuffer using the** dtv_dma()** function above: | + | Here are a couple of functions |
<code C> | <code C> | ||
Line 399: | Line 456: | ||
void draw_Clear(void){ | void draw_Clear(void){ | ||
- | // Clear the contents of the screen buffer with a solid colour | + | // Clear the contents of the screen buffer with a solid colour, line by line |
unsigned char i; | unsigned char i; | ||
Line 418: | Line 475: | ||
} | } | ||
</ | </ | ||
+ | |||
+ | == Bugs == | ||
+ | |||
+ | I've seemingly found a bug which manifests when making sequential DMA calls in a tight loop and the transfer length is 182 bytes or greater. | ||
+ | |||
+ | You can successfully DMA copy quite a large region in a single operation and there are no side-effects, | ||
+ | |||
+ | {{: | ||
+ | |||
+ | The colour bars in the image should be continuous for the entire screen width, but they glitch at two points. | ||
+ | The above example was generated by the following pseudocode: | ||
+ | |||
+ | < | ||
+ | screen = FRAMEBUFFER_LOCATION; | ||
+ | for (line = 0; line < SCREEN_HEIGHT; | ||
+ | memset(line_buffer, | ||
+ | dtv_DMA(& | ||
+ | colour++; | ||
+ | screen += SCREEN_WIDTH; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | If you reduce the transfer size to 181 bytes you do not get the glitching, and if you have a // | ||
+ | |||
+ | ---- | ||
+ | |||
+ | === Blitter Operation === | ||
+ | |||
+ | One of the big features of the DTV2 & 3 is the addition of [[https:// | ||
+ | |||
+ | The Blitter documentation is extensive, with dozens of different registers to set and configure. However, in most circumstances you will want to copy one solid block of pixels to another area (either on-screen or off - the blitter works over the entire 0-2048kb memory region, but //not// ROM). | ||
+ | |||
+ | Here I show a variation of the blitter command to transfer w x h pixels from src to dest. | ||
+ | |||
+ | * **src**: address of the upper-left pixel of a rectangular block of source pixels | ||
+ | * **dst**: address of the upper-left pixel of the destination area. | ||
+ | * **w**: width of the source area to copy in pixels | ||
+ | * **h**: number of lines of the source area to copy | ||
+ | |||
+ | The source area is OR-ed with the destination and transparency in the source area is honoured (source pixels with value //0// will //not// overwrite pixels in the destination). | ||
+ | |||
+ | **dtv_Blit()** | ||
+ | |||
+ | <code C> | ||
+ | #include < | ||
+ | #include < | ||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | void dtv_Blit(uint32_t src, uint32_t dst, uint16_t w, unsigned char h){ | ||
+ | // Blit 'width * height' | ||
+ | // using the blitter engine. | ||
+ | // | ||
+ | // ' | ||
+ | // ' | ||
+ | // ' | ||
+ | // ' | ||
+ | |||
+ | uint16_t total_bytes = w * h; | ||
+ | uint16_t modulus = SCREEN_WIDTH - w; | ||
+ | |||
+ | // ================================== | ||
+ | // Source address | ||
+ | // ================================== | ||
+ | POKE(BLITTER_SOURCE_A_LOW, | ||
+ | POKE(BLITTER_SOURCE_A_MED, | ||
+ | POKE(BLITTER_SOURCE_A_HI, | ||
+ | |||
+ | // ================================== | ||
+ | // Destination address | ||
+ | // ================================== | ||
+ | POKE(BLITTER_DEST_LOW, | ||
+ | POKE(BLITTER_DEST_MED, | ||
+ | POKE(BLITTER_DEST_HI, | ||
+ | |||
+ | // ================================== | ||
+ | // Set line length | ||
+ | // ================================== | ||
+ | POKE(BLITTER_SOURCE_A_LENGTH_LOW, | ||
+ | POKE(BLITTER_SOURCE_A_LENGTH_HI, | ||
+ | POKE(BLITTER_DEST_LENGTH_LOW, | ||
+ | POKE(BLITTER_DEST_LENGTH_HI, | ||
+ | |||
+ | // ================================== | ||
+ | // Set modulus/ | ||
+ | // ================================== | ||
+ | POKE(BLITTER_SOURCE_A_MODULO_LOW, | ||
+ | POKE(BLITTER_SOURCE_A_MODULO_HI, | ||
+ | POKE(BLITTER_DEST_MODULO_LOW, | ||
+ | POKE(BLITTER_DEST_MODULO_HI, | ||
+ | |||
+ | // ================================== | ||
+ | // Step sizes | ||
+ | // ================================== | ||
+ | POKE(BLITTER_SOURCE_A_STEP, | ||
+ | POKE(BLITTER_DEST_STEP, | ||
+ | |||
+ | // ================================== | ||
+ | // Set total transfer size | ||
+ | // ================================== | ||
+ | POKE(BLITTER_SIZE_LOW, | ||
+ | POKE(BLITTER_SIZE_HI, | ||
+ | |||
+ | // ================================== | ||
+ | // Configure blitter mode and start blit | ||
+ | // ================================== | ||
+ | POKE(BLITTER_CFG, | ||
+ | POKE(BLITTER_MINTERM_CFG, | ||
+ | POKE(BLITTER_START, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Here's an example of the blitter function in operation, copying a **100x100** rectangle of pixels from the source position of **1,1** to the destination at **219, | ||
+ | |||
+ | This is the source image - a basic set of colour bars: | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Using dtv_Blit() we copy from 1,1 to 219,100 a 100x100 rectangle in OR mode: | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Notice that the transparent (aka //black//) pixels in the source region are preserved when copying to the destination, | ||
+ | |||
+ | == Bugs == | ||
+ | |||
+ | However, there is a problem. On the **DTV2** systems (the initial PAL models), the blitter is partially bugged and transparent copies result int a vertical banding effect: | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Therefore, //without mitigation//, |