Go Down

Topic: [RESOLU] Adressage supérieur à 64ko sur une MEGA ? (Read 2052 times) previous topic - next topic

bricoleau

May 18, 2015, 07:53 pm Last Edit: May 21, 2015, 11:23 pm by bricoleau
Bonjour

Petite question pour anticiper des problèmes que je sens poindre...

J'utilise couramment PROGMEM (librairie avr/pgmspace.h) pour stocker en flash mes données constantes.
Il s'agit principalement (mais pas seulement) de chaînes de caractères.

Mon petit projet perso avance doucement mais sûrement.
Le code compilé approche gentiment les 65536 octets, qu'il va allègrement dépasser au final.

Et là, pris d'un doute, avant de me retrouver avec un exécutable qui fait n'importe quoi, je fais une petite vérif et pan !

Le nono ne gère l'adressage que sur 16 bits

Code: [Select]
void setup() {
  void (*pointeurFonction)();

  Serial.begin(9600);
  
  pointeurFonction = loop;
  
  Serial.print("taille d'un pointeur de fonction=");
  Serial.print(sizeof(pointeurFonction));
  Serial.println(" octets");
  
  Serial.print("taille PGM_P=");
  Serial.print(sizeof(PGM_P));
  Serial.println(" octets");
}

void loop() {
}


Code: [Select]
taille d'un pointeur de fonction=2 octets
taille PGM_P=2 octets



On fait comment pour :
1) accéder à des constantes stockées en flash, au-delà des 64 ko ?
2) gérer des pointeurs sur des fonctions placées également au-delà des 64 ko ?

D'ailleurs, le nono gère comment son propre pointeur d'instruction sur une mega ?
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

fdufnews


_pepe_

#2
May 18, 2015, 09:47 pm Last Edit: Aug 20, 2018, 01:43 am by _pepe_
Supprimé

bricoleau

Merci pour les infos

Effectivement j'ai commencé par dépiauter la lib pgmspace.h
On y trouve des fonctions suffixées par _P, que j'utilisais déjà pour recopier des data depuis la mémoire flash vers la RAM.

Par exemple
Code: [Select]
void test()
  static const char texte_P[] PROGMEM = "Ma constante de type texte";
  const size_t taille_texte = sizeof(texte_P);
  char texte[taille_texte];

  strcpy_P(texte, texte_P);
  ...



Sont également disponibles, des fonctions suffixées _PF, pour faire la même chose mais en partant d'un pointeur long uint_farptr_t codé sur 4 octets.
Le problème est d'avoir l'adresse longue de la variable en flash.

De ce que j'en comprends, en natif le compilo n'est pas en mesure de fournir cette adresse longue.
Il faut en passer par une macro GET_FAR_ADDRESS, que j'ai récupérée sur le net :
Code: [Select]
#define GET_FAR_ADDRESS(var) \
({ \
uint_farptr_t tmp; \
\
__asm__ __volatile__( \
\
"ldi %A0, lo8(%1)" "\n\t" \
"ldi %B0, hi8(%1)" "\n\t" \
"ldi %C0, hh8(%1)" "\n\t" \
"clr %D0" "\n\t" \
: \
"=d" (tmp) \
: \
"p" (&(var)) \
); \
tmp; \
})


A vue de nez, je dirais que cette macro insère du code en assembleur, que l'on ne saurait faire générer par le compilateur.

Et effectivement, l'adaptation de l'exemple semble fonctionner :
Code: [Select]
void test()
  static const char texte_P[] PROGMEM = "Ma constante de type texte";
  const size_t taille_texte = sizeof(texte_P);
  char texte[taille_texte];

  strcpy_PF(texte, GET_FAR_ADDRESS(texte_P));
  ...


Je dis "semble", car je n'ai pas testé avec un texte stocké au-delà des 64ko.

Cela devrait déjà résoudre un premier point.
Avant que j'enchaîne sur les points suivants, voyez-vous quelquechose à rectifier dans l'analyse jusqu'ici?
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

