Wednesday, July 29, 2009

powersave

Jusqu'ici, mes petits programmes étaient plutôt dévoreurs de batterie sur la DS. J'avais beau dire à mes p'tits n'veux que "tu fermes ta DS et tu viens manger tes frites, le jeu sera toujours là quand tu auras fini", c'était loin d'être le cas pour Bilou ou mon sprite editor.

Et pourtant, le fonctionnement est plutôt simple: un registre (REG_POWERCNT dans libnds et POWCNT1 sur gbatek) permet d'activer ou de désactiver certaines puces de la DS pour réduire la consommation. Côté ARM7 (connu alors sous POWCNT2), il contrôle le son et le Wifi. Côté ARM9, il contrôle les processeurs graphiques, permettant d'activer ou de désactiver séparément la 2D, la 3D, etc. Tout ce qu'il y a à faire, c'est :

if (lid_closed()) {
u16 power = REG_POWER;
REG_POWER = 0;
while (lid_closed()) swiWaitForVBlank();
REG_POWER = power;
}
la position du capot étant donnée par le 7eme bit des "boutons" de la console. Le coeur du fonctionnement étant évidemment la boucle "while(lid_closed) waitForVBlank() qui va utiliser les fonctions du BIOS de la DS pour mettre le CPU en pause jusqu'à ce qu'une interruption se produise (en l'occurrence un retour d'écran), et ce en continu tant que le capot est fermé. Au lieu d'exécuter 66 millions d'instructions par secondes, le processeur se contentera alors d'environ 600 à 1200 d'entre-elles (à la grosse louche, hein) et le rendu graphique est désactivé.

Evidemment, pour faire "pro", il faudrait aussi signaler au co-processeur (l'ARM7) de mettre en berne le son (sauf si on programme un iPoDS) et le Wifi (sauf si un transfer de fichier est en cours). En clair, si l'ARM7 pourrait gérer sa puissance de manière autonome, il reste préférable de le reléguer dans un rôle d'esclave.

Ma première tentative pour appliquer ça n'a pas été franchement probante, mais quelques "printf" m'ont vite révélé mon erreur : je n'ai encore aucune routine d'interruption pour le 'retour d'écran' (VBLANK) et les interruptions VBLANK sont carrément masquées par mes programmes. Résultat, swiWaitForVBlank() attend indéfiniment :P Si j'y remédie et que je passe à du "waitForVBlank" dans ma boucle principale également, c'est toute l'application qui devrait devenir nettement moins gourmande.
while(REG_VCOUNT>192); // wait for vblank
while(REG_VCOUNT<192);
c'est indigne d'un vétéran. Quand mes étudiants codent comme ça, je les menace de leur imposer de coder sous DOS.

Et si je vous raconte tout ça au conditionnel, c'est que mon routeur WiFi à la maison m'a lâchement laché, rendant le développement DS aussi pratique qu'à Bâle, le temps libre en moins.

edit: après essai, curieusement, le son est mis en veille lui aussi, automatiquement et sans communication explicite de ma part entre ARM7 et ARM9 ... Ne loupez pas non plus le commentaire très intéressant de thoduv (lapinou jumps) pour passer de la théorie à la pratique "pro".

4 comments:

cyborgjeff said...

toujours pas trouvé de soluce pour ton routeur ?
Sinon, c'est vrai qu'à prime à bord, on aurait pas penser à s'occuper de faire de l'économie de batterie... c'est une bonne idée de ta part !

thoduv said...

Si ça a effectivement l'air simple en théorie, la réalisation est nettement plus penible. Parce qu'en fait, si ta méthode permet déjà de réduire la consommation de batterie, elle a l'inconvénient que les deux CPU tournent toujours. Or, il est possible de les mettre en veille, de façon à ce qu'ils ne se réveillent que lors d'une IRQ (via swiSleep): c'est la meilleure méthode, et celle utilisée par les jeux officiels (sauf lorsque le Wifi fonctionne, on ne peut alors pas faire mieux). Voilà la méthode "pro". ^^

On peut alors paramétrer l'ARM7 pour ne se réveiller qu'à l'IRQ "lid", et l'ARM9 pour ne se réveiller qu'à l'IRQ "ipcsync" en provenance de l'ARM7. Mais ça devient tout de suite casse-tête à mettre en place notamment au niveau de la gestion des IRQ, de la communication IPC (qui utilise des IRQ), du son, et des accès disque (et ça devient carrément affreux si tu streame l'audio depuis la carte mémoire). Trouver où insérer des pauses, des timings et tout devient assez galère (je trouve).

J'avais mis ça en place dans Lapinou (je ne pouvais pas me résoudre à faire ça à moitié !), et ça marchait pas trop mal: quelque fois seulement la veille ne s'arrêtait plus (dommage!), et souvent le streaming MP3 ne redémarrait pas.
Si tu veux des infos ou du code, n'hésite pas.

Néanmoins ça a commencé a être implémenté récemment dans la libnds, de façon espérons-le correcte. (seule la partie ARM7 est faite: http://devkitpro.svn.sourceforge.net/viewvc/devkitpro/trunk/libnds/source/arm7/system.c?view=markup&sortby=date)

PypeBros said...

argh. un petit test avec écouteurs de "AppleAssault" vient de briser la magie: le processeur ARM7 n'a pas coupé le chip sonore et ne s'est pas mis du tout en veille. En fait, le fait de passer en "sleep" sur l'ARM9 a tout simplement désactivé les hauts-parleurs de la DSlite de la même manière que si j'avais branché des écouteurs. Mais dans les écouteurs, le son, lui, tourne toujours :P

PypeBros said...

@thoduv: effectivement, dans la libnds contemporaine du devkit 32, swiSleep et consort sont utilisés d'emblée. Et avec le même risque de non-reprise que celui que tu décrivais.