در دو قسمت قبلی در مورد ساخت wave player با stm32 صحبت کردیم، در این بخش میخواهیم یک رابط گرافیکی برای wave player طراحی کنیم پس با ما همراه باشید.
برای نمایش اطلاعات گرافیکی مورد نظر از نمایشگر Nokia 1661 استفاده میکنیم که در این مقاله شرح داده شده است و کتابخانه آن قابل دانلود است.
پس برای شروع کار، مثل گذشته به سراغ ایجاد پروژه میرویم.
ایجاد پروژه
در این قسمت نیز برای سهولت کار فایلهای مربوط به پروژه قبلی را کپی میکنیم و سپس تغییرات موردنیاز را در آن اعمال میکنیم. چون در کتابخانه مذکور، پایههای موردنیاز برای کار با LCD قابل تنظیم هستند، نیازی به تغییر پروژه از طریق Cube MX نیست و بهسادگی به سراغ نوشتن کد میرویم.
نوشتن کد پروژه
ابتدا فایلهای کتابخانه nokia1661 را به پوشه پروژه اضافه میکنیم، محتویات این پوشه باید بهصورت زیر باشد:
یک پوشه دیگر نیز برای نوشتن توابع مورد نیاز برای رابط گرافیکی ایجاد میکنیم.
اکنون وارد فایل nokia1661_Hw.h میشویم تا تغییرات مورد نیاز را در آن اعمال کنیم. ابتدا نوع میکروکنتر را STM انتخاب میکنیم؛
//#define LCD_AVR_HW 1 #define LCD_STM_HW 1
سپس پورت و پایههای مربوط به LCD را مشخص میکنیم (در اینجا پایههای PC1 تا PC4 انتخابشدهاند) همچنین کتابخانه stm اضافهشده را کامنت میکنیم و بهجای آن بخشهای موردنیاز را قرار میدهیم:
#include <stdint.h> #include "stm32f1xx_ll_gpio.h" #include "stm32f1xx_ll_rcc.h" //#include <stm32f10x.h> #include <string.h> #define LCD_PORT GPIOC #define LCD_GPIO_Enable() RCC->APB2ENR |= (1<<16) #define LCD_RST 4 #define LCD_CS 3 #define LCD_SDA 2 #define LCD_CLK 1
حالا باید به سراغ فایل nokia1661_lcd_driver.c برویم. در این فایل برای نمایش کاراکتر و رشته از تابعی استفادهشده که از فونت موردنظر ما پشتیبانی نمیکند. فونت موجود در کتابخانه دانلود شده نیز برای این نمایشگر کمی کوچک به نظر میرسد. پس برای خوانایی بهتر و داشتن رابط گرافیکی زیباتر تابعی برای نمایش فونت 8 در 15 را جایگزین تابع موجود میکنیم؛
void nlcdChar(rgb_color16bit color, char ch) { ch = ch - ' '; uint16_t index = ch * 17; uint8_t cWidth = vga8x15[index]; _nlcdSetWindow(_lcd_data.charx, _lcd_data.chary, cWidth + 1, 15); // shift char x _lcd_data.charx += cWidth + 1; if(_lcd_data.charx + cWidth + 1 >= nlcdGetWidth()) { // zero char x _lcd_data.charx = 0; // shift char y _lcd_data.chary += 15; if(_lcd_data.chary + 15 >= nlcdGetHeight()) { _lcd_data.charx = 0; _lcd_data.chary = 0; } } for(int j = 0; j < 8; j++) { for(int i = 1; i < (cWidth * 2 + 1); i+= 2) // i += 2; and relative changes; { uint8_t byte = vga8x15[index + i]; uint8_t shift = j; if(j > 7) shift = j - 8; uint8_t byteSelect = 1 << shift; bool bitIsOne = byte & byteSelect; if(bitIsOne) { _nlcdSendPixel(color); } else { _nlcdSendPixel(_lcd_data.bg_color); } } _nlcdSendPixel(_lcd_data.bg_color); } for(int j = 8; j < 16; j++) { for(int i = 2; i < (cWidth * 2 + 1); i+= 2) // i += 2; and relative changes; { uint8_t byte = vga8x15[index + i]; uint8_t shift = j; if(j > 7) shift = j - 8; uint8_t byteSelect = 1 << shift; bool bitIsOne = byte & byteSelect; if(bitIsOne) { _nlcdSendPixel(color); } else { _nlcdSendPixel(_lcd_data.bg_color); } } _nlcdSendPixel(_lcd_data.bg_color); } }
باید فونت مورد نظرمان را نیز به این فایل اضافه کنیم. این فونت از این لینک یا از لینک گیتهاب در انتهای متن قابل دریافت است؛
#include "BigFont.h"
کار ما با این فایل نیز تمامشده است. برای زیبایی رابط گرافیکی wave player، میخواهیم یک عکس پسزمینه هم به پروژه اضافه کنیم. برای این منظور یک تصویر با عرض 121 پیکسل و ارتفاع 60 پیکسل در نظر گرفتیم (میتوانید تصویر دلخواهی با ابعاد متفاوت را به آرایه 16 بیتی RGB تبدیل کنید و یا از همین تصویر موجود در فایل پروژه استفاده کنید). در پوشهای که برای رابط گرافیکی ایجاد کردیم یک فایل برای ذخیره اطلاعات این تصویر میسازیم (waveplayer_LCD_Wallpaper.h) و در اون اطلاعات مربوط بهاندازه تصویر و محل قرار گرفتن آن و همچنین داده مربوط به خود تصویر را ذخیره میکنیم؛
uint8_t wallPaperHeader[4] = {0x05,0x3a,0x79,0x3c}; const uint16_t wallPaper[7260] = { 0x18c3, 0x18c3, 0x18e3, 0x18e3, 0x2104, 0x2104, 0x2124, 0x2945, ...
حالا باید به سراغ فایل waveplayer_LCD_Utility.h برویم تا توابع موردنیاز را در آن اعلان کنیم؛
#include <stdint.h> #include "stm32f1xx_ll_utils.h" #include "nokia1661_lcd_driver.h" /********************/ /********///Functions: void delay_ms(uint32_t); void LCD_Show_image(uint8_t *, uint16_t *);
فایل waveplayer_LCD_Utility.h باید در nokia1661_lcd_driver.h نیز اضافه شود تا بتوان از تابع تأخیر استفاده کرد.
سپس ا توابع اعلانشده را در فایل waveplayer_lcd_driver.c تعریف میکنیم؛
#include "waveplayer_LCD_Utility.h" /********************/ /********///Functions: void delay_ms(uint32_t x) { LL_mDelay(x); } /* 16 bits array */ void LCD_Show_image(uint8_t *header, uint16_t *image) { uint8_t x_offset = header[0]; uint8_t y_offset = header[1]; uint16_t sizex = header[2]; uint16_t sizey = header[3]; uint16_t color16bit = 0; for(uint8_t j = 0; j <= sizey; j++) for(uint8_t i = 0; i <= sizex; i++) { color16bit = image[j * sizex + i]; nlcdPixel(x_offset + i, y_offset + j, color16bit); } }
اکنونکه تمامی توابع و اطلاعات موردنیاز را تعریف کردیم باید رابط گرافیکی را به wave player اضافه کنیم. میخواهیم روی نمایشگر اطلاعاتی مثل اسم فایل در حال پخش، مدتزمان فایل، زمان سپریشده، زمان باقیمانده و همچنین نواری برای نشان دادن پروسه را نمایش دهیم. بدین منظور ابتدا برای نمایش پس رنگ پسزمینه و اسم فایل به سراغ فایل WAV_Handler.c و تابع scan_files میرویم. در ابتدای این تابع LCD را راهاندازی میکنیم، جهت نمایش اطلاعات و رنگ پسزمینه را تعیین میکنیم؛
/* for displaying details on LCD */ nlcdInit(); nlcdSetBackgroundColor(LCD_VGA_GRAY); nlcdSetOrientation(LCD_ORIENTATION_180);
سپس در همین تابع و در قسمت مربوط به پخش فایل، قبل از پخش کردن فایل، اسم آن را نمایش میدهیم؛
if(strstr(&fno.fname[strlen(fno.fname) - 5], ".WAV")) { char *filePath = (char*) malloc(sizeof(char) * 128); snprintf(filePath, 127, "%s/%s", workPath, fno.fname); #if(Debug == 1) printf("we play: %s\n", filePath); #endif nlcdClear(); nlcdGotoCharXY(1,1); nlcdStringP(LCD_VGA_BLACK, PSTR("Now playing: ")); nlcdGotoCharXY(3,3); nlcdStringP(LCD_VGA_BLUE, PSTR(fno.fname)); wave_play(filePath); free(filePath); }
برای ادامه کار باید تابع wave_play را تغییر دهیم، اما قبل از آن، دو متغیر گلوبال برای نمایش دادن زمان تعریف میکنیم؛
/* LCD interface */ uint16_t elapsedHalfBuffers; //number of half_buffers that have been played volatile uint16_t counter = 0;
متغیر counter را باید در روال مربوط به systick و در فایل SD_Utility.c افزایش دهیم؛
void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ disk_timerproc(); counter++; /* USER CODE END SysTick_IRQn 0 */ /* USER CODE BEGIN SysTick_IRQn 1 */ /* USER CODE END SysTick_IRQn 1 */ }
فراموش نشود که برای جلوگیری از خطا در این قسمت باید متغیر را در این فایل با extern اعلان کنیم.
حالا به سراغ تابع wave_play میرویم. در این تابع متغیرهای موردنیاز برای نمایش زمان را تعریف میکنیم و سپس زمانها را در مکان مناسب نمایش میدهیم؛
char play_time[6] = {0}; char remaining_time[6] = {0}; char file_Duration[6] = {0}; uint32_t timeElapsed = 0, timeRemaining = 0; #define BytesInFile wavHeader1.dataHeader.Subchunk2Size #define ByteRate wavHeader1.signatureHeader.ByteRate #define fileDuration BytesInFile / ByteRate nlcdGotoCharXY(1,5); nlcdStringP(LCD_VGA_FUCHSIA, PSTR("Duration: ")); sprintf(file_Duration, "%d:%02d", fileDuration/60, fileDuration%60); nlcdStringP(LCD_VGA_FUCHSIA, PSTR(file_Duration));
از تابعی که نوشتیم برای نمایش تصویر پسزمینه استفاده میکنیم؛
LCD_Show_image(wallPaperHeader, (uint16_t*) wallPaper);
بعد از آن باید حاشیهی نوار پروسه را ایجاد کنیم؛
/* Progrees Bar border */ for(int i=0; i<120; i++) { nlcdPixel(5+i, 120, LCD_VGA_BLUE); nlcdPixel(5+i, 121, LCD_VGA_BLUE); } for(int i=0; i<16; i++) { nlcdPixel(5, 120+i, LCD_VGA_BLUE); nlcdPixel(6, 120+i, LCD_VGA_BLUE); } for(int i=0; i<120; i++) { nlcdPixel(5+i, 120+15, LCD_VGA_BLUE); nlcdPixel(5+i, 121+15, LCD_VGA_BLUE); } for(int i=0; i<16; i++) { nlcdPixel(5+120, 120+i, LCD_VGA_BLUE); nlcdPixel(6+120, 120+i, LCD_VGA_BLUE); }
در ادامه و در بدنه حلقه while(!(stop || next)) زمان و نوار پروسه را نمایش میدهیم همچنین متغیر استاتیک i را قبل از حلقه تعریف میکنیم؛
static UINT i = 0; while(!(stop || next)) { if(counter >= 1000) { counter = 0; timeElapsed = (elapsedHalfBuffers * (WavebufferLength / 2)) / ByteRate; timeRemaining = fileDuration - timeElapsed; sprintf(play_time, "%d:%02d", timeElapsed/60, timeElapsed%60); sprintf(remaining_time, "%d:%02d", timeRemaining/60, timeRemaining%60); nlcdGotoCharXY(1,18); nlcdStringP(LCD_VGA_BLACK, PSTR(play_time)); nlcdGotoCharXY(17,18); nlcdStringP(LCD_VGA_BLACK, PSTR(remaining_time)); uint8_t playPercent = (uint8_t) 100 * ((float)(elapsedHalfBuffers * (WavebufferLength / 2)) / BytesInFile); for(; i < 116 * playPercent / 100; i++) { nlcdPixel(7+i, 125, LCD_VGA_BLACK); nlcdPixel(8+i, 126, LCD_VGA_BLACK); nlcdPixel(9+i, 127, LCD_VGA_BLACK); nlcdPixel(10+i,128, LCD_VGA_BLACK); nlcdPixel(9+i, 129, LCD_VGA_BLACK); nlcdPixel(8+i, 130, LCD_VGA_BLACK); nlcdPixel(7+i, 131, LCD_VGA_BLACK); } }
تنها دستور باقیماند صفر کردن متغیر استاتیک i بعد از حلقه است؛
memset(&i, 0, sizeof(UINT));
در صورت اتصال صحیح نمایشگر به میکروکنترلر، باید اطلاعات موردنظر شبیه تصویر زیر برروی LCD نمایان شوند؛
لینک فایل این پروژه روی گیتهاب
منبع: سیسوگ