BootLoader du système intégré détaillé

Que faire après la mise sous tension (phase de démarrage)


1. La première ligne de programme

Après avoir obtenu la carte PCB vide, l'ingénieur en matériel testera d'abord si les circuits principaux sont connectés, si les joints de soudure sont vides, déconnectés ou en court-circuit, puis soudez les modules un par un. Après la mise sous tension du système, il est nécessaire de vérifier si la tension d'alimentation du CPU et de chaque composant est normale, si le circuit oscillant fourni au CPU peut commencer à osciller normalement et si la mémoire externe peut être lue et écrite normalement. Après avoir téléchargé notre programme sur la carte avec l'outil JTAG, les vérifications suivantes doivent être effectuées avant de déboguer le système:

  • Utilisez l'outil de débogage pour définir un point d'arrêt sur la première ligne du programme pour vous assurer que le programme s'est arrêté;
  • Vérifiez si le compteur de programme PC de la CPU est correct;
  • Vérifiez si le contenu de la RAM interne du CPU est le même que le fichier exécutable que nous avons téléchargé;
  • La première commande du programme est de définir le registre d'état de la CPU et d'observer si le registre d'état de la CPU change comme prévu;
  • Continuez en une seule étape pour confirmer si le registre du PC changera en conséquence et si le résultat de l'exécution de chaque commande est correct.

Après avoir vérifié les éléments ci-dessus, il ne peut que prouver que le circuit d'alimentation et le processeur de la carte sont normaux. Ensuite, continuez à vérifier le processeur et les périphériques, et confirmez l'exactitude et la stabilité de la carte avant de passer au test suivant.


2. Test matériel de base

Comme la responsabilité de Boot-Loader est d'aider d'autres programmes à organiser un environnement exécutable, les vérifications suivantes doivent être effectuées:

  • Test de fonctionnement du registre CPU (registre d'état, registre général, registre de carte mémoire);
// 设定SP(Stack Point)寄存器
//
asm("xld.w  %r15, 0x2000");
asm("ld.w %sp, %r15");

// 设定CPU的状态寄存器
//
asm("xld.w  %r15, 0x200010");
asm("ld.w   %psr, %r15");

// 将寄存器0x300023的bit 1设为1
*(volatile unsigned char *)0x300023 |= 0x2;
  • Le paramètre Stack Pointer est-il correct? L'appel de fonction fonctionne-t-il correctement?
  • Le compteur d'interruption est-il correctement réglé? Le programme vectoriel d'interruption fonctionne-t-il normalement?
  • Initialisation de la mémoire et test de fonctionnement pour s'assurer que toutes les mémoires peuvent être lues et écrites normalement;
  • Chargez le segment de données dans la RAM, définissez la valeur initiale du segment bss et chargez le programme qui doit fonctionner en RAM dans la RAM. Assurez-vous que les valeurs initiales des variables globales sont correctes lorsque le programme principal est exécuté.

L'étape suivante ne peut être effectuée qu'après avoir réussi le test ci-dessus.


(1) Confirmez si l'appel de fonction peut s'exécuter normalement

La configuration correcte de la pile est la condition préalable à la réussite de l'appel de la fonction. Lors du développement d'un système embarqué, le système doit gérer la pile par lui-même. Si la gestion est incorrecte, la fonction peut être appelée ou plantée après plusieurs couches d'appels. Parce que le langage C utilise la pile pour accomplir les tâches suivantes:

  • Adresse de retour de la fonction de stockage;
  • Passage de paramètres lors de l'appel de fonction (lorsqu'il y a de nombreux paramètres);
  • Variables locales à l'intérieur de la fonction de stockage;
  • Lorsque la routine de service d'interruption est exécutée (lorsqu'une interruption se produit), l'état actuel de la CPU et l'adresse de retour sont stockés.

La configuration de l'adresse du point de pile (Stack Point) est une chose très importante, mais elle est facilement négligée. Principalement lors de la programmation sous Windows ou Linux, lorsque le système d'exploitation génère un fichier exécutable, l'éditeur de liens ajoutera automatiquement un code de démarrage au programme, qui contient la configuration de la mémoire de la pile. Cependant, dans un système embarqué sans système d'exploitation, le point de pile doit être configuré avant d'appeler une fonction.

