piątek, 14 kwietnia 2017

[V] STM32F429I - Projekt - Zegar RTC, Wyświetlacz, Touchpad

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.

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:

  1. uint32_t RTC_Init(RTC_ClockSource_t source)   //Inicjalizacja RTC
  2. {
  3.     uint32_t status;
  4.     RTC_time_t datatime;
  5.    
  6.     //--------------------------------------------------------------
  7.     RTC_Handle.Instance = RTC;                                 
  8.     RTC_Handle.Init.AsynchPrediv = RTC_ASYNC_PREDIV;           
  9.     RTC_Handle.Init.SynchPrediv = RTC_SYNC_PREDIV;                 
  10.     RTC_Handle.Init.HourFormat = RTC_HOURFORMAT_24;                
  11.     RTC_Handle.Init.OutPut = RTC_OUTPUT_DISABLE;                   
  12.     RTC_Handle.Init.OutPutType = RTC_OUTPUT_TYPE_PUSHPULL;         
  13.     RTC_Handle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;     
  14.     //--------------------------------------------------------------
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                 //PWR ON
  16.     HAL_PWR_EnableBkUpAccess();                                 //BKP Domain
  17.     status = HAL_RTCEx_BKUPRead(&RTC_Handle, RTC_STATUS_REG);   //Status RTC
  18.    
  19.     //Sprawdzenie czy RTC zostalo juz zainicjalizowane
  20.     if (status == RTC_STATUS_TIME_OK)
  21.     {
  22.         //Wlaczenie zegara wewnetrznego, jeśli taki został wybrany
  23.         if (source == RTC_ClockSource_Internal)
  24.         {
  25.             RTC_Config(RTC_ClockSource_Internal);
  26.         }
  27.         HAL_RTC_WaitForSynchro(&RTC_Handle);                    //Oczekiwanie na synchronizacje RTC
  28.         RTC_GetDateTime(&datatime, RTC_Format_BIN);             //Pobranie daty oraz czasu
  29.         __HAL_RCC_CLEAR_RESET_FLAGS();                          //Wyczyszczenie flag resetu
  30.         return 1;                                               //Zwroc OK
  31.     }
  32.     else
  33.     {
  34.         RTC_Config(source);                                     //Wlacz RTC zegar
  35.        
  36.         //Ustaw wartosci domyslne w strukturze daty
  37.         RTC_DateStruct.Year = 0;
  38.         RTC_DateStruct.Month = 1;
  39.         RTC_DateStruct.Date = 1;
  40.         RTC_DateStruct.WeekDay = RTC_WEEKDAY_TUESDAY;
  41.        
  42.         HAL_RTC_SetDate(&RTC_Handle, &RTC_DateStruct, RTC_FORMAT_BIN);
  43.         //Ustaw czas wartosci domyslne
  44.         RTC_TimeStruct.Hours = 0x00;
  45.         RTC_TimeStruct.Minutes = 0x00;
  46.         RTC_TimeStruct.Seconds = 0x00;
  47.         RTC_TimeStruct.TimeFormat = RTC_HOURFORMAT_24;
  48.         RTC_TimeStruct.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  49.         RTC_TimeStruct.StoreOperation = RTC_STOREOPERATION_RESET;
  50.         //Ustaw czas
  51.         HAL_RTC_SetTime(&RTC_Handle, &RTC_TimeStruct, RTC_FORMAT_BCD);
  52.        
  53.         //Wlacz RTC
  54.         HAL_RTC_Init(&RTC_Handle);
  55.         //Zapisz dane do back up register
  56.         HAL_RTCEx_BKUPWrite(&RTC_Handle, RTC_STATUS_REG, RTC_STATUS_TIME_OK);
  57.        
  58.         //RTC byl juz zainicjalizowany
  59.         return 0;
  60.     }
  61. }

Ustawienie zegarów:

  1. void RTC_Config(RTC_ClockSource_t source)
  2. {
  3.     RCC_OscInitTypeDef RCC_OscInitStruct;
  4.     RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
  5.     PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  6.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;          //Wylaczenie PLL
  7.     if (source == RTC_ClockSource_Internal)                 //LSI
  8.     {
  9.         RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI;
  10.         RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  11.         PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
  12.     }
  13.     else                                                    //LSE
  14.     {
  15.         RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
  16.         RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  17.         PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
  18.     }
  19.    
  20.     HAL_RCC_OscConfig(&RCC_OscInitStruct);                  //Configuracja oscylatora
  21.     HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);        //Select peripheral clock
  22.     __HAL_RCC_RTC_ENABLE();                                 //Enable RTC clock
  23. }

