Tachibana
Langue : français

Groupe 14 — Série USB (CDC) Trinity

14,1 Serial Status · 14,2 Serial Read Byte · 14,3 Serial Write Byte · 14,4 Serial Read Block · 14,5 Serial Write Block · 14,6 Serial Set Line Coding

Propre à Trinity : un appareil série USB (classe CDC-ACM) branché sur un port USB-A de la carte, en général un modem, comme le Modem Wi-Fi Neo6502, ou un adaptateur USB-série. P7 choisit l'appareil (0 le premier, 1 le second) ; un numéro plus grand revient à l'appareil 0, sans erreur. Les fonctions série du groupe 10 (10,13 à 10,18) passent aussi par cet appareil quand 10,19 le choisit, ce qui est le cas par défaut dès qu'un modem est branché.

Au banc, Phosphoneo branche un modem Hayes simulé (tools/fake_modem.py) : les exemples lui envoient AT et attendent « OK », comme le ferait un vrai modem. Ils n'affichent que ce qui ne dépend pas du modem (branché ou non, erreurs, nombre d'octets écrits, présence de « OK »).

14,1 Serial Status Trinity

Dit si l'appareil série USB est branché et combien d'octets reçus attendent d'être lus.

Entrées
OctetsTypeRôle
P7uint8appareil : 0 ou 1 (au-delà : 0)
Sorties
OctetsTypeRôle
P0uint81 si l'appareil est branché
P1-P2uint16octets reçus en attente (0 si l'appareil est absent)

L'appel ne signale jamais d'erreur. L'exemple montre que l'appareil 5 (hors plage) est vu comme l'appareil 0, puis qu'après l'envoi de AT des octets attendent. Sans modem, les deux appareils sont absents (vérifié au banc). NeoBASIC n'a pas de mot-clé pour ce groupe.

Texte de la documentation du firmware (anglais)

USB CDC-ACM serial device (modem, USB-serial adapter) on the Neo6502 host port. Parameter:7 selects the device (0 = first). Returns Parameter:0 = 1 if the device is connected, Parameters:1,2 = number of bytes waiting to be read.

C (llvm-mos)
/* Savoir si un modem USB est branché et combien d'octets il a envoyés (groupe 14, fonction 1). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

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

static uint16_t en_attente(void) {              /* 14,1 : octets reçus en attente (appareil 0) ; 0xFFFF si absent */
    libre();
    ControlPort.params[7] = 0;                  /* P7 : appareil 0, le premier */
    api(14, 1);
    if (!ControlPort.params[0])
        return 0xFFFF;
    return ControlPort.params[1] | ControlPort.params[2] << 8;
}

static uint8_t envoyer(const char *s, uint8_t n) {   /* 14,5 : écrit n octets ; rend le nombre écrit */
    libre();
    ControlPort.params[0] = (uint16_t)s & 0xFF; /* P0-P1 : adresse */
    ControlPort.params[1] = (uint16_t)s >> 8;
    ControlPort.params[2] = n;                  /* P2-P3 : nombre d'octets */
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 5);
    return ControlPort.params[2];
}

static char recu[128];
static uint8_t nrecu;

static void lire(void) {                        /* 14,4 : ajoute à recu ce qui est arrivé */
    libre();
    ControlPort.params[0] = (uint16_t)(recu + nrecu) & 0xFF;
    ControlPort.params[1] = (uint16_t)(recu + nrecu) >> 8;
    ControlPort.params[2] = sizeof recu - 1 - nrecu;
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 4);
    nrecu += ControlPort.params[2];
    recu[nrecu] = 0;
}

static uint8_t attendre_ok(void) {              /* lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs) */
    uint32_t fin = neo_system_timer() + 300;
    while (neo_system_timer() < fin) {
        lire();
        for (uint8_t i = 0; i + 1 < nrecu; i++)
            if (recu[i] == 'O' && recu[i + 1] == 'K')
                return 1;
    }
    return 0;
}

static uint8_t branche(uint8_t appareil) {      /* Serial Status : P0 = 1 si branché */
    libre();
    ControlPort.params[7] = appareil;           /* P7 : numéro de l'appareil */
    api(14, 1);
    return ControlPort.params[0];
}

int main(void) {
    uint8_t b0 = branche(0), b5 = branche(5);   /* 5 : hors plage (2 appareils, 0 et 1) */
    uint16_t avant = en_attente(), apres = 0;
    envoyer("AT\r", 3);
    uint32_t fin = neo_system_timer() + 300;
    while (apres == 0 && neo_system_timer() < fin)
        apres = en_attente();                   /* Serial Status : P1-P2, octets reçus pas encore lus */
    attendre_ok();                              /* puis on lit la réponse */
    printf("Appareil 0 : %s, appareil 5 : %s\n", b0 ? "branche" : "absent", b5 ? "branche" : "absent");
    printf("En attente avant AT : %u, apres : %s\n", avant, apres > 0 && apres != 0xFFFF ? "des octets" : "rien");
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Appareil 0 : branche, appareil 5 : branche
En attente avant AT : 0, apres : des octets
Mad Pascal
// Savoir si un modem USB est branché et combien d'octets il a envoyés (groupe 14, fonction 1).
program cdc1;
uses crt, neo6502;
var recu: array[0..127] of byte;                // réponse du modem
    nrecu: byte;

function en_attente: word;                      // 14,1 : octets reçus en attente (appareil 0) ; $FFFF si absent
begin
  NeoWaitMessage;
  NeoMessage.params[7] := 0;                    // P7 : appareil 0, le premier
  NeoSendMessage(14, 1);
  NeoWaitMessage;
  if NeoMessage.params[0] = 0 then result := $FFFF
  else result := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

function envoyer(adresse: word; n: byte): byte; // 14,5 : écrit n octets ; rend le nombre écrit
begin
  NeoWaitMessage;
  wordParams[0] := adresse;                     // P0-P1 : adresse
  wordParams[1] := n;                           // P2-P3 : nombre d'octets
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 5);
  NeoWaitMessage;
  result := NeoMessage.params[2];
end;

procedure lire;                                 // 14,4 : ajoute à recu ce qui est arrivé
begin
  NeoWaitMessage;
  wordParams[0] := word(@recu) + nrecu;
  wordParams[1] := 127 - nrecu;
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 4);
  NeoWaitMessage;
  nrecu := nrecu + NeoMessage.params[2];
end;

function attendre_ok: boolean;                  // lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs)
var fin: cardinal;
    i: byte;
begin
  result := false;
  fin := NeoGetTimer + 300;
  while (not result) and (NeoGetTimer < fin) do begin
    lire;
    if nrecu > 1 then
      for i := 0 to nrecu - 2 do
        if (recu[i] = ord('O')) and (recu[i + 1] = ord('K')) then result := true;
  end;
end;
const at: array[0..2] of byte = (ord('A'), ord('T'), 13);
var b0, b5: byte;
    avant, apres: word;
    fin: cardinal;

function branche(appareil: byte): byte;         // Serial Status : P0 = 1 si branché
begin
  NeoWaitMessage;
  NeoMessage.params[7] := appareil;             // P7 : numéro de l'appareil
  NeoSendMessage(14, 1);
  NeoWaitMessage;
  result := NeoMessage.params[0];
end;

