BaroneRosso.it - Forum Modellismo

BaroneRosso.it - Forum Modellismo (https://www.baronerosso.it/forum/)
-   Circuiti Elettronici (https://www.baronerosso.it/forum/circuiti-elettronici/)
-   -   Linguaggio "C" nel PIC e nell'Arduino (https://www.baronerosso.it/forum/circuiti-elettronici/254907-linguaggio-c-nel-pic-e-nellarduino.html)

Naraj 29 giugno 12 23:49

Linguaggio "C" nel PIC e nell'Arduino
 
Solo da qualche giorno ho iniziato a giocare con l'Arduino. L'ultimo micro che ho programmato in assembler era il PIC e devo dire che quest'ultimo mi sembra più adatto a progetti miniaturizzati e vorrei continuare con esso, ma programmandolo in "C".

Mi piacerebbe sapere, da chi programma in "C" sia il PIC che l'Arduino, se ci sono notevoli differenze tra i due linguaggi di programmazione.

Naraj.

Davide B. 30 giugno 12 00:18

Post Duplicato... scusate.

Davide B. 30 giugno 12 00:19

Il linguaggio di programmazione è "quasi" lo stesso, nel senso che, a seconda del compilatore e della sua aderenza ai vari standard si somiglieranno tutti più o meno per quanto riguarda la scrittura delle istruzioni stesse.
Detto questo alcune implementazioni sono abbastanza diverse (vedi ad esempio il MikroC) per quanto riguarda la scrittura e la lettura dei registri.
Detto questo la forte differenza (chiamiamola pure limitazione) che ho notato nella programmazione in c dei pic della serie 16 e 18 rispetto agli altri microcontrollori è una limitazione di tipo architetturale, dovuta alla lunghezza dello stack che è limitata, e che quindi impone un limite più risicato nell'albero di chiamate delle funzioni stesse.
In pratica, partendo dalla funzione main con il PIC16 si possono avere annidamenti fino a 5 livelli, nei pic più grandi il numero cresce, ma non si può andare più il la di tanto.
Negli atmel tipo il 328 la ram è suddivisibile come nei micro più "seri", o meglio, nella maggioranza di essi.
Inoltre, generalmente, a parità di costo sembra che la atmel fornisca più RAM e più FLASH (ma di questo non sono sicuro).
Infine, paragonando la serie ATMEGA con la serie PIC16F il compilatore è probabilmente più efficiente(sulla 18 non ci scommetto avendoli usati poco).

romoloman 30 giugno 12 01:31

Quoto in parte Davide,
Ovviamente le differenze stanno tutte nella parte di basso livello, registri, timers, interrupt i/o ports, quindi ovviamente non vi è una portabilità da un'architettura all'altra

Citazione:

Originalmente inviato da Davide B. (Messaggio 3278171)
Infine, paragonando la serie ATMEGA con la serie PIC16F il compilatore è probabilmente più efficiente(sulla 18 non ci scommetto avendoli usati poco).

Beh qui invece non sono d'accordo, anche perché nella stessa famiglia AVR l'efficienza dei compilatori varia non solo a seconda del produttore ma anche della versione.
avr-gcc 4.7.2 ottimizza il codice per dimensione molto più di avr-gcc 4.6.2 quindi parlare di efficienza è un concetto abbastanza astratto, non esistendo un solo compilatore C per entrambe le famiglie.
(il compilatore della microchip comunque è molto valido)

ElNonino 01 luglio 12 12:22

my 2 cents:

per creare codice 'C' portatile fra dispositivi diversi è una buona regola utilizzare lo ANSI C che è universalmente standard, poi crearsi alcune macro per operazioni di base anch'esse standard.

Io poi mi son creato alcuni files header contenenti i #define personalizzati dei registri o porte dei vari micro, in tal modo modifico solo alcuni <include> ed il codice resta invariato passando da un PIC16 a dsPIC33 o STM32; chiaramente tenendo poi comunque conto delle diverse risorse hw e velocità (temporizzazioni) dei dispositivi.

Per abitudine poi utilizzo un altro <include> comune a tutti che definisce alcuni tipi di variabili, char, byte, word, etc etc costituiti da struct ed union; ciò consente di accedere direttamente al singolo bit, byte...di ogni variabile; ad esempio in una media mobile di 16..64 valori basta estrarre lo lsb o lsw dalla variabile anzichè dividere per 16 o 64.

Un buon stile di programmazione facilità molto la portatilità.

:yeah:

romoloman 01 luglio 12 16:53

Citazione:

Originalmente inviato da ElNonino (Messaggio 3279584)
my 2 cents:

per creare codice 'C' portatile fra dispositivi diversi è una buona regola utilizzare lo ANSI C che è universalmente standard, poi crearsi alcune macro per operazioni di base anch'esse standard.

Io poi mi son creato alcuni files header contenenti i #define personalizzati dei registri o porte dei vari micro, in tal modo modifico solo alcuni <include> ed il codice resta invariato passando da un PIC16 a dsPIC33 o STM32; chiaramente tenendo poi comunque conto delle diverse risorse hw e velocità (temporizzazioni) dei dispositivi.

Per abitudine poi utilizzo un altro <include> comune a tutti che definisce alcuni tipi di variabili, char, byte, word, etc etc costituiti da struct ed union; ciò consente di accedere direttamente al singolo bit, byte...di ogni variabile; ad esempio in una media mobile di 16..64 valori basta estrarre lo lsb o lsw dalla variabile anzichè dividere per 16 o 64.

Un buon stile di programmazione facilità molto la portatilità.

:yeah:

Si ma se in un processore hai solo 2 livelli di interrupt e nell'altro 6 poco aiutano i define soprattutto se ne vuoi usare 6.

ElNonino 01 luglio 12 20:27

Citazione:

Originalmente inviato da romoloman (Messaggio 3280046)
Si ma se in un processore hai solo 2 livelli di interrupt e nell'altro 6 poco aiutano i define soprattutto se ne vuoi usare 6.

Hai ragione, ma un' altra buona regola di programmazione (in taluni campi è un must) è di limitare al massimo l'uso degli interrupt 1 o 2 massimi.

Io credo che in campo modellistico (ed anche professionale) basti usare anche solo un interrupt generato da un timer per schedulare tutte le operazioni.

E' chiaro che alcune funzioni come DMA, pwm hw, e molte altre sono appunto hw specifiche e quindi non è possibile renderle portatili con facilità: ad esempio alcuni micro Cypress comprendono blocchi analogici che è difficile trovare in prodotti di altre marche però alcune funzioni di controllo di, ad esempio, SPI, I2C, UART, protocolli di comunicazione ed altro possono essere rese facilmente portatili al 90% o più.

:yeah:

Naraj 02 luglio 12 14:21

Grazie ragazzi.
Ho ricevuto molte risposte in più di quelle che mi servivano.
Ora posso continuare la programmazione con il "C" sapendo di avere alle spalle una schiera di persone disponibili e con un'ottima preparazione specifica.
Sicuramente avrò ancora bisogno di voi.

Naraj.

Davide B. 02 luglio 12 15:18

Citazione:

Hai ragione, ma un' altra buona regola di programmazione (in taluni campi è un must) è di limitare al massimo l'uso degli interrupt 1 o 2 massimi.
Io credo che in campo modellistico (ed anche professionale) basti usare anche solo un interrupt generato da un timer per schedulare tutte le operazioni.
Dunque... USB, Ethernet, timer, seriali, SD... tutto in polling?
Nei PIC può darsi, negli ARM sicuramente esistono modi più efficienti.
Quando ho 3 seriali utilizzo 3 interrupt diversi.
Forse fai un po' di confusione tra kernel real time ed interrupt...

ElNonino 02 luglio 12 17:54

Citazione:

Originalmente inviato da Davide B. (Messaggio 3281399)
Dunque... USB, Ethernet, timer, seriali, SD... tutto in polling?
Nei PIC può darsi, negli ARM sicuramente esistono modi più efficienti.
Quando ho 3 seriali utilizzo 3 interrupt diversi.
Forse fai un po' di confusione tra kernel real time ed interrupt...

La scelta di usare o meno interrupt a iosa dipende principalmente dal tipo di applicazione, dall'affidabilità che si vuole raggiungere e sicuramente anche dall'architettura del micro.

E' chiaro che ogni interrupt va ad interromper il normale flusso del programma, comporta il salvataggio di registri, della posizione dell'ultima istruzione e magari anche di qualche variabile nonchè il loro successivo ripristino.

Tutte queste operazioni quindi vanno ad alterare il tempo di ciclo dell'intera applicazione e quando si usano più interrupt, anche se con livelli di priorità ben congegnati, le possibiltà che qualcosa vada storto aumentano. Sicuramente si ha una latenza non predicibile del tempo di ciclo.

Gli ARM ed anche i dsPIC e famiglie superiori hanno le funzioni DMA che consentono ad esempio di ricevere dati senza interrompere la normale schedulazione del programma, quindi basta all' interno del ciclo principale andarsi a leggere il flag del canale DMA e se settato operare di conseguenza a tempo opprtuno.

A me l'uso smodato degli interrupt in applicazioni semplici come quelle che mi trovo normalmente a realizzare non piace e mi ricorda un poco lo 'spaghetti code' irto di 'GOTO' di alcuni programmatori in BASIC; in genere preferisco anche usare più micro piccoli dedicati ad operazioni time-critical anzichè un megaprocessore che fa tutto lui, son scelte.

:yeah:

Davide B. 02 luglio 12 18:38

Citazione:

E' chiaro che ogni interrupt va ad interromper il normale flusso del programma, comporta il salvataggio di registri, della posizione dell'ultima istruzione e magari anche di qualche variabile nonchè il loro successivo ripristino.
Tutte queste operazioni quindi vanno ad alterare il tempo di ciclo dell'intera applicazione e quando si usano più interrupt, anche se con livelli di priorità ben congegnati, le possibiltà che qualcosa vada storto aumentano. Sicuramente si ha una latenza non predicibile del tempo di ciclo.
Sul fatto che ogni interrupt debba andare ad interrompere il flusso normale del programma vorrei ben sperarlo, visto che gli interrupt nascono proprio per quello.
Sul fatto che debbano salvare (push) e ripristinare (pop) i registri ed il program counter (che guarda caso è un registro) sono pure d'accordo, non mi spaventa il problema.
Sulla latenza, le macchine arm cortex ma anche quelle dei pic16 sono abbastanza deterministiche all'entrata ed all'uscita... Peraltro non credo che latenze di qualche microsecondo (esagerando) possano pesare così tanto su una applicazione a microcontrollori.
Se parliamo di jitter, ovvero di slittamento temporale dell'applicazione principale perché scattano gli interrupt allora significa che quest'ultima è stata fatta male, ma questo è colpa di chi scrive l'applicazione, non dell'architettura e neanche degli interrupt.
In uno dei miei orologi, pur andando piano (57,6 MHz) e con un'architettura non particolarmente aggiornata, ovvero un arm7 (LPC2368) con attivi interrupt di USB, 2 timer, 2 seriali, SD card, il jitter del sistema rimane quello del quarzo.
Citazione:

Gli ARM ed anche i dsPIC e famiglie superiori hanno le funzioni DMA che consentono ad esempio di ricevere dati senza interrompere la normale schedulazione del programma, quindi basta all' interno del ciclo principale andarsi a leggere il flag del canale DMA e se settato operare di conseguenza a tempo opprtuno.
I DMA sono una comodità, ma non precludono gli interrupt. Anzi.
Poi ripeto. Tu parli di ciclo e di schedulazione. Ma lavorando così il tuo jitter rischia di essere superiore al mio
Citazione:

A me l'uso smodato degli interrupt in applicazioni semplici come quelle che mi trovo normalmente a realizzare non piace e mi ricorda un poco lo 'spaghetti code' irto di 'GOTO' di alcuni programmatori in BASIC; in genere preferisco anche usare più micro piccoli dedicati ad operazioni time-critical anzichè un megaprocessore che fa tutto lui, son scelte.
Hai ragione. Sono scelte. Ma vanno ponderate al fatto che i microcontrollori sono cambiati rispetto a qualche anno fa.
Nelle mie applicazioni i miei micro sono quantomeno in low power mode per il 70% - 80% del tempo perché esistono norme che implicano che uno debba risparmiare il più possibile energia, cosa che, tra l'altro i micro attuali fanno e piuttosto bene.
Me lo spieghi perché devo tenere un micro acceso in un while(1)?
Nei miei micro gira un kernel real time che si occupa di semplificarmi lo sviluppo senza impapocchiarlo in cicli senza fine, con maggiore leggibilità del codice, manutenibilità e portabilità.
Quanto al GOTO... non mi spaventa usarlo come nessun'altra istruzione del c. Anzi, ti dirò di più. Con alcune versioni di un compilatore c in cui avevo degli If con livelli molto annidati, per un errore di scrittura dello stesso mi sbagliava un long jump mi caricava solo l'LS Byte e non il MS Byte). Cosa che ho corretto indovina un po' con cosa?

ElNonino 03 luglio 12 09:19

Mi pare di capire che operiamo in campi applicativi dei micro molto diversi, nel mio caso lo sleep del micro non è proprio attuabile, così come gli annidamenti eccessivi.

Il jtter dell'oscillatore non mi preoccupa, uso solo TCXO esterni di ottima qualità.

:yeah:

Davide B. 03 luglio 12 10:57

Per me puoi anche usare dei BVA, non è che sono invidioso.
Mi rimane il dubbio del fatto che un ciclo while(1) con una serie di if annidate non abbia jitter maggiore di una gestione interrupt fatta "al minimo sindacale".
Mi spiego meglio.
Da quello che ho capito, i tuoi main dovrebbero assomigliare a qualcosa del genere
Codice:

while(1)
{
if (RichiestaPeriferica1)
{
}
if (RichiestaPeriferica2)
{
}
if (RichiestaPeriferica3)
{
}
if (RichiestaPeriferica4)
{
}
}

o, ancora peggio, il ciclo while è sostituito da un timer (in interrupt).
Supponiamo che debba essere servita Periferica4, e che la richiesta arrivi subito dopo l'if (RichiestaPeriiferica4)
Nella condizione migliore, ogni if può essere scritta con 4 istruzioni in assembler, la while (1) con un bel salto incondizionato all'indietro.
Nella migliore delle ipotesi hai che la latenza, a seconda del momento in cui ti arriva la richiesta, può variare di diversi cicli macchina, e che la latenza è pesantemente dipendente da come è fatto il ciclo.
Se ti arriva prima del ciclo, magari ti ci vuole un ciclo macchina, se ti arriva dopo, magari anche una ventina o una trentina...
Se poi mi aiuti a capire, posso anche correre il rischio di imparare qualcosa.

romoloman 03 luglio 12 12:14

Io credo senza alcuna polemica che non esista un approccio corretto in assoluto. Molto spesso un mix di interrupt e idle loops è quantomeno inevitabile...
Se voglio che una routine non abbia jitter una delle poche speranze che ho è proprio quella di usare un interrupt legato ad un timer (esempio il settaggio del pwm hardware per la generazione di un treno di impulsi ppm.) così come se ad esempio se devo leggere la seriale velocemente su un AVR per garantire che il buffer di ricezione non si riempia mai (3 miseri byte).
Per altre cose un approccio a idle loop può invece essere ottimale quando ad esempio devo fare calcoli e gestire ingressi/uscite su loop dove la prevedibilità del tempo di esecuzione complessivo non sia rilevante.
Anche l'utilizzo di S.O. realtime a volte aiuta, a volte è ridondante a volte non praticabile.
Ritornando in topic: un approccio alla programmazione che usi pesantemente le risorse del processore diminuisce la portabilità ma di contro spesso aumenta le performance.

ElNonino 03 luglio 12 13:41

Concordo con romoloman che non esiste un approccio valido per tutto, ogni applicazione è diversa e può anche richiedere hardware diverso.

Non è poi mia presunzione voler insegnare alcunchè, so di aver molto da imparare perchè la programmazione in se è cosa dinamica ed in continua evoluzione; nel mio campo di lavoro è un must avere temporizzazioni precise ed una latenza conosciuta e stabile, il sistema che uso normalmente (salvo varizioni sul tema è visibile nel seguente codice):

Codice:

while(1)
{
        ++Tic;
        switch (Tic & 7)
        {
                case 0:        SetMod();
                        break;

                case 1:        SendVar1();
                        break;

                case 2:        MisPTMet();
                        break;

                case 3:        CalH2O();
                        break;

                case 4:        CalAcc();
                        FrenMot();
                        break;

                case 5:        CalRpm();
                        CalPre();
                        AntiDet();
                        break;

                case 6:        DiaMat();
                        IntBid();
                        break;

                case 7:        CompGas();
                        CompOil();
                        CompAir();
                        CompEgr();
                        break;
        }
        TaskVel();
        WaitMain();
}

void WaitMain()
{
        while(!TMR2IF);
        TMR2IF = 0;                TMR2IE = 0;
        WriteTimer2(0);                                        // 512 us
}

Con questa struttura è chiaro che tutte le funzioni dei task devono essere completate in meno di 512us come pure deve essere rispettato che TaskX + TaskVel < 512us.

Il Taskvel deve avere un tempo di esecuzione molto ridotto, tipo leggere solo un flag di OK, è altrettanto evidente che è molto importante la sequenza di assegnazione delle funzioni all' interno dei task.

In questa applicazione l'unico interrupt presente (e non mostrato) è la comunicazione seriale PC -> dispositivo; poichè tale evento avviene solo in caso diagnostico o di messa a punto, tutta la comunicazione avviene in 4,096ms e quindi i dati acquisiti od i comandi di output vengono 'congelati' per lo stesso tempo.

Del Timer2 leggo solo il flag di interrupt (overflow) e non genero e tratto realmente lo intterrupt.

:yeah:

Davide B. 03 luglio 12 18:47

Citazione:

Io credo senza alcuna polemica che non esista un approccio corretto in assoluto. Molto spesso un mix di interrupt e idle loops è quantomeno inevitabile...
Se voglio che una routine non abbia jitter una delle poche speranze che ho è proprio quella di usare un interrupt legato ad un timer (esempio il settaggio del pwm hardware per la generazione di un treno di impulsi ppm.) così come se ad esempio se devo leggere la seriale velocemente su un AVR per garantire che il buffer di ricezione non si riempia mai (3 miseri byte).
Che le cose si possano fare in modo diverso è vero e sacrosanto.
Che ognuno possa usare l'approccio che più gli piace pure.
Per quanto riguarda gli Idle Loop, li uso, ma il meno possibile.
Citazione:

Anche l'utilizzo di S.O. realtime a volte aiuta, a volte è ridondante a volte non praticabile.
Per quello che ho visto aiuta nel 99% dei casi. Nel restante è indispensabile. Ma questo è perché sono abituato oramai a lavorare in un certo modo.
Citazione:

Ritornando in topic: un approccio alla programmazione che usi pesantemente le risorse del processore diminuisce la portabilità ma di contro spesso aumenta le performance.
Ritornando in topic sarebbe buona norma lavorare a due livelli, parte alta dove hai il 90% del codice, il flusso dei vari programmi o task, scritta in c rigorosamente standard (e lo standard è K&R, non ANSI o cazzabubole varie), ed una parte bassa che è l'unica che deve avere accesso al silicio, scritta in c ma fortemente dipendente non solo dalla famiglia di micro, ma addirittura dal singolo componente della famiglia stessa.
In quest'ultima ci mettiamo dentro interrupt e quant'altro.
La comunicazione tra le due parti avviene attraverso a delle funzioni standard (magari anche stub).
ElNonnino, stasera ti mando un MP.

romoloman 03 luglio 12 22:11

Citazione:

Originalmente inviato da Davide B. (Messaggio 3283465)
Ritornando in topic sarebbe buona norma lavorare a due livelli, parte alta dove hai il 90% del codice, il flusso dei vari programmi o task, scritta in c rigorosamente standard (e lo standard è K&R, non ANSI o cazzabubole varie), ed una parte bassa che è l'unica che deve avere accesso al silicio, scritta in c ma fortemente dipendente non solo dalla famiglia di micro, ma addirittura dal singolo componente della famiglia stessa.
In quest'ultima ci mettiamo dentro interrupt e quant'altro.
La comunicazione tra le due parti avviene attraverso a delle funzioni standard (magari anche stub).
ElNonnino, stasera ti mando un MP.

Personalmente amo il C++ e la programmazione ad oggetti anche per i microprocessori. Sarà che in tal modo metto nelle classi la parte di basso livello a seconda del silicio uso quella che mi aggrada, ma i metodi pubblici sono sempre gli stessi e nascondo il basso livello dentro le classi.
Così come mi piacciono le struct per memorizzare i dati in modo da compattare al massimo l'uso della memoria... ma poter accedere alle variabili in modo leggibile da codice.
Ma qui si scende nella filosofia...

Davide B. 03 luglio 12 22:48

Romoloman... mi lasci basito!
Si che mi sembravi un tipo strano... ma il c++ anche nei microcontrollori...
Occhio che poi ti vengono delle strane deformazioni professionali.
Del tipo che quando ti dichiari ad una tipa ti dichiari come private object Romoloman...
Prometto di smetterla di dire ca....

romoloman 03 luglio 12 23:24

Citazione:

Originalmente inviato da Davide B. (Messaggio 3283809)
Romoloman... mi lasci basito!
Si che mi sembravi un tipo strano... ma il c++ anche nei microcontrollori...
Occhio che poi ti vengono delle strane deformazioni professionali.
Del tipo che quando ti dichiari ad una tipa ti dichiari come private object Romoloman...
Prometto di smetterla di dire ca....

Guardati il codice di open9x....
/ - open9x - Custom firmware for the Eurgle/FlySky/Imax/Turnigy 9x r/c Transmitter - Google Project Hosting

tre processori differenti... magari si può fare di meglio... ma sono contento di usare il c++


Tutti gli orari sono GMT +2. Adesso sono le 09:46.

Basato su: vBulletin versione 3.8.11
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
E' vietata la riproduzione, anche solo in parte, di contenuti e grafica. Copyright 1998/2019 - K-Bits P.I. 09395831002