Cible unique

La première partie de cet article configure l'environnement de construction et construit le logiciel pour une seule cible.

Arborescence du projet

L'arborescence du projet est la suivante :

idreammicro
|_helloworld.c
|_env_avr.py
|_env_target.py
|_SConscript
|_SConstruct

Avec :

  • helloworld.c le fichier source ;
  • env_avr.py le script de configuration de l'environnement de construction ;
  • env_target.py le script d'adaptation de l'environnement de construction à la cible ;
  • SConscript le script de construction du logiciel ;
  • SConstruct le script de construction du projet.

[Télécharger l'archive complète]

Configuration de l'environnement de construction

On crée un fichier nommé env_avr.py dont le rôle est de configurer l'environnement de construction pour avr-gcc.

# Create and initialize the environment.
env_avr = Environment()

# Set environment for AVR-GCC.
env_avr['CC'] = 'avr-gcc'
env_avr['CPPPATH'] = '/usr/lib/avr/include'
env_avr['OBJCOPY'] = 'avr-objcopy'
env_avr['SIZE'] = 'avr-size'
env_avr.Append(CCFLAGS = '-Os')

# Export environment set for AVR-GCC.
Export('env_avr')

L'export de l'environnement de construction env_avr ligne 12 permet à des scripts tiers de l'utiliser.

Cet environnement de construction générique pour microcontrôleur AVR peut être utilisé par des cibles multiples.

Adaptation à la cible

On crée un fichier nommé env_target.py dont le rôle est d'adapter l'environnement de construction à la cible : type de microcontrôleur, fréquence d'horloge, etc...

# Import environment set for AVR-GCC.
Import('env_avr')

# Create target environment by cloning AVR environment.
env_target = env_avr.Clone()

# Declare some variables about microcontroller.
# Microcontroller type.
DEVICE = 'atmega328'
# Microcontroller frequency.
CPU_FREQUENCY = '16000000UL' # Hz

# Set environment for an Atmel AVR Atmega 328 microcontroller.
env_target.Append(CCFLAGS = '-mmcu=' + DEVICE)
env_target.Append(LINKFLAGS = '-mmcu=' + DEVICE)
env_target.Append(CPPDEFINES = 'F_CPU=' + CPU_FREQUENCY)

# Export environment set for target.
Export('env_target', 'DEVICE')

Quelques nouveautés sont à souligner :

  • Ligne 2, import de l'environnement de construction pour microcontrôleur AVR.
  • Ligne 5, création d'un nouvel environnement de construction dédié à la cible à partir de l'environnement pour AVR. En effet on souhaite conserver un environnement pour AVR générique et ne pas le modifier.
  • Ligne 19, export de l'environnement env_target et de la variable DEVICE indiquant le type de microcontrôleur de la cible. Cette variable sera nécessaire pour calculer l'empreinte mémoire du logiciel.

En cas de changement de cible, seules les variables DEVICE (ligne 9) et CPU_FREQUENCY (ligne 11) nécessitent d'être modifiées.

Construction du logiciel

On crée un fichier nommé SConscript qui utilise l'environnement de construction précédemment configuré afin de compiler le projet.

# Import environment set for target.
Import('env_target', 'DEVICE')

# Set target name.
TARGET = 'helloworld'

# Set source file.
sources = 'helloworld.c'

# Build program.
env_target.Program(target = TARGET + '.elf', source = sources)

# Create hex binary file.
env_target.Command(TARGET + '.hex', TARGET + '.elf', env_target['OBJCOPY'] + ' -O ihex $SOURCE $TARGET')

# Compute memory usage.
env_target.Command(None, TARGET + '.elf', env_target['SIZE'] + ' -C --mcu=' + DEVICE + ' $SOURCE') 

Pour un autre projet, il suffit de modifier le nom de la cible à l'aide de la variable TARGET ligne 5 ainsi que les sources du projet spécifiées à l'aide de la variable sources ligne 8.

Fichier SConstruct

Enfin, le SConstruct exécute les différents scripts détaillés ci-dessus tout en assurant les exports et les imports d'environnements de construction d'un script à l'autre.

# Set environment for AVR-GCC.
SConscript(
    'env_avr.py'
)

# Import environment set for AVR-GCC.
Import('env_avr')

# Set environment for target.
SConscript(
    'env_target.py',
    exports = 'env_avr'
)

# Import environment set for target.
Import('env_target', 'DEVICE')

# Build program.
SConscript(
    'SConscript',
    exports = 'env_target'
)

L'environnement de construction est désormais configuré. Voici venu le temps de passer à la construction du logiciel.

Construction du logiciel

Dans une console, se placer dans le répertoire du projet, par exemple /home/jlesech/workspace/idreammicro, à l'aide de la commande :

~$ cd /home/jlesech/workspace/idreammicro

Lancer la construction du logiciel  à l'aide de la commande :

~workspace/idreammicro$ scons

Comme avec l'environnement de construction de l'article précédent, la construction du logiciel produit :

A l'issue de la construction du logiciel, on observe toujours l'empreinte mémoire du logiciel :

AVR Memory Usage
----------------
Device: atmega328

Program:     176 bytes (0.5% Full)
(.text + .data + .bootloader)

Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)

Cibles multiples