begin
  b0 := branche(0);
  b5 := branche(5);                             // 5 : hors plage (2 appareils, 0 et 1)
  avant := en_attente;
  apres := 0;
  envoyer(word(@at), 3);
  fin := NeoGetTimer + 300;
  while (apres = 0) and (NeoGetTimer < fin) do
    apres := en_attente;                        // Serial Status : P1-P2, octets reçus pas encore lus
  attendre_ok;                                  // puis on lit la réponse
  write('Appareil 0 : ');
  if b0 <> 0 then write('branche') else write('absent');
  write(', appareil 5 : ');
  if b5 <> 0 then writeln('branche') else writeln('absent');
  write('En attente avant AT : ', avant, ', apres : ');
  if (apres > 0) and (apres <> $FFFF) then writeln('des octets') else writeln('rien');
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Appareil 0 : branche, appareil 5 : branche
En attente avant AT : 0, apres : des octets
NeoBASIC
10 ' Savoir si un modem USB est branche et combien d'octets il a envoyes (groupe 14, fonction 1)
20 r = alloc(128) : nr = 0 : ' reponse du modem
30 a = 0 : gosub 800 : b0 = c : a = 5 : gosub 800 : b5 = c : ' 5 : hors plage (2 appareils, 0 et 1)
40 gosub 900 : avant = w
50 s$ = "AT" : gosub 2000
60 apres = 0 : f = time() + 300
70 repeat : gosub 900 : apres = w : until apres <> 0 | time() > f : ' Serial Status : P1-P2, octets recus pas encore lus
80 gosub 2200 : ' puis on lit la reponse
90 a$ = "absent" : if b0 then a$ = "branche"
100 b$ = "absent" : if b5 then b$ = "branche"
110 print "Appareil 0 : ";a$;", appareil 5 : ";b$
120 e$ = "rien" : if apres > 0 & apres <> 65535 then e$ = "des octets"
130 print "En attente avant AT : ";avant;", apres : ";e$
140 end
800 ' Serial Status : c = 1 si l'appareil a est branche
810 while peek($ff00) : wend : poke $ff0b, a : poke $ff01, 1 : poke $ff00, 14
820 while peek($ff00) : wend : c = peek($ff04)
830 return
900 ' 14,1 : octets en attente sur l'appareil 0 dans w (65535 si absent)
910 while peek($ff00) : wend : poke $ff0b, 0 : poke $ff01, 1 : poke $ff00, 14
920 while peek($ff00) : wend : w = 65535 : if peek($ff04) then w = deek($ff05)
930 return
2000 ' 14,5 : envoie s$ (plus un retour chariot) ; nombre d'octets ecrits dans n
2010 b = alloc(40) : for i = 1 to len(s$) : poke b + i - 1, asc(mid$(s$, i, 1)) : next i : poke b + len(s$), 13
2020 while peek($ff00) : wend
2030 doke $ff04, b : doke $ff06, len(s$) + 1 : poke $ff0b, 0 : poke $ff01, 5 : poke $ff00, 14
2040 while peek($ff00) : wend : n = peek($ff06)
2050 return
2100 ' 14,4 : ajoute a r (nr octets deja lus) ce qui est arrive
2110 while peek($ff00) : wend
2120 doke $ff04, r + nr : doke $ff06, 127 - nr : poke $ff0b, 0 : poke $ff01, 4 : poke $ff00, 14
2130 while peek($ff00) : wend : nr = nr + peek($ff06)
2140 return
2200 ' lit jusqu'a OK ou 3 secondes (time() : horloge en cs) ; ok = 1 si OK recu
2210 ok = 0 : f = time() + 300
2220 repeat : gosub 2100
2230 for i = 0 to nr - 2 : if peek(r + i) = 79 & peek(r + i + 1) = 75 then ok = 1
2240 next i
2250 until ok | time() > f
2260 return
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Appareil 0 : branche, appareil 5 : branche
En attente avant AT : 0, apres : des octets
Assembleur 65C02 (64tass)
; Savoir si un modem USB est branché et combien d'octets il a envoyés (groupe 14, fonction 1).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:  lda #0
        jsr branche
        sta b0
        lda #5                     ; 5 : hors plage (2 appareils, 0 et 1)
        jsr branche
        sta b5
        jsr attente
        lda w
        sta avant
        lda #<cmd_at
        ldx #>cmd_at
        ldy #3
        jsr envoyer
        stz apres
        jsr depart
_att:   jsr attente                ; Serial Status : P1-P2, octets reçus pas encore lus
        bcs _v
        lda w
        ora w+1
        bne _des
_v:     jsr ecoule
        bcc _att
        bra _suite
_des:   lda #1
        sta apres
_suite: jsr attendre_ok             ; puis on lit la réponse
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda b0
        bne _ob0
        lda #<ta
        ldx #>ta
        jsr ecrire
        bra _eb0
_ob0:   lda #<tb
        ldx #>tb
        jsr ecrire
_eb0:
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda b5
        bne _ob5
        lda #<ta
        ldx #>ta
        jsr ecrire
        bra _eb5
_ob5:   lda #<tb
        ldx #>tb
        jsr ecrire
_eb5:
        lda #13
        jsr WriteCharacter
        lda #<t2
        ldx #>t2
        jsr ecrire
        lda avant
        jsr decimal
        lda #<t3
        ldx #>t3
        jsr ecrire
        lda apres
        bne _oapres
        lda #<tr
        ldx #>tr
        jsr ecrire
        bra _eapres
_oapres:   lda #<td
        ldx #>td
        jsr ecrire
_eapres:
        lda #13
        jsr WriteCharacter
        rts

; Serial Status de l'appareil A : A = 1 si branché.
branche:
        pha
        jsr WaitMessage
        pla
        sta ControlPort+11         ; P7 : numéro de l'appareil
        lda #14
        ldx #1                     ; 14,1 : Serial Status
        jsr appel
        lda ControlPort+4
        rts

; 14,1 : octets en attente sur l'appareil 0 dans w (16 bits) ; C = 1 si l'appareil est absent.
attente:
        jsr WaitMessage
        stz ControlPort+11         ; P7 : appareil 0, le premier
        lda #14
        ldx #1                     ; 14,1 : Serial Status
        jsr appel
        lda ControlPort+5          ; P1-P2 : octets reçus pas encore lus
        sta w
        lda ControlPort+6
        sta w+1
        lda ControlPort+4          ; P0 : 1 si branché
        cmp #1                     ; C = 0 si absent...
        eor #1
        lsr a                      ; ... d'où C = 1 si absent
        rts
w:      .word 0

; 14,5 : envoie n octets depuis l'adresse A (poids faible), X (poids fort), n dans Y ; nombre écrit dans A.
envoyer:
        pha
        jsr WaitMessage
        pla
        sta ControlPort+4          ; P0-P1 : adresse
        stx ControlPort+5
        sty ControlPort+6          ; P2-P3 : nombre d'octets
        stz ControlPort+7
        stz ControlPort+11         ; P7 : appareil 0
        lda #14
        ldx #5                     ; 14,5 : Serial Write Block
        jsr appel
        lda ControlPort+6
        rts

; 14,4 : ajoute à recu ce qui est arrivé (nrecu octets déjà lus).
lire:   jsr WaitMessage
        clc
        lda #<recu
        adc nrecu
        sta ControlPort+4          ; P0-P1 : recu + nrecu
        lda #>recu
        adc #0
        sta ControlPort+5
        sec
        lda #127
        sbc nrecu
        sta ControlPort+6          ; P2-P3 : place restante
        stz ControlPort+7
        stz ControlPort+11
        lda #14
        ldx #4                     ; 14,4 : Serial Read Block
        jsr appel
        clc
        lda nrecu
        adc ControlPort+6          ; P2-P3 : octets lus
        sta nrecu
        rts

; Horloge (1,1) en centièmes, 16 bits bas dans A (poids faible) et X (poids fort).
horloge:
        jsr WaitMessage
        lda #1
        ldx #1                     ; 1,1 : Timer
        jsr appel
        lda ControlPort+4
        ldx ControlPort+5
        rts

; Note le départ d'une attente de 3 secondes.
depart: jsr horloge
        sta debut
        stx debut+1
        rts
; C = 1 si 3 secondes (300 cs) sont passées depuis depart.
ecoule: jsr horloge
        sec
        sbc debut
        tay
        txa
        sbc debut+1
        cmp #>300                  ; (maintenant - départ) >= 300 ?
        bne _f
        cpy #<300
_f:     rts
debut:  .word 0

; Lit jusqu'à « OK » ou 3 secondes ; A = 1 si OK reçu.
attendre_ok:
        jsr depart
_b:     jsr lire
        ldx #0
_c:     inx
        cpx nrecu
        bcs _pas
        lda recu-1,x
        cmp #'O'
        bne _c
        lda recu,x
        cmp #'K'
        bne _c
        lda #1
        rts
_pas:   jsr ecoule
        bcc _b
        lda #0
        rts

recu:   .fill 128                  ; réponse du modem
nrecu:  .byte 0

cmd_at: .text "AT", 13
t0:     .text "Appareil 0 : ", 0
t1:     .text ", appareil 5 : ", 0
t2:     .text "En attente avant AT : ", 0
t3:     .text ", apres : ", 0
ta:     .text "absent", 0
tb:     .text "branche", 0
td:     .text "des octets", 0
tr:     .text "rien", 0
b0:     .byte 0
b5:     .byte 0
avant:  .byte 0
apres:  .byte 0

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

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

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

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

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Appareil 0 : branche, appareil 5 : branche
En attente avant AT : 0, apres : des octets

14,2 Serial Read Byte Trinity

Lit un octet reçu de l'appareil série USB, sans attendre.

Entrées
OctetsTypeRôle
P7uint8appareil : 0 ou 1
Sorties
OctetsTypeRôle
P0uint8octet lu
erreuruint81 si rien n'est arrivé ou si l'appareil est absent

Avant tout envoi, rien n'est arrivé : erreur 1. L'exemple envoie AT, puis lit octet par octet jusqu'à « OK ».

Texte de la documentation du firmware (anglais)

Read one byte from the CDC device (Parameter:7) into Parameter:0. Sets the error flag if no byte is available or no device is connected.

C (llvm-mos)
/* Lire la réponse du modem octet par octet (groupe 14, fonction 2). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

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

static uint16_t en_attente(void) {              /* 14,1 : octets reçus en attente (appareil 0) ; 0xFFFF si absent */
    libre();
    ControlPort.params[7] = 0;                  /* P7 : appareil 0, le premier */
    api(14, 1);
    if (!ControlPort.params[0])
        return 0xFFFF;
    return ControlPort.params[1] | ControlPort.params[2] << 8;
}

static uint8_t envoyer(const char *s, uint8_t n) {   /* 14,5 : écrit n octets ; rend le nombre écrit */
    libre();
    ControlPort.params[0] = (uint16_t)s & 0xFF; /* P0-P1 : adresse */
    ControlPort.params[1] = (uint16_t)s >> 8;
    ControlPort.params[2] = n;                  /* P2-P3 : nombre d'octets */
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 5);
    return ControlPort.params[2];
}

static char recu[128];
static uint8_t nrecu;

