در قسمت پنجم از آموزش LVGL به بررسی تنظیمات فریم بافر در LVGL پرداختیم. در این قسمت قصد داریم که درباره راهاندازی اولیه فریم بافر در LVGL صحبت کنیم. پس تا پایان این قسمت ما را همراهی کنید.
lv_disp_draw_buf_t استراکچر
ابتدا میریم سراغ استراکچر lv_disp_draw_buf_t که اولین استراکچر برای راهاندازی فریم بافر است. بهطورکلی، این استراکچر حاوی اطلاعات مربوط به بافر صفحهنمایش میباشد. همچنین، دارای یک سری متغیرها میباشد که این متغیرها زیرمجموعه این متغیر آرایهای هست که یک متغیر بر مبنای زبان C است که ما به آنها متغیر استراکچری میگوییم. البته شما میتوانید اسم آن را خودتان تغییر دهید.
این استراکچر بر مبنای lv_disp_draw_buf_t تعریف میشود و شامل آدرس بافر اول، آدرس بافر دوم، آدرس بافر فعال، تعداد بافرها بر پیکسل و یک سری متغیری که خود سیستم گرافیکی با آنها کار میکند، میباشد. تمامی این موارد در کد زیر آورده شده است:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | typedef struct _lv_disp_draw_buf_t { void * buf1; /**< First display buffer.*/ void * buf2; /**< Second display buffer.*/ void * buf_act; uint32_t size; /*In pixel count*/ volatile int flushing; volatile int flushing_last; volatile uint32_t last_area : 1; volatile uint32_t last_part : 1; } lv_disp_draw_buf_t; |
lv_color_t استراکچر
یک استراکچری دیگری نیز وجود دارد که شما باید با آن کار کنید، lv_color_t نام دارد. بهطورکلی، این استراکچر حاوی اطلاعات مربوط به رنگها میباشد و متناسب با کیفیت رنگ تعریف شده در کانفیگ تشکیل میشود.
lv_color_t یک استراکچری است که به شما اطلاعاتی درباره مفاهیم رنگ و اینکه سیستمتان با چه سیستم رنگی کار میکند، میدهد. تمامی رنگها در سیستم گرافیکی LVGL بر اساس lv_color به سیستم منتقل میشوند. این lv_color باتوجه به تنظیماتی که شما در جلسه قبل توی lv_confing یاد گرفتید، میتوانید این کیفیت رنگ را وارد کنید و خود سیستم گرافیکی باتوجه به کیفیت رنگی که انتخاب کردید، استراکچر lv_color_t را پیکربندی میکند.
زمانی که شما بهصورت ۳۲ بیت کار میکنید، این استراکچر به این صورت تعریف میشود که استراکچر اصلی حاوی یک استراکچر دیگر در هست که ch نام دارد و همچنین، یک متغییر ۳۲ بیتی به نام full در آن وجود دارد که همه اینها زیرمجموعه lv_color32_t هستند که با نام lv_color_t در سیستمتان معرفی میشود.
طبق کد زیر، در استراکچر اول اطلاعات رنگ بهصورت جزئی قرار دارد. یک متغیر دیگر در اینجا قرار دارد به نام full، این متغیر حاوی کد رنگ است. یعنی اگر شما در طول برنامه به مقدار یکی از رنگها نیاز داشتید، میتوانید از طریق متغیر channel یا همان ch، به رنگهای موجود دسترسی پیدا کنید و مقدار دهید و اگر به کل رنگ احتیاج داشتید از طریق full value میتوانید به رنگ موردنظر دسترسی پیدا کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | typedef union { struct { uint8_t blue; uint8_t green; uint8_t red; uint8_t alpha; } ch; uint32_t full; } lv_color32_t; typedef union { struct { uint8_t blue : 2; uint8_t green : 3; uint8_t red : 3; } ch; uint8_t full; } lv_color8_t; typedef union { struct { uint16_t blue : 5; uint16_t green : 6; uint16_t red : 5; } ch; uint16_t full; } lv_color16_t; |
ساخت بافر تصویر
اکنون شما باید یک بافر برای تصویر خود تعریف کنید. برای این کار ابتدا شما باید استراکچر مربوط به بافرتان را تعریف کنید. اصولاً بهصورت استاتیک هم تعریف میشود؛ چون قرار نیست جای دیگری از آن استفاده کنیم. همچنین، شما یک متغیر بر اساس lv_disp_draw_buf_t تعریف میکنید و یک نامی به آن اختصاص میدهید. در اینجا disp_buf1، متغییری است که با این استراکچر تعریف کردیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 1.One Buffer static lv_disp_draw_buf_t disp_buf1; static lv_color_t buf1[MY_HOR_RES * 100]; lv_disp_draw_buf_init(&disp_buf1, buf1, NULL, MY_HOR_RES * 100); 2.Two Buffer static lv_disp_draw_buf_t disp_buf1; static lv_color_t buf1 [MY_HOR_RES * 100]; static lv_color_t buf2 [MY_HOR_RES * 100]; lv_disp_draw_buf_init(&disp_buf1, buf1, buf2, MY_HOR_RES * 100); |
بعد از آن، باید بافرهایی با اندازه مشخص تعریف کنید. آن فریم بافرهایی است که در جلسه قبل براتون توضیح دادم. پس ابتدا یک متغیر آرایهای تعریف میکنید سپس از روی lv_color_t بهصورت اتوماتیک تشخیص میدهد که کدام رنگ است و این فرایند تشخیص توسط کامپایلر انجام میشود و شما فقط به آن یک اندازه و یک اسم میدهید. اصولاً اندازهی بافرها ضریبی از طول تصویر هستند. حالا در اینجا ما یک بافر تعریف کردیم.
بعد از اینکه بافر اول خود را تعریف کردید و قصد داشتید one buffer کار کنید، از دستور زیر استفاده میکنید:
1 | lv_disp_draw_buf_init(&disp_buf1, buf1, NULL, MY_HOR_RES * 100); |
و اگر نیاز داشتید double buffer کار کنید، از دستور زیر استفاده میکنیم که مشابه دستور قبل است با این تفاوت که یک بافر دیگر هم در آن تعریف میکنیم.
1 | lv_disp_draw_buf_init(&disp_buf1, buf1, buf2, MY_HOR_RES * 100); |
- این عدد 100 در دستور به دلخواه خود شماست و به رم میکروکنترلر بستگی دارد.
lv_disp_drv_t disp_drv استراکچر
بعد از تعریف بافر، باید به سیستمتان درایوری که قرار است بافرها را راه اندازی کند، معرفی کنید. از طریق استراکچر lv_disp_drv_t disp_drv شما استراکچر مربوط به درایورتان را تعریف میکنید.
بهطورکلی، این استراکچر حاوی تنظیمات و توابع و اطلاعات مربوط راهاندازی نمایشگر است که این استراکچر، یک استراکچر بزرگ است و به طور کلی شامل موارد زیر است:
- توابع فلاش و راه اندازی تصویر
- اطلاعات تصویر مانند طول و عرض و dpi صفحهنمایش
- چرخش و آنتی لایزینگ نوشتهها و ترنسپرنت (شفافیت)
نمونه کد تعریف یک درایور تصویر:
1 2 3 4 5 6 7 8 9 10 11 12 13 | static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.draw_buf = &disp_buf1; disp_drv.flush_cb = display_flush; disp_drv.hor_res = MY_HOR_RES; disp_drv.ver_res = MY_VER_RES; lv_disp_drv_register(&disp_drv); |
برای اینکه شما بتوانید یک display drive تعریف کنید، باید ابتدا استراکچر آن را تعریف کنید. استراکچر آن با عنوان lv_disp_drv_t تعریف میشود.
سپس باید یک سری اطلاعاتی مربوط به تصویرتان را به آن بدهید. اولین مورد draw_buf است. دومین مورد معرفیکردن تابع flush به سیستم است که تابع flush یک تابع call back میباشد. تابع call back را در ادامه توضیح خواهیم داد.
بعد از اینکه شما مشخصات اولیه تصویر را به سیستم دادید، باید display را در سیستم register (ثبت) کنید. سپس باید بریم سراغ تابع display_flush. البته قبل از اینکه ما بریم سراغ این تابع، باید یک متغیر استراکچری دیگر (به نام lv_area_t) را توضیح دهیم.
استراکچر lv_area_t
این استراکچر حاوی اطلاعات مربوط به یک سطح است؛ بهعبارتدیگر، این استراکچر، سطح را مشخص میکند. سیستم گرافیکی LVGL هر کجا که خواست یک سطحی را مشخص کند با lv_area_t آن را به شما ارائه میکند. یا اگر یک سطحی داشته باشید با این متغیر به شما معرفی میشود.
استراکچر lv_area_t دارای X2، X1، Y2 و Y1 است که توسط متغیر coordinate یا lv_coord_t مشخص میشود که در نهایت، lv_area_t را تشکیل میدهد. به شکل زیر توجه کنید که در آن جایگاه X2، X1، Y2 و Y1 را نشان میدهد:
1 2 3 4 5 6 7 8 9 10 11 | typedef struct { lv_coord_t x1; lv_coord_t y1; lv_coord_t x2; lv_coord_t y2; } lv_area_t; |
حالا میریم سراغ تابع display_flush.
تابع display_flush
ورودیهای تابع display_flush به شما یک درایور، یک area و یک color_p میدهد. این تابع یک سری اطلاعات به شما میدهد که شما به کمک این اطلاعات دادههای تصویر را به وضعیتهای پیکسلی تبدیل میکنید.
به شکل زیر توجه کنید:
در اینجا، area طول و عرض صفحه مستطیلی را مشخص میکند.
این دو سطح که در این شکل می بینید، با lv_area تعیین می شوند و اطلاعاتی که در این شکل هست با اشاره گر color_pکه از نوع lv_color_t هست، به آن اشاره می شود.
حالا بریم سراغ نوشتن disp_flush.
به کد زیر (شیوه نوشتن تابع disp_flush) توجه کنید:
به کمک دو حالقه تو در تو به اندازه طول و عرض تصویر، اطلاعات تصویر را به صورت تک پیکسل به نمایشگر انتقال میدهد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){ int32_t x; int32_t y; for(y = area->y1; y <= area->y2; y++) { for(x = area->x1; x <= area->x2; x++) { LCD_DrawPoint(x, y, color_p->full); color_p++; } } lv_disp_flush_ready(disp_drv); } |
✅ نکته
* اگر به کد دقت کنید میبینید که آدرس به صورت color_p++ نوشته شده که یعنی این آدرس هر بار یک واحد افزایش پیدا میکند.
* این روش راحت ترین و عمومی ترین روش نوشتن تابع disp_flush در همه سیستم های گرافیکی هست.
حالا بریم سراغ تابع disp_flush در یک مد دیگر.
⚡ توجه
همانطور که گفته شد، روش قبلی یک روش عمومی است و همچنین، کندترین روش میباشد؛ زیرا دستور LCD_DrawPoint(x, y, color_p->full); موجود در این روش، خیلی سربار دارد و باعث کندشدن سیستم شما میشود.
disp_flush SPI LCD
بهطورکلی نمایشگرهایی که درایور دارند دارای یک قابلیت هستند؛ اینکه شما می توانید سطح مشخص کنید و بدون سربار دستوری بعد از معرفی یک سطح فقط داده خود را بریزید.
در این حالت، اولین کاری که شما باید بکنید، این است که از طریق LCD_Address_Set (یک دستور عمومی است و در همه نمایشگرها وجود دارد) اون area ایی که سیستم گرافیکیتان به شما داده است را set کنید. (مطابق کد زیر)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){ uint32_t x; uint32_t y; LCD_Address_Set(area->x1, area->y1, area->x2, area->y2); uint32_t size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 2; HAL_SPI_Transmit(&hspi2, (uint8_t *)color_p, size,1000); lv_disp_flush_ready(disp_drv); } |
اکنون سطح معرفی شد. سپس سایز یا اندازه area را با استفاده از X و Y تعیین میکنیم. سپس این دادهها بهصورت SPI به LCD ارسال میشوند. بعد از آن این دادهها به صفحهنمایش میرود. وقتی این دادهها به صفحهنمایش انتقال یافت، شما از طریق دستور lv_disp_flush_ready(disp_drv) به سیستم گرافیکی خود اعلام میکنید که تابع disp_flush کارش را انجام داده است.
disp_flush Parallel LCD
اکنون حالت نمایشگر پارالل (Parallel) را بررسی میکنیم که بهطورکلی، اکثر نمایشگرها یا Parallel هستند یا SPI.
به کد زیر دقت کنید که در ادامه آن را برای شما توضیح دادهایم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){ uint32_t i; uint32_t size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); LCD_Address_Set(area->x1, area->y1, area->x2, area->y2); LCD_WR_REG(GRAM_WRITE); for(i = 0; i < size; i++) { LCD_WR_DATA(color_p->full); color_p++; } lv_disp_flush_ready(disp_drv); } |
در این حالت همانند حالت SPI، اولین کاری که باید بکنید این است که تعداد پیکسلهایتان را مشخص کنید. سپس در LCD، دستور set windows را میزنید و یکسطحی در LCD خود تعریف میکنید.
بعد از آن، به کمک دستور LCD_WR_REG(GRAM_WRITE) نمایشگر را در حال دریافت داد قرار میدهید.
سپس بر اساس یک حلقه و دستور LCD_WR_DATAرنگهای درون color_p را به نمایشگر منتقل میکنید.
در نهایت، دستور lv_disp_flush_ready(disp_drv) را قرار می دهید و از طریق این دستور، شما به سیستم گرافیکی خود اعلام میکنید که انتقال بافر بهصورت سختافزاری تمام شده است.
خب…
این قسمت از آموزش سیستم گرافیکی LVGL هم به پایان رسید. حتماً در ادامه این آموزش، با سیسوگ همراه باشید.