Lorsqu'une fonction est appelée en langage C, comme fun (a, b), le code machine compilé doit contenir les actions suivantes:

  • Exécutez l'instruction push, stockez les paramètres a et b dans Stack et diminuez le pointeur de pile SP de un;
  • Stocker la valeur du registre de compteur de programme courant PC (c'est-à-dire l'adresse de retour: l'adresse de l'instruction suivante de l'instruction d'appel de fonction) dans la pile;
  • Exécutez la commande Call, définissez la valeur PC sur l'adresse de la fonction fun (), et la prochaine commande à exécuter est la première commande de la fonction.
  • Lorsque la fonction fun est exécutée, la valeur SP courante peut être utilisée pour calculer les adresses des paramètres a et b;
  • S'il y a des variables locales à l'intérieur de la fonction, ces variables sont stockées dans la pile à leur tour. Par conséquent, dans le développement embarqué, essayez de ne pas définir des variables avec une taille trop grande, sinon il y a un risque de débordement de pile.
  • Lorsque la fonction est exécutée, la CPU exécutera la commande ret, qui prendra l'adresse de retour du haut de la pile et l'affectera au registre du PC. L'instruction suivante exécutera la ligne d'instructions suivante après la fonction pour terminer l'appel de fonction.

Si le registre SP n'est pas défini sur l'adresse correcte ou si une zone de stockage suffisamment grande n'est pas configurée en tant qu'espace de pile, une erreur est susceptible de se produire lors de l'appel de la fonction. La figure suivante est un exemple de dépassement de l'espace de pile, détruisant le segment de données du programme:

Les variables locales sont trop volumineuses entraînant un débordement de pile

Afin d'éviter l'apparition de la situation ci-dessus, le haut (adresse maximale) d'un certain bloc de RAM est généralement sélectionné comme valeur initiale du registre SP, mais la taille appropriée de la pile spécifique dépend de l'environnement logiciel et matériel spécifique et des exigences du projet. La méthode générale consiste à définir d'abord un peu plus grand, par exemple, environ 2 Ko à 4 Ko, puis à demander au testeur d'exécuter toutes les fonctions (fonctions) du système et d'enregistrer la valeur minimale de SP après chaque appel de fonction, qui est la même que le haut de la pile. La différence d'adresse est l'espace de pile minimum requis, généralement un peu plus.


(2) Confirmer si le système d'interruption peut fonctionner normalement

L'ingénieur responsable de l'écriture du pilote doit remplir la table des vecteurs d'interruption avec l'adresse du programme de service d'interruption et doit s'assurer que le système d'interruption est normal lorsque le pilote est exécuté. De manière générale, effectuez les tâches suivantes:

  • Un tableau de tableaux de vecteurs d'interruption, avec des notes détaillées sur la source d'interruption représentée par chaque entrée;
  • S'il s'agit d'un contrôleur d'interruption externe, le pilote du contrôleur d'interruption doit être terminé avant que le test du système d'interruption puisse être lancé.
  • Définit le registre d'adresse de la table des vecteurs d'interruption du CPU (certains processeurs n'ont pas de registre d'adresse de table de vecteurs d'interruption, mais il spécifiera une adresse fixe comme adresse de la table de vecteurs d'interruption)
  • Réglez le registre de contrôle d'interruption de la CPU (priorité, bit d'activation d'interruption, etc.)
  • Après avoir confirmé que l'interruption est déclenchée, l'ISR correspondant sera exécuté.
  • Fournissez des exemples ISR afin que les rédacteurs ISR n'aient pas besoin de connaître les détails du système d'interruption.
// ISR模板
//
void isr_template(void)
{
    // 将所有通用目的寄存器存到堆栈
    //
    asm("pushn %r15"); /*将r0 - r15 都存到堆栈中 */

    //将ALR与AHR寄存器通过r1存到堆栈
    //你无需搞清ALR和AHR是什么寄存器,不同的CPU有不同的寄存器需要存储
    //
    asm("ld.w   %r1, %alr");
    asm("ld.w   %r0, %ahr");
    asm("pushn  %r1");

    //调用C语言函数your_ISR,即真正ISR要处理的事写在该函数里就行
    //
    asm("xcall your_ISR");

    //从堆栈中取回被调用时的ALR和AHR寄存器的值
    //
    asm("popn   %r1");
    asm("ld.w   %alr, %r1");
    asm("ld.w   %ahr, %r0");

    //从堆栈中取回r1 - r15的值
    //
    asm("popn   %r15");

    //执行中断返回指令,返回被中断的程序
    //
    asm("reti");
}

Les endroits qui sont sujets à des erreurs dans les liens ci-dessus sont:

  • Le registre de priorité d'interruption n'est pas défini correctement;
  • La relation correspondante entre chaque entrée dans la table des vecteurs d'interruption et la source d'interruption est fausse;
  • L'adresse de la table de vecteurs d'interruption n'est pas définie correctement. De nombreux processeurs nécessitent que l'adresse de la table de vecteurs d'interruption soit définie à une adresse paire ou un multiple de 4, voire un multiple de 128 Ko.

