استفاده از NimBLE برای بلوتوث کم مصرف در ESP32

دستگاههای هوشمند کممصرف مانند ردیابهای ورزشی و beaconها برای ارتباط بیسیم بدون مصرف زیاد باتری عمدتاً از Bluetooth Low Energy (BLE) استفاده میکنند. در بین سازندگان این نوع سختافزارها، تراشه ESP32 بسیار محبوب است چون Wi‑Fi و بلوتوث را در قالب یک چیپ کوچک ارائه میدهد.
در پیادهسازیهای مرسوم ESP32، معمولاً از بسته نرمافزاری Bluedroid برای مدیریت بلوتوث استفاده میشود. Bluedroid قابلیتهای زیادی دارد و هم Bluetooth Classic و هم BLE را پشتیبانی میکند، اما سنگین است: فضای ذخیرهسازی و منابع پردازشی زیادی را اشغال میکند چون برای اداره همزمان حالتهای پیچیده هر دو نوع بلوتوث طراحی شده است. اگر پروژه شما تنها به BLE نیاز داشته باشد، استفاده از کل سیستم حجیم Bluedroid غیرضروری است.
برای مقابله با این مشکل، استک سبک و متنباز NimBLE که ابتدا زیر پروژه سیستمعامل بلادرنگ Apache Mynewt توسعه داده شده بود، رشد کرد. مهاجرت از فریمورک سنگین Bluedroid به NimBLE باعث کاهش مصرف حافظه و افزایش کارایی ESP32 میشود.
در این راهنما دلایل برتری NimBLE نسبت به Bluedroid را بررسی میکنیم، نحوه راهاندازی آن در محیط آردوینو را توضیح میدهیم و شما را در پیادهسازی اولین سرور NimBLE راهنمایی میکنیم.
Bluedroid در برابر NimBLE
برای درک مفید بودن NimBLE لازم است تفاوتهای ساختاری کلیدی بین استک NimBLE و پیادهسازی استاندارد مبتنی بر Bluedroid در ESP32 را بدانید.
مصرف Flash و SRAM
مشکل اصلی Bluedroid این است که همزمان برای پشتیبانی از Bluetooth Classic و BLE طراحی شده است. حتی اگر در کد قابلیتهای کلاسیک را غیرفعال کنید، بخشهایی از کد پسزمینه که بهینه نشدهاند همچنان وجود دارند و فضا مصرف میکنند.
NimBLE اما از ابتدا به صورت استکی اختصاصی برای BLE طراحی شده است. با حذف منطقهای پروتکلی غیرضروری، فضای ذخیرهسازی دائمی کمتری اشغال میکند و در زمان اجرا نیز حافظه فعال به مراتب کمتری مصرف میشود.
کارایی زمان اجرا و پاسخدهی
علاوه بر کاهش فضا، NimBLE سریعتر و پاسخگوتر است که برای پروژههایی با حالت deep sleep اهمیت دارد. در deep sleep، ESP32 بخشهای زیادی را خاموش میکند تا باتری ذخیره شود؛ پس از بیدار شدن باید بلوتوث را راهاندازی، دادهای تبلیغ و دوباره به خواب برود؛ هرچه این چرخه سریعتر انجام شود مصرف باتری کمتر خواهد شد.
آزمایشها نشان میدهند NimBLE معمولاً زودتر از Bluedroid آماده میشود. برای مثال، Bluedroid ممکن است حدود 631 milliseconds برای مقداردهی اولیه نیاز داشته باشد، در حالی که NimBLE حدود 209 milliseconds وقت میگیرد؛ این یک بهبود چشمگیر است.
مقایسهای ساده به صورت جدول زیر:
| فاز عملیات | میانگین تأخیر Bluedroid | میانگین تأخیر NimBLE | تغییر عملکرد |
|---|---|---|---|
| مقداردهی استک | 631 milliseconds | 209 milliseconds | 66.8% کاهش |
| اتصال Client | 316 milliseconds | 414 milliseconds | 31.0% افزایش |
| کل زمان گذشته | 946 milliseconds | 623 milliseconds | 34.1% کاهش |
اگرچه NimBLE ممکن است برای مذاکره و برقراری اتصال فیزیکی با دستگاه مقابل زمان بیشتری صرف کند، اما به دلیل شروع بسیار سریعتر، مجموع زمان کوتاهتر است و این به نفع مصرف باتری است چون ESP32 زمان بیداری کمتری خواهد داشت.
تراکم اتصالات همزمان
NimBLE توانایی مدیریت تعداد بیشتری اتصال BLE همزمان را دارد. در حالی که استک Bluedroid معمولاً ESP32 را بسته به تنظیمات به 3 تا 7 اتصال همزمان محدود میکند، NimBLE به طور ذاتی از تا 9 اتصال همزمان پشتیبانی میکند.
این ظرفیت اضافی مخصوصاً وقتی مفید است که بخواهید یک هاب مرکزی بسازید که باید دادهها را از چندین سنسور به صورت همزمان جمعآوری کند.
مبانی Bluetooth Low Energy (BLE)
قبل از ساخت سرور NimBLE روی ESP32 باید با اصول پایه BLE آشنا باشید.
در کد نمونهای که در ادامه میآید، ESP32 را به عنوان یک سرور تنظیم خواهیم کرد. دستگاه سرویس مشخصی را تبلیغ میکند و در دل آن سرویس، یک characteristic قرار میدهیم که مقدار واقعی داده را در خود دارد. برای اینکه دیگر دستگاهها بدانند این داده چیست، به هر بخش یک شناسه یکتا به نام UUID میدهیم. اگر این اصطلاحها برایتان ناآشناست، در ادامه ساختار داده در BLE را به صورت مختصر مرور میکنیم تا کد واضح شود.
در BLE همه چیز حول دو سیستم اصلی GAP و GATT است.
GAP (Generic Access Profile)
GAP مدیریت فازهای اتصال و advertising را در بلوتوث بر عهده دارد. GAP باعث میشود دستگاه شما برای محیط اطراف قابلمشاهده باشد و مشخص میکند چگونه دو دستگاه یکدیگر را کشف و اتصال را برقرار میکنند.
GAP نقشهای مختلفی برای دستگاهها تعریف میکند؛ دو مفهوم اصلی که باید در نظر داشته باشید Central و Peripheral هستند.
Peripheralها معمولاً دستگاههای کوچک، کممصرف و با منابع محدود مانند سنسور ضربان قلب یا تگهای مجاورت هستند که به یک دستگاه مرکزی قدرتمندتر متصل میشوند.
Centralها معمولاً دستگاههای دارای پردازش و حافظه بیشتر مانند تلفن همراه یا تبلت هستند که به آنها متصل میشویم.

