Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

17
voti

Microcontrollori: come svincolarsi dall'hardware.

La prima caratteristica necessaria che un programma deve avere è quella di funzionare, questo è vero. Ma non è affatto vero che un programma funzionante sia un buon programma. In questo breve articolo vorrei introdurre un paio di concetti: l' astrazione dell'hardware (hardware abstraction) e la tecnica per nascondere le informazioni (information hiding). Sappiamo tutti che le cattive abitudini sono quelle più difficili da perdere, quindi abituarsi a seguire quelle buone aiuterà, sopratutto chi si è avvicinato da poco al mondo dei microcontrollori, a scrivere da subito buoni programmi. L'articolo non vuole essere affatto una lezione ma semplicemente un' introduzione ad un modo di programmare che ritengo molto valido. Non me ne vogliano quindi gli informatici se mi prenderò alcune licenze e ricorrerò ad approssimazioni. Per illustrare questi due semplici ma utilissime tecniche utilizzerò un programma molto semplice: il classico LED lampeggiante, la versione da microcontrollore del programma "Hello World".

Indice

Un programma funzionante

Come esempio prendo il programma di prova che ho utilizzato in questo articolo
Una cosa che salta subito all' occhio guardano le prime #include è che utilizza un AVR, quindi è un programma specifico per questa famiglia di micro. Scendendo più in basso troviamo la routine di servizo dell'interrupt ciclica utilizzata per implementare un timer software e, all'interno del main riusciamo a capire che il LED è collegato al pin 0 della porta B. Quello che faremo è organizzare il programma in modo che sia un programma di tipo generale, adattabile a qualsiasi micro e con la possibilità di essere svincolati, oltre che dal micr,o anche dall'hardware di questo.

//--------------------------------------------------------------------------
// Lampeggio.c
//
// Created: Tanto tempo fa in una galassia molto lontana
//  Author: TardoFreak
//--------------------------------------------------------------------------

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>

// Timer software.
// Nota: vanno dichiarate come "volatile" per fare in modo che il 
// valore sia sempre e comunque letto evitando che l' ottimizzazione 
// non lo faccia.
volatile unsigned short SoftTimer1;

// Routine di servizio chiamata quando il contenuto del comparatore A 
// corrisponde al valore del timer. Quando raggiunge tale valore il 
// timer viene resettato e viene invocata questa routine.
ISR(TIMER1_COMPA_vect)
{
  if(SoftTimer1) SoftTimer1--;
}

//--------------------------------------------------------------------------
int main(void)
{
  // disabilita watchdog
  MCUSR &= ~(1 << WDRF); 
  wdt_disable();
  
  // Inizializza timer 1 per timeout 10ms
  // Questa istruzione assicura che l' I/O clock per il timer1 sia abilitato
  PRR0 &= ~(1<<PRTIM1);

  // Carica il registro di comparazione per ottenere 20ms
  OCR1A = 3124;
	
  // Enable output compare A match 
  TIMSK1 = (1<<OCIE1A)|(0<<TOIE1);
	
  // Avvia il timer1, prescaler 1/64 modo operativo Clear Top Count
  TCCR1B = (0<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10);
	
  // Predispone la porta B come uscita
  DDRB = 0xff;
	
  // Abilita le interrupt
  sei();
	
  while(1)
  {
    if(!SoftTimer1)
    {
      PORTB ^= 0x01;    // Commuta l' uscita PB0
      SoftTimer1 = 25;  // Carica il timer software per intervallo 500ms.
    }		
  }
}

L' astrazione dell' hardware

Svincolarsi dall' hardware significa evitare di avere a che fare con le operazioni a basso livello. Per fare questo modifichiamo il programma dapprima definendo dei simboli per identificare la porta del LED ed il bit a cui è collegato:

#define LED_PORT PORTB
#define LED_DDR DDRB
#define LED_MASK 0x01

Questo è già un primo passo per svincolarsi dall' hardware perché, se un giorno noi decidessimo che il LED dovrà essere collegato ad un altro pin, ci basterà modificare queste defines per assegnare l'uscita dove meglio crediamo.
L'uscita del LED deve essere inizializzata. Nel programma originale l'inizializzazione viene fatta con una semplice istruzione, trasformiamola in funzione e, per non farci mancare niente scriviamo anche due funzioni: una per accendere il LED e l' altra per spegnerlo. In questo modo abbiamo tutte le funzioni per controllare il LED come meglio ci aggrada.

