gozar Posted January 11, 2015 Share Posted January 11, 2015 I want to change the colors of certain lines in graphics 0 in cc65. Assembler can be embedded in cc65, but I can't figure out how to get started. Does anyone have any examples of use? Quote Link to comment Share on other sites More sharing options...
Shawn Jefferson Posted January 12, 2015 Share Posted January 12, 2015 (edited) You could use in-line asm, but the cc65 compiler can optimize that in-line code, sometimes doing things you don't want. I usually write routines like DLIs or VBIs in a separate assembly file and link to my project. Here's something I had laying around that I modified to do a simple DLI. Main C code: #include <atari.h> #include <stdio.h> #include <stdlib.h> #include <conio.h> #define SAVMSC *(unsigned int *) 88 // Screen address #define NMIEN *(unsigned char *) 0xD40E // NMI enable #define SDMCTL *(unsigned char *) 559 // Antic DMA control shadow #define SDLSTL *(unsigned int *) 560 // Display list start shadow #define VDSLST *(unsigned int *) 0x200 // Display list interrupt vector char dl[] = {112,112,112,66,0,0,130,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,65,0,0}; void dli(void); void main(void) { unsigned char i; dl[4] = (unsigned char) (SAVMSC % 256); dl[5] = (unsigned char) (SAVMSC / 256); dl[sizeof(dl)-2] = ((unsigned) &dl) % 256; dl[sizeof(dl)-1] = ((unsigned) &dl) / 256; NMIEN = 0xC0; // enable dli + vbi SDMCTL = 0; // turn off antic SDLSTL = (unsigned int) &dl; // new dlist address VDSLST = (unsigned int) &dli; // set dli vector SDMCTL = 34; // turn on antic for(i=0; i<24; ++i) { gotoxy(0, i); cprintf("This is a string! %d", i); } while(1); return; } Assembly DLI routine: .export _dli COLPF2 = $D018 WSYNC = $D40A .code _dli: pha ; save registers lda #$40 sta WSYNC sta COLPF2 pla ; restore registers rti Command line to compile/assemble and link: cl65 -t atari -Osir vsdli.s testvs.c -o testdli.xex testdli.xex Edited January 12, 2015 by Shawn Jefferson 2 Quote Link to comment Share on other sites More sharing options...
danwinslow Posted January 12, 2015 Share Posted January 12, 2015 (edited) I do as Shawn does. However, If you do decide to embed, look up the asm function. ex. - ASM("LDA #22"); You can use replacement variables in the string and pass in certain values, kind of like printf works. Don't have the info handy, or I'd be more precise, but if you look through the cc65 docs for the ASM keyword, you'll see it. Edited January 12, 2015 by danwinslow Quote Link to comment Share on other sites More sharing options...
phaeron Posted January 12, 2015 Share Posted January 12, 2015 There are some problems with this code: char dl[] = {112,112,112,66,0,0,130,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,65,0,0}; Display lists cannot cross a 1K boundary. This will fail randomly depending on where the compiler puts this array. NMIEN = 0xC0; // enable dli + vbi Crash if display list currently has DLI instructions in it, especially if it's garbage. This is most prone to occur if the active display list has been overwritten with garbage (sloppy loaders). DLIs should be shut off first, then the display list and DLI vector changed, then DLIs re-enabled. SDMCTL = 0; // turn off antic This only turns off DMA after vertical blank, and only if there isn't an IRQ running at that time (keyboard). To do this safely, you have to wait for vertical blank and then hit SDMCTL=0, DMACTL=0, or alternatively, use one of OS timers 3-5 to wait for stage 2 VBI to run. 1 Quote Link to comment Share on other sites More sharing options...
dmsc Posted January 13, 2015 Share Posted January 13, 2015 (edited) Hi, Some comments on your code, over the comments from Avery: #include <atari.h> #include <stdio.h> #include <stdlib.h> #include <conio.h> #define SAVMSC *(unsigned int *) 88 // Screen address #define NMIEN *(unsigned char *) 0xD40E // NMI enable #define SDMCTL *(unsigned char *) 559 // Antic DMA control shadow #define SDLSTL *(unsigned int *) 560 // Display list start shadow #define VDSLST *(unsigned int *) 0x200 // Display list interrupt vector You can use the definitions already included from the atari.h header, accessing the registers like C structs for example, ANTIC.nmien = 0xC0; Sadly, most of the OS variables are not defined, only IOCB and DCB. char dl[] = {112,112,112,66,0,0,130,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,65,0,0}; The DLI can be writen using the header defines, DL_*, and as Avery says, you must ensure that the data don't cross 1k boundaries. There are two ways to do that, one is copying the array to a runtime-allocated buffer already aligned and the other is defining your own aligned segment, as: #pragma data-name (push, "DISPLAY_LIST") char dl[] = { DL_BLK8, DL_BLK8, DL_BLK8, DL_CHR40x8x1 | DL_LMS, 0,0, DL_DLI | DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_CHR40x8x1, DL_JVB, 0, 0 }; #pragma data-name (pop)Note that you will need a custom linker script with the DISPLAY_LIST segment defined. Assembly DLI routine: .export _dli COLPF2 = $D018 WSYNC = $D40A .code _dli: pha ; save registers lda #$40 sta WSYNC sta COLPF2 pla ; restore registers rti Also, here you can include "atari.inc" and use the already defined equates, all the OS and hardware equates are there. Edited January 13, 2015 by dmsc Quote Link to comment Share on other sites More sharing options...
Shawn Jefferson Posted January 13, 2015 Share Posted January 13, 2015 (edited) There are some problems with this code: I knew there would be. It was test code I dug up quickly... Maybe I shouldn't have posted it as I wouldn't want to pass on too many bad ideas! Display lists cannot cross a 1K boundary. This will fail randomly depending on where the compiler puts this array. Yes... Some projects I've written I've manually adjusted the location of the data segment to make sure it didn't cross a 1k boundary. This code doesn't I believe... you do have to keep that in mind, for sure. Use the linker config for that in cc65. Crash if display list currently has DLI instructions in it, especially if it's garbage. This is most prone to occur if the active display list has been overwritten with garbage (sloppy loaders). DLIs should be shut off first, then the display list and DLI vector changed, then DLIs re-enabled. This only turns off DMA after vertical blank, and only if there isn't an IRQ running at that time (keyboard). To do this safely, you have to wait for vertical blank and then hit SDMCTL=0, DMACTL=0, or alternatively, use one of OS timers 3-5 to wait for stage 2 VBI to run. To safely change the display list with DLIs are you saying you pretty much have to do it in a VBI? So that I know better for any future code I may write, what would the safe process look like to enable a display list with DLIs? Edited January 13, 2015 by Shawn Jefferson Quote Link to comment Share on other sites More sharing options...
phaeron Posted January 13, 2015 Share Posted January 13, 2015 To safely change the display list with DLIs are you saying you pretty much have to do it in a VBI? Thinking about it some more, yeah. It doesn't need to be in the vertical blank interrupt, but it needs to be within a single vertical blank. There are too many ways for it to break if some of the changes happen mid frame or span more than one frame. Let's take the first case with DLIs being enabled first. Assume that we turn off the display first with SDMCTL=DMACTL=0, wait for vertical blank to set NMIEN=0xC0, and then let the stage 2 VBI restart the new display list by copying SDLSTL/SDLSTH -> DLISTL/DLISTH and SDMCTL->DMACTL. If that doesn't happen that frame because stage 2 VBI is skipped, we can crash. Why? Because ANTIC can fire DLIs even if display list DMA is disabled, if the last display list instruction that was read had the DLI bit on. DMACTL bit 5 only disables display list fetch and not the display list. DLIs firing randomly = badness. Therefore, turning on DLIs first is not safe. Okay, so what if we swap it around so the display list is set up first, and then enable DLIs? Well, the first problem here is that stage 2 VBI can be skipped, so to actually enforce this ordering you'd need to wait for it, such as by setting up and polling OS timer 3. Even then, if you don't immediately enable DLIs before VBLANK ends, you've got part or all of a frame where the display list is running without its DLIs. At a minimum that will look like an ugly glitch, and it can screw up the display list for the next frame and lead to a DLI crash. So we're not any better off this way. So, what we're left with is that we definitely need to flip on DLIs and swap the display list in the same frame. The easiest way to do that is to wait for vertical blank, do the required register changes manually, and modify the shadow variables at the same time so that if/when stage 2 VBI runs it doesn't screw things up: // Wait for vertical blank. // This loop will end either be immediately before the VBI // or just after it. If it stops before, the VBI will run // a few cycles later, so everything is still good. while(VCOUNT < 124) ; // change the display list interrupt vector, now that DLIs are // off VDSLST = (unsigned)my_dli; // change the display list // - safe to do this without protection around the lo/hi byte // writes since we're in vertical blank, and if the VBI // happens to run in between, we'll re-write the hardware // registers afterwards with the correct values SDLSTL = (unsigned)display_list; DLISTL = (unsigned)display_list; // enable DLIs // - safe to do as we are still in vertical blank and the // display list hasn't started yet NMIEN = 0xC0; // re-enable the display // - can't rely on stage 2 VBI to do copy to DMACTL as (a) it's // already run by now and (b) we *HAVE* to start the display // list next frame SDMCTL = 0x22; DMACTL = 0x22; Most programs have low enough VBI or IRQ overhead that they can rely on this routine running atomically within VBLANK (22 scanlines minimum). In a game with a long VBI or faster timer IRQs running, it may be necessary to do this within the VBI. However, in that case you've probably already swapped out the OS VBI handler anyway. It's a pain to get all this correct, but I see a lot of random failures in the wild from programs that don't robustly turn DLIs on or off, especially when loaded from bad loaders that let the active display list get trashed before running a program. You really don't want to try to debug the mess that occurs when DLIs run with the wrong timing and start nesting. By the way: if you absolutely want to make the first screen switch of your program bulletproof, even this isn't quite enough -- you need to swap in a blank frame first. Hint: vertical scrolling. 1 Quote Link to comment Share on other sites More sharing options...
danwinslow Posted January 13, 2015 Share Posted January 13, 2015 hah! That'll teach ya to post code, Shawn! Phaeron had some interesting things to say about actual runtime problems with the code. DMSC, that's a cool alignment idea, you are assuming that linker script would manually set the address for the segment to an aligned address, correct? The personal style issues that have no bearing on the correctness of the program I probably would have left out. Anyway, I'm glad it was you Shawn, my code would have been much worse. Quote Link to comment Share on other sites More sharing options...
dmsc Posted January 14, 2015 Share Posted January 14, 2015 Hi! DMSC, that's a cool alignment idea, you are assuming that linker script would manually set the address for the segment to an aligned address, correct? Yes, you add to the linker config, in the SEGMENT section, a line like: DISPLAY_LIST: load = RAM, type = rw, define = yes, align = $400;The line must be after INIT and before BSS (if not, the C library would be confused), the best place is just after the DATA section. Note that the linker script does not support multiple XEX headers in the generated file, so all the space before the section will be zero-filled in the resulting XEX. Also, if you have multiple data with address restrictions, it is best to define new RAM sections with specific sizes so that you can manually fit data to sections, with the linker verifying that the data fits. Linker configs are a complicated, but adds a lot of flexibility to the compilation. Quote Link to comment Share on other sites More sharing options...
Shawn Jefferson Posted January 14, 2015 Share Posted January 14, 2015 (edited) hah! That'll teach ya to post code, Shawn! I look at it this way... I learned something that I wouldn't have learned if I hadn't posted that code, and I hope the OP's question was answered as a result. I did post it with some trepidation, since most of the coders here are way, way ahead of me. (Wait till I release the code from the Moria port I'm doing... that will give some folks a laugh I'm sure.) The personal style issues that have no bearing on the correctness of the program I probably would have left out. Those were all good points too, I always ignore those equates and header files that are there to make life easier... the technical issues around safely setting up a display list with DLIs are much more interesting to me though. Anyway, I'm glad it was you Shawn, my code would have been much worse. You've got the next one. Edited January 14, 2015 by Shawn Jefferson Quote Link to comment Share on other sites More sharing options...
ilmenit Posted January 14, 2015 Share Posted January 14, 2015 My way of dealing with alignments in CC65 is just to use higher STARTADDRESS in configuration file. Under it I put all screen data, sprites and also RMT music on constant addresses. Then there's no need to define aligned sections as alignments usually waste some memory. Quote Link to comment Share on other sites More sharing options...
billkendrick Posted May 28, 2015 Share Posted May 28, 2015 Added a very basic DLI to the little "hello world" I've been working on (in short stints, once every 6-18 months, it seems) since August 2013 http://newbreedsoftware.com/atari/hiworld/ One thing I found was that the screen went 'wonky' if I fiddled with DMACTL and SDMCTL. If I just left them as-is, everything worked fine. (I added the "while (VCOUNT<124)" trick, to avoid being another "in the wild" case of something crashing). Thanks Phaeron! 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.