در قسمت قبلی یاد گرفتیم که چطور یک کد ساده blink را بهصورت بدون سیستمعامل (baremetal) بر روی f1c100 اجرا کنیم و دراینباره یک کتابخانه بسیار مفید را به شما معرفی کردیم. در این آموزش میخواهیم به سراغ راهاندازی گرافیک روی f1c100s با lvgl برویم و در این راستا یک نرمافزار کاربردی را هم برای طراحی گرافیک به کمک lvgl معرفی کنیم.
شاید برای شما مفید باشد: آموزش امبدد لینوکس از 0 تا 100
بررسی کد LVGL
در قسمت قبل گفتیم که کتابخانه F1C100s_projects شامل چه بخشهایی میباشد. بخش موردنیاز ما در این آموزش lvgl_demo میباشد که شامل یک مثال کامل برای راهاندازی گرافیک روی lcd است. ما در این بخش به بررسی آن میپردازیم.
ابتدا کد main.c را بررسی میکنیم:
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | #include "main.h" #include <stdint.h> #include <stdio.h> #include <string.h> #include "system.h" #include "f1c100s_timer.h" #include "f1c100s_intc.h" #include "io.h" #include "display.h" #include "arm32.h" #include "lvgl.h" #include "lv_demo_widgets.h" void timer_init(void); void timer_irq_handler(void); void lvgl_print_cb(lv_log_level_t level, const char *file, uint32_t line, const char *func, const char *buf); int main(void) { system_init(); // Initialize clocks, mmu, cache, uart, ... arm32_interrupt_enable(); // Enable interrupts printf("Hello from firmware!\r\n"); timer_init(); lv_init(); lv_log_register_print_cb(lvgl_print_cb); display_init_lvgl(); display_set_bl(100); //touch_init_lvgl(); lv_demo_widgets(); while (1) { lv_task_handler(); } return 0; } void timer_init(void) { // Configure timer to generate update event every 1ms tim_init(TIM0, TIM_MODE_CONT, TIM_SRC_HOSC, TIM_PSC_1); tim_set_period(TIM0, 24000000UL / 1000UL); tim_int_enable(TIM0); // IRQ configuration intc_set_irq_handler(IRQ_TIMER0, timer_irq_handler); intc_enable_irq(IRQ_TIMER0); tim_start(TIM0); } void timer_irq_handler(void) { lv_tick_inc(1); tim_clear_irq(TIM0); } void lvgl_print_cb(lv_log_level_t level, const char *file, uint32_t line, const char *func, const char *buf) { /*Use only the file name not the path*/ size_t p; for (p = strlen(file); p > 0; p--) { if (file[p] == '/' || file[p] == '\\') { p++; /*Skip the slash*/ break; } } static const char *lvl_prefix[] = { "Trace", "Info", "Warn", "Error", "User" }; printf("%s: %s \t(%s #%lu %s())\r\n", lvl_prefix[level], buf, &file[p], line, func); } |
به هدرها فقط هدر lv_demo_widgets.h اضافهشده که گرافیک اصلی آنجا طراحیشده است. خود کتابخانه lvgl برای نمایش انیمیشنها و زمانبندی داخلی نیاز داره که تابع lv_tick_inc رو هرچند میلیثانیه یک بار صدا بزنیم، برای این کار هم از تایمر استفادهشده و در وقفه تایمر این تابع صدا زده میشه. تابع بعدی که عملیات اصلی طراحی گرافیک را اجرا میکند تابع lv_demo_widgets میباشد که درواقع از هدر lv_demo_widgets.h فراخوانی میشود.
کامپایل و اجرای کد
کامپایل و اجرای این کد کاملاً مثل کامپایل و اجرای کد blink میمونه که در قسمت قبلی مفصل بررسی کردیم فقط این بار باید حواسمون باشه که بهجای اسمهای پروژه blink اسمهای پروژه lvgl_demo رو وارد کنیم.
1 2 3 4 5 6 7 | #compile cd F1C100s_projects-master/LVGL_demo make -j4 #program sunxi-fel -p spiflash-write 0x00000 simple-loader.bin sunxi-fel -p spiflash-write 0x10000 LVGL_demo.bin |
این هم یک تصویر از خروجی برنامه روی lcd
معرفی نرم افزار برای کتابخانه lvgl
همیشه طراحی خود گرافیک جزو بخشهای زمانبر یک پروژه دارای گرافیک هست. اما به کمک نرمافزار طراحی گرافیک این کار با سرعت خیلی بیشتری انجام میشه، خود lvgl نرمافزاری برای طراحی گرافیک نداره اما نرمافزارهایی هستند که به ما این قابلیت رو ارائه میدهند (مثل SquareLine و GUIGuider) ما در اینجا برای راهاندازی گرافیک روی f1c100s از نرمافزار GUIGUIDER شرکت nxp استفاده میکنیم.
ابتدا نسخه لینوکس نرمافزار را از این لینک دانلود کرده و با این دستور نصب میکنیم:
1 | sudo dpkg -i Gui-Guider-Setup-1.3.1-GA.deb |
احتمالاً بعد از نصب نیاز به زدن این دستور هم هست
1 | sudo apt --fix-broken install |
خب بعد از نصب gui guider یک محیط خیلی سادهای برای کاربر باز میشود که به این صورت هست:
بعد از انتخاب پروژه جدید و انتخاب ورژن 7 lvgl روی simulator و با مشخص کردن اسم پروژه و سایز lcd پروژه جدیدمون رو درست میکنیم. محیط پروژه نرمافزار خیلی ساده است و با عملیات درگ و دراپ میشود انواع widgets ها رو استفاده کرد.
بعد از ساختن گرافیک با کلیک روی دکمه سبزرنگ بالا کدتون کامپایل و اجرا میشه. توی قسمت code viewer میتونید تبدیل گرافیک به کد C تون رو ببینید و با باز کردن پروژه توی vscode میتونید کد ساختهشده رو ادیتش کنید.
پوشه پروژه ما شامل چند فولدر هست که لازمه یک توضیحی در مورد آنها بدهیم:
1 2 3 | cd ~/GUI-Guider-Projects/baremetal480 ls baremetal480.guiguider custom generated import lvgl lvgl-simulator temp |
فولدر ها:
- generated: شامل screen های ساختهشده توسط نرمافزار (درون این پوشه نباید بهصورت دستی چیزی ادیت کنیم، چراکه با کامپایل کد در نرمافزار تغییرات ما از بین میره)
- custom: تمام کدهای خودمون رو باید در این بخش وارد کنیم
- lvgl-simulator: کدهای شبیهساز
- lvgl: کتابخانه اصلی lvgl
- …
استفاده از نرم افزار برای F1c100s
تا اینجای کار کد برای محیط دسکتاپ ساخته شد که همونطور که گفتیم میتونید با زدن دکمه سبزرنگ شبیهسازی کد را اجرا کنید اما برای اجرا و کامپایل کد روی f1c100 باید کد را برای اون کاستوم کنید که برای این کار از این پروژه در گیت هاب من میتونید استفاده کنید که تغییرات لازم رو در اون انجام دادم.
1 2 | cd ~/GUI-Guider-Projects/ git clone https://github.com/MohMahdiKolahi/f1c100_baremetal_lvgl |
خب با دانلود کردن پروژه و با import فایل bare_metal_v7.guiguider در نرمافزار میتونید یک گرافیک ساده را مشاهده کنید که حاوی widgets های مختلفی هست ازجمله, sreen, button و chart در ادامه به بررسی کد f1c100_baremetal_lvgl میپردازیم.
بررسی کد انیمشن
گفتیم که کد گرافیکی ساختهشده بهوسیله نرمافزار gui guider در پوشه generated اعمال میشود و اگر ما بخواهیم تغییر در کد انجام بدهیم باید آن را درجایی غیر از پوشه generated انجام بدهیم تا با تغییر و ذخیره تغییرات نرمافزار کد اعمالی ما حذف نشود. برای این کار ما تغییراتمان را در custom.c اعمال کردیم. این کد انیمشنی را برای چرخاندن needle یا همان شمارنده عدد gauge طراحی میکند:
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 56 57 | /** * @file custom.c * */ /********************* * INCLUDES *********************/ #include <stdio.h> #include "lvgl/lvgl.h" #include "custom.h" /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ /********************** * STATIC VARIABLES **********************/ static void gauge_anim(lv_obj_t *gauge, lv_anim_value_t value); /** * Create a demo application */ void custom_init(lv_ui *ui) { lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, ui->screen_gauge2); lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)gauge_anim); lv_anim_set_values(&a, 0, 180); lv_anim_set_time(&a, 2500); lv_anim_set_playback_time(&a, 1000); lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); lv_anim_start(&a); } static void gauge_anim(lv_obj_t *gauge, lv_anim_value_t value) { lv_gauge_set_value(gauge, 0, value); //static char buf[64]; //lv_snprintf(buf, sizeof(buf), "%d", value); //lv_obj_t * label = lv_obj_get_child(gauge, NULL); //lv_label_set_text(label, buf); //lv_obj_align(label, gauge, LV_ALIGN_IN_TOP_MID, 0, lv_obj_get_y(label)); } |
در تابع custom_init اول یک شی از animation به نام a میسازیم سپس آن را init میکنیم مقدار آن را به gauge نسبت میدهیم بعد تابع صدازدنش را به تابع gauge_anim نسبت میدهیم سپس مقدار مینیمم و ماکزیمم آن را بین 0 تا 180 قرار میدهیم. در آخر، زمان طی کردن و تکرار انیمیشن را به ترتیب 2.5 و 1 ثانیه قرار میدهیم و آن را درنهایت start میکنیم.
بررسی کد uart و وقفه آن
در این کد سعی شده که با استفاده از وقفه Uart عددی که ما با استفاده از پورت سریال میفرستیم را بخواند و نتیجه عدد را روی یک نمایشگر gauge نشان دهد. تغییرات مربوط به یورات در فایل main.c نوشتهشده و تغییرات مربوط به تکان دادن needle در هنگام ارسال عدد بهوسیله پورت سریال در کد set_scr_screen.c نوشتهشده است. که اول کد main رو بررسی میکنیم:
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | #include "main.h" #include <stdint.h> #include <stdio.h> #include <string.h> #include "system.h" #include "f1c100s_timer.h" #include "f1c100s_intc.h" #include "io.h" #include "display.h" #include "arm32.h" #include "lvgl/lvgl.h" #include "lv_demo_widgets.h" #include "gui_guider.h" #include "events_init.h" #include "custom.h" #include "f1c100s_uart.h" static void hal_init(void); void timer_init(void); void timer_irq_handler(void); void muart_init(void); void uart0_irq_handler(void); void uart2_irq_handler(void); #define UART_MAX 100 uint8_t uart_buffer[UART_MAX]; uint16_t uart_buffer_ind = 0; void lvgl_print_cb(lv_log_level_t level, const char *file, uint32_t line, const char *func, const char *buf); lv_ui guider_ui; int main(int argc, char ** argv) { system_init(); /*Initialize LittlevGL*/ lv_init(); /*Initialize the HAL (display, input devices, tick) for LittlevGL*/ hal_init(); muart_init(); /*Create a GUI-Guider app */ setup_ui(&guider_ui); events_init(&guider_ui); custom_init(&guider_ui); //lv_gauge_set_value(&guider_ui.screen_gauge_1,0,50); while(1) { /* Periodically call the lv_task handler. * It could be done in a timer interrupt or an OS task too.*/ lv_task_handler(); } return 0; } /** * Initialize the Hardware Abstraction Layer (HAL) for the Littlev graphics library */ static void hal_init(void) { system_init(); // Initialize clocks, mmu, cache, uart, ... arm32_interrupt_enable(); // Enable interrupts printf("Hello from firmware!\r\n"); timer_init(); lv_init(); lv_log_register_print_cb(lvgl_print_cb); display_init_lvgl(); display_set_bl(100); } void timer_init(void) { // Configure timer to generate update event every 1ms tim_init(TIM0, TIM_MODE_CONT, TIM_SRC_HOSC, TIM_PSC_1); tim_set_period(TIM0, 24000000UL / 1000UL); tim_int_enable(TIM0); // IRQ configuration intc_set_irq_handler(IRQ_TIMER0, timer_irq_handler); intc_enable_irq(IRQ_TIMER0); tim_start(TIM0); } void uart_irq_handler(void) { uint8_t in_char = uart_get_rx(UART0); if (uart_buffer_ind < UART_MAX) { uart_buffer[uart_buffer_ind] = in_char; uart_buffer_ind++; if (in_char == '\r' || in_char == '\n') { uart_buffer[uart_buffer_ind] = '\0'; int BufferToInt; sscanf(uart_buffer, "%d", &BufferToInt); printf("Received ! %d \r\n", BufferToInt); uart_buffer_ind = 0; memset(uart_buffer, 0, UART_MAX); dosm(&guider_ui,BufferToInt); } } else { memset(uart_buffer, 0, UART_MAX); } //printf("Received ! %c\r\n",cha); } void muart_init(void) { uart_enable_interrupt(UART0, UART_IEN_RBF); intc_set_irq_handler(IRQ_UART0, uart_irq_handler); intc_enable_irq(IRQ_UART0); printf("uart enabled\r\n"); } void timer_irq_handler(void) { lv_tick_inc(1); tim_clear_irq(TIM0); } void lvgl_print_cb(lv_log_level_t level, const char *file, uint32_t line, const char *func, const char *buf) { /*Use only the file name not the path*/ size_t p; for (p = strlen(file); p > 0; p--) { if (file[p] == '/' || file[p] == '\\') { p++; /*Skip the slash*/ break; } } static const char *lvl_prefix[] = { "Trace", "Info", "Warn", "Error", "User" }; printf("%s: %s \t(%s #%lu %s())\r\n", lvl_prefix[level], buf, &file[p], line, func); } void sdelay(int loops) { __asm__ __volatile__ ("1:\n" "subs %0, %1, #1\n" "bne 1b":"=r" (loops):"0"(loops)); } |
تابع muart_init وظیفه راه اندازی اولیه وقفه یوارت را برعهده دارد. تابع uart_irq_handler کار اصلی خواندن پورت سریال و ذخیره ورودی بر روی uart_buffer را انجام میدهد که در نهایت یک رشته کاراکتری به ما ارسال میکند ولی ما برای نشان دادن عدد برروی gauge نیاز به int داریم که با استفاده از دستورات:
1 2 3 | int BufferToInt; sscanf(uart_buffer, "%d", &BufferToInt); printf("Received ! %d \r\n", BufferToInt); |
تبدیل به int میشود و با استفاده از دستور printf روی پورت سریال نشان داده میشود، تابع dosm(&guider_ui,BufferToInt) که در خط 116 فراخوانی شده در فایل set_scr_screen.c تعریف شده که دو ورودی میگیرد:
1 2 3 4 | void dosm(lv_ui *ui,uint8_t buffer) { lv_gauge_set_value(ui->screen_gauge_1, 0, buffer); } |
این تابع عدد ورودی ارسالی را دریافت کرده و بهجای متغیر buffer در تابع lv_gauge_set_value میگذارد که نهایتاً مقدار needle را روی گرافیک مشخص میکند.
توجه: نرمافزار guiguider تنها قابلیت طراحی گرافیک را دارد و کارهایی از قبیل ساخت انیمیشن باید بهصورت دستی انجام گردد. دقت کنید که تغییرات نرمافزار در فایلهای setup_scr_screen.c و setup_scr_screen2.c اعمال میشود.
بعدازاینکه کدمان را بررسی کردیم نوبت به کامپایل و اجرای آن میشود برای این کار باید به پوشه lvgl-simulator رفته و با دستور make فایل makefile که در آن قرار دارد را کامپایل کنیم:
1 2 | cd /path/to/f1c100_baremetal_lvgl/lvgl-simulator make |
نحوه اجرای برنامه روی spi nor flash دقیقاً مانند قسمت قبلی است و باید با دستور sunxi-fel انجام شود:
1 2 3 | cd /path/to/f1c100_baremetal_lvgl/lvgl-simulator/build sunxi-fel -p spiflash-write 0x00000 mysimple-loader.bin sunxi-fel -p spiflash-write 0x10000 LVGL_demo.bin |
با توجه به اعمالی که تابه اینجا انجام دادیم نحوه راهاندازی گرافیک روی f1c100s را فرا گرفتیم، لطفا نظرات و سوالات خود را با ما به اشتراک بگذارید.
در این بخش میتونید به همه قسمتهای این سری آموزش دسترسی پیدا کنید:
- کار با f1c100s بدون سیستم عامل (BareMetal) – قسمت اول
- کار با f1c100 به صورت baremetal – قسمت دوم (LVGL)
سلام دمتون گرم عالی بود
فقط اگه میشه لینک مستقیم نرم افزار gui guider توی سایت قرار بدید چون به خاطر تحریم ها و محدودیت های سایت nxp نمیشه دانلود کرد هر کاری کردم موفق نشدم
سلام و وقت بخیر . خیلی متشکرم بابت اطلاعات بسیار مفیدی که در اختیار همه گذاشتین .
من همه ی مراحل را انجام دادم و جواب گرفتم واقعا عالی بودین.
فقط الان به مرحله ای رسیدم که میخوام تاچ اسکرین GT911 رو به روش BareMetal راه اندازی کنم ولی توی راه اندازیش به مشکل خوردم .
البته کتابخانه های GT911 توی GitHub هست ولی باید دستورات i2c میکرو رو با اون کتابخونه ترکیب کرد.
ممنون میشم اگر راهکاری دارین برای این مشکل بفرمایین.
پیشاپیش متشکرم بخاطر سایت خوبتون و مطالب حرفه ای که ارایه میدین.
سلام. خواهش میکنم.
به نظر من xboot رو هم بررسی کنید . چراکه امکانات کامل تری داره و تاچ gt911 رو هم پشتیبانی میکنه
https://github.com/xboot/xboot
سلام
کتابخانه استفاده از ای سی تاچ NS2009 موجود نیست ؟
ممکنه نحوه استفاده از ای سی تاچ NS2009 وقتی بصورت Bare Metal استفاده میکنیم را راهنمایی بفرمایید.
سلام فکر نمیکنم نوشتن کتابخانه اش کار سختی باشه
باید ببینید با کدوم چیپ همخوانی داره و از درایور اون استفاده کنید.
ممون از وقتی که گذاشتید و تشکر بابت موضوع خوبتون
میخواستم ببینم Makefile ای که GUI guider قابلیت کراس کامپایل شدن داره من از ورژن 8 استفاده کردم آیا روال به همین صورت هستش ؟
ممنون میشم راهنمایی کنید
سلام.
نسخه 8 هم قابلیت کراس کامپایل داره و روالش به همین صورت هست
با سلام و عرض ادب
ممنون از زحماتتون و واقعا تشکر از اینکه این مطلب رو تهیه کردید
من حقیقتش با ورژن 8 پروژه رو بردم جلو و سیمولیت کردم و با بیلد روت هم یک کامپایلر درست کردم و و SDL رو هم اد کردم و برنامه رو با Make file ای که خود GUI guider درست کرده و GCC رو مقدارش رو با مقدار کراس کامپایلر عوض کردم و کامپایل کردم و کامپایل شد و بدون ارور انجام شد ولی متاسفانه وقتی رو برد میزارم و رانش میکنم هیچ اتفاقی نمیوفته میشه لطف کنید یک توضیحی در مورد روند انجام کار بگید ؟ آیا ورژن 8 با 7 در این زمینه فرقی داره ؟آیا make file ای که GUI guider ایجاد میکنه اصلا به درد برد میخوره ؟
ممنون
سلام
این پروژه ای که در گیت هاب قرار داده شده کانفیگ هاش برای استفاده به صورت baremetal تغییر کرده و توی کدهاش تغییر ایجاد شده
برای استفاده در لینوکس هم لازمه که کانفیگ هاش رو مطابق سیستم عاملتون تغییر بدید
سلام خسته نباشید
میخاستم با نرم افزار GUI GUIDER طراحی انجام بدم برای ESP32-S3
سردرگمم ک چجوری از فایل هایی ک GUI GUIDER خروجی میده توی اردوینو استفاده کنم
میشه راهنمایی کنید فایل های ک GUI GUIDER بهم خروجی میده هر کدوم برا چیه
و چجوری باید ازش توی اردوینو استفاده کنم
salam ..lotfan GUI Guider ro bara download bezariid.. mamnon. chon nemishe download kard..mersi
والله اونقدر لازم داشتم این مطلبرو..
چون من دلفی کارم .. و با لازارووس
کار میکردم با lvgl فقط مشکل ide داشتم ..
خیلی عالی بود.
تشکر کافی نیست میدونم..
عالی هستید.
نظر لطفتون هست 🙂