//--------------------------------------------------------------------------
//  void HAL_LEDon(void)
//    accende il LED

void HAL_LEDon(void)
{
  LED_PORT |= LED_MASK;
}	 

//--------------------------------------------------------------------------
//  void HAL_LEDoff(void)
//    spegne il LED

void HAL_LEDoff(void)
{
  LED_PORT &= (LED_MASK ^ 0xff);
}	  

//--------------------------------------------------------------------------
//  void HAL_initlED(void)
//    inizializza l' uscita che pilota il LED

void HAL_initLED(void)
{
  // Predispone il pin 0 della la porta B come uscita
  LED_DDR |= LED_MASK;
  HAL_LEDoff();
}

Ora scriviamo anche la funzione che fa commutare il LED.

//--------------------------------------------------------------------------
//  void HAL_toggleLED(void)
//    cambia lo stato del LED

void HAL_toggleLED(void)
{
  LED_PORT ^= LED_MASK;
}

Domanda: ma è necessario tutto questo casino, questa complicazione?
Non è indispensabile per scrivere un programma che funzioni ma lo è per scrivere un buon programma, più avanti sarà facile caipre il perché.
Una piccola nota: il suffisso HAL messo davanti al nome delle funzioni è un acronimo che significa "Hardware Abstraction Layer". Anche questo ha ragione di esistere ed anche la sua presenza sarà più chiara più avanti.

Un modulo solo per l' hardware

Siamo già ad un buon punto. Per fare le cose ben fatte la cosa migliore è prendere queste funzioni e metterle all' interno di un modulo a parte. Così facendo non impesteremo il programma principale con le funzioni relative all' hardware rendendolo più semplice da leggere. Inoltre l' avere tutte le funzioni per la gestione dell' hardware in un solo modulo ci premetterà una facile manutenzione del programma.
In buona sostanza un modulo è costituito da due files: un file sorgente dove risiedono le funzioni ed un file header da includere nel programma che usa questo modulo. Dobbiamo quindi creare questi due files che chiamiamo HAL_pierin.c (dove ci sono le funzioni) e HAL_pierin.h dove ci sono i prototipi di queste funzioni. Incominciamo con il file header

#ifndef HAL_PIERIN_H
#define HAL_PIERIN_H

#define LED_PORT PORTB
#define LED_DDR DDRB
#define LED_MASK 0x01

extern void HAL_LEDon(void);
extern void HAL_LEDoff(void);
extern void HAL_initLED(void);
extern void HAL_toggleLED(void);

#endif

// Fine file header

Nota: la prima e la seconda linea del file header servono per evitare inclusioni multiple. Una volta incluso il file il simbolo HAL_PIERIN_H sarà definito quindi, in caso di inclusioni annidate i simboli seguenti non verranno ridefiniti e non si genererà nessun errore.
E questo è il file del modulo

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>

#include "HAL_pierin.h"

//--------------------------------------------------------------------------
//  void HAL_LEDon(void)
//    accende il LED

void HAL_LEDon(void)
{
  LED_PORT |= LED_MASK;
}	 

//--------------------------------------------------------------------------
//  void HAL_LEDoff(void)
//    spegne il LED

void HAL_LEDoff(void)
{
  LED_PORT &= (LED_MASK ^ 0xff);
}	  

//--------------------------------------------------------------------------
//  void HAL_initlED(void)
//    inizializza l' uscita che pilota il LED

void HAL_initLED(void)
{
  // Predispone il pin 0 della la porta B come uscita
  LED_DDR |= LED_MASK;
  HAL_LEDoff();
}

//--------------------------------------------------------------------------
//  void HAL_toggleLED(void)
//    cambia lo stato del LED

void HAL_toggleLED(void)
{
  LED_PORT ^= LED_MASK;
}

// Fine modulo

Ora basta includere il file header nel programma principale e le funzioni per la gestione dell' hardware saranno disponibili. Il programma principale sarà ora questo:

//--------------------------------------------------------------------------
// Lampeggio.c
//
// Created: Tanto tempo fa in una galassia molto lontana
//  Author: TardoFreak
//--------------------------------------------------------------------------

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>

#include "HAL_pierin.h"

