Tachibana
Langue : français

Groupe 12 — Blitter Officiel

12,1 Blitter Busy · 12,2 Simple Blit Copy · 12,3 Complex Blit Copy · 12,4 Blit Image

Le blitter copie des blocs de mémoire côté RP2040, sans passer par le 65C02. Une adresse y a 24 bits : une page et 16 bits d'adresse. Pages : $00 la mémoire du 6502, $80 et $81 la mémoire vidéo (en mode 0, un octet par pixel, 320 par ligne : le pixel (x, y) est à y × 320 + x, au-delà de 65535 en page $81), $90 la mémoire graphique, $A0 et suivantes les banques de mémoire en flash (lecture seule).

Les exemples copient vers l'écran effacé, puis relisent les pixels avec 5,33.

12,1 Blitter Busy Officiel

Dit si le blitter est en train de copier.

Entrées : aucune.

Sorties
OctetsTypeRôle
P0uint8non nul si une copie est en cours ; toujours 0 aujourd'hui

Le firmware fait chaque copie en entier avant de rendre la main : l'appel rend toujours 0, avant comme juste après une copie. Il reste utile pour un programme qui doit aussi tourner sur un firmware où la copie serait faite en tâche de fond. NeoBASIC n'a pas de mot-clé pour cet appel.

Texte de la documentation du firmware (anglais)

Returns a non zero value in Parameter:0 if the blitter/DMA system is currently transferring data, used to check availability and transfer completion.

C (llvm-mos)
/* Savoir si le blitter est occupé (groupe 12, fonction 1). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

/* 12,2 : copie de n octets de page:adresse vers page:adresse ; rend l'indicateur d'erreur. */
static uint8_t copier(uint8_t ps, uint16_t as, uint8_t pd, uint16_t ad, uint16_t n) {
    libre();
    ControlPort.params[0] = ps;                 /* P0 : page source ($00 RAM du 6502, $80-$81 vidéo, $90 graphique) */
    ControlPort.params[1] = as & 0xFF;          /* P1-P2 : adresse source */
    ControlPort.params[2] = as >> 8;
    ControlPort.params[3] = pd;                 /* P3 : page destination */
    ControlPort.params[4] = ad & 0xFF;          /* P4-P5 : adresse destination */
    ControlPort.params[5] = ad >> 8;
    ControlPort.params[6] = n & 0xFF;           /* P6-P7 : nombre d'octets */
    ControlPort.params[7] = n >> 8;
    api(12, 2);
    return ControlPort.error;
}

static uint8_t occupe(void) {                   /* Blitter Busy */
    libre();
    api(12, 1);
    return ControlPort.params[0];
}

static char src[] = "ABCD", dst[5];