Włączenie przerwań od RTC:

  1. typedef enum
  2. {
  3.     RTC_Interupt_Dis = 0x00,
  4.     RTC_Interupt_60s,
  5.     RTC_Interupt_30s,
  6.     RTC_Interupt_15s,
  7.     RTC_Interupt_10s,
  8.     RTC_Interupt_5s,
  9.     RTC_Interupt_2s,
  10.     RTC_Interupt_1s,
  11.     RTC_Interupt_500ms,
  12.     RTC_Interupt_250ms,
  13.     RTC_Interupt_125ms
  14. } RTC_Interupt_t;
  15. RTC_Resoult_t RTC_Interrupts(RTC_Interupt_t int_value)
  16. {
  17.     uint32_t val_to_set;
  18.    
  19.     __HAL_RTC_WAKEUPTIMER_DISABLE(&RTC_Handle); //Disable interrupt
  20.    
  21.     __HAL_RTC_WAKEUPTIMER_DISABLE_IT(&RTC_Handle, RTC_IT_WUT);
  22.    
  23.     (EXTI->PR = RTC_EXTI_LINE_WAKEUPTIMER_EVENT); //Clear pending bit
  24.    
  25.     __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&RTC_Handle, RTC_FLAG_WUTF); //Clear flag
  26.    
  27.     if (int_value != RTC_Interupt_Dis) {
  28.         if (int_value == RTC_Interupt_60s)      {   val_to_set = 0x3BFFF;   }
  29.         else if (int_value == RTC_Interupt_30s) {   val_to_set = 0x1DFFF;   }
  30.         else if (int_value == RTC_Interupt_15s) {   val_to_set = 0xEFFF;    }
  31.         else if (int_value == RTC_Interupt_10s) {   val_to_set = 0x9FFF;    }
  32.         else if (int_value == RTC_Interupt_5s)  {   val_to_set = 0x4FFF;    }
  33.         else if (int_value == RTC_Interupt_2s)  {   val_to_set = 0x1FFF;    }
  34.         else if (int_value == RTC_Interupt_1s)  {   val_to_set = 0x0FFF;    }
  35.         else if (int_value == RTC_Interupt_500ms) { val_to_set = 0x7FF; }
  36.         else if (int_value == RTC_Interupt_250ms) { val_to_set = 0x3FF; }
  37.         else if (int_value == RTC_Interupt_125ms) { val_to_set = 0x1FF; }      
  38.         HAL_RTCEx_SetWakeUpTimer_IT(&RTC_Handle, val_to_set, RTC_WAKEUPCLOCK_RTCCLK_DIV8);
  39.        
  40.         HAL_NVIC_SetPriority(RTC_WKUP_IRQn, RTC_NVIC_PRIORITY, RTC_NVIC_WAKEUP_SUBPRIORITY);
  41.         HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
  42.     }
  43.     return RTC_Result_Ok;
  44. }

Obsługa Touchpada:

Na samym początku należy rozpocząć od inicjalizacji touchpada:

  1. uint32_t BSP_LCD_GetXSize(void)
  2. {
  3.   return LcdDrv->GetLcdPixelWidth();
  4. }
  5. uint32_t BSP_LCD_GetYSize(void)
  6. {
  7.   return LcdDrv->GetLcdPixelHeight();
  8. }
  9. uint8_t BSP_TS_Init(uint16_t XSize, uint16_t YSize)
  10. {
  11.   uint8_t ret = TS_ERROR;
  12.   //Inicjalizacja wielkości ekranu x oraz y
  13.   TsXBoundary = XSize;
  14.   TsYBoundary = YSize;
  15.   /* Read ID and verify if the IO expander is ready */
  16.   if(stmpe811_ts_drv.ReadID(TS_I2C_ADDRESS) == STMPE811_ID)
  17.   {
  18.     TsDrv = &stmpe811_ts_drv;   //Inicjalizacja struktury dla TS
  19.     ret = TS_OK;
  20.   }
  21.   if(ret == TS_OK)      //Inicjalizacja sterowników TS
  22.   {
  23.     TsDrv->Init(TS_I2C_ADDRESS);
  24.     TsDrv->Start(TS_I2C_ADDRESS);
  25.   }
  26.   return ret;
  27. }

