[Doom sur STM32] Compilation et debug - Episode #2

J'ai réussi à monter une version fonctionnelle de stm32doom avec les libs ST et du debug. Ça a été difficile, et je vais faire une description raccourcie.

Episode précédent

J'ai passé une quantité inavouable de temps perdu à essayer de faire des choses extrêmement simples. Ça me rend malade. Mais bon, c'est comme ça. Faisons un petit rappel des épisodes précédents.

Jusque-là j'ai cherché à partir du projet stm32doom, pour la bonne et simple raison que j'ai réussi à le compiler et le lancer sur une carte. il m'a semblé évident qu'il fallait que je parte de ce projet pour l'upgrader avec les fonctions qu'il manque.

  • Je ne comprend / maîtrise pas la chaîne de dev, c'est du GCC en ligne de commande, et je n'ai jamais réussi à compiler un projet pour STM32 en ligne de commande (et pourtant j'y ai passé du temps). J'arrive à ouvrir le projet dans CubeIDE (la chaîne de dev que je sais à peu près utiliser) et en bricolant j'arrive à le compiler et générer un binaire fonctionnel. Par contre, impossible de le lancer en debug, et je ne me vois pas faire quoi que ce soit dans ces conditions. D'autant que ce projet utilise des librairies et drivers "custom" développées par un allemand, avec donc le peu de doc et de commentaires en allemand.
  • Option possible : se conformer à la "volonté de l'auteur" et faire le dev en ligne de commande, et le debug avec GDB. Je ne me vois pas me plonger là-dedans, j'ai déjà assez souffert avec MBED. De plus cela suppose de continuer à utiliser les libs custom en allemand. Je n'ai rien de spécial contre le allemands, mais même après 10 ans d'allemand première langue au collège lycée je n'ai jamais été à l'aise avec cette langue, et comme toutes les docs sont en allemand ... Ça va être dur. Donc : me lancer sur des outils qui ne m'intéressent pas et que je suis garanti de galérer avec, euh, "non" ?
  • Trouver un moyen de créer un projet CubeIDE qui gère le debug, et intégrer toutes les sources dedans. Déjà essayé, je m'y suis cassé les dents.
  • Partir d'un projet template CubeIDE pour la carte, intégrer le HAL fourni par ST en remplacement des drivers allemands : c'est le sujet de l'épisode 1. J'ai réussi à initialiser tout le bas-niveau, a priori tout marche, par contre je cale sur le link avec la SDRAM externe, et je n'ai pas de piste pour gérer ce problème.

Je me suis concentré sur ce dernier point, et ai réussi, petit pas par petit pas, à régler la plupart des problèmes.

Prise de recul et tirage de comète

Si je regarde les problèmes que j'ai, le plus bloquant pour moi aujourd'hui est l'allocation mémoire : comment charger les données en SDRAM ? Clairement il n'y a pas une seule solution possible, et ça m'a fait pas mal réfléchir sur mon approche du problème.

stm32doom va forcer le mapping de certains modules dans le script du linker (le fichier .ld). Ça me surprend beaucoup car l'espace mémoire n'est ps disponible au démarrage, vu qu'il faut initialiser le module FMC et la SDRAM externe pour en disposer. Comment peut-il réussit à initialiser les variables sans que ça merde ? Je pense qu'il me manque des éléments de compréhension, et je commence à douter de la pertinence de la modification du .ld. Ou alors il ne prend en compte le .ld que pour le runtime, pas pour l 'init ? Mais comment configure-t-on ça ? Vraiment, il me manque des billes pour comprendre comment ça marche (ou comment c'est censé marcher).

