Using single core is fine. Dual core = Trouble!

User avatar
Moskus
Posts: 4
Joined: Mon Oct 02, 2023 11:24 am

Using single core is fine. Dual core = Trouble!

Postby Moskus » Wed Oct 04, 2023 8:56 am

I have a LilyGo A7000 series board that provides LTE connectivity. The idea is that the loop() function (running on core 1) should gather data and put it in a linked list, and a task running on core 0 should upload that data as fast as it can (which happens to be every second or so). Or they could be switched (loop uploading task and a different task gaterhs data).

Well, first I discovered mutex and and how important that is. And then I got told about xQueue and how I should implement that instead of creating my own solution.

This works just fine:
  1. void loop()
  2. {
  3.    CreateMessageAndAddToQueue();
  4.    SendMessages();
  5. }
If I put these in two different loops on the same core, the data is not generated.
  1. void setup()
  2. {
  3.   //more setup before this...
  4.  
  5.   xTaskCreatePinnedToCore(
  6.       Core0Loop,  /* Function to implement the task */
  7.       "Task1",    /* Name of the task */
  8.       10000,      /* Stack size in bytes */
  9.       NULL,       /* Task input parameter */
  10.       5,          /* Priority of the task */
  11.       NULL,       /* Task handle. */
  12.       1);         /* Core where the task should run */
  13.  
  14.   xTaskCreatePinnedToCore(
  15.       Core1Loop,  /* Function to implement the task */
  16.       "Task1",    /* Name of the task */
  17.       10000,      /* Stack size in bytes */
  18.       NULL,       /* Task input parameter */
  19.       5,          /* Priority of the task */
  20.       NULL,       /* Task handle. */
  21.       1);         /* Core where the task should run */
  22. }
  23.  
  24. void Core0Loop(void *parameter)
  25. {
  26.   while (true)
  27.   {
  28.     CreateDataAndAddToxQueue();
  29.   }
  30. }
  31.  
  32. void Core1Loop(void *parameter)
  33. {
  34.   while (true)
  35.   {
  36.     UploadxQueueAsJson();
  37.   }
  38. }
  39.  
  40. //Data generation
  41. int id = 0;
  42. void CreateMessageAndAddToQueue()
  43. {
  44.   if (IsConfigured)
  45.   {
  46.     Serial.print("#");
  47.  
  48.     // Generate test messages in the loop
  49.     Message messageToSend;
  50.     messageToSend.id = 4000 + id;
  51.     messageToSend.mdate = modem.getUnixTime();
  52.     messageToSend.LpA = random(0, 100);     // Random LpA between 0 and 10
  53.     messageToSend.LpC = random(0, 100);     // Random LpC between 0 and 10
  54.     messageToSend.Battery = random(30, 50); // Random Battery between 3.0 and 5.0
  55.  
  56.     // Send the message to the queue
  57.     if (xQueueSend(messageQueue, &messageToSend, portMAX_DELAY) != pdPASS)
  58.     {
  59.       Serial.println("Failed to send message to the queue");
  60.     }
  61.  
  62.     // Delay before generating the next test message
  63.     id++;
  64.   }
  65.   vTaskDelay(pdMS_TO_TICKS(newDataInterval));
  66. }

If I put them on different cores which should give the functions plenty of resources, I get watchdog errors:

[Codebox]
E (29125) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (29125) task_wdt: - IDLE (CPU 0)
E (29125) task_wdt: Tasks currently running:
E (29125) task_wdt: CPU 0: Task1
E (29125) task_wdt: CPU 1: Task1
E (29125) task_wdt: Aborting.
[/Codebox]
Googling that "watchdog got triggered" gives plenty of responses. But it seems the answer is often "do less in one of your functions", but that's not an option for me. Uploading takes time, and there's nothing I can do to prevent that.


