Multitasking su microcontrollori PIC


17

Il multitasking è importante in questi giorni. Mi chiedo come possiamo raggiungerlo nei microcontrollori e nella programmazione integrata. Sto progettando un sistema basato su un microcontrollore PIC. Ho progettato il suo firmware in MplabX IDE usando C e quindi progettato un'applicazione per esso in Visual Studio usando C #.

Dato che mi sono abituato ad usare i thread nella programmazione C # sul desktop per implementare attività parallele, c'è un modo per fare lo stesso nel mio codice del microcontrollore? MplabX IDE fornisce pthreads.hma è solo uno stub senza implementazione. So che esiste il supporto FreeRTOS ma l'utilizzo di questo rende il tuo codice più complesso. Alcuni forum affermano che gli interrupt possono anche essere usati come multitasking ma non credo che gli interrupt siano equivalenti ai thread.

Sto progettando un sistema che invia alcuni dati a una UART e allo stesso tempo deve inviare i dati a un sito Web tramite Ethernet (cablata). Un utente può controllare l'uscita attraverso il sito Web ma l'uscita si accende / spegne con un ritardo di 2-3 sec. Questo è il problema che sto affrontando. Esiste una soluzione per il multitasking nei microcontrollori?


I thread possono essere utilizzati solo su processori che eseguono un sistema operativo, poiché i thread fanno parte del processo e i processi vengono utilizzati solo nei sistemi operativi.
TicTacToe,

@Zola si hai ragione. Ma cosa succede nel caso dei controller?
Aerei

2
Possibile duplicato di RTOS per sistemi integrati
Roger Rowland,

1
Puoi spiegare perché hai bisogno di un vero multitasking e non puoi ragionevolmente implementare il tuo software sulla base di un approccio di round robin o di un ciclo select () o simile?
whatsisname

2
Bene, come ho già detto, sto inviando e ricevendo dati su uart e allo stesso tempo inviando e ricevendo dati su Ethernet. Oltre a questo, devo anche salvare i dati nella scheda SD insieme al tempo, quindi sì, è coinvolto DS1307 RTC e anche EEPROM. Fino ad ora ho solo 1 UART ma dopo alcuni giorni invierò e riceverò dati da 3 moduli UART. Il sito Web riceverà anche i dati da 5 diversi sistemi installati in remoto. Tutto questo deve essere parallelo ma giusto non è parallelo ma con un ritardo di pochi secondi. !
Aerei

Risposte:


20

Esistono due tipi principali di sistemi operativi multitasking, preventivo e cooperativo. Entrambi consentono di definire più attività nel sistema, la differenza è come funziona il cambio di attività. Ovviamente con un singolo core-processor è effettivamente in esecuzione solo un'attività alla volta.

Entrambi i tipi di sistemi operativi multitasking richiedono uno stack separato per ogni attività. Quindi questo implica due cose: in primo luogo, che il processore consente di posizionare gli stack in qualsiasi punto della RAM e quindi ha le istruzioni per spostare il puntatore dello stack (SP) in giro, ovvero non esiste uno stack hardware per scopi speciali come quello presente nella fascia bassa PIC. Questo esclude le serie PIC10, 12 e 16.

È possibile scrivere un sistema operativo quasi interamente in C, ma il commutatore di attività, in cui si sposta SP, deve essere in assembly. In varie occasioni ho scritto commutatori di attività per PIC24, PIC32, 8051 e 80x86. Le viscere sono tutte abbastanza diverse a seconda dell'architettura del processore.

Il secondo requisito è che ci sia abbastanza RAM per fornire più stack. Di solito si vorrebbe almeno un paio di centinaia di byte per uno stack; ma anche a soli 128 byte per attività, otto stack richiedono 1K byte di RAM, tuttavia non è necessario allocare lo stesso stack di dimensioni per ogni attività. Ricorda che hai bisogno di uno stack sufficiente per gestire l'attività corrente e tutte le chiamate alle sue subroutine nidificate, ma impila anche lo spazio per una chiamata di interruzione poiché non sai mai quando si verificherà.

Esistono metodi abbastanza semplici per determinare la quantità di stack utilizzata per ciascuna attività; ad esempio è possibile inizializzare tutti gli stack su un valore particolare, ad esempio 0x55, ed eseguire il sistema per un po ', quindi arrestare ed esaminare la memoria.

