Obligement - L'Amiga au maximum

Lundi 16 mai 2022 - 22:52  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in english


Twitter

Suivez-nous sur Twitter




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Sites de téléchargements
Associations
Pages Personnelles
Matériel
Réparateurs
Revendeurs
Presse et médias
Programmation
Logiciels
Jeux
Scène démo
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Bidouille : Développement du lecteur de disquette Arduino Amiga, alias DrawBridge
(Article écrit par Robert Smith et extrait d'amiga.robsmithdev.co.uk - juin 2021, mis à jour en février 2022)


Note : traduction par David Brunet.

Ce qui suit est la description et le journal du développement de ma solution de lecteur de disquette Arduino Amiga, alias DrawBridge.

Plan de l'article

Mon parcours sur Amiga

Je dois ma carrière à l'Amiga, plus précisément à l'A500+ que mes parents m'ont acheté pour Noël, quand j'avais 10 ans. Au début, je l'utilisais pour les jeux, mais à un moment donné, j'ai commencé à vouloir voir ce qu'il pouvait faire d'autre. J'ai bidouillé avec Deluxe Paint 3 et j'ai appris à connaître le Workbench.

Amiga 500+
L'Amiga 500 Plus

Chaque mois, j'achetais le populaire magazine Amiga Format. Un mois, j'ai eu une copie gratuite d'AMOS. J'ai participé au concours d'Amiga Format intitulé "Write A Game In AMOS" (programmer un jeu en AMOS) lorsqu'AMOS Professional a été publié sur une disquette de couverture, et j'ai été l'un des douze (je crois) gagnants avec mon jeu In The Pipe Line. Et il fallut vraiment leur courir après pour recevoir les prix !

AMOS
AMOS - The Creator

Plus tard, j'ai utilisé mon A500+ dans le cadre de mes projets de GCSE (NDLR : équivalent du brevet en France) et du A-Level (NDLR : équivalent du Bac en France) grâce à Highspeed Pascal, qui était compatible avec Turbo Pascal sur PC.

Quoi qu'il en soit, c'était il y a longtemps, et je dispose encore des boîtes de disquettes, et d'un A500+ qui ne fonctionne plus. J'ai alors voulu sauvegarder ces disquettes sur mon ordinateur, pour la préservation et la nostalgie.

Le site Amiga Forever a une excellente liste d'options qui incluent le matériel, et l'utilisation abusive de deux lecteurs de disquette dans un PC. Malheureusement, aucune de ces options n'était possible avec le matériel moderne, et les contrôleurs KryoFlux/Catweasel sont trop chers. J'ai été vraiment surpris de voir que le code source de la plupart des logiciels ne soit pas ouvert au public.

Passionné d'électronique et ayant bidouillé avec des appareils Atmel (AT89C4051) à l'université, j'ai décidé de jeter un coup d'oeil à l'Arduino (merci à GreatScott pour son inspiration qui m'a montré à quel point il est facile de se lancer). Je me suis demandé si cela était possible.

J'ai donc cherché sur Google "Arduino floppy drive reading code" (code de lecture de disquette Arduino), et après avoir ignoré tous les projets qui utilisaient le lecteur pour jouer de la musique, je n'ai pas vraiment trouvé de solutions. J'ai trouvé quelques discussions dans certains groupes suggérant que ce ne serait pas possible. J'ai trouvé un projet basé sur un FPGA qui était très intéressant à lire, mais ce n'était pas la direction que je voulais prendre, donc la seule option était de construire moi-même une solution.

Recherche

Lorsque j'ai commencé ce projet, je n'avais pas la moindre idée du fonctionnement d'un lecteur de disquette, et encore moins de la manière dont les données étaient codées sur les disquettes. Les sites Web suivants m'ont été d'une aide précieuse pour comprendre ce qu'il se passe et comment ils fonctionnent :
Hypothèses

Sur la base des recherches effectuées, je savais maintenant théoriquement comment les données étaient écrites sur la disquette et comment la disquette tournait.

J'ai commencé à calculer quelques chiffres. Sur la base de la vitesse de rotation de la disquette double densité (300 tr/min) et de la façon dont les données sont stockées (80 pistes, 11 secteurs par piste et 512 octets par secteur, codées à l'aide de la MFM), pour lire les données avec précision, je devais être capable d'échantillonner les données à 500 kHz ; c'est assez rapide si l'on considère que l'Arduino ne tourne qu'à 16 kHz.

Dans les sept tentatives qui suivent, je ne parle que du côté Arduino. Passez au chapitre décodage.

Tentative 1

Tout d'abord, j'ai dû trouver le matériel et l'interface avec le lecteur de disquette. J'ai pris le lecteur de disquette d'un vieux PC au travail, et j'ai récupéré son câble IDE par la même occasion.

Ci-dessous, une photo du lecteur de disquette provenant d'un vieux PC :

lecteur de disquette PC

En étudiant le brochage du lecteur, j'ai réalisé que je n'avais besoin que de quelques fils, et après avoir regardé le lecteur, j'ai réalisé qu'il n'utilisait pas non plus l'entrée 12 V.

Pour faire tourner le lecteur, il a fallu sélectionner le lecteur et activer le moteur. Le déplacement de la tête de lecture est simple. Il suffisait d'activer ou de désactiver la broche /DIR, puis d'envoyer une impulsion à la broche /STEP. Vous pouviez savoir si la tête avait atteint la piste 0 (la première piste) en surveillant la broche /TRK00.

J'étais curieux à propos de la broche /INDEX. Elle émet une impulsion à chaque rotation. Comme l'Amiga ne l'utilise pas pour trouver le début de la piste, je n'en avais pas besoin et je pouvais l'ignorer. Après cela, il suffit de choisir le côté de la disquette à lire (/SIDE1) et de connecter /RDATA.

Avec l'exigence d'un taux de transfert de données élevé, ma première pensée a été de trouver un moyen de rendre cela moins problématique en essayant de réduire les exigences sur ce taux.

Le plan était d'utiliser deux registres à décalage de 8 bits (SN74HC594N) pour réduire la fréquence d'échantillonnage requise par un facteur de 8. J'ai utilisé ce qu'Ebay appelle "Pro Mini atmega328 Board 5V 16M Arduino Compatible Nano" (donc je ne sais pas ce que c'est officiellement, mais cela fonctionne sur l'Uno !) pour mettre en mémoire tampon ces données parallèles et les envoyer au PC en utilisant son interface série/USART. Je savais que cela devait fonctionner plus vite que 500 kbauds (avec toutes les surcharges série impliquées aussi).

Après avoir abandonné la bibliothèque série standard de l'Arduino, j'ai été très heureux de constater que je pouvais configurer l'USART sur l'Arduino à 2 Mbauds, et avec l'une de ces cartes d'extension F2DI (sur Ebay, elle est nommée "Basic Breakout Board For FTDI FT232RL USB to Serial IC For Arduino" - voir ci-dessous), je pouvais envoyer et recevoir des données à cette vitesse (62,5 kHz) mais je devais le faire avec précision.

Voici la carte de dérivation FTDI qui s'adapte parfaitement à l'interface de la carte Arduino :

FTDI
Carte FTDI

Tout d'abord, j'ai utilisé l'Arduino pour configurer l'un des registres à décalage de 8 bits, un seul des 8 bits étant cadencé à fréquence élevée. L'autre recevait une impulsion directement du lecteur de disquette (fournissant ainsi une conversion série vers parallèle).

L'image suivante est une image folle de la platine d'expérimentation sur laquelle j'ai construit ceci à l'époque :

tentative 1

J'ai utilisé un des chronomètres de l'Arduino pour générer un signal de 500 kHz sur une de ses broches de sortie et comme le matériel gère cela, il est très précis ! En tout cas, mon multimètre l'a mesuré comme étant exactement 500 kHz.

Le code a fonctionné, j'ai cadencé 8 bits de données à 62,5 kHz, laissant le processeur de l'Arduino à peine utilisé. Cependant, je ne recevais rien de significatif. A ce stade, j'ai réalisé que je devais regarder de plus près les données réelles qui sortaient du lecteur de disquette. J'ai donc acheté un vieil oscilloscope bon marché sur Ebay (Gould OS300 20Mhz Oscilloscope) pour vérifier ce qu'il se passait.

En attendant que l'oscilloscope arrive, j'ai décidé d'essayer autre chose.

Voici un bout du code utilisé pour lire les données des registres à décalage :

void readTrackData() {
    byte op;
    
    for (int a=0; a<5632; a++) {
        // We'll wait for the "byte" start marker
        while (digitalRead(PIN_BYTE_READ_SIGNAL)==LOW) {};
        // Read the byte
        op=0;
        if (digitalRead(DATA_LOWER_NIBBLE_PB0)==HIGH) op|=1;
        if (digitalRead(DATA_LOWER_NIBBLE_PB1)==HIGH) op|=2;
        if (digitalRead(DATA_LOWER_NIBBLE_PB2)==HIGH) op|=4;
        if (digitalRead(DATA_LOWER_NIBBLE_PB3)==HIGH) op|=8;
        if (digitalRead(DATA_UPPER_NIBBLE_A0)==HIGH) op|=16;
        if (digitalRead(DATA_UPPER_NIBBLE_A1)==HIGH) op|=32;
        if (digitalRead(DATA_UPPER_NIBBLE_A2)==HIGH) op|=64;
        if (digitalRead(DATA_UPPER_NIBBLE_A3)==HIGH) op|=128;

        writeByteToUART(op);
        
        // Wait for high to drop again 
        while (digitalRead(PIN_BYTE_READ_SIGNAL)==HIGH) {};
    }
}

Tentative 2

J'ai décidé que les registres à décalage, bien qu'étant une bonne idée, n'étaient probablement pas utiles. J'étais capable de lire facilement 8 bits en une seule fois, mais je me suis rendu compte que je ne pouvais pas être sûr que tous les bits étaient correctement synchronisés en premier lieu. En lisant la documentation, il était suggéré que les données étaient plutôt des impulsions courtes plutôt que des impulsions haut et bas.

J'ai retiré les registres à décalage et je me suis demandé ce qu'il se passerait si j'essayais de vérifier une impulsion du lecteur dans une interruption (ISR) en utilisant le signal 500 kHz précédemment configuré. J'ai reconfiguré l'Arduino pour qu'il génère l'ISR, et après avoir résolu le problème des bibliothèques de l'Arduino qui m'empêchaient d'utiliser l'ISR que je voulais, je suis passé au Timer2 (l'un des trois chronomètres du microcontrôleur).

J'ai écrit un court ISR qui décalait d'un bit vers la gauche un octet unique global, puis si la broche connectée à la ligne de données du lecteur de disquette était "LOW" ("BAS", les impulsions sont basses), j'y plaçais un 1 par "OR" (OU). Toutes les huit fois que j'ai fait cela, j'ai écrit l'octet complet sur l'USART.

Cela ne s'est pas passé comme prévu ! L'Arduino a commencé à se comporter de manière très erratique et étrange. Je me suis vite rendu compte que l'ISR prenait plus de temps pour s'exécuter que le temps entre les appels à celui-ci. Je pouvais recevoir une impulsion toutes les deux microsecondes et en me basant sur la vitesse de l'Arduino et en supposant que chaque instruction C correspondait à un cycle de code machine, j'ai réalisé que je pouvais avoir au maximum 32 instructions. Malheureusement, la plupart seraient plus d'une instruction, et après une recherche sur Google, j'ai réalisé que la surcharge sur le démarrage d'un ISR était de toute façon massive. Sans compter que les fonctions digitalRead étaient très lentes.

J'ai abandonné la fonction digitalRead pour accéder directement aux broches du port. Cela ne m'a toujours pas aidé et n'était pas assez rapide. N'étant pas prêt à abandonner, j'ai mis de côté cette approche et j'ai décidé d'aller de l'avant et d'essayer autre chose.

C'est alors que l'oscilloscope que j'avais acheté est arrivé, et il a fonctionné ! C'était un vieil oscilloscope qui était probablement plus vieux que moi ! Mais il a quand même fait le travail parfaitement. Si vous ne savez pas ce qu'est un oscilloscope, consultez EEVblog #926 - Introduction à l'oscilloscope, et si vous aimez l'électronique, je vous suggère d'en regarder quelques autres et de parcourir le site d'EEVBlog.

Voici mon vieil oscilloscope récemment acheté (Gould OS300 20Mhz) :

Gould OS300

Après avoir connecté le signal 500 kHz à un canal et la sortie du lecteur de disquette à un autre, il était évident que quelque chose n'allait pas. Le signal 500 kHz était une onde carrée parfaite en l'utilisant comme déclencheur (ou bascule), les données de la disquette étaient éparpillées. Je pouvais voir les impulsions, mais c'était un peu flou. De même, si je déclenchais à partir du signal du lecteur de disquette, le signal carré de 500 kHz était éparpillé et non synchronisé avec lui.

Voici des photos des tracés sur l'oscilloscope déclenché par les deux canaux. Vous ne pouvez pas vraiment le voir, mais sur le canal qui n'est pas déclenché, il y a des milliers de lignes fantômes :

tracés

Individuellement, je pouvais mesurer les impulsions des deux signaux à 500 kHz, ce qui n'avait pas de sens, car s'ils fonctionnent tous les deux à la même vitesse mais ne se déclenchent pas pour que vous puissiez voir les deux signaux correctement, alors quelque chose ne va pas.

Après avoir beaucoup expérimenté avec les niveaux de déclenchement, j'ai réussi à comprendre ce qu'il se passait. Mon signal était un 500 kHz parfait, mais en regardant le signal du lecteur de disquette, ils étaient espacés correctement, mais pas tout le temps. Entre les groupes d'impulsions, il y avait une dérive d'erreur, et aussi des trous dans les données qui rendaient le signal totalement désynchronisé.

Si l'on se souvient des recherches précédentes, le lecteur était censé tourner à 300 tours par minute, mais il se peut qu'il ne tourne pas exactement à 300 tours par minute, et que le lecteur qui a écrit les données ne tourne pas non plus exactement à 300 tours par minute. Ensuite, il y a l'espacement entre les secteurs et les écarts entre les secteurs. Il est clair qu'il y avait un problème de synchronisation, et synchroniser le signal de 500 kHz au lecteur de disquette au début d'une lecture n'allait pas fonctionner.

J'ai également découvert que l'impulsion du lecteur de disquette était extrêmement courte, bien que vous puissiez modifier cela en changeant la résistance de rappel, et si la synchronisation n'était pas exactement correcte, l'Arduino pouvait manquer une impulsion.

Quand j'étais à l'université (Université de Leicester), j'ai suivi un module appelé Système Embarqué. Nous avons étudié les microcontrôleurs Atmel 8051. L'un des projets consistait à compter les impulsions d'une station météo simulée (codeur rotatif). A l'époque, j'échantillonnais la broche à intervalles réguliers, mais ce n'était pas très précis.