Funkcje ustawiające długość boków X oraz boku Y wyświetlacza. Następna pozwala na inicjalizację touchpada:

  1. uint32_t BSP_LCD_GetXSize(void)
  2. {
  3.   return LcdDrv->GetLcdPixelWidth();
  4. }
  5. uint32_t BSP_LCD_GetYSize(void)
  6. {
  7.   return LcdDrv->GetLcdPixelHeight();
  8. }
  9. uint8_t BSP_TS_Init(uint16_t XSize, uint16_t YSize)
  10. {
  11.   uint8_t ret = TS_ERROR;
  12.   //Inicjalizacja wielkości ekranu x oraz y
  13.   TsXBoundary = XSize;
  14.   TsYBoundary = YSize;
  15.   /* Read ID and verify if the IO expander is ready */
  16.   if(stmpe811_ts_drv.ReadID(TS_I2C_ADDRESS) == STMPE811_ID)
  17.   {
  18.     TsDrv = &stmpe811_ts_drv;   //Inicjalizacja struktury dla TS
  19.     ret = TS_OK;
  20.   }
  21.   if(ret == TS_OK)      //Inicjalizacja sterowników TS
  22.   {
  23.     TsDrv->Init(TS_I2C_ADDRESS);
  24.     TsDrv->Start(TS_I2C_ADDRESS);
  25.   }
  26.   return ret;
  27. }

Obsługa Wyświetlacza:

Obsługa wyświetlacza za pomocą funkcji BSP wygląda następująco:

Najpierw inicjalizacja wyświetlacza:

  1. uint8_t BSP_LCD_Init(void)
  2. {
  3.   /* On STM32F429I-DISCO, it is not possible to read ILI9341 ID because */
  4.   /* PIN EXTC is not connected to VDD and then LCD_READ_ID4 is not accessible. */
  5.   /* In this case, ReadID function is bypassed.*/  
  6.   /*if(ili9341_drv.ReadID() == ILI9341_ID)*/
  7.     /* LTDC Configuration ----------------------------------------------------*/
  8.     LtdcHandler.Instance = LTDC;
  9.    
  10.     /*
  11.      * Timing configuration  (Typical configuration from ILI9341 datasheet)
  12.      * HSYNC=10 (9+1) | HBP=20 (29-10+1) | ActiveW=240 (269-20-10+1) | HFP=10 (279-240-20-10+1)
  13.      * VSYNC=2 (1+1) | VBP=2 (3-2+1) | ActiveH=320 (323-2-2+1) | VFP=4 (327-320-2-2+1)
  14.     */
  15.    
  16.     LtdcHandler.Init.HorizontalSync = ILI9341_HSYNC;    //Configure horizontal synchronization width
  17.     LtdcHandler.Init.VerticalSync = ILI9341_VSYNC;      //Configure vertical synchronization height
  18.     LtdcHandler.Init.AccumulatedHBP = ILI9341_HBP;      //Configure accumulated horizontal back porch
  19.     LtdcHandler.Init.AccumulatedVBP = ILI9341_VBP;      //Configure accumulated vertical back porch
  20.     LtdcHandler.Init.AccumulatedActiveW = 269;          //Configure accumulated active width
  21.     LtdcHandler.Init.AccumulatedActiveH = 323;          //Configure accumulated active height
  22.     LtdcHandler.Init.TotalWidth = 279;                  //Configure total width
  23.     LtdcHandler.Init.TotalHeigh = 327;                  //Configure total height
  24.    
  25.     /* Configure R,G,B component values for LCD background color */
  26.     LtdcHandler.Init.Backcolor.Red= 0;              //R Value
  27.     LtdcHandler.Init.Backcolor.Blue= 0;             //B Value
  28.     LtdcHandler.Init.Backcolor.Green= 0;            //G Value
  29.    
  30.     /* LCD clock configuration */
  31.     /* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */
  32.     /* PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 192 Mhz */
  33.     /* PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 192/4 = 48 Mhz */
  34.     /* LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_8 = 48/4 = 6Mhz */
  35.     PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
  36.     PeriphClkInitStruct.PLLSAI.PLLSAIN = 192;
  37.     PeriphClkInitStruct.PLLSAI.PLLSAIR = 4;
  38.     PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_8;
  39.     HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
  40.    
  41.     //Polarity
  42.     LtdcHandler.Init.HSPolarity = LTDC_HSPOLARITY_AL;
  43.     LtdcHandler.Init.VSPolarity = LTDC_VSPOLARITY_AL;
  44.     LtdcHandler.Init.DEPolarity = LTDC_DEPOLARITY_AL;
  45.     LtdcHandler.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
  46.    
  47.     MspInit();
  48.     HAL_LTDC_Init(&LtdcHandler);
  49.    
  50.     /* Select the device */
  51.     LcdDrv = &ili9341_drv;
  52.     /* LCD Init */   
  53.     LcdDrv->Init();
  54.     /* Initialize the SDRAM */
  55.     BSP_SDRAM_Init();
  56.     /* Initialize the font */
  57.     BSP_LCD_SetFont(&LCD_DEFAULT_FONT);
  58.   return LCD_OK;
  59. }

