Tachibana
Langue : français

Architecture technique — OricTel

Vue d'ensemble

OricTel est composé de deux sous-systèmes indépendants :

  1. Programme Oric (6502 C/ASM) — le terminal Minitel.
  2. Bridge Python — passerelle WebSocket ↔ TCP (chemin secondaire).

Le montage de référence est aujourd'hui LOCI + PicoWiFiModemUSB : le modem AT ouvre lui-même les sockets TCP vers le serveur Minitel, le bridge n'est plus dans le chemin. Celui-ci reste utile pour les serveurs exposés uniquement en WebSocket.

Flux de données

Serveur Minitel (ws://3617.fr/ws)
        |
        | WebSocket (frames binaires, octets Vidéotex bruts)
        v
+-------------------+
| orictel_bridge.py |  Python asyncio
| Port TCP 3615     |  Relais bidirectionnel transparent
+-------------------+
        |
        | TCP socket (octets bruts, pas de trame)
        v
+-------------------+
| Phosphoric        |  Émulateur Oric
| Backend ACIA      |  --serial picowifi / com: / tcp:
| Modem AT 8N1      |  1200 bauds (Contrôle $18, Commande $0B)
+-------------------+
        |
        | Registres ACIA 6551 @ $0380-$0383 (base LOCI)
        | Timing cycle-exact
        v
+-------------------+
| Programme OricTel |  code 6502 (cc65)
|                   |
| serial_asm.s      |  <-- Driver ACIA bas niveau (polling, SEI/PLP)
| serial.c/tx.c     |  <-- Couche fine + file d'émission non bloquante
| at_modem.c        |  <-- Commandes AT, raccrochage, veille de porteuse
| videotex.c        |  <-- Machine à états protocole
| display.c         |  <-- Rendu HIRES (budget 1 ligne/passe)
| display_asm.s     |  <-- Blits assembleur (cellule, course, double hauteur)
| fonts.c           |  <-- Tables G0 / G1 / G2
| keyboard.c        |  <-- Scan clavier + mapping Minitel
| ui.c              |  <-- Helpers de menus (testables hôte)
| main.c            |  <-- Boucle principale, menus, session
+-------------------+

main.c est un cycle menus → connexion → session : la session ne se quitte que par ESC (confirmé dans la barre de statut, sans effacer la page), qui raccroche (at_hangup) et repart au menu Mode de connexion avec vtx_init. Les écrans d'échec de connexion et de perte de porteuse rendent la main sur ESC. ESC sur le menu principal sort d'OricTel par jmp ($FFFC) (vecteur de reset, indépendant de la ROM 1.0/1.1).

Montage de référence (matériel)

Oric --> ACIA 6551 $0380 (cartouche LOCI)
             |
             | firmware RP2040 : anneau RX de 32 OCTETS devant le registre
             v
        USB-CDC --> PicoWiFiModemUSB --> WiFi --> serveur Minitel (TCP)

L'anneau de 32 octets est un élément de conception important : le 6551 nu ne tamponne qu'un octet, et OricTel n'utilise aucune IRQ. C'est ce tampon qui donne au logiciel une tolérance d'environ 250 000 cycles (30 octets à 1200 bauds) entre deux lectures. Émuler ce montage sans --serial-buffer est donc plus pessimiste que le matériel réel.

Carte mémoire Oric

$0000-$00FB  Zero Page (cc65 : $00E2-$00FB)
$0100-$01FF  Pile 6502
$0200-$02FF  Variables système
$0300-$030F  VIA 6522 (miroir $0300-$03FF)
$0380-$0383  ACIA 6551 (série, base LOCI)
$0400-$0500  Zone système Oric
$0501-$97FF  CODE + DATA + BSS OricTel (~37 Ko ; BSS borné à $9800)
$9800-$9BFF  Jeu de caractères des lignes texte (copie de font_g0)
$9C00-$9FFF  Pile cc65 (1 Ko ; relevé < 32 octets en session)
$A000-$BF3F  Framebuffer HIRES (8000 octets)
$BF68-$BFDF  3 lignes texte sous le HIRES = barre de statut
$C000-$FFFF  ROM (16 Ko)

Registres ACIA 6551 (côté Oric)

AdresseLectureÉcriture
$0380Donnée reçueDonnée à envoyer
$0381Registre statutReset programme
$0382Registre commandeRegistre commande
$0383Registre contrôleRegistre contrôle

Configuration effective (base LOCI $0380)

Piège datasheet 6551 : TIC=01 (valeurs $05/$07) active l'IRQ TX ; TDRE étant levé en permanence, l'ACIA réassertait /IRQ sans fin et la machine gelait (régressions v0.3.3/v0.3.4). L'ancienne configuration V23 7E1 ($28/$69) ne correspond plus à aucun montage supporté : le PicoWiFiModemUSB dialogue en 8N1.

Bits du registre Status

Barre de statut (3 lignes texte)

En HIRES, l'Oric affiche encore 3 lignes texte sous les 200 lignes graphiques ($BF68-$BFDF). Elles étaient cachées parce que leur jeu de caractères, que la ROM place en $9800-$9FFF, était écrasé par la pile C. La pile est ramenée à 1 Ko en $9C00-$9FFF (zone du jeu alternatif, jamais sélectionné) et display_init copie font_g0 en $9900-$9BFF. Ligne 0 (cyan) : indicateur C/F, serveur, chrono mm:ss, mode de rendu, rappel ESC ; ligne 1 (jaune) : messages transitoires ; ligne 2 (blanc) : aide des touches. L'indicateur de liaison quitte ainsi la ligne 0 de la page Vidéotex, qui appartient au serveur.

Base de temps : Timer 2 du VIA 6522

La boucle de session n'a pas d'ISR et son itération n'a pas de durée fixe. Les délais (confirmation de perte de porteuse 4 s, retour de l'indicateur à F après 30 s) sont donc comptés sur T2 ($0308/$0309), lu à chaque itération : la ROM ne s'en sert que pour la cassette, il décompte en continu à 1 MHz, et l'on accumule les cycles écoulés en tics de 10 ms. OricTel l'arme lui-même et interdit son IRQ (IER = $20). Vérifié par make test-carrier : 4,0 s ± 0,1 sur ROM 1.0 et 1.1.