Le professeur Pont, chargé des cours de ce module, avait suggéré que j'aurais dû utiliser les fonctions de compteur matériel de l'appareil (je ne le savais même pas à l'époque).

J'ai vérifié la fiche technique de l'ATMega328 et, bien sûr, chacun des trois chronomètres pouvait être configuré pour compter les impulsions déclenchées par une entrée externe. La vitesse n'était donc plus un problème. Tout ce dont j'avais besoin était de savoir si une impulsion s'était produite dans une fenêtre de temps de deux microsecondes.

Tentative 3

J'ai ajusté le circuit Arduino pour réinitialiser le chronomètre 500 kHz lorsque la première impulsion était détectée et à chaque fois que le chronomètre 500 kHz était dépassé, je vérifiais la valeur du compteur pour voir si une impulsion avait été détectée. J'ai ensuite effectué la même séquence de décalage de bits et tous les 8 bits, j'ai écrit un octet sur l'USART.

Les données arrivaient et j'ai commencé à les analyser sur le PC. Dans les données, j'ai commencé à voir ce qui ressemblait à des données valides. Un mot de synchronisation bizarre apparaissait, ou des groupes de séquences 0xAAAA, mais rien de fiable. Je savais que j'étais sur une bonne voie, mais il me manquait encore quelque chose.

Tentative 4

J'ai réalisé qu'au fur et à mesure que les données étaient lues, les données du lecteur étaient probablement déphasées par rapport à mon signal de 500 kHz. Je l'ai confirmé en lisant 20 octets à chaque fois que je débutais la lecture.

En cherchant sur la façon de gérer ce problème de synchronisation, je suis tombé sur l'expression "boucle à verrouillage de phase" ou BVP ("PLL" en anglais). En termes très simples, pour ce que nous faisons, la boucle à verrouillage de phase ajuste dynamiquement la fréquence de l'horloge (les 500 kHz) pour compenser la dérive de fréquence et la variance du signal.

La résolution du chronomètre n'était pas assez élevée pour la faire varier par de petites quantités (par exemple : 444 kHz, 470 kHz, 500 kHz, 533 kHz, 571 kHz, etc.) et pour réaliser cela correctement, j'aurais probablement besoin que le code tourne beaucoup plus vite.

Les chronomètres de l'Arduino fonctionnent en comptant jusqu'à un nombre prédéfini (dans ce cas 16 pour 500 kHz) puis ils mettent un registre de débordement et recommencent à partir de 0. La valeur actuelle du compteur peut être lue et écrite à tout moment.

J'ai ajusté le circuit pour qu'il attende dans une boucle jusqu'à ce que le chronomètre déborde, et quand il a débordé, j'ai vérifié pour une impulsion comme avant. La différence cette fois-ci est que lorsqu'une impulsion est détectée dans la boucle, je réinitialise la valeur du compteur du chronomètre à une position de phase prédéfinie, resynchronisant ainsi le chronomètre à chaque impulsion.

J'ai choisi la valeur que j'ai écrite au compteur du chronomètre de façon à ce qu'il déborde à une microseconde de l'impulsion de détection (à mi-chemin) de sorte que la prochaine fois que le chronomètre débordera, l'impulsion aura été espacée de deux microsecondes.

Et cela a fonctionné ! Je pouvais maintenant lire des données presque parfaites sur la disquette. Je recevais encore beaucoup d'erreurs de somme de contrôle, ce qui était ennuyeux. J'ai résolu la plupart d'entre elles en relisant continuellement la même piste sur la disquette jusqu'à ce que j'aie les onze secteurs avec des sommes de contrôle d'en-tête et de données valides.

J'étais intrigué à ce stade, alors j'ai rebranché le tout à l'oscilloscope pour voir ce qu'il se passait maintenant, et comme je l'avais deviné, je pouvais maintenant voir les deux tracés car elles étaient synchronisées l'une avec l'autre :

tracés

J'aimerais bien y voir un peu plus clair, si quelqu'un veut me faire don d'un bel oscilloscope numérique haut de gamme (par exemple un de ces Keysight !), j'apprécierais vraiment !

Tentative 5

Je me suis demandé si je pouvais l'améliorer. En regardant le code, en particulier la boucle de lecture interne (voir ci-dessous), j'avais une boucle "while" qui attendait le débordement et ensuite un "if" interne qui cherchait une impulsion pour se synchroniser.

Voici une partie du code utilisé pour lire les données et se synchroniser avec elles :

register bool done = false;

// Wait for 500khz overflow 
while (!(TIFR2&_BV(TOV2))) {   
    // falling edge detected while waiting for the 500khz pulse.  
    if ((TCNT0) && (!done)) {
        // pulse detected, reset the timer counter to sync with the pulse
        TCNT2=phase;     

        // Wait for the pulse to go high again
        while (!(PIN_RAW_FLOPPYDATA_PORT & PIN_RAW_FLOPPYDATA_MASK)) {};
        
        done = true;
    }
}

// Reset the overflow flag
TIFR2|=_BV(TOV2); 
                      
// Did we detect a pulse from the drive?
if (TCNT0) {                            
    DataOutputByte|=1;
    TCNT0=0;
}

Je me suis rendu compte que selon l'instruction exécutée dans les boucles ci-dessus, le temps entre la détection de l'impulsion et l'écriture de "TCNT2=phase;" pouvait changer en fonction du temps pris pour exécuter quelques instructions.

Réalisant que cela pouvait causer des erreurs ou des fluctuations dans les données et qu'avec la boucle ci-dessus, il était possible que je manque l'impulsion du lecteur (manquant ainsi un bit de resynchronisation), j'ai décidé de prendre l'astuce d'une de mes tentatives précédentes, l'ISR (interruption).

J'ai câblé l'impulsion de données à une deuxième broche sur l'Arduino. Les données étaient maintenant connectées au déclencheur Counter0 et maintenant aussi à la broche INT0. INT0 est l'une des plus hautes priorités d'interruption et devrait donc minimiser les délais entre le déclenchement et l'appel de l'ISR, et comme c'est la seule interruption qui m'intéresse, toutes les autres sont désactivées.

Tout ce que l'interruption devait faire était d'exécuter le code de resynchronisation ci-dessus, ce qui a changé le code pour ressembler à ceci :

// Wait for 500khz overflow 
while (!(TIFR2&_BV(TOV2))) {}   

// Reset the overflow flag
TIFR2|=_BV(TOV2);                       

// Did we detect a pulse from the drive?
if (TCNT0) {                            
    DataOutputByte|=1;
    TCNT0=0;
}

L'ISR ressemblait à ceci (notez que je n'ai pas utilisé "attachInterrupt" car cela ajoute aussi une surcharge à l'appel) :

volatile byte targetPhase;

ISR (INT0_vect) {
  TCNT2=targetPhase;
}

Mais la compilation produisait beaucoup trop de code pour être exécuté suffisamment rapidement. En fait, en désassemblant ce qui précède, on obtient :

push    r1
push    r0
in      r0,     0x3f	; 63
push    r0
eor     r1,     r1
push    r24 
lds     r24,    0x0102	; 0x800102 
sts     0x00B2, r24	; 0x8000b2 <__TEXT_REGION_LENGTH__+0x7e00b2>
pop     r24
pop     r0
out     0x3f,   r0	; 63
pop     r0
pop     r1
reti

En analysant le code, j'ai réalisé qu'il n'y avait que quelques instructions dont j'avais réellement besoin. Notant que le compilateur garderait la trace de tous les registres que j'aurais tapés, j'ai modifié l'ISR comme suit :

volatile byte targetPhase asm ("targetPhase");

ISR (INT0_vect) {
  asm volatile("lds __tmp_reg__, targetPhase");      
  asm volatile("sts %0, __tmp_reg__" : : "M" (_SFR_MEM_ADDR(TCNT2)));
}

Ce qui, désassemblé, a produit les instructions suivantes :

push	r1
push	r0
in	r0, 0x3f	; 63
push	r0
eor	r1, r1
lds	r0, 0x0102	; 0x800102 
sts	0x00B2, r0	; 0x8000b2 <__TEXT_REGION_LENGTH__+0x7e00b2>
pop	r0
out	0x3f, r0	; 63
pop	r0
pop	r1
reti

Il y a toujours trop d'instructions. J'ai remarqué que le compilateur ajoutait beaucoup d'instructions supplémentaires qui, pour mon application, n'avaient pas vraiment besoin d'être présentes. Je me suis donc renseigné sur l'ISR() et je suis tombé sur un deuxième paramètre ISR_NAKED. L'ajout de ce paramètre empêcherait le compilateur d'ajouter du code spécial, mais je serais alors responsable de la gestion des registres, de la pile et du retour correct de l'interruption. Je devais également maintenir le registre SREG, mais comme aucune des commandes que je devais appeler ne le modifiait, je n'avais pas besoin de m'en préoccuper.

Cela a changé le code de l'ISR pour devenir :

ISR (INT0_vect, ISR_NAKED) {
  asm volatile("push __tmp_reg__");                                   // Preserve the tmp_register
  asm volatile("lds __tmp_reg__, targetPhase");                       // Copy the phase value into the tmp_register
  asm volatile("sts %0, __tmp_reg__" : : "M" (_SFR_MEM_ADDR(TCNT2))); // Copy the tmp_register into the mem location where TCNT2 is
  asm volatile("pop __tmp_reg__");                                    // Restore the tmp_register
  asm volatile("reti");                                               // And exit the ISR
}

Ce que le compilateur a converti en :

push	r0
lds	r0, 0x0102	; 0x800102 
sts	0x00B2, r0	; 0x8000b2 <__TEXT_REGION_LENGTH__+0x7e00b2>
pop	r0
reti

Cinq instructions ! Parfait, ou du moins aussi rapide que cela devait l'être, prenant théoriquement 0,3125 microseconde pour s'exécuter ! Cela devrait maintenant signifier que la resynchronisation devrait se produire à des périodes précises après l'impulsion. Vous trouverez ci-dessous un diagramme temporel de ce qu'il se passe. C'est ainsi que l'on récupère les données d'un flux de données série qui n'a pas de signal d'horloge :

synchronisations

Cela a un peu amélioré les résultats. Ce n'est toujours pas parfait. Certaines disquettes se lisent parfaitement à chaque fois, d'autres prennent un temps fou et il faut sans cesse réessayer. Je ne sais pas si c'est parce que certaines disquettes sont restées très longtemps mal stockées, et que le magnétisme s'est dégradé à un niveau tellement faible que les amplificateurs des lecteurs ne peuvent pas le supporter. Je me suis demandé si cela n'avait pas quelque chose à voir avec le lecteur de disquette du PC, alors je l'ai connecté à un lecteur de disquette externe pour Amiga que j'avais, mais les résultats étaient identiques.

Tentative 6

Je me suis demandé s'il y avait autre chose à faire. Peut-être que le signal du lecteur était plus bruyant que je ne le pensais. Après avoir lu d'autres informations, j'ai découvert qu'une résistance de rappel de 1 kiloohm était la norme, alimentée par une bascule de Schmitt.

Après avoir installé une bascule de Schmitt hexagonale SN74HCT14N et reconfiguré le circuit pour qu'il se déclenche sur les fronts montants plutôt que sur les fronts descendants, j'ai fait un essai, mais cela n'a pas vraiment fait de différence notable. Je suppose que comme je cherchais une ou plusieurs impulsions à chaque fois, cela a probablement absorbé tout le bruit de toute façon. Nous allons donc nous en tenir à la méthode de la tentative 5 !

Ma solution finale sur la platine d'expérimentation ressemblait à ceci :

platine d'expérimentation

Notez que le câblage ci-dessus est légèrement différent de celui du croquis. J'ai réorganisé certaines des broches de l'Arduino pour rendre le schéma de circuit plus simple.

Tentative 7

J'étais un peu insatisfait de certaines disquettes que je n'avais pas pu lire. Parfois, les disquettes ne s'inséraient pas correctement dans le lecteur. Je suppose que le ressort de l'obturateur n'aidait pas.

J'ai commencé à chercher à détecter s'il y avait des erreurs dans les données MFM reçues de la disquette.

A partir des règles de fonctionnement du codage MFM, je me suis rendu compte que quelques règles simples pouvaient être appliquées comme suit :
  • Il ne peut y avoir deux bits "1" côte à côte.
  • Il ne peut y avoir plus de trois bits "0" côte à côte.
Tout d'abord, lorsque je décodais des données MFM, je regardais s'il y avait deux "1" à la suite. Si c'était le cas, je supposais que les données étaient devenues un peu floues avec le temps et j'ignorais le deuxième "1".

Avec cette règle, il y a littéralement trois situations de cinq bits où des erreurs peuvent se produire. C'est un nouveau domaine dans lequel je pourrais chercher à améliorer les données.

J'ai surtout été surpris par le fait qu'il n'y avait pas beaucoup d'erreurs MFM détectées. Je ne comprends pas très bien pourquoi certaines disquettes ne sont pas lues alors qu'aucune erreur n'a été détectée.

C'est une question à approfondir.

Décodage

Après avoir étudié le fonctionnement de la MFM, je n'étais pas tout à fait sûr de la manière dont elle s'alignait correctement.

Au début, je pensais que le lecteur émettait des "1" et des "0" pour les bits d'activation et de désactivation. Ce n'était pas le cas. Le variateur émet une impulsion pour chaque transition de phase, c'est-à-dire chaque fois que les données passent de 0 à 1 ou de 1 à 0.

Après avoir lu cela, je me suis demandé si je devais reconvertir les données en 1 et 0 en les envoyant à une bascule, ou lire les données, rechercher les secteurs, et si aucun n'était trouvé, inverser les données et réessayer !

Il s'avère que ce n'est pas le cas et que c'est beaucoup plus simple. Les impulsions sont en fait les données RAW MFM et peuvent être introduites directement dans les algorithmes de décodage. Maintenant que j'ai compris cela, j'ai commencé à écrire du code pour scruter un tampon du lecteur et rechercher le mot de synchronisation 0x4489. Étonnamment, je l'ai trouvé !

D'après les recherches que j'ai effectuées, j'ai réalisé que je devais en fait chercher 0xAAAAAA44894489. Une note a également suggéré qu'il y avait des bogues dans le code des premiers Amiga qui signifiaient que la séquence ci-dessus n'était pas trouvée. J'ai donc cherché 0x2AAAAAAA44894489 après avoir fait un "AND" (ET) entre les données et 0x7FFFFFFFFFFFFFFF.

Comme prévu, j'en ai trouvé jusqu'à onze sur chaque piste, correspondant au début réel des onze secteurs Amiga. J'ai ensuite commencé à lire les octets qui suivaient pour voir si je pouvais décoder les informations du secteur.

J'ai pris un bout de code d'une des références ci-dessus pour aider au décodage MFM. Pas la peine de réinventer la roue, hein ?

Après avoir lu l'en-tête et les données, j'ai essayé de les écrire sur la disquette comme un fichier ADF. Le format de fichier ADF standard est très simple. C'est littéralement juste les 512 octets de chaque secteur (des deux côtés de la disquette) écrits dans l'ordre. Après l'avoir écrit, j'ai essayé de l'ouvrir avec ADFOpus et j'ai obtenu des résultats mitigés, parfois il ouvrait le fichier, parfois il échouait. Il y avait manifestement des erreurs dans les données. J'ai commencé à regarder les champs de somme de contrôle dans l'en-tête, rejetant les secteurs avec des sommes de contrôle invalides et répétant la lecture jusqu'à ce que j'en ai onze valides.

Pour certaines disquettes, il s'agissait des onze lors de la première lecture, d'autres ont nécessité plusieurs tentatives et des valeurs de phase différentes également.

Finalement, j'ai réussi à écrire des fichiers ADF valides. Certaines disquettes prenaient des heures, alors que d'autres étaient écrites littéralement à la vitesse que l'Amiga les aurait lues. N'ayant plus d'Amiga en état de marche, je n'ai pas pu vérifier si ces disquettes se lisaient correctement, elles ont été stockées dans une boîte dans le grenier pendant des années et peuvent donc s'être dégradées.

Écriture de données - tentative 1

Après avoir réussi à lire des disquettes, je me suis dit que si l'on voulait conserver le support physique d'origine, il fallait pouvoir réécrire des disquettes. Je me suis dit que j'allais procéder à l'inverse, en commençant par le logiciel (c'est-à-dire convertir les fichiers de la disquette ADF en données MFM pour que l'interface puisse les écrire).

J'ai donc commencé par ajouter des classes pour lire une disquette ADF, et coder tous les secteurs en tant que piste unique. Sachant que je pouvais potentiellement tester les données que je créais en les renvoyant dans la partie décodage, j'ai commencé à travailler dessus. Pendant que je travaillais sur ce projet, j'ai décidé d'essayer de trouver ce qui n'allait pas avec mon Amiga. Après tout, je ne peux pas tester les disquettes que je crée si je n'ai rien de réel sur lequel les tester.

En démontant mon A500+, j'ai remarqué qu'il avait souffert d'un des problèmes les plus courants, la batterie de l'horloge avait coulé. J'ai donc dessoudé la batterie de la carte et j'ai commencé à nettoyer la carte. Pendant que j'y étais, j'ai sorti toute la machine et j'ai commencé à nettoyer 20 ans de poussière et de saleté. J'ai même démonté le lecteur de disquette pour le nettoyer.

En le nettoyant, j'ai décidé qu'il était temps d'effacer le jaunissement, j'ai donc suivi les informations sur Retr0brite et je l'ai essayé.

J'ai ensuite vérifié tous les joints de la carte mère principale et j'ai trouvé une connexion perdue près du connecteur d'alimentation, quelques retouches avec le fer à souder et tout était comme neuf. J'ai attendu d'être satisfait du processus Retr0brite avant de réassembler l'ordinateur.

Pendant ce temps, j'ai continué à travailler sur le code d'écriture des disquettes. Je voulais lire l'état de la ligne de protection en écriture, mais peu importe ce que j'y mettais, cela ne semblait pas changer la tension. J'ai donc démonté le lecteur et suivi les tracés des petits interrupteurs qui détectent l'état de la protection en écriture jusqu'à un petit circuit intégré. A ce stade, j'ai supposé que la sortie n'est probablement disponible que lorsque vous voulez réellement écrire des données.

Après de nombreuses expérimentations, j'ai découvert qu'il fallait tirer la broche /WRITE_GATE vers "LOW" (BAS) avant de mettre le lecteur en marche pour permettre l'écriture. A ce stade, vous pouvez obtenir le statut de protection en écriture. J'ai également remarqué que lorsque la broche /WRITE_GATE était sur "LOW" (BAS), le lecteur ne s'éteignait pas comme il le faisait jusqu'à ce que cette broche revienne à son état "HIGH" (HAUT) par défaut.

L'Amiga pouvait écrire une piste entière en une seule fois. Une piste en mémoire est de 11x512 octets (5638 octets), cependant, après codage MFM et mise au format AmigaDOS correct, la piste est de 14 848 octets. Eh bien, il n'y a aucune chance que cela puisse tenir dans les 2 ko de mémoire de l'Arduino, ni dans ses 1 ko d'EEPROM. J'avais besoin d'une méthode alternative.

J'ai décidé d'essayer d'envoyer les données octet par octet dans un fil d'exécution de haute priorité et d'attendre un octet de réponse de l'Arduino avant d'envoyer le suivant. J'ai changé le débit en bauds à 2 Mbauds pour réduire le décalage entre les caractères. Cela signifie qu'il fallait environ 5,5 microsecondes pour envoyer chaque caractère, et 5,5 microsecondes pour en recevoir un en retour. L'Arduino aurait besoin d'écrire 8 bits, à 500 kHz, et donc d'un nouvel octet tous les 16 microsecondes. Il devrait donc avoir le temps, en supposant que la boucle de code soit suffisamment serrée et que le système d'exploitation ne retarde pas trop l'envoi et la réception.

Ce fut un échec total. L'ensemble du cycle de lecture/écriture prenait beaucoup trop de temps, bien au-delà d'une rotation complète de la disquette. Le côté Arduino était probablement assez rapide, mais le système d'exploitation n'était pas assez réactif. La lecture des disquettes fonctionne parce que le système d'exploitation (Windows dans mon cas) mettait en mémoire tampon les données entrantes, mais pour l'écriture, Windows envoyait tout en une seule fois, mais comme le taux d'envoi est beaucoup plus rapide que ce dont l'Arduino a besoin, les données étaient perdues. C'est pourquoi j'ai opté pour ce processus d'accusé de réception bidirectionnel.

Écriture de données - tentative 2

Le contrôle de flux logiciel pour cette application n'était tout simplement pas assez rapide. J'ai décidé d'étudier le contrôle de flux matériel. J'ai remarqué que sur la carte FTDI, il y a des broches CTS et DTR. Elles signifient Clear To Send et Data Terminal Ready. J'ai remarqué que lorsque la carte de dérivation était connectée, la carte Arduino connectait le CTS à GND.

FTDI
La carte FTDI de dérivation avec la broche CTS pliée et un fil attaché

Je ne savais pas non plus dans quelle direction ces broches allaient réellement, mais après quelques expérimentations, j'ai découvert que la broche CTS pouvait être signalée par l'Arduino et utilisée par le PC pour contrôler le flux. Normalement, cela se fait en utilisant un tampon circulaire, mais dans mon cas, je ne pouvais pas le permettre, donc je l'ai simplement mis à "1" quand je ne veux pas de données, et "0" quand j'en veux.

platine d'expérimentation
La carte FTDI de dérivation connectée à l'Arduino sur la platine d'expérimentation

Cela signifie que je pouvais demander au système d'exploitation d'envoyer les octets en une seule fois, en espérant que tout soit géré au niveau du noyau et qu'il ne soit pas interrompu.

J'avais une boucle interne qui sortait chaque bit des 8 bits, mais j'ai décidé qu'il était probablement plus judicieux, du point de vue de la synchronisation, de la décomposer en huit jeux de commandes à la place.

Cela n'a pas fonctionné. Si je laissais le code s'exécuter sans exécuter la partie écriture sur la disquette, tous les octets étaient reçus correctement, mais pas si j'exécutais le code, et les octets reçus étaient perdus.

Je soupçonnais que le changement d'état de la ligne CTX n'arrêtait pas instantanément le flux de données et que l'ordinateur pouvait encore envoyer un caractère ou deux. Il est possible qu'au moment où j'ai signalé la ligne CTX, l'ordinateur était déjà en train d'envoyer le caractère suivant.

Écriture de données - tentative 3

Je ne voulais pas avoir d'interruption série car je ne voulais pas que les temps d'écriture soient faussés. J'ai réalisé qu'entre l'écriture de chaque bit sur le lecteur de disquette, il y aurait un certain nombre de cycles du processeur dans la boucle "while" suivante. J'ai décidé de vérifier entre chaque écriture de bit si un autre octet avait été reçu depuis que CTX était à l'état "HIGH" (HAUT) et de le stocker.

Ma théorie était que lorsque vous avez CTX à l'état "HIGH" (HAUT), l'ordinateur était probablement déjà en train de transmettre le prochain octet et comme vous ne pouvez pas l'arrêter au milieu de la transmission, il serait à la moitié après celui-ci. Cela signifie que je n'ai besoin de vérifier qu'un seul octet supplémentaire pendant la boucle et de l'utiliser s'il est trouvé au lieu de regarder à nouveau le port série.

Cela semble donc fonctionner et l'Arduino a terminé l'écriture sans perdre aucune donnée de l'ordinateur. La seule question qui se pose maintenant est de savoir s'il a réellement écrit des données, et si oui, si elles sont valides !

A ce stade, je n'avais codé qu'une seule piste, j'ai donc décidé de lancer l'algorithme complet pour coder les 80 pistes. Quelque chose d'étrange se produisait. La tête du lecteur ne bougeait plus du tout. Elle bougeait toujours en lecture, mais pas en écriture.

J'ai découvert que pour déplacer la tête du lecteur d'avant en arrière, il fallait d'abord lever la broche /WRITE GATE, je soupçonnais que cela était également nécessaire pour changer la surface. Une fois que j'ai ajouté le code pour faire cela, la tête du lecteur s'est déplacée comme prévu. Cela était logique et permettait d'éviter l'écriture accidentelle de pistes lors du déplacement de la tête.

A ce stade, j'ai écrit une image-disque que j'avais créée précédemment, puis j'ai essayé de la relire. Rien ne pouvait être détecté. Soit les données que j'avais écrites n'étaient pas valides, soit la façon dont je les écrivais était mauvaise.

J'ai décidé d'introduire les données de secteur MFM codées, que je créais dans mon algorithme de décodage de secteur utilisé par le lecteur, pour valider que ce que je générais était correct et valide, et c'était le cas. Il y avait manifestement un problème avec la façon dont j'écrivais les données sur la disquette.

Écriture des données - tentative 4

Comme aucune donnée n'était lue correctement, j'ai décidé d'essayer quelques approches différentes. Je ne savais pas si la broche /WRITE DATA devait être impulsée (et si oui, pendant combien de temps), basculée ou simplement réglée sur la valeur brute des données. Mon implémentation actuelle envoie des impulsions à la broche. Je n'ai pas pu trouver d'informations sur Internet sur la façon dont la broche d'écriture est physiquement censée être manipulée lors de l'écriture.

La tête de lecture nous envoie une impulsion à chaque fois qu'il y a une inversion de flux. J'ai décidé de modifier l'implémentation de sorte que /WRITE DATA soit simplement réglée sur la valeur du bit. Cela n'a pas fonctionné non plus. J'ai donc modifié le code pour basculer l'état actuel de la broche. Toujours aucun résultat.

Il est clair que l'une de ces approches devait être la bonne. J'ai donc décidé de ressortir mon fidèle oscilloscope pour voir ce qu'il se passait. J'ai décidé d'écrire le motif MFM 0xAA sur chaque octet d'une piste en continu. 0xAA en binaire est B10101010, donc cela me donnerait une onde carrée parfaite que je pourrais surveiller pour la fréquence requise.

Si je ne voyais pas d'onde carrée parfaite à la fréquence souhaitée, je savais qu'il devait y avoir un problème de synchronisation.

J'ai branché l'oscilloscope, mais j'ai été surpris de voir que la synchronisation était parfaite. Cependant, comme il s'agit d'un vieil oscilloscope, je ne pouvais pas voir plus que quelques impulsions. L'oscilloscope avait ce merveilleux mode "mag" x10. Lorsqu'on appuie dessus, cela augmente la base de temps de 10, mais plus important encore, cela permet de faire défiler toutes les données comme sur un oscilloscope numérique moderne.

Quelque chose n'était pas correct ici. Il semblait que tous les 12 bits environ, je me retrouvais avec une période de "HIGH" (HAUT).

scopes

Soit les données que j'envoyais n'étaient pas valides, soit il y avait quelque chose qui provoquait une pause dans le processus d'écriture tous les 12 bits environ. Douze étant un nombre étrange considérant qu'il n'y a que 8 bits dans un octet.

Après avoir réfléchi à cela, je me suis demandé si je n'avais pas un problème de contrôle de flux. La façon dont j'avais conçu la boucle était de récupérer tout octet supplémentaire reçu après que nous ayons attendu un octet. Mais ce n'était pas assez malin pour empêcher l'attente d'un octet sur deux. J'avais deux choix, déplacer quelque chose dans une interruption, ou corriger la boucle.

J'ai décidé d'essayer de corriger le fonctionnement de la boucle en premier. Le problème était dû à un délai causé par l'attente de l'octet suivant de l'ordinateur. Si nous abaissions CTX et attendions un octet, le temps que nous rehaussions CTX, un autre octet était déjà en route.

J'ai modifié la boucle de sorte que lorsque le deuxième octet reçu avait été utilisé, l'Arduino tirait momentanément CTS vers le bas puis vers le haut à nouveau pour permettre l'envoi d'un autre caractère. Cela signifie que lors de la prochaine boucle, nous aurons déjà reçu le prochain octet et qu'aucune attente ne sera nécessaire.

Le test a produit une onde carrée parfaite :

scope

Cela signifie que toute la synchronisation pour écrire une piste était parfaite, il ne restait plus qu'à écrire les données réelles. J'ai décidé de laisser fonctionner le système pour quelques pistes et faces, puis de le relire pour voir s'il avait écrit correctement. Je réglais la broche /WRITE DATA sur la valeur binaire correspondante des données reçues.

Lorsque j'ai relu les données, il semblait que rien n'avait été codé, mais je suis ensuite passé à l'autre face de la disquette. Bien sûr, il y avait mon motif. Je ne savais pas pourquoi il n'avait écrit que sur une face de la disquette.

Après avoir réfléchi, je me suis demandé si la broche /WRITE GATE ne fonctionnait pas comme je le pensais. Il m'est apparu qu'en tirant la broche vers le bas, cela pouvait activer la tête d'effacement du lecteur. Si c'était le cas, je ne devais le faire que lorsque j'écrivais, sinon je risquais d'avoir du bruit sur la disquette lorsqu'elle tournait et effaçait.

J'ai modifié l'ensemble du code pour que /WRITE GATE ne soit utilisée que lors du premier démarrage du lecteur, puis uniquement pendant la boucle d'écriture. Cela a fonctionné ! J'écrivais maintenant des données sur les deux faces de la disquette !

J'ai donc réessayé avec une image-disque ADF réelle et j'ai attendu que l'opération se termine. J'ai ensuite utilisé la partie lecteur pour voir si je pouvais la relire. Cela a fonctionné ! Mais pour une raison inconnue, il a fallu un certain temps pour relire cette disquette. Je n'ai pas eu d'erreurs MFM mais il a eu du mal à trouver tous les secteurs.

Il y avait deux possibilités pour moi maintenant. Premièrement, les données avaient-elles été écrites assez rapidement, et deuxièmement, la disquette fonctionnerait-elle dans un vrai Amiga ?

Trop impatient à l'idée que j'avais peut-être écrit une disquette, j'ai démarré l'A500+ qui était à présent fonctionnel et j'ai inséré la disquette. Quelques instants plus tard, la disquette a démarré, puis a affiché le fameux message d'erreur de somme de contrôle. J'écrivais donc quelque chose de valide, mais ce n'était pas cohérent.

J'ai décidé qu'à moins de pouvoir relire les données à un rythme beaucoup plus précis, l'écriture d'une disquette était inutile.

Lecture des données (à nouveau)

Je voulais améliorer la qualité de la lecture car je n'étais pas satisfait de l'implémentation actuelle. L'implémentation actuelle ne permettait pas assez de flexibilité pour que les impulsions arrivent à des moments légèrement différents. J'avais besoin d'une nouvelle approche.

Tout d'abord, j'ai décidé que j'allais synchroniser la lecture avec l'impulsion /INDEX. Ce n'est pas requis par l'Amiga, mais cela peut s'avérer utile plus tard pour tester l'écriture et la lecture.

Plusieurs personnes dans les commentaires de la première moitié de ce projet ont suggéré que je devrais enregistrer la synchronisation entre les impulsions plutôt que d'utiliser la méthode que j'avais implémentée. Le seul problème était de transmettre ces données au PC assez rapidement. Si je devais envoyer un octet pour chaque bit, je pourrais facilement dépasser le maximum de 2 Mbauds.

J'ai décidé que la meilleure chose à faire serait d'essayer de donner un peu de sens à ces données. J'ai donc décidé de laisser le compteur que j'utilisais à l'origine tourner librement, jusqu'à 255. J'ai ensuite placé le code dans une boucle en attendant une impulsion et à ce moment-là, j'ai vu combien de temps s'était écoulé.

Dans une situation idéale, la valeur minimale serait de 32 (correspondant à deux microsecondes). Avec la MFM, il ne peut y avoir qu'un maximum de trois "0" à la suite, donc le maximum que cette valeur devrait atteindre est 128. Cela signifie qu'il y avait un maximum de quatre combinaisons possibles.

J'ai échantillonné plusieurs disquettes pour voir où se trouvait la majorité de ces fréquences, et les résultats sont présentés ci-dessous :

graph

En regardant ceci, je trouve la majorité des points autour d'un compteur de 52, 89 et 120. Cependant, ces valeurs étaient quelque peu spécifiques à ma disquette et ne constituaient donc pas une bonne ligne directrice. Après quelques expérimentations, j'ai utilisé la formule suivante : valeur=(COMPTEUR-16)/32. Lorsqu'elle est coupée entre 0 et 3, cela me donne la sortie dont j'ai besoin. A chaque fois que j'en utilisais 4, je pouvais écrire un octet.

Il m'est apparu qu'étant donné qu'il est impossible d'avoir deux "1" ensemble dans un flux de bits codés MFM, je pouvais supposer sans risque que la première valeur n'était pas valide et pouvait être traitée comme une autre séquence "01". La partie suivante consistait à décompresser ces données une fois reçues par le PC et à les retransformer en données MFM. C'était simple, puisque "00" ne pouvait pas se produire, un "01" signifiait écrire "01", un "10" signifiait écrire "001" et un "11" signifiait écrire "0001". J'ai fait un essai et à ma grande surprise, mes résultats étaient 100% satisfaisants. J'ai essayé avec quelques autres disquettes aussi : résultat de 100% ! J'avais maintenant un lecteur de disquette très fiable.

Cette nouvelle approche étant beaucoup plus tolérante aux données de la disquette, je n'avais plus besoin d'analyse de phase ou d'autant de tentatives. La plupart de mes disquettes sont maintenant parfaitement lues. Certaines ont nécessité quelques tentatives, mais elles ont fini par fonctionner. La dernière partie consistait à analyser statistiquement les données et à voir si elles pouvaient être réparées, mais dans 99% des cas, les données erronées étaient totalement méconnaissables et n'étaient donc d'aucune utilité.

Écriture des données - tentative 5

Maintenant que je pouvais vérifier ce que j'avais écrit avec une grande précision, cela signifiait que tester le code d'écriture serait beaucoup plus facile.

J'ai commencé à analyser le code pour voir ce qui n'allait pas. J'ai écrit une séquence 0x55 sur une piste entière, puis je l'ai relue. De temps en temps, un bit était décalé dans les données qui revenaient, ce qui signifiait qu'il y avait une sorte de problème de synchronisation dans l'écriture.

Il s'est avéré que cela était en partie dû à la façon dont je manipulais le port série, et en partie dû à l'utilisation du chronomètre. J'attendais que le chronomètre atteigne la valeur 32, j'écrivais le bit, puis je le réinitialisais. Je l'ai changé pour ne pas avoir à modifier la valeur du compteur du chronomètre.

J'écrivais le premier bit quand le compteur atteignait 16, puis le suivant quand il atteignait 48 (16+32), et le suivant quand il atteignait 80 (16+32+32) et ainsi de suite. Le Timer2 n'étant que de 8 bits, il revient à zéro après le 8e bit, exactement quand nous en avons besoin. Cela signifie que tant que nous écrivons le bit à la valeur requise du chronomètre, nous sommes exactement à 500 ko/s.

J'ai aussi regardé comment je lisais les données sur le port série. Elles étaient lues entre chaque bit, mais elles devaient être aussi courtes que possible. Après un peu d'expérimentation, j'ai obtenu le bloc le plus court possible.

Après avoir modifié le code Windows pour gérer la vérification, j'étais prêt à réessayer. Cette fois, je savais que si la vérification de la disquette se faisait correctement, elle devait fonctionner correctement sur l'Amiga.

J'ai donc essayé d'écrire une autre disquette. Avec la vérification, cela a pris plus de temps. Avec le nouvel algorithme, environ 95% des pistes ont passé la vérification du premier coup, et seules les 5% restantes ont dû être réécrites une nouvelle fois. J'étais satisfait de ce résultat et j'ai inséré la disquette dans l'Amiga. Et elle a parfaitement fonctionné !

Écriture de données - tentative 6

Après quelques commentaires de la part de certaines personnes qui ont utilisé ce système, il est apparu clairement que même avec la vérification du lecteur, celui-ci ne produisait pas toujours des disquettes parfaitement lisibles. Le logiciel pouvait les relire parfaitement, mais les ordinateurs Amiga signalaient quelques erreurs de somme de contrôle ici et là.

J'ai jeté un autre coup d'oeil au code, je me suis demandé si ce n'était pas un problème de synchronisation et j'ai cherché à voir s'il ne pouvait pas être piloté par des interruptions, mais malheureusement avec la petite quantité de temps entre chaque bit, il n'y a tout simplement pas assez de temps avec les interruptions pour y parvenir tout en préservant les registres que vous modifiez, etc.

Je me suis ensuite penché sur le code de l'écriture. Il y a une petite chance qu'après l'écriture d'un octet complet, le code ait pu faire une boucle pour commencer à écrire l'octet suivant avant que le chronomètre n'ait débordé à 0, permettant au premier bit d'être écrit plus tôt.

J'ai ajouté une petite boucle pour m'assurer que cela ne pouvait pas se produire, ce qui, je l'espère, résoudra le problème pour tous ceux qui ont ce problème.

Diagnostics

J'ai reçu de nombreux commentaires de personnes qui ont réalisé ce projet avec succès, qu'il fonctionne ou non. J'ai décidé d'intégrer un module de diagnostic dans le code pour aider ceux qui ne parviennent pas à faire fonctionner le leur.

L'option de diagnostic consiste en quelques commandes supplémentaires à traiter par l'Arduino ainsi que toute une série d'événements qui sont exécutés pour s'assurer que tout est câblé correctement.

Écriture des données - tentative 7

Après avoir reçu de nombreux rapports d'erreurs concernant la somme de contrôle pour les disquettes écrites, j'ai commencé à investiguer. J'ai d'abord pensé que j'allais devoir examiner les données MFM de la disquette, mais le problème était en fait beaucoup plus simple...

En regardant X-Copy Pro pour voir les erreurs de somme de contrôle, il a signalé les codes 4 et 6 signifiant des erreurs de somme de contrôle dans les en-têtes de secteur et les zones de données. Si cela n'avait été que la zone de données, j'aurais supposé qu'il s'agissait uniquement de l'écriture des derniers bits de la piste, mais ce n'était pas le cas.

J'ai commencé à regarder le code d'écriture et le bloc que j'avais autour de chaque piste, me demandant si je n'écrasais pas le début d'une piste de temps en temps, alors j'ai réduit massivement le bloc post-piste de 256 octets à 8. À ma grande surprise, ma vérification a alors donné lieu à une tonne d'erreurs.

Cela m'a fait me demander si le problème réel n'était pas que je n'écrivais pas assez de données. J'ai commencé à ajouter une commande d'effacement de piste à l'Arduino qui écrirait le motif 0xAA sur toute la piste et écrirait ensuite ma piste. A ma grande surprise, X-Copy l'a approuvé à 100%. J'espère donc avoir résolu ce problème.

Alors, que s'est-il passé ?

J'ai été en retrait de ce projet pendant un certain temps. Mais j'ai récemment repris le flambeau pour résoudre quelques problèmes et fusionner certaines des excellentes choses que d'autres ont faites dans ce projet.

Par exemple, il existe une version qui lit les disquettes Commodore 1581, ainsi que les disquettes Atari HD.

Lecture des données (à nouveau)

J'avais réfléchi à l'idée du pourquoi les temps que j'avais utilisés pour la lecture étaient ce qu'ils étaient. Bien qu'ils fonctionnent, les chiffres ne suivent pas vraiment un motif exact, et ne suivent pas les plages qu'ils devraient.

Je savais qu'il y avait trois motifs de bits MFM qui devaient se produire, 01, 001 et 0001. J'ai également vu 00001 qui n'est techniquement pas autorisé. En ignorant le dernier, ces motifs devraient prendre 64, 96 et 128 microsecondes. En théorie, les points centraux devraient être (64-96) 80 et (96-128) 112. C'est ce que j'avais. Mais je me suis soudainement rendu compte que je travaillais avec des synchronisations à la vitesse d'horloge de l'Arduino. Cela signifie que chaque instruction compte pour cela.

J'avais une disquette, avec une piste qui se lisait toujours avec des erreurs, mais sur un Amiga, elle se lisait bien. Cela devait avoir quelque chose à voir avec ces synchronisations, car il semblait que l'Amiga devait être très tolérant à la dérive des bits.

J'ai commencé à regarder le code désassemblé et les instructions impliquées. J'ai été surpris de constater que la lecture de la valeur du compteur et sa remise à zéro prenaient ensemble quatre tics d'horloge. J'ai donc fait quelques expériences en appliquant ce décalage aux synchronisations utilisées et à ma grande surprise, la disquette pouvait maintenant être lue !

Afin de mettre de l'ordre dans tout cela pour une utilisation future, tous ces calculs sont maintenant effectués en haut du code.

Écriture des données (à nouveau) - précompensation - échec 1

Un des commentaires que j'ai lus pendant que je travaillais sur ce projet, était que les gens étaient surpris que l'écriture fonctionne aussi bien qu'elle le faisait sans ce qui est connu comme la précompensation (precomp) d'écriture. Après quelques lectures de fond, j'ai eu du mal à trouver si cela était même utilisé sur l'Amiga, bien que l'on puisse voir que cela était géré par le matériel.

La théorie de la précompensation d'écriture est un peu comme ceci : en écrivant sur les pistes extérieures, chaque fois que vous envoyez un "1", vous provoquez une transition de flux sur la surface de la disquette. Cela revient à créer de petits aimants à la surface de la disquette. Sur les pistes extérieures, cela ne pose aucun problème, mais à mesure que vous vous rapprochez des pistes intérieures, ces aimants se rapprochent les uns des autres. Sans tenir compte des détails, vous êtes censé ajuster légèrement les temps entre les bits pour compenser tout cela, ainsi que le temps nécessaire à la création du champ magnétique.

Il est surprenant de constater qu'il existe un certain nombre d'informations contradictoires à ce sujet, mais la base est d'essayer d'ajuster les synchronisations de manière à maintenir les bits "1" aussi éloignés que possible les uns des autres sans les faire tomber dans une autre position.

La partie délicate de tout cela est la valeur de la précompensation et le moment où il faut l'utiliser. Certains documents parlent de l'appliquer à partir de la piste 40. D'autres disent après la piste 79. Certains disent les deux, mais avec des quantités différentes.

L'Amiga gère une précompensation de 0, 140, 280 et 560 nanosecondes. Oui, des nanosecondes. C'est une quantité incroyablement petite. L'horloge de base de 16 MHz de l'Arduino est de 62,5 nanosecondes, donc seulement deux instructions de base (comme add) ajouteraient 125 nanosecondes. Je me suis demandé si nous pouvions être aussi précis avec la résolution disponible.

Je me suis rendu compte qu'il n'y avait aucun moyen de calculer cela sur l'Arduino, et qu'il faudrait donc le précalculer sur le PC avant de l'envoyer. J'ai décidé d'utiliser un code qui lirait le flux binaire brut de la MFM et le convertirait comme suit :

Motif MFM Codé en binaire en tant que
01 00
001 01
0001 10
00001 11

Donc, avec ce motif, je pourrais envoyer quatre de ces séquences pour chaque octet. Mais qu'en est-il de la précompensation ? Je devais dire pour chaque séquence si la séquence devait être "EARLY" (c'est-à-dire -125 nanosecondes), "LATE" (+125 nanosecondes) ou normale. Et voilà deux bits de plus.

