Tachibana
Language: English

Group 14 — USB Serial (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

Trinity only: a USB serial device (CDC-ACM class) plugged into a USB-A port of the board, usually a modem, such as the Neo6502 Wi-Fi modem, or a USB-serial adapter. P7 selects the device (0 the first, 1 the second); a larger number falls back to device 0, without an error. The serial functions of group 10 (10,13 to 10,18) also go through this device when 10,19 selects it, which is the default as soon as a modem is plugged in.

On the bench, Phosphoneo plugs in a simulated Hayes modem (tools/fake_modem.py): the examples send it AT and wait for "OK", as a real modem would do. They only print what does not depend on the modem (plugged in or not, errors, bytes written, presence of "OK").

14,1 Serial Status Trinity

Tells whether the USB serial device is plugged in and how many received bytes are waiting to be read.

Inputs
BytesTypeMeaning
P7uint8device: 0 or 1 (above: 0)
Outputs
BytesTypeMeaning
P0uint81 if the device is plugged in
P1-P2uint16received bytes waiting (0 if the device is absent)

The call never flags an error. The example shows that device 5 (out of range) is seen as device 0, then that bytes are waiting after sending AT. Without a modem, both devices are absent (checked on the bench). NeoBASIC has no keyword for this group.

Firmware documentation text

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;
}
Output observed in the Phosphoneo emulator, Trinity firmware 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.
Output observed in the Phosphoneo emulator, Trinity firmware 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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Appareil 0 : branche, appareil 5 : branche
En attente avant AT : 0, apres : des octets
65C02 assembly (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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Appareil 0 : branche, appareil 5 : branche
En attente avant AT : 0, apres : des octets

14,2 Serial Read Byte Trinity

Reads one byte received from the USB serial device, without waiting.

Inputs
BytesTypeMeaning
P7uint8device: 0 or 1
Outputs
BytesTypeMeaning
P0uint8byte read
erreuruint81 if nothing has arrived or the device is absent

Before anything is sent, nothing has arrived: error 1. The example sends AT, then reads byte by byte until "OK".

Firmware documentation text

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;
}
Output observed in the Phosphoneo emulator, Trinity firmware 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.
Output observed in the Phosphoneo emulator, Trinity firmware 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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant l'envoi : erreur 1
Reponse a AT : OK
65C02 assembly (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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant l'envoi : erreur 1
Reponse a AT : OK

14,3 Serial Write Byte Trinity

Sends one byte to the USB serial device.

Inputs
BytesTypeMeaning
P0uint8byte to send
P7uint8device: 0 or 1
Outputs
BytesTypeMeaning
erreuruint81 if the device is absent or did not take the byte

For more than one byte, 14,5 does it all in one call. The example sends A, T and a carriage return, one at a time, then waits for "OK".

Firmware documentation text

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;
}
Output observed in the Phosphoneo emulator, Trinity firmware 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.
Output observed in the Phosphoneo emulator, Trinity firmware 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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Envoi de AT : erreur 0
Reponse : OK
65C02 assembly (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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Envoi de AT : erreur 0
Reponse : OK

14,4 Serial Read Block Trinity

Reads the bytes received from the USB serial device in one go, up to a maximum, without waiting.

Inputs
BytesTypeMeaning
P0-P1addrdestination
P2-P3uint16maximum number of bytes
P7uint8device: 0 or 1
Outputs
BytesTypeMeaning
P2-P3uint16number of bytes read (may be 0)
erreuruint81 if the device is absent

Returning 0 bytes is not an error: call again later. The length is limited to the end of 6502 memory. The example sends ATI (identification) and reads blocks until "OK"; the identification text depends on the modem.

Firmware documentation text

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;
}
Output observed in the Phosphoneo emulator, Trinity firmware 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.
Output observed in the Phosphoneo emulator, Trinity firmware 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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Reponse a ATI : texte, puis OK
65C02 assembly (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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Reponse a ATI : texte, puis OK

14,5 Serial Write Block Trinity

Sends a run of bytes to the USB serial device in one go.

Inputs
BytesTypeMeaning
P0-P1addrsource
P2-P3uint16number of bytes
P7uint8device: 0 or 1
Outputs
BytesTypeMeaning
P2-P3uint16number of bytes written
erreuruint81 if the device is absent

Compare the number written with the number asked: the device may take fewer. The length is limited to the end of 6502 memory.

Firmware documentation text

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;
}
Output observed in the Phosphoneo emulator, Trinity firmware 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.
Output observed in the Phosphoneo emulator, Trinity firmware 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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Ecrits : 3 octets, reponse : OK
65C02 assembly (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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Ecrits : 3 octets, reponse : OK

14,6 Serial Set Line Coding Trinity

Sets the speed and format of the USB device serial line.

Inputs
BytesTypeMeaning
P0-P3uint32speed in bauds
P4uint8data bits (0: 8)
P5uint8parity: 0 none, 1 odd, 2 even
P6uint8stop bits, 1 or 2 (0: 1)
P7uint8device: 0 or 1
Outputs
BytesTypeMeaning
erreuruint81 if the device is absent or refuses the setting

Many USB modems ignore this setting: they have no real serial line. A USB-serial adapter applies it. The example sets 115200 bauds, 8N1, on device 0 (accepted) then on device 1, which is absent (error 1).

Firmware documentation text

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;
}
Output observed in the Phosphoneo emulator, Trinity firmware 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.
Output observed in the Phosphoneo emulator, Trinity firmware 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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Appareil 0 : erreur 0, appareil 1 : erreur 1
65C02 assembly (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
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Appareil 0 : erreur 0, appareil 1 : erreur 1

← The Neo6502 API