یک دستگاه peripheral با ارسال بستههای تبلیغاتی در فواصل مشخص، وجود خود را به دستگاههای مرکزی اطراف اطلاع میدهد. پس از برقراری اتصال بین peripheral و central، فرایند advertising متوقف شده و نقش GATT آغاز میشود.
GATT (Generic ATTribute Profile)
GATT مشخص میکند دادهها چگونه سازماندهی شوند و چگونه دو دستگاه BLE دادهها را با هم به اشتراک بگذارند. برخلاف GAP که تعاملات پاییندست با دستگاهها را تعریف میکند، GATT فقط به روشها و فرمتهای تبادل داده میپردازد.
دادهها به صورت سلسلهمراتبی در سرویسها سازماندهی میشوند؛ هر سرویس مجموعهای از دادههای مرتبط را که characteristic نامیده میشوند گروهبندی میکند. این ساختار در تصویر زیر نشان داده شده است:

سرویسها
یک سرویس GATT مجموعهای از دادههای مرتبط به نام characteristicها است. هر سرویس میتواند یک یا چند characteristic داشته باشد و برای خود یک شناسه عددی یکتا یا UUID دارد که یا 16 بیت (برای سرویسهای رسمی) یا 128 بیت (برای سرویسهای سفارشی) است.
به عنوان مثال، سرویس Heart Rate یک سرویس رسمی با UUID 16 بیتی 0x180D است و میتواند تا 3 characteristic داشته باشد: Heart Rate Measurement، Body Sensor Location و Heart Rate Control Point.
Characteristics
یک characteristic GATT در واقع مجموعهای از Attributes است. Attributes همان دادههایی هستند که بین دستگاههای BLE منتقل میشوند. یک characteristic معمولاً از مشخصات زیر تشکیل میشود:

