Lesson A1 -Binary Coded Decimal | |
Lesson A2 - Interrupt Mode 2 |
Binary Coded Decimal is Super-Simple... rather than storing 0-255 in a byte... we just store 0-9! Why do we want to do that? well converting numbers stored in HEX to ascii for the screen can be a real pain! but Binary Coded Decimal makes it easy... lets take a look! |
Number | 1234 |
Hex | &04D2 |
Binary Coded Decimal | &01 &02 &03 &04 |
Packed Binary Coded Decimal | &12 &34 |
In the example above, the most significant byte was stored first, so 1345678 would be stored &12 &34 &56 &78... in this lessons examples we're going to store things backwards, so it would be stored &78 &56 &34 &12.... this is because many of our commands will start from the least significant byte, so we can save a little time this way!... if you don't like it, you can always change the code! |
Some of our commands WILL need to
start from the end of the data... so we're going to create a
simple function to alter HL and DE to move to the end of a BCD
number B bytes in length... Our BCD commands are going to store the number of bytes in B... so to get HL and DE to point to the last byte, we need to add B-1 We're doing this to HL and DE because most of our commands use two parameters |
Showing BCD is easy! First we need to move to the highest value byte... then we convert the top nibble to Ascii - and show it! Then we do the same for the Bottom nibble... and show that as Ascii too! We then repeat until B is Zero, to show all the bytes... Much easier and faster than if we were storing HEX! |
Try the program to the right! Put a
breakpoint on the first command, and watch how DAA alters A and
the Carry flag to keep things right! DAA also uses the H Flag for its own purposes... but that's not something we need to worry about! Basically, We can do ADD/ADC or SUB/SBC on a byte.. and DAA will fix things... we just need to alter the next byte if the carry flag is set! |
Ok,
so
we've shown some numbers to the screen, but we actually need to
be able to do some maths too!... fortunately the Z80 allows us
to use our typical ADC and SBC commands to do addition and
subtraction in Binary Coded Decimal! How? well its simple
actually! |
Now we know how to use DAA things
aer pretty easy... Lets create a function that adds BCD value at (HL) to the one at (DE)... for B bytes We do OR A to clear the carry flag... then we process each byte (from smallest to biggest) using ADC to add the values... DAA sorts out our numbers, and we store the result, then we just repeat... ADC will add the carry if there was one caused by the last addition |
Subtraction is basically exactly the same, we just use SBC this time instead! |
Finally, we may want to compare a
BCD value to another, and see if they are equal, or which is
higher... If we start from the most significant byte, we can just do our CP to compare, returning as soon as we find a byte that is different. If both are the same, we just clear the carry flag and return Z |
Ok, We're done! All the commands today are run in the same way, by passing the address of one or usually two parameters in HL and DE, and the length in B, Remember, the BCD data is in reverse order! If you want to see more, take a look at "Lesson_A1.asm" in the Samples.7z! |
This is not called by the user
directly, it's called by the other modules and shows HL as hex,
surrounded by ** symbols If needed it will pause the system after showing the Breakpoint or register |
This module will pop a register and show it to screen... because
we need HL, but don't want to use the stack we back it up with
self modifying code... Then we pop a pair of the stack... this will be the return address... Next we swap HL with the pair on the stack... HL will now be the pair pushed before the call, and the return address will be at the top of the stack... All that's left to do is show HL... then restore HL and return to our program! |
This module is super simple! all it does is get the return address off the stack.. show it, and return! |
More complex than the last
version... this one overwrites the 3 bytes preceeding the return
address... These will be the CALL command that called the monitor - the end result is that the monitor is only called once... so it can be used in a repeating loop to find out the program counter location (or if the loop even ran - if your program is crashing) without slowing down the program with lots of pauses. |
Lesson
A2 - Interrupt Mode 2 We've looked at Interrupt mode 1 before in Lesson 7 ,but there's more stuff we need to cover before we've mastered them! Lets take another look now, quickly at Interrupt Mode 0 (which is useless) Mode 1 again, and then we'll look at how to make use of the less used Interrupt Mode 2, and why it may really help you! |
You may see
examples online that use a block of 257 &FF bytes in
the rom as the IM2 block, which would jump to &FFFF... this
works OK on the 48k, but does not work well on the 128k systems. If you want to play it safe you can't do that... but using 257 bytes for the IM2 block may be tough on a 48k system! |
The Assembly code for using
the IM2 interrupt handler is pretty easy, it just takes a lot of
memory! First we disable interrupts, Then we put a jump to our interrupt code at &8181 Now we fill &8000-&8101 with the byte &81 We set I to &80, and the interrupt mode to IM2 for good measure, lets use the spare space for the stack pointer we're done, so we just enable interrupts! |
Remember that you
don' t NEED to use interrupts if you don't want to! They help
make timing easy (for updating music) and allow you to switch
colors midscreen, but if you don't need things like that, just
keep interrupts turned off! You'll save some speed! and won't have to worry about all this silliness |
If we want to
support the GBZ80 and Z80, we need to create two copies of these
macros - one which compiles to the true Z80 command and one that
compiles to the GBZ80 equivalent... If we use only the macros in our code, we'll be able to support both GB and other systems seamlessly!... if you want to see this in action take a look at GrimeZ80! |
The first thing we will need to do
is allocate some memory for the registers we cannot use, the
examples shown are for the Gameboy, We will use these to store the values the registers would hold. |
Swap the DE and HL register. This one's easy, we just push HL and DE onto the stack... and pop them off in the opposite order! |
LDI is a popular command, but the
GBZ80 doesn't have it, fortunately it's easy to fake... load from (HL) save it to (DE), INC HL,DE and DEC BC The real command doesn't affect A, so we push it to the stack while we're working |
Halt is a bit odd... the GBZ80 does have it, but there is a bug, and it tends to skip the command after the HALT command in some cases... therefore we will create a macro to do us the job! |
on a regular Z80... DI
HALT will lock up the CPU, but on the GBZ80 it will not, however
there is a bug in the command... The next command following a HALT will be skipped, so we need to put a NOP after the HALT command On the GBZ80 HALT is used to save power, hence the difference in it's functionality |
DJNZ is pretty simple - but the GBZ80 doesn't have it... All we need to do is DEC B... then JumP if it's not Zero |
I use the R register as a random number source, here is a simple 'random' number generator, which uses the last R value, and the state of various registers for a randomization source. |
This command
will emulate the use of R as a random seed... if you were using
it for something else, you'll have to write your own command! |
Loading A to or from the fake registers is very easy... we just Read or Write directly into the ram address we're using for the fake register. |
If we need to compare with one of the 8 bit parts of IX or IY, we can do this by setting HL to the address of the register, and using CP (HL)... this means A and F will be set correctly. |
We do pretty much the same for INC and DEC... |
If we're reading into our fake registers, we don't need to use HL at all, we can just do it with the accumulator. |
When we want to transfer from a register like IXL, we can do this via the A register, then transfer it to the B reg. |
While we can make the program code read only, we still need
enough 'data space' for the bytes that were being modified... All the labels that were being modified... and the number of bytes that were being changed need to be allocated in the RAM of the machine. |
This is pretty much the simplest command... a is being loaded
with a value, and that value is being altered with self modified
code. Note the +1 is removed... it now has no meaning, as it was being used to point to the second byte of the line being modified Of course 'PLY_Track1_InstrumentSpeed' is now just an 'allocated' byte in the 'dataspace' mentioned above.... it is defined by an EQU command The same can be done with 16 bit commands like LD BC,xxxx |
Was: |
Now: |
In this example, the parameter in an ADD command was being modified - we need to add an address... however there is no ADD (addr) command... so I have swapped around the commands, and loaded the value into A and added E... rather than loading E into A and adding the value. | Was: |
Now: |
Here we have an instance where the
Value being compared is being modified. We can make this into ROM, by storing the value to compare to in RAM... and pointing HL to that ram store... then using CP (HL) |
Was: |
Now: |
This JR substitute only works for POSITIVE JR commands... also if the JR command only has one or two possible versions, it would probably be easier to replace it with a set of CMP and JR Z commands! |
It wasn't needed in Arkostracker,
but there may be times we need to use a Self-modified CALL We can emulate this by using the EX (SP),HL command to swap a value from a memory address onto the top of the stack... This will also work with a self-modified JumpP... Just use JP FakeCall instead! |
Was: |
Now: |
These are just
examples of possible ways to remove self modifying code - There
will be better ways depending on the exact circumstances of the
program... If you really get stuck, maybe you can run some or all of the code from ram - if you have enough free! |