Tachibana
Language: English

Group 1 — System Official

1,0 DSP Reset · 1,1 Timer · 1,2 Key Status · 1,3 Basic · 1,4 Credits · 1,5 Serial Status · 1,6 Locale · 1,7 System Reset · 1,8 MOS · 1,10 Write character to debug · 1,11 Return Version Information · 1,12 Set Interrupt Tick · 1,13 Get Interrupt Tick · 1,16 Set Frame Interrupt · 1,17 Get Frame Interrupt · 1,18 Select Bank · 1,19 Get Bank Info · 1,20 Get Date Time · 1,21 Set Date Time · 1,22 Write Bank · 1,23 Sync Clock From Modem · 1,24 Set Time Zone · 1,25 Get Time Zone · 1,26 Bank Checksum · 1,27 Apply Boot Choice

1,0 DSP Reset Changed by Trinity

Resets the messaging system (the mailbox) and the components that depend on it. Only for exceptional needs.

Inputs: none.

Outputs: none.

The official documentation says it "should normally not be used". An ordinary program does not need it. With Trinity, the console is cleared by the call: the example prints its message afterwards.

Difference in Trinity — Trinity makes it a soft reset: the API states go back to those of power on (console cleared, mode 0, palette, sprites, fonts, toolbox, no memory bank mapped, sound and stream stopped, tick and frame interrupts off). The 6502 memory is kept, USB devices stay mounted; no logo, no beep.

Firmware documentation text

Soft reset (Trinity T-36, ADR-0001) : puts the API states back as after power on — console cleared, mode 0, palette, sprites, fonts (2,21 built in, Latin-1 letters), toolbox (as 32,19), no memory bank mapped, sound channels and stream stopped, interrupt tick and frame interrupt off. The 6502 memory is kept, the USB devices stay mounted and the display is not restarted (unless another mode was on). No logo, no beep.

C (llvm-mos)
/* Réinitialiser le système de messagerie (groupe 1, fonction 0). */
#include <stdio.h>
#include <neo6502.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 */
        ;
}

