Oppure

Loading
29/06/17 12:18
TheDarkJuster
Buongiorno a tutti, mi sto cimentando nella scritturar di un termostato con GSM. L'hardware è abbastanza semplice: 1 arduino, 1 shield gsm, qualche termometro e il necessario per gestire un buon relè.

Visto che volevo sperimentare con FreeRTOS ho deciso di usare il porting per arduino. Una di scelta di cui non mi pento, anche se mi ha dato diversi grattacapi: in primis succedevano veri e propri disastri con l'allocazione dinamica della memoria! Ho quindi dovuto disabilitarla, abilitare l'uso di memoria preallocata per tutte le strutture del SO ed evitare in tutti i modi di allocare nuova memoria, visto che il risultato di tale allocazione è un terno al lotto........

Trovate la versione modificata da me di FreeRTOS qui: github.com/NeroReflex/… prestate particolarmente attenzione a questo file scritto da me: github.com/NeroReflex/ArduinoRTOS/blob/master/src/… che è una raccolta di macro.

Ho poi importato FreeRTOS nel mio progetto arduino, che si compone. per il momento, di 2 task:

Il primo è quello che legge la temperatura attraverso un termometro e decide cosa farne:

#define pdMSTOTICKS( xTimeInMs ) ( (xTimeInMs / portTICK_PERIOD_MS) + 1 )

#ifdef SERIAL_DBG
    static struct BoilerRegulatorDebugInfo debugInfo;

    aImportStaticQueue(temperatureDebug);
#endif

aImportStaticSemaphore(temperaturePoll);

// ---------------------------------------------------
// -------------- Static variables -------------------
// ---------------------------------------------------
static Logic logic;                       // boiler management logic
static float oneWire_Temp = 127.5f;       // last read temperature
static bool boilerOn = false;             // the boiler status
TickType_t xLastWakeTime_BoilerRegulator; // used for the final delay
static uint8_t deviceCount = 0;           // the number of ds18x20 devices
static bool validMeasure = false;         // is the last measure valid?
extern struct StatusPoll lastMeasure;

// ---------------------------------------------------
// ------------- Most important Task -----------------
// ---------------------------------------------------
void TaskBoilerRegulator(void *pvParameters __attribute__((unused))) {
#ifdef SERIAL_DBG
    logic.setThresholdTemperature(DBG_TEMPERATURE);
    logic.setHysteresis(DBG_HYSTERESIS);
#endif

    // Set the heater pin
    pinMode(BOILER_DIGITAL_PIN, OUTPUT);
    digitalWrite(BOILER_DIGITAL_PIN, LOW);

    // setup thermometers
    OneWire ow(ONEWIRE_DIGITAL_PIN);
    DallasTemperature sensors(&ow);

    // This one is used for the good delay
    xLastWakeTime_BoilerRegulator = xTaskGetTickCount();

    for (;;) {
        // A few seconds delay to let other tasks run
        vTaskDelayUntil(&xLastWakeTime_BoilerRegulator, pdMSTOTICKS( 5000 ));

        sensors.begin(); // IC Default 9 bit. If you have troubles consider upping it 12. Ups the delay giving the IC more time to process the temperature measurement
        sensors.setResolution(DS18X20_PRECISION_BITS);

        // Get the number of devices
        deviceCount = sensors.getDeviceCount();

        // Read the temperature from the OneWire bus (if any device is present)
        if (deviceCount) {
            // Send the command to get temperatures
            sensors.requestTemperatures();

            float temp = 127.5f;

            // get the lowest temperature from the group of ds18b20
            for (int i = 0; i < deviceCount; i++) {
              // read the temperature
              float currentTemp = sensors.getTempCByIndex(i);

              // skip useless values!
              if (currentTemp < -60.0f) continue;
              else if (currentTemp > +50.0f) continue;

              // try updating the best temperature with the smallest one
              if (currentTemp < temp) {
                temp = currentTemp;
                validMeasure = true;
              }
            }

            // Update the current temperature
            if (validMeasure) oneWire_Temp = temp;
            validMeasure = false;

            // Update logic status
            logic.notifyTemperature(oneWire_Temp);

            // Edit pin value only if needed
            if ((bool)logic.heat() != boilerOn) {
                // Update the logic
                boilerOn = (logic.heat() == 0x01) ? true : false;

                // New mode for the boiler
                digitalWrite(BOILER_DIGITAL_PIN, (boilerOn != false) ? HIGH : LOW);
            }
        }

        // update the temperature for the GSM Shield task
        if (xSemaphoreTake(aGetStaticSemaphoreName(temperaturePoll), 3)) {
          lastMeasure.boilerOn = boilerOn;
          lastMeasure.temperatureValue = oneWire_Temp;
        }

#ifdef SERIAL_DBG
        // Build the debug info structure
        debugInfo.count = (uint8_t)deviceCount;
        debugInfo.value = oneWire_Temp;
        debugInfo.enabled = boilerOn;

        // Attempt to send debug informations to the debug task
        xQueueSend(aGetStaticQueueName(temperatureDebug), (const void *)&debugInfo, 8);
#endif
    }
}


