Développement propre et facile sur Chevino

J’ai présenté en ces lieux la carte électronique Chevino que j’ai développée pour palier au coût prohibitif solutions commerciales à base de microcontrôleur 8 bits. Tout ceci n’explique pas comment programmer les dites cartes sans utiliser l’IDE libre développé pour les cartes arduino.

Il faut savoir que la spécificités de ces cartes, est leur apparentes simplicité d’emploi. En effet, on code du vrai C ou C++ comme les grands, mais les petites roulettes sont là pour empêcher les gens de tomber.

D’abord, au niveau du code, tout ce qu’on peut faire c’est modifier deux fonctions setup() et loop(), il ne faut pas oublier qu’il y a un main.c sous jaçent qui utilise ces deux fonctions mais qui fait bien d’autres choses, voici le contenu du main.cpp utilisé par arduino (on le trouve typiquement dans /usr/share/arduino/hardware/arduino/cores/arduino/main.cpp)

int main(void) 
{ 
        init(); 
        initVariant(); 
 
#if defined(USBCON) 
        USBDevice.attach(); 
#endif 
 
        setup(); 
        for (;;) { 
                loop(); 
                if (serialEventRun) serialEventRun(); 
        }     
        return 0; 
}

Ceci rend un peu plus claire la présence des fonctions setup() et loop(). Mais il n’y a pas que ça puisque c’est la bibliothèque Wiring qui est d’une grande aide pour coder. Au fond c’est celle-ci qui est un peu lourde, surtout si l’on veut juste modifier l’état d’une broche entrée/sortie, et même pour les choses plus compliquées.

Au final de quoi ai-je besoin pour développer tout seul sur chevino (ou tout autre carte arduino-compatible) ?

Juste un fichier main.c, la libc avr avec avrdude et éventuellement un Makefile pour tout automatiser. On appellera avantageusement les fonctions des libs avr et on trouvera une documentation avantageuse sur le site de la libc avr. Après on peut avoir besoin de certaines fonctions comme les fonctions delay(), delayMicroseconds(), millis() et micros() de Wiring que rien n’empêche de mettre dans une bibliothèque que l’on inclura dans main.c.

$ cat main.c
#include <stdlib.h>
#include <string.h>
#include <avr/pgmspace.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include "helper.h"

void setup(void) {
  DDRB = DDRB | 0x10; //pin5 port b en sortie;
  delay(1000);
}

void loop(void) {
  PORTB = PORTB | 0x01;
  delay(1000);
  PORTB = PORTB & ~0x01;
  delay(1000);
}

int main(void)
{
    init();
    setup();
    for (;;) {
        loop();
    }
    return 0;
}

$ cat helper.h
#ifndef HELPER_H
#define HELPER_H

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdarg.h>
#include <avr/io.h>
#include <avr/interrupt.h>


#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

#ifdef __cplusplus
  extern "C" {
#endif

void init(void);
uint32_t millis(void);
uint32_t micros(void);
void delayMicroseconds(uint16_t us);
void delay(uint32_t ms);

#ifdef __cplusplus
  } //extern "C"
#endif
#endif //HELPER_

On définit 5 fonctions, la première : init() est appelée par le main au début du programme et sert à initaliser les registres du microncontrôleur dans le bon état. Les fonctions millis et micros, delay et delayMicroseconds sont extraite de la lib wring parce qu’elle marchaient vraiment bien pour un coût mémoire mininal.

On définit un fichier helper.c à partir du fichier wiring.c de la lib arduino, mais rassurer vous, tout est disponible sur le dépôt github. Je vous renvoie aussi au dépôt pour le Makefile. Ce qu’on y remarquera c’est que la directive upload permet de programmer le microcontrôleur (et son bootloader) en utilisant le convertisseur usb/série (ici /dev/ttyUSB0) et le programme avrdude. Sinon il s’agit juste de compiler les fichiers sources que l’on donne au début du programme par

SRC = main.c helper.c

Donc armé de ces 4 fichiers (Makefile, main.c, helper.c, helper.h), je tape :

$ make && make upload

Et voilà c’est fini.

Comparons maintenant la taille de ce fichier au programme d’exemple Blink.ino qui fait la même chose :

$ cat Blink.ino

int led = 12;

void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);
}

void loop() {
  digitalWrite(led, HIGH);
  delay(1000);
  digitalWrite(led, LOW);
  delay(1000);
}

On demande la taille du fichier à avrsize :

$ avrsize -C --mcu=atmega328p Blink.cpp.elf 
// à aller chercher dans /tmp
AVR Memory Usage 
---------------- 
Device: atmega328p 
 
Program:    1118 bytes (3.4% Full) 
(.text + .data + .bootloader) 
 
Data:         11 bytes (0.5% Full) 
(.data + .bss + .noinit) 

$ avrsize -C --mcu=atmega328p main.elf
AVR Memory Usage 
---------------- 
Device: atmega328 
 
Program:     650 bytes (2.0% Full) 
(.text + .data + .bootloader) 
 
Data:          9 bytes (0.4% Full) 
(.data + .bss + .noinit)

La réponse est sans appel, 1118 octects pour le programme arduino avec toutes ses bibliothèques et juste 650 octects pour le programme en C natif. Bien sûr, tout ceci a un coût, pour l’utilisateur d’abord puisqu’il faut utiliser des appels un peu plus obscurs, mais on gagne comme ça presque la moitié de l’espace mémoire occupé.

Je n’invente rien, j’utilise juste la libc avr sans surcouche et ça fait du bien parfois de retrouver la réalité, mais dans un prochain article, on utilisera une bibliothèque SPI inspirée de celle de wiring pour faire un serveur http utilisant moins de 10 kiO de flash !

J’en remet un petit coup, tout est disponible sur le dépôt github associé.

 

Published by

paul_f4hed

Bricoleur du samedi matin, programmeur du dimanche.