دليل · FreeRTOS · ثنائي النواة
انقل العمل إلى النواة الثانية في ESP32 عبر FreeRTOS
لدى ESP32 الكلاسيكي نواتان، لكنّ حلقة Arduino loop() لا تعمل إلا على إحداهما.
وما إن تقع قراءة مستشعر بطيئة أو تحليل بيانات في loop() حتى تتباطأ صفحة الويب ويتلعثم Wi-Fi.
الحل موجود على الرقاقة أصلًا: FreeRTOS. أنشئ مهمّة، وثبّتها على النواة الأخرى — فتعملان
جنبًا إلى جنب.
هذه الخطوة التالية بعد millis() والمقاطعات: تلك تمنع حلقة واحدة من الحجب؛ أما FreeRTOS فيتيح تشغيل عدة حلقات في آنٍ واحد — على نوى عتادية حقيقية.
أنت تستخدمه أصلًا
تعمل نواة Arduino لـ ESP32 فوق FreeRTOS. فـ setup()/loop() عندك هي نفسها مهمّة
(loopTask) مثبّتة على النواة 1. وحزمة Wi-Fi/Bluetooth تشغّل مهامّها، أغلبها
على النواة 0. فـ«إضافة مهمّة» ليست غريبة — أنت فقط تنشئ واحدة أخرى مما يفعله النظام أصلًا.
مهمّتك الخاصة
المهمّة دالّة بحلقة لا نهائية for(;;) لا تعود أبدًا. تُنشئها مرة، ويشغّلها المجدوِل إلى الأبد،
مقسّمًا وقت المعالج بينها وبين كل شيء آخر. القاعدة التي يتعثّر بها الجميع: داخل المهمّة استخدم
vTaskDelay() لا delay() — فـ vTaskDelay يعيد المعالج للمهامّ الأخرى
أثناء الانتظار.
ثبّتها على النواة الثانية
استخدم xTaskCreatePinnedToCore() وأعطها النواة 0 — فيبقى العمل الثقيل بعيدًا
عن النواة 1 حيث يعيش loop() وخادم الويب:
void sensorTask(void *pv) {
for (;;) {
float t = readTemp(); // your slow work
Serial.printf("temp: %.1f\n", t);
vTaskDelay(pdMS_TO_TICKS(1000)); // yield — never delay() in a task
}
}
void setup() {
Serial.begin(115200);
xTaskCreatePinnedToCore(
sensorTask, // function to run
"sensor", // name (for debugging)
4096, // stack in bytes — too small = crash/reboot
NULL, // parameter
1, // priority (higher = more urgent)
NULL, // task handle (optional)
0); // core 0 — leave core 1 for loop() + Wi-Fi
}
void loop() {
// core 1: keep the web server / UI / Wi-Fi responsive
} الوسائط السبع هي: الدالّة، اسم، حجم المكدّس، معامل، الأولوية، مقبض اختياري، والنواة (0 أو 1). اثنان منها يعضّان المبتدئين: مكدّس صغير جدًّا فتنهار المهمّة بمجرد لمسها ذاكرة؛ والأولوية نسبية — مهمّة عالية الأولوية لا تتنازل ستُجوّع كل ما دونها.
تمرير البيانات بين النواتين — طابور
الفخّ الآن هو مشاركة البيانات. متغيّر عامّ عادي يُكتب على النواة 0 ويُقرأ على النواة 1 قد «يتمزّق» أو
يُقرأ قديمًا — volatile وحده لا يكفي لأيّ شيء أكبر من كلمة واحدة محاذاة. الطريقة النظيفة
طابور: مهمّة تضع القيم وأخرى تستقبلها، ويتولّى FreeRTOS القفل:
QueueHandle_t q;
void producer(void *pv) {
for (;;) {
float v = readTemp();
xQueueSend(q, &v, 0); // hand the value across — no shared globals
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void setup() {
q = xQueueCreate(8, sizeof(float)); // room for 8 floats
xTaskCreatePinnedToCore(producer, "prod", 4096, NULL, 1, NULL, 0);
}
void loop() {
float v;
while (xQueueReceive(q, &v, 0) == pdTRUE) // core 1 drains it safely
Serial.println(v);
} الطوابير مثالية لتقسيم مُنتِج/مُستهلِك — مهمّة مستشعر تقيس ومهمّة واجهة تعرض. أما بنية مشتركة
تلمسها من الجهتين فاحمِها بـ mutex (xSemaphoreCreateMutex()). القاعدة:
مرّر نسخًا عبر طابور، ولا تشارك المتغيّرات العامة.
مطبّات تُعيد تشغيل اللوحة
- مكدّس صغير جدًّا. 1–2 كيلوبايت يكفي لوامض؛ أيّ شيء يلمس Wi-Fi أو JSON أو
Stringيريد 4–8 كيلوبايت. فيض المكدّس يعيد التشغيل فورًا. delay()داخل مهمّة يحجب خانة مجدوِل تلك النواة — استخدمvTaskDelay(pdMS_TO_TICKS(ms)).- مراقب النواة 0. مهمّة جشعة على النواة 0 لا تتنازل تُسقِط مراقب المهام في هلع. نفّذ
vTaskDelay()مرة واحدة على الأقل في كل حلقة. Serialمن مهمّتين قد يتداخل؛ للتسجيل الجادّ وجّه الطباعة عبر مهمّة واحدة (طابور من جديد).- الأولويات نسبية. لا تجعل كل شيء عاليًا — ابدأ أغلب المهامّ عند 1.
⚠ أيّ رقائق ESP ثنائية النواة فعلًا
- نواتان: الكلاسيكي ESP32 وESP32-S3. هنا يمنحك التثبيت على النواة 0 توازيًا حقيقيًا.
- نواة واحدة: ESP32-C3 وC6 وS2 وH2. مهامّ FreeRTOS تعمل رغم ذلك —
يقسّم المجدوِل وقتها — لكن لا نواة ثانية للتثبيت عليها.
xTaskCreatePinnedToCore(…, 1)يعمل على النواة 0 فقط. ممتاز للبنية لا لتقسيم الحِمل. - بلا FreeRTOS (نواة Arduino): يشغّل ESP8266 حلقة تعاونية واحدة. لا
xTaskCreate— اعتمد على millis() والمقاطعات. لست متأكدًا مما لديك؟ انظر دليل الرقائق.
النمط الذي يفيد في كل مشروع تقريبًا: أبقِ loop() للشبكة والواجهة، وادفع العمل الحاجب —
مستشعر أو ملف أو تحليل — إلى مهمّة مثبّتة تردّ عبر طابور. فتكفّ اللوحة عن التلعثم، ويبقى لوح
القيادة سلسًا بينما تؤدّي المستشعرات عملها.
لست متأكدًا هل للوحتك نواة ثانية — أو أيّ الأطراف متاحة لمهمّة المستشعر؟