Parlons Arduino > Trucs & astuces

Techniques de mise au point

(1/3) > >>

savignyexpress:
Bonjour à tous,

En systèmes embarqués tels que l'Arduino, il n'est pas toujours facile de mettre au point les applications. Contrairement à un PC où on peut écrire à l'écran / sur un fichier ou utiliser un debugger, les possibilités de voir ce qui se passe sont beaucoup plus réduites.

Avec ce fil, j'aimerais démarrer un partage sur les techniques que vous utilisez pour la mise au point de vos réalisations. Il peut s'agir d'astuces logicielles aussi bien que matérielles et, bien entendu, une combinaison des deux.

Led clignotante indiquant la bonne santé du système
Je démarre avec la première "recette", par ailleurs décrite dans mon blog à l'adresse: https://savignyexpress.wordpress.com/2014/01/17/gare-du-reseau-dun-ami-programmation/. Il s'agit de prévoir une tâche quasi parallèle qui fait clignoter la led de la carte Arduino ou, dans le cas d'un montage maison, la led que l'on ne manquera pas d'ajouter.

Si on ne veut pas utiliser les protothreads, on peut programmer une tâche quasi-parallèle faisant clignoter une led avec la technique de gestion du temp décrite par Jean-Luc sur le site Locoduino.org http://www.locoduino.org/spip.php?article6.

Une led clignotante est un très bon indicateur de la "santé" du système. Si elle clignote, c'est signe que déjà pas mal de choses fonctionnent bien dans le programme. Si elle reste bloquée allumée ou éteinte, le programme est bloqué quelque part ou il est parti dans une boucle infinie. Pour que cette indication soit pertinente, il ne faut pas gérer cette led dans une routine d'interruption reliée à un timer car on courrait le risque que seule la routine d'interruption fonctionne alors que le reste du programme est bloqué.

Meilleures salutations.

Marc-Henri

Thierry:
C'est pas bête du tout.