Afin de démontrer la pertinence d'une telle configuration d'un environnement de construction, la seconde partie de cet article construit le logiciel pour deux cibles.

Arborescence du projet

L'arborescence du projet est similaire :

idreammicro
|_helloworld.c
|_env_avr.py
|_env_arduino_uno.py
|_env_arduino_mega2560.py
|_SConscript
|_SConstruct

On remarque quelques changements :

  • renommage du fichier env_target.py en env_arduino_uno.py ;
  • création du fichier env_arduino_mega2560.py.

Ces deux scripts permettent d'adapter l'environnement de construction à deux cibles différentes, la première étant une carte Arduino Uno et la seconde une carte Arduino Mega2560.

Les fichiers env_avr.py et SConscript étant rigoureusement identiques à ceux de la première partie, ils ne seront pas passés en revue dans cette seconde partie.

[Télécharger l'archive complète]

Adaptation à la cible Arduino Uno

Cette cible étant la même que lors de la première partie, rien à signaler...

# Import environment set for AVR-GCC.
Import('env_avr')

# Create target environment by cloning AVR environment.
env_target = env_avr.Clone()

# Declare some variables about microcontroller.
# Microcontroller type.
DEVICE = 'atmega328'
# Microcontroller frequency.
CPU_FREQUENCY = '16000000UL' # Hz

# Set environment for an Atmel AVR Atmega 328 microcontroller.
env_target.Append(CCFLAGS = '-mmcu=' + DEVICE)
env_target.Append(LINKFLAGS = '-mmcu=' + DEVICE)
env_target.Append(CPPDEFINES = 'F_CPU=' + CPU_FREQUENCY)

# Export environment set for target.
Export('env_target', 'DEVICE')

Adaptation à la cible Arduino Mega2560

Cette cible est nouvelle. Par rapport à la précédente, seule la variable DEVICE (ligne 9) diffère.

# Import environment set for AVR-GCC.
Import('env_avr')

# Create target environment by cloning AVR environment.
env_target = env_avr.Clone()

# Declare some variables about microcontroller.
# Microcontroller type.
DEVICE = 'atmega2560'
# Microcontroller frequency.
CPU_FREQUENCY = '16000000UL' # Hz

# Set environment for an Atmel AVR Atmega 2560 microcontroller.
env_target.Append(CCFLAGS = '-mmcu=' + DEVICE)
env_target.Append(LINKFLAGS = '-mmcu=' + DEVICE)
env_target.Append(CPPDEFINES = 'F_CPU=' + CPU_FREQUENCY)

# Export environment set for target.
Export('env_target', 'DEVICE')

Fichier SConstruct

Le fichier SConstruct est celui qui évolue le plus et ordonne la construction du logiciel pour les deux cibles.

# Set environment for AVR-GCC.
SConscript('env_avr.py')

# Import environment set for AVR-GCC.
Import('env_avr')

# Define environments to use (one environment per target).
environments = [
    'env_arduino_uno',
    'env_arduino_mega2560'
]

# Browse environments.
for environment in environments:
    # Set environment for target.
    SConscript(
        environment + '.py',
        exports = 'env_avr'
    )
    # Import environment set for target.
    Import('env_target')
    # Build program.
    SConscript(
        'SConscript',
        variant_dir = environment,
        exports = 'env_target',
        duplicate = 0
    )

Quelques explications s'imposent :

  • Les lignes 1 à 5 créent toujours l'environnement de construction pour AVR.
  • Les lignes 7 à 11 définissent une liste d'environnements pour lesquels construire le logiciel, chaque environnement correspondant à une cible.
  • Les lignes 13 à 28 construisent le logiciel pour toutes les environnements de la liste.

Par rapport au SConstruct de la première partie, des arguments supplémentaires sont passés au SConscript :

  • Ligne 25, le mot clé variant_dir permet de spécifier le dossier de sortie de la construction du logiciel.
  • Ligne 27, on passe le paramètre duplicate avec la valeur 0 pour indiquer à SCons qu'il ne faut pas recopier les fichiers sources dans le dossier de sortie.

Construction des logiciels

La construction des logiciels se fait toujours de la même manière, en une seule fois à l'aide de la commande scons.

Les dossiers env_arduino_uno et env_arduino_mega2560 sont créés respectivement pour les cibles Arduino Uno et Arduino Mega2560. De la même manière que lors de la première partie, chacun d'eux contient les fichiers objet et binaires à l'issue de la construction du logiciel.

On observe également les empreintes mémoire pour les deux cibles.

  • Cible Arduino Mega2560 :
AVR Memory Usage
----------------
Device: atmega2560

Program:     330 bytes (0.1% Full)
(.text + .data + .bootloader)

Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)
  • Cible Arduino Uno :
AVR Memory Usage
----------------
Device: atmega328

Program:     176 bytes (0.5% Full)
(.text + .data + .bootloader)

Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)

Conclusion

Si l'environnement de construction configuré lors de l'article précédent est parfaitement fonctionnel, force est de reconnaître qu'il est monolithique.

La solution présentée dans ce second article est nettement plus professionnelle et offre désormais cette modularité, chaque opération étant effectuée dans un script dédié. Imaginez la duplication de code s'il avait été nécessaire de construire le logiciel pour deux cibles distinctes sans cette solution...