Consulting Services
مقالاتإنترنت الأشياءلغة سي و متحكمات

كل ما يتعلق بـESP32 – الجزء الثاني

طباعة الرسائل وأساسيات الواي فاي

أجزاء سلسلة كل ما يتعلق بـESP32

متعلقات

كان الجزء الأول من هذه السلسلة عبارة عن لمحة عن ESP32 من وجهة نظر برمجية وعتادية مع شرح مفصل لتطبيق الليد الوامض.

ستعتمد هذه السلسلة وبسبب تشابك وتعقيد هذا النظام على مفاهيم يتم طرحها على شكل صندوق أسود ولاحقاً يتم توضيح تلك المفاهيم، حيث يجب للقارئ أن يمتلك فهم متماسك عن نظام الـESP32 قبل الخوض في التفاصيل. سوف يعرض هذا الجزء العمليات الأساسية الخاصة بالـWiFi مثل البحث والاتصال بنقاط الاتصال Access Points وأيضاً طباعة الرسائل عبر واجهة الـUART، وكل هذا باستخدام توابع من ESP-IDF.

 

بحث عن الشبكات المتوفرة

 

إن هذا المثال ربما هو أبسط مثال يمكن البدء به لأخذ نظرة أقرب لكيفية التعامل مع الـWiFi في ESP32. لنوفر الكلمات ونبدأ بالشرح العملي لكتابة الكود من الأسفل للأعلى.

تستخدم مكتبة الوايفاي في ESP32 تقسيمة من الذاكرة فلاش non-volatile storage partition لتخزين بعض المعلومات، لذلك يجب تهيئة هذه التقسيمة باستخدام تابع nvs_flash_init من مكتبة nvs_flash. إن تابع معالجة الأحداث events handler هو أمر آخر مهم في عمل مكتبة الوايفاي، وذلك لأن إجراءات الوايفاي الخلفيّة متعددة وليس على المطوّر سوى فحص ما هي الحالة الجديدة للوايفاي حيث كل وقوع لحالة جديدة يتم استدعاء تابع معالجة الحالة event handler. يتم تحديد اسم التابع المعالج للحالة بتمريره كوسيط إلى التابع esp_event_loop_init من مكتبة esp_event_loop.

كما أن الوايفاي لا يعمل منفصلاً فهو تابع لمجموعة من الطبقات الأخرى للاتصال، لذلك لا يتم تهيئة فقط الوايفاي عبر التابع esp_wifi_init من المكتبة ‘esp_wifi’  وإنما أيضاً تهيئة طبقة الـTCP/IP وتسمى lwIP وهي توصيف برمجي لطبقة الـTCP/IP مفتوح المصدر ومصمّم للأجهزة المدمجة.

عليهِ، يجب تضمين المكاتب التالية:

#include "esp_wifi.h"

#include "esp_event_loop.h"

#include "nvs_flash.h"

كما يجب تحديد نمط عمل الوايفاي كمحطّة أو نقطة اتصال Station or SoftAP إلى جانب مجموعة من الإعدادات الأخرى العامّة المتعلقة بالوايفاي وعمله على مسح الشبكات المجاورة.

void app_main(void)
{
   nvs_flash_init();

   tcpip_adapter_init();

   esp_event_loop_init(event_handler, NULL);

   wifi_init_config_t conf = WIFI_INIT_CONFIG_DEFAULT();

   esp_wifi_init(&conf);

   esp_wifi_set_mode(WIFI_MODE_STA);

   esp_wifi_start();

   wifi_scan_config_t scanConf;

   scanConf.ssid = NULL;
   scanConf.bssid = NULL;
   scanConf.channel = 0;
   scanConf.show_hidden = 1;
   scanConf.scan_type = WIFI_SCAN_TYPE_ACTIVE;

   esp_wifi_scan_start(&scanConf, 0);
}

 

لنقم الآن برسم تخيل عن التابع event_handler. هذا التابع ببساطة يجب أن يتحقق إذا البحث قد انتهى ومن ثم يقوم بطباعة معلومات عن الشبكات المعثورة باستخدام التابع esp_wifi_scan_get_ap_records الذي سيعيد تقرير عن كل شبكة كما سنرى في الكود. بما أن عدد الشبكات متغير فنحن أمام خيارين، حجز مكان ثابت في الذاكرة بطول أعظمي منطقي أو إجراء حجز ديناميكي بناءً على العدد الذي تم إيجاده من الشبكات وفي كلا الخيارين، فإن المتحول من النوع wifi_ap_record_t. عملياً هذا الـevent_handler داخل المكتبة هو عبارة عن مؤشر لتابع (المزيد عن هذا المفهوم في هذا المقال من موقع geeksforgeeks) ، حيث يتم استدعاء العنوان الذي يشير له مع وسيط من النوع system_event_t والذي يحوي قيمة تدل على معلومات حول الحدث. مثلاً:  event->event_info.scan_done.number أو event->event_id.

 

