Ten post poświęcę na przygotowanie programu obsługującego zegar czasu rzeczywistego w mikrokontrolerze STM32F429I. Do projektu dodatkowo zostanie dołożona obsługa wyświetlacza oraz touchpada w oparciu o biblioteki HAL'a.
W tej części pominę inicjalizację RTC, ponieważ ona będzie włączona bezpośrednio w przygotowanym programie z pominięcie włączenia poprzez Cuba'a.
Program oraz jego główne funkcje:
Poniżej przedstawię poszczególne funkcje dla RTC, następnie przejdę do opisu touchpada oraz wyświetlacza.
Na samym początku układ RTC.
Poniżej funkcja włączająca cały układ:
Ustawienie zegarów:
Włączenie przerwań od RTC:
Obsługa Touchpada:
Na samym początku należy rozpocząć od inicjalizacji touchpada:
Funkcje ustawiające długość boków X oraz boku Y wyświetlacza. Następna pozwala na inicjalizację touchpada:
Obsługa Wyświetlacza:
Obsługa wyświetlacza za pomocą funkcji BSP wygląda następująco:
Najpierw inicjalizacja wyświetlacza:
Kolejna funkcja z biblioteki BSP służy do wybrania warstwy oraz bufora:
Następnie wykorzystywane są funkcje odpowiedzialne za wybranie aktywnej warstwy, włączenie wyświetlacza oraz zapełnienie ekranu wybranym kolorem
Do wyświetlania tekstu na ekranie wykorzystuje takie funkcje jak:
Tekst wyświetlany może też zostać zdefiniowany bezpośrednio w definicjach. Wtedy przyjmuje on następującą postać:
Do definicji podaje się bufor z danymi, które mają być wyświetlone na wyświetlaczu.
Teraz przejdę do obsługi dotyku. Klawisze zostały zdefiniowane w przygotowanej strukturze, która zawiera wszystkie potrzebne elementy.
W strukturze przechowywany jest numer przycisku, wskaźnik na funkcję obsługującą kliknięcie w obszarze przycisku, pozycję startową na osi X oraz osi Y, ostatnie dwa parametry zawierają szerokość oraz wysokość prosokąta. Wszystkie zdefiniowane przyciski są zebrane razem jako wskaźniki w allButtons[4].
Inicjalizacja przycisków przebiega w sposób następujący:
W pętli for następuje przejście pomiędzy wszystkimi przyciskami, po ich umieszczeniu na ekranie
Obsługa dotyku zrealizowana została na liczniku 6, jego inicjalizacja wygląda następująco:
W procedurze obsługi przerwania zrealizowałem aktualizowanie RTC oraz sprawdzanie każdego z przycisków z wywołanie obsługi kliknięcia:
Obsługa przycisku na kliknięcie. Jest on wywoływany w przerwaniu od przycisku, po sprawdzeniu miejsca jego kliknięcia.
Pliki z projektem można znaleźć na dysku Google pod tym linkiem.
Kwarc:
Jeśli chodzi o źródło taktowania układu to możliwe są dwa rozwiązania, kwarc wewnętrzny oraz zewnętrzny. Oba o wartościach 32.768 kHz. W obu płytkach discovery (STM32F407VG oraz STM32F429I) kwarc zewnętrzny nie został zamontowany, zostało na niego tylko przygotowane miejsce.
Zegar wewnętrzny posiada dosyć duży błąd oraz ulega sporym wahaniom wraz ze zmianami temperatury. Może to być dosyć mocno uciążliwie, jeżeli utrzymywanie poprawnego czasu jest bardzo ważne dla aplikacji. Ten problem można rozwiązać na dwa sposoby.
Pierwszy z nich polega na zamontowaniu stabilnego kwarcu zewnętrznego. Pozwoli on na znacznie dokładniejsze odmierzanie czasu. Drugi sposób natomiast dotyczy zostania przy kwarcu wewnętrznym natomiast przeprowadzanie aktualizacji czasu poprzez internet. Dla prostszych aplikacji sposób 1 jest zdecydowanie bardziej odpowiedni. Dla bardzo rozbudowanych układów, które już wykorzystują połączenie z internetem równie dobrze można wprowadzić aktualizację czasu.
Na obrazku powyżej przedstawiłem podłączenie kwarcu zaczerpnięte z dokumentacji do układu.
Drugim ważnym elementem jest podtrzymywanie czasu przez układ po zaniku zasilania. Na płytce nie wprowadzono możliwości dołączenia baterii. Odpowiednią podstawkę na baterie CR2032 należy zamontować na płytce w następujący sposób:
W pierwszej kolejności trzeba odlutować rezystor R52 (STM32F429I) bądź R26(STM32F407). Należy to zrobić ponieważ gdy odłączone będzie zasilanie to bateria będzie wykorzystywana do zasilania całego układu, drugim minusem jest to, że po ponownym załączeniu zasilania VDD będzie powodował ładowanie baterii. Co w przypadku standardowej CR2032 nie byłoby wskazane.
Po tej drobnej operacji koszyk z baterią należy przylutować np. do kondensatora podłączonego do pinu VBAT tzn. C28 (STM32F407 oraz STM32F429I) bądź bezpośrednio do pinu 6 mikrokontrolera.
Tworzenie projektu CubeMx:
W tej części pominę inicjalizację RTC, ponieważ ona będzie włączona bezpośrednio w przygotowanym programie z pominięcie włączenia poprzez Cuba'a.
Program oraz jego główne funkcje:
Poniżej przedstawię poszczególne funkcje dla RTC, następnie przejdę do opisu touchpada oraz wyświetlacza.
RTC programowanie:
Na samym początku układ RTC.
Poniżej funkcja włączająca cały układ:
- uint32_t RTC_Init(RTC_ClockSource_t source) //Inicjalizacja RTC
- {
- uint32_t status;
- RTC_time_t datatime;
- //--------------------------------------------------------------
- RTC_Handle.Instance = RTC;
- RTC_Handle.Init.AsynchPrediv = RTC_ASYNC_PREDIV;
- RTC_Handle.Init.SynchPrediv = RTC_SYNC_PREDIV;
- RTC_Handle.Init.HourFormat = RTC_HOURFORMAT_24;
- RTC_Handle.Init.OutPut = RTC_OUTPUT_DISABLE;
- RTC_Handle.Init.OutPutType = RTC_OUTPUT_TYPE_PUSHPULL;
- RTC_Handle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
- //--------------------------------------------------------------
- __HAL_RCC_PWR_CLK_ENABLE(); //PWR ON
- HAL_PWR_EnableBkUpAccess(); //BKP Domain
- status = HAL_RTCEx_BKUPRead(&RTC_Handle, RTC_STATUS_REG); //Status RTC
- //Sprawdzenie czy RTC zostalo juz zainicjalizowane
- if (status == RTC_STATUS_TIME_OK)
- {
- //Wlaczenie zegara wewnetrznego, jeśli taki został wybrany
- if (source == RTC_ClockSource_Internal)
- {
- RTC_Config(RTC_ClockSource_Internal);
- }
- HAL_RTC_WaitForSynchro(&RTC_Handle); //Oczekiwanie na synchronizacje RTC
- RTC_GetDateTime(&datatime, RTC_Format_BIN); //Pobranie daty oraz czasu
- __HAL_RCC_CLEAR_RESET_FLAGS(); //Wyczyszczenie flag resetu
- return 1; //Zwroc OK
- }
- else
- {
- RTC_Config(source); //Wlacz RTC zegar
- //Ustaw wartosci domyslne w strukturze daty
- RTC_DateStruct.Year = 0;
- RTC_DateStruct.Month = 1;
- RTC_DateStruct.Date = 1;
- RTC_DateStruct.WeekDay = RTC_WEEKDAY_TUESDAY;
- HAL_RTC_SetDate(&RTC_Handle, &RTC_DateStruct, RTC_FORMAT_BIN);
- //Ustaw czas wartosci domyslne
- RTC_TimeStruct.Hours = 0x00;
- RTC_TimeStruct.Minutes = 0x00;
- RTC_TimeStruct.Seconds = 0x00;
- RTC_TimeStruct.TimeFormat = RTC_HOURFORMAT_24;
- RTC_TimeStruct.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
- RTC_TimeStruct.StoreOperation = RTC_STOREOPERATION_RESET;
- //Ustaw czas
- HAL_RTC_SetTime(&RTC_Handle, &RTC_TimeStruct, RTC_FORMAT_BCD);
- //Wlacz RTC
- HAL_RTC_Init(&RTC_Handle);
- //Zapisz dane do back up register
- HAL_RTCEx_BKUPWrite(&RTC_Handle, RTC_STATUS_REG, RTC_STATUS_TIME_OK);
- //RTC byl juz zainicjalizowany
- return 0;
- }
- }
Ustawienie zegarów:
- void RTC_Config(RTC_ClockSource_t source)
- {
- RCC_OscInitTypeDef RCC_OscInitStruct;
- RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
- PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; //Wylaczenie PLL
- if (source == RTC_ClockSource_Internal) //LSI
- {
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI;
- RCC_OscInitStruct.LSIState = RCC_LSI_ON;
- PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
- }
- else //LSE
- {
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
- RCC_OscInitStruct.LSEState = RCC_LSE_ON;
- PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
- }
- HAL_RCC_OscConfig(&RCC_OscInitStruct); //Configuracja oscylatora
- HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); //Select peripheral clock
- __HAL_RCC_RTC_ENABLE(); //Enable RTC clock
- }
Włączenie przerwań od RTC:
- typedef enum
- {
- RTC_Interupt_Dis = 0x00,
- RTC_Interupt_60s,
- RTC_Interupt_30s,
- RTC_Interupt_15s,
- RTC_Interupt_10s,
- RTC_Interupt_5s,
- RTC_Interupt_2s,
- RTC_Interupt_1s,
- RTC_Interupt_500ms,
- RTC_Interupt_250ms,
- RTC_Interupt_125ms
- } RTC_Interupt_t;
- RTC_Resoult_t RTC_Interrupts(RTC_Interupt_t int_value)
- {
- uint32_t val_to_set;
- __HAL_RTC_WAKEUPTIMER_DISABLE(&RTC_Handle); //Disable interrupt
- __HAL_RTC_WAKEUPTIMER_DISABLE_IT(&RTC_Handle, RTC_IT_WUT);
- (EXTI->PR = RTC_EXTI_LINE_WAKEUPTIMER_EVENT); //Clear pending bit
- __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&RTC_Handle, RTC_FLAG_WUTF); //Clear flag
- if (int_value != RTC_Interupt_Dis) {
- if (int_value == RTC_Interupt_60s) { val_to_set = 0x3BFFF; }
- else if (int_value == RTC_Interupt_30s) { val_to_set = 0x1DFFF; }
- else if (int_value == RTC_Interupt_15s) { val_to_set = 0xEFFF; }
- else if (int_value == RTC_Interupt_10s) { val_to_set = 0x9FFF; }
- else if (int_value == RTC_Interupt_5s) { val_to_set = 0x4FFF; }
- else if (int_value == RTC_Interupt_2s) { val_to_set = 0x1FFF; }
- else if (int_value == RTC_Interupt_1s) { val_to_set = 0x0FFF; }
- else if (int_value == RTC_Interupt_500ms) { val_to_set = 0x7FF; }
- else if (int_value == RTC_Interupt_250ms) { val_to_set = 0x3FF; }
- else if (int_value == RTC_Interupt_125ms) { val_to_set = 0x1FF; }
- HAL_RTCEx_SetWakeUpTimer_IT(&RTC_Handle, val_to_set, RTC_WAKEUPCLOCK_RTCCLK_DIV8);
- HAL_NVIC_SetPriority(RTC_WKUP_IRQn, RTC_NVIC_PRIORITY, RTC_NVIC_WAKEUP_SUBPRIORITY);
- HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
- }
- return RTC_Result_Ok;
- }
Obsługa Touchpada:
Na samym początku należy rozpocząć od inicjalizacji touchpada:
- uint32_t BSP_LCD_GetXSize(void)
- {
- return LcdDrv->GetLcdPixelWidth();
- }
- uint32_t BSP_LCD_GetYSize(void)
- {
- return LcdDrv->GetLcdPixelHeight();
- }
- uint8_t BSP_TS_Init(uint16_t XSize, uint16_t YSize)
- {
- uint8_t ret = TS_ERROR;
- //Inicjalizacja wielkości ekranu x oraz y
- TsXBoundary = XSize;
- TsYBoundary = YSize;
- /* Read ID and verify if the IO expander is ready */
- if(stmpe811_ts_drv.ReadID(TS_I2C_ADDRESS) == STMPE811_ID)
- {
- TsDrv = &stmpe811_ts_drv; //Inicjalizacja struktury dla TS
- ret = TS_OK;
- }
- if(ret == TS_OK) //Inicjalizacja sterowników TS
- {
- TsDrv->Init(TS_I2C_ADDRESS);
- TsDrv->Start(TS_I2C_ADDRESS);
- }
- return ret;
- }
Funkcje ustawiające długość boków X oraz boku Y wyświetlacza. Następna pozwala na inicjalizację touchpada:
- uint32_t BSP_LCD_GetXSize(void)
- {
- return LcdDrv->GetLcdPixelWidth();
- }
- uint32_t BSP_LCD_GetYSize(void)
- {
- return LcdDrv->GetLcdPixelHeight();
- }
- uint8_t BSP_TS_Init(uint16_t XSize, uint16_t YSize)
- {
- uint8_t ret = TS_ERROR;
- //Inicjalizacja wielkości ekranu x oraz y
- TsXBoundary = XSize;
- TsYBoundary = YSize;
- /* Read ID and verify if the IO expander is ready */
- if(stmpe811_ts_drv.ReadID(TS_I2C_ADDRESS) == STMPE811_ID)
- {
- TsDrv = &stmpe811_ts_drv; //Inicjalizacja struktury dla TS
- ret = TS_OK;
- }
- if(ret == TS_OK) //Inicjalizacja sterowników TS
- {
- TsDrv->Init(TS_I2C_ADDRESS);
- TsDrv->Start(TS_I2C_ADDRESS);
- }
- return ret;
- }
Obsługa Wyświetlacza:
Obsługa wyświetlacza za pomocą funkcji BSP wygląda następująco:
Najpierw inicjalizacja wyświetlacza:
- uint8_t BSP_LCD_Init(void)
- {
- /* On STM32F429I-DISCO, it is not possible to read ILI9341 ID because */
- /* PIN EXTC is not connected to VDD and then LCD_READ_ID4 is not accessible. */
- /* In this case, ReadID function is bypassed.*/
- /*if(ili9341_drv.ReadID() == ILI9341_ID)*/
- /* LTDC Configuration ----------------------------------------------------*/
- LtdcHandler.Instance = LTDC;
- /*
- * Timing configuration (Typical configuration from ILI9341 datasheet)
- * HSYNC=10 (9+1) | HBP=20 (29-10+1) | ActiveW=240 (269-20-10+1) | HFP=10 (279-240-20-10+1)
- * VSYNC=2 (1+1) | VBP=2 (3-2+1) | ActiveH=320 (323-2-2+1) | VFP=4 (327-320-2-2+1)
- */
- LtdcHandler.Init.HorizontalSync = ILI9341_HSYNC; //Configure horizontal synchronization width
- LtdcHandler.Init.VerticalSync = ILI9341_VSYNC; //Configure vertical synchronization height
- LtdcHandler.Init.AccumulatedHBP = ILI9341_HBP; //Configure accumulated horizontal back porch
- LtdcHandler.Init.AccumulatedVBP = ILI9341_VBP; //Configure accumulated vertical back porch
- LtdcHandler.Init.AccumulatedActiveW = 269; //Configure accumulated active width
- LtdcHandler.Init.AccumulatedActiveH = 323; //Configure accumulated active height
- LtdcHandler.Init.TotalWidth = 279; //Configure total width
- LtdcHandler.Init.TotalHeigh = 327; //Configure total height
- /* Configure R,G,B component values for LCD background color */
- LtdcHandler.Init.Backcolor.Red= 0; //R Value
- LtdcHandler.Init.Backcolor.Blue= 0; //B Value
- LtdcHandler.Init.Backcolor.Green= 0; //G Value
- /* LCD clock configuration */
- /* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */
- /* PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 192 Mhz */
- /* PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 192/4 = 48 Mhz */
- /* LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_8 = 48/4 = 6Mhz */
- PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
- PeriphClkInitStruct.PLLSAI.PLLSAIN = 192;
- PeriphClkInitStruct.PLLSAI.PLLSAIR = 4;
- PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_8;
- HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
- //Polarity
- LtdcHandler.Init.HSPolarity = LTDC_HSPOLARITY_AL;
- LtdcHandler.Init.VSPolarity = LTDC_VSPOLARITY_AL;
- LtdcHandler.Init.DEPolarity = LTDC_DEPOLARITY_AL;
- LtdcHandler.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
- MspInit();
- HAL_LTDC_Init(&LtdcHandler);
- /* Select the device */
- LcdDrv = &ili9341_drv;
- /* LCD Init */
- LcdDrv->Init();
- /* Initialize the SDRAM */
- BSP_SDRAM_Init();
- /* Initialize the font */
- BSP_LCD_SetFont(&LCD_DEFAULT_FONT);
- return LCD_OK;
- }
Kolejna funkcja z biblioteki BSP służy do wybrania warstwy oraz bufora:
- void BSP_LCD_LayerDefaultInit(uint16_t LayerIndex, uint32_t FB_Address)
- {
- LCD_LayerCfgTypeDef Layercfg;
- //Layer Init
- Layercfg.WindowX0 = 0;
- Layercfg.WindowX1 = BSP_LCD_GetXSize();
- Layercfg.WindowY0 = 0;
- Layercfg.WindowY1 = BSP_LCD_GetYSize();
- Layercfg.PixelFormat = LTDC_PIXEL_FORMAT_ARGB8888;
- Layercfg.FBStartAdress = FB_Address;
- Layercfg.Alpha = 255;
- Layercfg.Alpha0 = 0;
- Layercfg.Backcolor.Blue = 0;
- Layercfg.Backcolor.Green = 0;
- Layercfg.Backcolor.Red = 0;
- Layercfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
- Layercfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
- Layercfg.ImageWidth = BSP_LCD_GetXSize();
- Layercfg.ImageHeight = BSP_LCD_GetYSize();
- HAL_LTDC_ConfigLayer(&LtdcHandler, &Layercfg, LayerIndex);
- DrawProp[LayerIndex].BackColor = LCD_COLOR_WHITE;
- DrawProp[LayerIndex].pFont = &Font24;
- DrawProp[LayerIndex].TextColor = LCD_COLOR_BLACK;
- //Dithering activation
- HAL_LTDC_EnableDither(&LtdcHandler);
- }
Następnie wykorzystywane są funkcje odpowiedzialne za wybranie aktywnej warstwy, włączenie wyświetlacza oraz zapełnienie ekranu wybranym kolorem
- void BSP_LCD_SelectLayer(uint32_t LayerIndex)
- {
- ActiveLayer = LayerIndex;
- }
- void BSP_LCD_DisplayOn(void)
- {
- if(LcdDrv->DisplayOn != NULL)
- {
- LcdDrv->DisplayOn();
- }
- }
- void BSP_LCD_Clear(uint32_t Color)
- {
- /* Clear the LCD */
- FillBuffer(ActiveLayer, (uint32_t *)(LtdcHandler.LayerCfg[ActiveLayer].FBStartAdress), BSP_LCD_GetXSize(), BSP_LCD_GetYSize(), 0, Color);
- }
Do wyświetlania tekstu na ekranie wykorzystuje takie funkcje jak:
- void BSP_LCD_DisplayChar(uint16_t Xpos, uint16_t Ypos, uint8_t Ascii)
- {
- DrawChar(Xpos, Ypos, &DrawProp[ActiveLayer].pFont->table[(Ascii-' ') *\
- DrawProp[ActiveLayer].pFont->Height * ((DrawProp[ActiveLayer].pFont->Width + 7) / 8)]);
- }
- void BSP_LCD_DisplayStringAtLine(uint16_t Line, uint8_t *ptr)
- {
- BSP_LCD_DisplayStringAt(0, LINE(Line), ptr, LEFT_MODE);
- }
- void BSP_LCD_DisplayStringAt(uint16_t X, uint16_t Y, uint8_t *pText, Text_AlignModeTypdef mode)
- {
- uint16_t refcolumn = 1, i = 0;
- uint32_t size = 0, xsize = 0;
- uint8_t *ptr = pText;
- while (*ptr++) { size++; } //Get text size
- xsize = (BSP_LCD_GetXSize()/DrawProp[ActiveLayer].pFont->Width); //Characters number per line
- switch (mode) //Select data align metod
- {
- case CENTER_MODE:
- {
- refcolumn = X+ ((xsize - size)* DrawProp[ActiveLayer].pFont->Width) / 2;
- break;
- }
- case LEFT_MODE:
- {
- refcolumn = X;
- break;
- }
- case RIGHT_MODE:
- {
- refcolumn = X + ((xsize - size)*DrawProp[ActiveLayer].pFont->Width);
- break;
- }
- default:
- {
- refcolumn = X;
- break;
- }
- }
- //send data as string character by character on LCD
- while ((*pText != 0) & (((BSP_LCD_GetXSize() - (i*DrawProp[ActiveLayer].pFont->Width)) & 0xFFFF) >= DrawProp[ActiveLayer].pFont->Width))
- {
- //Display one character on LCD
- BSP_LCD_DisplayChar(refcolumn, Y, *pText);
- // Decrement the column position by 16
- refcolumn += DrawProp[ActiveLayer].pFont->Width;
- // Point on the next character */
- pText++;
- i++;
- }
- }
Tekst wyświetlany może też zostać zdefiniowany bezpośrednio w definicjach. Wtedy przyjmuje on następującą postać:
- #define printf_date(...) BSP_LCD_SetFont(&Font16);\
- BSP_LCD_DisplayStringAt(0, BSP_LCD_GetYSize()/17, __VA_ARGS__, CENTER_MODE);
- #define printf_time(...) BSP_LCD_SetFont(&Font16);\
- BSP_LCD_DisplayStringAt(0, BSP_LCD_GetYSize()/10, __VA_ARGS__, CENTER_MODE);
Do definicji podaje się bufor z danymi, które mają być wyświetlone na wyświetlaczu.
Teraz przejdę do obsługi dotyku. Klawisze zostały zdefiniowane w przygotowanej strukturze, która zawiera wszystkie potrzebne elementy.
- typedef void (*ButtonClickHandler) (uint8_t buttonId);
- typedef struct{
- const uint8_t ButtonId;
- ButtonClickHandler Handler;
- const uint16_t Xpos;
- const uint16_t Ypos;
- const uint16_t Width;
- const uint16_t Height;
- }Button_t;
- Button_t HourIncrement = {
- .ButtonId = 0x01,
- .Handler = HourIncrementHandler,
- .Xpos = 0,
- .Ypos = 80,
- .Width = 50,
- .Height = 60,
- };
- Button_t HourDecrement = {
- .ButtonId = 0x02,
- .Handler = HourDecrementHandler,
- .Xpos = 60,
- .Ypos = 80,
- .Width = 50,
- .Height = 60,
- };
- Button_t MinutesIncrement = {
- .ButtonId = 0x03,
- .Handler = MinutesIncrementHandler,
- .Xpos = 120,
- .Ypos = 80,
- .Width = 50,
- .Height = 60,
- };
- Button_t MinutesDecrement = {
- .ButtonId = 0x04,
- .Handler = MinutesDecrementHandler,
- .Xpos = 180,
- .Ypos = 80,
- .Width = 50,
- .Height = 60,
- };
- Button_t *allButtons[4] = {
- &HourIncrement,
- &HourDecrement,
- &MinutesIncrement,
- &MinutesDecrement
- };
W strukturze przechowywany jest numer przycisku, wskaźnik na funkcję obsługującą kliknięcie w obszarze przycisku, pozycję startową na osi X oraz osi Y, ostatnie dwa parametry zawierają szerokość oraz wysokość prosokąta. Wszystkie zdefiniowane przyciski są zebrane razem jako wskaźniki w allButtons[4].
Inicjalizacja przycisków przebiega w sposób następujący:
- for(i=0; i<4; i++) //Stworzenie przycisków
- {
- BSP_LCD_FillRect(allButtons[i]->Xpos, allButtons[i]->Ypos, allButtons[i]->Width, allButtons[i]->Height);
- }
- BSP_LCD_SetTextColor(LCD_COLOR_BLACK);
- BSP_LCD_DisplayStringAt(25, 100, (uint8_t*)"M+", CENTER_MODE);
- BSP_LCD_DisplayStringAt(85, 100, (uint8_t*)"M-", CENTER_MODE);
- BSP_LCD_DisplayStringAt(145, 100, (uint8_t*)"H+", CENTER_MODE);
- BSP_LCD_DisplayStringAt(205, 100, (uint8_t*)"H-", CENTER_MODE);
W pętli for następuje przejście pomiędzy wszystkimi przyciskami, po ich umieszczeniu na ekranie
Obsługa dotyku zrealizowana została na liczniku 6, jego inicjalizacja wygląda następująco:
- static void MX_TIM6_Init(void)
- {
- TIM_MasterConfigTypeDef sMasterConfig;
- htim6.Instance = TIM6;
- htim6.Init.Prescaler = 1000;
- htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
- htim6.Init.Period = 400;
- if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
- {
- Error_Handler();
- }
- sMasterConfig.MasterOutputTrigger = TIM_TRGO_ENABLE;
- sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
- if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
- {
- Error_Handler();
- }
- HAL_TIM_Base_Start_IT(&htim6);
- }
W procedurze obsługi przerwania zrealizowałem aktualizowanie RTC oraz sprawdzanie każdego z przycisków z wywołanie obsługi kliknięcia:
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
- uint8_t i=0;
- char data[40];
- if (status == 1)
- {
- BSP_TS_GetState(&TS_State);
- if (TS_State.TouchDetected)
- {
- coordinate.positionX = Calibration_GetX(TS_State.X);
- coordinate.positionY = Calibration_GetY(TS_State.Y);
- sprintf(data, "Touch: X:%u Y:%u", coordinate.positionX, coordinate.positionY);
- printf_touch_pos((uint8_t*)data);
- for(i=0;i<4;i++)
- {
- if((coordinate.positionY > allButtons[i]->Ypos) && (coordinate.positionY < (allButtons[i]->Ypos + allButtons[i]->Height)))
- {
- if((coordinate.positionX > allButtons[i]->Xpos) && (coordinate.positionX < (allButtons[i]->Xpos + allButtons[i]->Width)))
- {
- allButtons[i]->Handler(allButtons[i]->ButtonId);
- status = 0;
- }
- }
- }
- }
- else
- {
- coordinate.positionX = 0;
- coordinate.positionY = 0;
- }
- }
- RTC_GetDateTime(&RTCD, RTC_Format_BIN);
- if (RTCD.Seconds != sec)
- {
- sec = RTCD.Seconds;
- sprintf(data, "D: %02d.%02d.%04d", RTCD.Day, RTCD.Month, RTCD.Year + 2000);
- printf_date((uint8_t*)data);
- sprintf(data, "T: %02d.%02d.%02d", RTCD.Hours, RTCD.Minutes, RTCD.Seconds);
- printf_time((uint8_t*)data);
- BSP_LCD_SetBackColor(LCD_COLOR_YELLOW);
- }
- }
Obsługa przycisku na kliknięcie. Jest on wywoływany w przerwaniu od przycisku, po sprawdzeniu miejsca jego kliknięcia.
- ButtonClickHandler HourIncrementHandler(uint8_t buttonId)
- {
- BSP_LCD_DisplayStringAtLine(9, (uint8_t*)"H+");
- SetDate(1, 0);
- }
- ButtonClickHandler HourDecrementHandler(uint8_t buttonId)
- {
- BSP_LCD_DisplayStringAtLine(9, (uint8_t*)"H-");
- SetDate(2, 0);
- }
- ButtonClickHandler MinutesIncrementHandler(uint8_t buttonId)
- {
- BSP_LCD_DisplayStringAtLine(9, (uint8_t*)"M+");
- SetDate(0, 1);
- }
- ButtonClickHandler MinutesDecrementHandler(uint8_t buttonId)
- {
- BSP_LCD_DisplayStringAtLine(9, (uint8_t*)"M-");
- SetDate(0, 2);
- }
Pliki z projektem można znaleźć na dysku Google pod tym linkiem.