- Value: داده واقعی ذخیرهشده در characteristic؛ میتواند عدد، رشته یا آرایهای از بایتها باشد.
- Descriptor: اطلاعات تکمیلی یا گزینههای پیکربندی برای characteristic.
علاوه بر Value، هر characteristic با خواصی مرتبط است که شامل موارد زیر میشود:
- Handle: یک عدد 16 بیتی که برای دسترسی به characteristic روی دستگاه سرور استفاده میشود.
- UUID: شناسه یکتا (معمولاً 128 بیتی برای ویژگیهای سفارشی) که نشان میدهد characteristic چه مفهومی دارد.
- Permissions: تعیین میکند چه عملیاتی روی characteristic مجاز است، مانند read، write یا notify.
UUID (Universally Unique Identifier)
UUIDها تضمین میکنند سرویسها و characteristicها به صورت یکتا شناسایی شوند. دو نوع UUID در BLE وجود دارد:
- 16-bit UUID: برای پروفایلها، سرویسها و characteristicهای رسمی که توسط Bluetooth-SIG استاندارد شدهاند استفاده میشود. مثلاً سرویس Heart Rate با UUID 16 بیتی 0x180D و characteristic مربوطه Heart Rate Measurement با UUID 0x2A37 مشخص شدهاند.
- 128-bit UUID: برای سرویسها و characteristicهای سفارشی استفاده میشود. برای مثال یک UUID 128 بیتی میتواند به شکل 4fafc201-1fb5-459e-8fcc-c5c9c331914b باشد.
GATT Server و GATT Client
از دید GATT، وقتی دو دستگاه به هم متصل میشوند، هر کدام در یکی از دو نقش زیر قرار میگیرند:
- GATT server: دستگاهی که دیتابیس characteristicها را نگهداری میکند.
- GATT client: دستگاهی که از سرور میخواند یا روی آن مینویسد.
در شکل نمونه، peripheral (ESP32) به عنوان GATT server که داده را نگه میدارد عمل میکند و دستگاه مرکزی مثل یک گوشی هوشمند نقش GATT client را دارد که آن داده را میخواند.

ایجاد یک سرور ساده NimBLE روی ESP32
حالا که با مفهوم GATT server، سرویس و characteristic آشنا هستید، وقت ساختن یک سرور است.
برای ساده کردن کار از کتابخانه NimBLE-Arduino استفاده خواهیم کرد. این کتابخانه که توسط Ryan Powell توسعه یافته است، استک NimBLE را برای کامپایل و اجرا در Arduino IDE آماده میکند و هدفش ارائه جایگزینی سبکتر برای کتابخانه مبتنی بر Bluedroid است و در عین حال دستورات برنامهنویسی را تقریباً مشابه نگه میدارد تا مهاجرت کد آسانتر باشد.
نصب کتابخانه
مراحل نصب:
- Arduino IDE را باز کنید و از نوار سمت چپ روی Library Manager کلیک کنید.
- در کادر جستجو عبارت “nimble” را تایپ کنید.
- کتابخانه NimBLE-Arduino نوشته h2zero را پیدا کنید.
- روی دکمه Install کلیک کنید تا نصب شود.