En réfléchissant à ça, j'ai repensé plus généralement à ces histoires d'allocation de mémoire. Ce port est basé sur Chocolate Doom, qui est un source port ayant volonté d'être fidèle à l'original, mais avec quand-même quelques améliorations pour faciliter la compatibilité avec les systèmes modernes. Et entre autres, ce que j'ai compris c'est qu'il va charger l'intégralité des WADs en RAM, ce qui est normal : de nos jours n'importe quel coucou dispose de plusieurs Go de RAM, alors que les WADs dépassent rarement 10Mo. Et, toujours de ce que j'ai compris, stm32doom va donc charger l'intégralité du WAD dans la RAM de la carte, qui est de 8Mo, ce qui fait qu'on ne peut pas entrer un WAD qui fait plus que 8Mo, et donc que seul le Doom shareware rentre. Or, en lisant le Black Book de Fabien Sanglard sur Doom, section 5.7 on apprend que la configuration minimale requise devait comprendre 4Mo de RAM, et donc le jeu allouait 4Mo de mémoire, et s'il détectait la présence de 8Mo, il en allouait 8Mo pour se faciliter la vie. Ce qui veut dire qu'il y avait une allocation dynamique des données, et donc que la limite de taille de WAD n'est pas une limitation intrinsèque du jeu d'origine, mais bien une limitation due aux choix d'architecture du portage : pas d'allocation dynamique, tout est chargé en RAM au démarrage, donc il faut plus de 8Mo pour charger le jeu. Ayant lu ça, je me pose plein de questions, et je me dis que j'ai bien envie de voir comment je pourrais utiliser cela à mon avantage et remettre au goût du jour le gestionnaire de mémoire du Doom d'origine, de façon à pouvoir faire sauter cette limite arbitraire ... Il y a une piste à creuser.

Allocation mémoire

A l'époque les gestionnaires de mémoires n'étaient pas fiables, et difficilement débuggable, ce qui n'incitait pas à utiliser malloc. A la place, on faisait ses propres mécanismes d'allocation dynamique de mémoire, et c'est le cas de Doom, via les modules "z_". Au démarrage il va allouer 4Mo de RAM (8Mo s'il détecte que c'est possible) et gérer lui-même l'allocation des données dans cet espace, sans se reposer sur l'OS.

Au début de D_DoomMain, on va appeler Z_Init (), qui va à son tour appeler I_ZoneBase(), qui va appeler aller chercher en RAM de quoi stocker les structure de données. Et dans le code de stm32doom, pour obtenir de l'espace mémoire on fait ... un malloc :P

Dans le code d'origine, l'allocation mémoire est faite via l'utilisation de la fonction Z_malloc() qui fait appel au gestionnaire de mémoire custom de Doom, pour ne pas utiliser malloc. L'initialisation du gestionnaire de mémoire est faite dans le module i_system, normal, les modules "i_" sont les modules lié à l'implémentation, équivalent du HAL, on va dire. Or, dans Chocolate Doom, ils ne se sont pas embêtés, et pour faire l'allocation d'origine du gestionnaire de mémoire, ils font un bête malloc, vu que "de nos jours" malloc est devenu fréquentable. Et donc on va faire un malloc pour initialiser un gestionnaire de mémoire qui a été conçu pour ne pas utiliser malloc, ce qui est assez truculent - ou anachronique en fonction de comment on veut le voir.

Et donc, j'ai l'impression que j'ai un coup à jouer ici, car c'est à ce niveau qu'on vient dire où les données vont se trouver, et donc c'est là qu'il faudrait lui dire d'aller y mettre dans la SDRAM. Et normalement, après, il va se démerder. Ça a l'air trop facile dit comme ça ...

Ce que je ne comprend pas, c'est que je ne trouve pas où on dit au malloc d'aller chercher en SDRAM, je n'ai pas trouvé de re-définition de la fonction, et encore moins de code qui va explicitement donner l'adresse de la SDRAM. Comment fait-il pour savoir qu'il faut aller chercher la mémoire là-bas ? Dans le HAL il y a une fonction SystemInit_ExtMemCtl() qui permet de définir que les data programme seront assignées dans la RAM externe, mais cette fonction est appelée uniquement sous un switch de compilation dont je ne trouve aucune mention dans le reste du code. Par contre, dans syscalls.c, on trouve ça:

#define HEAP_SIZE		0x00700000 // 7 MB
/* heap */
__attribute__ )
static char heap[HEAP_SIZE];
/* pointer to current position in heap */
static char* _cur_brk = heap;

Or, la heap est l'espace mémoire dans laquelle malloc va aller allouer de la mémoire. Ici, avec le directive __attribute__, on vient forcer le linker à placer la variable "heap" dans la section SDRAM. Je ne trouve aucune autre mention de cette variable ailleurs dans le code, ni de _cur_brk d'ailleurs. Par contre, la fonction qui va jouer avec cette variable est _sbrk_r(). Or, en cherchant sur google, on apprend que cette fonction est utilisée par malloc, et qu'il faut en faire une version custom si on veut modifier la gestion de la heap. Donc il semble bien que ça soit ici qu'on dise à malloc d'aller jouer dans la SDRAM.