لنلقِ نظرة على كود التابع:

esp_err_t event_handler(void *ctx, system_event_t *event)
{
   if (event->event_id == SYSTEM_EVENT_SCAN_DONE)

   {

   uint16_t APCnt = event->event_info.scan_done.number;


   if(APCnt == 0) return 0;

   printf("%d AP available\n",APCnt);

   wifi_ap_record_t *list = malloc(sizeof(wifi_ap_record_t)*APCnt);

   printf("\r\n|------------------------------|\r\n");

   esp_wifi_scan_get_ap_records(&APCnt,list);

   printf("        SSID              RSSI\r\n");    


   for (uint8_t i = 0;i < APCnt;i++)
   {

      printf("\t%s\t\t%d\r\n", list[i].ssid, list[i].rssi);

   }

   printf("|------------------------------|\r\n");

   free(list);

   }

   return ESP_OK;

}

طباعة الرسائل

 

إنها المرّة الأولى التي نحتاج فيها لطباعة الرسائل. إن الشركة Expressif قد جهزت برنامج خاص في الـESP-IDF ويسمى IDF monitor ويمكن البدء به باستخدام التعليمة “make monitor”. هذا البرنامج ليس serial monitor اعتيادي وإنما مخصص للـESP32 مثل فك ترميز العناوين وإيقاف البرنامج على الدارة وإعادة تشغيل الدارة.

بالواقع سيظهر الكثير من الرسائل على الشاشة وهذا بسبب نظام طباعة الرسائل logging system  المتوفر في ESP-IDF، حيث يحوي عدّة مستويات من الرسائل:

  • error (lowest)
  • warning
  • info
  • debug
  • verbose (highest)

تحوي كل مكتبة مجموعة من الرسائل الداخلية، ويمكن التحكم بمستوى إظهار الرسائل عند بناء الكود وذلك من: Component config->Log output->Default log عندما تظهر شاشة الإعدادات من “make menuconfig”، كما يمكن للمستخدم تحديد السوية من خلال التابع esp_log_level_set ولكن لا يستطيع رفع السوية أكثر من المحددة أثناء البناء.

كل ملف مصدري لديه اسم وسم TAG name خاص به، مثلاً التطبيق الحالي يمكن أن يحمل الوسم التالي “scan_main”. إن التابع esp_log_level_set يأخذ وسيطين الأول الاسم الوسم والثاني السويّة.

نشاهد بالنظر إلى الصورة أعلاه العديد من الأسطر وبعضها يبدأ بالحرف “I” والذي يدل على أن السطر هو info وبجانبه الاسم الوسم “boot”. للطباعة هناك العديد من التوابع الماكروية macros حسب كل مستوى

  • ESP_LOGE for error.
  • ESP_LOGW for warning.
  • ESP_LOGI for info.
  • ESP_LOGD for Debug.
  • ESP_LOGV for verbose.

غالباً ما تكون طباعة الرسائل هي من أجل التنقيح debugging لذلك من الجيد الإشارة لماكرو اسمه ESP_ERROR_CHECK من المكتبة ‘esp_err’  والذي يتحقق من القيمة المرتجعة من التوابع في ESP-IDF التي تعيد من النوع esp_err_t ،فإذا كانت القيمة لا تساوي ESP_OK، فإنه يقوم بطباعة رسالة خطأ تشير لرقم السطر البرمجي الذي حصل فيه الخطأ واسم الملف المصدري واسم التابع.

#define ESP_ERROR_CHECK(x) do {                                        \
       esp_err_t __err_rc = (x);                                       \
       if (__err_rc != ESP_OK) {                                       \
           _esp_error_check_failed(__err_rc, __FILE__, __LINE__,       \
                                   __ASSERT_FUNC, #x);                 \
       }                                                               \
    } while(0);

وبهذا لاحقاً وعند استخدام التابع ESP_ERROR_CHECK للتحقق من نجاح تنفيذ التابع، سيكون لديك تصور أولي عن وظيفته.

 

الاتصال بنقطة نفاذ محدّدة

 

إن التطبيق الثاني في هذا الجزء هو خاص بالاتصال بنقطة نفاذ محدّدة. سنعيد استخدام أجزاء عديدة من الكود السابق لأن نفس الهيكلية ستكون هنا أيضاً.

سنضمن مكتبتين إضافيتين وهما string و esp_log. الأولى لأننا سنستخدم التابع strcpy والثانية سنقوم بطباعة بعض الرسائل باستخدام النظام الخاص بالـESP32 كما تم توضيحه في الفقرة الماضية.