Non dici che tipo di PIC vuoi usare. La maggior parte dei PIC24 e PIC32 avranno un sacco di spazio per eseguire un sistema operativo multitasking; il PIC18 (l'unico PIC a 8 bit con stack nella RAM) ha una dimensione RAM massima di 4K. Quindi è piuttosto incerto.

Con il multitasking cooperativo (il più semplice dei due), il cambio di attività viene eseguito solo quando l'attività "cede" il suo controllo al sistema operativo. Ciò accade ogni volta che l'attività deve chiamare una routine del sistema operativo per eseguire alcune funzioni che attenderanno, come una richiesta I / O o una chiamata timer. Questo rende più facile per il sistema operativo cambiare stack, poiché non è necessario salvare tutti i registri e le informazioni sullo stato, l'SP può essere semplicemente commutato su un'altra attività (se non ci sono altre attività pronte per essere eseguite, uno stack inattivo è dato controllo). Se l'attività corrente non deve effettuare una chiamata al sistema operativo ma è in esecuzione da un po ', deve rinunciare volontariamente al controllo per mantenere la risposta del sistema.

Il problema con il multitasking cooperativo è che se l'attività non abbandona mai il controllo, può ostacolare il sistema. Solo esso e tutte le routine di interruzione a cui viene dato il controllo possono essere eseguiti, quindi il sistema operativo sembrerà bloccarsi. Questo è l'aspetto "cooperativo" di questi sistemi. Se viene implementato un timer watchdog che viene ripristinato solo quando viene eseguita una commutazione di attività, è possibile rilevare queste attività errate.

Windows 3.1 e precedenti erano sistemi operativi cooperativi, motivo per cui le loro prestazioni non erano così grandi.

Il multitasking preventivo è più difficile da implementare. Qui, alle attività non è richiesto di rinunciare al controllo manualmente, ma invece a ciascuna attività può essere concesso un periodo di tempo massimo per l'esecuzione (diciamo 10 ms), quindi un passaggio di attività viene eseguito all'attività eseguibile successiva, se presente. Ciò richiede l'interruzione arbitraria di un'attività, il salvataggio di tutte le informazioni sullo stato, quindi il passaggio dell'SP a un'altra attività e l'avvio. Ciò rende il commutatore di attività più complicato, richiede più stack e rallenta un po 'il sistema.

Sia per il multitasking cooperativo che preventivo, possono verificarsi interruzioni in qualsiasi momento che impediranno temporaneamente l'attività in esecuzione.

Come sottolinea un supercat in un commento, un vantaggio offerto dal multitasking cooperativo è che è più facile condividere le risorse (ad es. Hardware come un ADC multicanale o software come la modifica di un elenco collegato). A volte due attività vogliono accedere alla stessa risorsa contemporaneamente. Con la pianificazione preventiva, sarebbe possibile per il sistema operativo cambiare attività nel mezzo di un'attività usando una risorsa. Pertanto sono necessari blocchi per impedire che un'altra attività entri e acceda alla stessa risorsa. Con il multitasking cooperativo, ciò non è necessario perché l'attività controlla quando lo rilascerà automaticamente sul sistema operativo.


3
Un vantaggio del multitasking cooperativo è che nella maggior parte dei casi non è necessario utilizzare i blocchi per coordinare l'accesso alle risorse. Sarà sufficiente garantire che i compiti lascino sempre le risorse in uno stato condivisibile ogni volta che rinunciano al controllo. Il multitasking preventivo è molto più complicato se un'attività può essere disattivata mentre contiene un blocco su una risorsa necessaria per un'altra attività. In alcuni casi, il secondo compito potrebbe finire per essere bloccato più a lungo di quanto non sarebbe stato in un sistema cooperativo, dal momento che il compito che aveva il blocco avrebbe
deviato

1
... risorse complete per completare l'azione che (sul sistema preventivo) avrebbe richiesto il blocco, rendendo così l'oggetto custodito disponibile per il secondo compito.
supercat,

1
Mentre i multitasking cooperativi richiedono disciplina, garantire che i requisiti di tempistica siano soddisfatti a volte può essere più facile in un multitasker cooperativo che in uno preventivo. Dato che pochissimi blocchi dovranno essere mantenuti su uno switch di attività, un sistema di switch di task round robin a cinque task in cui i task sono tenuti a non superare i 10ms senza cedimenti, combinato con una piccola logica che dice "Se il task X è urgente deve essere eseguito, eseguirlo in seguito ", assicurerà che l'attività X non debba mai attendere più di 10 ms una volta che segnala prima di iniziare. Al contrario, se un'attività richiede un blocco quale attività X ...
supercat

1
... sarà necessario ma viene disattivato da uno switcher preventivo prima di rilasciarlo, X potrebbe non fare nulla di utile fino a quando lo scheduler della CPU non inizia a eseguire la prima attività. A meno che lo scheduler non includa la logica per riconoscere e gestire l'inversione di priorità, potrebbe volerci un po 'di tempo prima che riesca a lasciare che la prima attività finisca la sua attività e rilasci il blocco. Tali problemi non sono irrisolvibili, ma risolverli richiede molta complessità che avrebbe potuto essere evitato in un sistema cooperativo. I sistemi cooperativi funzionano benissimo tranne un gotcha: ...
supercat

3
non hai bisogno di più stack in cooperativa se codifichi in continuazioni. In sostanza il tuo codice è diviso in funzioni che void foo(void* context)la logica del controller (kernel) estrae un puntatore e una coppia di puntatori di funzione della coda e lo chiama uno alla volta. Tale funzione utilizza il contesto per memorizzare le sue variabili e simili e può quindi aggiungere inviare una continuazione alla coda. Tali funzioni devono tornare rapidamente per consentire ad altre attività il loro momento nella CPU. Questo è un metodo basato su eventi che richiede solo un singolo stack.
maniaco del cricchetto

16

Il threading è fornito da un sistema operativo. Nel mondo embedded di solito non abbiamo un sistema operativo ("bare metal"). Quindi questo lascia le seguenti opzioni:

  • Il classico ciclo di polling principale. La tua funzione principale ha un po '(1) che fa il task 1 e poi il task 2 ...
  • Ciclo principale + flag ISR: si dispone di un ISR che svolge la funzione time-critical e quindi avvisa il ciclo principale tramite una variabile flag che l'attività necessita di assistenza. Forse l'ISR inserisce un nuovo carattere in un buffer circolare, quindi dice al ciclo principale di gestire i dati quando è pronto per farlo.
  • Tutti gli ISR: gran parte della logica qui viene eseguita dall'ISR. Su un controller moderno come un ARM che ha più livelli di priorità. Questo può fornire un potente schema "simile a thread", ma può anche essere fonte di confusione per il debug, quindi dovrebbe essere riservato solo per vincoli di temporizzazione critici.
  • RTOS: un kernel RTOS (facilitato da un ISR timer) può consentire il passaggio tra più thread di esecuzione. Hai citato FreeRTOS.

Vi consiglierei di utilizzare il più semplice degli schemi sopra che funzionerà per la vostra applicazione. Da quello che descrivi, vorrei che il ciclo principale generasse pacchetti e li posizionasse in buffer circolari. Quindi disporre di un driver UART basato su ISR che viene attivato ogni volta che il byte precedente viene inviato fino a quando non viene inviato il buffer, quindi attende più contenuto del buffer. Approccio simile per Ethernet.


3
Questa è una risposta molto utile perché affronta la radice del problema (come eseguire il multitasking su un piccolo sistema incorporato, anziché i thread come soluzione). Un paragrafo su come potrebbe applicarsi alla domanda originale sarebbe superbo, forse includendo i pro ei contro di ciascuno per lo scenario.
David,

8

Come in qualsiasi processore single-core, non è possibile eseguire il software multitasking reale. Quindi devi aver cura di alternare tra più attività in un modo. I diversi RTOS se ne stanno occupando. Hanno uno scheduler e sulla base di un tick di sistema passeranno da un'attività all'altra per darti una capacità multitasking.

I concetti coinvolti nel farlo (salvataggio e ripristino del contesto) sono piuttosto complicati, quindi farlo manualmente sarà probabilmente difficile e renderà il tuo codice più complesso e poiché non l'hai mai fatto prima, ci saranno errori. Il mio consiglio qui sarebbe di usare un RTOS testato proprio come FreeRTOS.

Hai detto che gli interrupt forniscono un livello di multitasking. Questo è un po 'vero. L'interrupt interromperà il programma corrente in qualsiasi momento ed eseguirà il codice lì, è paragonabile a un sistema a due task in cui hai 1 task con priorità bassa e un altro con priorità alta che termina all'interno di una fascia oraria dello scheduler.

Quindi potresti scrivere un gestore di interrupt per un timer ricorrente che invierà alcuni pacchetti su UART, quindi eseguiamo il resto del programma per alcuni millisecondi e invieremo i byte successivi. In questo modo ottieni una capacità multitasking limitata. Ma avrai anche un'interruzione piuttosto lunga che potrebbe essere una brutta cosa.

L'unico modo reale per eseguire più attività contemporaneamente su una MCU single-core è utilizzare il DMA e le periferiche poiché lavorano indipendentemente dal core (DMA e MCU condividono lo stesso bus, quindi funzionano un po 'più lentamente quando entrambi sono attivi). Quindi, mentre il DMA sta mescolando i byte su UART, il tuo core è libero di inviare il materiale su Ethernet.