Je suis tout de même surpris de cette implémentation. Pourquoi "faire un détour" par syscalls.c et ne pas plutôt tout simplement définir la section .heap dans la SDRAM dès le fichier de script du linker ? A mon avis, définir la heap en SDRAM directement dans le .ld doit foutre la merde, car ça signifie que tant que la SDRAM n'est pas initialisée il n'y a pas de heap, et même pas d'adresse valide pour définir la heap. Et quelque chose me dit que ça doit mal se passer au niveau de l'init ... En définissant cette variable dans le syscalls.c, elle sera traitée / initialisée dans le startup, et avant ça la heap sera dans la RAM intégrée. Ça doit permettre de faire une transition "en douceur". En tous cas je ne vois pas d'autre explication. Ce qui est dit sur ce forum a l'air de corroborer mes intuitions.

Bon, même en prenant ça en compte, il va toujours essayer de faire rentrer la SDRAM dans la RAM, alors que je lui ai défini une section séparée. Après quelques bricolages, et après avoir trouvé un exemple de fichier de mapping qui définit de la flash en SQPI, je note:


**IMPORTANT** la définition de la section ajoutée doit être APRÈS le .ARM.attributes 0 : { *(.ARM.attributes) } sinon ça déconne.

Bon, j'ai le .bss qui dépasse de 123240 octets ... Gné ?

Il y a un analyseur de mémoire dans CubeIDE, hautement plus pratique que de se palucher le .map à la main:

192ko + 121ko dans la RAM ? Bizarre.

On dirait qu'il y a des choses qui sont mappées dedans par erreur. On voit qu'il y a 252ko de .bss (variables non initialisées). Après copy-pasta des données dans un tableur, extraction des tailles de chaque section, et tri décroissant, on obtient les éléments qui prennent le plus de place dans la RAM:

Element name Size Module
visplanes 83 KB r_plane.c
openings 40 KB r_plane.c
states 26.44 KB info.c
dloop.o 20 KB dloop.c
viewangletox 16 KB r_main.c
mobjinfo 12.31 KB info.c
drawsegs 12 KB r_bsp.c
jpeg.o 12 KB jpeg.c
zlight 8 KB r_main.c
vissprites 7.5 KB r_things.c
statdump.o 6.25 KB statdump.c
S_sfx 5.11 KB sounds.c

Or, les modules qui sont forcés en SDRAM par le fichier de link de stm32doom sont:

  • i_video
  • r_bsp
  • w_wad
  • r_main
  • r_plane

Ce sont effectivement des modules qui prennent de la place, ça a donc du sens de les mettre dans la SDRAM. Alors : comment faire ?

Comme dit précédemment, lorsque je déclare explicitement les fichiers .o dans la section SDRAM dans le fichier .ld, je me prend des erreurs de double déclaration, genre:

E:\Documents\dev\cubeIDE\workspace_doom\stm32doom_cube_1\Debug\Core\chocdoom\r_plane.o: In function `R_InitPlanes':
E:/Documents/dev/cubeIDE/workspace_doom/stm32doom_cube_1/Debug/../Core/chocdoom/r_plane.c:97: multiple definition of `R_InitPlanes'
Core/chocdoom/r_plane.o:E:/Documents/dev/cubeIDE/workspace_doom/stm32doom_cube_1/Debug/../Core/chocdoom/r_plane.c:97: first defined here

Et bien entendu, il ne donne que l'information sur la première déclaration, pas les autres endroits où il le trouve, ce qui aurait pu permettre, par exemple, de comprendre l'origine du problème et le corriger. En mettant un * devant le nom du fichier .o l'erreur disparait, mais les variables sont toujours mappées en RAM.

Visiblement ce problème trouvé sur google est similaire. Le "Probably on Linux I would have never saw the problem &-(" ne me rassure pas du tout ... La doc de LD ne m'aide pas beaucoup plus, il n'y a aucune indication spécifique concernant l'ajout de fichiers objets dans les sections. Ça sent définitivement le bug ou l'effet de bord ...