Kolejna funkcja z biblioteki BSP służy do wybrania warstwy oraz bufora:

  1. void BSP_LCD_LayerDefaultInit(uint16_t LayerIndex, uint32_t FB_Address)
  2. {    
  3.   LCD_LayerCfgTypeDef   Layercfg;
  4.   //Layer Init
  5.   Layercfg.WindowX0 = 0;
  6.   Layercfg.WindowX1 = BSP_LCD_GetXSize();
  7.   Layercfg.WindowY0 = 0;
  8.   Layercfg.WindowY1 = BSP_LCD_GetYSize();
  9.   Layercfg.PixelFormat = LTDC_PIXEL_FORMAT_ARGB8888;
  10.   Layercfg.FBStartAdress = FB_Address;
  11.   Layercfg.Alpha = 255;
  12.   Layercfg.Alpha0 = 0;
  13.   Layercfg.Backcolor.Blue = 0;
  14.   Layercfg.Backcolor.Green = 0;
  15.   Layercfg.Backcolor.Red = 0;
  16.   Layercfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
  17.   Layercfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
  18.   Layercfg.ImageWidth = BSP_LCD_GetXSize();
  19.   Layercfg.ImageHeight = BSP_LCD_GetYSize();
  20.   HAL_LTDC_ConfigLayer(&LtdcHandler, &Layercfg, LayerIndex);
  21.   DrawProp[LayerIndex].BackColor = LCD_COLOR_WHITE;
  22.   DrawProp[LayerIndex].pFont     = &Font24;
  23.   DrawProp[LayerIndex].TextColor = LCD_COLOR_BLACK;
  24.   //Dithering activation
  25.   HAL_LTDC_EnableDither(&LtdcHandler);
  26. }

Następnie wykorzystywane są funkcje odpowiedzialne za wybranie aktywnej warstwy, włączenie wyświetlacza oraz zapełnienie ekranu wybranym kolorem

  1. void BSP_LCD_SelectLayer(uint32_t LayerIndex)
  2. {
  3.   ActiveLayer = LayerIndex;
  4. }
  5. void BSP_LCD_DisplayOn(void)
  6. {
  7.   if(LcdDrv->DisplayOn != NULL)
  8.   {
  9.     LcdDrv->DisplayOn();
  10.   }
  11. }
  12. void BSP_LCD_Clear(uint32_t Color)
  13. {
  14.   /* Clear the LCD */
  15.   FillBuffer(ActiveLayer, (uint32_t *)(LtdcHandler.LayerCfg[ActiveLayer].FBStartAdress), BSP_LCD_GetXSize(), BSP_LCD_GetYSize(), 0, Color);
  16. }