Pour ma part j'utilise un #define DEBUG_MODE dans mes bibliothèques. S'il est actif (c'est livré commenté, donc inactif, dans le .h principal de la librairie) des tests sont effectués sur les arguments de fonctions (index hors limites, valeurs impossibles, choses non allouées...) et les messages d'erreur correspondants sont affichés sur la console série. Il y a aussi des messages d'information signalant l'utilisateur de ce qui se passe : utilisation d'un bouton, réception d'un code DCC, demande de mouvement d'un moteur, etc... Et j'en profite pour ajouter un copyright et le numéro de version au tout début du Setup... L'utilisation du #define permet de ne pas prendre de mémoire et de temps supplémentaire d'exécution en version 'production'.

savignyexpress:
Bonsoir à tous,

Voici une autre recette de mise au point, la simulation sur PC.

Il est plus facile de mettre au point la logique d'un programme sur un PC où on peut afficher des informations à l'écran, lire des données au clavier et utiliser un debugger. En simulant les sorties par des affichages, les entrées par des lectures au clavier, on peut, dans un 1er temps, se concentrer sur le comportement du programme sans se prendre la tête avec des aspects spécifiques au matériel. La solution est la compilation conditionnelle qui délimite ce qui est spécifique à l'AVR et à la simulation sur PC.

Par exemple, voici le début du programme de gestion de la gare de mon réseau. Toutes les définitions spécifiques à l'AVR sont délimitées par #ifdef AVR ... #endif. Ce qui est spécifique au PC, ici l'include de la librarie stdio.h, est délimité par #ifdef PC ... #endif.


--- Code: ---#ifdef AVR
#include <inttypes.h>
#include <avr/io.h>

#define F_CPU 1000000UL
#include <util/delay.h>

// Période en ms.
#define PERIODE 50

// Boutons poussoirs.
#define BOUTONS PINB
#define B_A1 _BV(PD6)
#define B_A2 _BV(PB0)
#define B_B1 _BV(PB1)
#define B_B2 _BV(PB2)
#define B_B3 _BV(PB3)
#define B_C3 _BV(PB4)
#define P_A1 (!(PIND & B_A1))
#define P_A2 (!(PINB & B_A2))
#define P_B1 (!(PINB & B_B1))
#define P_B2 (!(PINB & B_B2))
#define P_B3 (!(PINB & B_B3))
#define P_C3 (!(PINB & B_C3))

// Sorties.
#define SetTJD_D (PORTD |= _BV(PD0))
#define ClrTJD_D (PORTD &= ~_BV(PD0))
#define SetTJD_N (PORTD |= _BV(PD1))
#define ClrTJD_N (PORTD &= ~_BV(PD1))

#define SetAIG_D (PORTA |= _BV(PA1))
#define ClrAIG_D (PORTA &= ~_BV(PA1))
#define SetAIG_N (PORTA |= _BV(PA0))
#define ClrAIG_N (PORTA &= ~_BV(PA0))

#define SetSelAB (PORTD |= _BV(PD2))
#define ClrSelAB (PORTD &= ~_BV(PD2))
#define SetSel12 (PORTD |= _BV(PD3))
#define ClrSel12 (PORTD &= ~_BV(PD3))
#define SetSel3 (PORTD |= _BV(PD4))
#define ClrSel3 (PORTD &= ~_BV(PD4))
#define SetSela12 (PORTD |= _BV(PD5))
#define ClrSela12 (PORTD &= ~_BV(PD5))

// Configuration des ports
#define CONFIG_DDRA (_BV(PA0)|_BV(PA1))

#define CONFIG_DDRB ~(B_A2|B_B1|B_B2|B_B3|B_C3);
#define CONFIG_PULLUPB (B_A2|B_B1|B_B2|B_B3|B_C3);

#define CONFIG_DDRD ~(B_A1)
#define CONFIG_PULLUPD B_A1
#endif

#ifdef PC
#include <stdio.h>
#endif

--- Fin du code ---

Plus loin, dans le source, les actions spécifiques aux 2 environnements, sont aussi délimitées. Les actions spécifiques à l'AVR, ici des appels de macros définies dans l'entête (cf. l'extrait de source ci-dessus), sont délimitées par la directive #ifdef AVR ... #endif. Les printf à l'écran sont délimités par #ifdef PC ... #endif. La logique du programme: l'instruction switch, les if, etc, sont communs car non délimités par des directives de compilation conditionnelle.


--- Code: --- switch (eCommandeAig)
{
case TJD:
if (nNbPeriodes == 0)
{
#ifdef AVR
ClrTJD_D;
ClrTJD_N;
ClrAIG_D;
ClrAIG_N;
(ePosJonctions == DEVIE ? SetTJD_D : SetTJD_N);
#endif

#ifdef PC
(ePosJonctions == DEVIE ? printf("\tTJD DEV\n") : printf("\tTJD NOR\n"));
#endif
}
else if (nNbPeriodes == (NB_PERIODES / 2))
{
#ifdef AVR
ClrTJD_D;
ClrTJD_N;
(ePosAiguillages == DEVIE ? SetAIG_D : SetAIG_N);
#endif

#ifdef PC
printf("\tTJD --\n");
(ePosAiguillages == DEVIE ? printf("\tAIG DEV\n") : printf("\tAIG NOR\n"));
#endif

eCommandeAig = AIG;
} // if
nNbPeriodes++;
break;
...

--- Fin du code ---

Travaillant dans un environnement natif, j'ai un Makefile qui permet de compiler pour PC et AVR. On voit les variables AVR_GCC et PC_GCC qui invoquent les compilateurs avr-gcc et gcc selon l'environnement. En lançant make avec la cible avr ou pc les builds correspondant sont lancés. Les flags de compilation AVR_CFLAGS et PC_FLAGS incluent les définitions des directives de compilation AVR et PC.


--- Code: ---###############################################################################
# Makefile for the project Maquette
###############################################################################
#
# 2009.09.11 MHP Adaptations pour produire soit un exécutable
# pour PC (simulation via stdio) ou pour AVR.
# 2009.10.xx MHP Ajout de la directive -E noreset pour ne pas rester en reset après chargement via
# avrdude.
# 2009.11.04 MHP Ajout de la directive -ggdb pour le debug en mode PC.

## General Flags
PROJECT = Maquette
MCU = attiny2313
AVR_TARGET = Maquette.elf
PC_TARGET = Maquette.exe
AVR_GCC = avr-gcc
PC_GCC = gcc

CPP = avr-g++

## Options common to compile, link and assembly rules
COMMON = -mmcu=$(MCU)

## Compile options common for all C compilation units.
AVR_CFLAGS = $(COMMON)
AVR_CFLAGS += -Wall -gdwarf-2 -Os -std=gnu99 -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -DAVR
AVR_CFLAGS += -MD -MP -MT $(*F).o -MF dep/$(@F).d

PC_CFLAGS = -W -Wall -DPC -ggdb
## Assembly specific flags
ASMFLAGS = $(COMMON)
ASMFLAGS += $(AVR_CFLAGS)
ASMFLAGS += -x assembler-with-cpp -Wa,-gdwarf2

## Linker flags
LDFLAGS = $(COMMON)
LDFLAGS +=  -Wl,-Map=Maquette.map


## Intel Hex file production flags
HEX_FLASH_FLAGS = -R .eeprom -R .fuse -R .lock -R .signature

HEX_EEPROM_FLAGS = -j .eeprom
HEX_EEPROM_FLAGS += --set-section-flags=.eeprom="alloc,load"
HEX_EEPROM_FLAGS += --change-section-lma .eeprom=0 --no-change-warnings


## Objects that must be built in order to link
AVR_OBJECTS = Maquette.avr.o
PC_OBJECTS = Maquette.pc.o

## Objects explicitly added by the user
LINKONLYOBJECTS =

## Build
avr: $(AVR_TARGET) Maquette.hex Maquette.eep Maquette.lss size
pc: $(PC_TARGET) Maquette.exe

## Compile
Maquette.avr.o: Maquette.c
$(AVR_GCC) $(INCLUDES) $(AVR_CFLAGS) -c  $< -o Maquette.avr.o

Maquette.pc.o: Maquette.c
$(PC_GCC) $(PC_CFLAGS) -c $< -o Maquette.pc.o


##Link
$(PC_TARGET): $(PC_OBJECTS)
$(PC_GCC) $(PC_OBJECTS) -o $(PC_TARGET)

$(AVR_TARGET): $(AVR_OBJECTS)
$(AVR_GCC) $(LDFLAGS) $(AVR_OBJECTS) $(LINKONLYOBJECTS) $(LIBDIRS) $(LIBS) -o $(AVR_TARGET)

%.hex: $(AVR_TARGET)
avr-objcopy -O ihex $(HEX_FLASH_FLAGS)  $< $@

%.eep: $(AVR_TARGET)
-avr-objcopy $(HEX_EEPROM_FLAGS) -O ihex $< $@ || exit 0

%.lss: $(AVR_TARGET)
avr-objdump -h -S $< > $@

size: ${AVR_TARGET}
@echo
@avr-size ${AVR_TARGET}

load: ${AVR_TARGET}
avrdude -v -p t2313 -c stk200 -e -U flash:w:Maquette.hex -E noreset

## Clean target
.PHONY: clean
clean:
-rm -rf $(AVR_OBJECTS) $(PC_OBJECTS) Maquette.elf Maquette.exe dep/* Maquette.hex Maquette.eep Maquette.lss Maquette.map Maquette.exe


## Other dependencies
-include $(shell mkdir dep 2>/dev/null) $(wildcard dep/*)

--- Fin du code ---

Je ne connais pas suffisamment l'Arduino pour savoir s'il serait possible de simuler un sketch Arduino de la même manière en le compilant avec avr-gcc, mais il doit certainement y avoir un moyen de le faire.

Meilleures salutations.

Marc-Henri

Thierry:
A vrai dire, j'ai aussi un truc similaire. Je suis un utilisateur historique de Visual Studio, c'est pourquoi dans mes zip de bibliothèque il y a toujours un répertoire VStudio avec un fichier 'solution' qui permet de déboguer un programme ino. Pour arriver au bout de cette possibilité, j'ai dû créer un arduino.h dédié.


--- Code: ---//-------------------------------------------------------------------
#ifndef __arduino_H__
#define __arduino_H__
//-------------------------------------------------------------------

#define int8_t signed char
#define uint8_t unsigned char
#define uint16_t unsigned int
#define boolean bool
#define byte unsigned char
#define strcpy_P(d, s) strcpy_s(d, this->sizex + 1, s)

#define HIGH 1
#define LOW 0
#define INPUT 1
#define INPUT_PULLUP 3
#define OUTPUT 2
#define DEC 1
#define BIN 2
#define HEX 3
#define NULL 0
#define PROGMEM
#define PGM_P   const char *

#define NUM_DIGITAL_PINS            70
#define NUM_ANALOG_INPUTS           16

#define CHANGE 1
#define FALLING 2
#define RISING 3

#define F(string) string
#define __FlashStringHelper char
#define prog_char char
byte pgm_read_byte(const char *);
int pgm_read_word(const char *);

void pinMode(int, int);
void digitalWrite(int, int);
int digitalRead(int);
void analogWrite(int, int);
int analogRead(int);
unsigned long millis();
unsigned long micros();
void delay(int);

int map(int, int, int, int, int);

void noInterrupts();
void interrupts();
void attachInterrupt(uint8_t, void(*)(void), int mode);
void detachInterrupt(uint8_t);

int freeMemory();

double power(double val, int exp);

//-------------------------------------------------------------------
#endif
//-------------------------------------------------------------------
--- Fin du code ---

Il me permet dans le pire des cas de ne rien faire, ou au mieux de faire semblant... Il y a aussi d'autres include comme serial.h ou LiquidCrystalFast.h ...

Pour LcdUi, et DcDcc qui l'utilise, j'ai créé un 'émulateur' d'Arduino, qui va afficher un écran Lcd texte jusqu'à quatre lignes et une console série (les Serial.print() vont dedans...):


C'est tellement plus pratique de tester l'interface utilisateur sur un vrai écran, avec des touches clavier pour faire semblant de manipuler l'encodeur ou les boutons...

Il  y a aussi un define VISUALSTUDIO pour conditionner certaines lignes de code...

DDEFF:
Salut à tous les deux,

Si je vous lis bien, vous êtes en train de me dire qu'on peut développer, simuler des appuis sur des touches et des allumages LED sans passer par l'IDE Arduino ?
Sans flasher la mémoire à tout va à chaque changement de virgule, finalement sans Arduino ??
Et sans se farcir le port série à tout bout de champ ?

Pour l'instant, je développe avec le seul IDE fourni par Arduino et je me tape des Serial.print(), etc ??
Je vous jure que, par moments, quand le programme (3124 lignes !) ne fait pas ce qu'on veut, il faut être vaillant !!  :P :P

S'il existe plus simple, je suis preneur.

Par ailleurs, je n'ai absolument rien compris à vos extraits de programmes.
ça va où ? ça se tape où ? dans quel logiciel ?

Pouvez-vous commencer par du simple :
Je voudrais un programme qui allume une LED quand on appuie sur un bouton.
Sans passer par l'IDE, Serial.print() et le fer à souder.

D'avance, merci

Navigation

[0] Index des messages

[#] Page suivante

Utiliser la version classique