News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_andymccall

Newbie assembler CPC question...

Started by andymccall, 12:19, 28 June 25

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

andymccall

Hi All,

I've been learning some assembly recently and to do this I write a simple app that draws filled rectangles in a test card-like pattern with a bitmap logo over it and accepts some keyboard input. I started on the neo-retro systems like the Agon, Neo6502 and Commander X16, but I'd now like to attempt it on my first computer - the Amstrad CPC!  It doesn't do anything, but it's something familiar I can work on and gives me a nice foundation for more development later.  Here's a video of the Neo version so you get an idea of what I'm trying to do.



Through some heavy use of AI to kickstart my CPC learning, I've got this assembly that draws two squares.  One should be red and the other should be yellow. The shapes are drawn correctly, but this code causes them to flash black/red and for the life of me I can't work out why!  I've tried different ink, pens and fill mode, but I can't get it and I've gone as far as I can do with AI too.  This is what it looks like at the moment, but the squares flash red/black.



Here's the code:

; =============================================
; Amstrad CPC Drawing Functions
; Draws two rectangles using reusable functions.
; Assembler: sjasmplus
; =============================================

    DEVICE AMSTRADCPC6128
    ORG 0x8000      ; Let's place our code at 0x8000, safely away from BASIC

; --- Constants
screen_base     EQU 0xC000      ; Base address of the screen memory
screen_size     EQU 0x4000      ; Size of the screen memory (16k)

amstrad_red     EQU 6           ; The hardware pen number for Red
amstrad_yellow  EQU 22          ; The hardware pen number for Yellow

; --- Firmware Call Addresses
fw_scr_set_mode EQU 0xBC0E      ; Set screen mode (a=mode)
fw_gra_set_ink  EQU 0xBC32      ; Set an ink to a hardware pen (a=ink, b=pen)
fw_key_wait     EQU 0xBB06      ; Wait for a key to be pressed
fw_txt_set_mode EQU 0xBB9C      ; Set text screen mode (for restoring)

; ====================================================================
; --- MAIN PROGRAM START
; ====================================================================
start:
    ; Set up the screen
    ld      a, 1
    call    set_screen_mode

    call    clear_screen

    ; --- Draw the first rectangle (RED) ---
    ; We use Ink 2 because its fill pattern (0x0F) is stable and avoids video contention.
    ld      a, 2                ; Use Ink 2
    ld      b, amstrad_red      ; Set Ink 2 to be the color Red
    call    set_pen_color

    ld      b, 20               ; X position in bytes (80 / 4)
    ld      c, 60               ; Y position
    ld      d, 20               ; Width in bytes (80 / 4)
    ld      e, 80               ; Height
    ld      a, 0x0F             ; Use the stable fill pattern for Ink 2
    call    draw_rect

    ; --- Draw the second rectangle (YELLOW) ---
    ; We use Ink 3 because its fill pattern (0xFF) is stable and avoids video contention.
    ld      a, 3                ; Use Ink 3
    ld      b, amstrad_yellow   ; Set Ink 3 to be the color Yellow
    call    set_pen_color

    ld      b, 45               ; X position in bytes (180 / 4)
    ld      c, 80               ; Y position
    ld      d, 10               ; Width in bytes (40 / 4)
    ld      e, 40               ; Height
    ld      a, 0xFF             ; Use the stable fill pattern for Ink 3
    call    draw_rect

    ; Wait and exit
    call    wait_for_key_press
    call    restore_screen_mode
   
    ret

; ====================================================================
; --- DATA STORAGE FOR DRAW_RECT
; ====================================================================
rect_params:
.x      db 0
.y      db 0
.width  db 0
.height db 0
.fill   db 0

; ====================================================================
; --- REUSABLE FUNCTIONS
; ====================================================================

; --------------------------------------------------------------------
; FUNCTION: clear_screen
; Clears the entire screen to the background color (Ink 0).
; Uses fast LDIR block copy.
; Corrupts: AF, BC, DE, HL
; --------------------------------------------------------------------
clear_screen:
    ld      hl, screen_base     ; Start of screen memory
    ld      de, screen_base + 1 ; Destination is one byte after start
    ld      (hl), 0             ; Set first byte to 0 (background color)
    ld      bc, screen_size - 1 ; Number of bytes to copy
    ldir                        ; Copy (hl) to (de), inc hl, inc de, dec bc. GO!
    ret

; --------------------------------------------------------------------
; FUNCTION: set_screen_mode
; Sets the CPC's screen mode.
; IN:   A = Mode number (0, 1, or 2)
; --------------------------------------------------------------------
set_screen_mode:
    call    fw_scr_set_mode
    ret