bricoleau

Bon, peu d'avis éclairé dans ce court laps de temps.
Pas grave ni anormal, voyons voir si je peux appâter un peu plus quelques "experts" arduino  :)

Passons à présent aux choses sérieures :

Comment adapter la fonction ci-dessous, pour la rendre compatible avec des textes stockés en mémoire flash au-delà des 64ko ?
Code: [Select]
void chargerTexte(uint8_t id_texte, char cible[])
{
  static const char texte000_P[] PROGMEM = "Mon texte zero";
  static const char texte001_P[] PROGMEM = "Mon texte un";
  static const char texte002_P[] PROGMEM = "Mon texte deux";
  static const char texte003_P[] PROGMEM = "Mon texte trois";
  static const char texte004_P[] PROGMEM = "Mon texte quatre";
  static const char * const textes_P[] PROGMEM = {
    texte000_P,
    texte001_P,
    texte002_P,
    texte003_P,
    texte004_P};

  strcpy_P(cible, (char*)pgm_read_word(&(textes_P[id_texte])));
}


Cette fonction est basée sur le standard proposé par le team arduino pour gérer une liste de libellés en flash
http://www.arduino.cc/en/Reference/PROGMEM

Elle utilise un tableau "textes_P" de pointeurs (courts) vers une liste de textes.
Le tableau "textes_P" est lui-même stocké en flash.

Tout le problème consiste à créer en flash un tableau textes_P de pointeurs longs.
Une fois ce tableau créé, il sera alors facile d'y lire l'adresse longue d'un texte en particulier, puis de le récupérer par la méthode précédente.
Seulement voilà, j'ai beau chercher, je ne trouve pas de moyen de faire créer ce tableau par le compilo.

Ce serait d'ailleurs cohérent avec le fait que pour valoriser une adresse longue, il est nécessaire de passer par quelques instructions en assembleur.

Est-ce quelqu'un aurait une idée d'implémentation ?

Pour ma part, j'en vois deux mais aucune vraiment satisfaisante

1) remplacer le tableau de pointeurs par un gros switch
Code: [Select]
switch (id_texte)
{
  case 0 : strcpy_PF(cible, GET_FAR_ADDRESS(texte000_P));break;
  case 1 : strcpy_PF(cible, GET_FAR_ADDRESS(texte001_P));break;
  case 2 : strcpy_PF(cible, GET_FAR_ADDRESS(texte002_P));break;
  ...
}


Ca fonctionne forcément.
Seul petit problème : mon programme contient une fonction de cette nature, pour gérer plus de 200 messages distincts.
Coder un gros switch infâme à 200 case n'est vraiment pas terrible (poids du code compilé + temps d'exécution).

2) remplacer le tableau de pointeurs par un tableau d'offset
En tablant sur le fait que le compilo va stocker tous les textes en suivant dans la flash, et toujours dans le même ordre.
Code: [Select]
const uint16_t offset_texte000 = 0;
const uint16_t offset_texte001 = offset_texte000 + sizeof(texte000_P);
const uint16_t offset_texte002 = offset_texte001 + sizeof(texte001_P);
... etc

static const uint16_t textes_P[] PROGMEM = {
  offset_texte000,
  offset_texte001,
  offset_texte002,
  ...};

Et derrière, déterminer l'adresse longue par un GET_FAR_ADDRESS du premier texte, en y ajoutant l'offset correspondant au texte désiré (offset lui-même lu dans le tableau des offset en flash).

Faut juste vérifier l'ordre ascendant ou descendant des adresses des textes, et ajuster le code en conséquence.

C'est un peu lourdingue au niveau du source, mais les const uint16_t disparaissent à la compil.
Le problème, c'est que cette solution peut être fragile, car elle repose probablement sur des règles de compilation / rangement des données en flash.

Donc là, si quelqu'un a une autre idée, je suis preneur !

Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