// Timer software.
// Nota: vanno dichiarate come "volatile" per fare in modo che il 
// valore sia sempre e comunque letto evitando che l' ottimizzazione 
// non lo faccia.
volatile unsigned short SoftTimer1;

// Routine di servizio chiamata quando il contenuto del comparatore A 
// corrisponde al valore del timer. Quando raggiunge tale valore il 
// timer viene resettato e viene invocata questa routine.
ISR(TIMER1_COMPA_vect)
{
  if(SoftTimer1) SoftTimer1--;
}



//--------------------------------------------------------------------------
int main(void)
{
  // disabilita watchdog
  MCUSR &= ~(1 << WDRF); 
  wdt_disable();
  
  // Inizializza timer 1 per timeout 10ms
  // Questa istruzione assicura che l' I/O clock per il timer1 sia abilitato
  PRR0 &= ~(1<<PRTIM1);

  // Carica il registro di comparazione per ottenere 20ms
  OCR1A = 3124;
	
  // Enable output compare A match 
  TIMSK1 = (1<<OCIE1A)|(0<<TOIE1);
	
  // Avvia il timer1, prescaler 1/64 modo operativo Clear Top Count
  TCCR1B = (0<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10);
	
  HAL_initLED();
	
  // Abilita le interrupt
  sei();
	
  while(1)
  {
    if(!SoftTimer1)
    {
      //PORTB ^= 0x01;    // Commuta l' uscita PB0
	  HAL_toggleLED();
      SoftTimer1 = 25;  // Carica il timer software per intervallo 500ms.
    }		
  }
}

Avanti così!

Come si dice "abbiamo fatto trenta, facciamo trentuno". E' ora di includere nel modulo di astrazione dell' hardware anche tutte le altre funzioni ed inizializzazioni della macchina compresa la funzione di servizio dell' interrupt ciclica. In questo modo il programma principale sarà ridotto all' essenziale e non ci sarà traccia di qualsiasi cosa dipendente dall' hardware. Aggiungiamo quindi al nostro modulo le funzione di inizializzazione del microcontrollore e dell' interrupt ciclica.

//--------------------------------------------------------------------------
//  void initMicro(void)
//    inizializza il microcontrollore
void HAL_initMicro(void)
{
  // disabilita watchdog
  MCUSR &= ~(1 << WDRF); 
  wdt_disable();
}

//--------------------------------------------------------------------------
//  void initCyclicInt(void)
//    Inizializza l' interrupt ciclica

void HAL_initCyclicInt(void)
{
  // Inizializza timer 1 per timeout 10ms
  // Questa istruzione assicura che l' I/O clock per il timer1 sia abilitato
  PRR0 &= ~(1<<PRTIM1);

  // Carica il registro di comparazione per ottenere 20ms
  OCR1A = 3124;
	
  // Enable output compare A match 
  TIMSK1 = (1<<OCIE1A)|(0<<TOIE1);
	
  // Avvia il timer1, prescaler 1/64 modo operativo Clear Top Count
  TCCR1B = (0<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10);
}

E quindi facciamo che spostare anche la funzione di servizio dell' interrupt. Questa funzione utilizza però una variabile chiamata SoftTimer1 che è dichiarata come variabile globale. E' buona cosa utilizzare per leggere il suo valore e per impostarlo delle funzioni a posta in modo da essere sicuri che nessuno possa accedervi erroneamente. Quindi dichiariamo la variabile all' interno del modulo HAL e scriviamo le due funzioni per leggerne il valore e per impostarlo.

//--------------------------------------------------------------------------
//  void HAL_setTimer1(unsigned int valore)
//    imposta il valore del timer software 1

void HAL_setTimer1(unsigned int valore)
{
  SoftTimer1 = valore;
}

//--------------------------------------------------------------------------
//  unsigned int HAL_getTimer1(void)
//    legge il valore del timer software 1
unsigned int HAL_getTimer1(void)
{
  return(SoftTimer1);
}

Ora il nostro programma principale sarà ridotto a questo.

//--------------------------------------------------------------------------
// Lampeggio.c
//
// Created: Tanto tempo fa in una galassia molto lontana
//  Author: TardoFreak
//--------------------------------------------------------------------------

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>

#include "HAL_pierin.h"

