News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu

VSync help! (Sharp MZ-80A !)

Started by kelp7, 09:49, 06 February 14

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

kelp7

Can you imagine the headache of having interrupts that aren't actually sync'd with anything? As that's what I'm going through right now.
I've not done any coding yet, just trying to get the theory sorted out in my head first. My first theory was that, at the beginning of my program, I would wait for vsync to begin and then set the 50hz interrupt clock counting down (initialise counter #2 with value &0276 which should divide a 31.5Khz input clock to 50hz). Then when the interrupt occurred, wait for vsync and do drawing to screen. Then set interrupt up again.


But if my interrupt code itself always waits for vsync, I wonder if I'm just going to get a lot of missed frames and flickering. Perhaps I should just reset the counter to &0276 the very moment the interrupt occurs? Arghh..... I think ... i'll just have to try some options out and see what happens....

kelp7

(In fact I don't think I have to reset counter to &0276 as a 'rate generator' mode of counter always returns to the original value you gave it when it reaches zero. But I would have to reset the counter #3 to &0001 to get the interrupt to happen again, otherwise when counter #3 reaches zero it'll go straight through to &FFFF which is very unwanted)

arnoldemu

Do exactly as you say, wait for a known position, e.g. vsync, and then setup the 8253.
Now you can rely on it being synced I hope.

As for the counter, there is probably a mode for the 8253 where you can set it to reset it's count and restart.
So it would count down to 0, reload and repeat?
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

arnoldemu

Quote from: redbox on 13:40, 15 May 14
Yes, also here and here.  Also, this is interesting.

I've seen it debated before that IM2 doesn't always jump to I*256+databus AND &FFFE as defined in the Z80 documentation, but sometimes just to I*256+databus.  Might be something to bear in mind...!
it's always consistent.
it doesn't do an &fffe ever.
it always takes the value on the bus and uses that as the basis.

So this is why the table is 257 bytes long. In addition because on the cpc the databus value is not fixed, we have to consider all 256 byte values are possible. Then because any is possible this means it can read from any table position so we need the same address each time. So that is why we fill it with a single byte and this is why the address is formed from the same byte for high and low.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

kelp7

Quote from: arnoldemu on 13:07, 21 May 14
Do exactly as you say, wait for a known position, e.g. vsync, and then setup the 8253.
Now you can rely on it being synced I hope.


Yes, I think that's the conclusion I came to in the end. I don't think I need to wait for vsync within the interrupt code itself. Just do it once at the start of the program and given that the rate generator should maintain the same frequency of counting (no matter what happens), i should be fine (I hope!).


The first thing I'll try will be one single basic change at the top of the screen I think and see if I can maintain it in a stable way with one single interrupt.


At a much later stage I envision setting up a variety of different interrupts to carry out minor changes at different vertical positions of the screen. That's really what will be the most useful in games. I guess it'll take some playing around with different 8253 settings to trigger each different position of the screen. So one interrupt would set up the next interrupt etc.


My other worry that occurred to me today was whether there might be slight differences between individual MZ-80As. Perhaps I would get it working on my MZ only to find that the interrupts which worked correctly (as triggered at vsync) might be slightly 'out' for another person's MZ. Horrible.... but.... worst case : I could provide an in-game menu that lets you 'calibrate' the graphics settings to your own MZ....




redbox

Quote from: kelp7 on 18:29, 21 May 14
At a much later stage I envision setting up a variety of different interrupts to carry out minor changes at different vertical positions of the screen. That's really what will be the most useful in games. I guess it'll take some playing around with different 8253 settings to trigger each different position of the screen. So one interrupt would set up the next interrupt etc.

Sounds cool, similar to vertical rupture... took the CPC coders a while to work out a way of doing that  :)

kelp7

Quote from: redbox on 20:06, 21 May 14
Sounds cool, similar to vertical rupture... took the CPC coders a while to work out a way of doing that  :)


