PIC32 vs dsPIC vs ARM vs AVR, l'architettura è importante quando programmiamo comunque in linguaggio C? [chiuso]


10

Attualmente stiamo utilizzando un microcontrollore PIC32 a 32 bit. Funziona benissimo per le nostre esigenze, ma stiamo anche esplorando altri microcontoller che possono adattarci meglio + abbiamo altri progetti per i quali stiamo selezionando MCU. A tale scopo abbiamo selezionato un microcontoller SAM DA basato su ARM che è lo stesso a 32 bit ma è basato su ARM (più popolare di PIC32 - per quanto riguarda l'industria).

Ora per PIC32 utilizziamo MPLAB ma per ARM cortx-M0, utilizzeremo Atmel Studio. Useremo il linguaggio C in entrambe le piattaforme. La cosa che mi preoccupa è che useremo due microcontoller a 32 bit (della stessa azienda) ma con architetture diverse. Ciò richiederà di apprendere due diversi dispositivi e aumenterà la nostra "curva di apprendimento" + i tempi di consegna. D'altra parte, penso anche che, poiché in entrambi i casi utilizzeremo il linguaggio C, la curva di apprendimento per ARM non dovrebbe essere così ascoltata e vale la pena esplorare anche quel processore.

La mia domanda principale è: quanta differenza fa l'architettura quando stiamo programmando in linguaggio C poiché fornisce un'astrazione degli interni del micrcontrollore. E quali sono le principali differenze in MPLAP e Atmel Studio , considerando la programmazione in linguaggio C.


2
Se le cose funzionano con PIC32, qual è il punto di passaggio? Anche se il codice porta completamente (non lo farà), c'è ancora la nuova catena di strumenti e l'IDE a cui abituarsi. Qual e il punto? Cambiare per motivi religiosi o essere "ARM based" (o qualsiasi altra cosa basata) è sciocco. Devi avere una buona ragione, ma non ce ne hai mostrato nessuno.
Olin Lathrop,

Non ho chiesto informazioni sul passaggio. Ho parlato della scelta di un'architettura diversa per altri progetti mentre stiamo lavorando su più progetti + c'è spazio per migliorare il nostro design esistente. Il punto principale riguardava la curva di apprendimento e le sfide nel lavorare contemporaneamente con due diverse architetture.
ingegnere

Una cosa che ho scoperto è che Atmel Studio offre un tempismo superiore rispetto al video di YouTube
ingegnere

Risposte:


20

Questo è un argomento piuttosto supponente. Posso parlare da solo (AVR, ARM, MSP430).

La differenza 1 (più significativa) è nelle periferiche. Ciascuna MCU ha UART, SPI, timer simili, ecc. - Solo i nomi dei registri e i bit sono diversi. Il più delle volte è stato il problema principale che ho dovuto affrontare quando ho spostato il codice tra i chip. Soluzione: scrivere i driver con un'API comune, in modo che l'applicazione possa essere portatile.

La differenza 2 è l'architettura della memoria. Se si desidera posizionare le costanti in flash su un AVR, è necessario utilizzare attributi speciali e funzioni speciali per leggerle. Nel mondo ARM è sufficiente dereferenziare un puntatore perché esiste un unico spazio di indirizzi (non so quanto piccoli PIC lo gestiscano, ma suppongo che siano più vicini ad AVR).

La differenza 3 è la dichiarazione e la gestione degli interrupt. avr-gccha la ISR()macro. ARM ha solo un nome di funzione (come someUART_Handler () - se si utilizzano le intestazioni CMSIS e il codice di avvio). I vettori di interrupt ARM possono essere posizionati ovunque (compresa la RAM) e modificati in fase di esecuzione (molto utile se si dispone ad esempio di due protocolli UART diversi che possono essere commutati). AVR ha solo la possibilità di utilizzare i vettori in "flash principale" o nella "sezione bootloader" (quindi se si desidera gestire gli interrupt in modo diverso è necessario utilizzare ifun'istruzione).

Differenza 4: modalità di sospensione e controllo della potenza. Se è necessario un consumo di energia minimo, è necessario levare tutte le funzionalità dell'MCU. Questo può differire molto tra MCU - alcuni hanno modalità di risparmio energetico più grossolane, alcuni possono abilitare / disabilitare singole periferiche. Alcuni MCU hanno regolatori regolabili in modo da poterli eseguire con una tensione inferiore a una velocità più bassa, ecc. Non vedo un modo semplice per ottenere la stessa efficienza su un MCU (diciamo) con 3 modalità di alimentazione globali e un altro con 7 modalità di alimentazione e controllo individuale dell'orologio periferico.

La cosa più importante per la portabilità è dividere chiaramente il codice in parti dipendenti dall'hardware (driver) e indipendenti dall'applicazione (applicazione). È possibile sviluppare e testare quest'ultimo su un normale PC con un driver simulato (ad es. Console anziché UART). Questo mi ha salvato molte volte poiché il 90% del codice dell'applicazione era completo prima che l'hardware prototipo uscisse dal forno di riflusso :)

A mio avviso, la cosa positiva di ARM è la "monocultura" - disponibilità di molti compilatori (gcc, Keil, IAR ... solo per citarne alcuni), molti IDE gratuiti e ufficialmente supportati (almeno per NXP, STM32, Silicon Labs, Nordic), molti strumenti di debug (SEGGER - in particolare Ozone, ULINK, OpenOCD ...) e molti produttori di chip (non inizierò nemmeno a nominarli). PIC32 è principalmente limitato a Microchip (ma è importante solo se non ti piacciono i loro strumenti.

Quando si tratta di codice C. È lo stesso al 99%, ifun'istruzione è la stessa, un ciclo funziona allo stesso modo. Tuttavia, dovresti preoccuparti della dimensione della parola nativa. Ad esempio, un forloop su un AVR è più veloce se si utilizza uint8_tper il contatore, mentre su ARM uint32_tè il tipo più veloce (o int32_t). ARM dovrebbe verificare ogni volta l'overflow di 8 bit se si utilizzava un tipo più piccolo.

La selezione di un MCU e / o di un fornitore in generale riguarda principalmente politica e logistica (a meno che non si abbiano vincoli ingegneristici molto chiari, ad esempio: alta temperatura - utilizzare MSP430 o Vorago). Anche se l'applicazione può essere eseguita su qualsiasi cosa e solo il 5% del codice (driver) deve essere sviluppato e supportato per tutta la durata del prodotto , è comunque un costo aggiuntivo per l'azienda. Tutti i posti in cui ho lavorato avevano un fornitore preferito e una linea MCU (come "scegli qualsiasi Kinetis che desideri a meno che non ci sia un ottimo motivo per scegliere qualcosa di diverso"). Aiuta anche se hai altre persone a chiedere aiuto, quindi come manager eviterei di avere un reparto di sviluppo di 5 persone in cui tutti usavano un chip completamente diverso.


3
“AVR è più veloce se usi uint8_t per il contatore, mentre su ARM uint32_t è il tipo più veloce (o int32_t). ARM dovrebbe verificare ogni volta l'overflow di 8 bit se si utilizzava un tipo più piccolo. " puoi usare uint_fast8_t se hai bisogno di almeno 8 bit.
Michael,

@Michael - sicuro di poter usare i tipi _fast, ma non puoi contare sul comportamento di overflow. Nel mio gcc's stdint.h ho "typedef unsigned int uint_fast8_t", che in pratica è un uint32_t :)
filo

Cercare di scrivere un'API che sia efficiente, universale e completa è difficile dato che piattaforme diverse hanno abilità diverse. Probabilmente la CPU conta meno delle periferiche e delle decisioni di progettazione prese con esse. Ad esempio, alcuni dispositivi hanno consentito di riconfigurare varie periferiche in qualsiasi momento al massimo in pochi microsecondi, mentre altri potrebbero richiedere più passaggi distribuiti su centinaia di microsecondi o addirittura millisecondi. Una funzione API destinata al modello precedente può essere utilizzabile all'interno di una routine di servizio di interruzione che funziona a 10.000Hz, ma ...
supercat

... non è in grado di supportare tale utilizzo su piattaforme che richiederebbero la diffusione di operazioni su centinaia di microsecondi. Non so perché i progettisti hardware non sembrano impegnarsi a fondo per supportare la semantica dell'API "operazioni rapide in qualsiasi momento", ma molti usano un modello che sincronizza le singole operazioni anziché dichiarare in modo che se, ad esempio, è stata data una richiesta a accendi un dispositivo e il codice si rende conto che non deve essere acceso, il codice deve attendere l'accensione del dispositivo prima che possa inviare la richiesta di spegnimento. La gestione senza problemi in un'API aggiunge ulteriori complicazioni.
supercat

11

Ho usato diversi MCU di quattro diversi produttori. Il lavoro principale ogni volta è di familiarizzare con le periferiche.

Ad esempio un UART in sé non è troppo complesso e trovo facilmente la porta dei miei driver. Ma l'ultima volta mi ci è voluto quasi un giorno per far sì che gli orologi, i pin I / O si interrompessero, abilitassero ecc. Risolti.

Il GPIO può essere molto complesso. Bit-set, bit-clear, bit-toggle, abilitazione / disabilitazione funzioni speciali, tri-state. Successivamente si ottengono interruzioni: qualsiasi fronte, salita, discesa, livello basso, livello alto, auto-pulizia o no.

Poi ci sono I2C, SPI, PWM, Timer e due dozzine di tipi di periferiche ciascuno con il proprio clock abilitato e ogni volta che i registri sono diversi con nuovi bit. Per tutti questi ci vogliono molte ore a leggere il foglio dati su come impostare quale bit in quali circostanze.

L'ultimo produttore aveva un sacco di esempio di codice che ho trovato inutilizzabile. Tutto è stato astratto. Ma quando l'ho rintracciato, il codice è passato attraverso sei! livelli di chiamate di funzione per impostare un bit GPIO. Bello se hai un processore 3GHz ma non su un MCU di 48MHz. Il mio codice alla fine era una riga singola:

GPIO->set_output = bit.

Ho provato a usare driver più generici ma ho rinunciato. Su un MCU sei sempre alle prese con i cicli di spazio e clock. Ho scoperto che il livello di astrazione è il primo ad uscire dalla finestra se si genera una forma d'onda specifica in una routine di interruzione chiamata a 10KHz.

Quindi ora ho tutto funzionante e non ho intenzione di cambiare di nuovo se non per una ragione molto, molto buona.

Tutto quanto sopra deve essere ammortizzato su quanti prodotti vendi e cosa risparmi. Vendere un milione: risparmiando 0,10 per passare a un altro tipo, puoi spendere 100.000 in ore di lavoro del software. Vendendo 1000 hai solo 100 da spendere.


1
Personalmente questo è il motivo per cui mi attengo all'assemblatore. Bel binario, nessuna astrazione.
Ian Bland,

Il preprocessore di C può fare abbastanza bene con le cose, specialmente se combinato con intrinseci __builtin_constant. Se si definiscono le costanti per ciascun bit I / O del modulo (numero di porta * 32 + numero di bit), è possibile scrivere una macro per la OUTPUT_HI(n)quale si otterrà un codice equivalente a GPIOD->bssr |= 0x400;se nè una costante come 0x6A, ma chiamare una subroutine semplice se nè non costante. Detto questo, la maggior parte delle API dei fornitori che ho visto vanno da mediocre a orribile.
supercat

8

Questa è più un'opinione / commento che una risposta.

Non vuoi e non dovresti programmare in C. C ++, se usato nel modo giusto , è di gran lunga superiore. (OK, devo ammetterlo, se usato nel modo sbagliato è molto peggio di C.) Questo ti limita ai chip che hanno un compilatore C ++ (moderno), che è praticamente tutto ciò che è supportato da GCC, incluso AVR (con alcune limitazioni, filo menziona i problemi di uno spazio di indirizzi non uniforme), ma escludendo quasi tutti i PIC (potrebbe essere supportato PIC32, ma non ho ancora visto nessuna porta decente).

Quando si programmano algoritmi in C / C ++, la differenza tra le scelte che si menziona è piccola (tranne per il fatto che un chip a 8 o 16 bit sarà in grave svantaggio quando si eseguono molti aritmetici a 16, 32 o più bit). Quando hai bisogno dell'ultima oncia di prestazione, probabilmente dovrai usare l'assemblatore (il tuo o il codice fornito dal venditore o da una terza parte). In tal caso, potresti voler riconsiderare il chip selezionato.

Quando si codifica l'hardware è possibile utilizzare un livello di astrazione (spesso fornito dal produttore) o scrivere il proprio (basato sul foglio dati e / o sul codice di esempio). Le astrazioni C esistenti dell'IME (mbed, cmsis, ...) sono spesso funzionalmente (quasi) corrette, ma falliscono in modo orribile nelle prestazioni (controlla che oldfarts abbia circa 6 livelli di riferimento indiretto per un'operazione di set di pin), usabilità e portabilità. Vogliono esporre tutte le funzionalità di quel particolare chip a te, che in quasi tutti i casi non ti serviranno e preferiresti non preoccuparti, e blocca il tuo codice a quel particolare fornitore (e probabilmente quel particolare chip).

Questo è dove C ++ può fare molto meglio: se fatto correttamente, un set di pin può passare attraverso 6 o più livelli di astrazione (perché ciò rende possibile un'interfaccia migliore (portatile!) E un codice più breve), ma fornisce un'interfaccia indipendente dal target per i casi semplici , e risulta comunque nello stesso codice macchina che scriveresti in assemblatore .

Un frammento dello stile di codifica che uso, che può renderti entusiasta o allontanarti dall'orrore:

// GPIO part of a HAL for atsam3xa
enum class _port { a = 0x400E0E00U, . . . };

template< _port P, uint32_t pin >
struct _pin_in_out_base : _pin_in_out_root {

   static void direction_set_direct( pin_direction d ){
      ( ( d == pin_direction::input )
         ? ((Pio*)P)->PIO_ODR : ((Pio*)P)->PIO_OER )  = ( 0x1U << pin );
   }

   static void set_direct( bool v ){
      ( v ? ((Pio*)P)->PIO_SODR : ((Pio*)P)->PIO_CODR )  = ( 0x1U << pin );    
   }
};

// a general GPIO needs some boilerplate functionality
template< _port P, uint32_t pin >
using _pin_in_out = _box_creator< _pin_in_out_base< P, pin > >;

// an Arduino Due has an on-board led, and (suppose) it is active low
using _led = _pin_in_out< _port::b, 27 >;
using led  = invert< pin_out< _led > >;

In realtà ci sono altri strati di astrazione. Tuttavia l'uso finale del led, diciamo per accenderlo, non mostra la complessità o i dettagli del target (per un arduin uno o una pillola blu ST32 il codice sarebbe identico).

target::led::init();
target::led::set( 1 );

Il compilatore non è intimidito da tutti quei livelli, e poiché non ci sono funzioni virtuali coinvolte, l'ottimizzatore vede attraverso tutto (alcuni dettagli, omessi, come abilitare il clock periferico):

 mov.w  r2, #134217728  ; 0x8000000
 ldr    r3, [pc, #24]   
 str    r2, [r3, #16]
 str    r2, [r3, #48]   

Ecco come l'avrei scritto in assembler - SE mi fossi reso conto che i registri PIO possono essere usati con offset da una base comune. In questo caso probabilmente lo farei, ma il compilatore è molto meglio nell'ottimizzare tali cose di me.

Quindi, per quanto ho una risposta, è: scrivere un livello di astrazione per il tuo hardware, ma farlo nel moderno C ++ (concetti, modelli) in modo che non danneggi le tue prestazioni. Con quello in atto, puoi passare facilmente a un altro chip. Puoi persino iniziare a sviluppare su alcuni chip casuali che hai in giro, avere familiarità, avere buoni strumenti di debug per, ecc. E rimandare la scelta finale a più tardi (quando hai più informazioni sulla memoria richiesta, sulla velocità della CPU ecc.).

Una delle menzogne ​​dello sviluppo integrato dell'IMO è scegliere prima il chip (è una domanda spesso posta su questo forum: quale chip dovrei scegliere per ... La migliore risposta è generalmente: non importa.)

(modifica - risposta a "Quindi per quanto riguarda le prestazioni, C o C ++ sarebbero allo stesso livello?")

Per gli stessi costrutti, C e C ++ sono gli stessi. Il C ++ ha molti più costrutti per l'astrazione (solo alcuni: classi, template, constexpr) che possono, come qualsiasi strumento, essere usati per il bene o per il male. Per rendere le discussioni più interessanti: non tutti sono d'accordo su ciò che è buono o cattivo ...


Quindi, dal punto di vista delle prestazioni, C o C ++ sarebbero allo stesso livello? Penso che il C ++ avrà più sovraccarico. Sicuramente mi hai indicato la giusta direzione, C ++ è la strada da percorrere non C.
ingegnere

I modelli C ++ impongono il polimorfismo in fase di compilazione che può essere a costo zero (o addirittura negativo) in termini di prestazioni, poiché il codice viene compilato per ogni caso d'uso specifico. Tuttavia, ciò tende a prestarsi meglio alla velocità di targeting (O3 per GCC). Il polimorfismo run-time, come le funzioni virtuali, può subire una penalità molto maggiore, sebbene sia più facile da mantenere e in alcuni casi abbastanza buono.
Hans,

1
Sostieni che C ++ è migliore, ma poi vai e usi i cast in stile C. Per vergogna.
JAB il

@JAB Non ho mai provato molto per i cast di nuovo stile, ma ci proverò. Ma la mia attuale priorità è su altre parti di questa biblioteca. Il vero problema è ovviamente che non sono riuscito a passare i puntatori come parametri del modello.
Wouter van Ooijen,

@Il mio stile cto (Compile Time Objects) ha un caso d'uso piuttosto stretto (vicino all'hardware, situazione nota in fase di compilazione), è più un C-killer che un rimpiazzo per usi trandizionali di OO basato su virtuale. Una cattura accidentale utile è che l'assenza di riferimenti indiretti consente di calcolare le dimensioni dello stack.
Wouter van Ooijen,

4

Se ho capito bene, vuoi sapere quali caratteristiche specifiche dell'architettura della piattaforma "pop-up" nel tuo ambiente in linguaggio C, rendendo più difficile scrivere codice portatile e sostenibile su entrambe le piattaforme.

C è già abbastanza flessibile in quanto è un "assemblatore portatile". Tutte le piattaforme che hai selezionato hanno compilatori GCC / commerciali disponibili che supportano gli standard linguistici C89 e C99, il che significa che puoi eseguire codice simile su tutte le piattaforme.

Vi sono alcune considerazioni:

  • Alcune architetture sono Von Neumann (ARM, MIPS), altre sono Harvard. Le principali limitazioni sorgono quando il programma C deve leggere i dati dalla ROM, ad esempio per stampare stringhe, avere dati definiti come "const" o simili.

Alcune piattaforme / compilatori possono nascondere questa "limitazione" meglio di altre. Ad esempio su AVR è necessario utilizzare macro specifiche per leggere i dati ROM. Su PIC24 / dsPIC sono disponibili anche le istruzioni dedicate per il primo. Tuttavia, in alcune parti è disponibile anche la funzione "Visibilità dello spazio programmi" (PSVPAG) che consente di mappare una pagina del FLASH nella RAM, rendendo disponibile l'indirizzamento immediato dei dati senza tblrd. Il compilatore può farlo in modo abbastanza efficace.

ARM e MIPS sono Von Neumann, quindi dispongono di aree di memoria per ROM, RAM e periferiche impacchettate su 1 bus. Non noterai alcuna differenza tra la lettura dei dati dalla RAM o "ROM".

  • Se ci si immerge al di sotto di C e si osservano le istruzioni generate per determinate operazioni, si riscontrano grandi differenze in termini di I / O. ARM e MIPS sono architetture di registro del magazzino di carico RISC . Ciò significa che l'accesso ai dati sul bus di memoria deve passare attraverso le istruzioni MOV. Ciò significa anche che qualsiasi modifica di un valore periferico porterà a un'operazione di lettura-modifica-scrittura (RMW). Esistono alcune parti ARM che supportano Bit-Banding, che mappano i registri set / clr-bit nello spazio periferico I / O. Tuttavia è necessario codificare questo accesso da soli.

D'altra parte un PIC24 consente alle operazioni ALU di leggere e scrivere i dati direttamente tramite indirizzamento indiretto (anche con modifiche al puntatore ..). Questo ha alcune caratteristiche di un'architettura come CISC, quindi 1 istruzione può fare più lavoro. Questo design può portare a core della CPU più complessi, clock inferiori, maggiore consumo di energia, ecc. Fortunatamente per te la parte è già progettata. ;-)

Queste differenze possono significare che un PIC24 può essere operazioni di I / O wrt "più incisive" rispetto a un chip ARM o MIPS con clock simile. Tuttavia, è possibile ottenere una parte ARM / MIPS del clocker molto più alta per gli stessi vincoli di prezzo / pacchetto / progettazione. Immagino che per termini pratici, penso che un sacco di "apprendimento della piattaforma" sta prendendo in considerazione ciò che l'architettura può e non può fare, quanto velocemente sarà un insieme di operazioni, ecc.

  • Le periferiche, la gestione dell'orologio, ecc. Differiscono per famiglia di componenti. A rigor di termini, questo cambierà anche all'interno dell'ecosistema ARM tra i fornitori, ad eccezione di alcune periferiche legate a Cortex m come NVIC e SysTick.

Queste differenze possono essere incapsulate in qualche modo dai driver di dispositivo, ma alla fine il firmware incorporato ha un alto livello di accoppiamento con l'hardware, quindi a volte non è possibile evitare il lavoro personalizzato.

Inoltre, se si lasciano gli ecosistemi di Microchip / ex Atmel, è possibile che le parti ARM richiedano una maggiore configurazione per farli funzionare. Intendo in termini di; abilitare gli orologi alle periferiche, quindi configurare le periferiche e "abilitarle", impostare NVIC separatamente, ecc. Questa è solo una parte della curva di apprendimento. Una volta che ti ricordi di fare tutte queste cose, nell'ordine giusto, scrivere driver di dispositivo per tutti questi microcontrollori ti sembrerà abbastanza simile ad un certo punto.

  • Inoltre, prova a utilizzare librerie come stdint.h, stdbool.h, ecc. Se non lo sei già. Questi tipi interi rendono esplicite le larghezze, il che rende il comportamento del codice più prevedibile tra le piattaforme. Ciò può significare l'uso di numeri interi a 32 bit su un AVR a 8 bit; ma se il tuo codice ne ha bisogno, così sia.

3

Sì e no. Dal punto di vista dei programmatori stai nascondendo idealmente i dettagli del set di istruzioni. Ma questo in qualche misura non è già rilevante per le periferiche che è l'intero punto di scrivere il programma non fanno parte del set di istruzioni. Allo stesso tempo, non è possibile confrontare solo parti del flash 4096Byte tra questi set di istruzioni, in particolare se si utilizza C, la quantità di consumo del flash / memoria è fortemente determinata dal set di istruzioni e dal compilatore, alcuni non dovrebbero mai vedere un compilatore (PIC tosse tosse) a causa di quanti sprechi di tali risorse vengono consumati dalla compilazione. Altri consumi di flash sono un sovraccarico minore. Le prestazioni sono anche un problema quando si utilizza un linguaggio di alto livello e le prestazioni sono importanti nelle applicazioni MCU, quindi può fare la differenza tra spendere $ 3 per scheda per l'MCU o $ 1.

Se si tratta di semplificare la programmazione (al costo complessivo del prodotto) dovresti essere in grado di scaricare un pacchetto di sviluppatori per la MCU in modo tale che l'architettura del set di istruzioni sia qualcosa che non vedi mai, quindi se questa è la tua preoccupazione principale, non è un problema. Ti costa ancora denaro per quanto riguarda il costo del prodotto per utilizzare queste librerie, ma, il time to market potrebbe essere più breve, trovo che le librerie impieghino più tempo / lavoro da usare rispetto a parlare direttamente con le periferiche.

In conclusione, i set di istruzioni sono l'ultima delle tue preoccupazioni, passa a problemi reali.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.