static void lire(void) {                        /* 14,4 : ajoute à recu ce qui est arrivé */
    libre();
    ControlPort.params[0] = (uint16_t)(recu + nrecu) & 0xFF;
    ControlPort.params[1] = (uint16_t)(recu + nrecu) >> 8;
    ControlPort.params[2] = sizeof recu - 1 - nrecu;
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 4);
    nrecu += ControlPort.params[2];
    recu[nrecu] = 0;
}

static uint8_t attendre_ok(void) {              /* lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs) */
    uint32_t fin = neo_system_timer() + 300;
    while (neo_system_timer() < fin) {
        lire();
        for (uint8_t i = 0; i + 1 < nrecu; i++)
            if (recu[i] == 'O' && recu[i + 1] == 'K')
                return 1;
    }
    return 0;
}

static uint8_t octet;

static uint8_t lire_octet(void) {               /* Serial Read Byte : rend l'indicateur d'erreur */
    libre();
    ControlPort.params[7] = 0;
    api(14, 2);
    octet = ControlPort.params[0];
    return ControlPort.error;
}

int main(void) {
    uint8_t e = lire_octet();                   /* rien n'est encore arrivé */
    envoyer("AT\r", 3);
    uint32_t fin = neo_system_timer() + 300;
    uint8_t prec = 0, ok = 0;
    while (!ok && neo_system_timer() < fin)
        if (lire_octet() == 0) {
            ok = prec == 'O' && octet == 'K';
            prec = octet;
        }
    printf("Avant l'envoi : erreur %u\n", e);
    printf("Reponse a AT : %s\n", ok ? "OK" : "rien");
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Avant l'envoi : erreur 1
Reponse a AT : OK
Mad Pascal
// Lire la réponse du modem octet par octet (groupe 14, fonction 2).
program cdc2;
uses crt, neo6502;
var recu: array[0..127] of byte;                // réponse du modem
    nrecu: byte;

function en_attente: word;                      // 14,1 : octets reçus en attente (appareil 0) ; $FFFF si absent
begin
  NeoWaitMessage;
  NeoMessage.params[7] := 0;                    // P7 : appareil 0, le premier
  NeoSendMessage(14, 1);
  NeoWaitMessage;
  if NeoMessage.params[0] = 0 then result := $FFFF
  else result := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

function envoyer(adresse: word; n: byte): byte; // 14,5 : écrit n octets ; rend le nombre écrit
begin
  NeoWaitMessage;
  wordParams[0] := adresse;                     // P0-P1 : adresse
  wordParams[1] := n;                           // P2-P3 : nombre d'octets
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 5);
  NeoWaitMessage;
  result := NeoMessage.params[2];
end;

procedure lire;                                 // 14,4 : ajoute à recu ce qui est arrivé
begin
  NeoWaitMessage;
  wordParams[0] := word(@recu) + nrecu;
  wordParams[1] := 127 - nrecu;
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 4);
  NeoWaitMessage;
  nrecu := nrecu + NeoMessage.params[2];
end;

function attendre_ok: boolean;                  // lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs)
var fin: cardinal;
    i: byte;
begin
  result := false;
  fin := NeoGetTimer + 300;
  while (not result) and (NeoGetTimer < fin) do begin
    lire;
    if nrecu > 1 then
      for i := 0 to nrecu - 2 do
        if (recu[i] = ord('O')) and (recu[i + 1] = ord('K')) then result := true;
  end;
end;
const at: array[0..2] of byte = (ord('A'), ord('T'), 13);
var octet, e, prec: byte;
    ok: boolean;
    fin: cardinal;

function lire_octet: byte;                      // Serial Read Byte : rend l'indicateur d'erreur
begin
  NeoWaitMessage;
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 2);
  NeoWaitMessage;
  octet := NeoMessage.params[0];
  result := NeoMessage.error;
end;

begin
  e := lire_octet;                              // rien n'est encore arrivé
  envoyer(word(@at), 3);
  fin := NeoGetTimer + 300;
  prec := 0;
  ok := false;
  while (not ok) and (NeoGetTimer < fin) do
    if lire_octet = 0 then begin
      ok := (prec = ord('O')) and (octet = ord('K'));
      prec := octet;
    end;
  writeln('Avant l''envoi : erreur ', e);
  if ok then writeln('Reponse a AT : OK') else writeln('Reponse a AT : rien');
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Avant l'envoi : erreur 1
Reponse a AT : OK
NeoBASIC
10 ' Lire la reponse du modem octet par octet (groupe 14, fonction 2)
20 r = alloc(128) : nr = 0 : ' reponse du modem
30 gosub 800 : e1 = e : ' rien n'est encore arrive
40 s$ = "AT" : gosub 2000
50 f = time() + 300 : p = 0 : ok = 0
60 repeat : gosub 800
70 if e = 0 then if p = 79 & o = 75 then ok = 1
80 if e = 0 then p = o
90 until ok | time() > f
100 print "Avant l'envoi : erreur ";e1
110 r$ = "rien" : if ok then r$ = "OK"
120 print "Reponse a AT : ";r$
130 end
800 ' Serial Read Byte : octet dans o, erreur dans e
810 while peek($ff00) : wend : poke $ff0b, 0 : poke $ff01, 2 : poke $ff00, 14
820 while peek($ff00) : wend : e = peek($ff02) : o = peek($ff04)
830 return
900 ' 14,1 : octets en attente sur l'appareil 0 dans w (65535 si absent)
910 while peek($ff00) : wend : poke $ff0b, 0 : poke $ff01, 1 : poke $ff00, 14
920 while peek($ff00) : wend : w = 65535 : if peek($ff04) then w = deek($ff05)
930 return
2000 ' 14,5 : envoie s$ (plus un retour chariot) ; nombre d'octets ecrits dans n
2010 b = alloc(40) : for i = 1 to len(s$) : poke b + i - 1, asc(mid$(s$, i, 1)) : next i : poke b + len(s$), 13
2020 while peek($ff00) : wend
2030 doke $ff04, b : doke $ff06, len(s$) + 1 : poke $ff0b, 0 : poke $ff01, 5 : poke $ff00, 14
2040 while peek($ff00) : wend : n = peek($ff06)
2050 return
2100 ' 14,4 : ajoute a r (nr octets deja lus) ce qui est arrive
2110 while peek($ff00) : wend
2120 doke $ff04, r + nr : doke $ff06, 127 - nr : poke $ff0b, 0 : poke $ff01, 4 : poke $ff00, 14
2130 while peek($ff00) : wend : nr = nr + peek($ff06)
2140 return
2200 ' lit jusqu'a OK ou 3 secondes (time() : horloge en cs) ; ok = 1 si OK recu
2210 ok = 0 : f = time() + 300
2220 repeat : gosub 2100
2230 for i = 0 to nr - 2 : if peek(r + i) = 79 & peek(r + i + 1) = 75 then ok = 1
2240 next i
2250 until ok | time() > f
2260 return
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Avant l'envoi : erreur 1
Reponse a AT : OK
Assembleur 65C02 (64tass)
; Lire la réponse du modem octet par octet (groupe 14, fonction 2).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:  jsr lire_octet             ; rien n'est encore arrivé : C = 1
        lda #0
        rol a
        sta e
        lda #<cmd_at
        ldx #>cmd_at
        ldy #3
        jsr envoyer
        stz prec
        stz ok
        jsr depart
_b:     jsr lire_octet
        bcs _t                     ; rien pour l'instant
        ldx prec                   ; octet précédent
        sta prec
        cpx #'O'
        bne _t
        cmp #'K'
        bne _t
        lda #1                     ; « OK » reçu
        sta ok
        bra _fin
_t:     jsr ecoule
        bcc _b
_fin:   lda #<t0
        ldx #>t0
        jsr ecrire
        lda e
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda ok
        bne _ook
        lda #<tr
        ldx #>tr
        jsr ecrire
        bra _eok
_ook:   lda #<tok
        ldx #>tok
        jsr ecrire
_eok:
        lda #13
        jsr WriteCharacter
        rts

; Serial Read Byte : C = 1 si rien n'est arrivé (erreur), sinon l'octet dans A.
lire_octet:
        jsr WaitMessage
        stz ControlPort+11         ; P7 : appareil 0
        lda #14
        ldx #2                     ; 14,2 : Serial Read Byte
        jsr appel
        lda ControlPort+2          ; erreur ?
        cmp #1                     ; C = 1 si erreur
        lda ControlPort+4          ; l'octet
        rts

; 14,1 : octets en attente sur l'appareil 0 dans w (16 bits) ; C = 1 si l'appareil est absent.
attente:
        jsr WaitMessage
        stz ControlPort+11         ; P7 : appareil 0, le premier
        lda #14
        ldx #1                     ; 14,1 : Serial Status
        jsr appel
        lda ControlPort+5          ; P1-P2 : octets reçus pas encore lus
        sta w
        lda ControlPort+6
        sta w+1
        lda ControlPort+4          ; P0 : 1 si branché
        cmp #1                     ; C = 0 si absent...
        eor #1
        lsr a                      ; ... d'où C = 1 si absent
        rts
w:      .word 0

