News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu

CPC Plus changing the palette in assembly

Started by Trystan, 08:52, 06 March 23

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Trystan

I've recently gotten back into BASIC, which led me to z80 assembly (I've done quite a bit of 6502 but the register heavy approach of z80 is new to me) and the CPC of all the 8 bit machines looks to have a good set of capabilities for experimenting with.

I've made a program that changes the palette, draws a pricture to screen and then ends when you press space but to make it so that the firmware didn't overwrite the palette (I believe due to the palette refreshing every now and then to enable flashing colours) seems to need quite a bit of work and I wanted to make sure I hadn't missed a trick.

My current method is to use a basic loader to set himem low so there's space, set the screen mode and put some palette values into memory (at &8000) which the assembly can use. This then runs the .bin file:
  - 1, unlock plus capabilities by writing the 17 magic unlock bytes
  - 2, disable firmware interrupts by writing EI,RET to &0038 (after backing the original data up so they can be restored at the end)
  - 3, page in ASIC RAM
  - 4, write palette data directly to &6400-&641f
  - 5, page out ASIC RAM
  - 6, load picture data to video memory
  - 7, so that the program doesn't end (causing firmware to rewrite the palette) I go into an infinite loop
  - 8, but I want to be able to end the program. Using a firmware call like &BB06 rewrites my palette though so I used the keyboard scanning routine from https://www.cpcwiki.eu/index.php/Programming:Keyboard_scanning to wait for the space key
  - 9, finally restore the interrupt at &0038 after space has been hit or the cpc becomes super unstable and hangs on most commands.

Anyway, it works fine but I wanted to make sure I hadn't missed something that would prevent me from having to gut the firmware interrupts to keep my palette. So an alternative to my points 2, 8 and 9. I know I can do it by putting some B-ASIC in my loader but I'd prefer an assembly solution, if there is one.

andycadley

Using the Plus features from BASIC is a bit of a faff, because you essentially have to unsubscribe the firmware ticket event responsible for setting the palette colours on every frame (and managing things like flashing colours).

The easy way is just to use the B-ASIC RSXs which take care of that for you, as well as giving you a bunch of BASIC commands to handle managing all the Plus hardware.

If not, there is some code in AA somewhere I can probably dig out.

andycadley

#2
Issue 114:

https://photos.app.goo.gl/mAMbAGDTFPkbKe377

CALL &8000 to disable the firmware interrupt (so Plus colours work) and &800C to restore normal behaviour.

roudoudou

could be cool to have this in ASM :)

(translating with mind, maybe i miss some)
enable ld hl,#8017 : ld bc,#FF01 : ld de,#8012 : jp #BCD7
disable ld hl,#8017 : jp #BCDD : xor a : ld (#B7F8),a : ret

is it ok? there is dead code at the end, maybe your picture is wrong, and this is a CALL #BCDD before the xor a

andycadley

Not sure, I was thinking the same thing though. Would be good to disassemble and put both versions on the wiki somewhere. Possibly with a better description of the issue than the magazine had (which is kind of wrong)

roudoudou

+ the value loaded to HL will probably need to be changed (with a label)
i lost my PDF about system vector so...

Trystan

#6
Thanks for the ideas, I'll have a look into it later. I've got a pdf of system vectors. According to that..

&BCD7 -  KL New Frame Fly
Action -  Sets up a frame flyback event block which will be acted on whenever a frame flyback occurs
Entry - HL contains the address of the event block in the central 32K of RAM, B contains the event class. C contains the ROM select address (if any) and DE contains the address of the event routine
Exit - AF,DE and HL are corrupt and all other registers preserved

&BCDD - KL Del Frame Fly
Action - Removes a frame flyback event block from the list of routines which are run when a frame flyback occurs
Entry - HL contains the address of the event block
Exit - AF, DE and HL are corrupt and all other registers are preserved