#include "esp_wifi.h"

#include "esp_event_loop.h"

#include "nvs_flash.h"

#include "esp_log.h"

#include "string.h"

إن التابع app_main لم يتغير ولكن يضاف له تهيئة تتضمن اسم وكلمة سر الشبكة المراد الاتصال بها. التابع المستخدم لهذا الغرض هو esp_wifi_set_config ويأخذ وسيط من النوع wifi_config_t والذي سيحوي المعلومات اللازمة. جميع الخيارات المتاحة من أجل هذه التهيئة موجودة في المكتبة esp_wifi_types.

static const char *WIFISSID = "SSID";

static const char *WIFIPWD = "PWD123456";

void app_main(void)
{
   nvs_flash_init(); 

   tcpip_adapter_init();
  
   wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

    ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 

   esp_event_loop_init(event_handler, NULL);  

   wifi_config_t wifi_config;

   strcpy((char *)wifi_config.sta.ssid,(char *)WIFISSID);

   strcpy((char *)wifi_config.sta.password,(char *)WIFIPWD);

   wifi_config.sta.scan_method = WIFI_FAST_SCAN;

   wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;

   wifi_config.sta.threshold.rssi = -100;

   wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;

   esp_wifi_set_mode(WIFI_MODE_STA);  

   esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); 

   esp_wifi_start();

}

إن wifi_config.sta.ssid و wifi_config.sta.password هي مصفوفات محرفية لذلك لنسخ المعلومات إليها سنستخدم strcpy.

بالحديث عن الجزء الأخير من الكود وهو تابع event handler، فإنه مشابه تماماً للتابع القديم مع بعض الإضافة.

static esp_err_t event_handler(void *ctx, system_event_t *event)
{

    switch (event->event_id) {

       case SYSTEM_EVENT_STA_START:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");

           ESP_ERROR_CHECK(esp_wifi_connect());

           break;


       case SYSTEM_EVENT_STA_GOT_IP:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");

           ESP_LOGI(TAG, "Got IP: %s\n",ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));

           break;


       case SYSTEM_EVENT_STA_DISCONNECTED:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");

           ESP_ERROR_CHECK(esp_wifi_connect());

           break;


       default:

           break;

    }

    return ESP_OK;

}

 

النسخة الكاملة من الكود:

 

#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "string.h"

static const char *TAG = "scan";

static const char *WIFISSID = "SSID";
static const char *WIFIPWD = "PWD123456";

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    switch (event->event_id) {

       case SYSTEM_EVENT_STA_START:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");

           ESP_ERROR_CHECK(esp_wifi_connect());

           break;

       case SYSTEM_EVENT_STA_GOT_IP:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");

           ESP_LOGI(TAG, "Got IP: %s\n",

                    ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));

           break;

       case SYSTEM_EVENT_STA_DISCONNECTED:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");

           ESP_ERROR_CHECK(esp_wifi_connect());

           break;

       default:

           break;

    }

    return ESP_OK;
}


void app_main(void)
{
   nvs_flash_init();  

   tcpip_adapter_init();  

   wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

   esp_event_loop_init(event_handler, NULL); 

   wifi_config_t wifi_config;

   strcpy((char *)wifi_config.sta.ssid,(char *)WIFISSID);
   strcpy((char *)wifi_config.sta.password,(char *)WIFIPWD);

   wifi_config.sta.scan_method = WIFI_FAST_SCAN;
   wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
   wifi_config.sta.threshold.rssi = -100;
   wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;

   esp_wifi_set_mode(WIFI_MODE_STA);
 
   esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); 

   esp_wifi_start();

}

هذا كل شيء لهذا الجزء. نلتقي في الجزء القادم، حيث سننتقل لتطبيقات أكتر تعقيداً في الواي فاي لإرسال واستقبال المعطيات عبر الوايفاي وفي جزء لاحق يجب أن نطّلع أيضاً على كيفية تقسيم الذواكر memory map والتعامل مع الذاكرة فلاش الخارجية وهذا ضروري من أجل فهم أكثر عمقاً للـESP32.

 

أجزاء سلسلة كل ما يتعلق بـESP32

متعلقات

Yahya Tawil

مؤسس عتاديات وأسعى لدعم المحتوى العربي عن النظم المضمنة في الويب العربي. خبرتي في مجال النظم المضمّنة تتركز في كتابة البرامج المضمنة وتصميم الدارات المطبوعة والمخططات وإنشاء المحتوى.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

هذا الموقع يستخدم Akismet للحدّ من التعليقات المزعجة والغير مرغوبة. تعرّف على كيفية معالجة بيانات تعليقك.

زر الذهاب إلى الأعلى