; 14,5 : envoie n octets depuis l'adresse A (poids faible), X (poids fort), n dans Y ; nombre écrit dans A.
envoyer:
        pha
        jsr WaitMessage
        pla
        sta ControlPort+4          ; P0-P1 : adresse
        stx ControlPort+5
        sty ControlPort+6          ; P2-P3 : nombre d'octets
        stz ControlPort+7
        stz ControlPort+11         ; P7 : appareil 0
        lda #14
        ldx #5                     ; 14,5 : Serial Write Block
        jsr appel
        lda ControlPort+6
        rts

; 14,4 : ajoute à recu ce qui est arrivé (nrecu octets déjà lus).
lire:   jsr WaitMessage
        clc
        lda #<recu
        adc nrecu
        sta ControlPort+4          ; P0-P1 : recu + nrecu
        lda #>recu
        adc #0
        sta ControlPort+5
        sec
        lda #127
        sbc nrecu
        sta ControlPort+6          ; P2-P3 : place restante
        stz ControlPort+7
        stz ControlPort+11
        lda #14
        ldx #4                     ; 14,4 : Serial Read Block
        jsr appel
        clc
        lda nrecu
        adc ControlPort+6          ; P2-P3 : octets lus
        sta nrecu
        rts

; Horloge (1,1) en centièmes, 16 bits bas dans A (poids faible) et X (poids fort).
horloge:
        jsr WaitMessage
        lda #1
        ldx #1                     ; 1,1 : Timer
        jsr appel
        lda ControlPort+4
        ldx ControlPort+5
        rts

; Note le départ d'une attente de 3 secondes.
depart: jsr horloge
        sta debut
        stx debut+1
        rts
; C = 1 si 3 secondes (300 cs) sont passées depuis depart.
ecoule: jsr horloge
        sec
        sbc debut
        tay
        txa
        sbc debut+1
        cmp #>300                  ; (maintenant - départ) >= 300 ?
        bne _f
        cpy #<300
_f:     rts
debut:  .word 0

; Lit jusqu'à « OK » ou 3 secondes ; A = 1 si OK reçu.
attendre_ok:
        jsr depart
_b:     jsr lire
        ldx #0
_c:     inx
        cpx nrecu
        bcs _pas
        lda recu-1,x
        cmp #'O'
        bne _c
        lda recu,x
        cmp #'K'
        bne _c
        lda #1
        rts
_pas:   jsr ecoule
        bcc _b
        lda #0
        rts

recu:   .fill 128                  ; réponse du modem
nrecu:  .byte 0

cmd_at: .text "AT", 13
t0:     .text "Avant l'envoi : erreur ", 0
t1:     .text "Reponse a AT : ", 0
tok:    .text "OK", 0
tr:     .text "rien", 0
e:      .byte 0
ok:     .byte 0
prec:   .byte 0

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

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

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

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

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Avant l'envoi : erreur 1
Reponse a AT : OK

14,3 Serial Write Byte Trinity

Envoie un octet à l'appareil série USB.

Entrées
OctetsTypeRôle
P0uint8octet à envoyer
P7uint8appareil : 0 ou 1
Sorties
OctetsTypeRôle
erreuruint81 si l'appareil est absent ou n'a pas pris l'octet

Pour plus d'un octet, 14,5 fait tout en un appel. L'exemple envoie A, T et un retour chariot, un par un, puis attend « OK ».

Texte de la documentation du firmware (anglais)

Write the byte in Parameter:0 to the CDC device (Parameter:7). Sets the error flag if no device is connected.

C (llvm-mos)
/* Envoyer une commande au modem octet par octet (groupe 14, fonction 3). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

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

static uint16_t en_attente(void) {              /* 14,1 : octets reçus en attente (appareil 0) ; 0xFFFF si absent */
    libre();
    ControlPort.params[7] = 0;                  /* P7 : appareil 0, le premier */
    api(14, 1);
    if (!ControlPort.params[0])
        return 0xFFFF;
    return ControlPort.params[1] | ControlPort.params[2] << 8;
}

static uint8_t envoyer(const char *s, uint8_t n) {   /* 14,5 : écrit n octets ; rend le nombre écrit */
    libre();
    ControlPort.params[0] = (uint16_t)s & 0xFF; /* P0-P1 : adresse */
    ControlPort.params[1] = (uint16_t)s >> 8;
    ControlPort.params[2] = n;                  /* P2-P3 : nombre d'octets */
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 5);
    return ControlPort.params[2];
}

static char recu[128];
static uint8_t nrecu;

static void lire(void) {                        /* 14,4 : ajoute à recu ce qui est arrivé */
    libre();
    ControlPort.params[0] = (uint16_t)(recu + nrecu) & 0xFF;
    ControlPort.params[1] = (uint16_t)(recu + nrecu) >> 8;
    ControlPort.params[2] = sizeof recu - 1 - nrecu;
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 4);
    nrecu += ControlPort.params[2];
    recu[nrecu] = 0;
}

static uint8_t attendre_ok(void) {              /* lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs) */
    uint32_t fin = neo_system_timer() + 300;
    while (neo_system_timer() < fin) {
        lire();
        for (uint8_t i = 0; i + 1 < nrecu; i++)
            if (recu[i] == 'O' && recu[i + 1] == 'K')
                return 1;
    }
    return 0;
}

static uint8_t ecrire(uint8_t c) {              /* Serial Write Byte : rend l'indicateur d'erreur */
    libre();
    ControlPort.params[0] = c;
    ControlPort.params[7] = 0;
    api(14, 3);
    return ControlPort.error;
}

int main(void) {
    uint8_t e = ecrire('A') | ecrire('T') | ecrire('\r');
    uint8_t ok = attendre_ok();
    printf("Envoi de AT : erreur %u\n", e);
    printf("Reponse : %s\n", ok ? "OK" : "rien");
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Envoi de AT : erreur 0
Reponse : OK
Mad Pascal
// Envoyer une commande au modem octet par octet (groupe 14, fonction 3).
program cdc3;
uses crt, neo6502;
var recu: array[0..127] of byte;                // réponse du modem
    nrecu: byte;

function en_attente: word;                      // 14,1 : octets reçus en attente (appareil 0) ; $FFFF si absent
begin
  NeoWaitMessage;
  NeoMessage.params[7] := 0;                    // P7 : appareil 0, le premier
  NeoSendMessage(14, 1);
  NeoWaitMessage;
  if NeoMessage.params[0] = 0 then result := $FFFF
  else result := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

function envoyer(adresse: word; n: byte): byte; // 14,5 : écrit n octets ; rend le nombre écrit
begin
  NeoWaitMessage;
  wordParams[0] := adresse;                     // P0-P1 : adresse
  wordParams[1] := n;                           // P2-P3 : nombre d'octets
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 5);
  NeoWaitMessage;
  result := NeoMessage.params[2];
end;

procedure lire;                                 // 14,4 : ajoute à recu ce qui est arrivé
begin
  NeoWaitMessage;
  wordParams[0] := word(@recu) + nrecu;
  wordParams[1] := 127 - nrecu;
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 4);
  NeoWaitMessage;
  nrecu := nrecu + NeoMessage.params[2];
end;

function attendre_ok: boolean;                  // lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs)
var fin: cardinal;
    i: byte;
begin
  result := false;
  fin := NeoGetTimer + 300;
  while (not result) and (NeoGetTimer < fin) do begin
    lire;
    if nrecu > 1 then
      for i := 0 to nrecu - 2 do
        if (recu[i] = ord('O')) and (recu[i + 1] = ord('K')) then result := true;
  end;
end;
var e: byte;
    ok: boolean;

function ecrire(c: byte): byte;                 // Serial Write Byte : rend l'indicateur d'erreur
begin
  NeoWaitMessage;
  NeoMessage.params[0] := c;
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 3);
  NeoWaitMessage;
  result := NeoMessage.error;
end;

