Fabrizio Caruso Posted September 28, 2018 Share Posted September 28, 2018 Has anyone tried to code for the Atari 2600 in C wirh CC65? I am trying to figure pout how to code the display. I understand I need to control the raster. I am looking for a simple "Hello World" example. Quote Link to comment Share on other sites More sharing options...
+Gemintronic Posted September 28, 2018 Share Posted September 28, 2018 Not a direct answer. I apologize in advance for that. You might consider something that is actively maintained such as this project: http://atariage.com/forums/topic/281105-open-source-c-project-template-now-available/ Pending that the 2600 has a ginormous amount of assembly resources. Quote Link to comment Share on other sites More sharing options...
zezba9000 Posted January 31, 2019 Share Posted January 31, 2019 (edited) Has anyone tried to code for the Atari 2600 in C wirh CC65? I am trying to figure pout how to code the display. I understand I need to control the raster. I am looking for a simple "Hello World" example. Did you find a good graphics / audio / input example with the CC65 compiler? Like an example game where you can see optimized solutions working. Edited January 31, 2019 by zezba9000 Quote Link to comment Share on other sites More sharing options...
Fabrizio Caruso Posted January 31, 2019 Author Share Posted January 31, 2019 There is no high level construct in CC65 for the Atari2600.Unfortunately CC65 only supports Assembly or "C as Assembly", i.e., you can write your code in C but you have to follow the beam as you do in Assembly.CC65 does not provide anything close to Batari BASIC. I may write a version of my hame if I understand how to do simple graphics. Let us say I have a buffer with a 32x16 grid of bits (or less), how can I draw this buffer as 32x16 full blocks on the screen in C? Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted January 31, 2019 Share Posted January 31, 2019 Let us say I have a buffer with a 32x16 grid of bits (or less), how can I draw this buffer as 32x16 full blocks on the screen in C?You need display kernel which has to be written in assembler. 1 Quote Link to comment Share on other sites More sharing options...
Fabrizio Caruso Posted January 31, 2019 Author Share Posted January 31, 2019 Why do I need to write in Assembler? Anyway were can I find a simple example to just write blocks from a grid of bits.Given the limited ram, I am assuming just a 32x16 or 32x12 grid.The beam has to be on or off depending on the value of the bit in the grid.If the main loop is fast enough, maybe I don't even need interrupts. At this stage I wouldn't mind if there were some flickering.I do not want to use player/missile/ball. Just big blocks with one single screen-wide color. Quote Link to comment Share on other sites More sharing options...
zezba9000 Posted January 31, 2019 Share Posted January 31, 2019 (edited) You need display kernel which has to be written in assembler. In this example: https://github.com/cc65/cc65/blob/master/samples/atari2600hello.c I'm able to compile and see the screen flashing colors as expected. I see TIA being used to set the background color. Does this expose the features needed? If not where is a good example of using the asm to write colored blocks or lines using the graphics unit I could invoke from C? From reading it seems the graphics unit supports block and horizontal lines. I'll have to find that article again. Example compiled like so: "..\bin\cl65 -t atari2600 .\atari2600hello.c -o atari2600hello.a26" Edited January 31, 2019 by zezba9000 Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted January 31, 2019 Share Posted January 31, 2019 You really have to understand the basics of the 2600 graphics. There is no frame buffer which you can address. Instead you have to "race the beam", updating several graphics registers in sync with it. That way you construct line by line of the display. And this requires assembler and cycle counting. A simple assembler kernel can be found here: How to Draw a Playfield, but you would have to modify it significantly to control it from outside. Quote Link to comment Share on other sites More sharing options...
zezba9000 Posted January 31, 2019 Share Posted January 31, 2019 (edited) You really have to understand the basics of the 2600 graphics. There is no frame buffer which you can address. Instead you have to "race the beam", updating several graphics registers in sync with it. That way you construct line by line of the display. And this requires assembler and cycle counting. A simple assembler kernel can be found here: How to Draw a Playfield, but you would have to modify it significantly to control it from outside. So got playfields working with this example: http://www.randomterrain.com/atari-2600-memories-tutorial-andrew-davie-13.html However I'm a little confused on what to use to get the rainbow effect correctly. Here is my C code so far. /*****************************************************************************/ /* */ /* Atari VCS 2600 sample C program */ /* */ /* Florent Flament (contact@florentflament.com), 2017 */ /* */ /*****************************************************************************/ #include <atari2600.h> // PAL Timings // Roughly computed based on Stella Programmer's guide (Steve Wright) // scanlines count per section. #define VBLANK_TIM64 51 // 45 lines * 76 cycles/line / 64 cycles/tick #define KERNAL_T1024 17 // 228 lines * 76 cycles/line / 1024 cycles/tick #define OVERSCAN_TIM64 42 // 36 lines * 76 cycles/line / 64 cycles/tick // test unsigned char flip = 0; void main(void) { unsigned char color = 0x79; // Stack variable TIA.colubk = 0xFF;// background color TIA.colupf = 0x00;// playfield color TIA.pf0 = 0x55;// playfield pattern 1 (01010101) TIA.pf1 = 0xAA;// playfield pattern 2 (10101010) TIA.pf2 = 0x55;// playfield pattern 3 (01010101) while (1) { // Vertical Sync signal TIA.vsync = 0x02; TIA.wsync = 0x00; TIA.wsync = 0x00; TIA.wsync = 0x00; TIA.vsync = 0x00; // Vertical Blank timer setting RIOT.tim64t = VBLANK_TIM64; // Doing frame computation during blank // TODO // Wait for end of Vertical Blank while (RIOT.timint == 0) { // Can I change values here? } TIA.wsync = 0x00; TIA.vblank = 0x00; // Turn on beam // Display frame RIOT.t1024t = KERNAL_T1024; while (RIOT.timint == 0) {} TIA.wsync = 0x00; TIA.vblank = 0x02; // Turn off beam // Overscan RIOT.tim64t = OVERSCAN_TIM64; while (RIOT.timint == 0) {} } } Edited January 31, 2019 by zezba9000 Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted January 31, 2019 Share Posted January 31, 2019 (edited) OK, I now see what you are trying (code always helps ). Yes, you can change values in the first timer loop. But you should do your changes before and use that loop for syncing only. But that area is outside the visible area. Instead you have to change your TIA registers in the display frame area. Here is a simple kernel, which updates the playfield color: // RIOT.t1024t = KERNAL_T1024; line = 228; do { TIA.colupf = line; // change color every (other) line TIA.wsync = 0x00; // start next scanline (any other value will do too, WSYNC doesn't care) } while (--line); // while (RIOT.timint == 0) {} // TIA.wsync = 0x00; I hope this helps. Edited January 31, 2019 by Thomas Jentzsch Quote Link to comment Share on other sites More sharing options...
Kiwi Posted January 31, 2019 Share Posted January 31, 2019 (edited) You'll need a variable to hold the color byte and possibly a scanline, but uses 5 cycle to inc compare to 2/3 cycle using x or y register. In the loop below, I'm using the y register preloaded with value of 192.There's 76 cycles per scanline. The kernel loop in asm would be, loop: ldx Color inx stx COLUPF stx Color iny (counting scanline) cpy bne loop You'll have to make a function in C with this asm to work.IncreaseVariable(Color)Something like this might able to replace the C code with the code above. While(RegisterY!=0) WSYNC(); IncreasePFColorUsingVariable(Color); Donedrawingonthisline();} Some kernal uses the y register to keep track of scanlines so it can fall through and go in the overscan area. Some time use timer register to fall through. I experienced course scanline count that the timer may have pushes my game to 263 scanline, still trying to understand that part.The key problem is doing C is that you have to design a scanline kernal. Placing Sprite anywhere on screen cost more cycle than using a fix y position using tables to load the data to the sprite registers. It's hard to explain since you have 76 cycle a scanline. You can't update everything on a line. If you're over 76 cycle, it automatically start drawing the next line. You have 22 cycle of Hblank to update your register before the beam enter from the right. There's a lot of information for newcomer to understand about the Atari 2600 like how to place sprite horizontal because there's no x coordinate. Or understand what the motion registers are for the 5 objects. Or the usage of HMOVE in the middle of the screen to reposition the sprites.C should be possible, but have to understand there's under scan for your calculation, the 192 line display kernel loop, overscan usually to check the sprites/PF for overlaps. Display Kernel is the difficult part to understand.Source code: seg Code org $f000 Start CLEAN_START NextFrame lsr SWCHB ; test Game Reset switch bcc Start ; reset? ; 1 + 3 lines of VSYNC VERTICAL_SYNC ; 37 lines of underscan TIMER_SETUP 37 lda #$AA sta PF1 sta COLUPF sta Color ldy #192 TIMER_WAIT ; 192 lines of frame loop STA WSYNC ldx Color inx stx COLUPF stx Color dey cpy #0 bne loop ; 29 lines of overscan TIMER_SETUP 29 TIMER_WAIT ; total = 262 lines, go to next frame jmp NextFrame Edited January 31, 2019 by Kiwi Quote Link to comment Share on other sites More sharing options...
zezba9000 Posted February 1, 2019 Share Posted February 1, 2019 (edited) OK, I now see what you are trying (code always helps ). Yes, you can change values in the first timer loop. But you should do your changes before and use that loop for syncing only. But that area is outside the visible area. Instead you have to change your TIA registers in the display frame area. Here is a simple kernel, which updates the playfield color: // RIOT.t1024t = KERNAL_T1024; line = 228; do { TIA.colupf = line; // change color every (other) line TIA.wsync = 0x00; // start next scanline (any other value will do too, WSYNC doesn't care) } while (--line); // while (RIOT.timint == 0) {} // TIA.wsync = 0x00; I hope this helps. NOTE: Ok so I should ask if anyone knows what the NTSC timings are as I'm in the US and wont be able to run this code on my UnoCart. // PAL Timings // Roughly computed based on Stella Programmer's guide (Steve Wright) // scanlines count per section. #define VBLANK_TIM64 51 // 45 lines * 76 cycles/line / 64 cycles/tick #define KERNAL_T1024 17 // 228 lines * 76 cycles/line / 1024 cycles/tick #define OVERSCAN_TIM64 42 // 36 lines * 76 cycles/line / 64 cycles/tick Using your suggestion I modified it to wait every other line as I think some asm instructions from C are taking to long so things can't keep up. #include <atari2600.h> // PAL Timings // Roughly computed based on Stella Programmer's guide (Steve Wright) // scanlines count per section. #define VBLANK_TIM64 51 // 45 lines * 76 cycles/line / 64 cycles/tick #define KERNAL_T1024 17 // 228 lines * 76 cycles/line / 1024 cycles/tick #define OVERSCAN_TIM64 42 // 36 lines * 76 cycles/line / 64 cycles/tick // test variables unsigned char flip = 0; //unsigned char buffer[80 * 96];// UnoCart back-buffer test void main(void) { unsigned char line = 0; //TIA.colubk = 0xFF;// background color //TIA.colupf = 0x00;// playfield color //TIA.pf0 = 0x55;// playfield pattern 1 (01010101) //TIA.pf1 = 0xAA;// playfield pattern 2 (10101010) //TIA.pf2 = 0x55;// playfield pattern 3 (01010101) while (1) { TIA.vblank = 0x00;// enable vblank // Vertical Sync signal TIA.vsync = 0x02; TIA.wsync = 0x00; TIA.wsync = 0x00; TIA.wsync = 0x00; TIA.vsync = 0x00; // write every two lines as one color TIA.wsync = 0x00;// not sure why I need to wait here? do { if (flip) TIA.wsync = 0x00;// wait for end of vblank else TIA.colubk = line;// change color flip = !flip; } while (--line); TIA.wsync = 0x00;// wait for last color to finish writing // disable vblack TIA.vblank = 0x02; //TIA.vblank = 42;// is this the correct value? // set timer to overscan RIOT.tim64t = OVERSCAN_TIM64; // prep next frame while waiting (main game logic goes here) flip = 0; line = 192; TIA.colubk = line; // wait for overscan to finish while (RIOT.timint == 0) {} } } I now can make a color pattern in C however I need to learn how to do this with the CA65 assembly compiler. Now I have 3 major things I would like to figure out if you or anyone has any tips. 1) How to render sprites (What timing helpers do I use for this). 2) Double-Buffering using the UnoCart with 32k of ram. How do I compile a .3e rom that allows for this. 3) CA65 how to compile an asm method I can invoke from C? (Having a hard time finding info here [maybe searching for the wrong thing]) Edited February 1, 2019 by zezba9000 Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted February 1, 2019 Share Posted February 1, 2019 (edited) Using your suggestion I modified it to wait every other line as I think some asm instructions from C are taking to long so things can't keep up. If you cannot do a simple color change within on scanline (hard to believe), then you will not get far at all using C for the kernel. Some math: With assembler, such a simple change takes 3-8 cycles (out of 76 per scanline). Updating just the playfield takes at least ~28 cycles (only PF1 and PF2) up to ~48 cycles. So this update, which (for an asymmetrical playfield) has to happen every scanline, will require multiple scanlines. You should look at the generated assembler code and identify why your simple kernel loop takes so long and try to fix that first. Edited February 1, 2019 by Thomas Jentzsch Quote Link to comment Share on other sites More sharing options...
zezba9000 Posted February 1, 2019 Share Posted February 1, 2019 (edited) If you cannot do a simple color change within on scanline (hard to believe), then you will not get far at all using C for the kernel. Some math: With assembler, such a simple change takes 3-8 cycles (out of 76 per scanline). Updating just the playfield takes at least ~28 cycles (only PF1 and PF2) up to ~48 cycles. So this update, which (for an asymmetrical playfield) has to happen every scanline, will require multiple scanlines. You should look at the generated assembler code and identify why your simple kernel loop takes so long and try to fix that first. I might be wrong about the assembly will have to look. I'm updating the background not playfield in my last example. How many cycles does that take? Also I wasn't compiling with optimizations (oops). So problem now is fixed and I can use the code below. // write every line as new color (seems to effect every two though) do { TIA.wsync = 0x00;// wait for end of vblank TIA.colubk = line;// change color } while (--line); Edited February 1, 2019 by zezba9000 Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted February 1, 2019 Share Posted February 1, 2019 Assuming you have to load line each time (instead of keeping it in a register) and it is a zeropage address, it should take 6 cycles. If the variable is optimized into a register, then it should take 3 cycles. You can use Stella's debugger to look at the generated code and cycles consumed in every detail. Quote Link to comment Share on other sites More sharing options...
danwinslow Posted February 1, 2019 Share Posted February 1, 2019 C is basically just a macro assembler anyway. Doing this stuff in C is the same as doing it in assembler for the most part. You will spend a lot of time driving the chips and registers with stuff like TIA.somereg = somevalue which is pretty much the same as LDA somevalue STA somereg You won't be able to use much of the C library, no printf's or fopens or anything like that. You won't gain anything in particular from using C anyways, other than the syntax for bit twiddling and testing if you know it better in C and some extra slowness. Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted February 1, 2019 Share Posted February 1, 2019 If the compiler is smart, it might help you optimizing register usage. Probably better than a noob would do but worse than an experienced coder. Quote Link to comment Share on other sites More sharing options...
gauauu Posted February 1, 2019 Share Posted February 1, 2019 (edited) I'm late to respond here, but my game Robo-Ninja Climb has the main logic written in C in cc65, with the kernel and atari-specific stuff written in ca65 asm. (the game logic is the same across this version and my NES version of the game) C is fairly slow, but it worked out fine. Source code is available at bitbucket. If the compiler is smart, it might help you optimizing register usage. Probably better than a noob would do but worse than an experienced coder. It's not smart. Register usage in cc65 is pretty inefficient, and ends up being relatively slow. Edited February 1, 2019 by gauauu 1 Quote Link to comment Share on other sites More sharing options...
zezba9000 Posted February 1, 2019 Share Posted February 1, 2019 You won't gain anything in particular from using C anyways, other than the syntax for bit twiddling and testing if you know it better in C and some extra slowness. C is portable so I can handle game logic in C to be used on other non 6502 CPUs. Thats the primary reason. Quote Link to comment Share on other sites More sharing options...
zezba9000 Posted February 1, 2019 Share Posted February 1, 2019 (edited) I'm late to respond here, but my game Robo-Ninja Climb has the main logic written in C in cc65, with the kernel and atari-specific stuff written in ca65 asm. (the game logic is the same across this version and my NES version of the game) C is fairly slow, but it worked out fine. Source code is available at bitbucket. It's not smart. Register usage in cc65 is pretty inefficient, and ends up being relatively slow. Thanks! I've been looking for a demo like this. You're using CA65 for the assembly and CC65 for the C correct? I'll download after work. Also anyone know how to compile out a .3e rom with the CC65 toolkit so I get 32k of ram? EDIT: Rather anyone know how to store memory in the 32k location in CA65 assembly? So I can hold a back-buffer there. Then I can just write the kernel parts in asm. I'm newer to using assembly as most my work is in C/C++ or C#. Edited February 1, 2019 by zezba9000 Quote Link to comment Share on other sites More sharing options...
gauauu Posted February 1, 2019 Share Posted February 1, 2019 Thanks! I've been looking for a demo like this. You're using CA65 for the assembly and CC65 for the C correct? I'll download after work. Also anyone know how to compile out a .3e rom with the CC65 toolkit so I get 32k of ram? Yeah, ca65 for assembly, cc65 for C. I don't know enough about the rom layout for .3e, but you need to adjust the ld65 linker config file to tell it where ram lives, and the difference between zero page and other ram. Something like this: MEMORY { ZP: start = $0080, size = $0080, type = rw; RAM: start = $0200, size = $0600, type = rw; #I don't know where ram really goes? #these are your various banks. adjust per the size of the bank and start address ROM0: start = $F000, size = $1000, type = ro, file = %O, fill=yes, fillval=$FF; ROM1: start = $F000, size = $1000, type = ro, file = %O, fill=yes, fillval=$FF; } SEGMENTS { ZEROPAGE: load = ZP, type = zp; BSS: load = RAM, type = bss, define = yes; #this assumes you have all these segments. adjust as needed. CODE0: load = ROM0, type = ro; RODATA0: load = ROM0, type = ro; VECTORS0: load = ROM0, type = ro, start=$FFFC; CODE1: load = ROM1, type = ro; RODATA1: load = ROM1, type = ro; VECTORS1: load = ROM1, type = ro, start=$FFFC; } Quote Link to comment Share on other sites More sharing options...
zezba9000 Posted February 2, 2019 Share Posted February 2, 2019 Yeah, ca65 for assembly, cc65 for C. I don't know enough about the rom layout for .3e, but you need to adjust the ld65 linker config file to tell it where ram lives, and the difference between zero page and other ram. Something like this: MEMORY { ZP: start = $0080, size = $0080, type = rw; RAM: start = $0200, size = $0600, type = rw; #I don't know where ram really goes? #these are your various banks. adjust per the size of the bank and start address ROM0: start = $F000, size = $1000, type = ro, file = %O, fill=yes, fillval=$FF; ROM1: start = $F000, size = $1000, type = ro, file = %O, fill=yes, fillval=$FF; } SEGMENTS { ZEROPAGE: load = ZP, type = zp; BSS: load = RAM, type = bss, define = yes; #this assumes you have all these segments. adjust as needed. CODE0: load = ROM0, type = ro; RODATA0: load = ROM0, type = ro; VECTORS0: load = ROM0, type = ro, start=$FFFC; CODE1: load = ROM1, type = ro; RODATA1: load = ROM1, type = ro; VECTORS1: load = ROM1, type = ro, start=$FFFC; } Looks like this doc says where it goes: http://kevtris.org/files/sizes.txt "F4 - 'Standard' 32K; uses 1FF4 to 1FFB" Because the UnoCart uses the same spec as Stella for 32k .3e roms that might work. Quote Link to comment Share on other sites More sharing options...
Fabrizio Caruso Posted April 24, 2020 Author Share Posted April 24, 2020 Hi everyone! Has anyone made any progress on using C to do even super simple graphics like just something on a symmetric playfield? Quote Link to comment Share on other sites More sharing options...
+splendidnut Posted April 24, 2020 Share Posted April 24, 2020 I've been doing some experiments recently... Quote Link to comment Share on other sites More sharing options...
Stechmann Posted October 14, 2023 Share Posted October 14, 2023 On a side note, 8bitworkshop has recently added cc65 support to their 2600 workflow: http://8bitworkshop.com/ Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.