It's the only way I can think of to carry out lots of minor fx on the screen without taking tonnes of processor time away from the main game code ! :) Just have a sort of daisy chain of interrupts where each new interrupt sets up the timing for the next one...
By the way, I actually mean horizontal effects but in terms of their vertical position on the screen. So perhaps an inverse-mode line every 50th line of the display. That sort of thing. I'm not sure I can convince the machine to do anything that splits the screen on a horizontal way, heh, any time i've tried to split a single horizontal line into multiple fx it simply can't seem to handle it and flickers like mad!

kelp7

#107
Quote from: arnoldemu on 13:07, 21 May 14
As for the counter, there is probably a mode for the 8253 where you can set it to reset it's count and restart.
So it would count down to 0, reload and repeat?


I think that's only for the Mode 2 Rate Generator, here's a bit of info:


Intel 8253 - Wikipedia, the free encyclopedia


So, my counter #3 of the 8253 is my interrupt trigger. I will set this to &0001 and it will reach &0000 when counter #2 is finished (the two are interlinked).


I don't believe it's a function of Mode 0 (the interrupt one) to reload itself with the initial value once it has reached zero. However my mode 2 rate generator (for counter #2) does do this.


Just for further info, this is how it's all wired up in the MZ-80A (ignore counter #0 which is for music / sound):


kelp7

(I realise that's not very clear, may scan it properly from my own manual if anyone's interested)

kelp7

Just a small update : I did actually get an interrupt of my own working with the 8253 end of last week but with problems. I just wanted to do something basic to begin with, so I set up an interrupt which would print the letter 'B' to screen memory and then increment HL (which held the screen memory address) to move along the screen memory every frame. So it would take 20 seconds to fill the screen that way (I guess, 1000 characters at 50 a second). This would happen while an infinite loop of NOPs would just sit there doing nothing. The actual result was that I got a single 'B' on the screen in the expected place but then nothing more. I couldn't tell if the program had frozen / crashed or what was going on. I was using the stack during the interrupt to preserve my HL screen memory address as I was using HL in the interrupt itself to re-set up the 8253 then POP it back off the stack again at the end. My only conclusion is that maybe because I'd forgotten to set up the Stack Pointer perhaps this could have caused the issue (code being overwritten maybe). But alternatively, perhaps I just didn't set up the 8253 correctly. So tonight will be writing the same program again and put some debugging code in to print the counter values to the screen (to make sure they keep rolling over once the interrupt's occurred). I guess at least I did get one instance of the interrupt occurring successfully, so something was right in my code!

arnoldemu

as with any interrupt handler make sure you push/pop the registers you are changing.

Also do an ei before the ret.

on mz80 you may need to do:

ei
reti

Perhaps you didn't setup the stack correctly, so make sure that it is in a good place in ram.

:)
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

kelp7

Okay, that is all excellent advice :) thanks! I didn't realise I might need to execute an EI. I think I'm okay with the usual RET (as that is what they use in the Kernel when the RTC interrupt occurs). But, my problems are more difficult than that. I've just written the program again and this time the main program loop just dumps the contents of the counters to the screen. Counter number 1 is working correctly, it is very fast and re-loads its value at the end of each count automatically. But Counter number 2 (which is the one which should toggle between 1 and 0) doesn't re-load (or latch) its value. Annoyingly it looks as if it counts from 1 to 0 and then to 65535..... argh.... i will get to the bottom of this though. I even put in some specific 'latching' control word when programming Counter 2 but that doesn't seem to have had an effect as far as I can see.

MaV

I don't have any experience with the 8253, but I do read your findings with much interest. :)

So, what you described here corresponds to MODE 0 where the counter continues the count after it reaches zero:
http://www.intel-assembler.it/portale/5/Programming-the-Intel-8253-8354-pit/howto-program-intel-pit-8253-8254.asp

If you can you should change the counter mode.
Black Mesa Transit Announcement System:
"Work safe, work smart. Your future depends on it."

kelp7

