وبسرور با ESP32 کنترل WS2812 | تغییر رنگ و افکت از مرورگر
معرفی پروژه
در این پروژه، یک وبسرور با ESP32 به صورت کامل با یک رابط کاربری زیبا طراحی و پیاده سازی میکنم که امکان کنترل کامل ماژول WS2812 رو از طریق مرورگر (موبایل یا کامپیوتر) میده. قابلیتها:
انتخاب رنگ با Color Picker.
5 افکت مختلف و جذاب.
تنظیم روشنایی (Brightness).
سرعت افکت قابل تنظیم.
رابط کاربری واکنشگرا (Responsive).
نحوه عملکرد WS2812 و ارتباط آن با ESP32 (توضیح جامع):
خب قبل از اینه بریم سراغ کد پروژه بهتره که اول مقداری با LEDهای آدرس پذیر و توی این پروژه به طور خاص WS2812 آشنا بشیم.
WS2812 دقیقاً چیست؟
WS2812 یک LED هوشمند RGB هست که داخل خودش یک درایور IC (تراشه کنترلکننده) داره. برخلاف LEDهای معمولی که فقط با ولتاژ/جریان کنترل میشن، WS2812 یک پروتکل دیجیتال برای دریافت داده داره.
- هر LED یک رجیستر 24 بیتی داخلی داره (8 بیت برای هر رنگ R، G، B)
- IC داخلی، PWM هر کانال رنگی رو بر اساس این مقدار تولید میکنه
- پین
DOUTداده رو به LED بعدی منتقل میکنه (این چیزیه که امکان زنجیرهای کردن (Daisy Chain) صدها LED با یک سیم رو میده)
پروتکل ارتباطی WS2812 (تکسیمه) :
چرا فقط یک سیم داده کافیه؟
WS2812 از یک پروتکل اختصاصی به نام NZR (Non-Return-to-Zero) استفاده میکنه که کاملاً بر پایهی تایمینگ پالس کار میکنه، نه سطح ولتاژ ساده. یعنی مقدار بیت (0 یا 1) با طول زمانی که سیگنال HIGH میمونه مشخص میشه.
ترتیب ارسال داده (GRB نه RGB!):
WS2812 داده رو به ترتیب G-R-B (نه RGB) و با بیت MSB اول دریافت میکنه — این نکته رو حتما در نظر داشته باشید.
24 بیت برای هر LED:
[G7 G6 G5 G4 G3 G2 G1 G0] [R7 R6 R5 R4 R3 R2 R1 R0] [B7 B6 B5 B4 B3 B2 B1 B0]
داخل کد قبلی، فرمت LED_PIXEL_FORMAT_GRB رو تنظیم کردیم تا توالی کد رنگ مورد نیاز رو برامون تولید کنه — این کار رو خودِ کامپوننت led_strip بهصورت خودکار مدیریت میکنه.
سیگنال Reset:
بعد از ارسال دادهی همهی LEDهای زنجیره، باید خط داده حداقل 50 میکروثانیه LOW بمونه. این سیگنال به همهی LEDها میگه: “داده تموم شد، مقادیر رو اعمال کن و منتظر فریم بعدی باش.”
چطور داده به چند LED زنجیرهای میرسه؟
این بخش جالبترین قسمت WS2812 هست:
ESP32 ──► [LED 1] ──► [LED 2] ──► [LED 3] ──► …
DIN DOUT─DIN DOUT─DIN DOUT─DIN
- ESP32 داده رو برای همهی LEDها پشت سر هم ارسال میکنه (24 بیت × تعداد LED)
- LED اول اولین 24 بیت رو “میبلعه” (Latch میکنه) و بقیهی بیتها رو از پین
DOUTبه LED بعدی پاس میده - LED دوم همین کار رو با 24 بیت بعدی انجام میده
- این فرآیند تا آخرین LED زنجیره ادامه پیدا میکنه
تو کد، آرایهای به اندازهی LED_STRIP_LED_COUNT داریم و با
led_strip_set_pixel(led_strip, i, r, g, b) مقدار هر ایندکس رو جدا تنظیم میکنیم. این تابع فقط بافر رو آپدیت میکنه، و led_strip_refresh() هست که واقعاً کل زنجیره رو ارسال میکنه.
چرا ESP32 از پریفرال RMT استفاده میکنه؟
مشکل بیتبنگ نرمافزاری
اگه بخوایم با gpio_set_level() معمولی و delay این تایمینگها رو تولید کنیم:
// روش اشتباه و ناپایدار ❌ gpio_set_level(PIN, 1); ets_delay_us(0.35); // خیلی کوتاه، دقت کافی نداره gpio_set_level(PIN, 0); ets_delay_us(0.9);
مشکلات این روش:
- FreeRTOS هر لحظه ممکنه Task رو Interrupt کنه (Context Switch).
- WiFi Stack و سایر تسکها CPU رو اشغال میکنن.
- حتی چند میکروثانیه تاخیر غیرمنتظره، کل زنجیره LED رو خراب میکنه (رنگهای اشتباه، پرش، فلیکر).
راهحل: پریفرال RMT (Remote Control Transceiver):
ESP32 یک واحد سختافزاری مستقل به نام RMT داره که اصلاً برای همین کار (تولید پالسهای دقیق زمانبندیشده) طراحی شده . در واقع این واحد ابتدا برای کنترل از راه دور مادونقرمز پیاده سازی شده بود، اما برای استفاده به منظور راه اندازی WS2812 هم عالیه.
| ویژگی | توضیح |
|---|---|
| مستقل از CPU | CPU یک بار داده رو در حافظهی RMT میریزه، خودش با کلاک سختافزاری پالس تولید میکنه |
| دقت بالا | Resolution قابل تنظیم (تو کد ما: 10MHz یعنی هر تیک = 100 نانوثانیه) |
| بدون قطعشدگی | هیچ Interrupt یا Task Switch نمیتونه پالس رو خراب کنه |
| DMA پشتیبانی | برای تعداد زیاد LED (صدها عدد)، میشه از DMA هم استفاده کرد |
چطور RMT کار میکنه؟ (پشت صحنه led_strip):
وقتی ()led_strip_refresh رو صدا میزنیم، این اتفاقات میافته:
-
کامپوننت led_strip، هر بیت از بافر RGB رو
به یک “RMT Symbol” (شامل مدت HIGH و LOW) تبدیل میکنه
-
این symbol ها در یک صف RMT قرار میگیرن:
بیت “1” ──► RMT Symbol { duration0: 9 ticks(HIGH), duration1: 4 ticks(LOW) }
بیت “0” ──► RMT Symbol { duration0: 4 ticks(HIGH), duration1: 9 ticks(LOW) }
-
واحد RMT این symbol ها رو مستقیماً روی GPIO
با دقت سختافزاری (بدون دخالت CPU) خروجی میده
-
در پایان، یک “Reset Symbol” (LOW طولانی) اضافه میشه
این دقیقاً همون چیزیه که در پیکربندی کد دیدیم:
led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 10 * 1000 * 1000, // هر تیک = 100ns → دقت کافی برای 0.35µs/0.9µs
.flags.with_dma = false, // برای ;100 LED نیازی به DMA نیست
};
ملاحظات سختافزاری مهم:
1. سطح ولتاژ (Voltage Level)
- ESP32 روی منطق 3.3 ولت کار میکنه.
- WS2812 معمولاً به سیگنال 5 ولت را برای تشخیص “HIGH” نیاز داره (البته اکثر ماژولها 3.3V رو هم به عنوان HIGH قبول میکنن، خصوصاً اگه فاصله سیم کوتاه باشه).
- برای اتصالات پایدارتر یا سیمهای طولانی، بهتره از Level Shifter (مثل 74HCT245 یا SN74AHCT125) استفاده کنید.
ESP32 (3.3V) ──► Level Shifter ──► WS2812 DIN (5V logic).
2. مصرف جریان
هر LED در حالت روشنایی کامل (سفید، 255,255,255) میتونه تا 60 میلیآمپر مصرف کنه:
مصرف کل = تعداد LED × 60mA (حداکثر)
مثال: 16 LED × 60mA = 960mA ≈ 1A
به همین دلیله که در پروژه، روشنایی رو پایین نگه داشتیم (مقدار v بین 50 تا 150 از 255) — هم برای جلوگیری از خیرگی چشم، هم کاهش مصرف جریان.
قانون طلایی: برای بیش از 8-10 LED، همیشه از منبع تغذیهی خارجی 5V استفاده کنید، نه از پین 5V بورد ESP32 (که معمولاً محدود به چند صد میلیآمپره).
3. خازن و مقاومت محافظ (اختیاری ولی توصیهشده).
چرا نمیشه چند LED رو با I2C یا SPI کنترل کرد؟
| ویژگی | WS2812 | I2C/SPI |
|---|---|---|
| تعداد سیم | 1 سیم داده | حداقل 2-4 سیم (Clock+Data) |
| آدرسدهی | بر اساس ترتیب زنجیره (فیزیکی) | آدرس منطقی (I2C) یا CS جدا (SPI) |
| کلاک | ندارد (Self-clocking با تایمینگ) | کلاک مجزا لازم دارد |
| افزودن LED جدید | فقط زنجیره رو ادامه بدید | نیاز به آدرسدهی/سیم جدید |
مزیت بزرگ WS2812: با یک پین GPIO میشه صدها LED رو کنترل کرد — چون تایمینگ خودش نقش کلاک رو بازی میکنه.
خلاصهی جریان کامل داده (از کد تا نور):
- کد شما:led_strip_set_pixel(led_strip, 5, 255, 0, 0).
- کامپوننت led_strip:مقدار RGB رو در بافر داخلی (index 5) ذخیره میکنه.
- صدا زدن led_strip_refresh():کل بافر (GRB) به RMT Symbol تبدیل میشه.
- واحد سختافزاری RMT:پالسهای دقیق (0.35µs/0.9µs) رو روی GPIO18 تولید میکنه.
- زنجیره فیزیکی WS2812:هر LED، 24 بیت مربوط به خودش رو میگیره،بقیه رو پاس میده به LED بعدی.
- IC داخلی هر LED:مقدار RGB رو به PWM تبدیل میکنه → نور قابل مشاهده 💡.
خب بریم سراغ کد پروژه.این پروژه ساختار مشابه ای با پروژه قبلی داره مخصوص در بخش راه اندازی ماژول RMTکه به اون نمیپردازیم و میتونید به پروژه زیر رجوع کنید.
تنظیمات وایفای:
برای اینکه بتونیم به برد متصل بشیم و وبسرور روی esp32 رو لود کنیم و افکتها و رنگها رو تغییر بدیم باید از طریق وایفای به برد متصل بشیم.
برای این کار نیاز هست تا esp32 رو در مد WIFI_STA کانفیگ کنیم تابتونیم با گوشی یا از طریق Access Point یا مودم به وب سرورمون دسترسی داشته باشیم.
این کار در eps32 از طریق Event Loop و TaskNotification به وسیله FreeRTOS هندل میشود.
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "اتصال قطع شد، تلاش مجدد...");
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ESP_LOGI(TAG, "وایفای متصل شد، IP دریافت شد");
xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
}
}
در تکه کد بالا با چک کردن event های وایفای وضعیت اتصال رو چک میکنیم تا از اتصال و برقرای ارتباط مطمعن بشیم.
void wifi_init_sta(void) {
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL));
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "در حال اتصال به وایفای...");
xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
}
در کد بالا نیز مراحل تنظیم ماژول وایفای در مد STA امده است. در بخش ssid و password باید اطلاعات مربوط به اکسس پوینت شما قرار بگیرد.
وب سرور:
تفاوت دیگری که این پروژه با پروژه قبلی دارد وجود یک وبسرور بر روی ESP32 میباشد.ما با استفاده از این وبسرور تنظیمات مورد نظر خودمون رو که شامل انتخاب افکت و رنگ ال ای دی هاست به ESP32 ارسال میکنیم.
برای داشتن وبسرور بر روی esp32 در این پروژه من از کامپوننت http_server ارائه شده همراه فریمورک IDFاستفاده میکنم کافیست کد زیر را به پروژه اضافه کنیم و هندلرهای متدهای مورد نیاز را بنویسیم.
httpd_handle_t start_webserver(void) {
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 8;
if (httpd_start(&server, &config) == ESP_OK) {
httpd_uri_t root_uri = { "/", HTTP_GET, root_handler, NULL };
httpd_uri_t color_uri = { "/api/color", HTTP_GET, color_handler, NULL };
httpd_uri_t brightness_uri = { "/api/brightness", HTTP_GET, brightness_handler, NULL };
httpd_uri_t speed_uri = { "/api/speed", HTTP_GET, speed_handler, NULL };
httpd_uri_t mode_uri = { "/api/mode", HTTP_GET, mode_handler, NULL };
httpd_register_uri_handler(server, &root_uri);
httpd_register_uri_handler(server, &color_uri);
httpd_register_uri_handler(server, &brightness_uri);
httpd_register_uri_handler(server, &speed_uri);
httpd_register_uri_handler(server, &mode_uri);
ESP_LOGI(TAG, "وبسرور با موفقیت راهاندازی شد");
}
return server;
}
بعد اضافه کردن کردن هندلرها پروژه را بیلد و فلش میکنم بعد از بالا امد برنامه وایفای به صورت خودکار در صورت درست بودن تنظیمات به APمتصل شده و یک ادرس IP دریافت میکند.
با استفاده از IPیافت شده و وارد کردن آن در مرورگر صفحه وبسرور ما لود شده و آماده استفاده میباشد.
با استفاده از پنل تنظیمات که تصویر آن را دربالا مشاهده میکنید میتوان به راحتی نوارهای LED WS2812 را کنترل کرد.
این پروژه اساس کار سیستمهای نورپردازی موجود در بازار میباشد که با اندکی تغییرات سخت افزاری میتوان تعداد دلخواه ال ای دی را درایو و کنترل نمود.