bricoleau

Enfin, voici le dessert : les pointeurs de fonction.

Mon programme en utilise quelques dizaines, de manière tout à fait classique pour de la programmation en C.
Problème : le pointeur lui-même est une donnée stockée en RAM sur 2 octets.

Lorsque la fonction appelée va se trouver en flash au-delà des 64ko, l'appel va aller n'importe où et faire n'importe quoi.

Et quand bien même j'arriverais à gérer en RAM des adresses longues pour les fonctions par un GET_FAR_ADDRESS(mafonction), je ne vois pas comment appeler en C la fonction pointée.
Peut-être qu'il faudrait là aussi une macro (que j'ai pas trouvée) embarquant quelques instructions en assembleur, pour gérer un appel long par adresse.
Ce qui est paradoxal, c'est que dans le cas d'un appel direct de fonction (par son nom, et non via un pointeur), l'adressage long est correctement géré au niveau du link.

Là, je sèche complètement pour trouver une solution propre.

A défaut, je risque d'être contraint à abandonner les pointeurs de fonction, ce qui n'est guère séduisant.

C'est-à-dire en gros, remplacer tous ces pointeurs par un code d'identification, et passer par des aiguilleurs d'appel :
Code: [Select]
void appelFonction(uint8_t laquelle)
{
  switch (laquelle)
  {
    case 0 : mafonction00();break;
    case 1 : mafonction01();break;
    ...
  }
}

Là au moins les appels longs seront correctement générés au niveau du link.
Le "pointeur" devient ainsi un simple uint8_t contenant l'identifiant de la fonction à appeler.
J'ai quand même un peu de bol, toutes les fonctions pointées ont même prototype, sinon il faudrait en plus se taper une gestion argc/argv.

Seulement, toutes ces fonctions pointées sont réparties dans n librairies distinctes et isolées.
De surcroît, elles sont internes à chaque librairie, et non exposées dans les .h
La mise en place d'un ou plusieurs "aiguilleurs" risque d'être très déstructurante pour le code source.

help please
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

bricoleau

#6
May 19, 2015, 11:02 pm Last Edit: May 19, 2015, 11:02 pm by bricoleau
Hello

Pendant l'avalanche de réactions et conseils, j'ai continué à rechercher sur le net.

Il semble que les pointeurs de fonctions ne soient pas un problème, y compris pour taper sur des fonctions stockées au-delà des 64k de mémoire flash... si certaines options de compilation / link sont correctement positionnées par l'IDE.

C'est-à-dire qu'on peut avoir un pointeur codé sur 16 bits, qui permette d'atteindre une adresse supérieure à 2^16. eeeet oui ! (si si).


Lorsque l'on affecte un pointeur de fonction avec l'adresse d'une fonction "lointaine", ce bougre de com-
-pilateur (désolé) s'en aperçoit, et il y a création automatique d'une section trampoline dans la première partie de la flash.

Comme son joli nom l'indique, la section trampoline permet de rebondir vers l'extérieur des 64k.

Le pointeur contient en réalité l'adresse sur 16 bits d'une zone de code dans les 64k, où se trouvent des instructions qui effectuent un jump vers la fonction lointaine.

Si la piste trampoline est OK, il ne reste donc plus que le problème du tableau de pointeurs longs vers des chaînes de caractères, pour gérer proprement un gros paquet de textes...
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

_pepe_

#7
May 20, 2015, 07:28 pm Last Edit: Aug 20, 2018, 01:43 am by _pepe_
Supprimé

bricoleau

Merci pour cette réponse

Je comprends la méthode, mais ne dispose pas des compétences en assembleur avr pour la mettre en oeuvre, ni d'une grande envie de me perfectionner dans ce domaine :D

De mon côté, je pars sur la solution avec des offset sur 16 bits, par rapport à une adresse longue de référence.