Thanks for that. Yes, I can see now that maybe I just need to re-check the code I'm writing. My interrupt handler was set to re-load Counter 2 (the one that is mode 0) but maybe I was doing something wrong. Will try again this weekend :) Lots of testing to do with different counts etc I think, then I can work out exactly what's happening. I presume that I do have to use Mode 0 on counter #2 as that is the one which generates the interrupt. The output from counter #2 is AND'd with the interrupt mask, so I need to have a counter mode where the output is low all of the time except when the counter reaches zero.


Anyway, lots of playing around to do! Will report back (and will actually put my code in here too on my next posting if I'm still getting problems, always good to have other people checking the code!)

kelp7

#114
Okay, i've got it working. But not in the way that I want to. In the end, I went back to priming the counters by CALLing the kernel routine which sets the RTC. Then my program started working! I wrote a simple interrupt handler that would print the letter 'B' to the screen memory address currently held in HL, then INC HL and return (after setting Counter 2 back to '1' having become '0'). Counter 1 was set to a 50hz count. So every 50th of a second it printed a 'B' to the screen.


So it seems there is something I'm not doing in my own code which the kernel *is* doing. Cannot see what this is! They're doing one extra load of counter 1 with a strange value of &000A and I don't know why. Then later in the same routine they set it to the correct value for doing their 1Hz count for the RTC. Other than that, I wondered whether I needed to set the interrupt mask (which is bit number 2 of address &E002). So I put a SET command in for this bit at this address but my code didn't work.


Very weird. Guess there's something I'm missing. So that's the first thing I need to sort out. The 2nd thing is that reading up on Mode 0 of the 8253 : it seems to take 1 single clock count before it starts counting. So annoyingly if I set up a count at 50hz when I reset the counter (after it's reached zero) it'll take another clock cycle to start up again, so it's going to be more like 49hz....argh..... so my second problem is resolving this matter. I think maybe at some point I will be trying a different mode for Counter 2 (like MaV suggested above) and perhaps choose the Square Wave mode.


Still, need to sort out the fact that my own code doesn't work first and work out what the kernel is doing differently :)

kelp7

Yeah, okay, I had stupid moment (not the last I'm sure and certainly not the first). I woke up this morning thinking about the fact that I hadn't actually used the DI or EI instructions hardly at all. I used 'EI' once at the end of my interrupt handler. But it seems the MZ doesn't start up with interrupts enabled. So the reason the kernel routine (for setting the RTC) allowed my own routine to work is because the kernel routine enables interrupts. Right, glad that's out of the way because now I am able to set up my own 8253 control words and counters and I've tested my usual print routine this morning and it functions exactly as I had always expected! Here's the program if anyone's interested:



LD SP,&4000 ; arbitrary address somewhere in RAM (doesn't matter for these purposes)

DI
LD HL,&1039 ; set up new interrupt handler address
LD DE,MYINT ; address of my new interrupt handler into DE
LD (HL),E ; by default the CPU jumps to &0038 on interrupt
INC HL ; this is in ROM on the MZ and always jumps to &1038
LD (HL),D ; 1038 is always a JP instruction

LD HL,&E007 ; load HL with control word address of 8253 chip
LD (HL),&74 ; Set counter #1 as a rate generator
LD (HL),&B0 ; Set counter #2 as an 'interrupt on terminal count'
DEC HL
LD (HL),&01 ; Least significant byte of counter value for counter #2
LD (HL),&00 ; Most significant byte of counter value for counter #2
DEC HL
LD (HL),&76 ; Least significant byte of counter value for counter #1
LD (HL),&02 ; Most significant byte of counter value for counter #1 (divide input clock of 31.5Khz down to 50hz)

EI
LD HL,&D000 ; first memory location of screen memory
LOOP: NOP
JR LOOP

MYINT: LD (HL),&02 ; print letter 'B' on screen in current screen memory location held in HL
INC HL
PUSH HL
LD HL,&E007 ; re-setup counter #2
LD (HL),&01
LD (HL),&00
POP HL
EI
RET



This all worked fine. All that I have to sort out now is the fact that, although I have set the clock to 50hz, the program took roughly a minute to fill the screen with the letter 'B'. This I can only attribute to the fact that Mode 0 of the 8253 means you have to wait a full clock cycle before the counter starts counting down again. So, as I said in my above post, it will essentially take an extra frame before it prints the 'B' again.


I'm hoping I can resolve this by switching Counter #2 to a Mode 3 counter which is a square wave. With this toggling the output on and off it should still produce the interrupt, then I have to work out the correct value for Counter #1 (which is used as Counter #2's input clock).


All good though so far!! :)