I'm not sure what's at &B7F8 (where the event is sending xor'd a), in the pdf I have (which is for the standard cpc range, not plus) it's 3 bytes after the second set of inks but it's not labelled

andycadley

It disassembles as something like:

KL_NEW_FRAME_FLY equ #BCD7
KL_DEL_FRAME_FLY equ #BCDD

enable:
LD HL, eventblock
LD BC,#81FF
LD DE,routine
JP KL_NEW_FRAME_FLY
disable:
LD HL,eventblock
JP KL_DEL_FRAME_FLY
routine:
XOR A
LD (#B7F8), A
RET
eventblock:
DEFS 8

#B7F8 must be some sort of system variable related to INK flashing (grimware doesn't say what it's used for) or indicating a colour has changed or something, I'm not entirely sure. The code is just setting up a frame flyback event to reset it to zero, so presumably it fools the system into not refreshing the colour palette.

Trystan

Thank you both for your input, that assembly code works perfectly. 

Between enabling the new event block and disabling it the plus keeps the palette I give it and I can still use firmware calls like &bb06 to get keyboard input (which I couldn't get working with my old method of completely disabling interrupts at &0038).

Animalgril987

#B7F8 is the OS 1.1 system variable "Colour Time". ( In OS 1.0 it's at #B1FD).

Trystan

Just to see if I could I put all of the information here into a set of RSX commands (mainly to see how it works) and it all seems to be working perfectly.

You can change colours with:
|plusenable
|flashdisable
|pageinasicram
poke #6400-601f,colours
|pageoutasicram
It should then keep the palette until you call |flashenable

Pretty sure all of this is mainly recreating B-ASIC but slower and with more rsx calls needed but it was a fun learning exercise. The next step might be looking up how to have arguments for RSX's to have a set palette command that pages in ASIC RAM, sets the colour then pages it out again.

org #8000
.kl_log_ext equ #bcd1
.kl_new_frame_fly equ #bcd7
.kl_del_frame_fly equ #bcdd

;;-------------------------------------------------------------------------------
;; install RSX

ld hl,work_space ;;address of a 4 byte workspace useable by Kernel
ld bc,jump_table ;;address of command name table and routine handlers
jp kl_log_ext ;;Install RSX's

.work_space             ;;Space for kernel to use
defs 4

;;-------------------------------------------------------------------------------
;; RSX definition
.jump_table
defw name_table            ;address pointing to RSX commands
                           ;list of jump commands associated with each command
                          
                           ;The name (in the name_table) and jump instruction
                           ;(in the jump_table), must be in the same
                           ;order.
                           ;i.e. the first name in the name_table refers to the
                           ;first jump in the jump_table, and vice versa.
jp UnlockPlus              ;routine for PLUSENABLE
jp PageInAsicRam           ;routine for PAGEINASICRAM
jp PageOutAsicRam          ;routine for PAGEOUTASICRAM
jp FlashDisable            ;routine for FLASHDISABLE
jp FlashEnable             ;routine for FLASHENABLE

;; the table of RSX function names
;; the names must be in capitals.

.name_table
defb "PLUSENABL","E"+&80     ;the last letter of each RSX name must have bit 7 set to 1.
defb "PAGEINASICRA","M"+&80     ;This is used by the Kernel to identify the end of the name.
defb "PAGEOUTASICRA","M"+&80
defb "FLASHDISABL","E"+&80
defb "FLASHENABL","E"+&80  
defb 0                     ;end of name table marker

; Code for the RSXs

UnlockPlus:
di
ld b,#bc
ld hl,PlusInitSequence
ld e,17

PlusInitLoop:
ld a,(hl)
out (c),a
inc hl
dec e
jr nz,PlusInitLoop
ei
ret

PageInAsicRam:
ld bc,#7fb8
out (c),c
ret

PageOutAsicRam:
ld bc,#7fa0
out (c),c
ret

FlashDisable:
ld hl, EventBlock
ld bc,#81FF
ld de,NoFlashRoutine
jp kl_new_frame_fly
ret

FlashEnable:
ld hl,EventBlock
jp kl_del_frame_fly
ret

NoFlashRoutine:
xor a
ld (#b7f8), a
ret

EventBlock:
def 8

PlusInitSequence:
db #ff,#00,#ff,#77,#b3,#51,#a8,#d4,#62,#39,#9c,#46,#2b,#15,#8a,#cd,#ee





andycadley

It probably is recreating B-ASIC, but it's fun to try anyway. And the last version of B-ASIC I saw seemed to have incorporated so much extra functionality that it was doing a bit more than just letting you access the Plus hardware.

Trystan

A bit more playing around and I've now made a |setpal command, which pages in ASIC RAM, sets a colour then pages it out again automatically so there's a few less commands needed. 

I think that'll do on this for now. The actual colour setting is probably slower than it needs to be but it seems to work. the format is |setpal, ink (0-31), red (0-15), green (0-15), blue (0-15).

Inks 0-15 set pen inks, ink 16 sets the border and inks 17-31 set sprite colours.

I'm really impressed with the RSX system, it seems super useful.

org #8000

.kl_log_ext equ #bcd1
.kl_new_frame_fly equ #bcd7
.kl_del_frame_fly equ #bcdd

;;-------------------------------------------------------------------------------
;; install RSX

ld hl,work_space ;;address of a 4 byte workspace useable by Kernel
ld bc,jump_table ;;address of command name table and routine handlers
jp kl_log_ext ;;Install RSX's

.work_space             ;;Space for kernel to use
defs 4

;;-------------------------------------------------------------------------------
;; RSX definition

.jump_table
defw name_table            ;address pointing to RSX commands

                           ;list of jump commands associated with each command
                          
                           ;The name (in the name_table) and jump instruction
                           ;(in the jump_table), must be in the same
                           ;order.

                           ;i.e. the first name in the name_table refers to the
                           ;first jump in the jump_table, and vice versa.

jp UnlockPlus            ;routine for PLUSENABLE
jp SetPal            ;routine for SETPAL
jp FlashDisable            ;routine for FLASHDISABLE
jp FlashEnable             ;routine for FLASHENABLE

;; the table of RSX function names
;; the names must be in capitals.

.name_table
defb "PLUSENABL","E"+&80     ;the last letter of each RSX name must have bit 7 set to 1.
defb "SETPA","L"+&80     ;This is used by the Kernel to identify the end of the name.
defb "FLASHDISABL","E"+&80
defb "FLASHENABL","E"+&80  

defb 0                     ;end of name table marker

; Code for the RSX routines

UnlockPlus:
di
ld b,#bc
ld hl,PlusInitSequence
ld e,17
PlusInitLoop:
ld a,(hl)
out (c),a
inc hl
dec e
jr nz,PlusInitLoop
ei
ret

SetPal:
; format |setpal,ink,r,g,b
; ix+0 = b
; ix+1 = -
; ix+2 = g
; ix+3 = -
; ix+4 = r
; ix+5 = -
; ix+6 = ink
; ix+7 = -
; check if we have 4 arguments, if not return (we probably should also print a message)
; we should probably also check arguments are 8 bit but for now just take the lower nibble of each
cp 4
ret nz

; page in ASIC RAM
ld bc,#7fb8
out (c),c

; set palette
; load address
ld h,#64 ;high byte for palette write
ld l,(ix+6) ;ink number
sla l ;double the low byte as two bytes per colour
ld a,(ix+4) ;b
sla a ;shift to upper nibble
sla a
sla a
sla a
add a,(ix+0) ;add r
ld (hl),a ;write
inc l ;next address
ld a,(ix+2) ;g (doesn't matter what's in the upper nibble)
ld (hl),a ;write

; page out ASIC RAM
ld bc,#7fa0
out (c),c
ret

FlashDisable:
LD hl, EventBlock
LD bc,#81FF
LD de,NoFlashRoutine
JP kl_new_frame_fly
ret

FlashEnable:
LD hl,EventBlock
JP kl_del_frame_fly
ret

NoFlashRoutine:
xor a
ld (#b7f8), a
ret

EventBlock:
defs 8

PlusInitSequence:
db #ff,#00,#ff,#77,#b3,#51,#a8,#d4,#62,#39,#9c,#46,#2b,#15,#8a,#cd,#ee



Trystan

One final question while I'm here. In my test project I've currently put this in memory at #BF00 (as https://www.cpcwiki.eu/index.php/Amstrad_Whole_Memory_Guide_-_General_system_arrangement says that can be used for small programs)

That article though looks to be for the 464 so I wanted to make sure there weren't going to be any conflicts (as http://www.cpcalive.com/docs/amstrad_cpc_6128_memory_map.html lists it as the machine stack) and it'd be safer to put it at the top of BASIC memory and reduce himem a bit.

andycadley

The machine stack probably doesn't go down that low, so it's probably safe. Unless you're planning on running a lot of recursive assembly code.  :laugh:

Lowering HIMEM and putting it there is another option, but then you've still really got to guess what a suitable value of HIMEM is as a machine with a lot of sideways ROMs will have lowered this further than a stock machine anyway. At some point you're always just plucking numbers out of thin air. 

BSC

#15
Quote from: Trystan on 14:36, 07 March 23One final question while I'm here. In my test project I've currently put this in memory at #BF00 (as https://www.cpcwiki.eu/index.php/Amstrad_Whole_Memory_Guide_-_General_system_arrangement says that can be used for small programs)
Back in the days, I liked to use the memory area at &afxx very often. Also reset-safe and I think that, at least on my 464, it was more or less not used by the OS/basic.

It seems that you are starting your assembly journey with some RSX commands..? That's nice to see because when I was looking at your first post today, this kl_log_ext thing at the beginning suddenly made me realize that the first assembly code I have ever written was also some RSX commands. Boy was I proud :)
** My website ** Some music

My hardware: ** Schneider CPC 464 with colour screen, 64k extension, 3" and 5,25 drives and more ** Amstrad CPC 6128 with M4 board, GreaseWeazle.

Longshot

Quote from: andycadley on 14:19, 06 March 23It disassembles as something like:

KL_NEW_FRAME_FLY equ #BCD7
KL_DEL_FRAME_FLY equ #BCDD

enable:
LD HL, eventblock
LD BC,#81FF
LD DE,routine
JP KL_NEW_FRAME_FLY
disable:
LD HL,eventblock
JP KL_DEL_FRAME_FLY
routine:
XOR A
LD (#B7F8), A
RET
eventblock:
DEFS 8

#B7F8 must be some sort of system variable related to INK flashing (grimware doesn't say what it's used for) or indicating a colour has changed or something, I'm not entirely sure. The code is just setting up a frame flyback event to reset it to zero, so presumably it fools the system into not refreshing the colour palette.

Creating an event to block the "color time" variable (&B7F8 (or &B1F2)) penalizes the CPU.

A better solution is shown here:
https://www.cpcwiki.eu/forum/programming/preventing-ink-blink-by-firmware/msg153776/#msg153776

This solution consists in disabling the Event Block "Set Inks" (&B7F9 (or &B1FE 464)) via KL DEL FRAME FLY (&BCDD)
This disables the BORDER and INK functions :

ld hl, &b7f9 ; &b1fe on cpc 464
call &bcdd
Rhaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!

Powered by SMFPacks Menu Editor Mod