Do wyświetlania tekstu na ekranie wykorzystuje takie funkcje jak:

  1. void BSP_LCD_DisplayChar(uint16_t Xpos, uint16_t Ypos, uint8_t Ascii)
  2. {
  3.   DrawChar(Xpos, Ypos, &DrawProp[ActiveLayer].pFont->table[(Ascii-' ') *\
  4.               DrawProp[ActiveLayer].pFont->Height * ((DrawProp[ActiveLayer].pFont->Width + 7) / 8)]);
  5. }
  6. void BSP_LCD_DisplayStringAtLine(uint16_t Line, uint8_t *ptr)
  7. {
  8.   BSP_LCD_DisplayStringAt(0, LINE(Line), ptr, LEFT_MODE);
  9. }
  10. void BSP_LCD_DisplayStringAt(uint16_t X, uint16_t Y, uint8_t *pText, Text_AlignModeTypdef mode)
  11. {
  12.   uint16_t refcolumn = 1, i = 0;
  13.   uint32_t size = 0, xsize = 0;
  14.   uint8_t  *ptr = pText;
  15.   while (*ptr++) { size++; }    //Get text size
  16.   xsize = (BSP_LCD_GetXSize()/DrawProp[ActiveLayer].pFont->Width);  //Characters number per line
  17.   switch (mode) //Select data align metod
  18.   {
  19.   case CENTER_MODE:
  20.     {
  21.       refcolumn = X+ ((xsize - size)* DrawProp[ActiveLayer].pFont->Width) / 2;
  22.       break;
  23.     }
  24.   case LEFT_MODE:
  25.     {
  26.       refcolumn = X;
  27.       break;
  28.     }
  29.   case RIGHT_MODE:
  30.     {
  31.       refcolumn = X + ((xsize - size)*DrawProp[ActiveLayer].pFont->Width);
  32.       break;
  33.     }
  34.   default:
  35.     {
  36.       refcolumn = X;
  37.       break;
  38.     }
  39.   }
  40.   //send data as string character by character on LCD
  41.   while ((*pText != 0) & (((BSP_LCD_GetXSize() - (i*DrawProp[ActiveLayer].pFont->Width)) & 0xFFFF) >= DrawProp[ActiveLayer].pFont->Width))
  42.   {
  43.     //Display one character on LCD
  44.     BSP_LCD_DisplayChar(refcolumn, Y, *pText);
  45.     // Decrement the column position by 16
  46.     refcolumn += DrawProp[ActiveLayer].pFont->Width;
  47.     // Point on the next character */
  48.     pText++;
  49.     i++;
  50.   }  
  51. }

Tekst wyświetlany może też zostać zdefiniowany bezpośrednio w definicjach. Wtedy przyjmuje on następującą postać:

  1. #define printf_date(...)         BSP_LCD_SetFont(&Font16);\
  2.                                  BSP_LCD_DisplayStringAt(0, BSP_LCD_GetYSize()/17, __VA_ARGS__, CENTER_MODE);
  3. #define printf_time(...)         BSP_LCD_SetFont(&Font16);\
  4.                                  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.

  1. typedef void (*ButtonClickHandler) (uint8_t buttonId);
  2. typedef struct{
  3.     const uint8_t ButtonId;
  4.     ButtonClickHandler Handler;
  5.     const uint16_t Xpos;
  6.     const uint16_t Ypos;
  7.     const uint16_t Width;
  8.     const uint16_t Height;
  9. }Button_t;
  10. Button_t HourIncrement = {
  11.         .ButtonId = 0x01,
  12.         .Handler = HourIncrementHandler,
  13.         .Xpos = 0,
  14.         .Ypos = 80,
  15.         .Width = 50,
  16.         .Height = 60,
  17. };
  18. Button_t HourDecrement = {
  19.         .ButtonId = 0x02,
  20.         .Handler = HourDecrementHandler,
  21.         .Xpos = 60,
  22.         .Ypos = 80,
  23.         .Width = 50,
  24.         .Height = 60,
  25.     };
  26. Button_t MinutesIncrement = {
  27.         .ButtonId = 0x03,
  28.         .Handler = MinutesIncrementHandler,
  29.         .Xpos = 120,
  30.         .Ypos = 80,
  31.         .Width = 50,
  32.         .Height = 60,
  33.     };
  34. Button_t MinutesDecrement = {
  35.         .ButtonId = 0x04,
  36.         .Handler = MinutesDecrementHandler,
  37.         .Xpos = 180,
  38.         .Ypos = 80,
  39.         .Width = 50,
  40.         .Height = 60,
  41.     };
  42. Button_t *allButtons[4] = {
  43.         &HourIncrement,
  44.         &HourDecrement,
  45.         &MinutesIncrement,
  46.         &MinutesDecrement
  47. };

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:

  1.   for(i=0; i<4; i++)    //Stworzenie przycisków
  2.   {
  3.       BSP_LCD_FillRect(allButtons[i]->Xpos, allButtons[i]->Ypos, allButtons[i]->Width, allButtons[i]->Height);
  4.   }
  5.   BSP_LCD_SetTextColor(LCD_COLOR_BLACK);
  6.   BSP_LCD_DisplayStringAt(25, 100, (uint8_t*)"M+", CENTER_MODE);
  7.   BSP_LCD_DisplayStringAt(85, 100, (uint8_t*)"M-", CENTER_MODE);
  8.   BSP_LCD_DisplayStringAt(145, 100, (uint8_t*)"H+", CENTER_MODE);
  9.   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:

  1. static void MX_TIM6_Init(void)
  2. {
  3.   TIM_MasterConfigTypeDef sMasterConfig;
  4.   htim6.Instance = TIM6;
  5.   htim6.Init.Prescaler = 1000;
  6.   htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
  7.   htim6.Init.Period = 400;
  8.   if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
  9.   {
  10.     Error_Handler();
  11.   }
  12.   sMasterConfig.MasterOutputTrigger = TIM_TRGO_ENABLE;
  13.   sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  14.   if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
  15.   {
  16.     Error_Handler();
  17.   }
  18.   HAL_TIM_Base_Start_IT(&htim6);
  19. }