int main(void) {
    uint8_t avant = occupe();
    copier(0x00, (uint16_t)src, 0x00, (uint16_t)dst, 4);
    uint8_t apres = occupe();                   /* la copie est déjà finie */
    printf("Occupe avant : %u, apres une copie : %u\n", avant, apres);
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Occupe avant : 0, apres une copie : 0
Mad Pascal
// Savoir si le blitter est occupé (groupe 12, fonction 1).
program blit1;
uses crt, neo6502;
var src, dst: array[0..3] of byte;
    avant, apres: boolean;
begin
  avant := NeoBlitterBusy;                      // Blitter Busy
  NeoBlitterCopy(word(@src), word(@dst), 4);    // 12,2 : page $00 (RAM du 6502) des deux côtés
  apres := NeoBlitterBusy;                      // la copie est déjà finie
  writeln('Occupe avant : ', ord(avant), ', apres une copie : ', ord(apres));
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Occupe avant : 0, apres une copie : 0
NeoBASIC
10 ' Savoir si le blitter est occupe (groupe 12, fonction 1)
20 gosub 900 : avant = o
30 s = alloc(4) : d = alloc(4) : ps = 0 : as = s : pd = 0 : ad = d : n = 4 : gosub 1000
40 gosub 900 : apres = o : ' la copie est deja finie
50 print "Occupe avant : ";avant;", apres une copie : ";apres
60 end
900 ' Blitter Busy : o
910 while peek($ff00) : wend : poke $ff01, 1 : poke $ff00, 12
920 while peek($ff00) : wend : o = peek($ff04)
930 return
1000 ' 12,2 : copie de n octets de ps:as vers pd:ad ; erreur dans e
1010 while peek($ff00) : wend
1020 poke $ff04, ps : doke $ff05, as : poke $ff07, pd : doke $ff08, ad : doke $ff0a, n
1030 poke $ff01, 2 : poke $ff00, 12
1040 while peek($ff00) : wend : e = peek($ff02)
1050 return
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Occupe avant : 0, apres une copie : 0
Assembleur 65C02 (64tass)
; Savoir si le blitter est occupé (groupe 12, fonction 1).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<occupe
        ldx #>occupe
        jsr envoi
        lda ControlPort+4
        sta avant
        lda #<copie
        ldx #>copie
        jsr envoi
        lda #<occupe
        ldx #>occupe
        jsr envoi
        lda ControlPort+4          ; la copie est déjà finie
        sta apres
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda avant
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda apres
        jsr decimal
        lda #13
        jsr WriteCharacter
        rts

occupe: .byte 12, 1, 0             ; 12,1 : Blitter Busy
copie:  .byte 12, 2, 8, 0          ; 12,2 : de la page $00 (RAM du 6502)...
        .word src
        .byte 0          ; ... vers la page $00
        .word dst, 4
src:    .text "ABCD"
dst:    .fill 4
t0:     .text "Occupe avant : ", 0
t1:     .text ", apres une copie : ", 0
avant:  .byte 0
apres:  .byte 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Occupe avant : 0, apres une copie : 0

12,2 Simple Blit Copy Officiel

Copie une suite d'octets d'une page de mémoire à une autre (mémoire du 6502, vidéo, graphique).

Entrées
OctetsTypeRôle
P0uint8page source
P1-P2addradresse source dans la page
P3uint8page destination
P4-P5addradresse destination dans la page
P6-P7uint16nombre d'octets
Sorties
OctetsTypeRôle
erreuruint81 si le début ou la fin d'une des deux zones n'existe pas, ou si la destination est une banque

Les zones peuvent se chevaucher. Une zone ne passe pas d'une page à l'autre : en mémoire vidéo, 65536 octets en page $80, le reste en page $81. L'exemple copie un texte en mémoire du 6502, huit pixels de couleur 5 à l'écran (ligne 100, x 100 à 107 ; le pixel suivant reste à 0), puis montre le refus d'une page qui n'existe pas. NeoBASIC n'a pas de mot-clé pour cet appel.

Texte de la documentation du firmware (anglais)

Copy Parameters:6,7 bytes of internal memory from Parameter:0:Parameters:1,2 to Parameter:3:Parameters:4,5. Sets error flag if the transfer is not possible (e.g. illegal write addresses). The upper 8 bits of the address are : 6502 RAM (00) VideoRAM (80,81) Graphics RAM(90)

C (llvm-mos)
/* Copier des octets en mémoire et vers l'écran (groupe 12, fonction 2). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

/* 12,2 : copie de n octets de page:adresse vers page:adresse ; rend l'indicateur d'erreur. */
static uint8_t copier(uint8_t ps, uint16_t as, uint8_t pd, uint16_t ad, uint16_t n) {
    libre();
    ControlPort.params[0] = ps;                 /* P0 : page source ($00 RAM du 6502, $80-$81 vidéo, $90 graphique) */
    ControlPort.params[1] = as & 0xFF;          /* P1-P2 : adresse source */
    ControlPort.params[2] = as >> 8;
    ControlPort.params[3] = pd;                 /* P3 : page destination */
    ControlPort.params[4] = ad & 0xFF;          /* P4-P5 : adresse destination */
    ControlPort.params[5] = ad >> 8;
    ControlPort.params[6] = n & 0xFF;           /* P6-P7 : nombre d'octets */
    ControlPort.params[7] = n >> 8;
    api(12, 2);
    return ControlPort.error;
}

static char texte[] = "ABCDEFGH", copie[9];
static uint8_t couleur[8] = {5, 5, 5, 5, 5, 5, 5, 5};

int main(void) {
    putchar(12);                                /* écran effacé : on relit des pixels */
    copier(0x00, (uint16_t)texte, 0x00, (uint16_t)copie, 8);        /* RAM du 6502 → RAM du 6502 */
    copier(0x00, (uint16_t)couleur, 0x80, 100 * 320 + 100, 8);      /* → écran, ligne 100, x 100 à 107 */
    uint8_t a = neo_graphics_read_pixel(100, 100), b = neo_graphics_read_pixel(107, 100);
    uint8_t c = neo_graphics_read_pixel(108, 100);
    uint8_t e = copier(0x70, 0, 0x00, (uint16_t)copie, 8);           /* page $70 : n'existe pas */
    printf("En memoire : %s\n", copie);
    printf("A l'ecran : %u %u, apres : %u\n", a, b, c);
    printf("Page $70 : erreur %u\n", e);
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 1
Mad Pascal
// Copier des octets en mémoire et vers l'écran (groupe 12, fonction 2).
program blit2;
uses crt, neo6502;
var texte, copie: string[8];
    couleur: array[0..7] of byte;
    a, b, c, e: byte;
begin
  write(chr(12));                               // écran effacé : on relit des pixels
  texte := 'ABCDEFGH';
  fillchar(@couleur, 8, 5);
  copie := '';
  NeoBlitterCopy(word(@texte) + 1, word(@copie) + 1, 8);   // Simple Blit Copy : RAM du 6502 → RAM du 6502
  copie[0] := chr(8);
  NeoBlitterCopy(word(@couleur), $800000 + 100 * 320 + 100, 8);   // → écran (page $80), ligne 100, x 100 à 107
  a := NeoGetPixel(100, 100);
  b := NeoGetPixel(107, 100);
  c := NeoGetPixel(108, 100);
  NeoBlitterCopy($700000, word(@copie) + 1, 8); // page $70 : n'existe pas
  NeoWaitMessage;
  e := NeoMessage.error;
  writeln('En memoire : ', copie);
  writeln('A l''ecran : ', a, ' ', b, ', apres : ', c);
  writeln('Page $70 : erreur ', e);
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 1
NeoBASIC
10 ' Copier des octets en memoire et vers l'ecran (groupe 12, fonction 2)
20 cls : ' ecran efface : on relit des pixels
30 t = alloc(8) : c = alloc(9) : k = alloc(8)
40 for i = 0 to 7 : poke t + i, 65 + i : poke k + i, 5 : next i : ' ABCDEFGH ; 8 octets de couleur 5
50 ps = 0 : as = t : pd = 0 : ad = c : n = 8 : gosub 1000 : ' Simple Blit Copy : RAM du 6502 vers RAM du 6502
60 ps = 0 : as = k : pd = $80 : ad = 32100 : gosub 1000 : ' vers l'ecran (page $80) : 100 * 320 + 100, ligne 100, x 100 a 107
70 a = point(100,100) : b = point(107,100) : d = point(108,100)
80 ps = $70 : as = 0 : pd = 0 : ad = c : gosub 1000 : ' page $70 : n'existe pas
90 s$ = "" : for i = 0 to 7 : s$ = s$ + chr$(peek(c + i)) : next i
100 print "En memoire : ";s$
110 print "A l'ecran : ";a;" ";b;", apres : ";d
120 print "Page $70 : erreur ";e
130 end
1000 ' 12,2 : copie de n octets de ps:as vers pd:ad ; erreur dans e
1010 while peek($ff00) : wend
1020 poke $ff04, ps : doke $ff05, as : poke $ff07, pd : doke $ff08, ad : doke $ff0a, n
1030 poke $ff01, 2 : poke $ff00, 12
1040 while peek($ff00) : wend : e = peek($ff02)
1050 return
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 1
Assembleur 65C02 (64tass)
; Copier des octets en mémoire et vers l'écran (groupe 12, fonction 2).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:   lda #12                    ; écran effacé : on relit des pixels
        jsr WriteCharacter
        lda #<c1
        ldx #>c1
        jsr envoi
        lda #<c2
        ldx #>c2
        jsr envoi
        lda #100
        ldy #100
        jsr pixel
        sta a
        lda #107
        ldy #100
        jsr pixel
        sta b
        lda #108
        ldy #100
        jsr pixel
        sta c
        lda #<c3
        ldx #>c3
        jsr envoi
        lda ControlPort+2
        sta e
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda #<copie
        ldx #>copie
        jsr ecrire
        lda #13
        jsr WriteCharacter
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda a
        jsr decimal
        lda #' '
        jsr WriteCharacter
        lda b
        jsr decimal
        lda #<t2
        ldx #>t2
        jsr ecrire
        lda c
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #<t3
        ldx #>t3
        jsr ecrire
        lda e
        jsr decimal
        lda #13
        jsr WriteCharacter
        rts

; Couleur du fond (5,33) en (A, Y), X et Y inférieurs à 256, rendue dans A.
pixel:  pha
        jsr WaitMessage
        pla
        sta ControlPort+4          ; P0-P1 : X
        stz ControlPort+5
        sty ControlPort+6          ; P2-P3 : Y
        stz ControlPort+7
        lda #5
        ldx #33                    ; 5,33 : Read Pixel
        jsr appel
        lda ControlPort+4
        rts

c1:     .byte 12, 2, 8, 0          ; 12,2 : RAM du 6502 → RAM du 6502
        .word texte
        .byte 0
        .word copie, 8
c2:     .byte 12, 2, 8, 0          ; → écran : page $80, ligne 100, x 100 à 107
        .word couleur
        .byte $80
        .word 100*320+100, 8
c3:     .byte 12, 2, 8, $70        ; page $70 : n'existe pas
        .word 0
        .byte 0
        .word copie, 8
texte:  .text "ABCDEFGH"
copie:  .fill 9                    ; zéro final : chaîne terminée
couleur: .byte 5, 5, 5, 5, 5, 5, 5, 5
t0:     .text "En memoire : ", 0
t1:     .text "A l'ecran : ", 0
t2:     .text ", apres : ", 0
t3:     .text "Page $70 : erreur ", 0
a:      .byte 0
b:      .byte 0
c:      .byte 0
e:      .byte 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 1

12,3 Complex Blit Copy Modifié par Trinity

Copie un rectangle vers un autre, en convertissant le format des pixels au passage, avec transparence possible.

Entrées
OctetsTypeRôle
P0uint8action : 0 copie, 1 copie sauf la valeur transparente, 2 couleur unie là où la source n'est pas transparente
P1-P2addradresse du descripteur de la zone source (12 octets)
P3-P4addradresse du descripteur de la zone cible (12 octets)
Sorties
OctetsTypeRôle
erreuruint8non nul si la copie est impossible (format, adresse, cible en banque)

Descripteur de zone, dans la mémoire du 6502 : octets 0-1 adresse et 2 page ; 3 à 0 ; 4-5 pas (ajouté à l'adresse d'une ligne à la suivante ; 0 répète la même ligne, négatif remonte) ; 6 format (0 octets ; 1 paires de 4 bits et 2 bits isolés, en source seulement ; 3 et 4 moitié haute ou basse de l'octet, en cible seulement ; depuis Trinity 0.16.69, 5 et 6 deux pixels par octet en cible, le premier pixel dans la moitié haute (x pair) ou basse (x impair), pour les pages 16 couleurs du mode 2 : selon la documentation, non essayés au banc) ; puis, en source seulement, 7 valeur transparente, 8 couleur unie, 9 hauteur, 10-11 largeur en pixels. La copie travaille par octets source : la largeur est arrondie à l'octet inférieur. L'exemple déplie 4 octets en 8 pixels (1 à 8) sur deux lignes, grâce à un pas source nul. NeoBASIC n'a pas de mot-clé pour cet appel.

Texte de la documentation du firmware (anglais)

Copy a source rectangular area to a destination rectangular area. It's oriented toward copying graphics data, but can be used as a more general-purpose memory mover. The source and target areas may be different formats, and the copy will convert the data on the fly. For example, you can expand 4bpp source graphics (two pixels per byte) into the 1 pixel per byte framebuffer. However, the blitting is byte-oriented. So the source width is always rounded down to the nearest full byte.

Parameter (0) is the blit action:

0 = copy 1 = copymasked - copy, but only where src is not the transparent value. 2 = solidmasked - set target to constant solid value, but only where src is not the transparent value. See below for transparent/solid values.

Parameters (1,2) address of the source rectangle data.

Parameters (3,4) address of the target rectangle data.

The source and target rectangle data is laid out in memory as follows:

0-2 24 bit address to copy from/to (address is address:page:0)

3 pad byte (must be zero)

4-5 Stride, in bytes. This is the value to add to the address to get from one line to the next. Used for both source and target. For example: - if blitting to the screen, a stride of screen width (320) would get to the next line. - a zero source stride would repeat a single line for the whole copy. - A negative target stride would draw from the bottom upward.

6 data format 0: bytes. Supported for both source and target. 1: pairs of 4-bit values (nibbles). Source only. 2: 8 single-bit values. Source only. 3: high nibble. Target only. 4: low nibble. Target only. 5: two pixels per byte, left pixel in the high nibble, the first pixel going to the HIGH nibble of the first byte (even x). Target only (Trinity T-90, the 16 colour pages of mode 2). 6: the same, the first pixel going to the LOW nibble of the first byte (odd x). Target only. With 5 and 6 every source format and action is supported ; each value keeps its low 4 bits (the solid value too) and a nibble that is not written is left unchanged.

7 A constant to use as the "transparent" value for BLTACT_MASK and BLTACT_SOLID. Source only. Not used in target.

8 A constant to use as the "solid" value for BLTACT_SOLID. Source only. Not used in target.

9 Height. The number of lines to copy. Source only. Not used in target. The copy is driven by the source height.

10-11 Width. The number of values to copy for each line. Source only. Not used in target. The copy is driven by the source width.

C (llvm-mos)
/* Déplier des pixels de 4 bits sur deux lignes de l'écran (groupe 12, fonction 3). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

/* Descripteurs de zone (voir la fiche) : adresse sur 24 bits, 0, pas, format, transparent, uni, hauteur, largeur. */
static uint8_t pixels[4] = {0x12, 0x34, 0x56, 0x78};   /* 8 pixels de 4 bits : 1 à 8 */
static uint8_t source[12], cible[12];

int main(void) {
    putchar(12);
    source[0] = (uint16_t)pixels & 0xFF;        /* adresse en RAM du 6502 (page $00) */
    source[1] = (uint16_t)pixels >> 8;
    source[4] = 0;                              /* pas 0 : la même ligne resservira */
    source[6] = 1;                              /* format 1 : paires de 4 bits */
    source[9] = 2;                              /* hauteur : 2 lignes */
    source[10] = 8;                             /* largeur : 8 pixels */
    uint16_t ecran = 120 * 320 + 100;           /* ligne 120, x 100 */
    cible[0] = ecran & 0xFF;
    cible[1] = ecran >> 8;
    cible[2] = 0x80;                            /* page $80 : mémoire vidéo */
    cible[4] = 320 & 0xFF;                      /* pas : une ligne d'écran */
    cible[5] = 320 >> 8;
    cible[6] = 0;                               /* format 0 : un octet par pixel */
    libre();
    ControlPort.params[0] = 0;                  /* P0 : action 0, copie */
    ControlPort.params[1] = (uint16_t)source & 0xFF;   /* P1-P2 : zone source */
    ControlPort.params[2] = (uint16_t)source >> 8;
    ControlPort.params[3] = (uint16_t)cible & 0xFF;    /* P3-P4 : zone cible */
    ControlPort.params[4] = (uint16_t)cible >> 8;
    api(12, 3);                                 /* Complex Blit Copy */
    uint8_t l[2][8];
    for (uint8_t y = 0; y < 2; y++)
        for (uint8_t x = 0; x < 8; x++)
            l[y][x] = neo_graphics_read_pixel(100 + x, 120 + y);
    for (uint8_t y = 0; y < 2; y++) {
        printf("Ligne %u :", y + 1);
        for (uint8_t x = 0; x < 8; x++)
            printf(" %u", l[y][x]);
        printf("\n");
    }
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 8
Mad Pascal
// Déplier des pixels de 4 bits sur deux lignes de l'écran (groupe 12, fonction 3).
program blit3;
uses crt, neo6502;
const pixels: array[0..3] of byte = ($12, $34, $56, $78);   // 8 pixels de 4 bits : 1 à 8
var source, cible: array[0..11] of byte;        // descripteurs de zone (voir la fiche)
    l: array[0..15] of byte;
    x, y: byte;
begin
  write(chr(12));
  fillchar(@source, 12, 0);
  fillchar(@cible, 12, 0);
  source[0] := lo(word(@pixels));               // adresse en RAM du 6502 (page $00)
  source[1] := hi(word(@pixels));
  source[6] := 1;                               // format 1 : paires de 4 bits ; pas 0 : la même ligne resservira
  source[9] := 2;                               // hauteur : 2 lignes
  source[10] := 8;                              // largeur : 8 pixels
  cible[0] := lo(120 * 320 + 100);              // ligne 120, x 100
  cible[1] := hi(120 * 320 + 100);
  cible[2] := $80;                              // page $80 : mémoire vidéo
  cible[4] := lo(320);                          // pas : une ligne d'écran
  cible[5] := hi(320);                          // format 0 : un octet par pixel
  NeoBlitterXCopy(0, @source, @cible);          // Complex Blit Copy, action 0 : copie
  for y := 0 to 1 do
    for x := 0 to 7 do
      l[y * 8 + x] := NeoGetPixel(100 + x, 120 + y);
  for y := 0 to 1 do begin
    write('Ligne ', y + 1, ' :');
    for x := 0 to 7 do write(' ', l[y * 8 + x]);
    writeln;
  end;
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 8
NeoBASIC
10 ' Deplier des pixels de 4 bits sur deux lignes de l'ecran (groupe 12, fonction 3)
20 cls
30 p = alloc(4) : poke p, $12 : poke p + 1, $34 : poke p + 2, $56 : poke p + 3, $78 : ' 8 pixels de 4 bits : 1 a 8
40 s = alloc(12) : c = alloc(12) : for i = 0 to 11 : poke s + i, 0 : poke c + i, 0 : next i
50 doke s, p : ' zone source : adresse en RAM du 6502 (page $00), pas 0 (la meme ligne resservira)
60 poke s + 6, 1 : poke s + 9, 2 : poke s + 10, 8 : ' format 1 (paires de 4 bits), hauteur 2, largeur 8
70 doke c, 38500 : poke c + 2, $80 : ' zone cible : 120 * 320 + 100 (ligne 120, x 100), page $80 (video)
80 doke c + 4, 320 : ' pas : une ligne d'ecran ; format 0, un octet par pixel
90 while peek($ff00) : wend
100 poke $ff04, 0 : doke $ff05, s : doke $ff07, c : poke $ff01, 3 : poke $ff00, 12 : ' Complex Blit Copy, action 0 : copie
110 while peek($ff00) : wend
120 for y = 0 to 1 : print "Ligne ";y + 1;" :"; : for x = 0 to 7 : print " ";point(100 + x, 120 + y); : next x : print : next y
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 8
Assembleur 65C02 (64tass)
; Déplier des pixels de 4 bits sur deux lignes de l'écran (groupe 12, fonction 3).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:   lda #12                    ; écran effacé : on relit des pixels
        jsr WriteCharacter
        lda #<blit
        ldx #>blit
        jsr envoi
        ldx #0
_ll1:  phx
        txa
        clc
        adc #100
        ldy #120
        jsr pixel
        plx
        sta l1,x
        inx
        cpx #8
        bne _ll1
        ldx #0
_ll2:  phx
        txa
        clc
        adc #100
        ldy #121
        jsr pixel
        plx
        sta l2,x
        inx
        cpx #8
        bne _ll2
        lda #<t1
        ldx #>t1
        jsr ecrire
        ldx #0
_al1:  lda #' '
        jsr WriteCharacter
        lda l1,x
        jsr decimal
        inx
        cpx #8
        bne _al1
        lda #13
        jsr WriteCharacter
        lda #<t2
        ldx #>t2
        jsr ecrire
        ldx #0
_al2:  lda #' '
        jsr WriteCharacter
        lda l2,x
        jsr decimal
        inx
        cpx #8
        bne _al2
        lda #13
        jsr WriteCharacter
        rts

; Couleur du fond (5,33) en (A, Y), X et Y inférieurs à 256, rendue dans A.
pixel:  pha
        jsr WaitMessage
        pla
        sta ControlPort+4          ; P0-P1 : X
        stz ControlPort+5
        sty ControlPort+6          ; P2-P3 : Y
        stz ControlPort+7
        lda #5
        ldx #33                    ; 5,33 : Read Pixel
        jsr appel
        lda ControlPort+4
        rts

blit:   .byte 12, 3, 5, 0          ; 12,3 : Complex Blit Copy, action 0 (copie)
        .word source, cible        ; zones source et cible
pixels: .byte $12, $34, $56, $78   ; 8 pixels de 4 bits : 1 à 8
source: .word pixels              ; adresse en RAM du 6502...
        .byte 0, 0                 ; ... page $00, octet de bourrage
        .word 0                    ; pas 0 : la même ligne resservira
        .byte 1, 0, 0, 2           ; format 1 (paires de 4 bits), transparent, uni, hauteur 2
        .word 8                    ; largeur : 8 pixels
cible:  .word 120*320+100          ; ligne 120, x 100...
        .byte $80, 0               ; ... page $80 (mémoire vidéo)
        .word 320                  ; pas : une ligne d'écran
        .byte 0, 0, 0, 0           ; format 0 : un octet par pixel
        .word 0
t1:     .text "Ligne 1 :", 0
t2:     .text "Ligne 2 :", 0
l1:     .fill 8
l2:     .fill 8

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 8

12,4 Blit Image Modifié par Trinity

Pose une image (décrite comme la source de 12,3) à l'écran, en la rognant sur les bords.

Entrées
OctetsTypeRôle
P0uint8action, comme 12,3
P1-P2addradresse du descripteur de la zone source
P3-P4int16X à l'écran (signé)
P5-P6int16Y à l'écran (signé)
P7uint8écriture : 0 octet entier, 3 moitié haute, 4 moitié basse
Sorties
OctetsTypeRôle
erreuruint8non nul en mode 1 ou 2, ou pour un format source inconnu

Le rognage se fait par octet source : une image d'un bit par pixel posée en x = -2 perd tout son premier octet (8 pixels), si bien que les pixels 0 à 5 restent vides et que l'image commence en x = 6. C'est ce que montre l'exemple (action 2, couleur 6). Trinity refuse l'appel dans les modes compacts 1 et 2, où l'image serait écrite au mauvais endroit ; 12,2 et 12,3 y copient des octets bruts. NeoBASIC n'a pas de mot-clé pour cet appel.

Texte de la documentation du firmware (anglais)

Blits an image from memory onto the screen. The image will be clipped, so it's safe to blit partly (or fully) offscreen-images. Mode 0 only : in the packed modes (1 and 2) the error flag is set and nothing is drawn (Trinity T-90). Functions 12,2 and 12,3 copy raw bytes into video RAM in every mode ; in the packed modes a byte holds 2 (mode 2) or 8 (mode 1) pixels, and pages $80/$81 address the video RAM from its start, not the draw page of 5,11.

Parameter (0) is the blit action (see function 3, Complex Blit):

Parameters (1,2) address of the source rectangle data.

Parameters (3,4) x pixel coordinate on screen (signed 16 bit)

Parameters (5,6) y pixel coordinate on screen (signed 16 bit)

Parameter (7) destination format, determines how framebuffer will be written: 0: write to whole byte. 1: unsupported 2: unsupported 3: write to high nibble only. 4: write to low nibble only.

NOTE: clipping operates at byte resolution on the source data. So, for example, if you blit a 1-bit image (format 2) to an x-position of -2, then the whole first byte will be skipped leaving 6 empty pixels on the left. Same happens on the right - either the whole source byte is used, or it'll be skipped.

C (llvm-mos)
/* Poser une image d'un bit par pixel, rognée à gauche (groupe 12, fonction 4). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

static uint8_t bits[2] = {0xFF, 0xFF};           /* une ligne de 16 pixels allumés */
static uint8_t source[12];

int main(void) {
    putchar(12);
    source[0] = (uint16_t)bits & 0xFF;
    source[1] = (uint16_t)bits >> 8;
    source[4] = 0;                              /* pas 0 : la même ligne sur toute la hauteur */
    source[6] = 2;                              /* format 2 : 8 pixels d'un bit par octet */
    source[7] = 0;                              /* valeur transparente */
    source[8] = 6;                              /* couleur des pixels à 1 (action 2) */
    source[9] = 4;                              /* hauteur */
    source[10] = 16;                            /* largeur */
    libre();
    ControlPort.params[0] = 2;                  /* P0 : action 2, couleur unie là où la source n'est pas transparente */
    ControlPort.params[1] = (uint16_t)source & 0xFF;   /* P1-P2 : zone source */
    ControlPort.params[2] = (uint16_t)source >> 8;
    ControlPort.params[3] = (uint8_t)-2;        /* P3-P4 : x = -2 (signé) */
    ControlPort.params[4] = 0xFF;
    ControlPort.params[5] = 100;                /* P5-P6 : y = 100 */
    ControlPort.params[6] = 0;
    ControlPort.params[7] = 0;                  /* P7 : écrit l'octet entier */
    api(12, 4);                                 /* Blit Image */
    uint8_t e = ControlPort.error;
    uint8_t p[16];
    for (uint8_t x = 0; x < 16; x++)
        p[x] = neo_graphics_read_pixel(x, 100);
    printf("Erreur %u ; x 0 a 15 :", e);
    for (uint8_t x = 0; x < 16; x++)
        printf(" %u", p[x]);
    printf("\n");
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0
Mad Pascal
// Poser une image d'un bit par pixel, rognée à gauche (groupe 12, fonction 4).
program blit4;
uses crt, neo6502;
const bits: array[0..1] of byte = ($FF, $FF);   // une ligne de 16 pixels allumés
var source: array[0..11] of byte;
    p: array[0..15] of byte;
    x, e: byte;
begin
  write(chr(12));
  fillchar(@source, 12, 0);
  source[0] := lo(word(@bits));
  source[1] := hi(word(@bits));
  source[6] := 2;                               // format 2 : 8 pixels d'un bit par octet ; pas 0
  source[7] := 0;                               // valeur transparente
  source[8] := 6;                               // couleur des pixels à 1 (action 2)
  source[9] := 4;                               // hauteur
  source[10] := 16;                             // largeur
  NeoBlitImage(2, @source, word(-2), 100, 0);   // Blit Image : action 2, x = -2, y = 100, octet entier
  NeoWaitMessage;
  e := NeoMessage.error;
  for x := 0 to 15 do p[x] := NeoGetPixel(x, 100);
  write('Erreur ', e, ' ; x 0 a 15 :');
  for x := 0 to 15 do write(' ', p[x]);
  writeln;
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0
NeoBASIC
10 ' Poser une image d'un bit par pixel, rognee a gauche (groupe 12, fonction 4)
20 cls
30 b = alloc(2) : poke b, $ff : poke b + 1, $ff : ' une ligne de 16 pixels allumes
40 s = alloc(12) : for i = 0 to 11 : poke s + i, 0 : next i
50 doke s, b : poke s + 6, 2 : poke s + 8, 6 : ' format 2 (8 pixels d'un bit par octet), pas 0, couleur 6 pour les 1
60 poke s + 9, 4 : poke s + 10, 16 : ' hauteur 4, largeur 16
70 while peek($ff00) : wend
80 poke $ff04, 2 : doke $ff05, s : doke $ff07, $fffe : doke $ff09, 100 : poke $ff0b, 0 : ' Blit Image : action 2, x = -2 ($fffe), y = 100
90 poke $ff01, 4 : poke $ff00, 12
100 while peek($ff00) : wend : e = peek($ff02)
110 print "Erreur ";e;" ; x 0 a 15 :"; : for x = 0 to 15 : print " ";point(x, 100); : next x : print
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0
Assembleur 65C02 (64tass)
; Poser une image d'un bit par pixel, rognée à gauche (groupe 12, fonction 4).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:   lda #12                    ; écran effacé : on relit des pixels
        jsr WriteCharacter
        lda #<image
        ldx #>image
        jsr envoi
        lda ControlPort+2
        sta e
        ldx #0
_lp:    phx
        txa
        ldy #100
        jsr pixel
        plx
        sta p,x
        inx
        cpx #16
        bne _lp
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda e
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        ldx #0
_ap:  lda #' '
        jsr WriteCharacter
        lda p,x
        jsr decimal
        inx
        cpx #16
        bne _ap
        lda #13
        jsr WriteCharacter
        rts

; Couleur du fond (5,33) en (A, Y), X et Y inférieurs à 256, rendue dans A.
pixel:  pha
        jsr WaitMessage
        pla
        sta ControlPort+4          ; P0-P1 : X
        stz ControlPort+5
        sty ControlPort+6          ; P2-P3 : Y
        stz ControlPort+7
        lda #5
        ldx #33                    ; 5,33 : Read Pixel
        jsr appel
        lda ControlPort+4
        rts

image:  .byte 12, 4, 8, 2          ; 12,4 : Blit Image, action 2 (couleur unie)
        .word source
        .word 65536-2, 100         ; x = -2 (signé), y = 100
        .byte 0                    ; octet entier
bits:   .byte $FF, $FF             ; une ligne de 16 pixels allumés
source: .word bits
        .byte 0, 0
        .word 0                    ; pas 0 : la même ligne sur toute la hauteur
        .byte 2, 0, 6, 4           ; format 2 (bits), transparent 0, couleur 6, hauteur 4
        .word 16                   ; largeur
t0:     .text "Erreur ", 0
t1:     .text " ; x 0 a 15 :", 0
e:      .byte 0
p:      .fill 16

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0

← L'API du Neo6502