Bon, maintenant que je comprneds un peu mieux ce qu'il se passe, je vais tester autre chose. Plutôt que de déclarer dans le script de lnik les fichiers à mettre en SDRAM, je vais faire l'inverse : dans les déclarations des variables que je veux mettre en SDRAM, je vais mettre un __attribute__ pour les forcer en SDRAM. Maintenant que j'ai sorti la liste des variables qui prennent le plus de place, je peux les viser spécifiquement. Bien évidemment, ce n'est pas très élégant, puisque cela revient à intégrer des contraintes d'implémentation de la cible dans le code source de l'applicatif. Mais bon, à un moment il faut avancer.

Une autre approche un peu plus clean serait de dire que les variables dont on sait qu'elles prennent beaucoup de place aient leur propre section "générique", et que dans l'implémentation on doit choisir si on met cette section en RAM ou en SDRAM. C'est une pirouette pour que ça passe mieux, rien de plus.

... Ouais ben ça ne marche pas, il met bien la variable dans la section .SDRAM, et après il met .SDRAM dans .RAM, donc ça ne change rien. Il manque quelque chose. RTFM : lisons ce que la documentation de CubeIDE a à nous dire à ce sujet. Section 2.5.7.5 est décrit la méthode pour mettre des variables non-initialisées dans une section spécifique ("Locate uninitialized data in memory (NOLOAD)"). Et à part le fait qu'il en faut beaucoup moins que ce qui est écrit dans le LD de stm32doom, je ne vois pas de différence conceptuelle majeure.

Ok ok. Essayons de suivre la règle à la lettre. Déclaration d'une section SDRAM:

MEMORY
{ 
CCMRAM    (xrw)    : ORIGIN = 0x10000000,   LENGTH = 64K 
RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 192K 
FLASH    (rx)    : ORIGIN = 0x8000000,   LENGTH = 2048K
SDRAM     (rwx)  : ORIGIN = 0xD004B000, LENGTH = 7892K /* First 300K used for frame buffer */
}

Déclarons la section avec rien de plus que ce qu'ils montrent dans la doc:

/* External SDRAM */
.SDRAM (NOLOAD) :
{
*( .SDRAM*);
} >SDRAM

Et ajoutons la directive dans la déclaration de la variable visplanes (celle qui prend le plus de place):

__attribute__) visplane_t	    visplanes[MAXVISPLANES];

Compilons ... La RAM ne dépasse plus que de 38ko ! Et si je regarde dans l'analyseur de mémoire, visplanes n'apparait même plus. Par contre, dans le .map :

.SDRAM          0x00000000d004b000   0x714c00
*(.SDRAM*)
.SDRAM         0x00000000d004b000   0x700000 Core/Src/retarget.o
.SDRAM         0x00000000d074b000    0x14c00 Core/chocdoom/r_plane.o
0x00000000d074b000                visplanes

Victoire ! :) Et ce qui change tout, c'est de respecter la syntaxe EXACTE ! Minimum de code dans le .ld, __attribute__ au début de la ligne de déclaration de la variable. C'est putain de sensible ...

Pour ce qui est de forcer les variables et fonctions en SDRAM, j'ai essayé de les ajouter dans le .ld, mais ça ne marche pas. Et vu que je n'ai trouvé aucun exemple sous cubeIDE, je suspecte fortement que ça ne soit tout simplement pas possible.

L'on notera d'ailleurs au passage que je comprend mieux pourquoi l'allocation d'origine pour Z_Init est faite avec un malloc() : il n'y a pas que la mémoire dynamique qui est mise dans la SDRAM, mais aussi les variables trop volumineuses pour être stockées dans la RAM interne du MCU. Le fait d'allouer ces variables non-initialisées dans la SDRAM les fait "disparaitre des radars" et il devient moins facile de les retrouver. De ce fait, il est beaucoup plus facile d'utiliser malloc(), qui prendra en compte la présence de ces variables, et évitera de devoir gérer à la main l'allocation des variables volumineuses ET la zone dynamique. C'est fait automatiquement, et en plus ça laisse plus de flexibilité, puisque malloc() s'adapte à la mémoire disponible une fois les grosses variables enlevées, ce qui permet d'éviter de devoir adapter les zones mémoires et adresses quand on fait des modifs. Beaucoup moins pénible. Après, une façons "propre" de le faire en se débarrassant completement de malloc() serait sans doute de déclarer deux sous-régions dans la section SDRAM, une pour les variables, une pour la zone dynamique. Bon, on verra plus tard, peut-être.