//--------------------------------------------------------------------------
int main(void)
{
  HAL_initMicro();
  HAL_initCyclicInt();
  HAL_initLED();
  
  // Abilita le interrupt
  sei();
	
  while(1)
  {
    if(!HAL_getTimer1())
    {
      HAL_toggleLED();
      HAL_setTimer1(25);  // Carica il timer software per intervallo 500ms.
    }		
  }
}

Come si può notare non vi è più traccia di operazioni a basso livello ma non abbiamo ancora finito!

Il tocco finale

E' arrivato finalmente il momento di dare il tocco finale all' operazione. Come potete notare ci sono ancora alcune funzioni che sono specifiche del microcontrollore utilizzato come l' istruzione di abilitazione delle interrupt e le inizializzazioni. Vogliamo che queste non solo siano incluse nel modulo relativo all' hardware ma che siano raggruppate in un unica funzione di inizializzazione del sistema. In questo modo potremo anche togliere dal main l' inclusione dei files del micro e lasciare solo l' inclusione del modulo che fa riferimento al microcontrollore ed al suo hardware.
Dobbiamo nascondere al programma principale alcune informazioni come le defines del pin del LED e della variabile volatile utilizzata per il timer software. Scrivendo un unica funzione di inizializzazione potremo anche nascondere le inizializzazioni specifiche della interrupt ciclica, dei LED e del micro. In questo modo siamo sicuri che nessuna parte del programma principale possa fare casini aumentandone l' affidabilità. Inoltre possiamo rendere queste funzioni visibili solo a livello di modulo togliendo i prototipi dal file header ed assicurarci che non possano essere in qualsiasi modo richiamate. Per fare questo useremo la parolina magica static che metteremo davanti alla dichiarazione delle funzioni che vogliamo nascondere. I tre files che compongono il progetto, alla fine di tutto questo lavoro saranno questi.

// File HAL_pierin.h

#ifndef HAL_PIERIN_H
#define HAL_PIERIN_H

extern void HAL_LEDon(void);
extern void HAL_LEDoff(void);
extern void HAL_toggleLED(void);

extern void HAL_setTimer1(unsigned int valore);
extern unsigned int HAL_getTimer1(void);

extern void HAL_initSystem(void);

#endif

// Fine file header
// File HAL_pierin.c

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>

#include "HAL_pierin.h"

// Defines per il pin di uscita per il pilotaggio del LED
#define LED_PORT PORTB
#define LED_DDR DDRB
#define LED_MASK 0x01

static void HAL_initMicro(void);
static void HAL_initCyclicInt(void);

// Timer software.
// Nota: vanno dichiarate come "volatile" per fare in modo che il 
// valore sia sempre e comunque letto evitando che l' ottimizzazione 
// non lo faccia.
volatile unsigned short SoftTimer1;

//--------------------------------------------------------------------------
//  void HAL_LEDon(void)
//    accende il LED

void HAL_LEDon(void)
{
  LED_PORT |= LED_MASK;
}	 

//--------------------------------------------------------------------------
//  void HAL_LEDoff(void)
//    spegne il LED

void HAL_LEDoff(void)
{
  LED_PORT &= (LED_MASK ^ 0xff);
}	  

//--------------------------------------------------------------------------
//  void HAL_initLED(void)
//    inizializza l' uscita che pilota il LED

void HAL_initLED(void)
{
  // Predispone il pin 0 della la porta B come uscita
  LED_DDR |= LED_MASK;
  HAL_LEDoff();
}

//--------------------------------------------------------------------------
//  void HAL_toggleLED(void)
//    cambia lo stato del LED

void HAL_toggleLED(void)
{
  LED_PORT ^= LED_MASK;
}

//--------------------------------------------------------------------------
//  void HAL_initMicro(void)
//    inizializza il microcontrollore
static void HAL_initMicro(void)
{
  // disabilita watchdog
  MCUSR &= ~(1 << WDRF); 
  wdt_disable();
}

//--------------------------------------------------------------------------
//  void HAL_initCyclicInt(void)
//    Inizializza l' interrupt ciclica

static void HAL_initCyclicInt(void)
{
  // Inizializza timer 1 per timeout 10ms
  // Questa istruzione assicura che l' I/O clock per il timer1 sia abilitato
  PRR0 &= ~(1<<PRTIM1);

  // Carica il registro di comparazione per ottenere 20ms
  OCR1A = 3124;
	
  // Enable output compare A match 
  TIMSK1 = (1<<OCIE1A)|(0<<TOIE1);
	
  // Avvia il timer1, prescaler 1/64 modo operativo Clear Top Count
  TCCR1B = (0<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10);
}

