samedi 1 septembre 2018

Chapitre 42 : programmer en assembleur sans OS : partie logiciel


Maintenant les étapes de la partie logicielle :
                Installer les logiciels pour compiler et linker les programmes assembleur ARM sur l’ordinateur à partir du site (ou d’un autre du même type) : https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads
                Les programmes sont installés sur le disque C : répertoire Program Files(X86) et donc j’ai préférer recopier les exécutables as, ld, make et objcopy dans un répertoire outils du développement Raspberry.
                Créer un projet test avec comme source un programme assembleur d’un des chapitre précèdent, créer un fichier makefile (à partir d’un exemple) pour générer un exécutable sur l’ordinateur. Transférer cet exécutable sur le raspberry (et bien sûr sous Linux comme vous avez l’habitude) dans un répertoire adhoc et l’exécuter ( suivant l’outil de transfert, il sera peut être nécessaire de modifier les droits par chmod). Si le résultat est correct, bravo votre chaine de compilation sur l’ordinateur est correcte sinon il faut trouver l’erreur et corriger !!! (pensez au nom du processeur !!! vérifier dans le plan de chargement du linker que l’adresse de chargement est 0X10000 Etc etc.)
Tant que la liaison du câble n’est pas assurée (voir chapitre précédent) et que la chaine de compilation ne fournit pas un exécutable correct, il est inutile d’aller plus loin.

Sinon continuer (et toutes les étapes précédentes ne seront plus à refaire):
                Installer dans un répertoire, les fichiers du répertoire début1 (ici) et les modifier éventuellement avec vos propres données suivant le type de Raspberry. Mettre les sources dans un répertoire src et créer un répertoire build.
                Pour ce premier projet nous trouvons un programme boot.s qui effectue l’initialisation à zéro de la zone .bss et l’initialisation du pointeur de la pile. Eh oui sans OS, c’est à nous à tout faire !!!! Ce programme appelle le programme principal kernel_main (kernel.s) qui appelle l’initialisation de la mini uart, l’envoi du message de bienvenue puis qui boucle indéfiniment sur la réception d’un caractère et l’envoi de celui çi par l’uart vers le microordinateur connecté à l’autre bout du câble.
Les programmes util.s, utilgpio.s et mini_uart.s contiennent les fonctions nécessaires à ce petit projet. Les nombreux commentaires (et ceux du tutoriel indiqué dans le chapitre précèdent) permettent de comprendre les instructions. Les fonctions du GPIO sont les mêmes que celles déjà vues dans un chapitre précèdent.
Attention, suivant le modèle du Raspberry, il vous faudra modifier les adresses de base de la mini uart et du gpio contenues dans le fichier des constantes.
                Installer le fichier makeFile.txt et le modifier éventuellement avec les répertoires des outils as, ld, copyobj et le répertoire des sources. (attention suivant le modèle l’image binaire finale s’appelle kernel.img ou kernel7.img ou kernel8.img). Modifier aussi les options de compil avec celles déterminées lors du premier test de la chaine de compilation
                Lancer le tout avec l’utilitaire make (dans le répertoire debut1, il y a un .bat qui effectue ce lancement).
                Corriger les erreurs éventuelles, vérifier la présence du fichier kernel.img et examiner le plan de chargement contenu dans map1.txt. C’est instructif.
                Maintenant il faut préparer la carte SD. Si elle n’est pas formatée, suivre les mêmes instructions que pour la prise en compte de l’installation de base du Rapsberry. Puis n’installer que les fichiers bootcode.bin, config.txt et start.elf nécessaires au boot du GPU.
Le fichier config.txt ne contient que cela :

kernel_old=1
disable_commandline_tags=1
disable_overscan=1
framebuffer_swap=0