2
grazie, DMA sembra interessante. Lo cercherò sicuramente.!
Aerei

Non tutte le serie di PIC hanno DMA.
Matt Young,

1
Sto usando PIC32;)
Aerei

6

Le altre risposte hanno già descritto le opzioni più utilizzate (loop principale, ISR, RTOS). Ecco un'altra opzione come compromesso: Protothreads . Fondamentalmente è una lib molto leggera per i thread, che utilizza il ciclo principale e alcune macro C, per "emulare" un RTOS. Ovviamente non è un sistema operativo completo, ma per thread "semplici" può essere utile.


da dove posso scaricare il suo codice sorgente per Windows? Penso che sia disponibile solo per Linux.!
Aerei

@CZAbhinav Dovrebbe essere indipendente dal sistema operativo e puoi scaricare qui l'ultimo download .
Erebos,

Sono in Windows in questo momento e sto usando MplabX, non credo sia utile qui. Comunque grazie.!
Aerei

Non ho sentito parlare di protothreads, sembra una tecnica interessante.
Arsenal,

@CZAbhinav Di cosa stai parlando? È il codice C e non ha nulla a che fare con il tuo sistema operativo.
Matt Young,

3

Il mio design di base per un RTOS ridotto nel tempo non è cambiato molto in diverse micro famiglie. Fondamentalmente è un interruzione del timer che guida una macchina a stati. La routine del servizio di interrupt è il kernel del sistema operativo mentre l'istruzione switch nel ciclo principale è l'attività dell'utente. I driver di dispositivo sono routine di servizio di interrupt per interruzioni I / O.