; --------------------------------------------------------------------
; FUNCTION: set_pen_color
; Sets a given ink to a specific hardware color.
; IN:   A = Ink number (0-3 in Mode 1)
;       B = Hardware color number (0-26)
; --------------------------------------------------------------------
set_pen_color:
    call    fw_gra_set_ink
    ret

; --------------------------------------------------------------------
; FUNCTION: draw_rect
; Draws a filled rectangle using a robust, simplified logic.
; IN:   B = X position (in bytes, so X_coord / 4)
;       C = Y position
;       D = Width (in bytes, so width / 4)
;       E = Height
;       A = Fill pattern
; Corrupts: AF, BC, DE, HL
; --------------------------------------------------------------------
draw_rect:
    ; Save all parameters into our data structure
    ld      (rect_params.fill), a
    ld      a, b
    ld      (rect_params.x), a
    ld      a, c
    ld      (rect_params.y), a
    ld      a, d
    ld      (rect_params.width), a
    ld      a, e
    ld      (rect_params.height), a

y_loop:
    ; Calculate screen address for the current Y
    ld      a, (rect_params.y)
    call    calculate_screen_address

    ; Add X offset
    ld      a, (rect_params.x)
    ld      e, a
    ld      d, 0
    add     hl, de

    ; Draw the horizontal line
    ld      a, (rect_params.width)
    ld      b, a
x_loop:
    ld      a, (rect_params.fill)
    ld      (hl), a
    inc     hl
    djnz    x_loop

    ; Update Y and height for next iteration
    ld      a, (rect_params.y)
    inc     a
    ld      (rect_params.y), a

    ld      a, (rect_params.height)
    dec     a
    ld      (rect_params.height), a
   
    cp      0
    jp      nz, y_loop
    ret

; --------------------------------------------------------------------
; FUNCTION: calculate_screen_address
; Calculates screen address for a given Y coordinate.
; IN:   A = Y coordinate
; OUT:  HL = screen memory address for start of that line (X=0)
; USES: AF, BC, DE, HL
; --------------------------------------------------------------------
calculate_screen_address:
    ld      b, a            ; Store Y in b

    srl     a
    srl     a
    srl     a               ; a = Y / 8 (character line number)
    ld      l, a
    ld      h, 0            ; hl = character line number
   
    add     hl, hl          ; *2
    add     hl, hl          ; *4
    add     hl, hl          ; *8
    add     hl, hl          ; *16
    ld      de, hl          ; Store val*16 in de
    add     hl, hl          ; *32
    add     hl, hl          ; *64
    add     hl, de          ; hl = (val * 64) + (val * 16) = val * 80.

    ld      a, b            ; Restore original Y
    and     7               ; a = Y % 8
    ld      e, a
    ld      d, 0            ; de = pixel row number
   
    ld      b, 11
shift_loop:
    add     de, de
    djnz    shift_loop
   
    add     hl, de          ; Add the pixel row offset
    ld      de, screen_base
    add     hl, de          ; Add the screen base address
    ret

; --------------------------------------------------------------------
; FUNCTION: wait_for_key_press
; Halts execution until a key is pressed.
; --------------------------------------------------------------------
wait_for_key_press:
    call    fw_key_wait
    ret

; --------------------------------------------------------------------
; FUNCTION: restore_screen_mode
; Restores the screen mode to the one active before the program started.
; --------------------------------------------------------------------
restore_screen_mode:
    ld      a, 255
    call    fw_txt_set_mode
    ret

; --------------------------------------------------------------------
; Creates a CPC file that can be run directly from AMSDOS
; --------------------------------------------------------------------
    SAVEAMSDOS "rect.cpc", start, $-start, start

Would anyone be able to explain what I'm doing wrong please?

Jean-Marie

When using call &BC32, you must input the same color value both in B and C registers.
ex: ld bc,&0606
call &BC32

&BC32   SCR SET INK
      Action: Sets the colours of a PEN  - if the two values supplied
              are different then the colours will alternate (flash)
      Entry:  contains the PEN number,  B  contains the first colour,
              and C holds the second colour
      Exit:   AF, BC, DE  and  HL  are  corrupt,  and  all others are
              preserved



andymccall

Thank you so much @Jean-Marie !

Can I ask - where did you get that info from? I'm mostly scraping around various websites like Chibi Akumas' site for resources on the CPC.  Is there a definitive source I should be looking at?

Jean-Marie

You're welcome. On this very website you can find some useful info on the Firmware :
https://www.cpcwiki.eu/imgs/e/e1/The_Amstrad_CPC_Firmware_Guide.txt