L’instruction la plus importante est la première qui va indiquer que l’image du kernel doit être chargé à partir de l’adresse 00000000.
Recopier le fichier kernel.img crée par le make sur la carte SD. Enlever la carte et la mettre dans le Raspberry à la place de votre carte habituelle, connecter le cable TTL USB entre le Gpio du Raspberry et l’ordinateur, lancer putty avec la connexion série telle que définie précédemment, allumer le Raspberry et ……..
Soit ça marche et vous avez le message de bienvenue qui apparait sur l’écran de connexion Putty et vous pouvez taper des caractères sur le clavier de l’ordinateur et qui vont s’afficher sur l’écran de connexion. Bravo !!
Soit rien ne s’affiche et vous avez la led activité qui clignote à un certain rythme : Par exemple 7 éclairs cela veut dire que le GPU n’a pas trouvé le kernel.img (ou le kernel7 ou le kernel8). Donc vous corrigez !!
Soit rien rien du tout et il faut recommencer toutes les vérifications (en particulier les adresses de base du GPIO et la miniUART (attention il y a 2 uarts sur le BCM2835)). Si vous avez modifié les sources des programmes, il faut vérifier la bonne programmation car il n’y a aucune possibilité de savoir ou le programme bugge !!. Ma solution pour la mise au point a été de développer un programme de test sous Linux avec les outils des chapitres précédents mais cela oblige de mapper les registres du GPIO et ceux de la mini uart pour pouvoir y accéder (voir le chapitre sur l’accès au timer ).

Et maintenant que faire ?  D’abord pour arrêter notre programme, il faut éteindre le raspberry car le processeur ne peut pas être arrêté !!!! il boucle sans fin.
Puis modifier le programme kernel.s par exemple le libellé du message, relancer la création de l’image par make, recopier kernel.img sur la carte SD, remettre la carte dans le raspberry, le rallumer et voir si la modification est OK.
Ce sera la seule procédure à appliquer par la suite pour toute évolution future (avec bien sûr brancher le câble TTL USB et lancer Putty connexion série).
Puis il faut continuer en adaptant les procédures d’affichage d’un registre (et de tous les registres) vues dans les chapitres précédents pour remplacer les call system par l’envoi de chaine par la mini uart. Comme cela nous aurons une visibilité de ce qui se passe sur notre raspberry sans Os.

Avec beaucoup d'attention et de vérification,  tout cela fonctionne !!!!!


Chapitre 41 : programmer en assembleur sans OS (baremetal) partie 1