Concernant ces variables qui sont mises en SDRAM, seules celles qui ne sont pas initialisées peuvent être candidates. Si je veux mettre des variables initialisées, il faudra surement que je modfie le startup pour que les valeurs puissent être écrites au démarrage. je n'y tient pas spécialement tout de suite maintenant. Par contre, ça serait très utile, parce que la ROM coûte beaucoup moins cher que la RAM, et de toues façons les valeurs d'init sont déjà présentes dans la flash.

Bref, après tri, je mets en SDRAM:

  • visplanes et openings dans r_plane
  • ticdata dans d_loop
  • viewangletox, scalelight, scalelightfixed, zlight dans r_main
  • drawsegs dans r_bsp
  • GRAPHIC_JPG dans jpeg
  • vissprites dans r_things

Plus d'erreur de link qui explose la RAM, par contre : undefined reference to `_init' Grrr ... C'est un appel de fonction dans le fichier de startup, et c'est lié à la libc. Hum ... Je me rappelle avoir coché l'option "no standard files" pour voir ce que ça donne (c'est une des options dans le makefile de stm32doom), je vais le décocher pour voir.

OH ! Ca compile :P :P :P

On voit bien que les variables auquel j'ai ajouté un __attribute__ sont en SDRAM, et la heap fait 7 Mo.

Si on compare avec la répartition mémoire de stm32doom, on remarque qu'il y a nettement plus de variables en SDRAM, vu que l'allocation ne se fait pas par variable mais par fichier. Par contre, étant donné que toutes les variables en plus sont très petites, ça ne change pas grand chose de les mettre dans la SDRAM, ce n'était pas elles qui posaient problème.

Ellipse

Après ça j'ai galéré ma mère sur des problème de crash ultra-relou. En fait, la heap étant dans la SDRAM, il devient très difficile de ne pas faire une fausse manip et ne pas écraser les pointeurs en cours d'exécution, et c'est horriblment difficile à débug.

un problème récurrent que j'ia eu est que le FatFS refusait de démarrer, me balançant une erreur de disque. Or, il se trouve que je créait un pointeur vers les différents handle de l'USB, et que je faisait par la suite des opérations sur la SDRAM, comme, par exemple, l'effacer. Ce qui faisait que tout le contenu de ces pointeurs passait à zéro, et provoquait des erreurs dans les couches USB. Donc j'ai enlevé l'initialisation de la SDRAM à 0.

Puis j'ai eu des plantages pendant le fonctionnement de Doom, avec des tests de variables qui déclenchait une erreur fatale du fait que la valeur testée était à 0xFF. Or, je voyais cette valeur poper dans la SDRAM dès le démarrage, et je ne voyais aucune écriture à cette adresse, mais je voyais bien la lecture.

Et comme souvent, je découvre quelque chose par accident : de passage chez mes parents, j'ai voulu continuer à bosser sur le sujet, mais j'ai oublié de prendre un câble USB adapté (mini-USB). J'ai donc attrapé le premier qui me venait sous la main, et il s'avère qu'il s'aggissait d'un câble de charge, donc sans les fils pour les datas, donc impossible de se connecter dessus pour debug. Par contre, ça alimente la carte, et là ... il se trouve que ça ne plante pas ! La démo tourne sans discontinuer. Donc je suis en train de me dire que c'est sans doute le debug qui fout le bazar et fait qu'il y a des datas corrompues dans la SDRAM ... Je vais quand-même essayer de faire un effacement des données de la heap jusqu'à la fin de la SDRAM avant de lancer le jeu, pour voir ...

Et ... ça a l'air de fonctionner, ça ne plante pas, alors que je suis en debug.

