Technique/Logiciel/API/Use/Core/Instruction Set

De Wiki Techno-Innov
< Technique‎ | Logiciel‎ | API‎ | Use‎ | Core
Aller à la navigation Aller à la recherche
La version imprimable n’est plus prise en charge et peut comporter des erreurs de génération. Veuillez mettre à jour les signets de votre navigateur et utiliser à la place la fonction d’impression par défaut de celui-ci.

Jeu d'instructions

L'ensemble des micro-contrôleurs supportés par cette API utilisent un cœur ARM Cortex-M* (M0, M3, M4, ...). La majorité des informations relatives au jeu d'instruction concerné sont décrites sur le site infocenter.arm.com dans la section "Cortex-M series processors".

La majorité des instructions sont utilisées de façon transparentes par le compilateur lors de la compilation du code C.
Ces instructions et leur utilisation ne seront pas détaillées ici.

Instructions spéciales

#include "core/lpc_core.h"

Cependant, certaines instructions spécifiques ne sont pas présentes dans le langage C, et il est nécessaire d'utiliser quelques lignes d'assembleur pour y accéder.

Pour simplifier l'écriture du code C, ces lignes d'assembleur ont été regroupées dans le fichier d'entêtes "include/core/lpc_core.h" et placées à l'intérieur de fonctions ou de macro écrites en C.

Instructions "simples"

  • No operation : nop
#define nop() __asm volatile ("nop" : : : "memory")

Cette instruction permet de "ne rien faire" pendant un cycle horloge. La fonction nop() permet de faire appel à cette instruction.

  • Send event : sev
#define sev() __asm volatile ("sev" : : : "memory")

Cette instruction permet de "générer" un événement de façon logicielle. La fonction sev() permet de faire appel à cette instruction.

  • Wait For Event : wfe
#define wfe() __asm volatile ("wfe" : : : "memory")

Cette instruction permet de mettre en pause le micro-contrôleur en attendant le prochain événement. La fonction wfe() permet de faire appel à cette instruction.

  • Wait For Interrupt : wfi
#define wfi() __asm volatile ("wfi" : : : "memory")

Cette instruction permet de mettre en pause le micro-contrôleur en attendant la prochaine interruption. La fonction wfi() permet de faire appel à cette instruction.
Cette instruction est aussi utilisée pour la mise en veille du micro-contrôleur une fois la configuration correspondante effectuée.

  • Instruction Synchronization Barrier : isb
#define isb() __asm volatile ("isb" : : : "memory")

Cette instruction est une opération de synchronisation qui temporise l'exécution de nouvelles instructions jusqu'à ce que les instructions présentes dans le "pipeline" aient fini de s'exécuter.

  • Data Synchronization Barrier
#define dsb() __asm volatile ("dsb" : : : "memory")

Cette instruction est une opération de synchronisation qui temporise l'exécution de nouvelles instructions jusqu'à ce que l'ensemble des accès en mémoire aient été complétés.

  • Data Memory Barrier
#define dmb() __asm volatile ("dmb" : : : "memory")

Cette instruction est une opération de synchronisation qui temporise l'exécution de nouvelles instructions pour que l'ordre apparent des accès en mémoire soit respecté, même si les accès ne sont pas forcément complétés (seules les écritures relatives à des adresses accédées par les instructions en attente devront être complétées).

  • Enable IRQ Interrupts
static inline void lpc_enable_irq(void)

Cette instruction autorise les interruptions qui ont été activées. Les interruptions externes sont autorisées par défaut lors du démarrage du micro-contrôleur, mais pas activées.

  • Disable IRQ Interrupts
static inline void lpc_disable_irq(void)

Cette instruction interdit toutes les interruptions.
Voir la section sur les Interruptions pour plus d'informations sur l'utilisation des deux dernières instructions.

Accès aux registres spéciaux

  • Application Program Status Register (APSR)
static inline uint32_t get_APSR(void)
#define APSR_SATURATION  (get_APSR() & (0x1 << 27))  /* bit 27  Saturation condition flag */
#define APSR_OVERFLOW    (get_APSR() & (0x1 << 28))  /* bit 28  Overflow condition code flag */
#define APSR_CARRY       (get_APSR() & (0x1 << 29))  /* bit 29  Carry condition code flag */
#define APSR_ZERO        (get_APSR() & (0x1 << 30))  /* bit 30  Zero condition code flag */
#define APSR_NEGATIVE    (get_APSR() & (0x1 << 31))  /* bit 31  Negative condition code flag */
  • Interrupt Program Status Register (IPSR)
static inline uint32_t get_IPSR(void)
#define IPSR      (get_IPSR() & 0x1FF)  /* bit:  0..8  Exception number */
#define IPSR_IRQ0 16
#define IRQ_NUM   (IPSR - IPSR_IRQ0)
  • Control Register