Ainsi, chaque demi-octet (alias "nybble") contenait maintenant des informations concernant chaque séquence. Mais pouvais-je envoyer cela assez rapidement ? Le débit en bauds est de 2 Mbauds et de 10 bits environ (en tenant compte des bits de départ et d'arrêt, etc.), ce qui signifie que chaque octet arriverait à environ cinq secondes d'intervalle. D'après ce que nous avons appris, cela correspond à environ 88-90 tics d'horloge de l'Arduino. Dans un bon jour, disons 80 instructions. Ça semble beaucoup.

J'avais besoin de savoir que cela allait fonctionner et être précis. Mon fidèle oscilloscope n'aurait pas été capable de suivre le rythme, et même s'il l'avait pu, il n'avait pas d'options de stockage pour la petite quantité de données.

Je me suis décidé pour un analyseur logique, mais ils sont si chers. Jusqu'à ce que je trouve des clones bon marché de 24 MHz pour environ 10 £. Et ça a très bien marché. J'étais capable de capturer les données que j'écrivais et de vérifier avec précision les synchronisations. Ok, avec le théorème d'échantillonnage de Nyquist, j'avais vraiment besoin de quelque chose comme 32 MHz, mais c'était probablement suffisant.

Je l'ai branché, j'ai écrit du code pour le lire, j'ai mis le chronomètre à zéro et j'ai attendu dans une boucle qu'il atteigne la valeur requise basée sur 64+(32xmotif)+/-precomp. A première vue, quelque chose de très bizarre se passait. La séquence était là, mais les données se déplaçaient dans tous les sens. Les impulsions semblaient se décaler d'environ +/-180 nanosecondes partout, de façon apparemment aléatoire. C'était étrange.

Puis je me suis souvenu du problème que j'avais eu avec la lecture, et j'ai désassemblé le code. La boucle ressemblait à quelque chose comme cela :

while (TCNT2 < value) {};

Ça semble assez anodin. Mais ensuite, il faudrait extraire la valeur de TCNT (deux tics d'horloge), puis la comparer à une autre valeur. Un autre tic d'horloge. Puis revenir en arrière si ce n'était pas la valeur. Un autre tic d'horloge. Cette boucle nécessite quatre tics. C'est le quart de notre résolution. Comment étais-je supposé écrire quelque chose au quart de ça avec cette méthode ? La réponse est que vous ne pouvez pas.

Écriture de données (à nouveau) - précompensation - échec 2

J'avais donc besoin de trouver un moyen plus rapide de surveiller le compteur matériel, qui faisait tic-tac à la vitesse de certaines des instructions que j'utilisais. Je savais que TCNT2 (Counter2/Timer2) pouvait faire beaucoup plus que simplement compter.

J'ai commencé à regarder à nouveau la fiche technique de la puce ATMega328. Ce n'est pas pour les âmes sensibles, mais elle explique à peu près tous les aspects du microcontrôleur, des broches de contrôle aux dimensions physiques des puces.

Si vous allez à la section des compteurs, il y a beaucoup d'autres registres qui leur sont associés. Jusqu'à présent, je n'avais pas vraiment essayé de les utiliser, enfin, pas depuis mes toutes premières expérimentations quand j'ai commencé ce projet.

Il y a des registres qui vous permettent de contrôler l'intervalle dans lequel il compte (OCR2A et OCR2B), ainsi que des bits dans les registres qui contrôlent le fonctionnement du chronomètre. Par exemple TCCR2A est utilisé pour contrôler la sortie PWM, ou comme je l'ai configuré, aucune sortie.

Puis il y a son double, TCCR2B, que j'ai utilisé jusqu'à présent pour régler la vitesse du compteur, dans ce cas sur "no pre-scaling" (sans pré-dimensionnement), ce qui signifie qu'il fonctionne aussi vite que l'oscillateur (16 MHz).

Vous avez également TIMSK2 qui est impliqué dans l'activation des interruptions, et enfin TIFR2. C'est un registre intéressant. En regardant de plus près, il y a trois bits que vous pouvez lire. OCF2B, OCF2A et TOV2. Ces "bits" sont définis en fonction des règles établies dans TCCR2A. Par exemple, ces bits peuvent être activés lorsque TCNT2 atteint la valeur de OCR2A, OCR2B ou déborde vers zéro.

Je savais que la seule lecture de TCNT2 utilisait deux cycles d'instruction, mais qu'en est-il de la vérification de ces bits ? Après avoir désassemblé le code, j'ai découvert qu'ils pouvaient être testés en un seul cycle d'instruction. Cela signifiait que la boucle que j'utilisais auparavant pouvait être remplacée par quelque chose ressemblant plus à ceci :

while (!(TIFR2 & bit(OCF2B)) {};

Cette boucle se termine si je configure correctement les registres ci-dessus lorsque TCNT2 atteint OCR2B. Fantastique ! Une note intéressante : vous devez écrire un bit "1" dans ces registres pour les remettre à zéro. Ne me demandez pas pourquoi, c'est ce que dit la documentation, et c'est correct.

J'ai donc réimplémenté la solution en tenant compte de cela. C'était beaucoup mieux, mais il y avait encore beaucoup de fluctuations. Une partie du problème était que pour maintenir l'écriture sur la disquette, je devais également lire les données du port série.

Le code que j'utilisais actuellement pour lire le port série et conserver des données en mémoire tampon ressemblait à ceci :

if (UCSR0A & bit(RXC0)) {
	SERIAL_BUFFER[serialWritePos++] = UDR0;
	serialBytesInUse++;
}

Le bout de code ci-dessus, encore une fois, semble simple, mais se compile en environ 14 cycles d'instruction ! Alors, quand dois-je exécuter ce code ? Une fois par "bit" ? Rappelez-vous, nous n'écrivons pas de "0", nous n'écrivons que des "1". Donc l'intervalle minimum serait (théoriquement) de 64 microsecondes, le maximum étant de 128 microsecondes. Les données arrivent à raison d'un octet tous les 80 microsecondes. Mais nous devons contrôler cela (c'est à cela que servent les contrôles CTS).

Le microcontrôleur met en mémoire tampon jusqu'à deux caractères pour vous avant que les données ne soient perdues. Il semble donc que tant que je vérifie une fois par cycle, tout devrait bien se passer. Ce code a donc été placé avant la boucle "while".

Quelque chose de bizarre s'est produit. Le PC a fini d'envoyer toutes les données et a attendu la réponse. Mais l'Arduino ne l'a jamais envoyée. Le port série a aussi quelques registres intéressants. L'un d'eux vous dira si vous n'avez pas lu les données assez rapidement. Si vous faites :

if (UCSR0A & (bit(FE0)|bit(DOR0))) break;

...alors votre boucle se terminera si le port série a un dépassement de données, ou si une erreur de trame s'est produite. Donc, il s'avère que j'avais des erreurs de dépassement. Je ne lisais pas les données assez rapidement, donc le PC avait envoyé toutes les données, mais l'Arduino avait perdu/écrit certains des octets avant que j'aie eu la chance de les lire.

Je ne pouvais pas mettre le code ci-dessus dans la boucle "while" car cela pouvait potentiellement ajouter une fluctuation supplémentaire, la boucle pouvant potentiellement prendre plus de 14 cycles d'horloge avant de tester à nouveau. J'ai donc décidé d'essayer quelque chose comme ceci :

while (TCNT2<OCR2B-16) {
	if (UCSR0A & bit(RXC0)) {
		SERIAL_BUFFER[serialWritePos++] = UDR0;
		serialBytesInUse++;
	}
}
while (!(TIFR2 & bit(OCF2B)) {};
// Set the output to '0' at this point.  Wait a little, and then put it back to '1'

J'activais et désactivais la ligne CTS uniquement lorsqu'il y avait ou non de l'espace dans le tampon, et la théorie avec le code ci-dessus est de permettre jusqu'à 16 cycles d'horloge avant l'impulsion pour vérifier les données série, puis de s'arrêter et d'attendre dans une boucle plus serrée et de définir la sortie. L'impulsion de sortie, selon certains lecteurs, doit être quelque chose comme 0,15 ~ 1,1 microseconde. La méthode d'écriture précédente que j'utilisais était de 4 microsecondes et tout allait bien, de plus, c'est le front descendant (transition de 1 à 0) sur l'impulsion qui déclenche l'écriture, pas l'impulsion qui est basse en soi !

Cela semblait être un bon plan. Mais j'avais toujours des problèmes, cette fois, à savoir que mon code se rendait compte que je ne recevais pas assez de données du port série assez rapidement pour garder mon tampon de sortie plein. Il y a 16 cycles d'horloge dans la deuxième boucle qui ne lit pas les données du port série. Si je réduis ce nombre, le problème disparaît, mais la fluctuation revient.

À ce stade, j'avais presque abandonné. J'étais si proche, mais ce n'était pas assez bien. Je ne pouvais pas avoir de fluctuation, car s'il y en avait, essayer d'utiliser la précompensation serait de toute façon inutile.

Écriture des données (à nouveau) - précomposition - succès

J'avais besoin d'une nouvelle approche. Il devenait évident que le fait d'écrire les données en bits n'allait pas produire le niveau de précision dont j'avais besoin, enfin, pas avec ce microcontrôleur. Un plus rapide peut-être.

J'ai alors repensé à mes premières expériences et aux chronomètres. Ils pouvaient produire des formes d'onde, contrôlées au niveau matériel, généralement pour la PWM. Mais il s'agissait d'un motif répétitif. Je me suis demandé s'il n'y avait pas un moyen d'utiliser cette information, un moyen de faire varier ces formes d'onde pour produire ce dont j'avais besoin, en lui faisant faire les commutations précises et en le programmant simplement pour qu'il les fasse.

Ce dont j'avais vraiment besoin était une impulsion unique. C'est-à-dire : le mettre en marche, après un temps spécifique, il passerait au niveau bas, resterait là, et repasserait au niveau haut. Mais le microcontrôleur Atmel n'est pas conçu pour faire cela, ou du moins n'est pas supposé pouvoir le faire.

J'ai donc commencé à faire des recherches et je suis tombé sur une astuce qui utilisait le chronomètre du microcontrôleur AVR pour créer une impulsion unique. Intéressant. J'ai donc commencé à l'adapter à mes besoins. Il s'agit essentiellement de mettre le chronomètre à l'envers de sorte qu'il se bloque après la fin de l'impulsion. Je doute que cela ait jamais été prévu, mais la logique des chronomètres permet à cela de fonctionner !

J'ai donc commencé à modifier le code pour créer les séquences 01, 001, 0001, etc. en utilisant ce seul coup. Je l'ai démarré au premier "0", et l'ai laissé faire l'impulsion "1" aussi. Comme le chronomètre fonctionne également à 16 MHz, tout ce que j'aurais à faire serait de décaler le point de départ de l'impulsion pour obtenir une précompensation.

Après une analyse plus poussée des registres matériels de Timer2, j'ai fini par les configurer comme suit :

TCCR2A = bit(COM2B1) | bit(WGM20) | bit(WGM21)| bit(WGM22);  
TCCR2B = bit(WGM22) | bit(CS20);
  • TCCR2A : COM2B0 et COM2B1 combinés selon la fiche technique signifient CLEAR OC2B quand TCNT2=OCR2B, et SET OC2B quand TCNT2 atteint le bas (étant TCNT2=0). WGM20, WGM21, WGM22 indiquent que nous voulons utiliser le mode Fast PWM.
  • TCCR2B : WGM22 active la génération de la forme d'onde sur la broche de sortie associée, qui pour nous, heureusement, est la même broche avec laquelle nous avons écrit, et CS20 est la valeur de précale=1 que nous avons utilisé auparavant.
La façon dont le chronomètre fonctionne, est que lorsqu'il atteint BOTTOM (c'est-à-dire 0), il est supposé remettre TCNT2 à la valeur de OCR2A. Mais si nous le réglons également sur zéro, le chronomètre reste à 0 à moins que nous le changions manuellement.

Tout ce que j'avais à faire maintenant était de déterminer la valeur de OCR2B, qui est l'endroit où nous voulons que l'impulsion se déclenche, et la valeur initiale de TCNT2 pour donner la bonne quantité de délai pour cette impulsion.

Maintenant, comme le matériel générait l'impulsion, cela m'a donné plus de temps pour calculer les valeurs et surveiller le port série, laissant jusqu'à la dernière minute pour chronométrer précisément la réinitialisation de la séquence suivante.

Je me suis retrouvé avec un code comme celui-ci :

while (!(TIFR2 & bit(OCF2B))) {
	if (UCSR0A & bit(RXC0)) {
		SERIAL_BUFFER[serialWritePos++] = UDR0;
		serialBytesInUse++;	
	}
}; 

while (!(TIFR2 & bit(TOV2)));

TCNT2 = counter;
OCR2B = pulseStart;

Ainsi, la première boucle "while" s'exécute jusqu'à ce que l'impulsion commence, mais nous n'avons pas besoin de générer l'impulsion, donc nous ne nous soucions pas de sa précision, et la seconde attend précisément la condition de débordement (TCNT atteignant BOTTOM).

Après cela, nous réglons immédiatement TCNT2 (compteur) sur le délai pour la nouvelle impulsion, et nous écrivons où l'impulsion doit commencer.

Mais il y a un problème ici. La deuxième boucle "while" a également causé un petit délai, plus l'écriture de TCNT2 prend du temps. Mais cette fois, tout est fixé, et nous pouvons donc le compenser. J'ai mesuré six cycles d'horloge qui ont donné les synchronisations correctes.

C'est un succès ! J'ai confirmé dans l'analyseur logique que la sortie était bien celle dont j'avais besoin, qu'elle était chronométrée très précisément, et que le début de l'impulsion était décalé d'avant en arrière de deux tics comme je le voulais.

Si vous voulez voir exactement comment cela est fait, le code est disponible sur GitHub, comme le reste du projet. J'étais très satisfait. Au début du projet, je n'étais pas sûr que ce soit possible. Je pense qu'on m'aurait dit que ce n'était pas possible, mais en utilisant les composants intégrés au matériel plutôt que de faire le travail moi-même, cela a été possible.

WinUAE

Je vais résumer cette partie. Le processus réel a été beaucoup plus compliqué : déboguer les données au niveau des bits, voir ce que j'avais fait de travers et comprendre comment WinUAE implémentait l'accès à la disquette. J'avais dans un coin de ma tête l'idée de gérer le lecteur de manière native dans Windows. Cela impliquerait des pilotes de périphériques pour l'accès aux disquettes, puis des pilotes de système de fichiers pour gérer le système de fichiers Amiga. Ce serait fantastique, mais cela demanderait beaucoup de travail.

J'ai ensuite été distrait par une idée qui m'était venue à l'esprit à plusieurs reprises, et après avoir reçu un courriel à ce sujet, j'ai décidé d'enquêter davantage. Serait-il possible de gérer ce lecteur dans WinUAE, ce qui signifierait que vous pourriez utiliser des disquettes Amiga natives dans l'émulateur ?

Je n'en avais absolument aucune idée, mais je ne voyais en quoi ce ne serait pas le cas. J'ai supposé que, quelque part dans tout ce code, il devait simuler un lecteur de disquette... en quelque sorte... J'ai supposé que mon approche devrait changer quelque chose, mais comme vous l'avez vu, je n'ai pas peur de changer le code !

Première étape. Pourrais-je au moins compiler le code source de WinUAE ? Toni Wilen, qui le maintient si excellemment, inclut des instructions sur de telles choses, et quelques minutes après avoir téléchargé tous les fichiers, il s'est compilé et a fonctionné. Il ne me restait plus qu'à comprendre le code, et sans surprise, il y en a beaucoup, et dans tous les genres, probablement en raison de la nature du projet.

Il n'y a pas de raison que quelqu'un ait cherché à faire cela avant, et on pourrait dire que ce n'est pas nécessaire. Mais j'aime les défis. Toni Wilen avait fait remarquer qu'il aurait pensé qu'il serait difficile de synchroniser les chronomètres de l'Amiga émulé avec ceux d'une vraie disquette en train de tourner. J'ai supposé qu'il voulait dire physiquement faire correspondre la position de rotation de la MFM de la disquette à celle que l'émulateur pensait être, mais j'avais un plan.

Tout d'abord, afin de soutenir au mieux ce projet, j'ai décidé que je devais avoir un meilleur accès au lecteur de disquette. Bien que cela puisse fonctionner avec la configuration existante, il serait bien de pouvoir allumer et éteindre le moteur séparément des autres fonctions, et de détecter la présence d'une disquette dans le lecteur. Toutes ces fonctions sont disponibles sur la disquette, mais je n'en ai pas eu besoin et elles n'étaient pas correctement connectées. Il y a une modification détaillée pour cela dans la section "hardware". Elle est optionnelle mais hautement recommandée !

Mon plan était que si je pouvais, d'une manière ou d'une autre, transmettre les données au PC pour que WinUAE puisse les lire, alors tout devrait bien se passer. Je ne vais pas entrer dans les détails ici, mais bien que cela semble assez simple, la réalité est loin d'être simple.

Une partie du problème réside dans le fait que les données doivent être disponibles pour WinUAE lorsqu'il en a besoin, et non pas lorsque je les ai à disposition. Si je n'ai pas les données et que je fournis des données vierges, vous obtenez essentiellement des erreurs de lecture. Si toutefois je force WinUAE à attendre, le programme saccadera et se figera (même le son et le pointeur de la souris), ce qui est hautement indésirable et désagréable. Ce n'est la faute de personne, c'est juste le piège de ce que j'essaie de faire.

Maintenant, je peux mettre en cache une lecture de piste complète en mémoire, mais je dois être sûr qu'il s'agit d'une rotation complète et exacte sans chevauchement ni données manquantes, littéralement au bit près, ce qui n'est pas une mince affaire. Mais que dois-je demander à WinUAE de faire pendant qu'il le met en cache ?

Donc, premier défi. Lire une piste complète. Ah, cette impulsion /INDEX que le lecteur génère, ça doit aider. C'est le cas, mais pas au niveau de précision que j'avais espéré. C'est peut-être mon implémentation, mais j'ai trouvé que son impulsion vacillait un peu. Pas beaucoup, mais juste assez pour que ce ne soit pas assez fiable en l'état.

J'ai donc décidé de lire les données jusqu'à l'impulsion, de continuer à lire jusqu'à l'impulsion suivante, puis d'essayer de faire correspondre le chevauchement. Cela a fonctionné étonnamment bien, même si ce n'était pas aussi trivial que je le disais. Il a fallu plusieurs semaines pour que cela fonctionne de façon fiable, en s'assurant de ne pas manquer un seul bit.

L'inconvénient est que la disquette tourne d'un tour environ toutes les 200 millisecondes. Mais je dois d'abord attendre le marqueur d'index, qui peut se trouver jusqu'à 200 millisecondes plus loin si nous sommes vraiment malchanceux. Cela signifie que, dans le pire des cas, la lecture d'une piste entière peut prendre jusqu'à 400 millisecondes. Nous ne pouvons pas arrêter WinUAE pendant tout ce temps.

Au lieu d'attendre l'intégralité de la piste, je me suis demandé s'il n'était pas possible de fournir ce que nous avions jusqu'à présent, puis de bloquer le processus si nécessaire. Ce n'est pas bon. Il était encore trop instable pour être acceptable.

J'ai alors eu une idée. Nous parlons ici de matériel physique, qui ne fonctionne pas comme son homologue émulé. Par exemple, lorsque vous allumez le moteur pour faire tourner la disquette, vous ne pouvez pas lire les données immédiatement, vous devez généralement attendre jusqu'à 750 millisecondes. Je le sais, WinUAE le sait et le Kickstart le sait aussi.

Qu'en est-il du passage d'une piste à l'autre ? Y a-t-il un temps mort pendant lequel les données de la disquette ne sont pas disponibles ou ne sont pas considérées comme stables ? Il s'avère qu'il y en a un. C'est généralement jusqu'à environ 18 millisecondes. J'ai encore besoin de trouver jusqu'à 382 millisecondes de plus.

...ou je l'ai fait. J'ai eu une idée. Et si, jusqu'à ce que les données soient prêtes, je continuais à envoyer un "0" ou un "1" à WinUAE. Il n'y aurait pas de motif à verrouiller, et pas de données, mais compte tenu de la façon dont WinUAE fonctionne, il penserait toujours qu'une disquette est présente et tourne (il simule l'impulsion /INDEX). Cela permettrait-il d'allonger le temps mort ?

Il s'avère que oui. J'ai augmenté ce temps jusqu'à un maximum de 600 millisecondes avant que nous puissions potentiellement bloquer WinUAE ; et tout était fluide. Les données ont été lues, la souris et la musique ont fonctionné sans problème et une disquette s'est amorcée.

Les données étaient diffusées par l'Arduino en arrière-plan en utilisant un fil d'exécution séparé. J'avais mis en place un système de file d'attente dans lequel les commandes demandant de changer le numéro de piste, etc. étaient mises en file d'attente, mais nous simulions une réponse valide en sachant que la seule chose importante était de lire et d'écrire sur la disquette.

Toute commande émise arrêterait immédiatement ce flux, mais si elle était laissée, elle lirait continuellement les rotations exactes de la disquette, que WinUAE activerait et désactiverait à sa demande. Ma théorie est que cela peut aider à gérer la protection "bit faible" utilisée dans certains jeux, mais aussi corriger une erreur de lecture potentielle qui aurait pu se produire.

J'avais donc une disquette qui pouvait être lue avec succès et, bien que celle-ci n'était pas aussi rapide qu'un vrai Amiga, WinUAE ne semblait pas s'en soucier. Même X-Copy ne semblait pas s'en soucier.

Maintenant, le défi de l'écriture s'est présenté. J'avais à présent le nouveau code d'écriture de précomposition prêt à être utilisé. Avec un peu de chance, je pourrai aussi le fiabiliser.

Pour faire court, ce fut un véritable monstre à faire fonctionner. La raison principale était la façon dont WinUAE gérait les données qu'il essayait d'écrire. Les données pouvaient se perdre et je ne savais pas où était le début de celles-ci. Donc, à la place, j'ai décidé de capturer les données telles qu'elles sont reçues de l'écriture DMA de l'Amiga. C'est ce que WinUAE faisait de toute façon, mais je pouvais les recevoir directement, et ensuite, quand finalement la requête DMA était terminée, je pouvais simplement mettre en file d'attente une requête pour l'écrire sur la disquette.

Cela fonctionnait beaucoup mieux. Le meilleur test était d'essayer de formater une disquette à partir du Workbench, car il fallait aussi vérifier les résultats. Mes astuces de synchronisation pouvaient-elles fonctionner ici aussi ? Il semblerait que oui. J'étais maintenant capable de formater une disquette à partir du Workbench, de vérifier le formatage et de lire un fichier MOD sans aucune interruption de l'audio. Mission réussie.

Je voulais cependant une meilleure gestion, car j'avais quelques disquettes protégées contre la copie qui ne démarraient toujours pas. Je soupçonnais que cela avait quelque chose à voir avec des astuces consistant à faire varier légèrement les synchronisations des cellules de bits pour que certaines zones de la disquette soient lues plus rapidement que d'autres. C'est une technique assez courante. J'ai ajouté du code au flux de lecture pour savoir si un bit était lu normalement, rapidement ou lentement, que j'ai ensuite simulé dans WinUAE. Malheureusement, ce n'était pas suffisant, donc je suppose que la carte Arduino ne va pas aller plus loin. Je suppose que ces jeux étaient des copies pirates.

J'avais conçu cette interface pour également gérer d'autres matériels. Après tout, ajouter la gestion pour ce matériel avait été assez complexe, non seulement au niveau des considérations physiques, mais aussi pour le fait de comprendre ce qu'il se passait dans l'émulateur !

Le code a donc été écrit avec un ensemble de fonctions communes (classe abstraite) qui devaient être implémentées afin de gérer différents matériels, ce qui rendrait les choses beaucoup plus simples à l'avenir, et je voulais toujours atteindre mon objectif de démarrer correctement un jeu protégé contre la copie. J'en avais deux à essayer, Captain Planet et Lemmings, tous deux fournis avec le lot Cartoon Classics que j'ai eu avec l'Amiga 500+. Ces deux jeux ne fonctionnaient pas quand on les copiait, et tous deux ne démarraient que jusqu'à un certain point et s'arrêtaient sur la copie au même endroit où ils s'arrêtaient quand on les utilisait dans WinUAE avec mon lecteur.

WinUAE et les disquettes protégées contre la copie

Je n'aime pas abandonner. Je suis heureux que les disquettes AmigaDOS standards démarrent, je me suis tourné vers la protection contre la copie. Je savais déjà beaucoup de choses sur ce qu'il se passait, des pistes courtes/longues, des zones faibles/non formatées, etc. Je pensais que si je pouvais lire les données avec suffisamment de précision, je ne voyais pas pourquoi l'émulateur ne pourrait pas lancer ces jeux.

Le problème était les données. J'avais une bande passante très limitée sur le port série de l'Arduino. Nous fonctionnons à 2 Mbauds, ce qui (à 8 bits, 1 bit d'arrêt et 1 bit de départ) signifie que nous devrions pouvoir transmettre un octet tous les 5 microsecondes. Cependant, à partir de la disquette, la séquence la plus courte, "01", pourrait arriver à des intervalles de 4 microsecondes. Parfois même plus rapidement, en fonction de la vitesse du lecteur utilisé par rapport à celui qui écrit.

J'ai donc d'abord décidé de regarder ma disquette de Lemmings. Je savais qu'elle ne démarrait pas si on la copiait avec X-Copy, donc je savais qu'il y avait quelque chose de louche.

J'ai ajouté une commande supplémentaire au firmware de l'Arduino qui me donnerait (pour une seule rotation complète) une décomposition des temps pour les cellules de bits trouvées sur un cylindre. Il y avait évidemment des points chauds autour des intervalles de 4, 6 et 8 microsecondes, comme prévu. Mais il y avait des pistes qui présentaient beaucoup plus de variations.

Je savais qu'il y avait des astuces pour enregistrer les données à des débits légèrement différents, la théorie étant que si vous lisez la piste, la chronométrez et la comparez à une autre piste, vous pouvez mesurer la différence et détecter l'original par rapport à la copie (car la copie corrigerait cette légère incohérence de chronométrage). Nous parlons ici de très petites différences, si elles étaient importantes, le lecteur ne pourrait pas lire correctement les données.

Dans la version précédente, j'avais essayé d'ajouter une option "fast" (rapide) et "slow" (lent), mais cela ne fonctionnait pas. J'avais besoin de quelque chose de plus sophistiqué. Auparavant, j'avais essayé d'indiquer cette vitesse par cellule binaire, mais fallait-il vraiment être aussi précis ?

Je me suis demandé ce qu'il se passerait si je faisais la moyenne des temps sur plusieurs motifs, et que je l'envoyais au PC, et que je l'utilisais pour dire à WinUAE de lire les données légèrement plus vite ou plus lentement. Je savais qu'il pouvait le faire, j'avais trouvé quelques enregistrements SCP de ces disquettes et ils ont démarré, donc je savais que c'était possible.

Le format final que j'ai obtenu consistait à renvoyer un octet à l'ordinateur avec les informations suivantes :

Bits d'un octet
7 6 5 4 3 2 1 0
Index Cellule Cellule Vitesse
  • Index : ce bit est activé lorsque l'impulsion /INDEX est détectée.
  • Cellule : ceci représente ce que nous avons trouvé. C'est un 01=1, 001=2, 0001=3 ou autre chose=0.
  • Vitesse : vitesse moyenne pour les deux cellules.
La vitesse a été simplement calculée en additionnant la valeur du chronomètre - le début de la synchronisation de la cellule. Par exemple, si le temps était à 4,2 microsecondes, on enlève le 4,0. Bien entendu, ces valeurs sont exprimées en tics et non en microsecondes, mais vous comprenez l'idée. Cela donnerait (pour chaque cellule) un nombre entre 0 et 31, 15/16 étant le centre ou la "vitesse est 100% correcte".

Une fois additionné, ce nombre se situerait entre 0 et 63, donc nous le décalons de trois à droite (divisé par 8), ce qui nous donne un nombre entre 0 et 7, ce qui convient parfaitement. Mais est-ce que cela serait d'une résolution assez élevée pour résoudre le problème.

Une fois présenté correctement à WinUAE, Lemmings a démarré ! Donc, quelle que soit la méthode utilisée pour la synchronisation des cellules de bits, nous l'avons simplement ignorée. Maintenant, cela pourrait ne pas fonctionner pour tous les jeux qui utilisent cette technique, et si une plus haute résolution est nécessaire plus tard, nous devrions faire la moyenne des temps sur quatre cellules à la place.

Lemmings
Temps de la piste 0, face supérieure, disquette de Lemmings
Ici, certains temps de cellule binaire sont suspects, mais bien dans les limites de la tolérance !


Alors, et pour Captain Planet ? Pas de chance. Quelle que soit la méthode utilisée, ce n'était pas ça. Alors que faisait-il ? Encore une fois, j'ai essayé d'analyser les temps sur la disquette pour voir ce qu'il se passait. La plupart des pistes étaient assez typiques, mais il y en avait deux, en particulier la piste 0 de la face inférieure, qui était vraiment étrange.

Le meilleur moyen est de vous montrer à quoi ressemble une copie du contenu SCP de cette piste dans l'analyseur de piste de HxCFloppyEmulator. Je ne l'ai découvert que récemment :

SCP1

Ainsi, les sections vertes représentent onze secteurs de données. Il les détecte même comme AmigaDOS. Tout a l'air normal, les données se trouvent dans les 4, 6 et 8 microsecondes comme vous vous y attendez. La zone blanche est le "vide" à la fin qui remplit généralement l'espace pour compléter une rotation complète. La plupart du temps, cet espace est ignoré, et de toute façon, comme je dois fournir une révolution précise (ou rotation complète) des données à WinUAE, il est quoi qu'il en soit inséré. Les copieurs ignorent généralement cela, mais cela semble étrange, comme s'il y avait des données là aussi.

En regardant de plus près, vous pouvez voir ceci :

SCP2
Disquette visuelle pour Captain Planet, piste 0, face inférieure - intervalle des pistes

C'est un peu étrange. Donc, en ignorant les zones vertes qui sont des secteurs de données, la moitié droite de l'image est juste la séquence 0xAA (01010101) que vous voyez souvent, c'est normal, mais qu'est-ce qu'il se passe sur le côté gauche ?

Cela doit avoir quelque chose à voir avec cela. Mon code continuait déjà à fournir des révolutions de données à WinUAE, ce qui lui permettait de contourner le schéma de protection "bit faible" puisque les données mal lues étaient présentées différemment à chaque fois. Mais que se passait-il ici ?

J'ai décidé de prendre une seule rotation complète de données à partir du fichier SCP, et de l'insérer dans WinUAE, juste pour cette piste, et de lui faire lire le reste de la disquette comme avant. Et le jeu a démarré ! Il est donc clair qu'il n'avait pas besoin de plusieurs révolutions de données. Alors que se passait-il ? Si vous regardez l'enregistrement, cette dispersion de points est différente, donc il y a quelque chose qu'il se passe.

Il n'y a clairement aucun moyen d'envoyer cette folle quantité en 2 Mbauds. Je n'ai tout simplement pas la bande passante. Mais j'ai quand même quelques astuces.

Ce cas de "quelque chose d'autre" (plus de trois "0" à la suite) que j'ai pu recevoir, et que je traitais comme une erreur, commence peut-être à signifier quelque chose de plus utile.

J'ai recommencé à me documenter sur les disquettes, et ce qu'elles font s'il n'y a pas de flux de données pendant un certain temps. Si vous remarquez les synchronisations, certaines d'entre elles sont très élevées, entre 11 et 12 microsecondes. C'est hors de portée des données MFM.

Les lecteurs sont donc équipés d'un circuit qui ajuste automatiquement l'amplification de la tête lors de la lecture pour compenser les disquettes enregistrées avec des champs magnétiques d'intensité différente. Après tout, les disquettes plus anciennes fonctionnent encore, et leurs données magnétiques auront commencé à se dégrader.

Apparemment, après environ trois zéros d'affilée, il continue à essayer de se régler, et finalement cette amplification devient si élevée qu'un bit peut être détecté alors qu'il n'existe pas. C'est comme ça que le truc du "bit faible" est censé fonctionner. Quand il détecte un vrai bit, il est massivement surchargé et commence à revenir à ses niveaux précédents, et tout le processus recommence.

En regardant les données que mon code lisait, ce cas spécial était déclenché assez souvent, mais comment le traitais-je ? De manière incorrecte évidemment. Mais je ne pouvais pas signaler les séquences de 00001 ou plus, je n'avais tout simplement pas les bits nécessaires. Mais si je n'avais pas besoin de le faire ? Et si j'avais seulement besoin de savoir que nous avions dépassé la possibilité de la séquence 0001, mais sans détecter un "1", c'est-à-dire en confirmant une séquence "0000" ?

J'ai donc fait quelques modifications pour que, lorsque cette séquence était déclenchée, elle génère quatre bits "0", mais le jeu ne démarrait toujours pas. J'ai donc examiné la condition qui a déclenché cela. Nous attendions dans une boucle, jusqu'à ce que nous voyions un bit, ou que le chronomètre aille trop loin. Le dépassement étant ce qui a pu déclencher, ou un bit bien au-delà des normes. Cela signifie que 00001 et 00000 seraient détectés comme la même chose. Mais ils ne le sont pas.

J'ai rendu cela plus strict. J'ai modifié la boucle pour qu'elle se termine si le chronomètre atteint la limite de 8 microsecondes ou atteint un bit. A moins que le bit ne soit exactement sur cette limite, nous saurions maintenant avec précision que nous avons trouvé un 0000, et tous les futurs bits "1" seraient détectés sur la boucle suivante. Par exemple, nous pourrions détecter 0000 et ensuite 01, ce qui signifie que nous avons détecté avec succès 000001.

Est-ce que ça a marché ? Non. J'ai dormi là-dessus et j'y suis revenu dans la matinée.

Une idée m'est venue. Entre les vérifications des bits, j'effectue d'autres tâches, comme envoyer des données au PC ou vérifier si la broche /INDEX a émis un signal. Et si une impulsion de bit arrivait pendant ce temps, et que l'impulsion était suffisamment courte pour qu'au moment de la vérification, elle ait disparu et que nous l'ayons manquée.

Existe-t-il une meilleure façon de surveiller les données ? En apprenant comment je détecte maintenant l'impulsion /INDEX, j'ai commencé à regarder la fiche technique. La méthode que j'ai utilisée n'a pas fonctionné car les données n'arrivaient pas sur une broche qui pouvait détecter les fronts montants ou descendants. L'ATMega328 vous permet cependant de configurer une interruption pour le changement d'état de chaque broche.

Je ne veux pas d'interruption, mais il faut signaler qu'une interruption doit être générée. Bien sûr, si je fais ceci :

PCMSK2 = bit(PCINT20);
PCICR = bit(PCIE2);

...cela devrait normalement déclencher une interruption lorsque l'état de la broche (4) change. Mais vous ne pouvez détecter que les changements, pas les bords. Ce n'est pas un problème majeur. Si nous savons que nous ne sommes pas dans une impulsion, alors le changement serait l'impulsion, et tant que nous attendons jusqu'à ce que l'impulsion est fini avant de réinitialiser alors tout devrait être bon. Notre boucle principale de "détection d'impulsions" ressemble maintenant à ceci :

while ((!(PCIFR & bit(PCIF2)))&&(!(TIFR2 & bit(OCF2B)))) {}

En gros, j'attends dans la boucle que la broche change ou que le chronomètre atteigne le seuil supérieur de 8 microsecondes. Puis à la fin, j'ajoute ce qui suit :

while (!(PIN_READ_DATA_PORT & PIN_READ_DATA_MASK)) {};
PCIFR |= bit(PCIF2);

Bingo ! Le jeu a démarré ! Vous ne pouvez pas croire à quel point j'étais heureux ! Alors que ce qui précède semble n'être que quelques jours de travail, j'y ai travaillé soir après soir, en rentrant du boulot, pendant environ un mois pour arriver à ce résultat.

Cette protection contre la copie utilisait deux méthodes, d'abord, des secteurs valides que tout pouvait lire correctement, puis des données cachées dans l'espace entre les secteurs, mais cachées entre les zones non formatées. Ces données ne seraient normalement pas copiées.

Maintenant, je ne dis pas que cela va contourner toutes les protections anti-copie, mais ce sont les seules que j'ai pu essayer, et elles fonctionnent toutes les deux. Si quelqu'un en a d'autres qui ne fonctionnent pas avec ça, nous devrions voir si je pourrais obtenir une copie et l'essayer.

Mais pour l'instant, j'ai terminé. J'ai eu beaucoup de plaisir à essayer d'atteindre cet objectif impossible, et je suis heureux de ne pas avoir abandonné. Ces petits appareils Atmel peuvent faire tellement plus que ce à quoi vous vous attendez si vous prenez le temps d'étudier la fiche technique. Ils me rappellent un peu l'Amiga, à leur manière. Ils ont beaucoup d'options spéciales comme des chronomètres, des compteurs de surveillance, etc. disponibles au niveau matériel qu'il suffit d'activer, laissant le processeur faire d'autres choses. Une grande partie du code que je vois sur Internet me rend triste. Je vois que le PWM est fait manuellement alors que le matériel peut le faire. Je vois des gens qui veulent compter les impulsions de manière externe, et se retrouvent dans des boucles, et parfois des interruptions, alors que rien de tout cela n'est nécessaire. Si vous vous intéressez à la programmation embarquée, je vous conseille vivement de lire la fiche technique et le guide de programmation ! Il y a tellement plus pour ces petits appareils.

Dites NO à CH340

J'ai décidé de faire quelques vérifications sur les différences entre les convertisseurs USB/série FTDI et CH340. En bref, les deux fonctionnent parfaitement pour de petites quantités de données (comme quelques octets), mais pour des flux/grandes quantités de données, le CH340 ne peut pas faire face et les données commencent à être corrompues. Donc, n'utilisez pas de convertisseur CH340 pour ce projet.

En même temps, j'ai testé le convertisseur USB/série sur l'Arduino UNO. Même problème. Donc, pour ce projet, vous devez utiliser le convertisseur FTDI.

Kickstart 1.3

N'ayant jamais possédé d'Amiga avec quelque chose d'antérieur au Kickstart 2.04, je n'ai jamais vraiment eu l'occasion de le tester. C'était jusqu'à ce que j'essaie d'écrire quelques ADF sur de véritables disquettes pour pouvoir les essayer. Les disquettes étaient de vieilles démos, très bonnes, mais elles n'étaient pas compatibles avec le Kickstart 2+. J'ai trouvé une de mes vieilles disquettes qui contenait une copie de Relokick et j'ai redémarré en Kickstart 1.3.

Bizarrement, les disquettes que j'avais créées ne se chargeaient pas du tout. C'était comme si le système d'exploitation ne détectait rien. Après être retourné au code et avoir comparé les données d'une disquette qui fonctionnait et d'une disquette que j'avais écrite, j'ai découvert quelque chose de très intéressant.

A l'époque où je travaillais sur le lecteur, j'avais lu qu'il y avait un bogue dans le Kickstart 1.3 et que certaines disquettes étaient codées avec 2AAAAAAA44894489 (commençant par un 2, pas un A). Après un examen plus approfondi, j'ai vu que ce n'est pas un bogue. Le "2" a dû être placé à cet endroit, sinon la séquence MFM se serait retrouvée avec deux "1" d'affilée, et cela n'est pas autorisé.

Je sais que le trackdisk.device du Kickstart 2 et plus a été réécrit, donc je suppose qu'ils ont ignoré la partie 2AAAAAAA et ont juste cherché le mot de synchronisation (4489). Après avoir corrigé cela et réécrit la disquette, tout a fonctionné parfaitement.

WinUAE - mais plus rapide ?

J'ai été très satisfait de l'accès aux disquettes de WinUAE, et à l'époque, mon code n'était pas disponible publiquement. J'avais cependant distribué des copies à quelques personnes pour des tests supplémentaires.

Un de ces testeurs est membre de la scène démo et avait indiqué que certaines de ces démos faisaient des choses folles avec l'accès aux disquettes. Une démo spécifique dont il m'a parlé s'appelle State Of The Art de Spaceballs.


State Of The Art

J'ai donc décidé de télécharger l'ADF de cette démo, de l'écrire sur une disquette et de l'essayer sur mon Amiga. C'est vraiment une démo intéressante, d'un point de vue technique. Dès que la disquette est insérée, la démo démarre, presque instantanément, et pendant qu'elle tourne, elle charge les séquences suivantes en arrière-plan. Toute la démo est chronométrée de façon à ce que les animations et les effets durent suffisamment longtemps pour que les données soient lues sur la disquette.

Pour le plaisir, je l'ai essayé sur WinUAE. Ce n'était pas très bon. La disquette ne pouvait tout simplement pas suivre. Je n'ai pas été surpris, regardons les choses en face, il y a beaucoup de choses à faire pour que cela fonctionne.

Ainsi, je me suis demandé si je pouvais accélérer l'accès à la disquette sans affecter les performances de WinUAE. J'avais besoin d'une base de référence à mesurer.

Ma ligne de base est d'utiliser la disquette de Captain Planet. J'ai chronométré le temps écoulé entre l'apparition de la fenêtre de WinUAE et le début de la musique du jeu, soit 21 secondes seulement :


Captain Planet sur WinUAE, du début à la musique, 21,23 secondes

Il y a plusieurs facteurs qui affectent cette lenteur. Les principaux sont que lorsque l'émulateur passe du côté piste/disquette, je commence à lire les données instantanément. Mais elles ne seront pas disponibles pour l'émulateur avant environ 200 millisecondes dans le meilleur des cas. L'émulateur rejoue ensuite ces données, ce qui prend 200 millisecondes supplémentaires. Le problème est que le code doit extraire une rotation complète de la disquette pour que WinUAE voie une piste complète qui tourne parfaitement en boucle.

J'y suis parvenu en notant où l'impulsion /INDEX se trouvait sur la disquette, puis en capturant toutes les données entre ce moment et celui où elle se produit à nouveau. L'impulsion /INDEX n'est pas parfaitement alignée, mais elle est suffisamment proche pour que nous puissions faire une comparaison approximative afin d'aligner parfaitement les données.

Donc, avant même de commencer à lire les données à envoyer à l'émulateur, nous devons attendre la première impulsion /INDEX. Dans le pire des cas, c'est 200 millisecondes. Donc, pour une seule piste, cela peut prendre jusqu'à 600 millisecondes avant qu'elle ne soit traitée par AmigaOS, dans le meilleur des cas, environ 400 millisecondes.

Je me suis demandé s'il y avait un moyen d'éviter cela et de capturer une rotation complète sans les impulsions /INDEX. J'ai donc entrepris de réécrire le code, car c'était un peu le bazar, et maintenant que j'ai compris comment tout cela fonctionne, je pourrais probablement l'écrire un peu mieux.

Je me suis dit que je connaissais le temps nécessaire pour chaque cellule binaire, et que je pouvais donc inventer un marqueur /INDEX virtuel en chronométrant environ 200 millisecondes de données et en corrigeant ensuite le résultat.

Eh bien, cela n'a pas très bien fonctionné. Il s'avère, et j'aurais dû m'en rendre compte, que tous les lecteurs ne tournent pas exactement à 300 tours par minute, ce qui ne correspond pas exactement à 200 millisecondes.

J'ai donc décidé d'utiliser les impulsions /INDEX pour déterminer la durée d'une rotation, puis d'utiliser cette information pour mieux aligner une piste. Cela a fonctionné, et après quelques ajustements, j'ai obtenu un chargement légèrement plus rapide et amélioré. Nous sommes passés de 21,23 secondes à 19,5 secondes, soit un gain de près de 2 secondes !


Captain Planet sur WinUAE, du début à la musique, 19,5 secondes

Pas mal. Après avoir regardé quelques disquettes démarrer, j'ai eu quelques autres idées. Tout d'abord, chaque fois que l'émulateur demande quelque chose au lecteur, le code interrompt la lecture de ce qu'il est en train de lire. Les pistes incomplètes sont rejetées. Donc, si au moment de la demande d'interruption, la piste était complète à 90% ou plus et que le code n'avait pas d'enregistrement de cela auparavant, alors l'interruption était ignorée et la lecture de la piste pouvait s'achever. Techniquement, cela pourrait ralentir le processus d'environ 20 millisecondes, mais il s'agit d'un cas limite tellement petit qu'il accélérera la lecture de la piste par la suite.

Après avoir mesuré cela, je n'ai pas vraiment vu de différence. Alors que pouvais-je faire d'autre ? J'ai remarqué que les jeux que j'avais semblaient être bien optimisés. Typiquement, ces disquettes voyaient une piste, la lisaient, puis passaient à l'autre tête de lecture (supérieure ou inférieure) et la lisaient.

J'ai décidé qu'après avoir extrait une rotation complète et l'avoir présentée à l'émulateur, pendant que le système d'exploitation la lisait, je changerais de côté et la lirait aussi. C'est un temps mort, autant l'utiliser à quelque chose. Je pense que c'est ce qu'on appelle le "cache read-ahead" (cache de lecture anticipée), ou du moins c'est comme ça que je l'appelle. Pour les disquettes qui ne sont pas optimisées, cela ne fait aucune différence, mais essayons notre test de performances.

J'ai démarré la disquette, et oh, 16,14 secondes ! C'est presque 5 secondes plus rapide que la première tentative, et 3,5 secondes plus rapide que la tentative précédente :


Captain Planet sur WinUAE, du début à la musique, 16,14 secondes

Donc avec ces changements, pour ce jeu, j'ai accéléré l'accès d'environ 24%. C'est génial ! Ne vous méprenez pas, nous ne lisons pas la disquette plus rapidement, nous la lisons juste plus efficacement. Maintenant, qu'en est-il de la démo originale que j'avais essayer ? Eh bien, elle fonctionne beaucoup mieux, elle ne peut toujours pas garder le rythme mais c'est beaucoup mieux. Pour référence, sur une vraie machine, Captain Planet prend un peu plus de 12 secondes, donc il y a encore beaucoup d'améliorations qui pourraient être faites... si je pouvais juste trouver comment...

Une dernière chose, j'ai décidé d'appeler toute cette technologie (pour le plaisir) "RIT" (comme JIT - Just in Time) qui signifie "Read in Time(ish) (lire en temps réel).

La fantastique vidéo par RetroManCave - The Cave (juin 2021)

Je suis sûr que la plupart d'entre vous ont découvert mon projet grâce à la fantastique vidéo que Neil Thomas de RMC The Cave a réalisée en juin 2021.

En tant que mécène de longue date de sa chaîne YouTube, je me suis dit que ce serait bien qu'il soit celui qui annonce officiellement la nouvelle concernant WinUAE et j'étais en contact avec Neil Thomas pendant environ un mois avant de lui envoyer quoi que ce soit. Le premier envoi fut la carte Nano qu'il a brièvement montré dans une de ses vidéos, et comme il a reconstruit sa cave dans un nouvel endroit, il n'a pas pu prendre le temps de l'examiner jusqu'à ce que je lui envoie un lecteur slimline.

Travailler avec Neil Thomas a été fantastique. Nous nous sommes rapidement appelés sur Discord pour parler du projet, puis nous n'avons plus eu qu'à attendre, même si je n'avais aucune idée de ce qu'il y aurait dans la vidéo avant qu'il ne la diffuse.

Comme vous l'avez vu, la vidéo est géniale et Neil Thomas a fait un très bon travail en racontant l'histoire de la bonne manière. Je pense qu'aucun de nous ne s'attendait à ce que la vidéo soit visionnée autant de fois, et je remercie tout le monde pour le soutien. Je me fraie lentement un chemin dans la liste d'attente, alors soyez patients.

Un nouveau nom ?

Après plusieurs années à l'appeler par un nom long et dingue avec "Arduino" et "disquette", un nom plus recherché est apparu sur Discord... DrawBridge.

"Disk Reader And Writer BRIDGE" (entre le lecteur et l'ordinateur).

Fonction "Pas de clic" dans WinUAE (juin 2021)

J'ai fait quelques expérimentations et bien que tout le monde soit enthousiaste à l'idée d'entendre le lecteur cliqueter, il était temps de faire fonctionner la fonction "no-click" (pas de clic). C'était en fait très simple car elle est déclenchée lorsque l'émulateur essaie de passer à la piste -1. Ce n'est pas autorisé. Donc tout ce que j'avais à faire était de capturer cet événement (qui était ignoré) et de demander au lecteur de faire la même chose. Et ça a marché ! C'était facile...

Écriture améliorée à partir de WinUAE (juin 2021)

J'ai donc expérimenté l'écriture sur disquette dans WinUAE. Certaines écritures étaient intermittentes. En fait, l'écriture était correcte, mais diverses applications signalaient des erreurs.

Le problème est qu'il faut environ 200 millisecondes pour collecter les données de l'émulateur, puis 200 millisecondes pour les écrire sur la disquette, puis encore 200 millisecondes pour les relire, et enfin 200 millisecondes pour que l'émulateur les relise. Le problème est qu'une fois que l'émulateur a soumis ses 200 millisecondes de données, il les relit instantanément pour les vérifier. Mais je ne pouvais pas encore fournir les données et donc je ne faisais que renvoyer mon "1" habituel (pas de données). Il s'avère que la plupart des opérations se sont arrêtées avant que les données ne soient disponibles. Ce n'est pas vraiment surprenant...

Alors comment contourner ce problème ? Il s'est avéré que la protection contre la copie ne se préoccupe probablement pas de l'écriture sur la disquette, et bien que cette solution ne fonctionnerait probablement pas dans toutes les situations, elle pourrait être utile ici.

Typiquement, lors de l'écriture sur la disquette, l'Amiga met en place un transfert DMA. Ceci est évidemment simulé dans WinUAE. Vous avez peut-être remarqué les réglages de la vitesse de la disquette, et bien, en fait, ils perturbent les vitesses de transfert DMA.

J'ai décidé de prendre les données du tampon mémoire DMA au moment où il démarre et que je les écrirais sur la disquette immédiatement. Ainsi, je me suis débarrassé du premier et du second délai de 200 millisecondes ! Mais je devais encore les relire. Je me suis dit que si je relisais les données, et seulement quand j'avais lu une rotation complète de données, je pourrais alors signaler à l'Amiga que l'opération DMA est terminée.

Étonnamment, cela a fonctionné ! J'ai essayé la copie de la disquette avec X-Copy. Cela a fonctionné. J'ai même essayé de faire la copie d'une vraie disquette vers une autre... Incroyable, ça a aussi marché ! C'était assez facile...

Comment DrawBridge a sauvé les Lemmings ! (juillet 2021)

Alors que je travaillais sur la gestion des disquettes haute densité (HD), en coulisses, le lecteur que j'avais envoyé à Neil Thomas de RetroMan Cave a voyagé de son côté. Il a fini dans les mains de Mike Dailly (programmeur de jeux, créateur de Lemmings et Grand Theft Auto) qui l'a utilisé pour récupérer les données des disquettes contenant les éditeurs de niveaux originaux de Lemmings. C'est vraiment génial !


Une trouvaille pour Lemmings

Disquettes HD - Les rendre compatibles (juillet 2021)

Je n'avais pas vraiment envisagé la gestion des disquettes HD lorsque j'ai commencé ce projet. N'ayant jamais possédé un Amiga avec un lecteur de disquettes HD, cela ne m'a jamais effleuré. Les commentaires dans la vidéo produite par RetroMan Cave ont montré que cela était très nécessaire.

Premier problème cependant, comment pourrais-je savoir si je lis et écris correctement une disquette HD Amiga si je ne possède pas un lecteur pour le tester. Les lecteurs de disquette HD pour Amiga sont vraiment difficiles à trouver. Ils n'étaient inclus que dans certains des plus gros Amiga, et vous ne pouviez pas simplement utiliser un lecteur de disquette HD de PC.

Avec une disquette HD, les données sont codées à deux fois la vitesse. Ces synchronisations de 4, 6 et 8 microsecondes deviennent 2, 3 et 4 microsecondes. Paula, la puce dans l'Amiga qui est responsable de la lecture des données MFM du lecteur, ne pouvait pas gérer le taux de données plus rapide et Commodore a donc construit des lecteurs de disquette spécifiques pour l'Amiga qui, lorsqu'ils détectent une disquette HD, font tourner la disquette à la moitié de la vitesse (150 tr/min au lieu des 300 tr/min habituels). Cela permet de recevoir les données à la même vitesse pour les disquettes DD et HD.

Il y a plusieurs projets qui prétendent que l'on peut modifier un lecteur de disquette Amiga normal car les pastilles pour les interrupteurs sur la carte mère sont présents. Malheureusement, cela n'a pas fonctionné pour mon A500+ ou mon A1200.

J'ai eu une longue discussion à ce sujet sur mon serveur Discord. Mon plan initial était au moins de demander à quelqu'un de m'envoyer une disquette HD écrite à partir d'un vrai Amiga, mais même cela s'est avéré difficile.

C'est alors que deux membres de mon serveur Discord sont venus à la rescousse. Nous avions tous entendu dire que certains lecteurs Sony avaient été modifiés pour tourner à cette vitesse plus lente, mais les détails n'étaient jamais présents. Hany Tahvilzade et Siamak "Siliconix" Habibzade m'ont envoyé une disquette Sony modifiée. La solution se présente en deux parties, une carte d'interface spéciale qui se branche sur le connecteur de la disquette, et un recâblage interne vers une carte personnalisée. Je n'ai aucune idée de comment cela fonctionne, et il serait injuste pour moi d'essayer. Je suis très reconnaissant envers ces deux personnes de m'avoir envoyé ça. J'ai depuis imprimé un boîtier 3D pour cela et ajouté un câble pour qu'il puisse agir correctement comme un lecteur de disquette HD Amiga externe.

Ok, donc j'avais un lecteur. Première question, comment l'Amiga sait-il qu'une disquette HD est présente ? Le lecteur de disquette Amiga n'a pas de broche "c'est une disquette HD". Alors comment le sait-il ? On dirait de la magie.

Eh bien, les disquettes HD ont été gérées à partir du Kickstart 2.04, et la façon dont elles sont détectées est en fait assez simple. En lisant cette page (qui ne mentionne pas la haute densité) et ce fil de discussion :

Les lecteurs Amiga ont une séquence d'identification du lecteur que vous utilisez pour déterminer s'il y a un lecteur connecté.

1 - Allumez le moteur, puis éteignez-le. Cela réinitialise le port de décalage de l'identification du lecteur. Désactiver SELxB*.
2 - Activez SELxB* et lisez la ligne RDY* pour un bit de l'identification. Désactiver SELxB*.
3 - Boucle à 2 jusqu'à ce que vous ayez 32 bits.

Quelques valeurs :

$00000000 = aucune disquette connectée.
$FFFFFFFF = disquette 3,5" sans média ou média double densité (DD).
$AAAAAA = disquette 3,5" avec média HD (lecteurs haute densité uniquement).
$55555555 = disquette 5,25 pouces.

Voilà donc la magie qui se produit, et qui est contenue quelque part dans les modifications de ce lecteur.

Mise à jour de DrawBridge pour HD - Écriture de disquettes, tentative 1 (août 2021)

Maintenant que j'ai accès aux disquettes HD Amiga, je me suis tourné vers la lecture et l'écriture de ces disquettes dans DrawBridge. J'ai d'abord essayé l'écriture, vu que le code d'écriture pour les disquettes double densité était maintenant basé sur des chronomètres matériels, je savais que cela pouvait être fait avec précision et sans trop d'effort.

J'ai commencé à écrire une version de la fonction d'écriture pour disquettes haute densité. La seule différence est que, maintenant, j'envoyais deux fois plus de données qu'avant. Cela signifiait qu'il n'y avait plus assez de bande passante pour envoyer les informations supplémentaires pour la précompensation d'écriture. Ce n'est pas grave.

On m'a envoyé un lien vers Aminet qui contenait six fichiers ADF, trois en DD et trois en HD.

Pour écrire un de ces fichiers ADF HD, je devais d'abord comprendre la différence. La seule différence étant qu'il y avait deux fois plus de secteurs par piste. J'ai donc modifié le code pour qu'il comprenne et gère cette différence, ce qui est assez trivial.

Enfin, j'ai utilisé un analyseur logique pour vérifier que les impulsions que je créais étaient bien à 2, 3 et 4 microsecondes. Il s'agit d'une disquette PC, nous ne pouvons pas la faire tourner à 150 tr/min, nous devons travailler à 300 tr/min.

J'ai écrit une disquette, je l'ai essayé dans l'Amiga, et étonnamment ça a marché ! Bien que je ne pouvais pas écrire avec l'option "verify", parce que je n'avais pas encore écrit le code de lecture HD.

Mise à jour de DrawBridge pour HD - Lecture des disquettes (août 2021)

La lecture des données semblait devoir être assez simple également. Une fois de plus, il n'y avait pas assez de bande passante disponible pour envoyer des données brutes de synchronisation de flux, mais je pouvais renvoyer mon flux binaire MFM détecté. Cela ne fonctionnerait jamais pour des disquettes HD protégées contre la copie, mais je doute que des jeux protégés contre la copie n'aient jamais été distribués sur des supports HD.

Ma première tentative a complètement échoué. En partie à cause du fait que tout le code qui vérifiait les secteurs Amiga valides attendait 11 et non 22. Une fois que cela a été mis à jour, j'ai réessayé, mais je n'ai toujours pas réussi, en fait je n'ai même pas pu relire un seul secteur.

J'ai commencé à simplifier le code côté Arduino, en supprimant les parties qui, bien qu'utiles, n'étaient pas 100% essentielles. J'ai regardé la boucle principale, qui attendait une impulsion de la disquette :

while ((!(PCIFR & bit(PCIF2)))&&(!(TIFR2 & bit(OCF2B)))) {}

Cela fonctionnait bien pour les disquettes double densité, alors pourquoi pas pour les disquettes haute densité ? Eh bien, il s'est avéré que je n'avais tout simplement pas la bonne fréquence d'horloge pour cela. Je m'explique : pour arriver à 2 microsecondes, il y avait un maximum de 32 tics d'horloge, en supposant que les instructions étaient à 1 tic (certaines ne le sont pas), c'était un maximum théorique de 32 tics d'horloge/cycles que je pouvais gaspiller avant de vérifier la valeur du compteur.

En ignorant les 3 et 4 microsecondes, il y avait un problème ici. Si je prends le code ci-dessus, et que je le désassemble, nous avons (et je l'ai commenté) :

sbic 0x1b, 2
(1/2) Sauter l'instruction suivante si le bit 2 est CLEAR dans le registre d'E/S 0x1B (PCIFR) n'est pas activé.
rjmp .+4
(2) Retourner à la ligne ci-dessus
sbis 0x17, 2
(1/2) Sauter l'instruction suivante si le bit 2 est SET dans le registre d'E/S 0x17 (TIFR2) n'est pas activé.
rjmp .-8
(2) Retour à la première ligne

Cela reste dans ces quatre instructions jusqu'à ce que PCIFR soit activé (une impulsion est arrivée) ou que le Timer2 déborde (c'est-à-dire quand il n'y a pas de disquette). Qu'est-ce qui ne va pas avec ça ? Eh bien, ce n'était pas fiable. J'ai ajouté du code pour faire basculer une autre broche lorsqu'une impulsion était détectée, et le résultat était très instable.

Les chiffres entre parenthèses ci-dessus représentent le nombre de cycles d'horloge que chaque instruction prend pour fonctionner. Ainsi, en exécutant le code et en le parcourant ligne par ligne, si une impulsion arrivait alors que nous n'étions pas à la première ligne, elle pouvait prendre jusqu'à cinq cycles d'horloge supplémentaires pour être effectivement détectée et gérée.

C'était loin d'être idéal. A 16 MHz, la vitesse d'horloge de l'Arduino, il y avait littéralement un maximum de 16 tics d'horloge entre la détection d'une impulsion de 2, 3 ou 4 microsecondes. Quelque chose devait se produire.

J'ai supprimé le deuxième test. Oui, cela signifiait que le code resterait bloqué si vous retiriez la disquette, mais c'était mieux que rien.

Après avoir regardé les données entrantes, je commençais à voir quelque chose qui ressemblait à des données, mais ce n'était pas parfait. La fluctuation a maintenant été réduite à environ +/- trois cycles d'horloge.

Mise à jour de DrawBridge pour HD - Lecture des disquettes, tentative 2 (août 2021)

A cause de la fluctuation, je me suis demandé si je pouvais revenir à mes idées originales d'il y a quelques années et faire en sorte que la détection d'impulsion soit commandée par interruption.

En raison d'une décision de conception dont je n'étais pas conscient à l'époque, la broche que j'utilisais pour la lecture, ne pouvait être desservie que par l'interruption de la broche partagée. Ce n'était pas un problème, car il n'y avait qu'une seule broche sur laquelle nous voulions effectuer un déclenchement. Le problème est que vous ne pouviez pas choisir si elle était détectée sur un front montant ou descendant, c'était littéralement un déclencheur de changement d'état de la broche.

Pas de problème, je pouvais en tenir compte. Mais je savais déjà que la surcharge d'une interruption standard était beaucoup trop élevée. Pouvais-je en coder une plus rapide ?

J'ai implémenté une routine de service d'interruption (ISR) nue, poussant et sautant juste les registres que je changeais. J'ai inclus le code pour basculer une broche sur l'Arduino lorsqu'une impulsion était détectée afin de pouvoir vérifier qu'il réagissait correctement.

Il y avait encore de la fluctuation. Moins de fluctuation, mais elle était toujours là, et toujours les +/- trois cycles d'horloge. C'est étrange ! J'ai donc commencé à relire la fiche technique de l'ATMega328. Avant qu'une ISR soit réellement exécutée/servie, l'instruction en cours doit être terminée. La boucle principale à ce stade surveillait le débordement du chronomètre à nouveau, vu que je pouvais être capable de le faire. L'instabilité provenait maintenant de l'instruction que le processeur exécutait réellement au moment où l'impulsion se produisait.

Avec la surcharge supplémentaire, et le fait que cela n'a pas amélioré les résultats, j'ai abandonné le gestionnaire d'interruption.

Mise à jour de DrawBridge pour HD - Lecture des disques, tentative 3 - Succès ! (août 2021)

Maintenant que j'étais plus conscient du temps que prenaient les instructions, j'ai commencé à regarder si le code était capable de s'exécuter assez rapidement pour commencer, dans le pire des cas, avec les boucles, et ce que je pouvais faire pour accélérer les choses.

J'ai à nouveau désassemblé le code pour voir ce qu'il se passait. J'ai créé un organigramme montrant les instructions possibles pour capturer chaque cellule de bit, et j'ai calculé le nombre de tics d'horloge requis par chaque route, pour 2, 3 et 4 microsecondes.

L'implémentation actuelle était basée sur plusieurs instructions if. Bien que la fonction soit évidente, l'examen du code désassemblé était loin d'être idéal. Le compilateur essayait d'optimiser certaines choses d'une manière que je n'aurais pas préférée, et en tant que tel, il y avait plus d'instrutions RJMP (et donc de goto) dans le code que nécessaire.

J'avais un plan pour changer la façon dont je traitais cela. Heureusement, les 2, 3 et 4 microsecondes étaient exactement divisibles par 16, et correspondaient parfaitement à la fréquence de l'Arduino.

Le calcul de base dont j'avais besoin, pour savoir quel type de séquence (2 microsecondes=01, 3 microsecondes=001 et 4 microsecondes=0001) était en fait très simple. J'ai pris la valeur du chronomètre, je l'ai décalée pour que la cellule de bit tombe au milieu et je l'ai divisée par 16.

Il est intéressant de voir comment le compilateur a optimisé cela. En interne, il n'a pas fait de division par 16, il a appelé "swap" qui a échangé les bits inférieurs et supérieurs. Il a ensuite fait un "anded" avec "0x0F" pour ne garder que le demi-octet inférieur.

Les nombres correspondants étaient maintenant, 0&1=01, 2=001 et 3=0001. Pour la plupart des branches, cela fonctionnait un peu plus vite. Mais cela n'a toujours rien résolu.

Alors quoi d'autre ? J'ai commencé à regarder les autres chronomètres. Ignorant le Timer1, qui est un chronomètre 16 bits, j'ai regardé le Timer0. J'ai découvert quelque chose d'étrange. On pouvait aussi accéder au Timer0 en utilisant les instructions assembleur "IN" et "OUT", plutôt que "LDS" et "STD". LDS et STD nécessitent chacune deux cycles d'horloge par opération, alors que IN et OUT n'en nécessitent qu'un chacun. Il y avait deux cycles d'horloge que je pouvais économiser instantanément.

J'ai fait ce changement, et ouah, j'ai réussi à relire parfaitement la disquette que j'avais écrite précédemment. Je savais que cette disquette se lisait correctement sur un vrai Amiga, je savais donc que je pouvais maintenant lire correctement les disquettes Amiga. Pourquoi cela m'a-t-il aidé ? Eh bien, j'ai commencé à réaliser qu'en enlevant deux cycles d'horloge, cela rendait effectivement le code plus rapide, mais plus important encore, TIMING_OVERHEAD, qui était de -6, a effectivement compensé de manière incorrecte. Il était maintenant inférieur de -2, ce qui donnait -8. "8" étant le chiffre magique, parce que si nous étions à une cellule de bit, le seuil entre 2 et 3 microsecondes est de +/- 8 tics d'horloge. Cela signifiait probablement que c'était encore faux pour les disquettes double densité, mais comme ils fonctionnaient, je l'ai laissé tel quel.

Donc, à présent essayons quelques disquettes écrites sur Amiga...

Mise à jour de DrawBridge pour HD - Lecture des disquettes, tentative 4 (août 2021)

J'ai démarré mon A1200, après avoir branché le lecteur de disquette HD, j'ai formaté une disquette et j'ai commencé à la remplir avec tout ce que je pouvais trouver dans mon Workbench. Quelques instants plus tard, j'avais rempli la disquette.

J'ai ensuite inséré la disquette dans mon lecteur et j'ai commencé à essayer de la lire. Bizarre, j'ai obtenu un succès partiel. Le lecteur lisait certains secteurs parfaitement, mais d'autres non. Cela n'avait pas vraiment de sens. Je savais que je pouvais lire une disquette, comme je l'avais fait précédemment, alors qu'est-ce qui n'allait pas avec celle-ci ?

J'ai décidé de vider la disquette à l'aide de Greaseweazle et de l'examiner sous l'analyseur de pistes de HxCFloppyEmulator.

DrawBridge SCP

Les zones bleues, vertes et jaunes représentent les trois groupes de cellules binaires. La ligne rouge est la position idéale où ces chronomètres devraient être détectés. Les lignes de la grille représentent les 16 valeurs de temporisation entre chaque fenêtre. Le bleu, le vert et le jaune plus foncés représentent les zones qui sont très proches de la limite d'une autre fenêtre, qui pourrait être affectée par notre fluctuation.

C'était un ensemble de lignes ondulées ! Je crois que le terme technique est "pleurage et scintillement" ou "flottement" (flutter). Cela se produit avec tous les supports rotatifs (y compris les vinyles), mais cette fois-ci, c'était assez extrême. Remarquez que certaines des cellules binaires se trouvent dans les zones les plus sombres. Pas étonnant que je ne puisse pas le lire. Cela peut être le lecteur ou la disquette, ou les deux.

Je me suis demandé pourquoi j'étais capable de lire le mien. J'ai décidé de faire la même chose avec l'une des disquettes que j'avais écrites :

DrawBridge SCP

Il y avait moins de données, c'était à prévoir, c'était une disquette vierge formatée, mais le pleurage et scintillement était bien moindre ! Alors comment l'Amiga a-t-il réussi à lire les deux disquettes ?

Eh bien, j'imagine que c'est une combinaison de deux facteurs. Premièrement, la disquette a été relue sur le même lecteur, et comme le pleurage et scintillement correspondait à l'enregistrement, pour l'Amiga, il devait sembler plus plat. Deuxièmement, Paula (la puce qui gère la lecture) avait une boucle à verrouillage de phase de base implémentée à l'intérieur. Elle ajustait dynamiquement la vitesse de l'horloge, déplaçant ainsi ces fenêtres de manière dynamique au fur et à mesure que la disquette était lue, en chevauchant effectivement ces ondes.

Pourrais-je implémenter quelque chose comme ça ? Je pense que oui, mais il n'y aurait pas beaucoup de place pour le mouvement, ce ne serait que +/- quelques unités de chronomètre basées sur notre vitesse.

Une deuxième considération était la vitesse de la correction. Si elle était trop lente, elle serait inutile, trop rapide, elle corromprait tout autant les données. Si je devais implémenter cela sur le code des disquettes double densité, je devrais faire attention car cela pourrait affecter la protection contre la copie, mais avec la haute densité, je ne m'en souciais pas.

Mise à jour de DrawBridge pour HD - Lecture des disques, tentative 5 (août 2021)

Je savais maintenant que ce qui précède était un cas extrême, et que toutes les disquettes PC haute densité que je possédais avaient l'air parfaites après les avoir vues dans HxCFloppyEmulator Track Viewer. Cela signifiait que la généreuse disquette donnée n'était probablement pas le meilleur exemple de fonctionnement à 150 tr/min. Je soupçonnais qu'en le ralentissant, le moteur était probablement plus faible et donc qu'il y avait plus de pleurage et scintillement. Je me suis demandé si les lecteurs Commodore d'origine présentaient aussi cet attribut, mais en l'absence de données officielles, je n'avais aucun moyen de le savoir.

J'ai fait l'hypothèse que pour la plupart des disquettes, la solution trouvée jusqu'à présent serait probablement acceptable, super, j'avais donc un lecteur de disquette HD qui fonctionnait ! Mais je ne pouvais pas laisser tomber. Je voulais que cela fonctionne mieux.

Pour calculer et ajuster le chronomètre utilisé, pour simuler les effets d'une boucle à verrouillage de phase, j'avais besoin d'enregistrer dans le temps, de combien les 2, 3 et 4 microsecondes différaient/dérivaient du point central, puis de compenser par cette quantité de dérive.

Cela semblait assez simple, j'ai donc entrepris de calculer cette dérive. La dérive par échantillon a été facilement obtenue en utilisant ce qui suit :

7-(counter & 0x0F)

Cela pouvait fonctionner, car la division par 16 supprimait le quartet inférieur, et ce quartet inférieur contenait la valeur d'erreur dont nous avions besoin. Donc, sur quatre lectures de cellules de bit, j'ai ajouté cette valeur d'erreur. Cette valeur d'erreur devrait être de +/- 7, de sorte que la fluctuation, avec le temps, s'annule d'elle-même. S'il fonctionnait plus vite ou plus lentement, ce nombre dériverait dans le temps.

Lorsque ce nombre dérive trop, je remets à zéro le compteur total, puis j'augmente ou diminue une valeur de compensation en conséquence.

Voilà, c'était la théorie. J'ai commencé à le mesurer, et à sortir le résultat, afin de pouvoir le contrôler. Le résultat était instable, mais je ne savais pas pourquoi. J'ai commencé à me demander si le code ajouté ne l'avait pas trop ralenti. J'ai donc recodé tout ce bloc en assembleur. Après avoir réussi à le faire fonctionner, cela n'a toujours pas fait de différence, au contraire, cela a empiré les choses (vous pouvez tester cela vous-même en activant la définition ENABLE_PLL à la ligne 1766). Fatigué et vaincu, j'ai décidé que DrawBridge ne pouvait pas aller plus loin. La fluctuation causée par la lecture du chronomètre était le facteur limitant. L'aspect positif étant que la plupart des disquettes HD fonctionneraient probablement sans problème.

Mise à jour de DrawBridge pour HD - Lecture des disques, tentative 6 (août 2021)

Vous connaissez le cas quand vous avez une démangeaison mais que vous ne pouvez pas vous gratter ? Eh bien, une semaine plus tard, je n'étais toujours pas satisfait de cette situation. Je savais qu'il n'y avait rien que je puisse faire pour accélérer le code, mais avais-je manqué quelque chose ? L'ironie de mes propres commentaires m'avait frappé. À un moment donné, j'avais fait remarquer que ces périphériques avaient parfois été mal utilisés pour faire quelque chose que le matériel pouvait déjà faire. J'ai étudié à nouveau la fiche technique, et j'ai même passé plusieurs heures à la lire. Puis, j'ai remarqué quelque chose. Une chose que je n'avais jamais vu auparavant. Timer1, le chronomètre 16 bits. Je l'avais négligé parce que je n'avais pas besoin d'un compteur 16 bits, mais contrairement aux deux autres, celui-ci avait une caractéristique spéciale.

Le Timer1 pouvait être configuré en mode capture d'entrée. A chaque fois qu'une impulsion est détectée (vous pouvez choisir un front montant ou descendant), la valeur actuelle du chronomètre est copiée dans un registre spécial, un drapeau est activé et une interruption est déclenchée. Le meilleur là-dedans est que tout se passe au niveau matériel sans aucune boucle, automatiquement. Cela signifie qu'il n'y aurait pas de fluctuation et que le code en cours n'aurait aucune importance. Cela semblait génial, mais j'avais à présent un nouveau problème. La broche utilisée pour déclencher la capture d'entrée était la broche 8. La broche de lecture de données était la broche 4, et la broche 8 était utilisée pour détecter si la tête de lecture était sur la piste 0. Il s'agit d'une décision de conception que j'ai prise il y a environ quatre ans, lorsque je débutais avec Arduino. Le choix était purement basé sur l'esthétique pour dessiner le schéma du circuit. La seule façon de faire fonctionner cette configuration était de changer la configuration des broches pour DrawBridge...

Présentation de DrawBridge Plus (août 2021)

J'ai décidé que l'approche la plus simple serait de simplement intervertir la broche 4 et la broche 8, et que pour maintenir la compatibilité ascendante, je devrais ajuster le code pour gérer les deux configurations. C'était la partie facile. L'ATMega328 possède une petite EEPROM programmable par l'utilisateur où je pouvais stocker les paramètres.

J'ai décidé de nommer cette nouvelle configuration DrawBridge Plus, un clin d'oeil à mon premier Amiga, l'A500+. Au début, j'étais préoccupé par la différence de conception, mais je me suis vite rendu compte que 99% des gens ne veulent pas de la gestion des disquettes haute densité, et que le 1% restant n'a peut-être pas besoin de la précision supplémentaire de toute façon. Cela dit, j'ai décidé de continuer quand même.

Tout ce qu'il faut faire pour passer à cette version est d'échanger les broches 4 et 8, puis d'utiliser mes outils logiciels pour programmer la valeur des paramètres de l'EEPROM afin de signaler cet échange. Je pourrais rendre cela très facile à faire pour tout le monde.

Je me suis lentement frayé un chemin à travers le code en produisant une seconde version de chaque bout de code qui lit les données, pour la nouvelle configuration des broches. La seule différence réside dans le fait que j'attendrais qu'un registre différent soit activé et que je calculerai ensuite la différence entre le temps enregistré et le temps enregistré précédent. De plus, la valeur de TIMING_OVERHEAD ne serait plus pertinente puisqu'il n'y aurait plus de surcharge.

C'était facile, je me sentais confiant. Revenons maintenant au code HD. J'ai commencé sans le code de la boucle à verrouillage de phase (PLL), je n'étais jamais sûr à 100% qu'il fonctionnait correctement de toute façon, et de plus, cette nouvelle méthode pourrait s'avérer suffisante. Malheureusement, ce n'était pas le cas. Il ne capturait toujours pas correctement tous les secteurs de la disquette. Encore une fois, je craignais que le code ne soit pas assez rapide, j'ai donc écrit une version en assembleur. Cela n'a fait aucune différence, mais c'était intéressant d'apprendre la façon de faire.

Retour à la boucle à verrouillage de phase. Cette fois, cependant, lorsque je l'ai mis en route, la disquette a été parfaitement lue ! Oui ! Parce qu'il n'y avait pas de fluctuation, le code était capable de suivre les fluctuations de vitesse pendant la rotation de la disquette et de les compenser.

Cela m'a rappelé l'un des schémas de protection contre la copie, qui plaçait des transitions de flux sur les bords entre les séquences MFM, et à cause de la boucle à verrouillage de phase, pouvait parfois être dupé pour être dans l'un ou l'autre côté. La copie de la disquette corrigeait ce problème.

J'ai décidé d'ajouter une boucle à verrouillage de phase également au code des disquettes double densité. J'ai décidé que n'importe qui devrait pouvoir le désactiver de la même manière que le mode DrawBridge Plus. Cela peut aider, mais au cas où cela causerait un problème, cela pourrait être désactivé.

Accélérer la lecture des disquettes de WinUAE (septembre 2021)

J'avais pensé à accélérer l'accès aux disquettes en lecture. Je pensais que cela serait utile plus tard pour les disquettes HD. Celles-ci ne sont généralement pas utilisées pour les jeux et il est donc très peu probable qu'une disquette HD utilise une protection contre la copie.

Je pensais également que si vous utilisiez les disquettes dans le Workbench par exemple, la protection contre la copie n'avait pas d'importance. WinUAE dispose d'un mode de vitesse pour les disquettes, qui comprend également un mode turbo. Ces modes indiquent au système d'exploitation qu'un transfert DMA a eu lieu beaucoup plus rapidement qu'il n'aurait dû.

Je me suis demandé ce qu'il se passerait si je pouvais en faire une option. Je n'ai pas trouvé comment faire fonctionner le mode turbo, mais j'ai découvert que je pouvais simplement dire à WinUAE que les données arrivaient beaucoup plus vite. J'avais une disquette (ma disquette Workbench modifiée) qui prenait exactement 29 secondes pour démarrer, et je l'ai donc utilisée comme disquette de test.

Résultat, la disquette a maintenant démarré en 25 secondes. Pas mal, il est temps de tester à nouveau la démo de Spaceballs...

Donc, Spaceballs, un bon point de repère pour cela était quand la musique originale s'arrêtait de jouer, quelle piste essayait de lire. En mode "Fast", c'était toujours la piste 18. Ok, dans le nouveau mode "Turbo"... nous sommes arrivés à la piste 23 ! Donc c'est beaucoup plus rapide (un vrai matériel irait jusqu'à 28/29, la démo ne devrait pas s'arrêter !).

En regardant cela, j'ai découvert un séquencement critique bizarre, et après l'avoir corrigé, la démo fonctionne à présent correctement tout le temps !

Gestion officielle dans Amiberry (septembre 2021)

J'ai régulièrement discuté avec le développeur d'Amiberry, la version d'UAE la plus populaire sur le Raspberry Pi. Nous avions testé des configurations pour que la gestion que j'avais implémentée dans WinUAE fonctionne sur Amiberry. Cette version est devenue très stable et a été publiée ! Merci beaucoup Dimitris Panokostas aka MidWan.

En route vers la haute densité dans WinUAE (octobre 2021)

Ainsi, armé d'une configuration DrawBridge et d'une configuration DrawBridge Plus, j'ai commencé à chercher ce qui devait se passer. Ma première idée était d'utiliser la broche de détection HD sur le lecteur pour détecter si une disquette HD avait été insérée. Eh bien, cela s'est avéré être un problème. Avec les lecteurs de disquette slimline, la broche s'abaisse pour une disquette double densité et reste élevée pour une disquette haute densité. Cependant, avec un lecteur de disquette PC normal de taille normale, la broche ne faisait rien. Elle restait en permanence à l'état haut. Après quelques lectures supplémentaires, j'ai découvert que cette broche n'est généralement pas connectée.

Je devais détecter manuellement si la disquette insérée était une disquette DD ou HD. C'était assez facile à faire, je faisais tourner le moteur, commençais à capturer des données, et comptais combien d'échantillons étaient à moins de deux ou trois microsecondes ou plus de cinq microsecondes. En me basant sur le plus grand nombre d'échantillons, je devrais être capable de détecter la densité. Après quelques expérimentations, cela a fonctionné la plupart du temps. La seule fois où cela n'a pas fonctionné, c'était avec une disquette non formatée. Comme les lecteurs PC ne pouvaient pas détecter si les disquettes étaient double ou haute densité, cela explique probablement pourquoi les disquettes PC vierges sont généralement formatées.

Mon algorithme pour la sélection de la densité était le suivant : si la broche de détection HD était basse, je supposais qu'il s'agissait d'une disquette double densité, sinon j'effectuais mon test.

Suite à ma précédente découverte, l'Amiga utilise cette séquence spéciale d'identification de la disquette pour identifier le type de support inséré. Cela semble être déclenché après un événement de signal de changement de disquette. Le problème était qu'au moment où cet événement était généré par le lecteur, j'avais besoin de savoir de quel type de support il s'agissait. WinUAE gérerait les signaux pour moi, mais je devais d'abord détecter le type de disquette comme par magie.

J'ai donc modifié le code de DrawBridge et, lorsque j'ai détecté un événement de changement de disquette, avant d'en informer WinUAE, j'ai effectué un test de densité, juste une rotation momentanée de la disquette. Après quelques modifications, cela a bien fonctionné. En insérant une disquette double densité et en essayant de la formater, on obtient 880 ko, et en la remplaçant par une disquette haute densité, on obtient 1760 ko ! Parfait. Et après quelques modifications pour les besoins supplémentaires en données, je pouvais maintenant également lire les disquettes depuis le Workbench.

Sur la base des améliorations de vitesse ci-dessus, j'ai configuré les disquettes haute densité pour qu'elles fonctionnent toujours dans ce mode Turbo. Elles étaient vraiment lentes sur le matériel d'origine, au moins de cette façon elles seraient plus rapides...

WinUAE officiel (octobre 2021)

J'étais en discussion avec Toni Wilen (le programmeur des versions officielles de WinUAE) depuis un certain temps maintenant. Ayant vu que cela fonctionnait pour lui, il était heureux de commencer à intégrer DrawBridge. Le principal point de discussion était le moyen le plus simple pour moi de continuer à mettre à jour DrawBridge sans avoir à faire recompiler WinUAE. Nous avons décidé que je produirais une "API". Essentiellement, je compilerais tout le code de Floppy Bridge dans une bibliothèque DLL, que WinUAE lirait et utiliserait si elle était présente. La plupart des changements seraient dans la DLL, et maintenant que j'ai la gestion des disquettes haute densité, il n'y aurait probablement pas d'autres changements.

Il y avait aussi de nouvelles options, et la façon dont il était configuré commençait à devenir désordonnée. Toni Wilen voulait travailler sur sa propre interface graphique pour cette interface et j'en étais satisfait.

Cela a évolué plusieurs fois, et nous sommes parvenus à une solution stable. Je fournis un accès aux profils de configuration, et WinUAE appelle une commande dans les DLL de mon API pour afficher les dialogues de configuration. C'est beaucoup plus facile.

Écriture sans fluctuation (octobre 2021)

En essayant d'améliorer la gestion des disquettes haute densité, je me suis demandé si la lecture serait améliorée si je pouvais écrire les données avec plus de précision. En regardant ce que j'écrivais avec un analyseur logique, j'ai pu constater que les impulsions que j'écrivais étaient fluctuantes d'environ +/- 125 ns. La raison principale en était la boucle que j'avais qui attendait que l'impulsion soit terminée. Le problème est que, comme auparavant, une boucle peut provoquer une fluctuation en fonction du moment où quelque chose se produit dans la boucle.

J'ai jeté un autre coup d'oeil à la fiche technique de l'ATMega328. Avant, j'avais eu l'idée d'utiliser le chronomètre à usage unique pour rendre les chronométrages précis. Cela me semblait correct, mais je me suis demandé s'il y avait un moyen pour que le chronométrage reste précis.

Il s'est avéré qu'il y en avait un, et que c'était en fait assez simple. En mode FAST PWM, qui est celui que j'utilisais, le chronomètre comptait jusqu'à un nombre, puis se remettait à 0.

J'ai découvert que si je calculais la durée totale de l'impulsion et que je la mettais à jour à chaque fois que l'impulsion était terminée, le chronométrage restait parfait. La raison en est que la valeur du chronomètre n'était jamais modifiée dans le code, seulement par le processeur, donc aucun tic n'était perdu ou devait être comptabilisé.

Pour que cela fonctionne, j'ai également supprimé l'utilisation du tampon série de 256 octets. Il utilisait en fait trop de temps pour faire ce qu'il faisait de toute façon. Donc maintenant, je dispose d'une écriture sans fluctuation pour les disquettes double et haute densité !

Une lecture encore plus rapide dans WinUAE (octobre 2021)

Il me restait deux fonctionnalités à implémenter. La première était AutoCache. L'idée était que tant que le système d'exploitation n'utilisait pas le lecteur de disquette, ou que nous avions déjà lu suffisamment de données, je demandais au lecteur de lire les pistes restantes en mémoire. J'ai choisi un ordre spécifique : pistes 0-10, puis 40-50, puis remplissage des espaces restants. C'était assez étrange d'entendre une disquette continuer à être accédée alors que le Workbench ne l'utilisait plus. Le code était conçu de telle sorte que si, à un moment donné, le système d'exploitation voulait accéder à la disquette, cette mise en cache en arrière-plan s'arrêterait instantanément, permettant un accès correct. À ma grande surprise, cela a fonctionné du premier coup !

L'autre fonctionnalité que je désirais implémenter était un peu plus difficile. Je voulais permettre à mon mode "Fast" (Turbo) de fonctionner dans autant d'endroits que possible. Je savais, en me basant sur le fonctionnement de certains systèmes de protection contre la copie, que si je devais l'activer en permanence, certaines disquettes ne pourraient pas démarrer du tout.

J'ai décidé que je pourrais analyser les données d'une piste juste après qu'elle ait été lue, et ensuite calculer la "vitesse" moyenne de ces données. Ensuite, je pouvais regarder à nouveau la piste et voir combien de données différaient de cette moyenne par un seuil spécifique. Si ce total atteignait un chiffre suffisamment élevé, je désactivais l'option "Turbo".

Eh bien, c'était intéressant, j'imagine que cela ne fonctionnerait pas avec toutes les disquettes, mais ma disquette Lemmings démarre maintenant plus vite dans WinUAE que sur un vrai Amiga d'environ 5 secondes !

Un mot sur Greaseweazle et SuperCard Pro (octobre 2021)

On m'a demandé à plusieurs reprises pourquoi j'avais choisi d'ajouter la gestion de Greaseweazle, un produit concurrent, à cette solution. Je n'ai pas été payé pour faire cela. Je voulais mettre la solution WinUAE à la disposition du plus grand nombre de personnes possible. C'est une solution Open Source, donc disponible pour tout le monde de toute façon.

La première fois que Keir Fraser (le créateur de Greaseweazle) en a eu connaissance, c'est lorsque je lui ai proposé une première version bêta. Cela faisait longtemps que les gens demandaient cette solution, mais personne n'avait relevé le défi. J'avais un peu peur qu'il ne réponde pas, car il s'agit d'un produit concurrent (bien que DrawBridge ait été le premier, et ne soit en aucun cas un produit concurrent), mais il a été très content, et m'a permis de l'annoncer quand je me sentais prêt. Entre la première version que je lui ai envoyée et la version finale, j'ai travaillé avec Keir Fraser pour que quelques commandes supplémentaires soient ajoutées au firmware du Greaseweazle (comme la gestion du changement de disquette et la possibilité de lire l'état de la protection en écriture si nécessaire), qu'il a ajouté avec plaisir. Pour ceux qui ne le savent pas, il fabrique également l'Amiga Test Kit, un outil utile pour diagnostiquer les pannes de votre Amiga.

Je travaille toujours avec Jim Drew pour ajouter la gestion de la SuperCard Pro. Jim Drew est dans le milieu des disquettes depuis très longtemps et il est extrêmement compétent en matière de disquettes, de protection contre la copie et de préservation des données. Il est également un membre actif de mon serveur Discord concernant la protection contre la copie, etc. Jim Drew m'a envoyé une carte SuperCard Pro pour aider au développement, ce dont je lui suis très reconnaissant. Cette carte n'était pas seulement utile pour travailler sur l'ajout de la gestion de DrawBridge, mais aussi très utile pour aider à développer la gestion des disquettes haute densité Amiga.

Ma chaîne YouTube (octobre 2021)

On m'a demandé plusieurs fois si j'avais une chaîne YouTube, et j'aurais dû y mettre tout mon journal de développement. J'ai finalement décidé d'oublier le son de ma propre voix et de lancer ma chaîne.

Mon objectif est d'essayer de publier une vidéo toutes les une à deux semaines pour parler de rétro, d'électronique, d'Arduino, de programmation, etc. Nous verrons comment cela se passe. Vous pouvez consulter le site www.youtube.com/c/RobSmithDev. N'oubliez pas de vous abonner !

Écriture de fichiers SCP (novembre 2021)

Cela me démangeait depuis un certain temps de pouvoir lire et créer un fichier SCP, à une résolution raisonnable, mais de ne pas pouvoir en écrire un en retour. J'étais bloqué par la résolution des données. Je suis tombé sur un message dans un forum où les gens discutaient de la raison pour laquelle les fichiers SCP ne pouvaient pas être utilisés sur le lecteur Gotek (principalement parce qu'ils étaient trop gros). Jim Drew a fait remarquer que pour la lecture, la résolution de 25 ns n'est pas vraiment nécessaire, et que la taille du fichier pourrait être considérablement réduite si la résolution était ramenée à quelque chose comme 100 ns, tout en continuant à fonctionner correctement.

Le problème que j'avais rencontré était qu'il n'y avait tout simplement pas assez de bande passante dans une connexion série de 2 Mbauds pour écrire des données à une résolution de 25 ns, mais je me suis rendu compte que je n'en avais pas besoin. J'ai commencé à chercher ce dont j'avais besoin de manière réaliste.

J'ai d'abord établi quelques règles :
  1. Je ne voulais pas l'utiliser pour écrire des fichiers SCP haute densité. Il était peu probable qu'un utilisateur d'Amiga veuille le faire.
  2. Je voulais une résolution de 125 ns, c'est-à-dire deux tics d'horloge sur l'Arduino.
  3. En réalité, il n'est pas possible d'écrire des impulsions détectables à moins de 3500 ns d'intervalle, donc je peux ignorer tout ce qui est plus court que cela.
J'ai créé une feuille de calcul qui m'a permis de jouer avec les chiffres jusqu'à ce que je trouve une combinaison qui fonctionnerait et s'adapterait au débit en bauds. Le problème suivant est qu'il est parfaitement possible d'avoir une pause entre les impulsions qui soit très grande. J'ai décidé que je pouvais surmonter ce problème en envoyant un code spécial qui signifiait "continuer pendant un nombre spécifique de nanosecondes sans écrire".

J'ai décidé d'utiliser un nombre de cinq bits pour stocker les temps de flux, et avec cinq bits, j'ai pu faire tenir huit temps de flux dans cinq octets !

La feuille de calcul m'a permis d'ajuster certaines constantes pour obtenir les meilleures options et les plus flexibles. J'ai décidé que le temps de flux minimum qui pouvait être écrit était de 2750 ns. Il serait codé comme un 0. Chaque temps suivant serait 125 ns de plus que le précédent.

Cela continuerait jusqu'à 29. J'ai réservé 30, qui se traduit par "pause pour 3250 ns". Par cinq octets (huit temps de flux), le total doit être d'au moins 260 000 ns ou nous pourrions potentiellement manquer de données en les envoyant assez rapidement.

Le résultat est le suivant :

Temps (ns) Valeur Temps Arduino
2750044
2875146
3000248
3125350
3250452
3375554
3500656
3625758
3750860
3875962
40001064
41251166
42501268
43751370
45001472
46251574
47501676
48751778
50001880
51251982
52502084
53752186
55002288
56252390
57502492
58752594
60002696
61252798
625028100
637529102

Et tout temps de flux supérieur à 6250 ns était alors décomposé en un ou plusieurs codes 30 qui signifiaient attendre 3250 ns (code 31 signifiant "STOP!"). Ceux-ci étaient codés dans les cinq octets comme ceci :

Numéro d'octet
76543210
D4C4B4A4A3A2A1A0
C3C2C1C0B3B2B1B0
E3E2E1E0D3D2D1D0
E4H4G4F4F3F2F1F0
H3H2H1H0G3G2G1G0

À ma grande surprise, cela a parfaitement fonctionné. J'ai décidé d'envoyer une des rotations à partir d'un fichier SCP, et voilà. Des fichiers SCP écrits sur la disquette. Quelques mois auparavant, je n'aurais jamais cru que cela serait possible. Même moi, je peux me tromper !

Écriture de fichiers IPF (novembre 2021)

Donc, après avoir écrit des fichiers SCP, les fichiers IPF devraient être faciles à écrire, non ? Eh bien, en dehors de la nature fermée du projet, c'était en fait très facile à implémenter. Un fichier IPF utilise la même commande que ci-dessus pour écrire la piste. La seule partie que je n'ai jamais pu tester est celle des secteurs faibles et des bits faibles, car j'ai eu du mal à trouver un fichier IPF qui en contenait.

WinUAE 4.9 avec la gestion officielle de DrawBridge est enfin disponible ! (décembre 2021)

Cela faisait tellement longtemps que j'attendais que cela devienne réalité. Ce qui était déjà intégré dans Amiberry a été lentement intégré dans WinUAE, et après de nombreuses versions bêta, c'est enfin arrivé ! Vous avez besoin du greffon FloppyBridge de ma page WinUAE pour utiliser DrawBridge !

Quelqu'un a dit MiSTer ? (décembre 2021)

Il est finalement arrivé par courrier :

MiSTer Multisystem
MiSTer Multisystem

C'est la carte principale du MiSTer Multisystem. Une seule carte pour utiliser la configuration MiSTer. J'ai eu la chance d'obtenir l'une des 500 premières cartes. Pas de faveurs spéciales, j'ai juste fait vite !

Alors pourquoi en voudrais-je un ? Eh bien, à part pour des raisons évidentes... il pourrait être un de mes prochains projets : DrawBridge pour MiSTer (aussi appelé "DrawBridge dans le MiSTer"). Peut-être bien...

Alors, que va-t-il se passer ensuite ?

Pour être honnête, je n'ai jamais été aussi occupé. De la construction de circuits imprimés pour satisfaire la liste d'attente, à l'ajout de nouvelles fonctionnalités. Maintenant que j'ai ma propre chaîne YouTube, je suis encore plus occupé !

L'ensemble du projet est gratuit et Open Source sous la licence GNU General Public Licence V3. Si nous voulons avoir un quelconque espoir de préserver l'Amiga, nous ne devrions pas nous arnaquer les uns les autres. En outre, je veux apporter quelque chose en retour à la meilleure plate-forme sur laquelle je n'ai jamais travaillé. J'espère aussi que les gens développeront ce projet, le feront évoluer et le partageront.

Je me suis rendu compte que cette interface est également bonne pour d'autres formats. Et bien que je ne les gère pas directement avec mes applications Windows (vous pouvez extraire une disquette double densité et haute densité via l'option SCP), le lecteur devrait être capable de lire n'importe quel format de disquette MFM. Il faut juste écrire le code pour décoder les données de la piste.

La bibliothèque DLL FloppyBridge pour WinUAE pourrait également être gérée par d'autres émulateurs si quelqu'un veut relever le défi.

Je garderai un oeil sur ce projet, et je ne sais pas si c'est à cause de ce projet ou si c'est juste une coïncidence, mais il y a quelques autres projets qui font la même chose, pas spécifiquement pour l'Amiga, et en utilisant du matériel différent, mais dans tous les cas c'est bon à voir. Je vais continuer à étudier et à améliorer la gestion de WinUAE quand le temps le permettra. Si quelqu'un a une idée de ce qui ne se charge pas, vous êtes les bienvenus !


[Retour en haut] / [Retour aux articles]


Soutenez le travail de l'auteur