Comment juger si l'ISR a été correctement exécuté? La méthode générale consiste à sélectionner une source d'interruption simple (par exemple, diviser par 0 interruption d'erreur), définir un point d'arrêt dans son ISR, puis en une seule étape pour voir si le programme ISR peut être exécuté en douceur et revenir à l'endroit où l'interruption s'est produite (diviser par zéro) L'énoncé suivant de l'instruction).

(3) Test de mémoire

Les problèmes de mémoire sont:

  • Aspect matériel: mauvaise connexion de la ligne de données et de la ligne d'adresse;

  • Aspect logiciel: SRAM, NOR Flash et ROM ne nécessitent pas de circuits supplémentaires et peuvent être utilisés directement, mais la SDRAM nécessite l'utilisation de circuits de contrôleur SDRAM supplémentaires. Le programme doit d'abord définir la configuration du contrôleur SDRAM (taille de la SDRAM, vitesse, etc.);

  • Le réglage de la synchronisation de la mémoire externe, si le réglage de la synchronisation est trop rapide, le système sera instable, trop lent, les performances du système se détérioreront. Le tableau de réglage de la synchronisation du processeur général expliquera comment le régler.

  • Avant de passer à la partie inférieure du travail, testez chaque octet de la mémoire pour vous assurer que la lecture et l'écriture (si elle peut être écrite) sont normales. La méthode consiste à écrire à tour de rôle 0x00, 0xFF, 0x55, 0xAA dans chaque octet, en s'assurant que chaque bit sera écrit avec 0 et 1.

  • int SRAM_testing(void)
    {
      int i,counter =0;
      //待测RAM起始地址为0x2000000,大小为2MB.
      unsigned char *pointer = (unsigned char *)0x2000000;
      unsigned char data[4]={0x00,0xFF,0x55,0xAA};
    
      for(i=0; i<4; i++)
      {    // 逐一对每个字节写入某特殊值
          for(j=0; j<(8*1024*1024); j++)
              pointer[i] = data[i] 
           // 逐一读出每个字节,判断写入的值是否正确      
          for(j=0; j<(8*1024*1024); j++)
              pointer[i]==data[i]?::counter++;
      }      
      return counter; //返回出错字节的个数  
    }
  • Pour la ROM en lecture seule, comment vérifier que les données gravées dans la mémoire sont cohérentes avec le fichier image d'origine? La méthode d'inspection de la somme de contrôle est généralement utilisée. Autrement dit, calculez si la somme de contrôle du fichier image d'origine et du fichier gravé dans la ROM sont égales.

  • /***************************************************************
    Function Name: calculate_ROM_checksum
    Function Purpuse:计算起始地址为0x2000000,size为8MB存储器的校验和
    ****************************************************************/
    unsigned long calculate_ROM_checksum(void)
    {
      unsigned long checksum = 0;
      unsigned char *pointer = 0x2000000;
      for(i=0; i<(8*1024*1024); i++)
          checksum += pointer[i];
      return checksum;
    }

(4) Initialisation du CPU

Pendant la phase Boot-Loader, les paramètres suivants liés au processeur doivent être définis:

  • Définissez le registre de pointeur de pile SP;
  • Définissez le registre d'état et désactivez les interruptions;
  • Définir le pointeur de table de vecteur d'interruption;
  • Définir l'état d'exécution du processeur (synchronisation de l'horloge);
  • Réglez le contrôleur de mémoire (si une mémoire de type SDRAM est utilisée);
  • Définissez la synchronisation du fonctionnement du processeur de chaque mémoire;
  • Réglez la fonction PIN de la CPU;
  • Initialisez les périphériques (contrôleur LCD, contrôleur USB, interface de carte SD, etc.)

3. Chargement des segments de programme et initialisation des données

(1) Chargez la section de données

Les variables globales avec des valeurs initiales doivent être stockées dans un fichier exécutable et gravées dans la ROM. Mais comme la valeur de ces variables globales sera modifiée pendant l'exécution, elle ne peut bien sûr pas fonctionner en ROM et doit être adressée à la RAM lorsqu'elle est connectée. C'est précisément à cause de cette caractéristique de "stocké en ROM, exécuté en RAM" qu'il est nécessaire de transférer le segment de données, et ces choses doivent être faites avant que tous les programmes utilisent des variables globales.

Utilisation de la mémoire pendant l'exécution

Dans la figure ci-dessus, le contenu du segment de données était à l'origine après le segment rodata dans le fichier exécutable, mais pendant l'exécution, le segment de données doit être copié après le segment bss dans la RAM. Le script de connexion est le suivant:

.data __END_bss : AT(__END_rodata)
{
    __START_data = .;
    *(.data);
    __END_data = .;

    // 定义可在程序中使用的变量“__START_data_LMA”,表示data段的存储起始地址LMA
    __START_data_LMA = LOADADDR(.data);

    //定义可在程序中使用的变量“__SIZE_DATA”,表示data段的大小
    __SIZE_DATA = __END_data - __START_data;
}

La procédure de transfert est la suivante:

/**************************************************
Function Name: copy_data_section()
Function Purpuse:将可执行文件中的数据段复制到内存中
***************************************************/
extern unsigned long *__START_data;
extern unsigned long *__START_data_LMA;
extern int __SIZE_DATA;

void copy_data_section(void)
{
    int i;
    unsigned long *dest = __START_data;
    unsigned long *src = __START_data_LMA;
    //假设data段的大小是4的整数倍个字节
    for(i=0; i<(__SIZE_DATA/4); i++)
        dest[i] = src[i];    
}

(2) Définissez le segment bss

La configuration de la section bss est relativement simple, car les membres de la section bss sont des variables globales sans valeur initiale et aucun espace de stockage n'est nécessaire. Lors de l'exécution, définissez simplement l'espace d'exécution (VMA) de la section bss sur 0.

/*******************************************
定义bss段,起始地址(VMA)从0开始
******************************************/
.bss 0x0 : 
{
    __START_bss = .;
    *(.bss);
    __END_bss = .;

    //定义可在程序中使用的变量:__SIZE_BSS
    __SIZE_BSS = __END_bss - __START_bss;
}

Le code pour définir le segment bss sur 0 est le suivant:

/**************************************************
Function Name: clear_bss_section()
Function Purpuse:将bss段清零
***************************************************/
extern unsigned long * __START_bss;
extern int __START_BSS;

void clear_bss_section(void)
{
    int i;
    unsigned long * dest = __START_bss;
    //假设bss段的大小为4的整数倍字节大小
    for(i=0; i<(__SIZE_BSS/4); i++)
        dest[i] = 0;
}

Attention : Pendant la phase de démarrage, la section de données et la section bss doivent être définies en premier, sinon la valeur de la variable globale lors de l'exécution est incorrecte. En d'autres termes, le programme de démarrage ne peut pas utiliser de variables globales avant de définir les sections data et bss. Si vous devez les utiliser, évitez d'attribuer des valeurs lors de la définition des variables globales et vous devez explicitement affecter des valeurs dans le programme. Par exemple:

<img src = " https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20201125224022.png " alt = "Vous devez être prudent lorsque vous utilisez des variables globales dans le programme Boot-Loader" style = "zoom: 80%;" />

(3) Chargez le segment de texte

Lorsqu'un programme système ou un module de programme d'application nécessite une vitesse d'exécution plus élevée, ils peuvent souvent être copiés dans la mémoire système pour être exécutés. Cependant, la mémoire système est souvent limitée en espace et il est impossible de toutes les charger en même temps. Nous écrivons donc généralement une fonction, l'adressons à la même adresse et la chargeons en cas de besoin.

Les performances de différents types de mémoire dans l'ordre décroissant sont les suivantes: registre CPU, cache CPU, RAM interne CPU, SRAM externe, NOR Flash, SDRAM, Mask ROM, NAND Flash.

NAND Flash: Prix bas et grande capacité. Considérez-le comme un périphérique semblable à un disque dur, mais il ne peut pas être directement adressé et le programme ne peut pas être exécuté directement sur lui;

NOR Flash: Le prix est élevé, la capacité est petite, mais la lecture des données est rapide. Pensez-y comme une ROM réinscriptible, et le programme peut s'exécuter directement dessus.

Mask ROM: coût élevé et capacité limitée, mais le programme peut être exécuté directement dessus;

SDRAM: performances à haut coût, généralement utilisées comme mémoire externe du système, le programme peut être exécuté directement dessus;

SRAM: cher, de petite capacité, généralement utilisé comme mémoire intégrée du système, le programme peut être exécuté directement dessus.

(4) Plusieurs architectures de mémoire système

  • Architecture de démarrage à partir de NAND Flash:

  • image-20201125230828425

  • Le processus de démarrage est :

    • Après la mise sous tension, le programme intégré du CPU lira le programme Boot-Loader à partir de l'adresse spécifique du NAND Flash (généralement la première adresse de bloc) vers la mémoire interne du CPU.
    • Le CPU transfère le contrôle au Boot-Loader dans la mémoire interne;
    • Boot-Loader initialise la SDRAM, puis charge le programme principal dans la SDRAM à partir de NAND Flash;
    • Boot-Loader transfère le contrôle au programme principal.

    Pour plus d'informations, veuillez cliquer et suivre:
    Embedded Linux & ARM
    CSDN Blog
    Brief Book Blog
    Know the Column

Je suppose que tu aimes

Origine blog.51cto.com/14592069/2556462
conseillé
Classement