Après les opérations ci-dessus, nous pouvons démarrer la première tâche. La fonction prvStartFirstTask() est utilisée pour démarrer la première tâche. Il s'agit d'une fonction d'assemblage. Le code source de la fonction est le suivant :
__asm void prvStartFirstTask( void )
{
PRESERVE8
ldr r0, =0xE000ED08 ;R0=0XE000ED08 (1)
ldr r0, [r0] ;取 R0 所保存的地址处的值赋给 R0 (2)
ldr r0, [r0] ;获取 MSP 初始值 (3)
msr msp, r0 ;复位 MSP (4)
cpsie I ;使能中断(清除 PRIMASK) (5)
cpsie f ;使能中断(清除 FAULTMASK) (6)
dsb ;数据同步屏障 (7)
isb ;指令同步屏障 (8)
svc 0 ;触发 SVC 中断(异常) (9)
nop
nop
}
(1) Enregistrez 0XE000ED08 dans le registre R0. De manière générale, la table vectorielle doit être stockée à partir de l'adresse de départ (0X00000000).Cependant, certaines applications peuvent avoir besoin de modifier ou de redéfinir la table vectorielle au moment de l'exécution.Le processeur Cortex-M fournit une fonction appelée relocalisation de la table vectorielle à cette fin.caractéristique . La fonction de relocalisation de table vectorielle fournit un registre programmable appelé registre de décalage de table vectorielle (VTOR). L'adresse du registre VTOR est 0XE000ED08. La table vectorielle peut être redéfinie via ce registre. Par exemple, dans la bibliothèque officielle ST de STM32F103, le registre VTOR sera défini via la fonction SystemInit(). Le code est le suivant : SCB ->VTOR = FLASH_BASE | VECT_TAB_OFFSET ; //VTOR =0x08000000+0X00 Grâce à la ligne de code ci-dessus, l'adresse de début de la table vectorielle est redéfinie sur 0X08000000, et l'adresse de début de la table vectorielle stocke la valeur initiale de MSP.
(2) Lisez les données à l'adresse stockée dans R0 et enregistrez-les dans le registre R0, c'est-à-dire lisez la valeur dans le registre VTOR et enregistrez-la dans le registre R0. Une fois cette ligne de code exécutée, la valeur de R0 doit être 0X08000000 . C'est l'adresse de départ de la redéfinition de la table des vecteurs
(3) Lisez les données à l'adresse stockée dans R0 et enregistrez-les dans le registre R0, c'est-à-dire lisez les données stockées à l'adresse 0X08000000 et enregistrez-les dans le registre R0 (car la valeur de MSP est enregistrée dans le premier des variables) . Nous savons que l'adresse initiale de la table vectorielle stocke la valeur initiale du pointeur de pile principal MSP.Après l'exécution de cette ligne de code, le registre R0 stocke la valeur initiale de MSP. Regardons maintenant (1), (2), (3) ces trois étapes sont juste pour obtenir la valeur initiale de MSP !
(4) Réinitialiser MSP, la valeur initiale de MSP est enregistrée dans R0, l'affecter à MSP équivaut à réinitialiser MSP.
(5) et (6), activez l'interruption, pour plus de détails sur ces deux instructions, reportez-vous à la section 4.2.3 du "Chapitre 4 Architecture" dans "The Definitive Guide".
(7) et (8), barrières de synchronisation des données et de synchronisation des instructions, pour plus de détails sur ces deux instructions, veuillez vous reporter à la section 5.6.13 du "Chapitre 5 Jeu d'instructions" dans "Le guide définitif".
(9) Appelez l'instruction SVC pour déclencher l'interruption SVC , SVC est également appelé appel de gestion de demande, les exceptions SVC et PendSV sont très importantes pour la conception du système d'exploitation. Les exceptions SVC sont déclenchées par l'instruction SVC. Pour plus de détails sur SVC, veuillez vous reporter à la section 10.3 du "Chapitre 10 Fonctionnalités de prise en charge du système d'exploitation" dans "Le guide définitif". Dans FreeRTOS, seule l'exception SVC est utilisée pour démarrer la première tâche, et SVC n'est plus utilisé dans les programmes suivants.
8.2.4 Fonction de service d'interruption SVC
L'interruption SVC est déclenchée en appelant l'instruction SVC dans la fonction prvStartFirstTask(), et le démarrage de la première tâche est terminé dans la fonction de service d'interruption SVC. La fonction de service d'interruption SVC doit être SVC_Handler(), mais dans FreeRTOSConfig.h via #define La façon de redéfinir xPortPendSVHandler() est la suivante :
#define xPortPendSVHandler PendSV_Handler La fonction vPortSVCHandler() est définie dans le fichier port.c. Cette fonction est également écrite en assembleur. Le code source de la fonction est le suivant :
__asm void vPortSVCHandler( void )
{
PRESERVE8
ldr r3, =pxCurrentTCB ;R3=pxCurrentTCB 的地址 (1)
ldr r1, [r3] ;取 R3 所保存的地址处的值赋给 R1 (2)
ldr r0, [r1] ;取 R1 所保存的地址处的值赋给 R0 (3)
ldmia r0!, {r4-r11, r14} ;出栈 ,R4~R11 和 R14 (4)
msr psp, r0 ;进程栈指针 PSP 设置为任务的堆栈 (5)
isb ;指令同步屏障
mov r0, #0 ;R0=0 (6)
msr basepri, r0 ;寄存器 basepri=0,开启中断 (7)
orr r14, #0xd ; (8)
bx r14 (9)
}
(1) Obtenez l'adresse de stockage du pointeur pxCurrentTCB, pxCurrentTCB est un pointeur vers TCB_t, ce pointeur pointe toujours vers la tâche en cours d'exécution. Ici, obtenez d'abord l'adresse où le pointeur est stocké
(2) Obtenir l'adresse du premier membre via l'adresse du bloc de contrôle de tâche
(3) Obtenez la valeur du premier membre via l'adresse du premier membre, et la valeur du premier membre est la valeur en haut de la pile lorsque la fonction est créée. Cette valeur est l'adresse du sommet de la pile à ce moment.
(4), R4~R11, R14 Ces registres sont extraits de la pile. L'instruction LDMIA est utilisée ici. L'instruction LDMIA est une instruction multi-chargement/stockage, mais ici il s'agit d'une instruction d'accès multi-chargement/stockage avec écriture différée. L'utilisation est la suivante : LDMIA Rn! , {reg list} signifie à lire à partir de l'emplacement mémoire spécifié par Rn Plusieurs mots sont extraits, l'adresse est incrémentée (IA) après chaque lecture et Rn est réécrite une fois le transfert terminé. Pour STM32, l'adresse augmente de 4 octets à la fois, comme le code suivant : LDR R0, =0X800 LDMIA R0!, {R2~R4} Les deux lignes de code ci-dessus servent à affecter les données à l'adresse 0X800 au registre R2 , et affectez les données à l'adresse 0X804 au registre R3, affectez les données de l'adresse 0X8008 au registre R4, et puis, le point important vient ! A ce moment R0 est de 800A ! Grâce à cette étape, nous restaurons les valeurs des registres R4 ~ R11 à partir de la pile de tâches. Certains amis ici demanderont pourquoi les registres R0~R3, R12, PC et xPSR ne sont pas restaurés ? En effet, ces registres seront automatiquement sautés (restaurés) par le MCU lors de la sortie de l'interruption, tandis que R4 ~ R11 doivent être sautés manuellement par l'utilisateur. Nous en parlerons lorsque nous analyserons la fonction de service d'interruption PendSV. Après cette étape, examinons où pointe le pointeur supérieur de la pile ? Comme le montre la figure 8.2.4.5 :
On peut voir sur la figure 8.3.4.5 qu'après avoir restauré R4~R11 et R14, le pointeur supérieur de la pile doit pointer vers l'adresse 0X20000EB8, qui est l'adresse de stockage pour enregistrer la valeur du registre R0. Après avoir quitté la fonction de service d'interruption, le pointeur de pile de processus PSP doit restaurer d'autres valeurs de registre à partir de cette adresse.
(5) Définissez le pointeur de pile de processus PSP, PSP=R0=0X20000EB8, comme illustré à la Figure 8.2.4.6
(6) Réglez le registre R0 sur 0.
(7) Réglez le registre BASEPRI sur R0, qui est 0, et activez l'interruption !
(8) La valeur du registre R14 est ORed avec 0X0D, et le résultat est la nouvelle valeur du registre R14. Cela signifie que le CPU entre en mode thread et utilise la pile de processus après avoir quitté l'exception !
(9) Après avoir exécuté cette ligne de code, le matériel restaure automatiquement les valeurs des registres R0~R3, R12, LR, PC et xPSR, la pile utilise la pile de processus PSP, puis exécute la fonction de tâche enregistrée dans le enregistrer l'ordinateur. Jusqu'à présent, le planificateur de tâches de FreeRTOS a officiellement commencé à fonctionner !