//--------------------------------------------------------------------------
// Routine di servizio chiamata quando il contenuto del comparatore A 
// corrisponde al valore del timer. Quando raggiunge tale valore il 
// timer viene resettato e viene invocata questa routine.
ISR(TIMER1_COMPA_vect)
{
  if(SoftTimer1) SoftTimer1--;
}

//--------------------------------------------------------------------------
//  void HAL_setTimer1(unsigned int valore)
//    imposta il valore del timer software 1

void HAL_setTimer1(unsigned int valore)
{
  SoftTimer1 = valore;
}

//--------------------------------------------------------------------------
//  unsigned int HAL_getTimer1(void)
//    legge il valore del timer software 1
unsigned int HAL_getTimer1(void)
{
  return(SoftTimer1);
}

//--------------------------------------------------------------------------
//  void HAL_initSystem(void)
//    inizializza tutto il sistema

void HAL_initSystem(void)
{
  HAL_initMicro();
  HAL_initCyclicInt();
  HAL_initLED();
  
  // Abilita le interrupt
  sei();
}

// Fine modulo
//--------------------------------------------------------------------------
// Lampeggio.c
//
// Created: Tanto tempo fa in una galassia molto lontana
//  Author: TardoFreak
//--------------------------------------------------------------------------

#include "HAL_pierin.h"

//--------------------------------------------------------------------------
int main(void)
{
  HAL_initSystem();
	
  while(1)
  {
    if(!HAL_getTimer1())
    {
      HAL_toggleLED();
      HAL_setTimer1(25);  // Carica il timer software per intervallo 500ms.
    }		
  }
}

Cambiare pin e microcontrollore

E' facile intuire come si può cambiare il pin che pilota il LED infatti basta modificare le defines poste all' inizio del modulo di astrazione dell' hardware ma non solo, è anche possibile, modificando opportunamente le funzioni di accensione/spegnimento/commutazione del LED utilizzare anche una circuiteria diversa. In queso esempio il pin è collegato al LED con una semplice resistenza ed il LED è collegato a 0V. Nel caso dovessimo per forza, comodità o altri motivi utilizzare un LED collegato verso l' alimentazione sarà sufficiente modificare le funzioni. Ora è evidente che il programma principale è una boiata ma supponiamo di avere un programma grande e complesso dove in più punti è necessario comandare il LED. Se si usassero operazioni a basso livello bisognerebbe modificarle tutte mentre così basta modificare solo le tre funzioni.
E se invece il LED deve essere sostituito, ad esempio, con un pallino su uno schermo LCD? Anche qui il problema non esiste perché sempre solo tre sono le funzioni da modificare ma non si toccherebbe affatto il programma principale.
E se dobbiamo cambiare il microcontrollore sarà sufficiente sviluppare il modulo specifico per il nuovo microcontrollore lasciando così inalterato il programma principale. Se, ad esempio, vorrò utilizzare un PIC o un ARM mi basterà riscrivere le funzioni di gestione dell' hardware per levarmi la paura.

Conclusioni

Ho preso come esempio un programma semplice ma mi pare che oramai sia chiaro che questo modo di scrivere i programmi raggiunge gli obiettivi principali: ordine, facile manutenibiltà, affidabilità e flessibilità. Si potrebbe obiettare che il codice ottenuto è più lento e corposo. Questo potrebbe essere vero se i compilatori fossero stupidi ma i compilatori di oggi sono mooolto furbi. Se si accorgono che una funzione è corta scrivono il codice direttamente senza chiamarla. I compilatori della Mikroelektronika (MikroC), seppur economici, già lo fanno. Per non parlare poi di compilatori come quello della KEIL. Sono talmente furbi che queste cose le fanno senza dire niente a nessuno.
In questo breve articolo ho illustrato solo il layer di astrazione dell' hardware ma non è l' unico layer che di solito si implementa. Una altro layer è il cosiddetto API (Application Program Interface) che solitamente racchiude anche quello di astrazione dell' hardware.
I prefissi HAL che ho messo davanti alle funzioni servono appunto per indicare il layer a cui si fa riferimento proprio per evitare intrusioni accidentali a livelli più bassi.
Un' altra piccola considerazione: la pratica di nascondere informazioni e di incapsulare i moduli dentro altri moduli è quello che poi ha determinato lo sviluppo dei linguaggio orientati a gli oggetti (come il C++) e, di conseguenza, anche quelli fortemente orientati agli oggetti come il Java per sempio. In C++, ad esempio, quando si crea un oggetto viene eseguito subito il metodo chiamato "costruttore" che poi altro non è che la onnipresente funzione di inizializzazione di un modulo scritto in C e l' ereditarità è resa più semplice mentre in C passa attraverso i files header.
Che altro dire? Divertitevi con i micro e, se potete, evitate di prendere cattive abitudini di programmazione in modo da poter scrivere buoni programmi.
La fine del mondo non è arrivata, il Natale è alle porte quindi non mi resta che auguravi BUONE FESTE e buona sperimentazione.

