Lorsque vous recevez un nouveau circuit intégré (CI), la première étape consiste à vérifier si le fabricant propose une carte de développement. Si c'est le cas, utilisez celle-ci. Sinon, vous devrez consulter la documentation technique du CI pour concevoir votre propre carte de circuit imprimé (PCB).
Analyse de la documentation technique
La consultation attentive de la fiche technique du CI est cruciale. Portez une attention particulière aux points suivants :
- Séquence d'alimentation : Respectez l'ordre et les temporisations lors de la mise sous tension.
- Résistance de rappel sur UART1 RXD : Assurez-vous que la ligne de réception du port série 1 est correctement configurée avec une résistance de rappel.
- Mode de démarrage : Comprenez les différentes options de démarrage du CI et choisissez celle qui convient à votre application.
Premier programme : Clignotement d'une LED
Pour vérifier le bon fonctionnement du CI, commencez par un programmme simple comme le clignotement d'une LED. Cela implique les étapes suivantes, basées sur la fiche technique :
- Identification des registres : Localisez les adresses mémoire et les fonctions des broches GPIO dans la documentation.
- Configuration de la broche : Activez l'horloge du périphérique, configurez la fonction multiplexée de la broche (par exemple, GPIO), définissez le mode (entrée/sortie), les caractéristiques électriques (pull-up/pull-down, vitesse) et le niveau de sortie (haut/bas).
Programmation en assembleur
Pour une compréhension approfondie du fonctionnement matériel, vous pouvez écrire ce programme en assembleur. Il est important de connaître les différences entre les syntaxes GNU et Intel. Voici un exemple utilisant la syntaxe GNU pour manipuler directement les registres :
# Charger l'adresse du registre de configuration de la broche dans R0
ldr r0, =0X020C4068
# Charger la valeur de configuration (par exemple, activer la sortie) dans R1
ldr r1, =0XFFFFFFFF
# Écrire la valeur dans le registre
str r1, [r0]
L'utilisation d'un compilateur croisé comme arm-linux-gnueabihf-gcc est nécessaire. Vous pourriez rencontrer des problèmes de compatibilité de version ; envisagez d'utiliser une version antérieure si nécessaire.
Processus de compilation et de liaison (Assembleur)
- Compilation : Compilez le fichier asembleur en fichier objet sans lier. ```
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
- Liaison : Liez le fichier objet pour créer un fichier exécutable. La
0X87800000représente l'adresse de base où le code sera chargé en mémoire (souvent en DDR). ``` arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf - Conversion en binaire : Convertissez le fichier ELF en un format binaire brut pour le chargement. ```
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
- Désassemblage : Utilisez
objdumppour examiner le code assembleur généré et faciliter le débogage. ``` arm-linux-gnueabihf-objdump -D led.elf > led.dis
Utilisation d'un Makfeile
Pour automatiser ce processus, vous pouvez utiliser un Makefile. Assurez-vous d'utiliser des tabulations et non des espaces pour l'indentation :
led.bin:led.s
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
clean:
rm -rf *.o led.bin led.elf led.dis
Notez que le choix de l'adresse de liaison (0X87800000) est critique car elle correspond à l'emplacement en mémoire DDR.
Le fichier led.bin peut ensuite être gravé sur une carte SD ou une autre méthode de stockage, en fonction de la configuration de démarrage du CI.
Programmation en C
Bien que l'assembleur soit utile pour comprendre les bases, la majorité du développement se fait en C. Un environnement C nécessite une initialisation en assembleur, notamment la mise en place du pointeur de pile (SP).
Initialisation en assembleur pour C
.global _start
/*
* _start: Point d'entrée du programme. Initialise l'environnement
* d'exécution C.
*/
_start:
/* Entrer en mode SVC */
mrs r0, cpsr /* Lire le registre d'état du processeur */
bic r0, r0, #0x1f /* Masquer les bits de mode (M0-M4) */
orr r0, r0, #0x13 /* Définir le mode SVC (0x13) */
msr cpsr, r0 /* Écrire le nouveau mode dans le registre CPSR */
ldr sp, =0X80200000 /* Initialiser le pointeur de pile */
b main /* Sauter vers la fonction principale C */
Définition des registres en C
Pour simplifier l'accès aux registres en C, utilisez des macros :
#define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068)
Implémentation dans le fichier C principal
Le code C utilise ensuite ces définitions pour contrôler le matériel :
SW_MUX_GPIO1_IO03 = 0x5;
Makefile et script de liaison améliorés (C)
Le Makefile et le script de liaison (.lds) sont mis à jour pour gérer les sources C et assembleur :
Makefile :
objs := start.o main.o
ledc.bin:$(objs)
arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
%.o:%.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ <<
%.o:%.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ <<
%.o:%.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ <<
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis
Script de liaison (imx6ul.lds) :
SECTIONS{
. = 0X87800000;
.text :
{
start.o
main.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : { *(.data) }
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .;
}
SDK et gestion du projet
Si le fabricant fournit un kit de développement logiciel (SDK), vous pouvez l'adapter à vos besoins en ajoutant ou supprimant des fichiers. Sinon, vous devrez développer votre propre SDK.
Pour les projets de grande envergure, une bonne organisation du code est essentielle. La gestion des sources par fonctionnalité, la clarté des modules et un code lisible contribuent à la maintenabilité. Cela s'inscrit dans le concept de BSP (Board Support Package).
Modèle avancé de compilation et de liaison
Voici un modèle plus sophistiqué pour la compilation et la liaison, adapté aux projets plus complexes :
Compilation
CROSS_COMPILE ?= arm-linux-gnueabihf- # Préfixe du compilateur croisé
TARGET ?= bsp # Nom de la cible binaire
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
# Répertoires des fichiers d'en-tête
INCDIRS := imx6ul \
bsp/clk \
bsp/led \
bsp/delay
# Répertoires des fichiers source (.c et .s)
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay
# Options de compilation et chemins d'inclusion
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
# Recherche des fichiers d'assemblage et C
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
# Extraction des noms de fichiers sans le chemin
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
# Génération des objets à partir des sources
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)
# Chemin de recherche pour les sources
VPATH := $(SRCDIRS)
.PHONY: clean
# Règle principale de création de la cible binaire
$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
# Règle de compilation pour les fichiers d'assemblage (.S)
$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ <<
# Règle de compilation pour les fichiers C (.c)
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ <<
# Règle de nettoyage
clean:
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
Liaison (script imx6ul.lds)
SECTIONS{
. = 0X87800000; /* Adresse de base en mémoire */
.text :
{
obj/start.o /* Fichier d'entrée */
*(.text) /* Code des autres sections */
}
.rodata ALIGN(4) : {*(.rodata*)} /* Données en lecture seule */
.data ALIGN(4) : { *(.data) } /* Données initialisées */
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON) } /* Données non initialisées */
__bss_end = .;
}