Group 12 — Blitter Official
12,1 Blitter Busy · 12,2 Simple Blit Copy · 12,3 Complex Blit Copy · 12,4 Blit Image
The blitter copies memory blocks on the RP2040 side, without going through the 65C02. An address there has 24 bits: a page and a 16-bit address. Pages: $00 the 6502 memory, $80 and $81 the video memory (in mode 0, one byte per pixel, 320 per line: pixel (x, y) is at y × 320 + x, past 65535 in page $81), $90 the graphics memory, $A0 and up the memory banks in flash (read only).
The examples copy to the cleared screen, then read the pixels back with 5,33.
12,1 Blitter Busy Official
Tells whether the blitter is busy copying.
Inputs: none.
| Bytes | Type | Meaning |
|---|---|---|
P0 | uint8 | non zero if a copy is running; always 0 today |
The firmware completes each copy before returning: the call always returns 0, before as well as right after a copy. It stays useful for a program that must also run on a firmware where the copy would be done in the background. NeoBASIC has no keyword for this call.
Firmware documentation text
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;
}Occupe avant : 0, apres une copie : 0Mad 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.Occupe avant : 0, apres une copie : 0NeoBASIC
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 returnOccupe avant : 0, apres une copie : 065C02 assembly (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 0Occupe avant : 0, apres une copie : 012,2 Simple Blit Copy Official
Copies a run of bytes from one memory page to another (6502, video, graphics memory).
| Bytes | Type | Meaning |
|---|---|---|
P0 | uint8 | source page |
P1-P2 | addr | source address within the page |
P3 | uint8 | destination page |
P4-P5 | addr | destination address within the page |
P6-P7 | uint16 | number of bytes |
| Bytes | Type | Meaning |
|---|---|---|
erreur | uint8 | 1 if the start or end of either area does not exist, or if the destination is a bank |
The areas may overlap. An area does not run from one page into the next: in video memory, 65536 bytes in page $80, the rest in page $81. The example copies a text within 6502 memory, eight pixels of colour 5 to the screen (line 100, x 100 to 107; the next pixel stays at 0), then shows a missing page being refused. NeoBASIC has no keyword for this call.
Firmware documentation text
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;
}En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 1Mad 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.En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 1NeoBASIC
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 returnEn memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 165C02 assembly (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 0En memoire : ABCDEFGH
A l'ecran : 5 5, apres : 0
Page $70 : erreur 112,3 Complex Blit Copy Changed by Trinity
Copies a rectangle to another one, converting the pixel format on the way, with optional transparency.
| Bytes | Type | Meaning |
|---|---|---|
P0 | uint8 | action: 0 copy, 1 copy except the transparent value, 2 solid colour where the source is not transparent |
P1-P2 | addr | address of the source area descriptor (12 bytes) |
P3-P4 | addr | address of the target area descriptor (12 bytes) |
| Bytes | Type | Meaning |
|---|---|---|
erreur | uint8 | non zero if the copy is impossible (format, address, target in a bank) |
Area descriptor, in 6502 memory: bytes 0-1 address and 2 page; 3 is 0; 4-5 stride (added to the address from one line to the next; 0 repeats the same line, negative goes upward); 6 format (0 bytes; 1 pairs of 4 bits and 2 single bits, source only; 3 and 4 high or low half of the byte, target only; since Trinity 0.16.69, 5 and 6 two pixels per byte as target, the first pixel in the high (even x) or low (odd x) half, for the 16-colour pages of mode 2: according to the documentation, not tried on the bench); then, source only, 7 transparent value, 8 solid colour, 9 height, 10-11 width in pixels. The copy works on source bytes: the width is rounded down to a whole byte. The example unfolds 4 bytes into 8 pixels (1 to 8) on two lines, thanks to a zero source stride. NeoBASIC has no keyword for this call.
Firmware documentation text
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;
}Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 8Mad 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.Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 8NeoBASIC
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 yLigne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 865C02 assembly (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 0Ligne 1 : 1 2 3 4 5 6 7 8
Ligne 2 : 1 2 3 4 5 6 7 812,4 Blit Image Changed by Trinity
Draws an image (described like the source of 12,3) on the screen, clipping it at the edges.
| Bytes | Type | Meaning |
|---|---|---|
P0 | uint8 | action, as for 12,3 |
P1-P2 | addr | address of the source area descriptor |
P3-P4 | int16 | screen X (signed) |
P5-P6 | int16 | screen Y (signed) |
P7 | uint8 | write: 0 whole byte, 3 high half, 4 low half |
| Bytes | Type | Meaning |
|---|---|---|
erreur | uint8 | non zero in mode 1 or 2, or for an unknown source format |
Clipping works per source byte: a one-bit-per-pixel image drawn at x = -2 loses its whole first byte (8 pixels), so pixels 0 to 5 stay empty and the image starts at x = 6. This is what the example shows (action 2, colour 6). Trinity refuses the call in the packed modes 1 and 2, where the image would be written at the wrong place; 12,2 and 12,3 copy raw bytes there. NeoBASIC has no keyword for this call.
Firmware documentation text
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;
}Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0Mad 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.Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0NeoBASIC
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 : printErreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 065C02 assembly (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 0Erreur 0 ; x 0 a 15 : 0 0 0 0 0 0 6 6 6 6 6 6 6 6 0 0