Que signifie ((Port *) 0x41004400UL) ici?

Je travaille sur une carte en développement dotée d’un micro-contrôleur basé sur ARM 32 bits (notamment la carte Atmel SAM D21J18A). Je suis encore en phase d’apprentissage et il me rest beaucoup à faire, mais je suis vraiment dans les systèmes embarqués.

J’ai de l’expérience en C. Cependant, ce n’est évidemment pas suffisant. Je regardais les codes d’un exemple de projet réalisé par Atmel et je n’en ai pas vraiment compris certaines parties. Voici l’un d’entre eux:

#define PORT ((Port *)0x41004400UL) /**< \brief (PORT) APB Base Address */ 

Le port est défini comme:

  typedef struct { PortGroup Group[2]; /**< \brief Offset: 0x00 PortGroup groups [GROUPS] */ } Port; 

et PortGroup est défini comme:

 typedef struct { __IO PORT_DIR_Type DIR; /**< \brief Offset: 0x00 (R/W 32) Data Direction */ __IO PORT_DIRCLR_Type DIRCLR; /**< \brief Offset: 0x04 (R/W 32) Data Direction Clear */ __IO PORT_DIRSET_Type DIRSET; /**< \brief Offset: 0x08 (R/W 32) Data Direction Set */ __IO PORT_DIRTGL_Type DIRTGL; /**< \brief Offset: 0x0C (R/W 32) Data Direction Toggle */ __IO PORT_OUT_Type OUT; /**< \brief Offset: 0x10 (R/W 32) Data Output Value */ __IO PORT_OUTCLR_Type OUTCLR; /**< \brief Offset: 0x14 (R/W 32) Data Output Value Clear */ __IO PORT_OUTSET_Type OUTSET; /**< \brief Offset: 0x18 (R/W 32) Data Output Value Set */ __IO PORT_OUTTGL_Type OUTTGL; /**< \brief Offset: 0x1C (R/W 32) Data Output Value Toggle */ __I PORT_IN_Type IN; /**< \brief Offset: 0x20 (R/ 32) Data Input Value */ __IO PORT_CTRL_Type CTRL; /**< \brief Offset: 0x24 (R/W 32) Control */ __O PORT_WRCONFIG_Type WRCONFIG; /**< \brief Offset: 0x28 ( /W 32) Write Configuration */ RoReg8 Reserved1[0x4]; __IO PORT_PMUX_Type PMUX[16]; /**< \brief Offset: 0x30 (R/W 8) Peripheral Multiplexing n */ __IO PORT_PINCFG_Type PINCFG[32]; /**< \brief Offset: 0x40 (R/W 8) Pin Configuration n */ RoReg8 Reserved2[0x20]; } PortGroup; 

Donc, ici, nous examinons l’adresse 0x41004400UL, obtenez les données, puis que se passe-t-il?

Je cherchai cela mais ne trouvai rien d’utile. Si vous avez des suggestions (tutoriels, livres, etc.), merci de me le faire savoir.

En règle générale, vous pouvez accéder à un registre de matériel en C de la manière suivante:

 #define PORT (*(volatile uint8_t*)0x1234) 
  • 0x1234 est l’adresse du registre
  • uint8_t est le type du registre, dans ce cas 1 octet grand.
  • volatile est requirejs pour que le compilateur sache qu’il ne peut pas optimiser une telle variable, mais que chaque lecture ou écriture dans la variable indiquée dans le code doit en réalité être effectuée.
  • (volatile uint8_t*) le littéral entier en une adresse du type souhaité.
  • Le plus à gauche * prend alors le contenu de cette adresse, de sorte que la macro puisse être utilisée comme si la variable PORT était une variable normale.

Notez que cela n’alloue rien! Cela suppose simplement qu’un registre matériel est présent à l’adresse donnée, auquel on peut accéder par le type spécifié ( uint8_t ).

En utilisant la même méthode, vous pouvez également avoir d’autres types de données C pour correspondre directement aux registres du matériel. Par exemple, en utilisant une structure pratique, vous pouvez mapper la totalité de la zone de registre d’un périphérique matériel particulier. Un tel code est cependant un peu dangereux et discutable, car il doit prendre en compte des éléments comme un alignement / struct padding et un aliasing.


En ce qui concerne le code spécifique de votre exemple, il s’agit d’une terrible carte de registre pour un périphérique matériel particulier (qui ressemble à un port d’E / S standard ordinaire) sur un certain microcontrôleur. Une telle bête est généralement fournie avec chaque compilateur prenant en charge le MCU.

Malheureusement, ces cartes de registres sont toujours écrites de manière affreuse et totalement non portable. Par exemple, deux caractères de soulignement __ est un identifiant interdit en C. Ni le compilateur ni le programmeur ne sont autorisés à déclarer de tels identificateurs (7.1.3).

Ce qui est vraiment étrange, c’est qu’ils ont omis le mot clé volatile . Cela signifie que vous avez l’un de ces scénarios ici:

  • Le mot clé volatile est masqué sous la définition du Port . Très probablement c’est le cas, ou
  • La carte du registre est pleine d’insectes mortels, ou
  • Le compilateur est une telle merde qu’il n’optimise pas les variables. Ce qui ferait disparaître les problèmes avec volatile .

Je voudrais enquêter plus loin.

En ce qui concerne le remplissage et l’aliasing de la structure, le fournisseur du compilateur a probablement implicitement supposé que seul son compilateur devait être utilisé. Ils n’ont aucun intérêt à vous fournir une carte de registre portable, de sorte que vous puissiez remplacer le compilateur du concurrent par le même MCU.

Rien ne se passe car vous ne présentez que quelques déclarations. Je ne suis pas tout à fait sûr de ce que la question est réellement, mais pour expliquer brièvement ce code:

  • 0x41004400UL est évidemment une adresse dans l’espace d’E / S (mémoire non standard) à laquelle commence un port (un ensemble de registres d’E / S)

  • Ce port est constitué de deux groupes avec un agencement similaire de registres uniques

  • struct PortGroup modélise ces registres exactement dans la présentation présente sur le matériel

Pour connaître la signification des registres, consultez la documentation sur le matériel.