begin
  e := ecrire(ord('A')) or ecrire(ord('T')) or ecrire(13);
  ok := attendre_ok;
  writeln('Envoi de AT : erreur ', e);
  if ok then writeln('Reponse : OK') else writeln('Reponse : rien');
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Envoi de AT : erreur 0
Reponse : OK
NeoBASIC
10 ' Envoyer une commande au modem octet par octet (groupe 14, fonction 3)
20 r = alloc(128) : nr = 0 : ' reponse du modem
30 c = asc("A") : gosub 800 : e1 = e : c = asc("T") : gosub 800 : e1 = e1 | e : c = 13 : gosub 800 : e1 = e1 | e
40 gosub 2200
50 print "Envoi de AT : erreur ";e1
60 r$ = "rien" : if ok then r$ = "OK"
70 print "Reponse : ";r$
80 end
800 ' Serial Write Byte : octet c, erreur dans e
810 while peek($ff00) : wend : poke $ff04, c : poke $ff0b, 0 : poke $ff01, 3 : poke $ff00, 14
820 while peek($ff00) : wend : e = peek($ff02)
830 return
900 ' 14,1 : octets en attente sur l'appareil 0 dans w (65535 si absent)
910 while peek($ff00) : wend : poke $ff0b, 0 : poke $ff01, 1 : poke $ff00, 14
920 while peek($ff00) : wend : w = 65535 : if peek($ff04) then w = deek($ff05)
930 return
2000 ' 14,5 : envoie s$ (plus un retour chariot) ; nombre d'octets ecrits dans n
2010 b = alloc(40) : for i = 1 to len(s$) : poke b + i - 1, asc(mid$(s$, i, 1)) : next i : poke b + len(s$), 13
2020 while peek($ff00) : wend
2030 doke $ff04, b : doke $ff06, len(s$) + 1 : poke $ff0b, 0 : poke $ff01, 5 : poke $ff00, 14
2040 while peek($ff00) : wend : n = peek($ff06)
2050 return
2100 ' 14,4 : ajoute a r (nr octets deja lus) ce qui est arrive
2110 while peek($ff00) : wend
2120 doke $ff04, r + nr : doke $ff06, 127 - nr : poke $ff0b, 0 : poke $ff01, 4 : poke $ff00, 14
2130 while peek($ff00) : wend : nr = nr + peek($ff06)
2140 return
2200 ' lit jusqu'a OK ou 3 secondes (time() : horloge en cs) ; ok = 1 si OK recu
2210 ok = 0 : f = time() + 300
2220 repeat : gosub 2100
2230 for i = 0 to nr - 2 : if peek(r + i) = 79 & peek(r + i + 1) = 75 then ok = 1
2240 next i
2250 until ok | time() > f
2260 return
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Envoi de AT : erreur 0
Reponse : OK
Assembleur 65C02 (64tass)
; Envoyer une commande au modem octet par octet (groupe 14, 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 #'A'
        jsr envoi1
        sta e
        lda #'T'
        jsr envoi1
        ora e
        sta e
        lda #13
        jsr envoi1
        ora e
        sta e
        jsr attendre_ok
        sta ok
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda e
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda ok
        bne _ook
        lda #<tr
        ldx #>tr
        jsr ecrire
        bra _eok
_ook:   lda #<tok
        ldx #>tok
        jsr ecrire
_eok:
        lda #13
        jsr WriteCharacter
        rts

; Serial Write Byte : envoie A ; erreur rendue dans A.
envoi1: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        stz ControlPort+11         ; P7 : appareil 0
        lda #14
        ldx #3                     ; 14,3 : Serial Write Byte
        jsr appel
        lda ControlPort+2
        rts

; 14,1 : octets en attente sur l'appareil 0 dans w (16 bits) ; C = 1 si l'appareil est absent.
attente:
        jsr WaitMessage
        stz ControlPort+11         ; P7 : appareil 0, le premier
        lda #14
        ldx #1                     ; 14,1 : Serial Status
        jsr appel
        lda ControlPort+5          ; P1-P2 : octets reçus pas encore lus
        sta w
        lda ControlPort+6
        sta w+1
        lda ControlPort+4          ; P0 : 1 si branché
        cmp #1                     ; C = 0 si absent...
        eor #1
        lsr a                      ; ... d'où C = 1 si absent
        rts
w:      .word 0

; 14,5 : envoie n octets depuis l'adresse A (poids faible), X (poids fort), n dans Y ; nombre écrit dans A.
envoyer:
        pha
        jsr WaitMessage
        pla
        sta ControlPort+4          ; P0-P1 : adresse
        stx ControlPort+5
        sty ControlPort+6          ; P2-P3 : nombre d'octets
        stz ControlPort+7
        stz ControlPort+11         ; P7 : appareil 0
        lda #14
        ldx #5                     ; 14,5 : Serial Write Block
        jsr appel
        lda ControlPort+6
        rts

; 14,4 : ajoute à recu ce qui est arrivé (nrecu octets déjà lus).
lire:   jsr WaitMessage
        clc
        lda #<recu
        adc nrecu
        sta ControlPort+4          ; P0-P1 : recu + nrecu
        lda #>recu
        adc #0
        sta ControlPort+5
        sec
        lda #127
        sbc nrecu
        sta ControlPort+6          ; P2-P3 : place restante
        stz ControlPort+7
        stz ControlPort+11
        lda #14
        ldx #4                     ; 14,4 : Serial Read Block
        jsr appel
        clc
        lda nrecu
        adc ControlPort+6          ; P2-P3 : octets lus
        sta nrecu
        rts

; Horloge (1,1) en centièmes, 16 bits bas dans A (poids faible) et X (poids fort).
horloge:
        jsr WaitMessage
        lda #1
        ldx #1                     ; 1,1 : Timer
        jsr appel
        lda ControlPort+4
        ldx ControlPort+5
        rts

; Note le départ d'une attente de 3 secondes.
depart: jsr horloge
        sta debut
        stx debut+1
        rts
; C = 1 si 3 secondes (300 cs) sont passées depuis depart.
ecoule: jsr horloge
        sec
        sbc debut
        tay
        txa
        sbc debut+1
        cmp #>300                  ; (maintenant - départ) >= 300 ?
        bne _f
        cpy #<300
_f:     rts
debut:  .word 0

; Lit jusqu'à « OK » ou 3 secondes ; A = 1 si OK reçu.
attendre_ok:
        jsr depart
_b:     jsr lire
        ldx #0
_c:     inx
        cpx nrecu
        bcs _pas
        lda recu-1,x
        cmp #'O'
        bne _c
        lda recu,x
        cmp #'K'
        bne _c
        lda #1
        rts
_pas:   jsr ecoule
        bcc _b
        lda #0
        rts

recu:   .fill 128                  ; réponse du modem
nrecu:  .byte 0

t0:     .text "Envoi de AT : erreur ", 0
t1:     .text "Reponse : ", 0
tok:    .text "OK", 0
tr:     .text "rien", 0
e:      .byte 0
ok:     .byte 0

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

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

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

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

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Envoi de AT : erreur 0
Reponse : OK

14,4 Serial Read Block Trinity

Lit d'un coup les octets reçus de l'appareil série USB, jusqu'à un maximum, sans attendre.

Entrées
OctetsTypeRôle
P0-P1addrdestination
P2-P3uint16nombre maximal d'octets
P7uint8appareil : 0 ou 1
Sorties
OctetsTypeRôle
P2-P3uint16nombre d'octets lus (peut être 0)
erreuruint81 si l'appareil est absent

Rendre 0 octet n'est pas une erreur : on rappelle plus tard. La longueur est bornée à la fin de la mémoire du 6502. L'exemple envoie ATI (identification) et lit par blocs jusqu'à « OK » ; le texte de l'identification dépend du modem.

Texte de la documentation du firmware (anglais)

Read up to Parameters:2,3 bytes into memory at Parameters:0,1 from the CDC device (Parameter:7) ; Parameters:2,3 return the count actually read (may be 0).

C (llvm-mos)
/* Envoyer une commande au modem et lire sa réponse par blocs (groupe 14, fonction 4). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

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

static uint16_t en_attente(void) {              /* 14,1 : octets reçus en attente (appareil 0) ; 0xFFFF si absent */
    libre();
    ControlPort.params[7] = 0;                  /* P7 : appareil 0, le premier */
    api(14, 1);
    if (!ControlPort.params[0])
        return 0xFFFF;
    return ControlPort.params[1] | ControlPort.params[2] << 8;
}

static uint8_t envoyer(const char *s, uint8_t n) {   /* 14,5 : écrit n octets ; rend le nombre écrit */
    libre();
    ControlPort.params[0] = (uint16_t)s & 0xFF; /* P0-P1 : adresse */
    ControlPort.params[1] = (uint16_t)s >> 8;
    ControlPort.params[2] = n;                  /* P2-P3 : nombre d'octets */
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 5);
    return ControlPort.params[2];
}

static char recu[128];
static uint8_t nrecu;

static void lire(void) {                        /* 14,4 : ajoute à recu ce qui est arrivé */
    libre();
    ControlPort.params[0] = (uint16_t)(recu + nrecu) & 0xFF;
    ControlPort.params[1] = (uint16_t)(recu + nrecu) >> 8;
    ControlPort.params[2] = sizeof recu - 1 - nrecu;
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 4);
    nrecu += ControlPort.params[2];
    recu[nrecu] = 0;
}

static uint8_t attendre_ok(void) {              /* lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs) */
    uint32_t fin = neo_system_timer() + 300;
    while (neo_system_timer() < fin) {
        lire();
        for (uint8_t i = 0; i + 1 < nrecu; i++)
            if (recu[i] == 'O' && recu[i + 1] == 'K')
                return 1;
    }
    return 0;
}

int main(void) {
    envoyer("ATI\r", 4);                        /* 14,5 : identification du modem */
    uint8_t ok = attendre_ok();                 /* Serial Read Block, en boucle jusqu'à « OK » */
    printf("Reponse a ATI : %s\n", ok && nrecu > 4 ? "texte, puis OK" : "incomplete");
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Reponse a ATI : texte, puis OK
Mad Pascal
// Envoyer une commande au modem et lire sa réponse par blocs (groupe 14, fonction 4).
program cdc4;
uses crt, neo6502;
var recu: array[0..127] of byte;                // réponse du modem
    nrecu: byte;

function en_attente: word;                      // 14,1 : octets reçus en attente (appareil 0) ; $FFFF si absent
begin
  NeoWaitMessage;
  NeoMessage.params[7] := 0;                    // P7 : appareil 0, le premier
  NeoSendMessage(14, 1);
  NeoWaitMessage;
  if NeoMessage.params[0] = 0 then result := $FFFF
  else result := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

function envoyer(adresse: word; n: byte): byte; // 14,5 : écrit n octets ; rend le nombre écrit
begin
  NeoWaitMessage;
  wordParams[0] := adresse;                     // P0-P1 : adresse
  wordParams[1] := n;                           // P2-P3 : nombre d'octets
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 5);
  NeoWaitMessage;
  result := NeoMessage.params[2];
