در این مقاله راه اندازی Ws2812 با STM32 را آموزش داده ایم. اگر با LED و ماژول Ws2812 آشنا باشید، حتما میدانید که این ال ای دی های قابل کنترل، توانایی تبدیل سیگنال های الکترونیکی به رنگ های مختلف را دارند به همین خاطر در بسیاری از پروژه ها به کار گرفته میشوند.
در این مطلب آموزشی از PWM با DMA برای ارسال دیتا به ماژول Ws2812 استفاده کرده ایم و البته همین آموزش برای Ws2812B نیز قابل استفاده است. با ما همراه باشید، در ادامه با مراحل مختلف راه اندازی این LED ها آشنا خواهید شد.
راه اندازی CubeMX
همانطور که در آموزش های قبل گفته شد، قبل از هرچیز باید کلاک را تنظیم و راه اندازی کنیم. توجه داشته باشید که APB2 با کلاک 72 Mhz کار میکند و در تصویر زیر میتوانید آن را مشاهده کنید.
Timer1 را روی حالت PWM Output تنظیم کرده و پس از فعال کردن آن، Prescaler را روی صفر قرار میدهیم. همچنین طبق تصویر زیر، لازم است از اینکه ARR روی 72-1 قرار داشته باشد اطمینان حاصل کنیم.
- تایمر 1 با APB2 مرتبط بوده و به همین خاطر در ابتدا با فرکانس 72 مگاهرتز کار خواهد کرد.
- این که Prescaler را روی صفر قرار داده ایم، به این معنی است که فرکانس را به 1 تقسیم کرده ایم و به همین خاطر فرکانس هنوز 72 مگاهرتز خواهد بود.
- به وسیله ARR 90 فرکانس را به مقدار مورد نظرمان یعنی 90 کاهش داده و در نتیجه 72/90 برابر با 800 کیلوهرتز خواهد بود.
- همچنین ARR 90 حداکثر دیوتی سایکل نیز بوده و به این معنی است که 100 درصد دیوتی سایکل برابر 90 خواهد بود. (در CCR)
- حالا برای تغییر دیوتی سایکل لازم است درصد را با توجه به 90 محاسبه کنیم.
- مثلا اگر میخواهیم دیوتی سایکل 30 درصد باشد، باید از عدد 27 استفاده کنیم و این عدد برای دیوتی سایکل 70 برابر 63 خواهد بود.
پس از انجام مراحل بالا، باید DMA را تنظیم کنیم.
- DMA را برای Cannel 1 تایمر 1 تنظیم کرده ایم.
- تا زمانی که دیتا به پریفرال ارسال میشود، به Memory to peripheral نیاز خواهیم داشت.
- از آنجایی که میخواهیم DMA دیتا را انتقال دهد، مطابق تصویر از Normal Mode استفاده میکنیم و زمانی که دستور ارسال شود این کار انجام خواهد شد.
- همچنین عرض دیتا را روی half word قرار داده ایم و البته دلیل مشخصی ندارد.
فرایند راه اندازی حالا تکمیل شده و میتوانیم سراغ دیتاشیت برویم و نکات مهم را بررسی کنیم.
اطلاعات ضروری بر اساس دیتاشیت
طبق تصویر زیر که در دیتاشیت قرار گرفته است، میتوانید نحوه ارسال صفر و یک به دستگاه را مشاهده کنید.
- همانطور که در تصویر مشخص شده، برای ارسال 0 لازم است پالس ارسالی به مدت 0.4 میکروثانیه 1 باشد و سپس به مدت 0.85 میکروثانیه صفر باشد.
- به این ترتیب، تقریبا یک سوم پالس 1 خواهد بود.
- همینطور اگر بخواهیم عدد 1 را ارسال کنیم لازم است دو سوم پالس ارسالی 1 باشد که در تصویر بالا نیز مشخص شده است.
- کد ریست شامل یک پالس 0 به مدت 50 میکروثانیه است.
چیدمان بیت ها طبق تصویر زیر خواهد بود:
- طبق دیتاشیت، هر دیتای ارسالی از 24 بیت تشکیل شده است که با رنگ های سبز و قرمز و آبی مشخص شده است.
- ابتدا بیت های مرتبط با رنگ سبز (MSB) ارسال میشوند.
- دیتای همه LED ها به صورت یکجا ارسال میشود. مثلا اگر 12 ال ای دی را کنترل می کنیم، لازم است همه 12 دیتا (یعنی 288 بیت) را پشت سر هم ارسال کنیم.
- درایور Ws2812 دیتا را دریافت کرده و 24 بیت اول را به LED اول اختصاص میدهد، 24 بیت دوم را به LED دوم اختصاص میدهد و… .
- پس از اینکه ارسال دیتا به پایان رسید و وضعیت همه LED ها مشخص گردید، باید سیگنال ریست را ارسال کنیم. یعنی برای مدت زمان 50 میکروثانیه پالس 0 ارسال گردد. اگر این موضوع نادیده گرفته شود، درایور دیتاهای ارسالی را به عنوان دیتای LED های بعدی تفسیر خواهد کرد.
این همه آن چیزی بود که نیاز داشتیم، حالا میتوانیم کدنویسی را شروع کنیم و ماژول Ws2812 را راه اندازی کنیم.
بخش کدنویسی
تعریف متغیرهای برنامه
defines
#define MAX_LED 8
#define USE_BRIGHTNESS 1
uint8_t LED_Data[MAX_LED][4];
uint8_t LED_Mod[MAX_LED][4]; // for brightness
- مشخص کنید چه تعداد LED را راه اندازی می کنید و حداکثر تعداد آنها را مقابل MAX_LED وارد کنید.
- جهت استفاده از کنترل روشنایی میتوانید USE_BRIGHTNESS را 1 قرار دهید و در غیر اینصورت آن را روی 0 تنظیم کنید.
- LED_Data و LED_Mode ماتریس هایی هستند که دیتاهای مرتبط با LED را ذخیره میکنند.
ذخیره سازی داده های LED
store LED data void Set_LED (int LEDnum, int Red, int Green, int Blue) { LED_Data[LEDnum][0] = LEDnum; LED_Data[LEDnum][1] = Green; LED_Data[LEDnum][2] = Red; LED_Data[LEDnum][3] = Blue; } #define PI 3.14159265 void Set_Brightness (int brightness) // 0-45 { #if USE_BRIGHTNESS if (brightness > 45) brightness = 45; for (int i=0; i<MAX_LED; i++) { LED_Mod[i][0] = LED_Data[i][0]; for (int j=1; j<4; j++) { float angle = 90-brightness; // in degrees angle = angle*PI / 180; // in rad LED_Mod[i][j] = (LED_Data[i][j])/(tan(angle)); } } #endif }
- LED_Data: برای ذخیره دیتای رنگ هرکدام از LED ها به صورت جداگانه استفاده می شود. ستون ها به ترتیب شماره LED، رنگ سبز، رنگ قرمز و رنگ آبی را نشان میدهند.
- LED_Mod: برای ذخیره سازی دیتای مربوط به LED اما مقدار مقیاسی و براساس تنظمات روشنایی مورد استفاده قرار میگیرد.
- کنترل روشنایی به سادگی قابل انجام است و از طریق تقسیم عدد واقعی به برخی اعداد صورت میگیرد. مثلا اگر میخواهیم نور قرمز در پرنور ترین و روشن ترین حالت ممکن قرار داشته باشد باید عدد 255 را قرار دهیم و برای اینکه روشنایی 25 درصد باشد، عدد 63 را قرار میدهیم.
- من از تابع Tangent برای ایجاد حالت طولی در مقیاس بندی استفاده می کنم.
- مقادیر روشنایی می تواند بین 0 تا 45 باشد.
تبدیل و ارسال دیتا به DMA
send data to LED
uint16_t pwmData[(24*MAX_LED)+50];
void WS2812_Send (void)
{
uint32_t indx=0;
uint32_t color;
for (int i= 0; i<MAX_LED; i++)
{
#if USE_BRIGHTNESS
color = ((LED_Mod[i][1]<<16) | (LED_Mod[i][2]<<8) | (LED_Mod[i][3]));
#else
color = ((LED_Data[i][1]<<16) | (LED_Data[i][2]<<8) | (LED_Data[i][3]));
#endif
for (int i=23; i>=0; i--)
{
if (color&(1<<i))
{
pwmData[indx] = 60; // 2/3 of 90
}
else pwmData[indx] = 30; // 1/3 of 90
indx++;
}
}
for (int i=0; i<50; i++)
{
pwmData[indx] = 0;
indx++;
}
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *)pwmData, indx);
while (!datasentflag){};
datasentflag = 0;
}
- در این قسمت از کدنویسی، رنگ ها را به صورت جداگانه به دیتای 24 بیتی تبدیل میکنیم.
- سپس دیتای هرکدام از رنگ ها را چک میکنیم.
- زمانی که بیت 1 باشد، 60 در بافر ذخیره میشود.
- چرا عدد 60 را قرار می دهیم؟ به این خاطر که برای ایجاد بیت 1، لازم است 2/3 پالس 1 باشد.
- مقدار ARR را روی 90 قرار داده بودیم و این 100 درصد دیوتی سایکل است. در واقع عدد 60 مقدار دو سوم دیوتی سایکل خواهد بود.
- همینطور توجه داشته باشید که من مقدار MSB را در LSB position ذخیره میکنم، چون طبق چیزی که ماژول نیاز دارد، لازم است MSB ابتدا ارسال گردد.
- طبق چیزی که قبل تر گفته شد، برای ارسال بیت 0 باید پالس با دیوتی سایکل یک سوم یعنی 90/3 ارسال گردد. به همین خاطر عدد 30 را قرار میدهیم.
- پس از اینکه همه مقادیر مرتبط با LED ها را ذخیره کردیم، لازم است مقادیر را ذخیره کنیم و سپس پالس ریست را ارسال کنیم. برای این کار پالس 0 به مدت 50 میکرو ثانیه ارسال میگردد.
- بدین منظور، میتوانیم 50 تا صفر را پشت سر هم ارسال کنیم. چون هر پریود 1.25 میکروثانیه است و با ارسال 50 صفر، پالس ریست توسط ماژول دریافت خواهد شد.
- در نهایت این دیتا به DMA ارسال میگردد.
متوقف کردن DMA
DMA callback
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
HAL_TIM_PWM_Stop_DMA(&htim1, TIM_CHANNEL_1);
datasentflag=1;
}
- پس از ارسال دیتا لازم است تابع Pulse Finish فراخوانی شود.
- با متوقف سازی DMA از انتقال دیتا به صورت خودکار جلوگیری خواهد شد.
تابع اصلی
main function
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM1_Init();
Set_LED(0, 255, 0, 0);
Set_LED(1, 0, 255, 0);
Set_LED(2, 0, 0, 255);
Set_LED(3, 46, 89, 128);
Set_LED(4, 156, 233, 100);
Set_LED(5, 102, 0, 235);
Set_LED(6, 47, 38, 77);
Set_LED(7, 255, 200, 0);
while (1)
{
for (int i=0; i<46; i++)
{
Set_Brightness(i);
WS2812_Send();
HAL_Delay (50);
}
for (int i=45; i>=0; i--)
{
Set_Brightness(i);
WS2812_Send();
HAL_Delay (50);
}
}
}
- در ابتدا، رنگ هرکدام از LED ها را تنظیم میکنیم.
- در حلقطه while مشخص کرده ایم هر 50 میلی ثانیه روشنایی LED ها افزایش یابد.
- در ادامه روشنایی را کاهش داده ایم.
- نکته مهم این است که پس از تنظیم مقادیر، لازم است تابع WS2812_Send فراخوانی شود تا اطلاعات توسط درایور LED دریافت گردد.
- همانطور که در کدنویسی مشاهده میکنید، من این تابع را داخل حلقه while فراخوانی کرده ام.
نتیجه گیری
خروجی پروژه را در تصویر زیر میتوانید مشاهده کنید:
از این که تا انتهای این مطلب آموزشی با ما همراه بودید از شما متشکریم. در این مقاله به آموزش راه اندازی WS2812 با STM32 پرداختیم و حالا این امکان را دارید که در پروژه های خود از این ماژول های فوق العاده استفاده کنیم. امیدواریم این مطلب آموزشی برای شما مفید باشد.
منبع: ترجمه از سایت controllerstech
اگر این نوشته برایتان مفید بود لطفا کامنت بنویسید.
سلام ، با تشکر از توضیحات روان و کامل شما .
چطور میتوانم از همکاری شما در یک پروژه کاملتر استفاده کنم ؟
بسیار خوب و زیبا و روان نوشته شده بود. اما یک سوال اگر من بخواهم ledها در یک وضعیت پایدار باشند و برای مدتی ( چندین ثانیه ) تغییر نکنند . آیا لازم ارسال کد تکرار و ارسال شود . یا نه فقط کافی است بخش ریست ارسال نشود . و هر وقت بخواهم تعیییرات انجام دهم ابتدا کد ریست و سپس ارسال اطلاعات جدید انجام دهم . از هم اکنون تشکر آموزش و پاسخ شما