E il thread di debug, che riceve le informazioni di debug dal task principale e le manda via seriale al pc:
#define pdMSTOTICKS( xTimeInMs ) ( (xTimeInMs / portTICK_PERIOD_MS) + 1 )

#ifdef SERIAL_DBG

aImportStaticQueue(temperatureDebug)

void TaskDebug(void *pvParameters __attribute__((unused))) {
    // Setup the hardware serial port
    Serial.begin(115200);

    // Wait for the serial port to be ready
    while (!Serial);

    // Hello, my dear :)
    Serial.println("========================================");
    Serial.println("Starting Debug (over serial 115200 baud)");
    Serial.println("========================================");

    for (;;) {
        // Get boiler regulator debug info
        struct BoilerRegulatorDebugInfo boilerRegulatorInfo;
        if(xQueueReceive(aGetStaticQueueName(temperatureDebug), (void*)&boilerRegulatorInfo, 3))
        {
            Serial.println();
            Serial.println("========================================");

            if (boilerRegulatorInfo.count > 0) {
                Serial.print("OneWire thermometers: ");
                Serial.println(boilerRegulatorInfo.count);
                Serial.print("OneWire temperature: ");
                Serial.print(boilerRegulatorInfo.value);
                Serial.println("° C");
            } else {
                Serial.println("No thermometers");
            }

            if (boilerRegulatorInfo.enabled) Serial.println("Boiler ON");
            else Serial.println("Boiler OFF");

            Serial.println("========================================");
        }


    }
}

#endif


Il task di debug ha priorità 1, il task principale 3 (la massima) significa che il task d debug può eseguirso SOLO mentre il task principale è in attesa di essere risvegliato (attraverso la syscall vTaskDelayUntil()).

Ora... Tutto ciò, funziona in maniera impeccabile. Non riscontro alcun problema.
Per capire il motivo della mia domandaa devo dirvi che il task che gestisce il modulo gsm è un task a se, che ho già preparato (almeno per quanto riguarda la struttura):
#define pdMSTOTICKS( xTimeInMs ) ( (xTimeInMs / portTICK_PERIOD_MS) + 1 )

aImportStaticSemaphore(temperaturePoll);

// ---------------------------------------------------
// -------------- Static variables -------------------
// ---------------------------------------------------
struct StatusPoll lastMeasure;
TickType_t xLastWakeTime_MobileManagement;

void TaskMobileManagement(void *pvParameters) {
    //SIM900 sim900;

    xLastWakeTime_MobileManagement = xTaskGetTickCount();

    for (;;) {
        // A few seconds delay to let other tasks run
        vTaskDelayUntil(&xLastWakeTime_MobileManagement, pdMSTOTICKS( 2000 ));

        /*SIM900DateTime currentTime;

        if (sim900.getTime(currentTime)) {

        }*/

    }
}


In questo codice ho disabilitato tutti i riferimenti alla classe SIM900, quindi non sarà posizionato sullo stack alcun elemento, mantenendo molto piccole le risorse utilizzate dal singolo task (per fare delle prove).

Il main è così, notate il commento posto proprio per evitare l'esecuzione dell'ultimo task:
// --------------------------------------------------
// --------------------- Queues ---------------------
// --------------------------------------------------
aDefineStaticQueue(temperatureDebug, sizeof(struct BoilerRegulatorDebugInfo), 1);

// --------------------------------------------------
// ------------------- Semaphores -------------------
// --------------------------------------------------
aDefineStaticSemaphore(temperaturePoll);

// --------------------------------------------------
// --------------------- Tasks ----------------------
// --------------------------------------------------
aDefineStaticTask(MobileCommunication, (configMINIMAL_STACK_SIZE + 25));
aDefineStaticTask(BoilerRegulator, (configMINIMAL_STACK_SIZE + 125));
#ifdef SERIAL_DBG
  aDefineStaticTask(Debug, (configMINIMAL_STACK_SIZE + 25));
#endif

// --------------------------------------------------
// -------------------- FreeRTOS --------------------
// --------------------------------------------------
void setup() {
    // Initialize Semaphores
    aInitStaticMutexSemaphore(temperaturePoll);

    // Initialize Queue
    aInitStaticQueue(temperatureDebug, sizeof(struct BoilerRegulatorDebugInfo), 1);

    // Prepare tasks
    aCreateTask(BoilerRegulator, TaskBoilerRegulator, NULL, (configMAX_PRIORITIES - 1));
    //aCreateTask(MobileCommunication, TaskBoilerRegulator, NULL, (configMAX_PRIORITIES - 2));
#ifdef SERIAL_DBG
    aCreateTask(Debug, TaskDebug, NULL, (configMAX_PRIORITIES - 3));
#endif

    // Start the real time scheduler.
    vTaskStartScheduler();

    // Probably we've failed trying to initialise heap for the scheduler. Let someone know.
    vApplicationMallocFailedHook();
}