کد آردوینو
<pمثال زیر ESP32 را به عنوان یک سرور NimBLE تنظیم میکند. دستگاه تبلیغ میشود تا بلوتوثهای اطراف آن را ببینند؛ همچنین یک سرویس و یک characteristic ایجاد میشود که کلاینتها میتوانند به آن متصل شده و مقدار داخل characteristic را بخوانند.
#include
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
void setup() {
// Initialize the primary serial interface for debugging output
Serial.begin(115200);
Serial.println("Starting NimBLE Server Configuration...");
// Phase 1: Initialize the BLE stack with a unique device name
NimBLEDevice::init("MyESP32");
// Phase 2: Instantiate the Server and create the Service
NimBLEServer *pServer = NimBLEDevice::createServer();
NimBLEService *pService = pServer->createService(SERVICE_UUID);
// Phase 3: Create the Characteristic and define its properties
NimBLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE
);
// Assign an initial payload to the characteristic
pCharacteristic->setValue("Hello World!");
// Phase 4: Start the service to make it accessible
pService->start();
// Phase 5: Configure and initiate advertising
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->setName("MyESP32");
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->enableScanResponse(true);
pAdvertising->start();
Serial.println("NimBLE Server is successfully advertising.");
}
void loop() {
// The FreeRTOS scheduler handles BLE tasks in the background;
// the main loop can remain empty or handle other sensor logic.
delay(2000);
}
توضیح کد
در ادامه خطوط کد را گامبهگام مرور میکنیم. ابتدا کتابخانه NimBLEDevice برای عملیات BLE در ESP32 درج شده است:
#include <NimBLEDevice.h>
سپس UUIDهای سرویس و characteristic تعریف میشوند؛ این شناسهها به دستگاههای دیگر میگویند چه نوع دادهای ارائه میکنید. در صورت تمایل میتوانید از یک تولیدکننده UUID آنلاین، UUIDهای دلخواه خود را بسازید.
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
در تابع setup ابتدا پورت سریال با baud rate برابر 115200 باز میشود:
Serial.begin(115200);
بعد با فراخوانی NimBLEDevice::init("MyESP32") استک BLE مقداردهی میشود و نام عمومی دستگاه را تعیین میکنیم تا در اسکن بلوتوث قابلشناسایی باشد.
NimBLEDevice::init("MyESP32"); پس از مقداردهی استک، یک سرور ایجاد میکنیم که اتصالات ورودی را مدیریت خواهد کرد:
NimBLEServer *pServer = NimBLEDevice::createServer();
بلافاصله پس از ایجاد سرور، یک سرویس با UUID تعریفشده میسازیم:
NimBLEService *pService = pServer->createService(SERVICE_UUID);
سپس یک characteristic درون سرویس ایجاد میکنیم. هنگام ایجاد آن UUID مشخص شده و خواص (Permissions/Properties) را تعیین میکنیم. در این مثال خواص READ و WRITE تعیین شدهاند تا دستگاه متصل بتواند مقدار را بخواند و در صورت نیاز بنویسد. خواص با نماد | کنار هم قرار میگیرند تا چندین قابلیت ترکیب شوند.
NimBLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE
);
اگر این مجوزها را فراموش کنید، کلاینتها هنگام تلاش با خطاهایی مانند “GATT WRITE NOT ALLOWED” روبهرو خواهند شد.
مقدار اولیه characteristic با متد setValue() تعیین میشود؛ در این مثال متن “Hello World!” قرار داده شده است، پس هنگامی که تلفن به سرور متصل شده و مقدار را میخواند، همان متن دریافت خواهد شد.
pCharacteristic->setValue("Hello World!"); برای اینکه سرویس قابلدسترس شود باید آن را با pService->start() فعال کنید؛ در غیر این صورت سرویس و characteristicها پنهان خواهند ماند.
pService->start();
مرحله بعد تنظیم advertising است تا ESP32 خود را به اطراف اعلام کند. شیٔ advertising را با NimBLEDevice::getAdvertising() بهدست میآوریم و تنظیماتی مانند نام تبلیغی، افزودن UUID سرویس به دادههای تبلیغ و فعالسازی scan response را انجام میدهیم.
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->setName("MyESP32"); pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->enableScanResponse(true);
pAdvertising->start();
تابع loop در این مثال تقریباً خالی است چون FreeRTOS کارهای BLE را در پسزمینه مدیریت میکند؛ در loop میتوانید منطق سنسورها، بررسی دکمهها، بهروزرسانی مقدار characteristic یا ارسال اعلانها را قرار دهید بدون آنکه اتصال بلوتوث مختل شود.
void loop() { // The FreeRTOS scheduler handles BLE tasks in the background;
// the main loop can remain empty or handle other sensor logic.
delay(2000);
}
آزمایش با nRF Connect
برای تست اتصال BLE باید ESP32 را با گوشی خود جفت کنید و از یک اپلیکیشن دیباگ بلوتوث روی گوشی استفاده کنید. یکی از گزینههای قدرتمند، اپ nRF Connect از Nordic است که برای iOS و Android موجود است و امکان اسکن، بررسی دستگاهها و ارتباط با آنها را فراهم میکند.
مراحل تست با nRF Connect:
- به Google Play یا App Store رفته و “nRF Connect for Mobile” را نصب کنید. سپس برنامه را اجرا کنید.
- اطمینان حاصل کنید بلوتوث گوشی روشن است.
- در اپ روی دکمه “SCAN” بزنید. اپ شروع به اسکن دستگاههای BLE اطراف میکند.