La struttura di base è la seguente:

unsigned char tick;

void interrupt HANDLER(void) {
    device_driver_A();
    device_driver_B();
    if(T0IF)
    {
        TMR0 = TICK_1MS;
        T0IF = 0;   // reset timer interrupt
        tick ++;
    }
}

void main(void)
{
    init();

    while (1) {
        // periodic tasks:
        if (tick % 10 == 0) { // roughly every 10 ms
            task_A();
            task_B();    
        }
        if (tick % 55 == 0) { // roughly every 55 ms
            task_C();
            task_D();    
        }

        // tasks that need to run every loop:
        task_E();
        task_F();
    }
}

Questo è fondamentalmente un sistema multitasking cooperativo. Le attività sono scritte per non entrare mai in un ciclo infinito, ma non ci interessa perché le attività vengono eseguite all'interno di un ciclo di eventi, quindi il ciclo infinito è implicito. Questo è uno stile di programmazione simile a linguaggi orientati agli eventi / non bloccanti come javascript o go.

Puoi vedere un esempio di questo stile di architettura nel mio software di trasmettitore RC (sì, lo uso effettivamente per pilotare aeroplani RC, quindi è un po 'critico per la sicurezza per impedirmi di schiantare i miei aerei e potenzialmente uccidere persone): https://github.com / slebetman / pic-txmod . Ha fondamentalmente 3 attività: 2 attività in tempo reale implementate come driver di dispositivo con stato (vedere roba ppmio) e 1 attività in background che implementa la logica di missaggio. Quindi sostanzialmente è simile al tuo server web in quanto ha 2 thread I / O.


1
Non chiamerei davvero quel "multitasking cooperativo", dato che non è sostanzialmente diverso da qualsiasi altro programma di microcontrollore che deve fare più cose.
whatsisname

2

Mentre apprezzo il fatto che la domanda ponga specificamente sull'uso di un RTOS incorporato, mi viene in mente che la domanda più ampia che si pone è "come realizzare il multitasking su una piattaforma integrata".

Ti consiglio vivamente di dimenticare di utilizzare un RTOS incorporato almeno per il momento. Lo consiglio perché penso che sia essenziale imparare prima come ottenere la 'concorrenza' delle attività mediante tecniche di programmazione estremamente semplici costituite da semplici programmatori di attività e macchine a stati.

Per spiegare in modo estremamente breve il concetto, ogni modulo di lavoro che deve essere svolto (cioè ogni "compito") ha una funzione particolare che deve essere chiamata ("spuntata") periodicamente affinché quel modulo faccia qualcosa. Il modulo mantiene il proprio stato corrente. Quindi hai un ciclo infinito principale (lo scheduler) che chiama le funzioni del modulo.

Illustrazione grezza:

for(;;)
{
    main_lcd_ui_tick();
    networking_tick();
}


...

// In your LCD UI module:
void main_lcd_ui_tick(void)
{
    check_for_key_presses();
    update_lcd();
}

...

// In your networking module:
void networking_tick(void)
{
    //'Tick' the TCP/IP library. In this example, I'm periodically
    //calling the main function for Keil's TCP/IP library.
    main_TcpNet();
}

Una struttura di programmazione a thread singolo come questa, in base alla quale chiamate periodicamente le funzioni principali della macchina a stati da un loop dello scheduler principale è onnipresente nella programmazione incorporata, ed è per questo che incoraggio fortemente l'OP a familiarizzarsi con esso prima di immergersi direttamente nell'uso Attività / thread RTOS.

Lavoro su un tipo di dispositivo incorporato con interfaccia LCD hardware, web server interno, client di posta elettronica, client DDNS, VOIP e molte altre funzionalità. Sebbene utilizziamo un RTOS (Keil RTX), il numero di singoli thread (attività) utilizzati è molto ridotto e la maggior parte del "multitasking" viene raggiunto come descritto sopra.

Per dare un paio di esempi di librerie che dimostrano questo concetto:

  1. La libreria di rete Keil. L'intero stack TCP / IP può essere eseguito a thread singolo; chiami periodicamente main_TcpNet (), che itera lo stack TCP / IP e qualsiasi altra opzione di rete che hai compilato dalla libreria (ad esempio il web server). Vedi http://www.keil.com/support/man/docs/rlarm/rlarm_main_tcpnet.htm . Certo, in alcune situazioni (possibilmente al di fuori dell'ambito di questa risposta) si raggiunge un punto in cui inizia a diventare utile o necessario utilizzare i thread (in particolare se si utilizzano socket BSD bloccanti). (Nota ulteriore: il nuovo V5 MDK-ARM in realtà genera un thread Ethernet dedicato, ma sto solo cercando di fornire un'illustrazione.)

  2. La libreria VOIP di Linphone. La libreria Linphone stessa è a thread singolo. Si chiama la iterate()funzione a un intervallo sufficiente. Vedi http://www.linphone.org/docs/liblinphone-javadoc/org/linphone/core/LinphoneCore.html#iterate () . (Un po 'di scarso esempio perché l'ho usato su una piattaforma Linux incorporata e le librerie di dipendenze di linphone indubbiamente generano thread, ma ancora una volta è per illustrare un punto.)

Tornando al problema specifico delineato dall'OP, il problema sembra essere il fatto che la comunicazione UART deve avvenire contemporaneamente ad alcune reti (trasmissione di pacchetti via TCP / IP). Non so quale libreria di rete stai effettivamente utilizzando, ma suppongo che abbia una funzione principale che deve essere chiamata frequentemente. Dovresti scrivere il tuo codice che si occupa della trasmissione / ricezione dei dati UART per essere strutturato in modo simile, come una macchina a stati che può essere ripetuta da chiamate periodiche a una funzione principale.


2
Grazie per questa bella spiegazione, sto usando la libreria TCP / IP fornita da microchip ed è un codice complesso molto grande. In qualche modo sono riuscito a romperlo in parti e renderlo utilizzabile in base alle mie esigenze. Proverò sicuramente uno dei tuoi approcci.!
Aerei

Buon divertimento :) L'uso di un RTOS semplifica decisamente la vita in molte situazioni. A mio avviso, l'uso di un thread (attività) rende lo sforzo di programmazione molto più semplice in un certo senso, dal momento che puoi evitare di dover suddividere l'attività in una macchina a stati. Invece, scrivi semplicemente il tuo codice di attività proprio come faresti nei tuoi programmi C #, con il tuo codice di attività creato come se fosse l'unica cosa che esiste. È essenziale esplorare entrambi gli approcci e, man mano che si fa più programmazione integrata, si inizia a capire quale approccio è il migliore in ogni situazione.
Trevor Pagina

Preferisco anche usare l'opzione di threading. :)
Aerei
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.