Programmation embarquée
Sommaire
résumé
le TP de la semaine de la fabacademy consiste à se familiariser avec la programmation de microcontroleurs et processeurs.
je vais commencer par programmer les deux circuits que j'ai réalisé : le FabISP et le helloWorld
premiers pas avec le tiny
La première étape est de finir mon travail en retard sur le fabISP : comme j'attendais l'arrivée des composants, je n'ai pas eu le temps de programmer le circuit à temps. De plus je ne comprenais pas bien la logique d'alimentation du circuit : je croyais que le programmateur alimentait la puce cible alors qu'il fallait qu'elle le soit séparément...
cette fois ça marche en suivant le protocole du tutoriel
make -f hello.ftdi.44.echo.c.make
avr-objcopy -O ihex hello.ftdi.44.echo.out hello.ftdi.44.echo.c.hex;\
avr-size --mcu=attiny44 --format=avr hello.ftdi.44.echo.out
AVR Memory Usage
Device: attiny44
Program: 776 bytes (18.9% Full) (.text + .data + .bootloader)
Data: 64 bytes (25.0% Full) (.data + .bss + .noinit)
cedric@cedric-Inspiron-5520 ~/fabacademy/electronicDesign/programming $ make -f hello.ftdi.44.echo.c.make program-avrisp2-fuses
avr-objcopy -O ihex hello.ftdi.44.echo.out hello.ftdi.44.echo.c.hex;\
avr-size --mcu=attiny44 --format=avr hello.ftdi.44.echo.out
AVR Memory Usage
Device: attiny44
Program: 776 bytes (18.9% Full) (.text + .data + .bootloader)
Data: 64 bytes (25.0% Full) (.data + .bss + .noinit)
avrdude -p t44 -P usb -c avrisp2 -U lfuse:w:0x5E:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e9207 avrdude: reading input file "0x5E" avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.01s
avrdude: 1 bytes of lfuse written avrdude: verifying lfuse memory against 0x5E: avrdude: load data lfuse data from input file 0x5E: avrdude: input file 0x5E contains 1 bytes avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ... avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
$ make -f hello.ftdi.44.echo.c.make program-avrisp2 avr-objcopy -O ihex hello.ftdi.44.echo.out hello.ftdi.44.echo.c.hex;\ avr-size --mcu=attiny44 --format=avr hello.ftdi.44.echo.out AVR Memory Usage
Device: attiny44
Program: 776 bytes (18.9% Full) (.text + .data + .bootloader)
Data: 64 bytes (25.0% Full) (.data + .bss + .noinit)
avrdude -p t44 -P usb -c avrisp2 -U flash:w:hello.ftdi.44.echo.c.hex
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e9207 avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip avrdude: reading input file "hello.ftdi.44.echo.c.hex" avrdude: input file hello.ftdi.44.echo.c.hex auto detected as Intel Hex avrdude: writing flash (776 bytes):
Writing | ################################################## | 100% 0.27s
avrdude: 776 bytes of flash written avrdude: verifying flash memory against hello.ftdi.44.echo.c.hex: avrdude: load data flash data from input file hello.ftdi.44.echo.c.hex: avrdude: input file hello.ftdi.44.echo.c.hex auto detected as Intel Hex avrdude: input file hello.ftdi.44.echo.c.hex contains 776 bytes avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.23s
avrdude: verifying ... avrdude: 776 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
Par contre, le programme ne répond pas par le port série.
en explorant les différents codes, j'ai trouvé dans http://academy.cba.mit.edu/classes/embedded_programming/hello.ftdi.44.echo.interrupt.c
un commentaire de neil : "set lfuse to 0x7E for 20 MHz xtal"
or dans la makefile que j'ai utilisé, le fuse est seté à 0x5E
j'essaye de comprendre le datasheet pour résoudre ce probleme...
Dans le datasheet, il est question de la calibration de l'oscilateur: "...OSCCAL = 0x7F gives a higher frequency than OSCCAL = 0x80..."
je n'ai pas réussi à décoder exactement le code pour comprendre à quoi correspond ce setting
mais je vois qu'il est question de selectionner l'external clock et le multiple de l'horloge.
En tous cas, le circuit fonctionne : je peux commencer à essayer de la programmer
il suffit de modifier le makefile en remplaçant :
program-avrisp2-fuses: $(PROJECT).hex
avrdude -p t44 -P usb -c avrisp2 -U lfuse:w:0x5E:m
par
program-avrisp2-fuses: $(PROJECT).hex
avrdude -p t44 -P usb -c avrisp2 -U lfuse:w:0x7F:m
en assembleur
j'ai choisi la facilité : au lieu d'écrire en hexadécimal, je vais utiliser l'assembleur...
En partant de l'exemple de Neil, je vais essayer de faire un clignoter ma led.
je commence à lire le programme et je trouve :
ldi temp, (1 << CLKPCE)
ldi temp1, (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0)
en lisant le datasheet, je comprends que :
'ldi = load immediate
(p30)
CLKPCE=1 : Clock Prescaler Change Enable
(0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0) : Clock prescaler select : Clock division factor = 1
donc logiquement, le systeme doit fonctionner à la fréquence du quarts (qui est l'orloge externe : 20mhz)
compilation
il faut avoir téléchargé le compilateur gavrasm
Une fois décompressé dans le dossier (ou installé) on peut compiler un programme avec la commande :
./gavrasm <fichier_source_asm>
par exemple dans mon cas :
./gavrasm hello.ftdi.44.blink.asm
j'arrive à compiler mon code, mais la led ne clignote pas...
quelques pistes sur l'assembleur
http://www.unixgarden.com/index.php/gnu-linux-magazine/coder-pour-atmel-attiny
des histoires d'horloge
voici ma boucle de délai :
delai:
;ldi temp, 255; max ;clr temp delay_loop: dec temp clr temp1 delay_loop2: dec temp1 clr temp2
delay_loop3:
dec temp2 brne delay_loop3 brne delay_loop2 brne delay_loop ret
malgré une boucle de délai conséquente (255^3), je ne voyais pas la led clignoter.
pourtant 255*255*255=16581375 cycles
et l'horloge tourne à 20Mhz, soit un cycle toutes les 1/20000000=0,00000005 sec
donc logiquement ma boucle devrai faire :
16581375*0,00000005=0,8s
or je ne vois pas le clignotement.
Par contre en changeant le diviseur de l'horloge à 256
ldi temp, (1 << CLKPCE)
ldi temp1, (1 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);/256
out CLKPR, temp
out CLKPR, temp1
le clignotement est apparent et dure à peu près la durée prévue. J'ai donc fait des erreurs de calcul?
désynchronisation
J'ai enfin réussi à faire clignoter la led, mais depuis, j'ai perdu mon tiny !
si j'essaye de le reprogrammer, j'ai :
make -f hello.ftdi.44.blink.asm.make program-avrisp2
avrdude -p t44 -P usb -c avrisp2 -U flash:w:hello.ftdi.44.blink.hex
avrdude: stk500v2_command(): command failed avrdude: stk500v2_program_enable(): bad AVRISPmkII connection status: Unknown status 0x00 avrdude: initialization failed, rc=-1
Double check connections and try again, or use -F to override this check.
avrdude done. Thank you.
make: *** [program-avrisp2] Erreur 1
Sur les conseils de Neil, j'essaye d'utiliser l'option "-i" de avrdude , qui spécifie la délai en microsecondes entre chaque changement de bit.
-i delay For bitbang-type programmers, delay for approximately delay microseconds between each bit state change. If the host system is very fast, or the target runs off a slow clock (like a 32 kHz crystal, or the 128 kHz internal RC oscilla‐ tor), this can become necessary to satisfy the requirement that the ISP clock frequency must not be higher than 1/4 of the CPU clock frequency.
Si je continue mes mauvais calculs :
l'horloge tourne à 20mhz mais est divisée par 256, donc elle fait 20000000/256=78125 cycles/sec
donc un cycle dure = 1/78125 = 0,0000128 sec = 12,8 microsecondes
Finalement l'option "-i" ne donne rien, mais j'ai fini par trouver une solution ici : utiliser l'option -B 1024 pour effacer le programme (c'est l'option bitclock period)
ouf !
patience et longueur de temps
J'ai remis l'horloge à son timing d'origine.
Mais diable ! comme il est difficile de faire patienter cette horloge qui tourne à l'allure vertigineuse de 20 millions de cycles par secondes !!!
j'ai finalement trouvé une solution plus ou moins élégante, en utilisant deux registres couplés (sous forme de words) qui au lieu de plafonner à 255, atteignent 65535 : ça fait déjà plus de cycles.
delai:
mov r16,temp
outer_loop:; duration ~
ldi r26, 0; set r26 to zero
ldi r27, 0; set r27 to zero
delay_loop:;~262143*65535+3= 17179541508 cycles
adiw r26, 1; add 1 to r26 and r27
ldi r28, 0; set r28 to zero
ldi r29, 0; set r29 to zero
delay_loop1:;~4*65535+3= 262143 cycles
adiw r28, 1; add 1 to r28 and r29; 2 cycles
brne delay_loop1; if no overflow loop; 1 or 2 cycles
brne delay_loop; if no overflow loop
dec r16; dcrement R16 brne outer_loop; if no overflow loop ret
Expérimentalement, j'ai trouvé que cette boucle tourne environ en une centiseconde.
C'est à dire que si on set le registre "temp" à 100, le délai durera une seconde :
ldi temp,100;delay 1 sec
rcall delai; delay
et ça marche !!!!!
Fichier:Hello.ftdi.44.blink.asm.zip
en C
Pour faire un peu plus simple, je vais essayer de programmer le bouton en C.
les pins :
- PA7 : le bouton (à mettre en pullup)
- PB2 : la led
quelques ressources utiles :
- pour comprendre les entrées sorties http://avrbasiccode.wikispaces.com/
délai variable
Mon programme permettra de régler le rythme de clignotement de ma led à l'aide du bouton.
Pour cela, j'ai besoin de faire un délai variable.
Lorsqu'on appuie sur le bouton, on incrémente un compteur qui servira pour le délai de clignotement de la led :
...
while (1) { if (PINA & button_pin_in){//if button not pushed PORTB |= led_pin_out; // Turn LED on long_delay_ms(blink_delay); PORTB &= ~led_pin_out; // Turn LED off long_delay_ms(blink_delay);
}else{//if button pushed blink_delay=10; while(!(PINA & button_pin_in)){ blink_delay+=10; _delay_ms(10); } } } ...
La fonction de base "_delay_ms" est certes plus pratique à utiliser qu'en assembleur, mais elle ne prend que des constantes, pas de variables.
pour gérer un délai variable je me suis inspiré de cette page http://www.instructables.com/id/Honey-I-Shrunk-the-Arduino-Moving-from-Arduino-t/step2/Our-first-AVR-C-project-Hello-world-LED/ en créant une fonction long_delay :
void long_delay_ms(uint16_t ms) {
for(ms /= 10; ms>0; ms--) _delay_ms(10);
}
Fichier:Hello.blink.button.44.zip Mon programme fonctionne plutôt bien, mais pour qu'il fonctionne mieux, je devrais attacher une interruption au bouton, car lorsque la led est dans sa phase de clignotement, le bouton est inactif.
mais je manque de temps pour implémenter ça cette semaine
à suivre...
avec arduino
Comme récréation, j'essaye la librairie de highlowtech pour programmer les tiny directement depuis l'interface arduino.
fondu de led réglable
le but du programme est de régler la luminance de la led en appuyant plus ou moins longtemps sur le bouton :
/*
set the value of the led by pushing the button more or less longtime This example code is in the public domain. led : 8 button : 7 */
- define led 8
- define bton 7
int value;
void setup() {
value=0; pinMode(led, OUTPUT); pinMode(bton, INPUT_PULLUP);
}
void loop() {
if (digitalRead(bton)==LOW){ if (value!=0){ value=0; } else{ while(digitalRead(bton)==LOW){ if (value<255) { value++; analogWrite(led,value); delay(40); }
} }
} analogWrite(led,value);
delay(40);
}
tentative sur smoothieboard
Pour explorer une programmation plus évoluée, j'imagine essayer de programmer de vrais fins de course sur le smoothieboard.
Cette carte de pilotage de machines CNC équipe la plupart des machines de notre atelier et j'aimerais la faire évoluer, particulièrement pour piloter notre router grand format : SentierBattu.
Une des fonctionalités qui nous fait défaut est une vraie implémentation des fins de course.
analyse du programme
Actuellement, le programme en C++ qui pilote cette carte est construit de façon modulaire :
je voudrais rajouter une interruption sur les fins de course, qui bloque ou inverse les steppers.
queqlues infos sur les interruptions : http://www.edaboard.com/thread196143.html
En fait Arthur, qui développe le programme m'a déconseillé d'utiliser les interuptions au risque de tout casser. De plus, il semblerai que les pins sur lesquels sont branchés les fins de course ne soit pas utilisables avec les interruptions.
Je décide alors de tenter une méthode qui peut risquer de ralentir un peu les mouvements :
implanter un appel dans la fonction
Stepper::trapezoid_generator_tick
Cette fonction gere les mouvements coordonnés des moteurs pas à pas. elle est appelée tout au long des mouvements.
Interroger ici les fins de course ralentira peut-être le mouvement, mais permettra d'être au plus proche des déplacements des axes, pour réagir vite en cas de dépassement des fins de course.
Cette fonction ne s'execute qu'à certaines conditions :
if(this->current_block && !this->paused && this->main_stepper->moving ) {...
j'insère alors au début de cette fonction :
if( THEKERNEL->endstops->overflow()){//if an endstop is touched
this->current_block->release();
}
et dans l'objet Endstops, je décris la fonction :
bool Endstops::overflow(){
for ( char c = 'X'; c <= 'Z'; c++ ) {
//if endstop hit and motor go in is direction if ( this->pins[c - 'X' + (this->steppers[c - 'X']->dir_pin.get() ? 0 : 3)].get() ) { return true; } }
}
Cette fonction aurai put marcher, mais je n'ai pas très bien compris la notion de modules en C++ : apparement, on ne peut appeler un module depuis un autre.
Donc cet appel croisé ne fonctionne pas. Lorsque j'ai demandé de l'aide sur l'IRC de smoothiware, je me suis fait gentillement renvoyé à mon bac à sable, notament car un des développeurs principaux est entrain d'implémenter les fins de course.
J'ai donc laissé là cette expérimentation. Cela-dit, cette recherche m'a permis de comprendre dans les grandes lignes comment fonctionne ce programme et comment contribuer au développement d'un logiciel libre (à savoir, notamment se tenir au courant des points sur lesquels travaillent les autres développeurs).