int main(void) {
    while (ControlPort.command)        /* boîte aux lettres libre */
        ;
    api(1, 0);                         /* DSP Reset */
    printf("Messagerie reinitialisee\n");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Messagerie reinitialisee
Mad Pascal
// Réinitialiser le système de messagerie (groupe 1, fonction 0).
program dspreset;
uses crt, neo6502;
begin
  NeoSubReset;                                  // DSP Reset
  NeoWaitMessage;                               // attendre la fin de l'appel
  writeln('Messagerie reinitialisee');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Messagerie reinitialisee
NeoBASIC
10 ' Reinitialiser le systeme de messagerie (groupe 1, fonction 0)
20 while peek($ff00) : wend
30 poke $ff01, 0 : poke $ff00, 1
40 while peek($ff00) : wend
50 print "Messagerie reinitialisee"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Messagerie reinitialisee
65C02 assembly (64tass)
; Réinitialiser le système de messagerie (groupe 1, fonction 0).
        .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:  jsr WaitMessage            ; boîte aux lettres libre
        lda #0
        sta ControlPort+1          ; fonction 0 : DSP Reset
        lda #1
        sta ControlPort            ; groupe 1 : lance l'appel
        jsr WaitMessage
        lda #<message
        sta ptr
        lda #>message
        sta ptr+1
        jmp ecrire

; Affiche la chaîne terminée par 0 pointée par (ptr).
ecrire: ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts
ptr = $80

message: .text "Messagerie reinitialisee", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Messagerie reinitialisee

1,1 Timer Official

Reads the system counter, which advances by 100 units per second (one unit = one hundredth of a second).

Inputs: none.

Outputs
BytesTypeMeaning
P0-P3uint32counter value (32 bits, low byte in P0)

Handy for timing, pacing an animation or waiting without blocking. The counter starts at zero at power-on.

NeoBASIC keywords that make this call: event(), time(), wait

Firmware documentation text

Deposit the value (32-bits) of the 100Hz system timer into Parameters:0..3.

C (llvm-mos)
/* Chronométrer une boucle avec le compteur à 100 Hz (groupe 1, fonction 1). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    long debut = neo_system_timer();            /* centièmes de seconde */
    for (volatile unsigned i = 0; i < 30000; i++)
        ;
    long duree = neo_system_timer() - debut;
    printf("Boucle : %ld centiemes\n", duree);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Boucle : 14 centiemes
Mad Pascal
// Chronométrer une boucle avec le compteur à 100 Hz (groupe 1, fonction 1).
program chrono;
uses crt, neo6502;
var debut: cardinal;
    i: word;
begin
  debut := NeoGetTimer;                         // centièmes de seconde
  for i := 0 to 29999 do ;
  writeln('Boucle : ', NeoGetTimer - debut, ' centiemes');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Boucle : 8 centiemes
NeoBASIC
10 ' Chronometrer une boucle avec le compteur a 100 Hz (groupe 1, fonction 1)
20 d = time()
30 for i = 1 to 3000 : next i
40 print "Boucle : ";time() - d;" centiemes"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Boucle : 24 centiemes
65C02 assembly (64tass)
; Chronométrer une boucle avec le compteur à 100 Hz (groupe 1, fonction 1).
; Affiche la durée (octet de poids faible, en décimal).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:  jsr lire_compteur
        sta debut
        ldx #0                     ; boucle d'attente : 256 x 256 tours
        ldy #0
boucle: dey
        bne boucle
        dex
        bne boucle
        jsr lire_compteur
        sec
        sbc debut                  ; durée, en centièmes
        pha
        ldx #0
texte:  lda message,x              ; "Boucle : "
        beq nombre
        jsr WriteCharacter
        inx
        bne texte
nombre: pla
        jsr decimal
        ldx #0
fin:    lda suffixe,x              ; " centiemes"
        beq retour
        jsr WriteCharacter
        inx
        bne fin
retour: rts

; Groupe 1, fonction 1 : le compteur revient dans les paramètres 0 à 3.
lire_compteur:
        jsr WaitMessage            ; boîte aux lettres libre
        lda #1
        sta ControlPort+1          ; fonction 1 : Timer
        sta ControlPort            ; groupe 1 : écrit en dernier, lance l'appel
        jsr WaitMessage            ; attendre la fin
        lda ControlPort+4          ; octet de poids faible
        rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        ldx #0                     ; x = centaines déjà affichées ?
        ldy #100
        jsr chiffre
        ldy #10
        jsr chiffre
        ora #'0'
        jmp WriteCharacter
chiffre:
        sty diviseur
        ldy #0
div:    cmp diviseur
        bcc fini
        sbc diviseur
        iny
        bne div
fini:   pha
        tya
        bne ecrire
        cpx #0
        beq saute
ecrire: ora #'0'
        jsr WriteCharacter
        ldx #1
saute:  pla
        rts

debut:    .byte 0
diviseur: .byte 0
message:  .text "Boucle : ", 0
suffixe:  .text " centiemes", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Boucle : 5 centiemes

1,2 Key Status Official

Tells whether a key is held down, and the state of the modifier keys (Shift, Ctrl, Alt, Meta).

Inputs
BytesTypeMeaning
P0uint8key code (USB HID usage: 4 = A, 44 = space, 80 = left arrow)
Outputs
BytesTypeMeaning
P0uint8key state: $FF down, 0 up
P1uint8modifier keys held (one bit each)

Reads the instantaneous keyboard state, without going through the queue of typed characters — ideal for a game. In NeoBASIC, key(n) makes the same call.

NeoBASIC keywords that make this call: key()

Firmware documentation text

Deposits the state of the specified keyboard key into Parameter:0. State of keyboard modifiers (Shift/Ctrl/Alt/Meta) is returned in Parameter:1. The key which to query is specified in Parameter:0.

C (llvm-mos)
/* Savoir si la barre d'espace est enfoncée (groupe 1, fonction 2). */
#include <stdio.h>
#include <neo/api.h>

#define TOUCHE_ESPACE 44                /* code USB HID de la barre d'espace */

int main(void) {
    uint8_t etat = neo_system_key_status(TOUCHE_ESPACE);
    printf("Espace : %s\n", etat ? "enfoncee" : "relachee");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Espace : relachee
Mad Pascal
// Savoir si la barre d'espace est enfoncée (groupe 1, fonction 2).
program touche;
uses crt, neo6502;
const TOUCHE_ESPACE = 44;                       // code USB HID de la barre d'espace
begin
  if NeoIsKeyPressed(TOUCHE_ESPACE) <> 0 then
    writeln('Espace : enfoncee')
  else
    writeln('Espace : relachee');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Espace : relachee
NeoBASIC
10 ' Savoir si la barre d'espace est enfoncee (groupe 1, fonction 2)
20 ' 44 est le code USB HID de la barre d'espace
30 if key(44)
40   print "Espace : enfoncee"
50 else
60   print "Espace : relachee"
70 endif
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Espace : relachee
65C02 assembly (64tass)
; Savoir si la barre d'espace est enfoncée (groupe 1, 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

TOUCHE_ESPACE = 44                 ; code USB HID de la barre d'espace

start:  jsr WaitMessage
        lda #TOUCHE_ESPACE
        sta ControlPort+4          ; P0 : la touche à interroger
        lda #2
        sta ControlPort+1          ; fonction 2 : Key Status
        lda #1
        sta ControlPort            ; groupe 1 : lance l'appel
        jsr WaitMessage
        lda ControlPort+4          ; P0 : $FF enfoncée, 0 relâchée
        beq relachee
        lda #<enfoncee
        ldx #>enfoncee
        bne afficher
relachee:
        lda #<txt_relachee
        ldx #>txt_relachee
afficher:
        sta ptr
        stx ptr+1
        jmp ecrire

; Affiche la chaîne terminée par 0 pointée par (ptr).
ecrire: ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts
ptr = $80

enfoncee:     .text "Espace : enfoncee", 13, 0
txt_relachee: .text "Espace : relachee", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Espace : relachee

1,3 Basic Changed by Trinity

Loads the resident environment and hands over to it with an indirect jump through address 0: NeoBASIC with the official firmware, NeoDOS with Trinity.

Inputs: none.

Outputs: none.

Used to end a machine code program by handing control back. The calling program is overwritten; nothing runs after the jump. In Mad Pascal, NeoExecuteBasic only makes the call: the jump through address 0 still has to be written (see the example).

Difference in Trinity — Trinity keeps the function number and name, but loads NeoDOS (resident environment since Trinity 0.4.0) instead of NeoBASIC. NeoBASIC remains available through boot/neobasic.bin or NEOBASIC.NEO. Since Trinity 0.16.60, 1,3 always loads NeoDOS: the boot menu choice is applied by 1,27, which the kernel calls once at reset.

Firmware documentation text

Loads the resident environment (NeoDOS on Trinity) and sets the indirect jump through address zero to start it. Since T-36 it always loads NeoDOS : the boot menu choice is applied by Function 1,27, which the kernel calls once at reset.

C (llvm-mos)
/* Rendre la main à l'environnement résident (NeoBASIC ou NeoDOS) (groupe 1, fonction 3). */
#include <stdio.h>
#include <neo6502.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 */
        ;
}

int main(void) {
    printf("Retour a l'environnement resident\n");
    while (ControlPort.command)
        ;
    api(1, 3);                                  /* charge l'environnement résident */
    __asm__ volatile ("jmp ($0000)");           /* et lui passe la main */
    return 0;                                   /* jamais atteint */
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Retour a l'environnement resident

NeoDOS version 0.32.1
(C) 2026 bmarty - Neo6502 disk operating system
Mad Pascal
// Rendre la main à l'environnement résident (NeoBASIC ou NeoDOS) (groupe 1, fonction 3).
program versresident;
uses crt, neo6502;
begin
  writeln('Retour a l''environnement resident');
  NeoExecuteBasic;                              // charge l'environnement résident (appel 1,3 seulement)
  NeoWaitMessage;
  asm
    jmp ($0000)                                 // et lui passe la main : NeoExecuteBasic ne le fait pas
  end;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Retour a l'environnement resident

NeoDOS version 0.32.1
(C) 2026 bmarty - Neo6502 disk operating system
NeoBASIC

Not applicable in NeoBASIC: A BASIC program does not need to reload the resident environment: it would erase the running program.

65C02 assembly (64tass)
; Rendre la main à l'environnement résident (NeoBASIC ou NeoDOS) (groupe 1, 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 #<message
        sta ptr
        lda #>message
        sta ptr+1
        jsr ecrire
        jsr WaitMessage
        lda #3
        sta ControlPort+1          ; fonction 3 : Basic
        lda #1
        sta ControlPort            ; groupe 1 : charge l'environnement résident
        jsr WaitMessage
        jmp ($0000)                ; et lui passe la main

; Affiche la chaîne terminée par 0 pointée par (ptr).
ecrire: ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts
ptr = $80

message: .text "Retour a l'environnement resident", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Retour a l'environnement resident

NeoDOS version 0.32.1
(C) 2026 bmarty - Neo6502 disk operating system

1,4 Credits Official

Prints the list of Neo6502 project contributors, kept in flash memory.

Inputs: none.

Outputs: none.

Firmware documentation text

Print the Neo6502 project contributors (stored in flash memory).

C (llvm-mos)
/* Afficher les contributeurs du Neo6502 (groupe 1, fonction 4). */
#include <neo/api.h>

int main(void) {
    neo_system_credits();
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Thanks to : Bill Auger,Wojciech Bocianski,Chris Garre
tt,David Given,Pete Gollan,Rien Matthijse,Andy McCall
,Paul Robson,Oliver Schmidt,Sascha Schneider,Veselin
Sladkov,Tsvetan Usunov
Mad Pascal
// Afficher les contributeurs du Neo6502 (groupe 1, fonction 4).
program credits;
uses crt, neo6502;
begin
  NeoCredits;
  NeoWaitMessage;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Thanks to : Bill Auger,Wojciech Bocianski,Chris Garre
tt,David Given,Pete Gollan,Rien Matthijse,Andy McCall
,Paul Robson,Oliver Schmidt,Sascha Schneider,Veselin
Sladkov,Tsvetan Usunov

                      A:\>
NeoBASIC
10 ' Afficher les contributeurs du Neo6502 (groupe 1, fonction 4)
20 while peek($ff00) : wend
30 poke $ff01, 4 : poke $ff00, 1
40 while peek($ff00) : wend
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Thanks to : Bill Auger,Wojciech Bocianski,Chris Garre
tt,David Given,Pete Gollan,Rien Matthijse,Andy McCall
,Paul Robson,Oliver Schmidt,Sascha Schneider,Veselin
Sladkov,Tsvetan Usunov
65C02 assembly (64tass)
; Afficher les contributeurs du Neo6502 (groupe 1, 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:  jsr WaitMessage
        lda #4
        sta ControlPort+1          ; fonction 4 : Credits
        lda #1
        sta ControlPort            ; groupe 1 : lance l'appel
        jmp WaitMessage
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Thanks to : Bill Auger,Wojciech Bocianski,Chris Garre
tt,David Given,Pete Gollan,Rien Matthijse,Andy McCall
,Paul Robson,Oliver Schmidt,Sascha Schneider,Veselin
Sladkov,Tsvetan Usunov

                      A:\>

1,5 Serial Status Official

Checks the serial port and, if it works, enters transfer mode: the Neo6502 receives data blocks sent by a computer, until Escape is pressed.

Inputs: none.

Outputs: none.

On the board, the call prints "Serial link enabled." and only returns when Escape is pressed. Without a usable serial port (emulators), it prints "Serial not functioning." and returns at once.

Firmware documentation text

Check the serial port to see if there is a data transmission.

C (llvm-mos)
/* Passer en mode transfert série (groupe 1, fonction 5). */
#include <neo/api.h>

int main(void) {
    neo_system_serial_update();         /* sur la carte : jusqu'à l'appui sur Échap */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Serial not functioning.
Mad Pascal
// Passer en mode transfert série (groupe 1, fonction 5).
program serie;
uses crt, neo6502;
var etat: byte;
begin
  etat := NeoCheckSerial;                       // sur la carte : jusqu'à l'appui sur Échap
  NeoWaitMessage;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Serial not functioning.
NeoBASIC
10 ' Passer en mode transfert serie (groupe 1, fonction 5)
20 while peek($ff00) : wend
30 poke $ff01, 5 : poke $ff00, 1
40 while peek($ff00) : wend
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Serial not functioning.
65C02 assembly (64tass)
; Passer en mode transfert série (groupe 1, fonction 5).
        .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:  jsr WaitMessage
        lda #1
        ldx #5                     ; fonction 5 : Serial Status
        jmp appel                  ; sur la carte : jusqu'à l'appui sur Échap

; 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
diviseur: .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Serial not functioning.

1,6 Locale Official

Selects the language and keyboard layout, with a two upper-case letter code (FR, US, DE…).

Inputs
BytesTypeMeaning
P0charfirst letter of the code (e.g. $46 for F)
P1charsecond letter of the code (e.g. $52 for R)

Outputs: none.

In NeoBASIC, locale "FR" makes the same call.

NeoBASIC keywords that make this call: locale

Firmware documentation text

Set the locale code specified in Parameters:0,1 as upper-case ASCII letters. Parameter:0 takes the first letter and Parameter:1 takes the second letter.

For example: French (FR) would require Parametr 0 being $46 and Parameter 1 being $52

C (llvm-mos)
/* Passer le clavier en disposition française (groupe 1, fonction 6). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    neo_system_locale("FR");
    printf("Clavier : FR\n");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Locale is now 'fr'
Clavier : FR
Mad Pascal
// Passer le clavier en disposition française (groupe 1, fonction 6).
program locale;
uses crt, neo6502;
begin
  NeoSetLocale('FR');
  NeoWaitMessage;
  writeln('Clavier : FR');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Locale is now 'fr'
Clavier : FR
NeoBASIC
10 ' Passer le clavier en disposition francaise (groupe 1, fonction 6)
20 locale "FR"
30 print "Clavier : FR"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Locale is now 'fr'
Clavier : FR
65C02 assembly (64tass)
; Passer le clavier en disposition française (groupe 1, fonction 6).
        .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:  jsr WaitMessage
        lda #'F'
        sta ControlPort+4          ; P0 : première lettre
        lda #'R'
        sta ControlPort+5          ; P1 : seconde lettre
        lda #6
        sta ControlPort+1          ; fonction 6 : Locale
        lda #1
        sta ControlPort            ; groupe 1 : lance l'appel
        jsr WaitMessage
        lda #<message
        sta ptr
        lda #>message
        sta ptr+1
        jmp ecrire

; Affiche la chaîne terminée par 0 pointée par (ptr).
ecrire: ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts
ptr = $80

message: .text "Clavier : FR", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Locale is now 'fr'
Clavier : FR

1,7 System Reset Official

Restarts the whole machine, like a power cycle: the RP2040 is reset by its watchdog, and the 65C02 with it.

Inputs: none.

Outputs: none.

Nothing runs after the call; the 65C02 memory is not kept.

Firmware documentation text

System Reset. This is a full hardware reset. It resets the RP2040 using the Watchdog timer, and this also resets the 65C02.

C (llvm-mos)
/* Redémarrer la machine (groupe 1, fonction 7). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    printf("Redemarrage...\n");
    neo_system_reset();                 /* ne revient pas */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Redemarrage...
Mad Pascal
// Redémarrer la machine (groupe 1, fonction 7).
program redemarre;
uses crt, neo6502;
begin
  writeln('Redemarrage...');
  NeoReset;                                     // ne revient pas
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Redemarrage...
NeoBASIC
10 ' Redemarrer la machine (groupe 1, fonction 7)
20 print "Redemarrage..."
30 while peek($ff00) : wend
40 poke $ff01, 7 : poke $ff00, 1
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Redemarrage...
65C02 assembly (64tass)
; Redémarrer la machine (groupe 1, fonction 7).
        .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 #<message
        sta ptr
        lda #>message
        sta ptr+1
        jsr ecrire
        jsr WaitMessage
        lda #7
        sta ControlPort+1          ; fonction 7 : System Reset
        lda #1
        sta ControlPort            ; groupe 1 : la machine redémarre
boucle: bra boucle

; Affiche la chaîne terminée par 0 pointée par (ptr).
ecrire: ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts
ptr = $80

message: .text "Redemarrage...", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Redemarrage...

1,8 MOS Official

Runs a MOS "star" command, such as *cat. Available commands: cat (or .), del, file, copy, ren, md, cd.

Inputs
BytesTypeMeaning
P0-P1addraddress of the command: length-prefixed string, the leading star is optional
Outputs
BytesTypeMeaning
erreuruint80 if the command succeeded, 255 if it is unknown

Arguments are separated by spaces; an argument containing spaces goes between quotes.

Firmware documentation text

Do a MOS command (a '* command') these are specified in the Wiki as they will be steadily expanded.

C (llvm-mos)
/* Lister le répertoire courant avec la commande MOS *cat (groupe 1, fonction 8). */
#include <stdio.h>
#include <neo6502.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 const char commande[] = "\x04*cat";      /* longueur, puis le texte */

int main(void) {
    printf("Catalogue :\n");
    while (ControlPort.command)
        ;
    ControlPort.params[0] = (uint16_t)commande & 0xFF;
    ControlPort.params[1] = (uint16_t)commande >> 8;
    api(1, 8);                                  /* MOS */
    if (ControlPort.error)
        printf("Commande inconnue\n");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Catalogue :
PROG.NEO                        173 bytes.
Mad Pascal
// Lister le répertoire courant avec la commande MOS *cat (groupe 1, fonction 8).
program cat;
uses crt, neo6502;
var commande: string[16];
begin
  writeln('Catalogue :');
  commande := '*cat';                           // chaîne Pascal : longueur puis texte
  NeoMOS(@commande);
  NeoWaitMessage;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Catalogue :
PROG.NEO                        273 bytes.
NeoBASIC
10 ' Lister le repertoire courant avec la commande MOS *cat (groupe 1, fonction 8)
20 c$ = "*cat" : a = alloc(8) : poke a, len(c$)
30 for i = 1 to len(c$) : poke a + i, asc(mid$(c$, i, 1)) : next i
40 print "Catalogue :"
50 while peek($ff00) : wend
60 poke $ff04, a & 255 : poke $ff05, a >> 8 : poke $ff01, 8 : poke $ff00, 1
70 while peek($ff00) : wend
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Catalogue :
prog.bas                        493 bytes.
boot                            <Dir>
65C02 assembly (64tass)
; Lister le répertoire courant avec la commande MOS *cat (groupe 1, fonction 8).
        .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 #<titre
        sta ptr
        lda #>titre
        sta ptr+1
        jsr ecrire
        jsr WaitMessage
        lda #<commande
        sta ControlPort+4          ; P0-P1 : adresse de la commande
        lda #>commande
        sta ControlPort+5
        lda #8
        sta ControlPort+1          ; fonction 8 : MOS
        lda #1
        sta ControlPort            ; groupe 1 : lance l'appel
        jmp WaitMessage

; Affiche la chaîne terminée par 0 pointée par (ptr).
ecrire: ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts
ptr = $80

titre:    .text "Catalogue :", 13, 0
commande: .byte 4                  ; longueur
          .text "*cat"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Catalogue :
PROG.NEO                        82 bytes.

1,10 Write character to debug Official

Sends a character to the debug port (the Pico UART on the board, stderr in the emulators), without touching the screen.

Inputs
BytesTypeMeaning
P0charthe character to send

Outputs: none.

To trace a program without disturbing the display.

Firmware documentation text

Writes a single character to the debug port (the UART on the Pico, or stderr on the emulator). This allows maximum flexibility.

C (llvm-mos)
/* Écrire une trace sur le port de débogage (groupe 1, fonction 10). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    const char *trace = "Bonjour\n";
    while (*trace)
        neo_system_debug_putc(*trace++);
    printf("Trace envoyee\n");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Trace envoyee
Mad Pascal
// Écrire une trace sur le port de débogage (groupe 1, fonction 10).
program trace;
uses crt, neo6502;
var trace: string[16];
    i: byte;
begin
  trace := 'Bonjour';
  for i := 1 to length(trace) do NeoWriteDebug(ord(trace[i]));
  NeoWriteDebug(10);
  NeoWaitMessage;
  writeln('Trace envoyee');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Trace envoyee
NeoBASIC
10 ' Ecrire une trace sur le port de debogage (groupe 1, fonction 10)
20 t$ = "Bonjour"
30 for i = 1 to len(t$)
40 while peek($ff00) : wend
50 poke $ff04, asc(mid$(t$, i, 1)) : poke $ff01, 10 : poke $ff00, 1
60 next i
70 while peek($ff00) : wend
80 print "Trace envoyee"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Trace envoyee
65C02 assembly (64tass)
; Écrire une trace sur le port de débogage (groupe 1, fonction 10).
        .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:  ldx #0
suivant:
        lda trace,x
        beq fini
        jsr WaitMessage
        sta ControlPort+4          ; P0 : le caractère
        lda #10
        sta ControlPort+1          ; fonction 10 : Write character to debug
        lda #1
        sta ControlPort            ; groupe 1 : lance l'appel
        inx
        bne suivant
fini:   jsr WaitMessage
        lda #<message
        sta ptr
        lda #>message
        sta ptr+1
        jmp ecrire

; Affiche la chaîne terminée par 0 pointée par (ptr).
ecrire: ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts
ptr = $80

trace:   .text "Bonjour", 10, 0
message: .text "Trace envoyee", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Trace envoyee

1,11 Return Version Information Official

Gives the firmware version, as major.minor.patch.

Inputs: none.

Outputs
BytesTypeMeaning
P0uint8major version
P1uint8minor version
P2uint8patch

Lets a program check that it runs on a recent enough firmware.

Firmware documentation text

Reads the current version Major.Minor.Patch into Parameters:0..2 These values are guaranteed to be in the range 0.255

C (llvm-mos)
/* Afficher la version du firmware (groupe 1, fonction 11). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    neo_version_t v;
    neo_system_version(&v);
    printf("Version : %d.%d.%d\n", v.major, v.minor, v.patch);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Version : 0.16.69
Mad Pascal
// Afficher la version du firmware (groupe 1, fonction 11).
program version;
uses crt, neo6502;
var majeure, mineure, correctif: byte;
begin
  NeoSendMessage(1, 11);                        // Return Version Information
  NeoWaitMessage;
  majeure := NeoMessage.params[0];
  mineure := NeoMessage.params[1];
  correctif := NeoMessage.params[2];
  writeln('Version : ', majeure, '.', mineure, '.', correctif);
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Version : 0.16.69
NeoBASIC
10 ' Afficher la version du firmware (groupe 1, fonction 11)
20 ' lire les resultats dans des variables avant tout print : print utilise aussi la boite aux lettres
30 while peek($ff00) : wend
40 poke $ff01, 11 : poke $ff00, 1
50 while peek($ff00) : wend : a = peek($ff04) : b = peek($ff05) : c = peek($ff06)
60 print "Version : ";a;".";b;".";c
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Version : 0.16.69
65C02 assembly (64tass)
; Afficher la version du firmware (groupe 1, fonction 11).
        .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:  jsr WaitMessage
        lda #11
        sta ControlPort+1          ; fonction 11 : Return Version Information
        lda #1
        sta ControlPort            ; groupe 1 : lance l'appel
        jsr WaitMessage
        lda ControlPort+4          ; recopier P0-P2 : l'affichage réutilise la boîte aux lettres
        sta version
        lda ControlPort+5
        sta version+1
        lda ControlPort+6
        sta version+2
        lda #<message
        sta ptr
        lda #>message
        sta ptr+1
        jsr ecrire
        lda version
        jsr decimal
        lda #'.'
        jsr WriteCharacter
        lda version+1
        jsr decimal
        lda #'.'
        jsr WriteCharacter
        lda version+2
        jsr decimal
        lda #13
        jmp WriteCharacter

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        ldx #0
        ldy #100
        jsr chiffre
        ldy #10
        jsr chiffre
        ora #'0'
        jmp WriteCharacter
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

; Affiche la chaîne terminée par 0 pointée par (ptr).
ecrire: ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts
ptr = $80

version:  .byte 0, 0, 0
diviseur: .byte 0
message:  .text "Version : ", 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Version : 0.16.69

1,12 Set Interrupt Tick Trinity

Triggers a periodic 65C02 interrupt (IRQB pin) at the chosen rate, from 1 to 1000 Hz; 0 stops it.

Inputs
BytesTypeMeaning
P0-P1uint16rate in Hz (1 to 1000), 0 to stop

Outputs: none.

Install the interrupt handler first: its address goes to $FFFE-$FFFF (in RAM), then CLI. IRQB stays low until the vector is fetched: the handler has nothing to acknowledge and cannot be re-entered. If the main program may also be preparing a call, a handler that calls the API must save and restore $FF01-$FF0B. DSP Reset (1,0) stops the tick.

Firmware documentation text

Starts (or stops with 0) a periodic interrupt to the 65C02 at Parameters:0,1 Hz (1..1000) on IRQB (Trinity T-14, F-60 of the fork). The interrupt vector $FFFE/$FFFF is in RAM : install a handler there first, then CLI. IRQB is held low until the 65C02 fetches the vector at $FFFF, so the handler needs no acknowledge and cannot re-enter ; with I=1 the line stays low until CLI (ticks coalesce, none is lost). An interrupt handler may call the API but must save and restore $FF01-$FF0B if the main program could be preparing a call. DSP Reset (1,0) stops the tick. First delivery on a board : to be validated (bus loop timing, see docs/BACKLOG.md T-14).

C (llvm-mos)
/* Compter les interruptions d'un tick à 100 Hz pendant une seconde (groupe 1, fonction 12). */
#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 volatile uint16_t compteur;

__attribute__((interrupt)) void tick(void) {   /* routine d'interruption (llvm-mos) */
    compteur++;
}

static void regler_tick(uint16_t hz) {
    libre();
    ControlPort.params[0] = hz & 0xFF;
    ControlPort.params[1] = hz >> 8;
    api(1, 12);                                 /* Set Interrupt Tick */
}

int main(void) {
    *(void (**)(void))0xFFFE = tick;            /* vecteur d'interruption, en RAM */
    regler_tick(100);
    __asm__ volatile ("cli");
    long debut = neo_system_timer();
    while (neo_system_timer() - debut < 100)    /* une seconde */
        ;
    __asm__ volatile ("sei");
    regler_tick(0);                             /* arrêter le tick */
    printf("Interruptions en 1 s : %u\n", compteur);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Interruptions en 1 s : 99
Mad Pascal
// Compter les interruptions d'un tick à 100 Hz pendant une seconde (groupe 1, fonction 12).
program tick;
uses crt, neo6502;
var compteur: word;
    vecteur: word absolute $FFFE;               // vecteur d'interruption, en RAM
    debut: cardinal;

procedure recevoir; assembler;                  // routine d'interruption : se termine par rti
asm
  inc compteur
  bne @+
  inc compteur+1
@:
  rti
end;

procedure regler_tick(hz: word);
begin
  NeoWaitMessage;
  wordParams[0] := hz;                          // P0-P1 : fréquence en Hz
  NeoSendMessage(1, 12);                        // Set Interrupt Tick
  NeoWaitMessage;
end;

begin
  compteur := 0;
  vecteur := word(@recevoir);
  regler_tick(100);
  asm
    cli
  end;
  debut := NeoGetTimer;
  repeat until NeoGetTimer - debut >= 100;      // une seconde
  asm
    sei
  end;
  regler_tick(0);                               // arrêter le tick
  writeln('Interruptions en 1 s : ', compteur);
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Interruptions en 1 s : 99
NeoBASIC

Not applicable in NeoBASIC: An interrupt handler is machine code; NeoBASIC cannot provide one.

65C02 assembly (64tass)
; Compter les interruptions d'un tick à 100 Hz pendant une seconde (groupe 1, fonction 12).
        .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 #<tick                 ; installer la routine d'interruption
        sta $FFFE
        lda #>tick
        sta $FFFF
        jsr WaitMessage
        lda #100
        sta ControlPort+4          ; P0-P1 : 100 Hz
        stz ControlPort+5
        lda #1
        ldx #12                    ; fonction 12 : Set Interrupt Tick
        jsr appel
        cli
        jsr compteur_100hz         ; attendre une seconde (100 centièmes)
        clc
        adc #100
        sta fin
_att:   jsr compteur_100hz
        cmp fin
        bne _att
        sei
        jsr WaitMessage
        stz ControlPort+4          ; 0 : arrêter le tick
        stz ControlPort+5
        lda #1
        ldx #12
        jsr appel
        lda #<message
        ldx #>message
        jsr ecrire
        lda nombre                 ; octet de poids faible suffit ici (< 256)
        jsr decimal
        lda #13
        jmp WriteCharacter

tick:   inc nombre                 ; routine d'interruption : rien à acquitter
        rti

; Octet de poids faible du compteur à 100 Hz (groupe 1, fonction 1).
compteur_100hz:
        jsr WaitMessage
        lda #1
        tax                        ; fonction 1 : Timer
        jsr appel
        lda ControlPort+4
        rts

; 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
diviseur: .byte 0

nombre:  .byte 0
fin:     .byte 0
message: .text "Interruptions en 1 s : ", 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Interruptions en 1 s : 99

1,13 Get Interrupt Tick Trinity

Gives the current rate of the interrupt tick set by function 1,12 (0 if it is stopped).

Inputs: none.

Outputs
BytesTypeMeaning
P0-P1uint16rate in Hz, 0 if stopped
Firmware documentation text

Returns the current interrupt tick rate in Parameters:0,1 (0 = off).

C (llvm-mos)
/* Lire la fréquence du tick d'interruption (groupe 1, fonction 13). */
#include <stdio.h>
#include <neo6502.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)
        ;
}

int main(void) {
    libre();
    api(1, 13);                                 /* Get Interrupt Tick */
    uint16_t hz = ControlPort.params[0] | (ControlPort.params[1] << 8);
    printf("Tick : %u Hz\n", hz);              /* 0 : aucun tick en cours */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Tick : 0 Hz
Mad Pascal
// Lire la fréquence du tick d'interruption (groupe 1, fonction 13).
program gettick;
uses crt, neo6502;
var hz: word;
begin
  NeoSendMessage(1, 13);                        // Get Interrupt Tick
  NeoWaitMessage;
  hz := wordParams[0];                          // 0 : aucun tick en cours
  writeln('Tick : ', hz, ' Hz');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Tick : 0 Hz
NeoBASIC
10 ' Lire la frequence du tick d'interruption (groupe 1, fonction 13)
20 ' lire les resultats dans des variables juste apres l'appel : print reutilise la boite aux lettres
30 while peek($ff00) : wend
40 poke $ff01, 13 : poke $ff00, 1
50 while peek($ff00) : wend : hz = peek($ff04) + 256 * peek($ff05)
60 print "Tick : ";hz;" Hz"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Tick : 0 Hz
65C02 assembly (64tass)
; Lire la fréquence du tick d'interruption (groupe 1, fonction 13).
        .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:  jsr WaitMessage
        lda #1
        ldx #13                    ; fonction 13 : Get Interrupt Tick
        jsr appel
        lda ControlPort+4          ; P0 : poids faible (le tick est ici arrêté : 0)
        sta hz
        lda #<message
        ldx #>message
        jsr ecrire
        lda hz
        jsr decimal
        lda #<unite
        ldx #>unite
        jmp ecrire

; 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
diviseur: .byte 0

hz:      .byte 0
message: .text "Tick : ", 0
unite:   .text " Hz", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Tick : 0 Hz

1,16 Set Frame Interrupt Trinity

Triggers a 65C02 interrupt at the start of every displayed frame (60 Hz, vertical sync of the DVI output); 0 stops it.

Inputs
BytesTypeMeaning
P0uint8non zero: enable, 0: stop

Outputs: none.

Same delivery as the tick (1,12): handler at $FFFE-$FFFF, then CLI. Tick and frame interrupt may both be on: the handler cannot tell them apart (use 5,37 Frame Count or 1,1 Timer). Ideal for flicker-free animation.

Firmware documentation text

Parameter:0 non zero : an interrupt is delivered to the 65C02 at the start of every displayed frame (60 Hz vertical sync of the DVI output), 0 stops it (Trinity T-14, F-10 of the fork). Same delivery as Function 1,12 : IRQB is held low until the vector fetch at $FFFF ; a frame arriving while interrupts are disabled coalesces with the next. The tick and the frame interrupt may both be on : the handler cannot tell them apart (use 5,37 Frame Count or 1,1 Timer). Stopped by DSP Reset (1,0).

C (llvm-mos)
/* Compter les images affichées en une seconde, par interruption (groupe 1, fonction 16). */
#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 volatile uint16_t images;

__attribute__((interrupt)) void image(void) {  /* une interruption par image */
    images++;
}

static void interruption_image(uint8_t active) {
    libre();
    ControlPort.params[0] = active;
    api(1, 16);                                 /* Set Frame Interrupt */
}

int main(void) {
    *(void (**)(void))0xFFFE = image;
    interruption_image(1);
    __asm__ volatile ("cli");
    long debut = neo_system_timer();
    while (neo_system_timer() - debut < 100)
        ;
    __asm__ volatile ("sei");
    interruption_image(0);
    printf("Images en 1 s : %u\n", images);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Images en 1 s : 60
Mad Pascal
// Compter les images affichées en une seconde, par interruption (groupe 1, fonction 16).
program images;
uses crt, neo6502;
var nombre: word;
    vecteur: word absolute $FFFE;
    debut: cardinal;

procedure image; assembler;                     // une interruption par image
asm
  inc nombre
  bne @+
  inc nombre+1
@:
  rti
end;

procedure interruption_image(active: byte);
begin
  NeoWaitMessage;
  NeoMessage.params[0] := active;
  NeoSendMessage(1, 16);                        // Set Frame Interrupt
  NeoWaitMessage;
end;

begin
  nombre := 0;
  vecteur := word(@image);
  interruption_image(1);
  asm
    cli
  end;
  debut := NeoGetTimer;
  repeat until NeoGetTimer - debut >= 100;
  asm
    sei
  end;
  interruption_image(0);
  writeln('Images en 1 s : ', nombre);
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Images en 1 s : 60
NeoBASIC

Not applicable in NeoBASIC: An interrupt handler is machine code; NeoBASIC cannot provide one.

65C02 assembly (64tass)
; Compter les images affichées en une seconde, par interruption (groupe 1, fonction 16).
        .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 #<image
        sta $FFFE
        lda #>image
        sta $FFFF
        jsr WaitMessage
        lda #1
        sta ControlPort+4          ; P0 : activer
        ldx #16                    ; fonction 16 : Set Frame Interrupt
        jsr appel
        cli
        jsr compteur_100hz
        clc
        adc #100
        sta fin
_att:   jsr compteur_100hz
        cmp fin
        bne _att
        sei
        jsr WaitMessage
        stz ControlPort+4          ; 0 : arrêter
        lda #1
        ldx #16
        jsr appel
        lda #<message
        ldx #>message
        jsr ecrire
        lda images
        jsr decimal
        lda #13
        jmp WriteCharacter

image:  inc images                 ; une interruption par image
        rti

compteur_100hz:
        jsr WaitMessage
        lda #1
        tax                        ; fonction 1 : Timer
        jsr appel
        lda ControlPort+4
        rts

; 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
diviseur: .byte 0

images:  .byte 0
fin:     .byte 0
message: .text "Images en 1 s : ", 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Images en 1 s : 60

1,17 Get Frame Interrupt Trinity

Tells whether the frame interrupt (function 1,16) is on.

Inputs: none.

Outputs
BytesTypeMeaning
P0uint8non zero if on
Firmware documentation text

Returns in Parameter:0 whether the frame interrupt (Function 1,16) is on.

C (llvm-mos)
/* Savoir si l'interruption d'image est active (groupe 1, fonction 17). */
#include <stdio.h>
#include <neo6502.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)
        ;
}

int main(void) {
    libre();
    api(1, 17);                                 /* Get Frame Interrupt */
    printf("Interruption d'image : %s\n", ControlPort.params[0] ? "active" : "inactive");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Interruption d'image : inactive
Mad Pascal
// Savoir si l'interruption d'image est active (groupe 1, fonction 17).
program getframe;
uses crt, neo6502;
begin
  NeoSendMessage(1, 17);                        // Get Frame Interrupt
  NeoWaitMessage;
  if NeoMessage.params[0] <> 0 then
    writeln('Interruption d''image : active')
  else
    writeln('Interruption d''image : inactive');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Interruption d'image : inactive
NeoBASIC
10 ' Savoir si l'interruption d'image est active (groupe 1, fonction 17)
20 while peek($ff00) : wend
30 poke $ff01, 17 : poke $ff00, 1
40 while peek($ff00) : wend : a = peek($ff04)
50 if a
60   print "Interruption d'image : active"
70 else
80   print "Interruption d'image : inactive"
90 endif
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Interruption d'image : inactive
65C02 assembly (64tass)
; Savoir si l'interruption d'image est active (groupe 1, fonction 17).
        .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:  jsr WaitMessage
        lda #1
        ldx #17                    ; fonction 17 : Get Frame Interrupt
        jsr appel
        lda ControlPort+4          ; P0 : non nul si active
        beq _non
        lda #<active
        ldx #>active
        jmp ecrire
_non:   lda #<inactive
        ldx #>inactive
        jmp ecrire

; 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
diviseur: .byte 0

active:   .text "Interruption d'image : active", 13, 0
inactive: .text "Interruption d'image : inactive", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Interruption d'image : inactive

1,18 Select Bank Trinity

Copies one of the 32 banks of 8 KB kept in the RP2040 flash into an 8 KB window of 65C02 RAM.

Inputs
BytesTypeMeaning
P0uint8bank number (0 to 31); $FF forgets the mapping without touching the window
P1-P2addrwindow address: page aligned, ending below $FF00
Outputs
BytesTypeMeaning
erreuruint8non zero if the bank or the address is invalid

The window is a copy: changing it does not change the bank (see 1,22). It must not hold the code making the call, the stack or the zero page. A bank never written reads $FF. The banks are also blitter pages $A0 to $BF (12,2), read only.

Firmware documentation text

Memory banks in flash (Trinity T-17, from F-23 of the fork). Copies bank Parameter:0 (0-31, 8k each, kept in the RP2040 flash and read in place) into the 8k window at Parameters:1,2 of 6502 RAM : the window address is page aligned and the window ends below $FF00. The copy takes place while the 65C02 waits for the call ; the window must not hold the code executing the call, its stack or its zero page. The window is a copy : changing it does not change the bank (see Function 1,22). Bank $FF forgets the mapping and leaves the window as it is. Sets the error flag if the bank or the address is invalid. A bank never written reads as $FF. The banks are also blitter pages $A0 (bank 0) to $BF, read only, for Function 12,2 (bank to video RAM for instance).

C (llvm-mos)
/* Recopier la banque 31 dans une fenêtre de 8 Ko (groupe 1, fonction 18). */
#include <stdio.h>
#include <neo6502.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 fenetre[8192] __attribute__((aligned(256)));   /* alignée sur une page */

int main(void) {
    libre();
    ControlPort.params[0] = 31;                                /* banque */
    ControlPort.params[1] = (uint16_t)fenetre & 0xFF;
    ControlPort.params[2] = (uint16_t)fenetre >> 8;
    api(1, 18);                                                /* Select Bank */
    if (ControlPort.error)
        printf("Banque ou fenetre invalide\n");
    else
        printf("Banque 31 : premier octet $%02X\n", fenetre[0]);   /* $FF si jamais écrite */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 31 : premier octet $FF
Mad Pascal
// Recopier la banque 31 dans une fenêtre de 8 Ko en $4000 (groupe 1, fonction 18).
program banque;
uses crt, neo6502;
var fenetre: array[0..8191] of byte absolute $4000;   // alignée sur une page
begin
  NeoWaitMessage;
  NeoMessage.params[0] := 31;                   // banque
  NeoMessage.params[1] := lo(word(@fenetre));   // P1-P2 : fenêtre
  NeoMessage.params[2] := hi(word(@fenetre));
  NeoSendMessage(1, 18);                        // Select Bank
  NeoWaitMessage;
  if NeoMessage.error <> 0 then
    writeln('Banque ou fenetre invalide')
  else
    writeln('Banque 31 : premier octet $', HexStr(fenetre[0], 2));   // $FF si jamais écrite
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 31 : premier octet $FF
NeoBASIC
10 ' Recopier la banque 31 dans une fenetre de 8 Ko (groupe 1, fonction 18)
20 m = alloc(8448) : w = (m + 255) & $ff00 : ' fenetre alignee sur une page
30 while peek($ff00) : wend
40 poke $ff04, 31 : poke $ff05, w & 255 : poke $ff06, w >> 8 : poke $ff01, 18 : poke $ff00, 1
50 while peek($ff00) : wend : e = peek($ff02)
60 if e
70   print "Banque ou fenetre invalide"
80 else
90   print "Banque 31 : premier octet ";peek(w)
100 endif
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 31 : premier octet 255
65C02 assembly (64tass)
; Recopier la banque 31 dans une fenêtre de 8 Ko en $4000 (groupe 1, fonction 18).
        .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

FENETRE = $4000                    ; alignée sur une page, loin du programme

start:  jsr WaitMessage
        lda #31
        sta ControlPort+4          ; P0 : banque 31
        lda #<FENETRE
        sta ControlPort+5          ; P1-P2 : fenêtre
        lda #>FENETRE
        sta ControlPort+6
        lda #1
        ldx #18                    ; fonction 18 : Select Bank
        jsr appel
        lda ControlPort+2          ; erreur ?
        bne _err
        lda #<message
        ldx #>message
        jsr ecrire
        lda FENETRE                ; premier octet ($FF = 255 si jamais écrite)
        jsr decimal
        lda #13
        jmp WriteCharacter
_err:   lda #<erreur
        ldx #>erreur
        jmp ecrire

; 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
diviseur: .byte 0

message: .text "Banque 31 : premier octet ", 0
erreur:  .text "Banque ou fenetre invalide", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 31 : premier octet 255

1,19 Get Bank Info Trinity

Gives the last selected bank, its window address, the number of banks and their size.

Inputs: none.

Outputs
BytesTypeMeaning
P0uint8last selected bank ($FF if none since reset)
P1-P2addrits window address
P3uint8number of banks
P4-P5uint16bank size in bytes
Firmware documentation text

Returns the last bank selected in Parameter:0 ($FF if none since reset) and its window address in Parameters:1,2, the number of banks in Parameter:3 and the bank size in Parameters:4,5 (Trinity T-17).

C (llvm-mos)
/* Connaître le nombre et la taille des banques (groupe 1, fonction 19). */
#include <stdio.h>
#include <neo6502.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)
        ;
}

int main(void) {
    libre();
    api(1, 19);                                 /* Get Bank Info */
    uint8_t nombre = ControlPort.params[3];
    uint16_t taille = ControlPort.params[4] | (ControlPort.params[5] << 8);
    printf("%u banques de %u octets\n", nombre, taille);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
32 banques de 8192 octets
Mad Pascal
// Connaître le nombre et la taille des banques (groupe 1, fonction 19).
program bankinfo;
uses crt, neo6502;
var nombre: byte;
    taille: word;
begin
  NeoSendMessage(1, 19);                        // Get Bank Info
  NeoWaitMessage;
  nombre := NeoMessage.params[3];
  taille := wordParams[2];                      // P4-P5
  writeln(nombre, ' banques de ', taille, ' octets');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
32 banques de 8192 octets
NeoBASIC
10 ' Connaitre le nombre et la taille des banques (groupe 1, fonction 19)
20 ' lire les resultats dans des variables juste apres l'appel : print reutilise la boite aux lettres
30 while peek($ff00) : wend
40 poke $ff01, 19 : poke $ff00, 1
50 while peek($ff00) : wend : n = peek($ff07) : t = peek($ff08) + 256 * peek($ff09)
60 print n;" banques de ";t;" octets"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
32 banques de 8192 octets
65C02 assembly (64tass)
; Connaître le nombre de banques (groupe 1, fonction 19).
        .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:  jsr WaitMessage
        lda #1
        ldx #19                    ; fonction 19 : Get Bank Info
        jsr appel
        lda ControlPort+7          ; P3 : nombre de banques
        jsr decimal
        lda #<message
        ldx #>message
        jmp ecrire

; 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
diviseur: .byte 0

message: .text " banques de 8192 octets", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
32 banques de 8192 octets

1,20 Get Date Time Trinity

Gives the local date and time (time zone of function 1,24), and where they come from.

Inputs: none.

Outputs
BytesTypeMeaning
P0-P1uint16year (1970 to 2099)
P2uint8month (1 to 12)
P3uint8day (1 to 31)
P4uint8hour (0 to 23)
P5uint8minute
P6uint8second
P7uint8source: 0 not set (1970-01-01 + time since power on), 1 software clock (1,21), 2 PCF8563 clock on UEXT, 3 SNTP time of the USB modem (1,23)
Firmware documentation text

Returns the date and time (Trinity T-18, F-14 of the fork), local time of the zone of Function 1,24 : Parameters:0,1 year (1970-2099), Parameter:2 month (1-12), Parameter:3 day (1-31), Parameter:4 hour (0-23), Parameter:5 minute, Parameter:6 second and Parameter:7 the source : 0 = not set (the value is then 1970-01-01 plus the time since power on), 1 = software clock set by Function 1,21 and advanced by the 100 Hz timer, 2 = PCF8563 real time clock found on the UEXT I2C bus (address $51), 3 = set from the SNTP time of the USB modem (Function 1,23 : the program asks for it, nothing is automatic, T-25).

C (llvm-mos)
/* Afficher la date et l'heure (groupe 1, fonction 20). */
#include <stdio.h>
#include <neo6502.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)
        ;
}

int main(void) {
    libre();
    api(1, 20);                                 /* Get Date Time */
    volatile uint8_t *p = ControlPort.params;
    printf("%04u-%02u-%02u %02u:%02u:%02u (source %u)\n",
           p[0] | (p[1] << 8), p[2], p[3], p[4], p[5], p[6], p[7]);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
1970-01-01 00:00:06 (source 0)
Mad Pascal
// Afficher la date et l'heure (groupe 1, fonction 20).
program dateheure;
uses crt, neo6502;
var p: array[0..7] of byte;
    i: byte;

procedure deux(n: byte);                        // deux chiffres, avec zéro de tête
begin
  if n < 10 then write('0');
  write(n);
end;

begin
  NeoSendMessage(1, 20);                        // Get Date Time
  NeoWaitMessage;
  for i := 0 to 7 do p[i] := NeoMessage.params[i];   // recopier : l'affichage réutilise la boîte aux lettres
  write(p[0] + p[1] * 256, '-');
  deux(p[2]); write('-'); deux(p[3]); write(' ');
  deux(p[4]); write(':'); deux(p[5]); write(':'); deux(p[6]);
  writeln(' (source ', p[7], ')');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
1970-01-01 00:00:06 (source 0)
NeoBASIC
10 ' Afficher la date et l'heure (groupe 1, fonction 20)
20 ' lire les resultats dans des variables juste apres l'appel, avant tout calcul : print et * reutilisent la boite aux lettres
30 while peek($ff00) : wend
40 poke $ff01, 20 : poke $ff00, 1
50 while peek($ff00) : wend : a0 = peek($ff04) : a1 = peek($ff05) : mo = peek($ff06) : j = peek($ff07) : h = peek($ff08) : mi = peek($ff09) : s = peek($ff0a) : so = peek($ff0b) : a = a0 + 256 * a1
60 print a;"-";right$("0"+str$(mo),2);"-";right$("0"+str$(j),2);" ";right$("0"+str$(h),2);":";right$("0"+str$(mi),2);":";right$("0"+str$(s),2);" (source ";so;")"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
1970-01-01 00:00:09 (source 0)
65C02 assembly (64tass)
; Afficher la date et l'heure (groupe 1, fonction 20) : jj/mm hh:mm:ss.
        .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:  jsr WaitMessage
        lda #1
        ldx #20                    ; fonction 20 : Get Date Time
        jsr appel
        ldx #7                     ; recopier P0-P7 : l'affichage réutilise la boîte aux lettres
_copie: lda ControlPort+4,x
        sta date,x
        dex
        bpl _copie
        lda date+3                 ; jour
        jsr deux
        lda #'/'
        jsr WriteCharacter
        lda date+2                 ; mois
        jsr deux
        lda #' '
        jsr WriteCharacter
        lda date+4                 ; heure
        jsr deux
        lda #':'
        jsr WriteCharacter
        lda date+5                 ; minute
        jsr deux
        lda #':'
        jsr WriteCharacter
        lda date+6                 ; seconde
        jsr deux
        lda #13
        jmp WriteCharacter

deux:   cmp #10                    ; deux chiffres, avec zéro de tête
        bcs _d
        pha
        lda #'0'
        jsr WriteCharacter
        pla
_d:     jmp decimal

; 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
diviseur: .byte 0

date:    .fill 8
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
01/01 00:00:06

1,21 Set Date Time Trinity

Sets the local date and time; the PCF8563 clock is set too when present.

Inputs
BytesTypeMeaning
P0-P6datesame layout as 1,20: year (2 bytes), month, day, hour, minute, second
Outputs
BytesTypeMeaning
erreuruint8non zero if a field is out of range (1970-2099, valid day of month, 24 hour time)
Firmware documentation text

Sets the date and time (Trinity T-18, F-14 of the fork), same layout as Function 1,20 (Parameter:7 ignored) : the software clock is set, and the PCF8563 too when present. Sets the error flag if a field is out of range (1970-2099, valid day of month, 24 hour time).

C (llvm-mos)
/* Régler la date et l'heure au 27 septembre 2026, 14:30:00 (groupe 1, fonction 21). */
#include <stdio.h>
#include <neo6502.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)
        ;
}

int main(void) {
    static const uint8_t date[7] = { 2026 & 0xFF, 2026 >> 8, 9, 27, 14, 30, 0 };
    libre();
    for (uint8_t i = 0; i < 7; i++)
        ControlPort.params[i] = date[i];
    api(1, 21);                                 /* Set Date Time */
    if (ControlPort.error)
        printf("Date refusee\n");
    else
        printf("Horloge reglee : 2026-09-27 14:30\n");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Horloge reglee : 2026-09-27 14:30
Mad Pascal
// Régler la date et l'heure au 27 septembre 2026, 14:30:00 (groupe 1, fonction 21).
program setdate;
uses crt, neo6502;
begin
  NeoWaitMessage;
  wordParams[0] := 2026;                        // P0-P1 : année
  NeoMessage.params[2] := 9;                    // mois
  NeoMessage.params[3] := 27;                   // jour
  NeoMessage.params[4] := 14;                   // heure
  NeoMessage.params[5] := 30;                   // minute
  NeoMessage.params[6] := 0;                    // seconde
  NeoSendMessage(1, 21);                        // Set Date Time
  NeoWaitMessage;
  if NeoMessage.error <> 0 then
    writeln('Date refusee')
  else
    writeln('Horloge reglee : 2026-09-27 14:30');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Horloge reglee : 2026-09-27 14:30
NeoBASIC
10 ' Regler la date et l'heure au 27 septembre 2026, 14:30:00 (groupe 1, fonction 21)
20 while peek($ff00) : wend
30 poke $ff04, 2026 & 255 : poke $ff05, 2026 >> 8 : poke $ff06, 9 : poke $ff07, 27 : poke $ff08, 14 : poke $ff09, 30 : poke $ff0a, 0 : poke $ff01, 21 : poke $ff00, 1
40 while peek($ff00) : wend : e = peek($ff02)
50 if e
60   print "Date refusee"
70 else
80   print "Horloge reglee : 2026-09-27 14:30"
90 endif
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Horloge reglee : 2026-09-27 14:30
65C02 assembly (64tass)
; Régler la date et l'heure au 27 septembre 2026, 14:30:00 (groupe 1, fonction 21).
        .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:  jsr WaitMessage
        ldx #6
_copie: lda date,x                 ; P0-P6 : année, mois, jour, heure, minute, seconde
        sta ControlPort+4,x
        dex
        bpl _copie
        lda #1
        ldx #21                    ; fonction 21 : Set Date Time
        jsr appel
        lda ControlPort+2          ; erreur ?
        bne _err
        lda #<message
        ldx #>message
        jmp ecrire
_err:   lda #<refus
        ldx #>refus
        jmp ecrire

; 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
diviseur: .byte 0

date:    .word 2026
         .byte 9, 27, 14, 30, 0
message: .text "Horloge reglee : 2026-09-27 14:30", 13, 0
refus:   .text "Date refusee", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Horloge reglee : 2026-09-27 14:30

1,22 Write Bank Trinity

Writes 8 KB of 65C02 RAM into a flash bank; the bank survives resets and firmware updates.

Inputs
BytesTypeMeaning
P0uint8bank number (0 to 31)
P1-P2addraddress of the 8 KB to write (same constraints as 1,18)
Outputs
BytesTypeMeaning
erreuruint8non zero if the bank or the address is invalid, or the verification fails

On the board, the flash sectors are erased then written, the display pauses for about 150 ms, and the content is verified. The emulators keep the banks in the file banks.flash of the storage. Flash wears: write a bank once, not in a loop (1,26 helps rewrite only what changed).

Firmware documentation text

Programs bank Parameter:0 with the 8k at Parameters:1,2 of 6502 RAM (same constraints as Function 1,18) : on the board the flash sectors are erased and written, the display pauses for about 150 ms, and the content is verified ; a written bank survives resets and reflashing of the firmware (it lives in the top 256k of the flash). The emulators keep the banks in the file "banks.flash" of the storage directory. Sets the error flag if the bank or the address is invalid or the verification fails (Trinity T-17). Flash wears : write banks once, not in loops.

C (llvm-mos)
/* Écrire un texte dans la banque 31, puis le relire (groupe 1, fonctions 22 et 18). */
#include <stdio.h>
#include <string.h>
#include <neo6502.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 tampon[8192] __attribute__((aligned(256)));

static void banque(uint8_t fonction, uint8_t numero) {
    libre();
    ControlPort.params[0] = numero;
    ControlPort.params[1] = (uint16_t)tampon & 0xFF;
    ControlPort.params[2] = (uint16_t)tampon >> 8;
    api(1, fonction);
}

int main(void) {
    memset(tampon, 0, sizeof tampon);
    strcpy((char *)tampon, "Bonjour");
    banque(22, 31);                             /* Write Bank : tampon -> banque 31 */
    if (ControlPort.error) {
        printf("Ecriture refusee\n");
        return 0;
    }
    memset(tampon, 0, sizeof tampon);           /* effacer la copie en RAM */
    banque(18, 31);                             /* Select Bank : banque 31 -> tampon */
    printf("Banque 31 relue : %s\n", (char *)tampon);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 31 relue : Bonjour
Mad Pascal
// Écrire un texte dans la banque 31, puis le relire (groupe 1, fonctions 22 et 18).
program writebank;
uses crt, neo6502;
var tampon: array[0..8191] of byte absolute $4000;   // 8 Ko alignés sur une page
    texte: string[16];
    i: byte;

procedure banque(fonction: byte);
begin
  NeoWaitMessage;
  NeoMessage.params[0] := 31;
  NeoMessage.params[1] := lo(word(@tampon));
  NeoMessage.params[2] := hi(word(@tampon));
  NeoSendMessage(1, fonction);
  NeoWaitMessage;
end;

begin
  texte := 'Bonjour';
  for i := 1 to length(texte) do tampon[i - 1] := ord(texte[i]);
  tampon[length(texte)] := 0;
  banque(22);                                   // Write Bank : tampon -> banque 31
  if NeoMessage.error <> 0 then
  begin
    writeln('Ecriture refusee');
    exit;
  end;
  tampon[0] := 0;                               // effacer la copie en RAM
  banque(18);                                   // Select Bank : banque 31 -> tampon
  write('Banque 31 relue : ');
  i := 0;
  while tampon[i] <> 0 do
  begin
    write(chr(tampon[i]));
    inc(i);
  end;
  writeln;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 31 relue : Bonjour
NeoBASIC
10 ' Ecrire un texte dans la banque 31, puis le relire (groupe 1, fonctions 22 et 18)
20 m = alloc(8448) : w = (m + 255) & $ff00
30 t$ = "Bonjour" : for i = 1 to len(t$) : poke w + i - 1, asc(mid$(t$, i, 1)) : next i : poke w + len(t$), 0
40 while peek($ff00) : wend
50 poke $ff04, 31 : poke $ff05, w & 255 : poke $ff06, w >> 8 : poke $ff01, 22 : poke $ff00, 1
60 while peek($ff00) : wend : e = peek($ff02)
70 if e then print "Ecriture refusee" : end
80 for i = 0 to 7 : poke w + i, 0 : next i
90 while peek($ff00) : wend
100 poke $ff04, 31 : poke $ff05, w & 255 : poke $ff06, w >> 8 : poke $ff01, 18 : poke $ff00, 1
110 while peek($ff00) : wend
120 r$ = "" : i = 0
130 while peek(w + i) : r$ = r$ + chr$(peek(w + i)) : i = i + 1 : wend
140 print "Banque 31 relue : ";r$
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 31 relue : Bonjour
65C02 assembly (64tass)
; Écrire un texte dans la banque 31, puis le relire (groupe 1, fonctions 22 et 18).
        .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

TAMPON = $4000                     ; 8 Ko alignés sur une page

start:  ldx #0                     ; copier le texte dans le tampon
_copie: lda texte,x
        sta TAMPON,x
        beq _ecrit
        inx
        bne _copie
_ecrit: lda #22                    ; fonction 22 : Write Bank
        jsr banque
        lda ControlPort+2
        bne _err
        stz TAMPON                 ; effacer la copie en RAM
        lda #18                    ; fonction 18 : Select Bank
        jsr banque
        lda #<message
        ldx #>message
        jsr ecrire
        lda #<TAMPON               ; le texte relu depuis la banque
        ldx #>TAMPON
        jsr ecrire
        lda #13
        jmp WriteCharacter
_err:   lda #<refus
        ldx #>refus
        jmp ecrire

; A = fonction (18 ou 22), sur la banque 31 et le tampon.
banque: pha
        jsr WaitMessage
        lda #31
        sta ControlPort+4
        lda #<TAMPON
        sta ControlPort+5
        lda #>TAMPON
        sta ControlPort+6
        plx
        lda #1
        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
diviseur: .byte 0

texte:   .text "Bonjour", 0
message: .text "Banque 31 relue : ", 0
refus:   .text "Ecriture refusee", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 31 relue : Bonjour

1,23 Sync Clock From Modem Trinity

Sets the date and time from the USB Wi-Fi modem (Pico W, Neo6502 Wi-Fi modem), which gets them through SNTP.

Inputs: none.

Outputs
BytesTypeMeaning
erreuruint80 success, 1 no modem connected, 2 modem without SNTP time (answer 1970) or silent

Sends AT+CIPSNTPTIME? on the USB link and applies the answer, taking out the modem offset: the clock holds UTC, and 1,20 shows the local time of the zone of 1,24. Waits 2 seconds at most; pending input on the modem link is discarded. Nothing is automatic: the program must ask for it.

Firmware documentation text

Sets the date and time from the USB modem (Pico W, Neo6502picowifi) : sends AT+CIPSNTPTIME? on the CDC link and applies the answer ; the tz offset of the modem (AT+CIPSNTPCFG?, whole hours) is taken out so the clock holds UTC and Function 1,20 shows the local time of the zone selected by Function 1,24. Waits 2 seconds at most. Error 1 if no modem is connected, 2 if the modem has no SNTP time yet (answer 1970) or does not answer. Pending input on the modem link is discarded (Trinity T-25).

C (llvm-mos)
/* Mettre l'horloge à l'heure du modem Wi-Fi USB (groupe 1, fonction 23). */
#include <stdio.h>
#include <neo6502.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)
        ;
}

int main(void) {
    libre();
    api(1, 23);                                 /* Sync Clock From Modem */
    switch (ControlPort.error) {
    case 0:  printf("Horloge synchronisee\n"); break;
    case 1:  printf("Pas de modem USB\n"); break;
    default: printf("Modem sans heure SNTP\n"); break;
    }
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Pas de modem USB
Mad Pascal
// Mettre l'horloge à l'heure du modem Wi-Fi USB (groupe 1, fonction 23).
program syncmodem;
uses crt, neo6502;
begin
  NeoSendMessage(1, 23);                        // Sync Clock From Modem
  NeoWaitMessage;
  case NeoMessage.error of
    0: writeln('Horloge synchronisee');
    1: writeln('Pas de modem USB');
  else
    writeln('Modem sans heure SNTP');
  end;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Pas de modem USB
NeoBASIC
10 ' Mettre l'horloge a l'heure du modem Wi-Fi USB (groupe 1, fonction 23)
20 while peek($ff00) : wend
30 poke $ff01, 23 : poke $ff00, 1
40 while peek($ff00) : wend : e = peek($ff02)
50 if e = 0 then print "Horloge synchronisee"
60 if e = 1 then print "Pas de modem USB"
70 if e > 1 then print "Modem sans heure SNTP"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Pas de modem USB
65C02 assembly (64tass)
; Mettre l'horloge à l'heure du modem Wi-Fi USB (groupe 1, fonction 23).
        .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:  jsr WaitMessage
        lda #1
        ldx #23                    ; fonction 23 : Sync Clock From Modem
        jsr appel
        lda ControlPort+2          ; 0 réussi, 1 pas de modem, 2 pas d'heure
        bne _pas0
        lda #<ok
        ldx #>ok
        jmp ecrire
_pas0:  cmp #1
        bne _pas1
        lda #<sans
        ldx #>sans
        jmp ecrire
_pas1:  lda #<muet
        ldx #>muet
        jmp ecrire

; 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
diviseur: .byte 0

ok:   .text "Horloge synchronisee", 13, 0
sans: .text "Pas de modem USB", 13, 0
muet: .text "Modem sans heure SNTP", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Pas de modem USB

1,24 Set Time Zone Trinity

Selects the time zone, by name (Europe/Paris, America/Montreal…) or as a fixed offset (UTC+2, UTC-3:30, +0530).

Inputs
BytesTypeMeaning
P0-P1addraddress of the name: length-prefixed string
Outputs
BytesTypeMeaning
erreuruint80 success, 1 unknown name, 2 flash write failed

The clock keeps UTC; 1,20, 1,21 and FAT file dates use the local time of the zone, daylight saving included (rules of the European Union, North America, Australia and New Zealand). The zone is saved in flash and applied again at every start up.

Firmware documentation text

Selects the time zone (Trinity T-26) from the length-prefixed name at Parameters:0,1 : an IANA style name of the built in table ("Europe/Paris", "Europe/Belgrade", "America/Montreal", "Asia/Tokyo"...) or a fixed offset ("UTC+2", "UTC-3:30", "+0530"). The clock keeps UTC ; Functions 1,20 and 1,21 and the FAT timestamps use the local time of the zone, daylight saving included (rules of the European Union, North America, Australia and New Zealand). The zone is saved in the settings sector of the flash and applied at every start up. Error 1 if the name is unknown, 2 if the flash write failed.

C (llvm-mos)
/* Choisir le fuseau horaire de Paris (groupe 1, fonction 24). */
#include <stdio.h>
#include <neo6502.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 const char fuseau[] = "\x0C" "Europe/Paris";   /* longueur (12), puis le nom */

int main(void) {
    libre();
    ControlPort.params[0] = (uint16_t)fuseau & 0xFF;
    ControlPort.params[1] = (uint16_t)fuseau >> 8;
    api(1, 24);                                 /* Set Time Zone */
    if (ControlPort.error == 1)
        printf("Fuseau inconnu\n");
    else if (ControlPort.error)
        printf("Ecriture en flash echouee\n");
    else
        printf("Fuseau : Europe/Paris\n");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Fuseau : Europe/Paris
Mad Pascal
// Choisir le fuseau horaire de Paris (groupe 1, fonction 24).
program setzone;
uses crt, neo6502;
var fuseau: string[32];                         // chaîne Pascal : longueur puis texte
begin
  fuseau := 'Europe/Paris';
  NeoWaitMessage;
  wordParams[0] := word(@fuseau);               // P0-P1 : adresse du nom
  NeoSendMessage(1, 24);                        // Set Time Zone
  NeoWaitMessage;
  case NeoMessage.error of
    0: writeln('Fuseau : ', fuseau);
    1: writeln('Fuseau inconnu');
  else
    writeln('Ecriture en flash echouee');
  end;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Fuseau : Europe/Paris
NeoBASIC
10 ' Choisir le fuseau horaire de Paris (groupe 1, fonction 24)
20 z$ = "Europe/Paris" : a = alloc(16) : poke a, len(z$)
30 for i = 1 to len(z$) : poke a + i, asc(mid$(z$, i, 1)) : next i
40 while peek($ff00) : wend
50 poke $ff04, a & 255 : poke $ff05, a >> 8 : poke $ff01, 24 : poke $ff00, 1
60 while peek($ff00) : wend : e = peek($ff02)
70 if e = 0 then print "Fuseau : ";z$
80 if e = 1 then print "Fuseau inconnu"
90 if e = 2 then print "Ecriture en flash echouee"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Fuseau : Europe/Paris
65C02 assembly (64tass)
; Choisir le fuseau horaire de Paris (groupe 1, fonction 24).
        .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:  jsr WaitMessage
        lda #<fuseau
        sta ControlPort+4          ; P0-P1 : adresse du nom
        lda #>fuseau
        sta ControlPort+5
        lda #1
        ldx #24                    ; fonction 24 : Set Time Zone
        jsr appel
        lda ControlPort+2
        bne _err
        lda #<ok
        ldx #>ok
        jmp ecrire
_err:   lda #<refus
        ldx #>refus
        jmp ecrire

; 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
diviseur: .byte 0

fuseau: .byte 12                   ; longueur
        .text "Europe/Paris"
ok:     .text "Fuseau : Europe/Paris", 13, 0
refus:  .text "Fuseau refuse", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Fuseau : Europe/Paris

1,25 Get Time Zone Trinity

Gives the name of the current time zone, its offset from UTC and whether daylight saving time applies.

Inputs
BytesTypeMeaning
P0-P1addraddress of a buffer: its first byte gives, before the call, the maximum length accepted; it then receives the name length, followed by the name
Outputs
BytesTypeMeaning
tamponstringzone name ("UTC" by default)
P2-P3int16offset from UTC in minutes (signed, daylight saving included)
P4uint8non zero if daylight saving time applies
Firmware documentation text

Writes the current time zone name into the length-prefixed buffer at Parameters:0,1 ("UTC" by default), the current offset from UTC in minutes (signed, daylight saving included) in Parameters:2,3 and whether daylight saving time is in force in Parameter:4 (Trinity T-26).

C (llvm-mos)
/* Afficher le fuseau horaire en cours (groupe 1, fonction 25). */
#include <stdio.h>
#include <neo6502.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 char nom[64];                            /* longueur, puis le nom */

int main(void) {
    nom[0] = sizeof nom - 1;                    /* capacité du tampon, lue par le firmware */
    libre();
    ControlPort.params[0] = (uint16_t)nom & 0xFF;
    ControlPort.params[1] = (uint16_t)nom >> 8;
    api(1, 25);                                 /* Get Time Zone */
    int16_t decalage = ControlPort.params[2] | (ControlPort.params[3] << 8);
    uint8_t ete = ControlPort.params[4];
    printf("Fuseau : %.*s, decalage %d min%s\n", nom[0], nom + 1, decalage, ete ? " (heure d'ete)" : "");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Fuseau : UTC, decalage 0 min
Mad Pascal
// Afficher le fuseau horaire en cours (groupe 1, fonction 25).
program getzone;
uses crt, neo6502;
var nom: string[63];                            // longueur, puis le nom
    decalage: smallint;
    ete: byte;
begin
  nom[0] := chr(63);                            // capacité du tampon, lue par le firmware
  NeoWaitMessage;
  wordParams[0] := word(@nom);                  // P0-P1 : tampon qui reçoit le nom
  NeoSendMessage(1, 25);                        // Get Time Zone
  NeoWaitMessage;
  decalage := smallint(wordParams[1]);          // P2-P3 : minutes, signé
  ete := NeoMessage.params[4];
  write('Fuseau : ', nom, ', decalage ', decalage, ' min');
  if ete <> 0 then write(' (heure d''ete)');
  writeln;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Fuseau : UTC, decalage 0 min
NeoBASIC
10 ' Afficher le fuseau horaire en cours (groupe 1, fonction 25)
20 ' lire les resultats dans des variables juste apres l'appel : print reutilise la boite aux lettres
30 a = alloc(64) : poke a, 63 : ' premier octet : capacite du tampon
40 while peek($ff00) : wend
50 poke $ff04, a & 255 : poke $ff05, a >> 8 : poke $ff01, 25 : poke $ff00, 1
60 while peek($ff00) : wend : d = peek($ff06) + 256 * peek($ff07) : e = peek($ff08)
70 if d > 32767 then d = d - 65536
80 n$ = "" : for i = 1 to peek(a) : n$ = n$ + chr$(peek(a + i)) : next i
90 print "Fuseau : ";n$;", decalage ";d;" min"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Fuseau : UTC, decalage 0 min
65C02 assembly (64tass)
; Afficher le nom du fuseau horaire en cours (groupe 1, fonction 25).
        .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 #63
        sta nom                    ; premier octet : capacité du tampon, lue par le firmware
        jsr WaitMessage
        lda #<nom
        sta ControlPort+4          ; P0-P1 : tampon qui reçoit le nom
        lda #>nom
        sta ControlPort+5
        lda #1
        ldx #25                    ; fonction 25 : Get Time Zone
        jsr appel
        lda ControlPort+6          ; P2 : décalage en minutes (poids faible)
        sta decalage
        lda #<message
        ldx #>message
        jsr ecrire
        ldx #0                     ; le nom : longueur puis caractères
_nom:   cpx nom
        beq _fin
        lda nom+1,x
        jsr WriteCharacter
        inx
        bne _nom
_fin:   lda #<suite
        ldx #>suite
        jsr ecrire
        lda decalage               ; 0 à 255 : suffit pour les fuseaux positifs de l'exemple
        jsr decimal
        lda #<unite
        ldx #>unite
        jmp ecrire

; 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
diviseur: .byte 0

nom:      .fill 64
decalage: .byte 0
message:  .text "Fuseau : ", 0
suite:    .text ", decalage ", 0
unite:    .text " min", 13, 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Fuseau : UTC, decalage 0 min

1,26 Bank Checksum Trinity

Computes the Fletcher-16 checksum of a bank directly in flash, without copying it to RAM.

Inputs
BytesTypeMeaning
P0uint8bank number (0 to 31)
Outputs
BytesTypeMeaning
P0-P1uint16s1 = sum of the 8192 bytes modulo 65536
P2-P3uint16s2 = sum of the successive values of s1 modulo 65536
erreuruint8non zero if the bank is invalid (the parameters are then zero)

A bank never written reads $FF: s1 = $E000. Tells which banks of a cache changed before rewriting them with 1,22, without copying everything.

Firmware documentation text

Returns the Fletcher 16 checksum of bank Parameter:0 (0-31) as stored in flash, without copying it to 6502 RAM (Trinity T-81) : s1 = sum of the 8192 bytes modulo 65536 in Parameters:0,1, s2 = sum of the successive values of s1 modulo 65536 in Parameters:2,3. A bank never written reads as $FF (s1 = $E000). Sets the error flag if the bank is invalid (the parameters are then zero). The 65C02 waits for the call ; it avoids copying each bank with Function 1,18 and summing it in 6502 code to find which banks of a cache need writing with Function 1,22.

C (llvm-mos)
/* Somme de contrôle de la banque 30 (groupe 1, fonction 26). */
#include <stdio.h>
#include <neo6502.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)
        ;
}

int main(void) {
    libre();
    ControlPort.params[0] = 30;                 /* banque */
    api(1, 26);                                 /* Bank Checksum */
    uint16_t s1 = ControlPort.params[0] | (ControlPort.params[1] << 8);
    uint16_t s2 = ControlPort.params[2] | (ControlPort.params[3] << 8);
    printf("Banque 30 : s1 = $%04X, s2 = $%04X%s\n", s1, s2, s1 == 0xE000 ? " (jamais ecrite)" : "");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 30 : s1 = $E000, s2 = $F000 (jamais ecrite)
Mad Pascal
// Somme de contrôle de la banque 30 (groupe 1, fonction 26).
program checksum;
uses crt, neo6502;
var s1, s2: word;
begin
  NeoWaitMessage;
  NeoMessage.params[0] := 30;                   // banque
  NeoSendMessage(1, 26);                        // Bank Checksum
  NeoWaitMessage;
  s1 := wordParams[0];
  s2 := wordParams[1];
  write('Banque 30 : s1 = $', HexStr(s1, 4), ', s2 = $', HexStr(s2, 4));
  if s1 = $E000 then write(' (jamais ecrite)');
  writeln;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 30 : s1 = $E000, s2 = $F000 (jamais ecrite)
NeoBASIC
10 ' Somme de controle de la banque 30 (groupe 1, fonction 26)
20 ' lire les resultats dans des variables juste apres l'appel, avant tout calcul : print et * reutilisent la boite aux lettres
30 while peek($ff00) : wend
40 poke $ff04, 30 : poke $ff01, 26 : poke $ff00, 1
50 while peek($ff00) : wend : b0 = peek($ff04) : b1 = peek($ff05) : b2 = peek($ff06) : b3 = peek($ff07) : s1 = b0 + 256 * b1 : s2 = b2 + 256 * b3
60 print "Banque 30 : s1 = ";s1;", s2 = ";s2
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 30 : s1 = 57344, s2 = 61440
65C02 assembly (64tass)
; Somme de contrôle de la banque 30 (groupe 1, fonction 26).
        .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:  jsr WaitMessage
        lda #30
        sta ControlPort+4          ; P0 : banque
        lda #1
        ldx #26                    ; fonction 26 : Bank Checksum
        jsr appel
        lda ControlPort+5          ; s1, poids fort puis poids faible
        sta s1+1
        lda ControlPort+4
        sta s1
        lda #<message
        ldx #>message
        jsr ecrire
        lda s1+1
        jsr hexa
        lda s1
        jsr hexa
        lda #13
        jmp WriteCharacter

hexa:   pha                        ; A en deux chiffres hexadécimaux
        lsr a
        lsr a
        lsr a
        lsr a
        jsr _h
        pla
        and #$0F
_h:     cmp #10
        bcc _c
        adc #6
_c:     adc #'0'
        jmp WriteCharacter

; 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
diviseur: .byte 0

s1:      .word 0
message: .text "Banque 30 : s1 = $", 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Banque 30 : s1 = $E000

1,27 Apply Boot Choice Trinity

Applies the choice of the boot menu (or of boot/auto.txt): loads the chosen program and returns 1, or returns 0 when there is nothing to start.

Inputs: none.

Outputs
BytesTypeMeaning
P0uint81: program loaded, jump through address 0 ready; 0: NeoDOS chosen, nothing to start, or failure (a message says so)

The kernel calls it once at reset, then 1,3 (NeoDOS) if it returned 0. The choice is forgotten once applied: called by a program, it therefore returns 0 and does nothing, as the example shows. A .bin is loaded at $0800 like NeoBASIC; a .neo must have an execution address.

Firmware documentation text

Applies the choice made in the boot menu or by boot/auto.txt (Trinity T-36, ADR-0001) : loads the chosen program and sets the jump through address zero, then returns 1 in Parameter:0 ; returns 0 when NeoDOS was chosen or the program could not be started (a message says so). The choice is forgotten once applied. The kernel calls it at reset, then Function 1,3 if it returned 0.

C (llvm-mos)
/* Demander le choix du menu de démarrage, déjà appliqué (groupe 1, fonction 27, Trinity). */
#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)
        ;
}

int main(void) {
    libre();
    api(1, 27);                                 /* Apply Boot Choice */
    printf("Choix de demarrage : %u\n", ControlPort.params[0]);   /* 0 : rien à lancer */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Choix de demarrage : 0
Mad Pascal
// Demander le choix du menu de démarrage, déjà appliqué (groupe 1, fonction 27, Trinity).
program bootchoice;
uses crt, neo6502;
var c: byte;
begin
  NeoSendMessage(1, 27);                        // Apply Boot Choice
  NeoWaitMessage;
  c := NeoMessage.params[0];                    // lu avant writeln, qui réutilise la boîte aux lettres
  writeln('Choix de demarrage : ', c);          // 0 : rien à lancer
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Choix de demarrage : 0
NeoBASIC
10 ' Demander le choix du menu de demarrage, deja applique (groupe 1, fonction 27, Trinity)
20 while peek($ff00) : wend : poke $ff01, 27 : poke $ff00, 1 : ' Apply Boot Choice
30 while peek($ff00) : wend : c = peek($ff04)
40 print "Choix de demarrage : ";c : ' 0 : rien a lancer
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Choix de demarrage : 0
65C02 assembly (64tass)
; Demander le choix du menu de démarrage, déjà appliqué (groupe 1, fonction 27, Trinity).
        .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:  jsr WaitMessage
        lda #1
        ldx #27                    ; 1,27 : Apply Boot Choice
        jsr appel
        lda ControlPort+4          ; 0 : rien à lancer
        sta choix
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda choix
        jsr decimal
        lda #13
        jmp WriteCharacter

t0:     .text "Choix de demarrage : ", 0
choix:  .byte 0

; 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
diviseur: .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Choix de demarrage : 0

← The Neo6502 API