Hum ... Je suspecte un effet de bord entre le fait de faire des sessions de debug à rallonges sans faire de hard-reset de la carte, ce qui doit garder les datas précédentes dans la SDRAM, couplé avec un problème de visualisation des données de la SDRAM via le debug. Parce que, bon, je vais regarder la mémoire en debug, et dans la section 0xD0000000 et au-delà ... Mais en fait le MCU ne connait pas le contenu de cet espace mémoire, tant qu'il n'est ps allé le chercher dans la SDRAM externe. Et a priori - ptet je me trompe ? - tant qu'il n'y a eu aucune opération explicite de lecture dans la SDRAM externe, le MCU ne peut savoir ce qu'il y a dedans, il ne peut pas "deviner" ce qu'il y a dans un composant externe. Or, quand je vais dans le memory browser en debug, alors que je suis en halt, je doute très très très fortement qu'il aille déclencher une lecture de la SDRAM externe, car cela supposerait de faire tourner le périphérique FMC. Donc ... qu'est-ce que je regarde, en vrai ?

Et à mon avis, pour je ne sais quelle raison, les données contenues dans cet espace mémoire sont foireuses. Or, en debug, ces données foireuses sont prises pour argent comptant par le CPU. Et je suspecte très fortement que les routines qui plantent à cause de ça ne font pas d'initialisation de la mémoire quand ils allouent un pointeur. En tout cas je n'ai pas vu d'écriture des données avant que ça plante, et je pense que l'initialisation n'est pas faite pour de simples raisons d'économies de performances et de temps de cycle. Je pense qu'ils partent du principe que les données présentes dans une zone nouvellement allouée sont supposées être à 0, et que donc il n'y a pas besoin de perdre du temps à les mettre à zéro vu qu'ils sont censés déjà être à zéro. Mais ça ne marche que si les données sont effectivement à zéro. Si elles ne le sont pas ... Et bien ça ne marche pas, CQFD.

Gestion du LCD

Ce sujet étant trop complexe pour pauvre petit cerveau, j'ai triché et suis allé chercher un code d'exemple qui utilise le BSP, qui est une lib fournie par ST pour les périphériques / composants externes sur leurs cartes d'évaluation. Celui du LCD initialise le LCD et la SDRAM, donc ça m'arrange. par contre attention où il met les frame buffers dans la SDRAM, car les exemples partent sur du rendu en ARGB8888, donc 32 bits par pixel, alors que l'on veut faire du RGB565.

Et donc, il faut bien faire gaffe à la configuration du LCD et faire les transferts en RGB565, sinon on a des couleurs bizarres et une mise en page ... Hum ... originale, et des plantages aléatoires à cause des pointeurs qui se font écraser par les transferts qui dépassent de l'espace alloué au frame buffer.

Un peu de doc:

https://www.compel.ru/wordpress/wp-content/uploads/2013/11/1_LTDC_ChromeART.pdf

https://www.programmersought.com/article/52071234672/

https://www.programmersought.com/article/10955632833/

La suite

Et donc maintenant je suis à un niveau où le jeu démarre et tourne a priori correctement, mais rame salement. Il faut que j'en trouve la raison, car c'est injouable, contrairement à la version stm32doom.

L'approche la plus évidente est de faire la liste des fonctions bas-niveau qui sont appelées dans la boucle principale, pour voir celles qui me concernent et que j'ai customisées, et voir comment les optimiser.

Note : j'ai remarqué que l'écriture finale du frame buffer est faite "manuellement" via une boucle for qui va écrire pixel par pixel depuis la palette de couleurs. il me semble que le "CLUT" dans le DMA2D est un look up table qui peut être utilisée pour faire cette conversion en HW. Il faudra que je me penche là-dessus.

En attendant, étant donné que j'ai commenté toutes les fonctions du touchscreen, en appels bas-niveau il ne reste que les fonctions d'affichage et la lecture du timer système. A savoir:

  • lcd_refresh
  • l'écriture dans le frame buffer
  • la mise à jour du systime

Si ça rame, soit ça vient d'un de ces trois items, soit j'ai un problème de config, genre la SDRAM mal configurée qui tourne plus lentement.

Pour le systime, j'y ai mis dans l'interruption SysTick_Handler je ne vois pas trop comment ça pourrait causer problème. Dans stm32doom c'es fait de la même façon, et je ne pense pas que ça créerait des ralentissements de la sorte. A moins dun effet de bod bizarre ?