Fin qui tutto bene. Una analisi dello schema di esecuzione rivela che:
vTaskStartScheduler() fa eseguire il processo principale perchè ha priorità maggiore, e nessun altro processo potrà eseguirsi finchè questo non si metterà in pausa. Quindi inizializza tutto ciò di cui ha bisogno al suo funzionamento e si mette in pausa appena entrato nel for.

Viene eseguito il processo secondario (di debug) che inizializza la seriale, stampa che il debug su schermo è iniziato e poco altro, visto che la coda è ancora vuota!

Viene risvegliato il processo principale, esegue una misura e la inserisce nella coda delle misure di debug. Il processo non può essere interrotto perché ha la priorità maggiore e nessun altro processo ha la stessa priorità, quindi la prima interruzione la farà al prossimo tentativo di lettura della temperatura (il for riparte).

A questo punto parte il processo di debug. Ha molto tempo per esegursi (circa 5 secondi) in questo lasso di tempo può SOLO trovare una misura sulla coda, stamparla a video e ciclare poi senza fare nulla, finchè il controllo non ritornerà al primo processo.

Il risultato è questo:
========================================
Starting Debug (over serial 115200 baud)
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 27.00° C
Boiler ON  <<<====== Ho impostato per motivi di debug la temperatura minima a 30.0° C ecco perchè è accesa
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 27.00° C
Boiler ON 
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 27.00° C
Boiler ON 
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 27.00° C
Boiler ON 
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 27.00° C
Boiler ON 
========================================

Poi metto un dito sui termometri e l temperatura si alza

========================================
OneWire thermometers: 2
OneWire temperature: 27.50° C
Boiler ON 
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 27.75° C
Boiler ON 
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 28.05° C
Boiler ON 
========================================

ECCETERA....

========================================
OneWire thermometers: 2
OneWire temperature: 31.14° C
Boiler OFF <<<=== L'isteresi è di un grado, quindi a un grado sopra il 30 si spegne
========================================


E fin qui tutto perfetto! Sono molto soddisfatto del risultato..... MA appena decommento la linea di creazione del task per il GSM succede un DISASTRO!

========================================
Starting Debug (over serial 115200 baud)
========================================

========================================
No thermometers
Boiler OFF
========================================

========================================
No thermometers
Boiler OFF
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 26.19° C
Boiler ON
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 26.19° C
Boiler ON
========================================

========================================
OneWire thermometers: 2
OneWire temperature: 26.19° C
Boiler ON
========================================


E qui si blocca TUTTO (presumibilmente)! Vedo il led del TX seriale che si accende, ma sul mio schermo non appare nient'altro.
Inoltre il led rimane acceso anche quando la temperatura supera 31° C. ma tutto ciò non dovrebbe assolutamente succedere!

Qualcuno sarebbe così gentile da darmi una mano a risolvere questo mistero?
aaa
29/06/17 16:10
pierotofy
Bel progetto!

Ho letto il post, ho guardato il codice, la mia prima impressione e' che probabilmente questo problema deriva dal cambio tra allocazione dinamica e statica.

#define configSUPPORT_DYNAMIC_ALLOCATION    0
#define configSUPPORT_STATIC_ALLOCATION     1


Forse non hai calcolato le dimensioni corrette per lo stack del modulo GPS?

aDefineStaticTask(MobileCommunication, (configMINIMAL_STACK_SIZE + 25));


E' giusto 25?
Il mio blog: piero.dev
29/06/17 17:37
TheDarkJuster
Il minimo + 25 è anche troppo per un task che usa pochissimo stack. Il problema lo ho trovato per sbaglio....

Quando creo il secondo task passo il puntatore alla funzione sbagliata. Quindi creavo due task uguali con priorità diverse e succedeva un casino.

Adesso che ho risolto e ho inserito diverso codice ho problemi di stack perché ho quasi finito la RAM :'( 98.8% used!!!

Spero non mi tocchi cambiare micro
aaa
29/06/17 19:16
TheDarkJuster
Finita la RAM. Sembra che sopra al 97% FreeRTOS non parta nemmeno più.

Devo cambiare micro..... Probabilmente comprerò un arduino Mega 2550 per non perdere tutto il lavoro fatto.
aaa
29/06/17 21:26
pierotofy
:k:
Il mio blog: piero.dev
30/06/17 1:41
TheDarkJuster
Pubblicherò il progetto su github, ma non escludo la possibilità di creare un articolo relativo alla mia esperienza con freertos e Arduino e di postarlo nel sito. O magari una guida specifica per freertos....

Allo stato attuale sono in grado di interagire con il modulo GSM, sapere se è bloccato da un pin o puk, oppure pronto per le comunicazioni e gestire il modulo RTC incluso nella shield e descritto nel protocollo AT.

Sono senza RAM e credo che la mia esperienza con Arduino uno finirà qui, tuttavia sono riuscito a ottimizzare di "molto" l'utilizzo della memoria RAM, a un punto tale che ora lo stack è sufficiente per tutti e tre i processi.

Vedremo se con Arduino mega riuscirò ad usare anche un display LCD. Sarebbe il top.

aaa