static inline uint32_t get_CONTROL(void)
static inline void set_CONTROL(uint32_t control)
  • Process Stack Pointer
static inline uint32_t get_process_stack_pointer(void)
static inline void set_process_stack_pointer(uint32_t top_of_stack)
  • Main Stack Pointer
static inline uint32_t get_main_stack_pointer(void)
static inline void set_main_stack_pointer(uint32_t top_of_stack)
  • Priority Mask
static inline uint32_t get_priority_mask(void)
static inline void set_priority_mask(uint32_t mask)
  • Base Priority
static inline uint32_t get_base_priority(void)
static inline void set_base_priority(uint32_t prio)
  • Fault Mask
static inline uint32_t get_fault_mask(void)
static inline void set_fault_mask(uint32_t mask)

Opérations sur les bits et octets

  • Double changement d'endianness 16-bit sur mot 32-bit
static inline uint32_t double_byte_swap_16(volatile uint32_t value)

Cette fonction permet de changer simultanément l'endianness de deux mots de 16 bits, sans changer l'ordre des mots.

  • Changement d'endianness 16-bit
static inline uint32_t byte_swap_16(volatile uint16_t value)

Cette fonction permet de changer l'endianness d'un mot de 16 bits. Le résultat est sur les 16 bits de poids faible de l'entier 32 bits retourné.

  • Changement d'endianness 32-bit
static inline uint32_t byte_swap_32(volatile uint32_t value)

Cette fonction permet de changer l'endianness d'un mot de 32 bits.

Voir aussi les fonctions ntohs(), ntohl(), htons() et htonl() pour la gestion de l'endianness et les fonctions clz(), ctz() et bits_set()("include/lib/utils.h") pour les opérations sur les bits.

Accès atomiques et Synchronisation

Certains cœurs de micro-contrôleurs Cortex-M* ne disposent pas d'instruction de synchronisation "atomiques" garantissant qu'il n'est pas possible que deux accès à la même variable n'aient pas été réalisés depuis deux points d'exécution différents (par exemple le programme principal et une routine de gestion d'une interruption) de façon concurrente (lecture de la variable dans le programme principal, puis interruption, lecture et modification dans la routine de gestion de l'interruption, et reprise du programme principal, qui écrase la valeur modifiée par la routine de gesion de l'interruption).

Les fonctions suivantes permettent de palier à ce manque.
Lorsque ces instructions existent pour le micro-contrôleur concerné, l'implémentation de ces fonctions utilise les instructions spécialisées fournies par le micro-contrôleur.

Accès atomiques - Interruptions gardés actives

static inline uint32_t sync_lock_test_and_set(volatile uint32_t *addr, uint32_t value)
static inline void sync_lock_release(volatile uint32_t *addr)

Ces deux fonctions permettent d'implémenter un mécanisme de mutex ou de rendez-vous, tout en laissant les interruptions actives entre la prise et la libération du verrou.
La fonction sync_lock_test_and_set() enregistre la valeur "value" à l'adresse "addr" et renvoie l'ancienne valeur. Les interruptions sont désactivées pendant l'exécution de la fonction mais rétablies avant le retour de la fonction.
La fonction sync_lock_release() met à zéro la variable dont l'adresse est passée en paramètre, sans désactiver les interruptions.

Accès atomiques - Interruptions désactivées

static inline uint32_t irq_sync_lock_test_and_set(volatile uint32_t *addr, uint32_t value)
static inline void irq_sync_lock_release(volatile uint32_t *addr)

Ces deux fonctions permettent d'implémenter un mécanisme de mutex en désactivant les interruptions.
La fonction irq_sync_lock_test_and_set() enregistre la valeur "value" à l'adresse "addr" et renvoie l'ancienne valeur. Lorsque l'ancienne valeur présente à l'adresse "addr" est non nulle, les interruptions sont réactivées (le mutex n'est pas pris). Sinon, le mutex est "pris" et les interruptions restent désactivées.
La fonction irq_sync_lock_release() met à zéro la variable dont l'adresse est passée en paramètre et réactive les interruptions.
Attention : Ces deux fonctions ne peuvent pas être utilisées pour implémenter un mécanisme de rendez-vous car le verrou ne peut être libéré depuis une routine de gestion d'interruption.
Attention : De nombreux modules de communication fonctionnent sur interruption, et ne seront plus fonctionnels pendant la durée du maintien du verrou.
Il est aussi préférable de limiter la quantité de code à exécuter entre la prise du mutex et sa libération.

Spécificités par micro-contrôleur

LPC82x

LPC122x