kelp7

Phew, more playing around this afternoon with different 8253 modes. I thought I could get away with a square wave generator. But, of course, the output remains high for half the period of the input clock. So naturally, all of the time that the output is remaining high it triggers the interrupt. So I managed to fill the screen with 'B' in an extremely fast time (as fast as the interrupt can be triggered basically). Tried many combinations so far, even setting Counter #1 as a rate generator itself but can't quite get my head round this at the moment. There must be a combination of 8253 modes here which will just pulse 'high' every 50th of a second!!! Going to take quite some work to sort this out I think.


And I realise why it took roughly 60 seconds with my previous routine to fill the screen. I bet the 8253 takes one full clock cycle to start counting, and then because i've just set the counter to '1', it then counts the '1' for one clock cyle and then finally there's one more clock cycle when it is at zero? maybe!


Somehow I'll get a 50hz interrupt out of this thing if it kills me!!!!!

kelp7

Just realised that if I use a square wave (or any other 8253 mode where the output is high for a long period) I could just set my interrupt handler to do what I want it to do and then point the interrupt vector to a new handler that just waits for the output to return low again and sets the original vector back up again :) getting the hang of this.....slowly....!

kelp7

After much thinking I've realised that the above wouldn't be any good as it would tie-up any potential game program etc for too long per frame. So..... alternatives:


1) The first interrupt handler ends by executing a DI and therefore disabling all further interrupts. Then the game code has a part of its own main game loop that checks for the state of the square wave counter, once we know it's outputting a zero again (output gone low) we can re-enabled interrupts (EI)

OR

2) Go back to basics and just use the original set-up of a 'rate generator' on counter #1 and a 'interrupt on terminal count' on counter #2 and just try and come up with a working set of rates for counter #1 that produce what I'm after. Maybe daisy-chain some interrupt handlers and the very last interrupt handler (towards the end of the frame) then waits for vsync to begin and sets up the first 8253 values again ready to jump back to the 1st interrupt handler again as well.


Heh, not sure how clear that is but these are the only feasible solutions now I think!


(I realise I have been quite enthusiastically wordy in this thread recently, will probably just get down to the coding now and have something working next time I write in here :) )

kelp7

#119
Haven't yet been able to get the square wave generator of the 8253 working properly. In the end I've gone back to using a rate generator on Counter #1 to clock the Mode-0 "Interrupt-on-terminal-count" Counter #2. I've managed to get the correct rate for a frame by going for a 150hz clock into Counter #2. It seems that's what I need for my 'actual' 50hz screen frame rate. Using 150hz I now fill the screen with 1000 characters in just 20 seconds.


That was a good breakthrough, just working out how this 8253 chip does its counting! Basically, if I load Counter #2 with a value of '1' it first waits one full clock cycle (one 50hz) before starting to count, then it spends the next 50hz counting from '1' to '0' and finally one further 50hz once it's reached '0' before triggering the interrupt.


I've now been coding a screen changing interrupt (inverse mode on for eight scanlines and then off). This has been a REAL headache though. My code waited for vsync to begin and then triggered the 8253 counters with 150hz etc etc. But what I got was extreme flickering all the way across the screen. So my fixed height block of inverse colour just moved its vertical position all over the place. I got some very neat effects however by altering the amount of Hz on my rate counter. At one point I had achieved smooth scrolling (both up and down the screen) of this fixed height bar of inverse. Looks great but not really what I want.