The complete code as it looks right now is shown below. The lte_post(jsonString) uses the A76XX library (https://github.com/gasagna/A76XX/) to upload code.
  1. #include <Arduino.h>
  2. #include "lte_upload.h"
  3. #include <ArduinoJson.h>
  4. #include <esp_task_wdt.h>
  5.  
  6. const uint64_t NodeID = ESP.getEfuseMac();
  7. unsigned long lastSendTime = 0;
  8. const unsigned long sendInterval = 2000; // milliseconds
  9. unsigned long lastNewDataTime = 0;
  10. const unsigned long newDataInterval = 500; // milliseconds
  11.  
  12. typedef struct Message
  13. {
  14.   uint64_t id;
  15.   uint32_t mdate;
  16.   double LpA;
  17.   double LpC;
  18.   double Battery;
  19. } Message;
  20.  
  21. // Create a queue to store messages
  22. QueueHandle_t messageQueue;
  23. const int maxMessageCount = 20; // Adjust as needed
  24. Message *messageBuffer = nullptr;
  25.  
  26. void SendMessagesTask(void *parameter);
  27. void loopTask(void *parameter);
  28.  
  29. bool IsConfigured = false;
  30. const TickType_t xMaxBlockTime = pdMS_TO_TICKS(sendInterval); // Maximum block time for waiting in milliseconds
  31.  
  32. void setup()
  33. {
  34.   // begin serial port
  35.   Serial.begin(115200);
  36.   setCpuFrequencyMhz(240);
  37.  
  38.   Serial.println("Starting setup()...");
  39.  
  40.   // Setup the LTE modem
  41.   if (!IsConfigured)
  42.   {
  43.     lte_setup();
  44.     IsConfigured = true;
  45.  
  46.     // Create the message queue
  47.     messageQueue = xQueueCreate(maxMessageCount, sizeof(Message)); // Adjust the size as needed
  48.  
  49.     // Allocate memory for the message buffer
  50.     messageBuffer = (Message *)malloc(maxMessageCount * sizeof(Message));
  51.     if (messageBuffer == nullptr)
  52.     {
  53.       Serial.println("Failed to allocate memory for message buffer");
  54.       while (true)
  55.       {
  56.       };
  57.     }
  58.  
  59.     // Start a task to send messages
  60.     xTaskCreatePinnedToCore(loopTask, "MyLoopTask", 1024 * 8, NULL, 1, NULL, 1); // CORE0
  61.     //xTaskCreate(loopTask, "MyLoopTask", 1024 * 8, NULL, 1, NULL);
  62.     // xTaskCreatePinnedToCore(SendMessagesTask, "MyMessageTask", 1024 * 8, NULL, 1, NULL, 0); // CORE1
  63.   }
  64.  
  65.   Serial.println("setup() done!");
  66. }
  67.  
  68. int id = 0;
  69. void CreateMessageAndAddToQueue()
  70. {
  71.   if (IsConfigured)
  72.   {
  73.     Serial.print("#");
  74.  
  75.     // Generate test messages in the loop
  76.     Message messageToSend;
  77.     messageToSend.id = 4000 + id;
  78.     messageToSend.mdate = modem.getUnixTime();
  79.     messageToSend.LpA = random(0, 100);     // Random LpA between 0 and 10
  80.     messageToSend.LpC = random(0, 100);     // Random LpC between 0 and 10
  81.     messageToSend.Battery = random(30, 50); // Random Battery between 3.0 and 5.0
  82.  
  83.     // Send the message to the queue
  84.     if (xQueueSend(messageQueue, &messageToSend, portMAX_DELAY) != pdPASS)
  85.     {
  86.       Serial.println("Failed to send message to the queue");
  87.     }
  88.  
  89.     // Delay before generating the next test message
  90.     id++;
  91.   }
  92.   vTaskDelay(pdMS_TO_TICKS(newDataInterval));
  93. }
  94.  
  95. void loopTask()
  96. {
  97.   while (true)
  98.   {
  99.     CreateMessageAndAddToQueue();
  100.   }
  101. }
  102.  
  103. void SendMessages()
  104. {
  105.   // for(int i = 0; i<3;i++)
  106.   // {
  107.   //   CreateMessageAndAddToQueue();
  108.   // }
  109.  
  110.   // Collect all messages
  111.   int messageCount = 0;
  112.   id = 0;
  113.  
  114.   TickType_t xStartTime = xTaskGetTickCount(); // Record the start time
  115.  
  116.   while (xTaskGetTickCount() - xStartTime < pdMS_TO_TICKS(sendInterval))
  117.   { // Collect for the last X seconds
  118.  
  119.     TickType_t xStartNow = xTaskGetTickCount(); // Record the start time
  120.  
  121.     Message receivedMessage;
  122.     if (xQueueReceive(messageQueue, &receivedMessage, xMaxBlockTime))
  123.     {
  124.       // Store the received message
  125.       if (messageCount < maxMessageCount)
  126.       {
  127.         messageBuffer[messageCount] = receivedMessage;
  128.         messageCount++;
  129.       }
  130.     }
  131.   }
  132.  
  133.   Serial.print("Message count: ");
  134.   Serial.println(messageCount);
  135.  
  136.   // Create a JSON array for the collected messages
  137.   DynamicJsonDocument doc(1024);          // Adjust the size as needed
  138.   JsonObject root = doc.to<JsonObject>(); // Create the outermost JSON object
  139.   root["mm_apikey"] = "myKey";
  140.   root["data"]["station_id"] = NodeID;
  141.   root["data"]["station_unixtime"] = modem.getUnixTime();
  142.  
  143.   // Create the "values" array
  144.   JsonArray values = root["data"]["values"].to<JsonArray>();
  145.  
  146.   for (int i = 0; i < messageCount; i++)
  147.   {
  148.     JsonObject messageObject = values.createNestedObject();
  149.     messageObject["id_node"] = messageBuffer[i].id;
  150.     messageObject["mdate"] = messageBuffer[i].mdate;
  151.     messageObject["lpa"] = messageBuffer[i].LpA;
  152.     messageObject["lpa"] = messageBuffer[i].LpC;
  153.     messageObject["battery"] = messageBuffer[i].Battery;
  154.   }
  155.  
  156.   // Convert the JSON document to a string
  157.   String jsonString;
  158.   serializeJson(doc, jsonString);
  159.  
  160.   // Send the JSON string (replace this with your actual sending logic)
  161.   Serial.println("Sending JSON: ");
  162.   Serial.println(jsonString);
  163.  
  164.   esp_task_wdt_reset();
  165.  
  166.   // Sending the json string
  167.   lte_post(jsonString);
  168.  
  169.   // Wait for a while before collecting and sending the next batch
  170.   TickType_t xEndTime = xTaskGetTickCount(); // Record the end time
  171.  
  172.   vTaskDelay(pdMS_TO_TICKS(sendInterval));
  173. }
  174.  
  175. bool initTask = false;
  176. void loop()
  177. {
  178.   if (!initTask)
  179.   {
  180.     esp_task_wdt_init(120, true);
  181.     esp_task_wdt_add(NULL);
  182.     initTask = true;
  183.   }
  184.   SendMessages();
  185. }
  186.  
  187. void SendMessagesTask(void *parameter)
  188. {
  189.   (void)parameter;            
  190.   esp_task_wdt_init(120, true);
  191.   esp_task_wdt_add(NULL);
  192.  
  193.   while (true)
  194.   {
  195.     SendMessages();
  196.   }
  197. }
I've tried lots of variations in the code above, but I don't know what I'm doing wrong.
I might be missing some basics here, but I don't really know where to start...
Last edited by Moskus on Fri Oct 06, 2023 6:47 pm, edited 1 time in total.

User avatar
Moskus
Posts: 4
Joined: Mon Oct 02, 2023 11:24 am

Re: Using single core is fine. Dual core = Trouble!

Postby Moskus » Fri Oct 06, 2023 6:46 pm

Wow, that code looked terrible.

But for anybody struggling with this, the culprit is this: modem.getUnixTime();
Run it on the modem core only ;)

Who is online

Users browsing this forum: No registered users and 121 guests