BTW there are 2 functions that draw rectangles (or boxes as they call'em):
108   &BC44   SCR FILL BOX
      Action: Fills an area of the  screen  with  an  ink - this only
              works for 'character-sized' blocks of screen
      Entry:  A contains the mask for the  ink  that is to be used, H
              contains the left hand colurnn  of  the area to fill, D
              contains the right hand column,  L  holds the top line,
              and E holds the bottom line of the area (using physical
              coordinates)

      Exit:   AF, BC, DE  and  HL  are  corrupt,  and  all others are
              preserved

109   &BC17   SCR FLOOD BOX
      Action: Fills an area of the  screen  with  an  ink - this only
              works for 'byte-sized' blocks of screen
      Entry:  C contains the  encoded  PEN  that  is  to  be used, HL
              contains the screen address of the top left hand corner
              of the area to fill, D  contains  the width of the area
              to be filled in bytes, and E contains the height of the
              area to be filled in screen lines
      Exit:   AF, BC, DE and HL are  corrupt, and all other registers
              are preserved
      Notes:  The whole of the  area  to  be  filled  must lie on the
              screen otherwise unpredictable results may occur


arnoldemu

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

MoteroV4

Quote from: andymccall on 12:51, 28 June 25Thank you so much @Jean-Marie !

Can I ask - where did you get that info from? I'm mostly scraping around various websites like Chibi Akumas' site for resources on the CPC.  Is there a definitive source I should be looking at?
I like them in PDF. For example: "FW System Vectors 6128", "The Amstrad CPC FW Guide" by Bob Taylor, and "CPC464 FW (1984)" by Amsoft. Downloadable in the next link. Keep the link, and explore it, this is a mine of gold  :D :

https://acpc.me/?language=eng#ACME/DOCUMENTS_TECHNIQUES/FIRMWARES

Released cpc games: Memtrainer, El Gerente (Remake)

lightforce6128

A good source to learn about how the firmware / the OS behaves is to look at its implementation in ROM. For this, there exist commented ROM listings that show the assembler sources and a lot of comments and explanations. This helps to understand how parameters are transferred from the caller via registers, how they are processed, and how a whole OS can be build up from a bunch of small assembler routines.

Usually I have a look in this ROM listing from the German publisher Markt&Technik, presented in our Wiki. Maybe somebody else can recommend another one in another language.

One drawback has to be mentioned: The developers of the CPC firmware did know the predecessing processor 8080 well, but not the newer Z80 processor. For this reason, some routines in the 464 ROM are more complex then they needed to be. The most famous example: Using "DEC B : JR NZ" instead of "DJNZ".

Another thing one should keep in mind: An OS needs to provide common services that can be used by a broad group of clients. E.g. screen managment should support a text editor as well as a spread sheet. This means that the used routines cannot be optimized to one purpose, because the purpose is not known in advance. Always be sure: Whatever you find in an OS can be optimized somehow to get faster or smaller or both, if the use case is restricted somehow.

trocoloco

for Firmware stuff I normally use this one, it has PDF format and HTML for specific parts of the firmware, such as keyboard, jumpblocks, text, graphics, memory sections on 464 & 6128, you name it

https://www.cantrell.org.uk/david/tech/cpc/cpc-firmware.html

andymccall

First - thank you to all that replied!  I really do appreciate everyone taking time out of their day to answer questions that I should be able to answer myself (but can't!) read from the documents!  I'm very much a do-er when it comes to learning, I need to implement something, then go and look at the references and after a few attempts I generally start to understand what it means.  I've always struggled reading a technical reference first, then implementing it.

I've read all the replies and looked a the the technical docs, and I'm still not getting it.  I was hoping someone could help me understand what I'm doing wrong.

I set the colour:

    ld      a, 1                ; Pen 1
    ld      b, 25               ; 25 = yellow
    ld      c, b
    call    0xBC32

The bit I'm getting stuck on is the fill pattern.  This code here should draw a box using my routing, but also a box using the firmware routine.

    ld      b, 0              ; X position in bytes (180 / 4)
    ld      c, 0              ; Y position
    ld      d, 10              ; Width in bytes (40 / 4)
    ld      e, 60              ; Height
    ld      a, #11            ; Use the stable fill pattern for Ink 1
    call    draw_rect

    ld      c, #11
    ld      hl, #C000 + 1000
    ld      d, 20
    ld      e, 50
    call    #BC17

The code to draw it using my routine draws a box, but I can't work out the fill pattern at all, it seems random!

The code to draw a box using the firmware call doesn't draw anything at all.

Can anyone tell me what I'm doing wrong on both the fill pattern and the firmware call? I think if I can work out the firmware call I should be able to then make sense of the rest of the Amstrad CPC docs.

Jean-Marie

Your fill patterns must be incorrect.
To fill a byte in mode 1 (4 pixels), you must follow this rule :
bit 7bit 6bit 5bit 4bit 3bit 2bit 1bit 0
pixel 0 (bit 0)pixel 1 (bit 0)pixel 2 (bit 0)pixel 3 (bit 0)pixel 0 (bit 1)pixel 1(bit 1)pixel 2 (bit 1)pixel 3 (bit 1)

Yeah, pixels are interleaved within a byte. How fun!
Try with Fill patterns 0, &0F, &F0, &FF
Here is some good documentation, but you'll have to translate it: CPCRULEZ > AMSTRAD CPC > > CODING > ANTIBUG > STRUCTURE DE LA M�MOIRE �CRAN DE L'AMSTRAD CPC PAR ANTIBUG

ZorrO

#10
Alan Sugar, when he ordered designed CPC, was afraid that other companies would clone it without a license, so he ordered make display more complicated. Unfortunately, this also made life difficult for programmers.

I don't know which of these links will seem more understandable to you, but each one explains which pixels fit into which bits in different MODEs.

https://cpcrulez.fr/codingBOOK_soft968-CPC464-664-6128_firmware_006.htm
http://www.cpcmania.com/docs/programming/painting_pixels_introduction_to_video_memory.htm
https://cpctech.cpcwiki.de/docs/graphics.html
CPC+PSX 4ever

Jean-Marie

I just noticed there is a typo in the Firmware doc ! There are 2 calls to BC17.
The correct value for SCR_FLOOD BOX is actually &BC47.

&BC17  SCR CHAR LIMITS
      Action: Gets the size  of  the  whole  screen  in  terms of the
              numbers of characters that can be displayed
      Entry:  No entry conditions
      Exit:  B contains the number of  characters across the screen,
              C contains the number of characters down the screen, AF
              is corrupt, and all other registers are preserved

andymccall

Okay, I kinda got there....

Honestly... I still don't really understand it, but I split the byte up into two 4 bits, then worked out the values for each colour when they were placed into both bits and came up with a table like this:
; 3 = Cyan
; 12 = Bright Cyan
; 15 = Pastel Blue
; 48 = White
; 51 = Bright Green
; 60 = Light Blue
; 192 = Bright Yellow
; 195 = Yellow (Mustard)
; 204 = Red
; 207 = Pink (Salmon)
; 240 = Black
; 243 = Pastel Green
; 252 = Bright Magenta

I still missing some colours, as there's only 13 colours there, but for now I've been able to move on.

I wish I could display the text at the lower resolution though, that fat font sucks!

Jean-Marie

Using Mode 0, the layout is different : There are 2 pixels of 4 bits per byte:

bit 7: pixel 1 (bit 0)
bit 6: pixel 2 (bit 0)
bit 5: pixel 1 (bit 2)
bit 4: pixel 2 (bit 2)
bit 3: pixel 1 (bit 1)
bit 2: pixel 2 (bit 1)
bit 1: pixel 1 (bit 3)
bit 0: pixel 2 (bit 3)

McArti0

You can change the display mode to another one after the 4th interrupt after Vsync.
CPC 6128, Whole 6128 and Only 6128, with .....
NewPAL v3 for use all 128kB RAM by CRTC as VRAM
One chip driver for 512kB(to640) extRAM 6128
TYPICAL :) TV Funai 22FL532/10 with VGA-RGB-in.

MoteroV4

Quote from: andymccall on Yesterday at 18:34I wish I could display the text at the lower resolution though, that fat font sucks!
It is something complex, but it can be done through interruptions. In each frame the Gate Array generates a rate of interruptions of 300Hz = 6 IRQ/frame, with 52 scanlines each one.
Approximately in the 5th interruption, the raster passes through the area where you have the text. When your program detects that IRQ you just have to change from mode 0 to Mode 1 or 2, and print the text (if it has time). And once the 6th interruption begins, already by the border of the screen, or at VSYNC, change again to mode 0.
Released cpc games: Memtrainer, El Gerente (Remake)

ZorrO

@andymccall - I have no idea where you got the numbers for the colors you listed.

And as for the width of the text, you can use a narrow font, like this:
https://www.cpcwiki.eu/forum/programming/basic-programming-tips/msg117620/#msg117620
CPC+PSX 4ever

andycadley

You're over complicating things. Since you're using the firmware to do the drawing, you might as well let it deal with the complexity of byte encoding too.

100 &BC2C  SCR INK ENCODE
Action: Converts a PEN to provide a mask which, if applied to a screen byte, will convert all of the pixels in the byte to the appropriate PEN
      Entry:  A contains a PEN number
      Exit:  A contains the encoded value of  the PEN, the flags are corrupt, and all other registers are preserved

      Notes:  The mask returned is  different  in  each of the screen modes


So all you need is something like:

LD A,(current_pen)
CALL SCR_INK_ENCODE

Powered by SMFPacks Menu Editor Mod