It's going to be really really difficult to get this interrupt sync'd properly I think. In the end, I have stuck a 'wait for vsync' in the interrupt handler itself. Once I did this I could get a stable bar of inverse at the top of the screen where I first wanted it. The sad thing about this is that it wasn't actually stable until I actually changed the rate generator on Counter #1 to 366hz!!! This is crazy and will tie up any program I want to write. I actually wrote the object code out to a file and loaded the file into BASIC to see if it worked and created my inverse bar at the top of the BASIC screen. It did indeed. But you could also no longer use BASIC at all! It didn't respond to keypresses or anything.


I imagine this is all down to the fact that my interrupt is not sync'd properly with the start of the frame. So when I wait for vsync in my interrupt handler it may already have started drawing the full frame. So it has all that time to wait before then waiting for vsync to begin again before drawing my bar.


Need to get this 366hz down to the approx. 50hz somehow!


Arrghh... headache time. Well, the only thing I can come up with is to make sure that I trigger my interrupt as close to the end of the frame as possible so that when it carries out its wait-for-vsync it doesn't have much time to wait for this. I guess this will have to happen when I set up the counters in the first place. Make sure they don't finish counting down until we are very close to the end of the frame.


Oh well, onwards........

kelp7

(also forgot to say that, obviously with the way the 8253 counters work, the 366hz is more like 122hz when the interrupt actually occurs. But still this is very nearly treble what I need anyway. Gotta get this 366hz down to 150hz so that there is plenty of time for the main software to still function! At the moment the 122hz interrupt basically happens so frequently there is no time for anything else, especially as the interrupt handler waits for vsync)

kelp7

#121
Kind of got this to work last night, all with interrupts.


It was a struggle though and I'm still at a confusing place with this but so far I have written a routine to turn on inverse-video mode for 8 scanlines using just the 8253 interrupt. The process was:


1. Set up the 8253 counters to a low value to launch interrupt handler #1 soon as possible. Spend the rest of the time waiting for key-press to exit this whole program.


2. Interrupt handler #1 waits for vsync then switches on inverse mode. Sets up 8253 counters for 8 scanline wait and sets up new vector to interrupt handler #2


3. Interrupt handler #2 switches inverse-video mode off and sets up 8253 counters and vector to re-trigger interrupt handler #1


Now, this sort of worked. I had to play around for ages with different values of counters to get what I needed. I even have, in the end, had to use a mixture of 8253 delays and manual delays (a very small 8-bit decrement counter). Strangely the 8 scanlines were very very flickery to begin with and did seem to extend way beyond 8 scanlines on certain frames. All very strange.


In the end, I got it stable (and only 8 scanlines high) by forcing interrupt handler #2 to launch interrupt handler #1 as soon as possible.


This is not a good thing and suggests that the flickering may have been caused by the 8253 not even being fast enough to carry out the changes I was asking of it within one frame. I can't believe this is the case though. The clock input to the 8253 is 31.5Khz. Even taking into account the fact that this is then divided down (by the effect of using a mode 0 counter triggered by a mode 2 counter), I should still be left with enough of a clock cycle which is faster than the usual 50hz of a frame.


Lots more experimenting to do. Next, I will try and do a second 8-scanline high bar of inverse somewhere else on the screen as well and see how it copes!

arnoldemu

Great to read that you are making progress with the interrupts.

Assuming the built in monitor is outputting something close to 50hz.
The line time *may* be 15Khz. The 8253 has an input clock of 31.5hz so it's close to twice per line.
It's edge triggered, so a value of 1/0 would probably be 1 scanline.
So 4 is probably 8 scanlines.

This is all guesswork.

Do you know of the refresh rate and the line frequency of the internal monitor.
Is there a document that describes it?
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

kelp7

Ah, that explains that then. There is no way it's possible to get the full 31.5Khz clock rate when you are using two inter-connected 8253 counters. I might get a third of that speed at its fastest.


Perhaps there might be the information you need here:


http://www.sharpmz.org/mz-80a/download/80asm.pdf




kelp7

And, as ever, always appreciate your advice arnoldemu :)

Powered by SMFPacks Menu Editor Mod