W procedurze obsługi przerwania zrealizowałem aktualizowanie RTC oraz sprawdzanie każdego z przycisków z wywołanie obsługi kliknięcia:

  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  2. {
  3.     uint8_t i=0;
  4.     char data[40];
  5.     if (status == 1)
  6.     {
  7.         BSP_TS_GetState(&TS_State);
  8.         if (TS_State.TouchDetected)
  9.         {
  10.             coordinate.positionX = Calibration_GetX(TS_State.X);
  11.             coordinate.positionY = Calibration_GetY(TS_State.Y);
  12.             sprintf(data, "Touch: X:%u Y:%u", coordinate.positionX, coordinate.positionY);
  13.             printf_touch_pos((uint8_t*)data);
  14.             for(i=0;i<4;i++)
  15.             {
  16.                 if((coordinate.positionY > allButtons[i]->Ypos) && (coordinate.positionY < (allButtons[i]->Ypos + allButtons[i]->Height)))
  17.                 {
  18.                     if((coordinate.positionX > allButtons[i]->Xpos) && (coordinate.positionX < (allButtons[i]->Xpos + allButtons[i]->Width)))
  19.                     {
  20.                         allButtons[i]->Handler(allButtons[i]->ButtonId);
  21.                         status = 0;
  22.                     }
  23.                 }
  24.             }
  25.         }
  26.         else
  27.         {
  28.             coordinate.positionX = 0;
  29.             coordinate.positionY = 0;
  30.         }
  31.     }
  32.     RTC_GetDateTime(&RTCD, RTC_Format_BIN);
  33.     if (RTCD.Seconds != sec)
  34.     {
  35.         sec = RTCD.Seconds;
  36.         sprintf(data, "D: %02d.%02d.%04d", RTCD.Day, RTCD.Month, RTCD.Year + 2000);
  37.         printf_date((uint8_t*)data);
  38.         sprintf(data, "T: %02d.%02d.%02d", RTCD.Hours, RTCD.Minutes, RTCD.Seconds);
  39.         printf_time((uint8_t*)data);
  40.         BSP_LCD_SetBackColor(LCD_COLOR_YELLOW);
  41.     }
  42. }

Obsługa przycisku na kliknięcie. Jest on wywoływany w przerwaniu od przycisku, po sprawdzeniu miejsca jego kliknięcia.

  1. ButtonClickHandler HourIncrementHandler(uint8_t buttonId)
  2. {
  3.     BSP_LCD_DisplayStringAtLine(9, (uint8_t*)"H+");
  4.     SetDate(1, 0);
  5. }
  6. ButtonClickHandler HourDecrementHandler(uint8_t buttonId)
  7. {
  8.     BSP_LCD_DisplayStringAtLine(9, (uint8_t*)"H-");
  9.     SetDate(2, 0);
  10. }
  11. ButtonClickHandler MinutesIncrementHandler(uint8_t buttonId)
  12. {
  13.     BSP_LCD_DisplayStringAtLine(9, (uint8_t*)"M+");
  14.     SetDate(0, 1);
  15. }
  16. ButtonClickHandler MinutesDecrementHandler(uint8_t buttonId)
  17. {
  18.     BSP_LCD_DisplayStringAtLine(9, (uint8_t*)"M-");
  19.     SetDate(0, 2);
  20. }

Pliki z projektem można znaleźć na dysku Google pod tym linkiem.