end;

procedure lire;                                 // 14,4 : ajoute à recu ce qui est arrivé
begin
  NeoWaitMessage;
  wordParams[0] := word(@recu) + nrecu;
  wordParams[1] := 127 - nrecu;
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 4);
  NeoWaitMessage;
  nrecu := nrecu + NeoMessage.params[2];
end;

function attendre_ok: boolean;                  // lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs)
var fin: cardinal;
    i: byte;
begin
  result := false;
  fin := NeoGetTimer + 300;
  while (not result) and (NeoGetTimer < fin) do begin
    lire;
    if nrecu > 1 then
      for i := 0 to nrecu - 2 do
        if (recu[i] = ord('O')) and (recu[i + 1] = ord('K')) then result := true;
  end;
end;
const ati: array[0..3] of byte = (ord('A'), ord('T'), ord('I'), 13);
var ok: boolean;
begin
  envoyer(word(@ati), 4);                       // 14,5 : identification du modem
  ok := attendre_ok;                            // Serial Read Block, en boucle jusqu'à « OK »
  if ok and (nrecu > 4) then writeln('Reponse a ATI : texte, puis OK') else writeln('Reponse a ATI : incomplete');
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Reponse a ATI : texte, puis OK
NeoBASIC
10 ' Envoyer une commande au modem et lire sa reponse par blocs (groupe 14, fonction 4)
20 r = alloc(128) : nr = 0 : ' reponse du modem
30 s$ = "ATI" : gosub 2000 : ' 14,5 : identification du modem
40 gosub 2200 : ' Serial Read Block, en boucle jusqu'a OK
50 r$ = "incomplete" : if ok & nr > 4 then r$ = "texte, puis OK"
60 print "Reponse a ATI : ";r$
70 end
900 ' 14,1 : octets en attente sur l'appareil 0 dans w (65535 si absent)
910 while peek($ff00) : wend : poke $ff0b, 0 : poke $ff01, 1 : poke $ff00, 14
920 while peek($ff00) : wend : w = 65535 : if peek($ff04) then w = deek($ff05)
930 return
2000 ' 14,5 : envoie s$ (plus un retour chariot) ; nombre d'octets ecrits dans n
2010 b = alloc(40) : for i = 1 to len(s$) : poke b + i - 1, asc(mid$(s$, i, 1)) : next i : poke b + len(s$), 13
2020 while peek($ff00) : wend
2030 doke $ff04, b : doke $ff06, len(s$) + 1 : poke $ff0b, 0 : poke $ff01, 5 : poke $ff00, 14
2040 while peek($ff00) : wend : n = peek($ff06)
2050 return
2100 ' 14,4 : ajoute a r (nr octets deja lus) ce qui est arrive
2110 while peek($ff00) : wend
2120 doke $ff04, r + nr : doke $ff06, 127 - nr : poke $ff0b, 0 : poke $ff01, 4 : poke $ff00, 14
2130 while peek($ff00) : wend : nr = nr + peek($ff06)
2140 return
2200 ' lit jusqu'a OK ou 3 secondes (time() : horloge en cs) ; ok = 1 si OK recu
2210 ok = 0 : f = time() + 300
2220 repeat : gosub 2100
2230 for i = 0 to nr - 2 : if peek(r + i) = 79 & peek(r + i + 1) = 75 then ok = 1
2240 next i
2250 until ok | time() > f
2260 return
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Reponse a ATI : texte, puis OK
Assembleur 65C02 (64tass)
; Envoyer une commande au modem et lire sa réponse par blocs (groupe 14, fonction 4).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:  lda #<cmd_ati              ; 14,5 : identification du modem
        ldx #>cmd_ati
        ldy #4
        jsr envoyer
        jsr attendre_ok            ; Serial Read Block, en boucle jusqu'à « OK »
        beq _inc
        lda nrecu
        cmp #5
        bcc _inc
        lda #<tok
        ldx #>tok
        jsr ecrire
        rts
_inc:   lda #<tinc
        ldx #>tinc
        jsr ecrire
        rts

; 14,1 : octets en attente sur l'appareil 0 dans w (16 bits) ; C = 1 si l'appareil est absent.
attente:
        jsr WaitMessage
        stz ControlPort+11         ; P7 : appareil 0, le premier
        lda #14
        ldx #1                     ; 14,1 : Serial Status
        jsr appel
        lda ControlPort+5          ; P1-P2 : octets reçus pas encore lus
        sta w
        lda ControlPort+6
        sta w+1
        lda ControlPort+4          ; P0 : 1 si branché
        cmp #1                     ; C = 0 si absent...
        eor #1
        lsr a                      ; ... d'où C = 1 si absent
        rts
w:      .word 0

; 14,5 : envoie n octets depuis l'adresse A (poids faible), X (poids fort), n dans Y ; nombre écrit dans A.
envoyer:
        pha
        jsr WaitMessage
        pla
        sta ControlPort+4          ; P0-P1 : adresse
        stx ControlPort+5
        sty ControlPort+6          ; P2-P3 : nombre d'octets
        stz ControlPort+7
        stz ControlPort+11         ; P7 : appareil 0
        lda #14
        ldx #5                     ; 14,5 : Serial Write Block
        jsr appel
        lda ControlPort+6
        rts

; 14,4 : ajoute à recu ce qui est arrivé (nrecu octets déjà lus).
lire:   jsr WaitMessage
        clc
        lda #<recu
        adc nrecu
        sta ControlPort+4          ; P0-P1 : recu + nrecu
        lda #>recu
        adc #0
        sta ControlPort+5
        sec
        lda #127
        sbc nrecu
        sta ControlPort+6          ; P2-P3 : place restante
        stz ControlPort+7
        stz ControlPort+11
        lda #14
        ldx #4                     ; 14,4 : Serial Read Block
        jsr appel
        clc
        lda nrecu
        adc ControlPort+6          ; P2-P3 : octets lus
        sta nrecu
        rts

; Horloge (1,1) en centièmes, 16 bits bas dans A (poids faible) et X (poids fort).
horloge:
        jsr WaitMessage
        lda #1
        ldx #1                     ; 1,1 : Timer
        jsr appel
        lda ControlPort+4
        ldx ControlPort+5
        rts

; Note le départ d'une attente de 3 secondes.
depart: jsr horloge
        sta debut
        stx debut+1
        rts
; C = 1 si 3 secondes (300 cs) sont passées depuis depart.
ecoule: jsr horloge
        sec
        sbc debut
        tay
        txa
        sbc debut+1
        cmp #>300                  ; (maintenant - départ) >= 300 ?
        bne _f
        cpy #<300
_f:     rts
debut:  .word 0

; Lit jusqu'à « OK » ou 3 secondes ; A = 1 si OK reçu.
attendre_ok:
        jsr depart
_b:     jsr lire
        ldx #0
_c:     inx
        cpx nrecu
        bcs _pas
        lda recu-1,x
        cmp #'O'
        bne _c
        lda recu,x
        cmp #'K'
        bne _c
        lda #1
        rts
_pas:   jsr ecoule
        bcc _b
        lda #0
        rts

recu:   .fill 128                  ; réponse du modem
nrecu:  .byte 0

cmd_ati: .text "ATI", 13
tok:    .text "Reponse a ATI : texte, puis OK", 13, 0
tinc:   .text "Reponse a ATI : incomplete", 13, 0

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

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

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

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

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Reponse a ATI : texte, puis OK

14,5 Serial Write Block Trinity

Envoie d'un coup une suite d'octets à l'appareil série USB.

Entrées
OctetsTypeRôle
P0-P1addrsource
P2-P3uint16nombre d'octets
P7uint8appareil : 0 ou 1
Sorties
OctetsTypeRôle
P2-P3uint16nombre d'octets écrits
erreuruint81 si l'appareil est absent

Comparer le nombre écrit au nombre demandé : l'appareil peut en prendre moins. La longueur est bornée à la fin de la mémoire du 6502.

Texte de la documentation du firmware (anglais)

Write Parameters:2,3 bytes from memory at Parameters:0,1 to the CDC device (Parameter:7) ; Parameters:2,3 return the count actually written.

C (llvm-mos)
/* Envoyer un bloc d'octets au modem (groupe 14, fonction 5). */
#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 uint16_t en_attente(void) {              /* 14,1 : octets reçus en attente (appareil 0) ; 0xFFFF si absent */
    libre();
    ControlPort.params[7] = 0;                  /* P7 : appareil 0, le premier */
    api(14, 1);
    if (!ControlPort.params[0])
        return 0xFFFF;
    return ControlPort.params[1] | ControlPort.params[2] << 8;
}

static uint8_t envoyer(const char *s, uint8_t n) {   /* 14,5 : écrit n octets ; rend le nombre écrit */
    libre();
    ControlPort.params[0] = (uint16_t)s & 0xFF; /* P0-P1 : adresse */
    ControlPort.params[1] = (uint16_t)s >> 8;
    ControlPort.params[2] = n;                  /* P2-P3 : nombre d'octets */
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 5);
    return ControlPort.params[2];
}

static char recu[128];
static uint8_t nrecu;