Jusqu’à maintenant, nous avons écrit des programmes en assembleur en utilisant les appels system de Linux pour les fonctions de base. Maintenant, nous allons commencer une série pour ne plus utiliser aucune fonction de l’OS.
Avertissement : Ne vous lancez pas la dedans si vous souhaitez développer rapidement des programmes importants. En effet, sans OS, il vous faudra réécrire en assembleur une grande partie des fonctionnalités offertes par un système d’exploitation complet (ou alors recopier les programmes de ce blog et des programmes trouvés sur internet qui contiennent les fonctionnalités que vous voulez utiliser).
Dans ce blog, nous essaierons d’aller le plus loin possible en récupérant des parties de logiciel déjà écrites sur Internet (ou en s’inspirant fortement) pour voir les difficultés et les contraintes de ce type de développement.
Déjà sans OS, cela veut dire que nous ne pouvons plus afficher quoi que ce soit à l’écran, plus de gestion du clavier à travers le port usb, plus de lecture écriture de la carte SD, plus de connexion ssh et même plus de wifi, bref nous n’avons plus rien !!   Ah si nous avons tous les pins du GPIO de disponibles et la lecture d’une image binaire contenant le ou les programmes et leur exécution lors du démarrage du Raspberry.
C’est pourquoi, les premiers exemples de développement trouvés sur Internet consistent à faire clignoter une LED relié aux pins du GPIO après avoir compilé quelques instructions assembleur ARM et généré une image binaire lisible par le GPU (voir la séquence de boot du raspberry).  Pas très folichon comme début d’expérience.
Voir le site en anglais: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/index.html
Heureusement, mon fils me signale un tutoriel qui explique comment programmer la mini uart du BCM2835, la relier à un ordinateur portable afin d’enclencher une communication entre les 2 ordinateurs. C’est déjà beaucoup mieux.
Bon le tutoriel est en anglais, mélange du C et de l’assembleur ARM, utilise des fonctions des librairies LINUX et concerne le Raspberry modèle 3 !!
Mais rien d’impossible à comprendre !!  donc aller sur : https://github.com/s-matyukevich/raspberry-pi-os
Dressons les étapes de la démarche à suivre pour arriver à notre fin  (remarque ces étapes ne sont à effectuer qu'une seule fois heureusement ) :
                Avoir un raspberry pi !!  (dans mon cas un modèle 1 B 512MO )et un ordinateur (dans mon cas sous Windows 10) qui puisse lire écrire sur une carte SD (ou acheter un petit lecteur de carte SD)
                Installer si ce n’est déjà fait le logiciel Putty sur le PC
                Acheter une carte SD (4GO suffisent car dans l’immédiat on ne sait pas écrire de fichiers sur la carte)
                Acheter un câble de liaison USB TTL   (voir modèle proposé ici https://cdn-learn.adafruit.com/downloads/pdf/adafruits-raspberry-pi-lesson-5-using-a-console-cable.pdf)
                Installer sur l’ordinateur, le driver nécessaire au câble : Attention, avec windows 10 et driver Prolific , j’ai rencontré des problèmes résolus en chargeant le driver du site :
http://www.totalcardiagnostics.com/support/Knowledgebase/Article/View/92/20/prolific-usb-to-serial-fix-official-solution-to-code-10-error
                Rechercher dans le gestionnaire de périphérique le port de l’ordinateur (comX) auquel est rattaché le câble
                En suivant le tutoriel précèdent, activer la console série sur le Raspberry, brancher le câble sur les pins du GPIO, redémarrer le raspberry.
                Sur l’ordinateur lancer putty connexion série, avec le port (comX) trouvé plus haut, et la vitesse 115200 (enregistrer la configuration pour la retrouver ultérieurement)
                Taper <ENTER>  et vous devez vous retrouver avec le login du raspberry pour établir une connexion.
                Sinon, boucler sur les étapes précédentes pour résoudre les problèmes potentiels !!!

Dans le chapitre suivant, nous verrons la partie logiciel. Bon courage.

lundi 13 août 2018

Chapitre 40 : algorithmes de recherche de chemins


Après ces chapitres sur l’utilisation de X11, nous allons revenir à la programmation de quelques algorithmes de recherche de chemins tels qu’ils sont présentés dans le livre déjà cité de Virginie MATHIVET aux éditions ENI.
Tous les sources de ce projet sont à télécharger depuis ce répertoire 
La transposition des programmes JAVA en assembleur ARM pose quelques petits problèmes de réflexion (Oui je sais pour les fanas de langage évolué, c’est du grand n’importe quoi !!) mais rien d’impossible. Nous allons donc manipuler des tables, et des listes chainées ce qui nous permet d’utiliser les registres comme pointeurs.
Pour pouvoir réutiliser ces algorithmes dans d’autres programmes, j’ai décidé de les écrire dans des petits programmes sources différents et d’utiliser l’utilitaire make pour compiler et linker l’ensemble du projet. Comme cela j’ai découvert les subtilités de ce programme !!  Vous trouverez ici le fichier des commandes pour ce projet et qu’il faudra lancer par make –f makefile.txt sur le raspberry.
Remarque : il manque dans ces commandes, la recompilation lorsque l’on a modifié uniquement un fichier d’include (modification d’une constante ou d’une structure par exemple).
Le programme maitre est le programme lancer.s qui va contenir la description des cartes, l’appel aux programmes de création des structures de données puis les appels à chaque algorithme de recherche : recherche en profondeur, en largeur, algorithme de Bellman-Ford, de Dijkstra et A* (pour la théorie, je vous renvoie au livre en question ou à des articles sur Internet).
Pour chaque algorithme, nous mesurerons le temps d’exécution avec les procédures décrites dans les chapitres précédents de ce blog.
Le programme le plus important est le programme carte.s qui va traduire la description de chaque carte en données exploitable par les algorithmes. Chaque case de la carte est enregistrée dans une table composée d’une structure Tuile. Ici, j’ai eu quelques difficultés pour associer les données d’un nœud (nécessaires aux algorithmes) avec les données internes à la tuile. J’ai donc crée une structure Tuile mais qui contient au début les données d’un Nœud. Ceci permet de généraliser les algorithmes à d’autres problèmes de recherche. Il suffira simplement de modifier (ou dédoubler) la structure pour adapter les données internes au problème concerné
La taille des cartes étant très variable, la place nécessaire sera réservée sur le tas. Pour cela une routine contenant l’appel système BREAK sera ajouté au fichier des routines. Voici son code :

allocPlace:
                push {r1,lr}    /* save des  2 registres */
                push {r2,r7}  /* save des registres */
                mov r2,r0
                mov r0,#0      /* recuperation de l'adresse du tas */
                mov r7, #0x2D                  /* code de l'appel systeme 'brk' */
                swi #0                      /* appel systeme */
                mov r1,r0       @ save du début
                add r0,r2                  @ reservation place demandé
                mov r7, #0x2D                  /* code de l'appel systeme 'brk' */
                swi #0                      /* appel systeme */
                cmp r0,#-1       @ erreur allocation
                beq 100f
                mov r0,r1    @ retour adresse début
100:
                pop {r2,r7}
                pop {r1,lr}   /* fin procedure */
                bx lr   

Dans le programme carte.s, nous décodons les lignes de la description de la carte et pour chaque case, nous créeons une tuile dans une table avec le type, sa place et son cout.
Puis à partir de cette table, nous créons une liste chainée de tous les nœuds et une liste chainée de tous les arcs, listes qui seront utilisées par les algorithmes.

Chaque algorithme est codé dans un programme spécifique suivant la description java du livre et en l’adaptant aux structures de données crées dans le programme carte. La mise au point est un peu délicate compte tenu du nombre de registres utilisés. A ce propos, le rôle de chaque registre dans un programme comme celui-ci n’est pas toujours facile à déterminer. Faut-il respecter par exemple les normes habituelles de passage des arguments aux sous-routines ? si oui, cela oblige à faire des mouvements de registres à registres si ceux-ci ne sont pas bien choisis. On s’aperçoit par exemple qu’il faudrait utiliser les registres à partir de r4 pour les indices supérieurs de boucle ou pour conserver les données principales du programme. Je n’ai pas encore trouvé la bonne méthode pour ces attributions, ce qui entraine quelques bizarreries dans mes programmes. Comme ici, une fonction qui n’utilise pas comme paramètre d’entrée le registre r0 mais seulement r1 (indice de ligne dans la table des tuiles) et r2 (indice de colonnes) ou alors le registre r10 qui sert dans plusieurs routines !!!
Après corrections de nombreuses anomalies, le programme donne les mêmes résultats pour les exemples du livre avec bien sûr des temps d’exécution bien différents. L’algorithme de Bellman-Ford a un temps d’exécution anormal par rapport aux autres. Il faut que j’analyse plus finement son comportement pour voir s’il n’y a pas une erreur de programmation ou un mauvais choix de la structure des données.
Exercices :  lire la description d’une carte à partir d’un fichier dont on passe le nom en paramètre.
                Le départ de plusieurs algorithmes est toujours la case 0,0. Modifier les algorithmes pour qu’ils commencent à la case départ donnée par la structure graphe.
Adapter les algorithmes à d’autres problèmes.
 


vendredi 6 juillet 2018

Chapitre 39 :Modification du curseur de la souris et chargement d’une image BMP


Nous reprenons un chapitre du document xlibbook pour montrer comment modifier la forme du curseur de la souris. J’ai ajouté à l’exemple donné le chargement et l’affichage d’une image au format BMP.
Dans ce programme, j’ai aussi essayé d’améliorer la gestion des évènements en stockant en fin de structure X11 des fenêtres, l’adresse de la procédure de gestion des évènements à appeler pour la fenêtre concernée. Pour cela nous déclarons une table des fenêtres crées avec autant de structure Fenêtre que de fenêtres plus une. La dernière servira de borne d’arrêt lors de la recherche.
Comme dans le programme précédent, nous reprenons les définitions des bitmap du curseur de la souris et de son masque données par le tutoriel sous la forme d’une série de .byte.
Dans le code, nous reprenons comme auparavant les procédures de connexion au serveur, de chargement des polices et des couleurs (même si nous n’en avons pas l’utilité dans ce programme), la création de la fenêtre principale et la création des contextes graphiques. Nous modifions la procédure de création des images pour convertir les 2 images du curseur à partir de leur définition de la .data.
Remarque : dans la création de la fenêtre principale, j’ai voulu tester la possibilité d’avoir une icone de la fenêtre sous la forme d’une image. J’ai donc créée avec le logiciel bitmap une image que j’ai chargée dans la procèdure lectureimage. Puis j’ai passé le pointeur crée à la fonction XSetStandardProperties. Mais cela n’a rien changé lors de l’affichage de la fenêtre (même en réduisant celle-ci). Dès que j’aurais un peu plus de temps, j’essaierai de trouver le problème (ou peut être que le serveur XWing ne prends pas cela en compte !!!)
Nous ajoutons ensuite la création du curseur de la souris par l’appel à la fonction X11 XCreatePixmapCursor à laquelle nous passons les 2 images précédemment créées. Ceci permet de faire apparaitre le curseur sur des fonds différents.
Ensuite, nous créons 3 fenêtres à l’intérieur de la fenêtre principale, les 2 premières comme sur l’exemple du tutoriel, la 3ième en bas à droite servira à afficher l’image de l’icône créée pour la fenêtre principale. Ceci afin de vérifier que l’image lue depuis le fichier et convertie était correcte.
Pour la 4ième fenêtre, nous allons d’abord charger une image au format bmp (Coquelicots.bmp) stockée dans le même répertoire que le programme afin de récupérer ses dimensions. Celles-ci nous serviront à créer la fenêtre exactement à la même taille. Pour la lecture et le chargement d’une image BMP, vous vous souvenez peut être que j’avais déjà fait cela pour l’afficher par l’intermédiaire du frameBuffer. J’ai donc récupéré le code écrit à ce moment là, je l’ai adapté et rendu plus autonome (pour pouvoir être réutilisé dans d’autres programmes ou dans une librairie). Les buffers nécessaires sont adaptées à la taille de l’image lue et crées sur le tas (voir un post précédent sur l’allocation mémoire sur le tas). Ainsi le programme appelant, n’a pas à se préoccuper d’allouer de la place pour l’image. Il faut 2 buffers pour stocker les codes RGB de chaque pixel car le stockage au format BMP est inversé par rapport à l’image réelle. Il y a aussi quelques subtilités dans la longueur de chaque ligne pour ce format d’image. Je vous laisse le soin de lire les commentaires si vous voulez améliorer cette partie !!!!
Vous remarquerez que dans chaque procédure de création de fenêtre, nous stockons dans la structure l’adresse de la fonction à appeler pour chaque évènement.
Ainsi dans la procédure de gestion des évènements, nous nous contentons de balayer la table des fenêtres pour retrouver la structure de la fenêtre concernée, puis de récupérer l’adresse de la fonction dans le registre r2 et nous appelons la procédure par l’utilisation de l’instruction blx r2.  Ensuite dans chaque fonction, nous pouvons tester plus précisément l’événement détecté et réagir en conséquence. Pour les 3 premières fenêtres secondaires, nous ne faisons rien et pour la 4ième, nous testons l’évènement expose pour ré afficher l’image bmp si la fenêtre a été masquée.
A l’exécution, en balayant les différentes fenêtres et le bandeau haut, vous voyez les changement de forme du curseur et sa lisibilité parfaite quelle que soit le fond.
Vous voyez que l’image BMP s’affiche correctement avec les bonnes couleurs mais qu’il y a quand même un certain délai. En lisant les explications du tutoriel, on s’aperçoit que la fonction XCreateImage crée l’image sur le poste client (donc le raspberry) et que c’est la fonction XPutImage qui la transfère sur le serveur ce qui peut prendre un certain temps !!
Exercice : Chercher une image d’un beau bouton, la mettre au format .bmp, puis la charger dans un programme et l’afficher dans une fenêtre bouton.
                Améliorer le code précédent en cherchant une image d’un bouton appuyé et afficher cette image lorsque l’on a cliqué sur le bouton (et bien sûr réafficher l’image du premier bouton lorsque la souris quitte le bouton).
                Dans le tutoriel de Ross Maloney, il montre un exemple de chargement d’une image en couleur avec un format XPM. Je n’ai pas réussi à utiliser cette fonction car il doit falloir une librairie particulière. Pour créer des images de ce format il faut charger sur le Raspberry le package pixmap et utiliser le logiciel pixmap (même principe que bitmap). Donc exercice : chercher la librairie qui permet d’utiliser les fonctions XPM, programmez un exemple d’affichage d’une image de ce format (voir l’exemple du tutoriel).

Je terminerais avec ce programme les exemples d'utilisation de X11. Je vous laisse le soin de découvrir toutes les autres possibilités. 
Je reprendrais ce blog à la fin des vacances.