L'écriture dans le frame buffer est la même que dans stm32doom, donc oui ça serait bien de l'optimiser en utilisant le DMA2D, mais sinon c'est exactement comme dans stm32doom.

Reste donc le lcd_refresh. Et à dans lcd_set_transparency j'ai un appel à __HAL_LTDC_ENABLE_IT qui est une macro (donc je doute qu'elle consomme beaucoup) et HAL_LTDC_Reload. Dans cette fonction il n'y a pas grand chose:

HALStatusTypeDef  HALLTDCReload(LTDCHandleTypeDef *hltdc, uint32_t ReloadType)
{
 /* Check the parameters */
 assertparam(ISLTDC_RELOAD(ReloadType));

 /* Process locked */
 __HAL_LOCK(hltdc);

 /* Change LTDC peripheral state */
 hltdc->State = HALLTDCSTATE_BUSY;

 /* Enable the Reload interrupt */
 __HALLTDCENABLEIT(hltdc, LTDCIT_RR);

 /* Apply Reload type */
 hltdc->Instance->SRCR = ReloadType;

 /* Change the LTDC state*/
 hltdc->State = HALLTDCSTATE_READY;

 /* Process unlocked */
 __HAL_UNLOCK(hltdc);

 return HAL_OK;
}

Il y a aussi un assert\params dans stm32doom, donc je doute que ça change grand chose. L'activation de l'interruption est faite par une macro, donc ne consomme pas grand chose, et le reste c'est juste de la mise à jour d'un état du handle. Reste le lock / unlock. Je vais essayer sans. Si ça ne change rien, je pense qu'il faudrait rgarder si l'activation de l'interruption ne va pas créer un process bloquant qui ralentit tout le reste.

Grmpf ... Ca rame légèrement moins, mais c'est touours pas fluide. je vais essyer de ne pas mettre l'activation de l'interruption, voir ce qu'il se passe. Sans l'interruption : pareil. Grmpf ... Et j'ai re-check avec le stm32doom "vanilla" : c'est clairement fluide, pas comme chez moi. WTF ?

... Est-ce que je n'aurais pas une frame sur deux qui saute ? Je suspecte soit que l'écriture des frame ne se fait pas au bon endroit dans la SDRAM, et qu'en fait il y a une frame qui est écrite "en-dehors" de la deuxième frame, soit que les adresses de frame buffer en sont pas bien définies.

J'ai testé avec un breakpoint sur le lcd_refresh(), et je vois bien que l'image ne se met à jour qu'une fois sur deux. Et clairement ça se met à jour quand on va écrire en 0xD0000000, mais pas quand on écrit en 0xD0025800. Ok, ok, ok, ça se précise.

Aha ! Sur les deux layers du LTDC, une seule est configurée, l'adresse est à 0x0. Et il se trouve que dans l'initialisation du LTDC il n'y a effectivement qu'une seule couche qui est initialisée, alors qu'il y a deux initialisations dans stm32doom. Et donc je comprend mieux cette partie de l'init : les deux couches ont la même configuration, sauf l'adresse, et donc on les initialise toutes les deux avec la même structure, en changeant juste l'adresse entre les deux. Corrigé : ne marche toujours pas. Damn' ...

Bon, après moult tests et modifications, j'ai réussi à aboutir à une config d'initialisation et de refresh du LCD que l'on peut considérer fluide. J'ai toujours l'impression que c'est légèrement moins fluide que sur stm32doom, mais je pense que c'est exploitable pour le moment, en tous cas toutes les frames sont là. Il faudra que j'améliore ça, mais dans un second temps. Surtout que là je ne vois pas trop dans quelle direction aller pour avancer sur ce point particulier (et il me semble qu'il y a des choses à regarder du côté des configurations des horloges).

Conclusion

C'est chaud ... J'ai un Doom qui débugge, mais encore améliorable, et j'y ai passé des SEMAINES ! Alors que le travail était déjà mâché. Bon, j'ai abouti, c'est tout ce qui compte. Maintenant il faut que j'attaque les ajouts.

Je mets en PJ le projet complet, et je vais faire un repo public sur Github.

Pour rappel : pour le moment il n'y a pas de contrôles disponibles, donc ça joue juste la démo.

Ajouter un rétrolien

URL de rétrolien : http://blog.randagodron.eu/index.php?trackback/92

Haut de page