static void lire(void) {                        /* 14,4 : ajoute à recu ce qui est arrivé */
    libre();
    ControlPort.params[0] = (uint16_t)(recu + nrecu) & 0xFF;
    ControlPort.params[1] = (uint16_t)(recu + nrecu) >> 8;
    ControlPort.params[2] = sizeof recu - 1 - nrecu;
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 4);
    nrecu += ControlPort.params[2];
    recu[nrecu] = 0;
}

static uint8_t attendre_ok(void) {              /* lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs) */
    uint32_t fin = neo_system_timer() + 300;
    while (neo_system_timer() < fin) {
        lire();
        for (uint8_t i = 0; i + 1 < nrecu; i++)
            if (recu[i] == 'O' && recu[i + 1] == 'K')
                return 1;
    }
    return 0;
}

int main(void) {
    uint8_t n = envoyer("AT\r", 3);             /* Serial Write Block : rend le nombre d'octets écrits */
    uint8_t ok = attendre_ok();
    printf("Ecrits : %u octets, reponse : %s\n", n, ok ? "OK" : "rien");
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Ecrits : 3 octets, reponse : OK
Mad Pascal
// Envoyer un bloc d'octets au modem (groupe 14, fonction 5).
program cdc5;
uses crt, neo6502;
var recu: array[0..127] of byte;                // réponse du modem
    nrecu: byte;

function en_attente: word;                      // 14,1 : octets reçus en attente (appareil 0) ; $FFFF si absent
begin
  NeoWaitMessage;
  NeoMessage.params[7] := 0;                    // P7 : appareil 0, le premier
  NeoSendMessage(14, 1);
  NeoWaitMessage;
  if NeoMessage.params[0] = 0 then result := $FFFF
  else result := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

function envoyer(adresse: word; n: byte): byte; // 14,5 : écrit n octets ; rend le nombre écrit
begin
  NeoWaitMessage;
  wordParams[0] := adresse;                     // P0-P1 : adresse
  wordParams[1] := n;                           // P2-P3 : nombre d'octets
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 5);
  NeoWaitMessage;
  result := NeoMessage.params[2];
end;

procedure lire;                                 // 14,4 : ajoute à recu ce qui est arrivé
begin
  NeoWaitMessage;
  wordParams[0] := word(@recu) + nrecu;
  wordParams[1] := 127 - nrecu;
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 4);
  NeoWaitMessage;
  nrecu := nrecu + NeoMessage.params[2];
end;

function attendre_ok: boolean;                  // lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs)
var fin: cardinal;
    i: byte;
begin
  result := false;
  fin := NeoGetTimer + 300;
  while (not result) and (NeoGetTimer < fin) do begin
    lire;
    if nrecu > 1 then
      for i := 0 to nrecu - 2 do
        if (recu[i] = ord('O')) and (recu[i + 1] = ord('K')) then result := true;
  end;
end;
const at: array[0..2] of byte = (ord('A'), ord('T'), 13);
var n: byte;
    ok: boolean;
begin
  n := envoyer(word(@at), 3);                   // Serial Write Block : rend le nombre d'octets écrits
  ok := attendre_ok;
  write('Ecrits : ', n, ' octets, reponse : ');
  if ok then writeln('OK') else writeln('rien');
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Ecrits : 3 octets, reponse : OK
NeoBASIC
10 ' Envoyer un bloc d'octets au modem (groupe 14, fonction 5)
20 r = alloc(128) : nr = 0 : ' reponse du modem
30 s$ = "AT" : gosub 2000 : ' Serial Write Block : nombre d'octets ecrits dans n
40 gosub 2200
50 r$ = "rien" : if ok then r$ = "OK"
60 print "Ecrits : ";n;" octets, reponse : ";r$
70 end
900 ' 14,1 : octets en attente sur l'appareil 0 dans w (65535 si absent)
910 while peek($ff00) : wend : poke $ff0b, 0 : poke $ff01, 1 : poke $ff00, 14
920 while peek($ff00) : wend : w = 65535 : if peek($ff04) then w = deek($ff05)
930 return
2000 ' 14,5 : envoie s$ (plus un retour chariot) ; nombre d'octets ecrits dans n
2010 b = alloc(40) : for i = 1 to len(s$) : poke b + i - 1, asc(mid$(s$, i, 1)) : next i : poke b + len(s$), 13
2020 while peek($ff00) : wend
2030 doke $ff04, b : doke $ff06, len(s$) + 1 : poke $ff0b, 0 : poke $ff01, 5 : poke $ff00, 14
2040 while peek($ff00) : wend : n = peek($ff06)
2050 return
2100 ' 14,4 : ajoute a r (nr octets deja lus) ce qui est arrive
2110 while peek($ff00) : wend
2120 doke $ff04, r + nr : doke $ff06, 127 - nr : poke $ff0b, 0 : poke $ff01, 4 : poke $ff00, 14
2130 while peek($ff00) : wend : nr = nr + peek($ff06)
2140 return
2200 ' lit jusqu'a OK ou 3 secondes (time() : horloge en cs) ; ok = 1 si OK recu
2210 ok = 0 : f = time() + 300
2220 repeat : gosub 2100
2230 for i = 0 to nr - 2 : if peek(r + i) = 79 & peek(r + i + 1) = 75 then ok = 1
2240 next i
2250 until ok | time() > f
2260 return
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Ecrits : 3 octets, reponse : OK
Assembleur 65C02 (64tass)
; Envoyer un bloc d'octets au modem (groupe 14, 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:  lda #<cmd_at
        ldx #>cmd_at
        ldy #3
        jsr envoyer                ; Serial Write Block : nombre d'octets écrits dans A
        sta n
        jsr attendre_ok
        sta ok
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda n
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda ok
        bne _ook
        lda #<tr
        ldx #>tr
        jsr ecrire
        bra _eok
_ook:   lda #<tok
        ldx #>tok
        jsr ecrire
_eok:
        lda #13
        jsr WriteCharacter
        rts

; 14,1 : octets en attente sur l'appareil 0 dans w (16 bits) ; C = 1 si l'appareil est absent.
attente:
        jsr WaitMessage
        stz ControlPort+11         ; P7 : appareil 0, le premier
        lda #14
        ldx #1                     ; 14,1 : Serial Status
        jsr appel
        lda ControlPort+5          ; P1-P2 : octets reçus pas encore lus
        sta w
        lda ControlPort+6
        sta w+1
        lda ControlPort+4          ; P0 : 1 si branché
        cmp #1                     ; C = 0 si absent...
        eor #1
        lsr a                      ; ... d'où C = 1 si absent
        rts
w:      .word 0

; 14,5 : envoie n octets depuis l'adresse A (poids faible), X (poids fort), n dans Y ; nombre écrit dans A.
envoyer:
        pha
        jsr WaitMessage
        pla
        sta ControlPort+4          ; P0-P1 : adresse
        stx ControlPort+5
        sty ControlPort+6          ; P2-P3 : nombre d'octets
        stz ControlPort+7
        stz ControlPort+11         ; P7 : appareil 0
        lda #14
        ldx #5                     ; 14,5 : Serial Write Block
        jsr appel
        lda ControlPort+6
        rts

; 14,4 : ajoute à recu ce qui est arrivé (nrecu octets déjà lus).
lire:   jsr WaitMessage
        clc
        lda #<recu
        adc nrecu
        sta ControlPort+4          ; P0-P1 : recu + nrecu
        lda #>recu
        adc #0
        sta ControlPort+5
        sec
        lda #127
        sbc nrecu
        sta ControlPort+6          ; P2-P3 : place restante
        stz ControlPort+7
        stz ControlPort+11
        lda #14
        ldx #4                     ; 14,4 : Serial Read Block
        jsr appel
        clc
        lda nrecu
        adc ControlPort+6          ; P2-P3 : octets lus
        sta nrecu
        rts

; Horloge (1,1) en centièmes, 16 bits bas dans A (poids faible) et X (poids fort).
horloge:
        jsr WaitMessage
        lda #1
        ldx #1                     ; 1,1 : Timer
        jsr appel
        lda ControlPort+4
        ldx ControlPort+5
        rts

; Note le départ d'une attente de 3 secondes.
depart: jsr horloge
        sta debut
        stx debut+1
        rts
; C = 1 si 3 secondes (300 cs) sont passées depuis depart.
ecoule: jsr horloge
        sec
        sbc debut
        tay
        txa
        sbc debut+1
        cmp #>300                  ; (maintenant - départ) >= 300 ?
        bne _f
        cpy #<300
_f:     rts
debut:  .word 0

; Lit jusqu'à « OK » ou 3 secondes ; A = 1 si OK reçu.
attendre_ok:
        jsr depart
_b:     jsr lire
        ldx #0
_c:     inx
        cpx nrecu
        bcs _pas
        lda recu-1,x
        cmp #'O'
        bne _c
        lda recu,x
        cmp #'K'
        bne _c
        lda #1
        rts
_pas:   jsr ecoule
        bcc _b
        lda #0
        rts

recu:   .fill 128                  ; réponse du modem
nrecu:  .byte 0

cmd_at: .text "AT", 13
t0:     .text "Ecrits : ", 0
t1:     .text " octets, reponse : ", 0
tok:    .text "OK", 0
tr:     .text "rien", 0
n:      .byte 0
ok:     .byte 0

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

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

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

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

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Ecrits : 3 octets, reponse : OK

