کنترل ماژول دایرهای WS2812 با ESP32 و ESP-IDF | افکتهای رنگی جذاب
کنترل ماژول دایرهای WS2812 با ESP32 و افکتهای رنگی جذاب (ESP-IDF)

معرفی پروژه
ماژولهای دایرهای WS2812 (که به نام NeoPixel Ring هم در بازار شناخته میشن) یکی از محبوبترین LEDهای قابل آدرسدهی برای پروژههای نورپردازی می باشند. این ماژولها معمولاً در سایزهای 8، 12، 16 و 24 LEDی عرضه میشوند و هر LED میتواند بیش از 16 میلیون رنگ را نمایش دهد.
- 🌈 رنگین کمان چرخان (Rainbow Rotation)
- 🔄 چرخش تک رنگ (Chase Effect)
- 🌊 موج رنگی (Color Wave)
- 💓 نبض رنگی (Breathing Effect)
نصب محیط توسعه ESP-IDF:
اگه قبلاً ESP-IDF رو نصب نکردید، مراحل زیر رو دنبال کنید:
Windows
# دانلود ESP-IDF installer از: https://dl.espressif.com/dl/esp-idf/
Linux/MacOs
mkdir -p ~/esp cd ~/esp git clone --recursive https://github.com/espressif/esp-idf.git cd esp-idf ./install.sh esp32 . ./export.sh
در مرحله بعدی کافیست اکستنش ESP-IDF را داخل VsCode نصب و فعال سازی نمایید.
ساخت پروژه:
در مرحله بعد باید یک پروژه ایجاد نمایید که به دو روش میتوانید این کار را انجام دهید:
روش اول استفاده از کلیدهای ترکیبی Ctr+Shift+P میباشد بعد از باز شدن منو گزینه ESP-IDF New Project را انتخاب نمایید.
در روش دوم در ترمینال VsCode میتوانید دستورات زیر را وارد نمایید.
idf.py create-project ws2812_ring_effects cd ws2812_ring_effects
مرحله 2: اضافه کردن کامپوننت led_strip
ESP-IDF یک کامپوننت داخلی برای WS2812 داره که از RMT استفاده میکنه.
فایل idf_component.yml رو در پوشهی main بسازید:
dependencies: espressif/led_strip: "^2.5.0"
بعد از ساخت فایل yml کافیست یکبار پروژه را بیلد نمایید تا کتابخانه های مورد نیاز دانلود و به پروژه اضافه شوند.
در نظر داشته باشید که در این مرحله بایستی به اینترنت متصل باشید.
بعد از بیلد موفق پروژه پوشه ای با نام managed_components به ساختار پروژه شما اضافه میشود که شامل کتابخانه های لازم و تعدادی مثال برای کار با Ws2812 در آن قرار دارد.
شرح عملکرد کد:
1. استفاده از کامپوننت led_strip:
کتابخانه ESP-IDF یک کامپوننت داخلی دارد که با استفاده از ماژول RMT میتوان ال ای دی های WS1228 و ال ای دی های مشابه رو راه اندازی کرد. استفاده از پریفال RMT دقیقترین راه برای کنترل WS2812 میباشد.
#include <stdio.h>; #include <math.h>; #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "led_strip.h" #include "esp_log.h"
2. تبدیل HSV به RGB:
برای اینکه رنگهای روانی داشته باشیم از مدل رنگیHSV (Hue, Saturation, Value) استفاده میکنیم.
و با استفاده از تابع hsv_to_rgb() فرمت رنگی HSV رو به RGB تبدیل میکنیم.
typedef struct {
uint8_t r, g, b;
} rgb_t;
rgb_t hsv_to_rgb(uint16_t h, uint8_t s, uint8_t v) {
rgb_t rgb;
uint8_t region, remainder, p, q, t;
if (s == 0) {
rgb.r = rgb.g = rgb.b = v;
return rgb;
}
region = h / 43;
remainder = (h - (region * 43)) * 6;
p = (v * (255 - s)) >> 8;
q = (v * (255 - ((s * remainder) >> 8))) >> 8;
t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8;
switch (region) {
case 0: rgb.r = v; rgb.g = t; rgb.b = p; break;
case 1: rgb.r = q; rgb.g = v; rgb.b = p; break;
case 2: rgb.r = p; rgb.g = v; rgb.b = t; break;
case 3: rgb.r = p; rgb.g = q; rgb.b = v; break;
case 4: rgb.r = t; rgb.g = p; rgb.b = v; break;
default: rgb.r = v; rgb.g = p; rgb.b = q; break;
}
return rgb;
}
3. افکتها:
قدم بعدی نوشتن توابع مورد نیاز برای پیاده سازی افکت های مورد نظرمون هست.
هر 50 میلی ثانیه یکی از افکتها فراخوانی میشود(20 فریم در ثانیه).
با گذشت 10 ثانیه افکت بعدی فراخوانی میشود.
افکت رنگین کمان:
void effect_rainbow_rotation(void) {
static uint16_t hue_offset = 0;
for (int i = 0; i &amp;amp;lt; LED_STRIP_LED_COUNT; i++) {
uint16_t hue = (hue_offset + (i * 255 / LED_STRIP_LED_COUNT)) % 256;
rgb_t color = hsv_to_rgb(hue, 255, 50); // روشنایی 50 (از 255)
led_strip_set_pixel(led_strip, i, color.r, color.g, color.b);
}
led_strip_refresh(led_strip);
hue_offset = (hue_offset + 2) % 256;
}
افکت چرخشی تک رنگ:
void effect_chase(void) {
static uint8_t position = 0;
static uint16_t hue = 0;
// turn of all led
led_strip_clear(led_strip);
// turn on led in daisy chain
rgb_t color = hsv_to_rgb(hue, 255, 100);
for (int i = 0; i &amp;lt; 3; i++) {
uint8_t led_index = (position + i) % LED_STRIP_LED_COUNT;
led_strip_set_pixel(led_strip, led_index, color.r, color.g, color.b);
}
led_strip_refresh(led_strip);
position = (position + 1) % LED_STRIP_LED_COUNT;
hue = (hue + 1) % 256;
}
افکت موج رنگی :
void effect_wave(void) {
static float phase = 0;
for (int i = 0; i &amp;lt; LED_STRIP_LED_COUNT; i++) {
float brightness = (sin(phase + (i * 3.14159 * 2 / LED_STRIP_LED_COUNT)) + 1) / 2;
uint8_t value = (uint8_t)(brightness * 100);
uint16_t hue = ((uint16_t)(phase * 10 + i * 10)) % 256;
rgb_t color = hsv_to_rgb(hue, 255, value);
led_strip_set_pixel(led_strip, i, color.r, color.g, color.b);
}
led_strip_refresh(led_strip);
phase += 0.1;
}
افکت نبض رنگی :
void effect_breathing(void) {
static float breath = 0;
static uint16_t hue = 0;
float brightness = (sin(breath) + 1) / 2;
uint8_t value = (uint8_t)(brightness * 150);
rgb_t color = hsv_to_rgb(hue, 255, value);
for (int i = 0; i &amp;lt; LED_STRIP_LED_COUNT; i++) {
led_strip_set_pixel(led_strip, i, color.r, color.g, color.b);
}
led_strip_refresh(led_strip);
breath += 0.05;
if (breath &amp;gt;= 6.28) {
breath = 0;
hue = (hue + 30) % 256;
}
}
بعد از نوشتن توابع مورد نیاز به سراغ پیاده سازی تنظیمات led_strip و پریفرال RMT و همچنین منطق برنامه در تابع app_main میرویم.
ابتدا تنظیمات مربوط به نوار LED رو ست میکنیم که من از پین شماره 18 استفاده کردم و تعداد LED ها هم 24 عدد میباشد .همچنین به این تکته دقت داشته باشید که WS2812 از توالی رنگ GRB استفاده میکند.
در مرحله بعد به سراغ ست کردن تنظیمات led_strip و تنظیمات پریفرال RMT میرویم که منبع کلاک و رزولوشن آن را تنظیم میکنیم.
led_strip_config_t strip_config = {
.strip_gpio_num = LED_STRIP_GPIO,
.max_leds = LED_STRIP_LED_COUNT,
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // WS2812 use GRB format
.led_model = LED_MODEL_WS2812,
.flags.invert_out = false,
};
led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 10 * 1000 * 1000, // 10MHz
.flags.with_dma = false,
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
ESP_LOGI(TAG, "LED Strip initialize (%d LED)", LED_STRIP_LED_COUNT);
اکنون به بخش انتهایی کد رسیدیم که وظیفه تعویض افکتها را در هر 10 ثانیه برعهده دارد.
while (1) {
uint32_t current_time = xTaskGetTickCount() * portTICK_PERIOD_MS;
// change effect every 10 seconde
if (current_time - last_switch > 10000) {
effect_mode = (effect_mode + 1) % 4;
last_switch = current_time;
ESP_LOGI(TAG, "Change effect to %d", effect_mode);
}
switch (effect_mode) {
case 0: effect_rainbow_rotation(); break;
case 1: effect_chase(); break;
case 2: effect_wave(); break;
case 3: effect_breathing(); break;
}
vTaskDelay(pdMS_TO_TICKS(50)); // 50ms = 20 FPS
نتیجه و تست :
بعد از آپلود کد، باید شاهد افکتهای زیر به ترتیب باشید:
- 0-10s: رنگکمان چرخان
- 10-20s: چرخش 3 LED با رنگ متغیر
- 20-30s: موج رنگی سینوسی
- 30-40s: نبض رنگی
سپس چرخه از اول تکرار میشه.
سوالات متداول (FAQ):
Q: چرا LED ها روشن نمیشن؟
- چک کنید GND مشترک باشه
- ولتاژ تغذیه حتماً 5V باشه
- پین GPIO درست متصل شده باشه
Q: چطور تعداد LEDها رو تغییر بدم؟
#define LED_STRIP_LED_COUNT 24 // به 24 تغییر بدید
Q: چطور سرعت افکتها رو کم یا زیاد کنم؟
- عدد
pdMS_TO_TICKS(50)درvTaskDelayرو تغییر بدید (کمتر = سریعتر) - یا ضرایب
+=در هر افکت رو تغییر بدید
Q: چطور فقط یک افکت اجرا بشه؟
خط effect_mode = (effect_mode + 1) % 4; رو کامنت کنید و مقدار دستی بدید:
effect_mode = 2; // فقط افکت موج
فایل کامل پروژه را از لینک زیر میتوانید دانلود نمایید.