Si cela ne donne pas satisfaction, j'ai en tête une autre solution qui me paraît dans mes cordes : compiler le source avec un tableau d'adresses longues valorisées à zéro, puis intercepter le .hex avant téléversement pour le hacker par un programme qui le modifierait en valorisant correctement les adresses, par analyse de la section data contenant mes chaînes de caractères.

Au pire j'aurais à ajouter des constantes de type signatures numériques, qui me serviront de balises de repérage avant/après la zone du .hex qui m'intéresse.
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

_pepe_

#9
May 20, 2015, 11:01 pm Last Edit: Aug 20, 2018, 01:43 am by _pepe_
Supprimé

bricoleau

C'est bon, j'ai une solution qui fonctionne pour les tableaux de chaînes de caractères.

Rappelons le problème : il s'agit de rendre la fonction ci-dessous compatible avec une implantation des données au-delà la barrière des 64 ko
Code: [Select]
void chargerTexte(uint8_t id_texte, char cible[])
{
  static const char texte000_P[] PROGMEM = "Mon texte zero";
  static const char texte001_P[] PROGMEM = "Mon texte un";
  static const char texte002_P[] PROGMEM = "Mon texte deux";
  static const char texte003_P[] PROGMEM = "Mon texte trois";
  static const char texte004_P[] PROGMEM = "Mon texte quatre";
  static const char * const textes_P[] PROGMEM = {
    texte000_P,
    texte001_P,
    texte002_P,
    texte003_P,
    texte004_P};

  strcpy_P(cible, (char*)pgm_read_word(&(textes_P[id_texte])));
}


Voici l'adaptation
Code: [Select]
#define GET_FAR_ADDRESS(var)                          \
({                                                    \
    uint_farptr_t tmp;                                \
                                                      \
    __asm__ __volatile__(                             \
                                                      \
            "ldi    %A0, lo8(%1)"           "\n\t"    \
            "ldi    %B0, hi8(%1)"           "\n\t"    \
            "ldi    %C0, hh8(%1)"           "\n\t"    \
            "clr    %D0"                    "\n\t"    \
        :                                             \
            "=d" (tmp)                                \
        :                                             \
            "p"  (&(var))                             \
    );                                                \
    tmp;                                              \
})

void chargerTexte(uint8_t id_texte, char cible[])
{
  static const char texte000_P[] PROGMEM = "Mon texte zero";
  static const char texte001_P[] PROGMEM = "Mon texte un";
  static const char texte002_P[] PROGMEM = "Mon texte deux";
  static const char texte003_P[] PROGMEM = "Mon texte trois";
  static const char texte004_P[] PROGMEM = "Mon texte quatre";

  const uint16_t offset_texte004 = 0; //les données sont rangées à l'envers
  const uint16_t offset_texte003 = offset_texte004 + sizeof(texte004_P);
  const uint16_t offset_texte002 = offset_texte003 + sizeof(texte003_P);
  const uint16_t offset_texte001 = offset_texte002 + sizeof(texte002_P);
  const uint16_t offset_texte000 = offset_texte001 + sizeof(texte001_P);

  static const uint16_t offsets[] PROGMEM = {
    offset_texte000,
    offset_texte001,
    offset_texte002,
    offset_texte003,
    offset_texte004};

  uint_farptr_t farptr;
  uint16_t offset;
 
  farptr = GET_FAR_ADDRESS(offsets);
  offset = pgm_read_word_far(farptr + (id_texte << 1));
  farptr = farptr + sizeof(offsets) + offset;
 
  strcpy_PF(cible, farptr);
}


Evidemment, j'ai mis 5 textes pour l'exemple, mais la liste peut être beaucoup plus longue.
Elle n'est limitée que par la gestion d'offset sur 16 bits, soit 65536 caractères au total pour l'ensemble des textes de la liste.
Pour s'affranchir de cette limite, il suffirait de passer les offsets sur 32 bits
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

bricoleau