14,6 Serial Set Line Coding Trinity

Règle la vitesse et le format de la ligne série de l'appareil USB.

Entrées
OctetsTypeRôle
P0-P3uint32vitesse en bauds
P4uint8bits de données (0 : 8)
P5uint8parité : 0 aucune, 1 impaire, 2 paire
P6uint8bits de stop, 1 ou 2 (0 : 1)
P7uint8appareil : 0 ou 1
Sorties
OctetsTypeRôle
erreuruint81 si l'appareil est absent ou refuse le réglage

Beaucoup de modems USB ignorent ce réglage : ils n'ont pas de vraie ligne série. Un adaptateur USB-série, lui, l'applique. L'exemple règle 115200 bauds, 8N1, sur l'appareil 0 (accepté) puis sur l'appareil 1, absent (erreur 1).

Texte de la documentation du firmware (anglais)

Set the line coding of the CDC device (Parameter:7) : Parameters:0..3 = baud rate (32 bits), Parameter:4 = data bits (default 8), Parameter:5 = parity (0 none, 1 odd, 2 even), Parameter:6 = stop bits (1 or 2). Many USB modems ignore it.

C (llvm-mos)
/* Régler la vitesse et le format de la ligne série du modem (groupe 14, fonction 6). */
#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 uint16_t en_attente(void) {              /* 14,1 : octets reçus en attente (appareil 0) ; 0xFFFF si absent */
    libre();
    ControlPort.params[7] = 0;                  /* P7 : appareil 0, le premier */
    api(14, 1);
    if (!ControlPort.params[0])
        return 0xFFFF;
    return ControlPort.params[1] | ControlPort.params[2] << 8;
}

static uint8_t envoyer(const char *s, uint8_t n) {   /* 14,5 : écrit n octets ; rend le nombre écrit */
    libre();
    ControlPort.params[0] = (uint16_t)s & 0xFF; /* P0-P1 : adresse */
    ControlPort.params[1] = (uint16_t)s >> 8;
    ControlPort.params[2] = n;                  /* P2-P3 : nombre d'octets */
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 5);
    return ControlPort.params[2];
}

static char recu[128];
static uint8_t nrecu;

static void lire(void) {                        /* 14,4 : ajoute à recu ce qui est arrivé */
    libre();
    ControlPort.params[0] = (uint16_t)(recu + nrecu) & 0xFF;
    ControlPort.params[1] = (uint16_t)(recu + nrecu) >> 8;
    ControlPort.params[2] = sizeof recu - 1 - nrecu;
    ControlPort.params[3] = 0;
    ControlPort.params[7] = 0;
    api(14, 4);
    nrecu += ControlPort.params[2];
    recu[nrecu] = 0;
}

static uint8_t attendre_ok(void) {              /* lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs) */
    uint32_t fin = neo_system_timer() + 300;
    while (neo_system_timer() < fin) {
        lire();
        for (uint8_t i = 0; i + 1 < nrecu; i++)
            if (recu[i] == 'O' && recu[i + 1] == 'K')
                return 1;
    }
    return 0;
}

static uint8_t regler(uint8_t appareil) {       /* Serial Set Line Coding : 115200 bauds, 8N1 */
    uint32_t baud = 115200;
    libre();
    ControlPort.params[0] = baud & 0xFF;        /* P0-P3 : vitesse */
    ControlPort.params[1] = baud >> 8;
    ControlPort.params[2] = baud >> 16;
    ControlPort.params[3] = baud >> 24;
    ControlPort.params[4] = 8;                  /* P4 : bits de données */
    ControlPort.params[5] = 0;                  /* P5 : pas de parité */
    ControlPort.params[6] = 1;                  /* P6 : 1 bit de stop */
    ControlPort.params[7] = appareil;           /* P7 : numéro de l'appareil */
    api(14, 6);
    return ControlPort.error;
}

int main(void) {
    uint8_t a = regler(0), b = regler(1);       /* l'appareil 1 n'est pas branché */
    printf("Appareil 0 : erreur %u, appareil 1 : erreur %u\n", a, b);
    return 0;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Appareil 0 : erreur 0, appareil 1 : erreur 1
Mad Pascal
// Régler la vitesse et le format de la ligne série du modem (groupe 14, fonction 6).
program cdc6;
uses crt, neo6502;
var recu: array[0..127] of byte;                // réponse du modem
    nrecu: byte;

function en_attente: word;                      // 14,1 : octets reçus en attente (appareil 0) ; $FFFF si absent
begin
  NeoWaitMessage;
  NeoMessage.params[7] := 0;                    // P7 : appareil 0, le premier
  NeoSendMessage(14, 1);
  NeoWaitMessage;
  if NeoMessage.params[0] = 0 then result := $FFFF
  else result := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

function envoyer(adresse: word; n: byte): byte; // 14,5 : écrit n octets ; rend le nombre écrit
begin
  NeoWaitMessage;
  wordParams[0] := adresse;                     // P0-P1 : adresse
  wordParams[1] := n;                           // P2-P3 : nombre d'octets
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 5);
  NeoWaitMessage;
  result := NeoMessage.params[2];
end;

procedure lire;                                 // 14,4 : ajoute à recu ce qui est arrivé
begin
  NeoWaitMessage;
  wordParams[0] := word(@recu) + nrecu;
  wordParams[1] := 127 - nrecu;
  NeoMessage.params[7] := 0;
  NeoSendMessage(14, 4);
  NeoWaitMessage;
  nrecu := nrecu + NeoMessage.params[2];
end;

function attendre_ok: boolean;                  // lit jusqu'à « OK » ou 3 secondes (1,1 : horloge en cs)
var fin: cardinal;
    i: byte;
begin
  result := false;
  fin := NeoGetTimer + 300;
  while (not result) and (NeoGetTimer < fin) do begin
    lire;
    if nrecu > 1 then
      for i := 0 to nrecu - 2 do
        if (recu[i] = ord('O')) and (recu[i + 1] = ord('K')) then result := true;
  end;
end;
var a, b: byte;

function regler(appareil: byte): byte;          // Serial Set Line Coding : 115200 bauds, 8N1
begin
  NeoWaitMessage;
  dwordParams[0] := 115200;                     // P0-P3 : vitesse
  NeoMessage.params[4] := 8;                    // P4 : bits de données
  NeoMessage.params[5] := 0;                    // P5 : pas de parité
  NeoMessage.params[6] := 1;                    // P6 : 1 bit de stop
  NeoMessage.params[7] := appareil;             // P7 : numéro de l'appareil
  NeoSendMessage(14, 6);
  NeoWaitMessage;
  result := NeoMessage.error;
end;

begin
  a := regler(0);
  b := regler(1);                               // l'appareil 1 n'est pas branché
  writeln('Appareil 0 : erreur ', a, ', appareil 1 : erreur ', b);
end.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Appareil 0 : erreur 0, appareil 1 : erreur 1
NeoBASIC
10 ' Regler la vitesse et le format de la ligne serie du modem (groupe 14, fonction 6)
20 a = 0 : gosub 1000 : e0 = e
30 a = 1 : gosub 1000 : e1 = e : ' l'appareil 1 n'est pas branche
40 print "Appareil 0 : erreur ";e0;", appareil 1 : erreur ";e1
50 end
1000 ' Serial Set Line Coding sur l'appareil a : 115200 bauds, 8N1 ; erreur dans e
1010 while peek($ff00) : wend
1020 doke $ff04, 115200 & $ffff : doke $ff06, 115200 >> 16 : ' P0-P3 : vitesse
1030 poke $ff08, 8 : poke $ff09, 0 : poke $ff0a, 1 : poke $ff0b, a : ' 8 bits, sans parite, 1 stop ; P7 : appareil
1040 poke $ff01, 6 : poke $ff00, 14
1050 while peek($ff00) : wend : e = peek($ff02)
1060 return
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Appareil 0 : erreur 0, appareil 1 : erreur 1
Assembleur 65C02 (64tass)
; Régler la vitesse et le format de la ligne série du modem (groupe 14, 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:  lda #0
        jsr regler
        sta a
        lda #1                     ; l'appareil 1 n'est pas branché
        jsr regler
        sta b
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda a
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda b
        jsr decimal
        lda #13
        jsr WriteCharacter
        rts

; Serial Set Line Coding de l'appareil A : 115200 bauds, 8N1 ; erreur rendue dans A.
regler: pha
        jsr WaitMessage
        pla
        sta ControlPort+11         ; P7 : numéro de l'appareil
        ldx #6
_c:     lda ligne,x                ; P0-P6 : vitesse (32 bits), bits de données, parité, bits de stop
        sta ControlPort+4,x
        dex
        bpl _c
        lda #14
        ldx #6                     ; 14,6 : Serial Set Line Coding
        jsr appel
        lda ControlPort+2
        rts
ligne:  .dword 115200
        .byte 8, 0, 1
t0:     .text "Appareil 0 : erreur ", 0
t1:     .text ", appareil 1 : erreur ", 0
a:      .byte 0
b:      .byte 0

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

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

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

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

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Appareil 0 : erreur 0, appareil 1 : erreur 1

← L'API du Neo6502