10

Commenti e note

Inserisci un commento

di ,

Io non lo faccio perché le varie parti dell' hardware sono, spesso e volentieri, interdipendenti. A mio parere un modulo deve essere una cosa a se. Per migliorare la leggibilità si può semplicemente spezzettare il sorgenti in diversi sorgenti semplicemente includendoli nel modulo. Trovo che l' organizzazione a strati sovrapposti (layer) sia la migliore.

Rispondi

di ,

E' un'ottimo modo per organizzare il codice a mio parere, e complimenti per l'articolo. Volevo chiedere una cosa: non sarebbe meglio dividere le funzioni in file sorgente diversi? Per esempio un file sorgente HAL_led.c con tutte le funzioni per il led, un file sorgente HAL_timer.c con le funzioni per il timer e la routine di gestione dell'interrupt di overflow, un file HAL_micro.c con le funzioni di inizializzazione, ecc... così nel caso di necessità di modificare una funzione in un grande progetto sarà più semplice trovare il pezzo di codice interessato andando ad aprire il file sorgente relativo.

Rispondi

di ,

Un altro metodo per l' ottimizzazione (e per scrivere buone funzioni) è quello di scriverle in modo da poter utilizzare l' attributo "pure", come sarebbe buona cosa scriverle in modo che siano "thread safe". Sono tutte tecniche utili per scrivere buoni programmi. Fortunatamente oggi, grazie alla potenza dei micro ed alla bontà dei compilatori si possono, e si devono a mio parere, evitare trucchetti strani. "Non ci sono più i micro di una volta" ... per fortuna, aggiungo io! :)

Rispondi

di ,

Articolo interessante, vorrei aggiungere una piccola precisazione a questa frase "Se si accorgono che una funzione è corta scrivono il codice direttamente senza chiamarla." Questo in C e C++ prende il nome di inline qui http://en.wikipedia.org/wiki/Inline_function su wikipedia viene spiegato un po' di più. A volte per essere sicuri cha la funzione sia inline c'è la possibilità di mettere l'attributo inline o __inline, poi dipende dal compilatore che si usa. Esempio: void HAL_LEDoff(void) potrebbe diventare con il compilatore GCC void __inline HAL_LEDoff(void) i compilatori già lo fanno di loro l'ottimizzazione ma a volte potrebbe essere utile diglierlo in modo specifico. Un esempio è quando si disattivano le ottimizzazioni.

Rispondi

di ,

Ottimo articolo TardoFreak :) Voto meritatissimo non c'è che dire! Aiuterà molti a non prendere "cattivi" stili di programmazione. Ciao, Francesco

Rispondi

di ,

Sono proprio ignorante.....non ho seguito i particolari dei vari passaggi perchè non conosco il linguaggio C, ma credo di aver compreso il senso generale dell'articolo che mi servirà ad avvicinarmi meglio al mondo della buona programmazione dei micro di cui ormai non si può più fare a meno.

Rispondi

di ,

Ottimo connubio di teoria ed esempi pratici per spiegare questi importanti concetti di ingegneria del software. Bravo e grazie, TardoFreak!

Rispondi

di ,

Il contrario dell' "Hard Coding"? :)

Rispondi

di ,

Un bellissimo articolo.
Utilissimo per chi inizia a programmare i microcontrollori, in modo da imparare bene da subito e senza prendere vizi, poi duri a morire. Grazie TardoFreak!!!!

Rispondi

Inserisci un commento

Per inserire commenti è necessario iscriversi ad ElectroYou. Se sei già iscritto, effettua il login.