Protocole Vidéotex — Machine à états

NORMAL ──ESC($1B)──> ESC_SEQ
       ──US($1F)───> US_ROW
       ──SO($0E)───> (basculer G1)
       ──SI($0F)───> (basculer G0)
       ──$20-$7F──> (afficher caractère)
       ──$08-$0B──> (déplacement curseur)
       ──$0C─────> (effacer écran)
       ──$0D─────> (retour chariot)

ESC_SEQ ──$40-$47──> (couleur encre) -> NORMAL
        ──$50-$57──> (couleur fond) -> NORMAL
        ──$48/$49──> (flash on/off) -> NORMAL
        ──$4C-$4F──> (taille) -> NORMAL
        ──$5B─────> CSI
        ──$39─────> PRO

US_ROW ──$40-$57──> US_COL (mémorise ligne)

US_COL ──$41-$68──> NORMAL (positionne curseur)

CSI ──params+lettre──> NORMAL (commande ANSI-like)

Affichage HIRES

Attributs par cellule

Rendu optimisé (plages « dirty », budget mesuré)

Chaque ligne modifiée est marquée « dirty » avec la plage de colonnes touchée. Le rendu n'est pas cadencé sur la trame vidéo : il est budgété à une ligne par passe depuis la boucle principale, qui rappelle display_render() tant qu'il reste des lignes sales et qu'aucun octet n'attend en réception. Pendant une passe la réception n'est pas relue : si la passe dépasse la tolérance du tampon RX, des octets sont perdus.

Coûts mesurés (make bench-render, cycles 6502 réels)

RégionCoûtEn temps-octet à 1200 bauds
passe display_render() (1 ligne)~151 00018,8
ligne hybride réaliste~79 00010,1
ligne double hauteur~134 00016,7
mise à jour d'une cellule~30 0003,6
vtx_process() (40 caractères)~105 00013,2

Budget d'un octet à 1200 bauds : 8 333 cycles. Tolérance du montage réel : ~250 000 cycles (anneau de 32 octets du firmware LOCI). Le banc affiche aussi l'empreinte SHA-256 du framebuffer après une page canonique : toute optimisation sans effet visuel doit la laisser inchangée.

Chemins rapides assembleur (display_asm.s)

RoutineRôle
blit_cell8une cellule taille normale (~190 cycles contre ~2 000 en C)
blit_runcourse de cellules éligibles ; s'arrête sur toute taille non normale
blit_cell4x2double hauteur : 4 lignes source étirées sur 8 lignes pixel
display_clearremplissage du framebuffer par pages (~5 ms)

Piège cc65 : ctx->screen[row][col] génère une multiplication 16 bits par 240 à chaque accès, et un pointeur paramètre est rechargé depuis la pile logicielle à chaque déréférencement. Les boucles chaudes hissent donc un pointeur de ligne, et videotex.c passe par un pointeur de contexte de portée fichier plus des tables d'offsets : 4,0× sur le rendu, 1,50× sur le décodeur.

Reproduit depuis docs/ARCHITECTURE.md (version de référence à jour sur GitHub).

← Documentation · Manuel d'utilisation →