#11
May 21, 2015, 11:10 pm Last Edit: May 21, 2015, 11:25 pm by bricoleau
Voici un programme de test

Celui-comporte 4 listes de 5 textes :
La liste A est stockée en flash sous la barre des 64 ko
La liste B est à cheval sur les 64 ko
La liste C est au-delà des 64 ko
La liste D est au-delà de 128 ko

J'ai ajouté des fillers pour remplir l'espace.

<Code trop gros - je le mets en pièce jointe>

Le résultat compilé fait 137 000 octets, ce qui le rend un peu long au téléversement ;D

Résultat OK à l'exécution :

Affichage des adresses des différentes données et filler, puis affichage correct des textes quelle que soit leur zone de stockage.

Code: [Select]
filler00, ptr=01:08, farptr=00:00:01:08 (000264) +000264
filler01, ptr=4f:28, farptr=00:00:4f:28 (020264) +020000
filler02, ptr=9d:48, farptr=00:00:9d:48 (040264) +020000
txtAff_P, ptr=fe:f0, farptr=00:00:fe:f0 (065264) +025000
txtA04_P, ptr=fe:fa, farptr=00:00:fe:fa (065274) +000010
txtA03_P, ptr=ff:36, farptr=00:00:ff:36 (065334) +000060
txtA02_P, ptr=ff:68, farptr=00:00:ff:68 (065384) +000050
txtA01_P, ptr=ff:90, farptr=00:00:ff:90 (065424) +000040
txtA00_P, ptr=ff:ae, farptr=00:00:ff:ae (065454) +000030
txtBff_P, ptr=ff:c2, farptr=00:00:ff:c2 (065474) +000020
txtB04_P, ptr=ff:cc, farptr=00:00:ff:cc (065484) +000010
txtB03_P, ptr=00:08, farptr=00:01:00:08 (065544) +000060
txtB02_P, ptr=00:3a, farptr=00:01:00:3a (065594) +000050
txtB01_P, ptr=00:62, farptr=00:01:00:62 (065634) +000040
txtB00_P, ptr=00:80, farptr=00:01:00:80 (065664) +000030
txtCff_P, ptr=00:94, farptr=00:01:00:94 (065684) +000020
txtC04_P, ptr=00:9e, farptr=00:01:00:9e (065694) +000010
txtC03_P, ptr=00:da, farptr=00:01:00:da (065754) +000060
txtC02_P, ptr=01:0c, farptr=00:01:01:0c (065804) +000050
txtC01_P, ptr=01:34, farptr=00:01:01:34 (065844) +000040
txtC00_P, ptr=01:52, farptr=00:01:01:52 (065874) +000030
filler03, ptr=01:66, farptr=00:01:01:66 (065894) +000020
filler04, ptr=4f:86, farptr=00:01:4f:86 (085894) +020000
filler05, ptr=9d:a6, farptr=00:01:9d:a6 (105894) +020000
txtDff_P, ptr=03:36, farptr=00:02:03:36 (131894) +026000
txtD04_P, ptr=03:40, farptr=00:02:03:40 (131904) +000010
txtD03_P, ptr=03:7c, farptr=00:02:03:7c (131964) +000060
txtD02_P, ptr=03:ae, farptr=00:02:03:ae (132014) +000050
txtD01_P, ptr=03:d6, farptr=00:02:03:d6 (132054) +000040
txtD00_P, ptr=03:f4, farptr=00:02:03:f4 (132084) +000030

flash_txtA00_P flash_txtB00_P flash_txtC00_P flash_txtD00_P
flash_txtA01_P flash_txtB01_P flash_txtC01_P flash_txtD01_P
flash_txtA02_P flash_txtB02_P flash_txtC02_P flash_txtD02_P
flash_txtA03_P flash_txtB03_P flash_txtC03_P flash_txtD03_P
flash_txtA04_P flash_txtB04_P flash_txtC04_P flash_txtD04_P
End
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

Go Up