4. در فهرست دستگاهها به دنبال “MyESP32” بگردید و مقابل آن روی “Connect” کلیک کنید.

5. وارد نمای “Services” خواهید شد. در آنجا فهرستی از سرویسهای در دسترس را میبینید. روی “Unknown Service” کلیک کنید؛ رشته UUID باید با SERVICE_UUID موجود در کد شما مطابقت داشته باشد.

6. با انتخاب یک سرویس میتوانید characteristics مرتبط را مشاهده کنید. در کنار هر characteristic دو آیکون وجود دارد: فلش پایین برای خواندن و فلش بالا برای نوشتن.

7. برای خواندن characteristic روی فلش پایین بزنید. خواهید دید UUID، خواص READ و WRITE و مقدار “Hello World!” دقیقاً همانطور که در کد قرار داده بودیم نمایش داده میشود.

سوالات متداول
چرا NimBLE را به جای Bluedroid روی ESP32 انتخاب کنیم؟
NimBLE به عنوان یک استک اختصاصی BLE طراحی شده و در مقایسه با Bluedroid فضای Flash و حافظه فعال کمتری مصرف میکند، سریعتر مقداردهی اولیه میشود (مثلاً 209ms در برابر 631ms برای Bluedroid) و تا 9 اتصال همزمان BLE را پشتیبانی میکند در حالی که Bluedroid معمولاً بین 3 تا 7 اتصال همزمان محدود است.
GAP و GATT چه تفاوتی دارند و هرکدام چه نقشی دارند؟
GAP مسئول دیدهشدن دستگاه و نحوه کشف و برقراری اتصال (advertising و نقشهای peripheral/central) است، در حالی که GATT قالببندی و شیوه تبادل دادهها را تعیین میکند؛ دادهها در GATT به صورت سرویسها و characteristicها سازماندهی میشوند.
برای تست سرور NimBLE راهاندازیشده چگونه از nRF Connect استفاده کنم؟
اپ nRF Connect را نصب و Bluetooth گوشی را روشن کنید؛ در اپ SCAN بزنید، فهرست دستگاهها را پیدا کنید (MyESP32)، به آن متصل شوید، وارد نمای Services شده و سرویس با UUID تعریفشده را باز کنید؛ سپس با آیکون خواندن مقدار characteristic را مشاهده خواهید کرد (در مثال مقدار “Hello World!” نمایش داده میشود).











