пʼятниця, 15 листопада 2019 р.

STEmWin: GUIBuilder приклад побудови вікна з графічними елементами і їх взаємодія


Передмова

В попередній статті інтегрували професійну бібліотеку EmWin до свого проекту та спробували щось відобразити на екрані і навіть помалювати за допомоги сенсорної панелі. В цій статті побудуємо, за допомоги програми GUIBuilder, графічний інтерфейс та додамо код взаємодії з графічним інтерфейсом.

Залізяччя

GUI Builder

Відкрийте програму "GUIBuilder", яка знаходиться в репозиторію CubeMX, або CubeIDE за шляхом "...\Middlewares\ST\STemWin\Software\GUIBuilder.exe".

Window

  1. Додайте елемент "Window";
  2. Встановіть властивості вікна: xPos - 0, yPos - 0, xSize - 240, ySize - 320;
  3. Встановимо колір вікна. ПКМ на елементі "Window", та оберіть "Set background color" -> Сірий (0xC0C0C0).
Window

Text

  1. Додайте елемент "Text";
  2. Встановимо позицію та розмір елементу. xPos - 0, yPos - 0, xSize - 240, ySize - 40;
  3. Встановимо шрифт. ПКМ на елементі "Text" -> "Set font" -> "GUI_FONT_24_1";
  4. Встановимо текст. ПКМ на елементі "Text" -> "Set text" -> "Example";
  5. Встановимо рівняння тексту. ПКМ на елементі "Text" -> "Set text alignment" -> "HV";
Text

Radio

  1. Додайте елемент "Radio";
  2. Встановимо позицію та розмір елементу: xPos - 20, yPos - 60, xSize - 200, ySize - 60, distance - 30;
  3. Дамо назву пунктам. ПКМ на елементі "Radio" -> "Set item text" -> "Label text" -> "Option 1" -> "Label index" -> "1", "Radio" -> "Set item text" -> "Label text" -> "Option 2" -> "Label index" -> "2";
  4. Встановимо шрифт: Font - GUI_FONT_20_1;
Radio

Progbar та 2 x Button

По аналогії з попередніми елементами додайте елементи "Progbar" та "Button". Розташуйте, встановіть розмір, оберіть шрифт, дайте назви кнопкам. Щоб було приблизно як на малюнку.
Приклад інтерфейсу

Генерування проекту

Меню: "File" -> "Save...". Файл "WindowDLG.c" збережеться в директорію програми "GUIBuilder" за шляхом "...\Middlewares\ST\STemWin\Software\WindowDLG.c".

Перевірка роботи

Продовжимо працювати з проектом який створили в попередній статті "Підключення бібліотеки EmWin та перевірка роботи"
    1. Відкрийте проект створений в процесі роботи з попередньою статтею, який знаходиться на вашому диску, та відкрийте його в Atolic TrueStudio, CubeIDE, або в IDE, яку ви використовуєте; 
    2. Створіть файл "WindowDLG.h" в своєму засобі розробки, або будь яким іншим редактором, з таким змістом:
      #ifndef __WINDOWDLG_H
      #define __WINDOWDLG_H
      
      #include "DIALOG.h"
      
      WM_HWIN CreateWindow(void);
      
      #endif /* __WINDOWDLG_H */
      
    3. Внесіть зміни до файлу "WindowDLG.c". Було:
      // USER START (Optionally insert additional includes)
      // USER END
      
      #include "DIALOG.h"
      
    4. Стало:
      // USER START (Optionally insert additional includes)
      // USER END
      
      #include "WindowDLG.h"
      
    5. Зкопіюйте ці файли, "WindowDLG.h" та "WindowDLG.с" в свій проект за шляхом "..\Drivers\EmWin\inc" та "..\Drivers\EmWin\src" відповідно;
    6. Відкрийте файл "main.c" та вставте в код рядок з файлом "WindowDLG.h":
      /* Private includes ----------------------------------------------------------*/
      /* USER CODE BEGIN Includes */
      #include "ili9341_light.h"
      #include "GUI.h"
      #include "XPT2046_touch.h"
      #include "WindowDLG.h"
      /* USER CODE END Includes */
    7. В користувацькій секції 2 і безкінечному циклі видаліть код, який написали з попередньої статті. Тепер ця ділянка має виглядати так:
      /* USER CODE BEGIN 2 */
        GUI_Init();
        lcdBacklightOn();
        CreateWindow();
        /* USER CODE END 2 */
      
        /* Infinite loop */
        /* USER CODE BEGIN WHILE */
        while (1)
        {
       GUI_Delay(250);
          /* USER CODE END WHILE */
      
          /* USER CODE BEGIN 3 */
        }
        /* USER CODE END 3 */
      }
    8. Можна вже компілювати, як немає помилок, заливаємо до чипу і запускаємо. На дисплеї з'явилося зображення, що ми створили за допомогою програми "GUIBuilder.exe". Але поки що наш GUI ніяк не реагує на натискання сенсорної панелі.
    9. Додамо декілька глобальних змінних до файлу "main.c", це добуті координати від сенсорної панелі і структура для координат і статусу натискання на сенсорну панель для GUI:
      /* USER CODE BEGIN PV */
      uint16_t x, y;
      GUI_PID_STATE tsState;
      /* USER CODE END PV */
      
    10. Перейдемо до користувацької секції 4. Там має бути функція зворотного виклику, яка викликається кожного разу коли ви натискаєте на сенсорну панель, та відпускаєте її. За це відповідає сигнал T_IRQ. Докладно налаштування цієї шпильки описано в статті. Перевірте, щоб переривання цієї ніжки відбувалось, як при падінні сигналу, так і при зростанні сигналу на ніжці - GPIO mode -> External Interrupt Mode with Rising/Falling edge trigger detection. Видаляємо весь код який знаходиться в функції зворотного виклику void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin) і залишився з попереднього прикладу, та пишемо там такий код:
      /* USER CODE BEGIN 4 */
      void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin)
      {
       if (GPIO_Pin == T_IRQ_Pin)
       {
        if(XPT2046_TouchPressed())
        {
         tsState.Pressed = true;
      
         if(XPT2046_TouchGetCoordinates(&x, &y))
         {
           tsState.x = x;
           tsState.y = GUI_GetScreenSizeY() - y;
         }
        }
        else
        {
         tsState.Pressed = false;
         tsState.x = -1;
         tsState.y = -1;
        }
      
        GUI_TOUCH_StoreStateEx(&tsState);
       }
      }
      /* USER CODE END 4 */
      
    11. Пояснення до коду. Кожного разу коли ви натискаєте чи відпускаєте сенсорну панель, викликається ця функція. Тут перевіряється чи відбулось натискання, або звільнення сенсорної панелі. Як натиснули, то берем координати точки натискання і заповнюємо структуру координатами і встановлюємо прапорець "натиснуто". Як звільнили сенсорну панель, то заповнюємо структуру відповідно, що координат немає і скидаємо прапорець "натиснуто". Заносимо данні з структури до GUI_TOUCH.
    12. Компілюємо, заливаємо, запускаємо. Тепер наш GUI реагує на натискання кнопок і можемо обрати опцію в елементі "Radio", але при цьому ще нічого не відбувається.
    13. Щоб додати реакцію на події коли натискати на опції елементу "Radio", відкриваємо файл "WindowDLG.c", знайдіть рядок "case ID_RADIO_0: // Notifications sent by 'Radio'", та додамо в секцію "case WM_NOTIFICATION_RELEASED:" код який буде оброблятись коли ви будете змінювати опцію в елементі "Radio" при відпусканні:
      case ID_RADIO_0: // Notifications sent by 'Radio'
            switch(NCode) {
            case WM_NOTIFICATION_CLICKED:
              // USER START (Optionally insert code for reacting on notification message)
              // USER END
              break;
            case WM_NOTIFICATION_RELEASED:
              // USER START (Optionally insert code for reacting on notification message)
               {
                char buf[32];
                hItem = WM_GetDialogItem(pMsg->hWin, ID_RADIO_0);
                RADIO_GetText(hItem, RADIO_GetValue(hItem), buf, 32);
                hItem = WM_GetDialogItem(pMsg->hWin, ID_TEXT_0);
                TEXT_SetText(hItem, buf);
               }
              // USER END
              break;
            case WM_NOTIFICATION_VALUE_CHANGED:
              // USER START (Optionally insert code for reacting on notification message)
              // USER END
              break;
            // USER START (Optionally insert additional code for further notification handling)
            // USER END
            }
            break;
      
    14. Компілюємо, заливаємо, запускаємо. Тицяємо на опції "Radio" і текст елементу "Text" змінюється відповідно до змін.
    15. По аналогії додамо реакцію на натискання кнопок. Будемо змінювати значення елементу "Progbar", та міняти напис на елементі "Text". Спочатку додамо змінну "i", що буде зберігати значення для "Progbar", на початку файлу "WindowDLG.c":
      /*********************************************************************
      *
      *       Static data
      *
      **********************************************************************
      */
      
      // USER START (Optionally insert additional static data)
      int i;
      // USER END
      
    16. Та напишемо обробник події відпускання кнопок:
          case ID_BUTTON_0: // Notifications sent by 'Button'
            switch(NCode) {
            case WM_NOTIFICATION_CLICKED:
              // USER START (Optionally insert code for reacting on notification message)
              // USER END
              break;
            case WM_NOTIFICATION_RELEASED:
              // USER START (Optionally insert code for reacting on notification message)
         {
          char buf[32];
          hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0);
          BUTTON_GetText(hItem, buf, 32);
          hItem = WM_GetDialogItem(pMsg->hWin, ID_TEXT_0);
          TEXT_SetText(hItem, buf);
          if(i < 100)
           ++i;
          hItem = WM_GetDialogItem(pMsg->hWin, ID_PROGBAR_0);
          PROGBAR_SetValue(hItem, i);
         }
              // USER END
              break;
            // USER START (Optionally insert additional code for further notification handling)
            // USER END
            }
            break;
          case ID_BUTTON_1: // Notifications sent by 'Button'
            switch(NCode) {
            case WM_NOTIFICATION_CLICKED:
              // USER START (Optionally insert code for reacting on notification message)
              // USER END
              break;
            case WM_NOTIFICATION_RELEASED:
              // USER START (Optionally insert code for reacting on notification message)
            {
           char buf[32];
        hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_1);
        BUTTON_GetText(hItem, buf, 32);
        hItem = WM_GetDialogItem(pMsg->hWin, ID_TEXT_0);
        TEXT_SetText(hItem, buf);
        if(i > 0)
          --i;
        hItem = WM_GetDialogItem(pMsg->hWin, ID_PROGBAR_0);
        PROGBAR_SetValue(hItem, i);
            }
              // USER END
              break;
            // USER START (Optionally insert additional code for further notification handling)
            // USER END
            }
            break;
          // USER START (Optionally insert additional code for further Ids)
          // USER END
          }
          break;
        // USER START (Optionally insert additional message handling)
        // USER END
      
    17. Компілюємо, заливаємо, запускаємо. Тицяємо на елементи, спостерігаємо за реакцією.

    Відео приклад роботи


    субота, 9 листопада 2019 р.

    STEmWin: Підключення бібліотеки EmWin та перевірка роботи


    Передмова

    Свої графічні бібліотеки це добре, але професійна графічна бібліотека, що дозволяє створювати графічні інтерфейси користувача STEmWin від Segger - ще краща. Пропоную навчитись спочатку інтегрувати цю бібліотеку до свого проекту і спробувати намалювати декілька примітивів для проби. Хоч бібліотека і входить до складу репозиторіїв, що надаються STM32CubeMX, але ще не для всіх серій МК CubeMX інтегрує цю бібліотеку автоматично до вашого проекту. Тому, вміти це зробити самому, буде нам завиграшки. 

    Залізяччя

    Схема підключення та розпіновка TFT дисплею з TOUCH SCREEN є у попередній статті "STM32: Бібліотека дисплею ILI9341 по інтерфейсу FSMC". В тій статті також описано як налаштувати FSMC в CubeMX, але для зручності налаштування інтерфейсу FSMC продублюю і в цій статті. Плюс є деякі доповнення для роботи бібліотеки EmWin.

    CubeMX

    Відкриваємо CubeMX або CubeIDE, створюємо новий проект, обираємо чип STM32F407VE і налаштовуємо периферію:
    • В розділі "System Core" обираємо "RCC": "High Speed Clock -> Crystal/Ceramic Resonator";
    • У вкладці "Clock Configuration" обираємо зовнішній кварц, та потрібну частоту. Раз ми працюємо з графікою, то швидкість буде тільки на користь, я виставив максимальну частоту для цього чипу - 160МГц.
    • В розділі "System Core" обираємо "SYS""Debug -> Serial Wire";
    • В розділі "Connectivity" обираємо "FSMC";
    • В розділі "NOR Flash/PSRAM/SRAM/ROM/LCD 1" активуємо "NE1 Chip Select", обираємо "Memory type -> LCD Interface", "LCD Register Select -> A18" (відповідно до розпіновки роз'єму для дисплею, в мене сигнал RS дисплею під'єднано до PD13 мікроконтролеру. А ніжка PD13 активується для RS, коли обрати A18), "Data -> 16 bits";
    • В розділі "Configurations -> NOR/PSRAM control" обираємо: "Memory type -> LCD Interface", "Bank -> Bank 1 NOR/PSRAM 1", "Write operation -> Enabled", "Extended mode -> Disabled". В розділі "NOR/PSRAM timing" обираємо: "Address setup time in HCLK clock cicles -> 1", "Data setup time in HCLK clock cycles -> 5", "Bus turn around time in HCLK clock cycles -> 0";
    • Не забуваємо про підсвічування екрану. Відповідно до схеми це PB1. Встановимо режим GPIO_Output і дамо назву цій шпильці - "LCD_BL";
    • Увага! В розділі "Computing" обираємо "CRC" та активуємо "Activated". Без цього бібліотека компілюється, але ніякого зображення на екран не виводиться.
    • Генеруємо проект. Даємо назву проекту, наприклад: "STM32F407VET6_Black_ILI9341_EmWin". Вказуємо шлях до проекту та зберігаємо проект на диску.
    Налаштування FSMC
    Налаштування CRC

    Інтегруємо бібліотеку до проекту

    Запускаємо свій засіб розробки. Я використовую, або "CubeIDE for STM32", або "Atolic True Studio for STM32". Обидві IDE абсолютно безкоштовні. Відкриваємо проект згенерований CubeMX або CubeIDE і додаємо потрібні файли бібліотеки до проекту.

    Додаємо файли спрощеного драйверу дисплея ILI9341

    Перед тим, як додати бібліотеку EmWin, додаємо спрощену бібліотеку для дисплею ILI9341_LIGHT, де будуть лиш три функції, це ініціалізація дисплея, увімкнути підсвітку дисплея, та вимкнути підсвітку дисплея. А також дефайни запису команд, даних, та читання регистрів дисплея. Ця спрощена бібліотека складається з двох файлів ili9341_light.h та ili9341_light.c Так буде зручніше і зрозуміліше.

    В цьому проекті буде дуже багато файлів і щоб не скидати все до купи, копіюємо всю теку бібліотеки ILI9341_LIGHT до теки Drivers вашого проекту, як показано на малюнку:
    Додавання теки бібліотеки ili9341_light до проекту
    Тепер, як додали файли, потрібно вказати компілятору шлях до теки "inc". Викликаємо властивості проекту: Project -> Properties -> C/C++ Build -> Settings -> Tool Settings -> C Compiler -> Directories -> Include path -> Add обираємо Workspace, та вказуємо в файловому провіднику шлях до теки "inc" бібліотеки "ili9341_light". Робимо як на малюнку. Тицяйте на зображенні для збільшення:
    Налаштування шляхів
    Додайте до коду в файлі "main.c" файл бібліотеки "ili9341_light.h":
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "ili9341_light.h"
    /* USER CODE END Includes */
    
    Тепер можна спробувати скомпілювати натиснувши "Ctrl+B", щоб переконатись у відсутності помилок при компіляції. Як все гаразд, рухаємось далі.

    Додаємо файли бібліотеки EmWim

    Бібліотеку можна завантажити на сайті SEGGER, але я рекомендую взяти з репозиторію CubeMX, або CubeIDE, тим паче, що ця бібліотека вже входить в комплект репозиторію вашого чипу і скачувати вже нічого не потрібно. Знаходиться бібліотека за шляхом - "C:\Users\ваш_логін\STM32Cube\Repository\STM32Cube_FW_F4_V1.24.1\Middlewares\ST\STemWin", де "ваш_логін", це ваша локальна тека користувача. Відкриваємо теку "STemWin" в віконці файлового провідника.

    А тепер уважно крок за кроком робимо такі речі:
    1. В своєму засобі розробці в дереві проекту в теці "Drivers" створюємо теку "EmWin": на теці "Drivers" тиснемо ПКМ -> New -> Folder -> EmWin -> Finish;
    2. Аналогічно в теці "EmWin" створюємо теки "inc" та "src";
    3. Всі файли з теки "...\Middlewares\ST\STemWin\inc" репозиторію, копіюємо до теки "Drivers\EmWin\inc" вашого проекту;
    4. Файли "GUIConf.h", "LCDConf_FlexColor_Template.h" з теки "...\Middlewares\ST\STemWin\Config" репозиторію, копіюємо до теки "Drivers\EmWin\inc" вашого проекту;
    5. Файли "GUIConf.c""LCDConf_FlexColor_Template.c" з теки "...\Middlewares\ST\STemWin\Config" репозиторію, копіюємо до теки "Drivers\EmWin\src" вашого проекту;
    6. Перейдіть до свого засобу розробки і в дереві проекту знайдіть файли "LCDConf_FlexColor_Template.h" і  "LCDConf_FlexColor_Template.c" та переименуйте їх на "LCDConf_FlexColor.h" і "LCDConf_FlexColor.c";
    7. Файл "STemWin_CM4_wc32.a" з теки "...\Middlewares\ST\STemWin\Lib" репозиторію, копіюємо до теки "Drivers\EmWin" вашого проекту;
    8. Файл "GUI_X.c" з теки "...\Middlewares\ST\STemWin\OS" репозиторію, копіюємо до теки "Drivers\EmWin\src" вашого проекту;
    9. Тепер, як додали файли, потрібно вказати компілятору шлях до теки "Drivers\EmWin\inc" вашого проекту. Як це ми вже робили раніше, коли додали файли спрощеного драйверу ILI9341_LIGHT. Викликаємо властивості проекту: Project -> Properties -> C/C++ Build -> Settings -> Tool Settings -> C Compiler -> Directories -> Include path -> Add обираємо Workspace, та вказуємо в файловому провіднику шлях до теки "inc" бібліотеки "EmWin" вашого проекту;
    10. І на останок потрібно вказати лінковщику на файл "STemWin_CM4_wc32.a"Project -> Properties -> C/C++ Build -> Settings -> Tool Settings -> C Linker -> Miscellaneous -> Additional object files -> Add обираємо Workspace, та вказуємо в файловому провіднику шлях до файлу "STemWin_CM4_wc32.a" бібліотеки "EmWin" вашого проекту;
    Налаштування шляху до файла для лінковщика

    Налаштування бібліотеки EmWin

    1. В своєму засобі розробки відкриваємо файл "GUIConf.c" і шукаємо такий рядок:
      //
      // Define the available number of bytes available for the GUI
      //
      #define GUI_NUMBYTES  0x200000
    2. Зараз для GUI виділено аж 2Мб пам'яті RAM. Міняємо це значення на 32Кб:
      //
      // Define the available number of bytes available for the GUI
      //
      #define GUI_NUMBYTES  32 * 1024
      
    3. Відкриваємо файл "main.c" і додаємо такі рядки: 
      /* Private includes ----------------------------------------------------------*/
      /* USER CODE BEGIN Includes */
      #include "ili9341_light.h"
      #include "GUI.h"
      /* USER CODE END Includes */
      
    4. Та для проміжної перевірки, що все зроблено вірно, додамо ще виклик функції ініціалізації GUI: 
      /* USER CODE BEGIN 2 */
        GUI_Init();
        /* USER CODE END 2 */
      
    5. Компілюємо проект "Ctrl+B", як немає помилок, то все в порядку, зроблено все вірно. Продовжуємо налаштування далі;
    6. Відкриваємо файл "LCDConf_FlexColor.h", та додаємо рядок з "#include"
      #include "main.h"
      
    7. Відкриваємо файл "LCDConf_FlexColor.c", та додаємо такі рядки поруч з іншими "#include"
      #include "LCDConf_FlexColor.h"
      #include "ili9341_light.h"
      
    8. До функції "LcdWriteReg" додаємо виклик функції запису команди до дисплею: 
      /********************************************************************
      *
      *       LcdWriteReg
      *
      * Function description:
      *   Sets display register
      */
      static void LcdWriteReg(U16 Data)
      {
              // ... TBD by user
       LCD_CmdWrite(Data);
      }
      
    9. До функції "LcdWriteData" додаємо виклик функції запису даних до дисплею: 
      /********************************************************************
      *
      *       LcdWriteData
      *
      * Function description:
      *   Writes a value to a display register
      */
      static void LcdWriteData(U16 Data)
      {
              // ... TBD by user
       LCD_DataWrite(Data);
      }
      
    10. До функції "LcdWriteDataMultiple" додаємо виклик функції запису масиву даних до дисплею: 
      /********************************************************************
      *
      *       LcdWriteDataMultiple
      *
      * Function description:
      *   Writes multiple values to a display register.
      */
      static void LcdWriteDataMultiple(U16 * pData, int NumItems) 
      {
        while (NumItems--) 
        {
         // ... TBD by user
         LCD_DataWrite(*pData);
         pData++;
        }
      }
      
    11. До функції "LcdReadDataMultiple" додаємо виклик функції читання масиву даних з дисплею: 
      /********************************************************************
      *
      *       LcdReadDataMultiple
      *
      * Function description:
      *   Reads multiple values from a display register.
      */
      static void LcdReadDataMultiple(U16 * pData, int NumItems) 
      {
        while (NumItems--) 
        {
         // ... TBD by user
         *pData = LCD_DataRead();
         pData++;
        }
      }
      
    12. В розділі "Orientation" цього ж файлу "LCDConf_FlexColor.c" мають бути такі рядки: 
      //
      // Orientation
      //
      Config.Orientation = GUI_MIRROR_Y;
      GUIDRV_FlexColor_Config(pDevice, &Config);
      
    13. А в розділі "Set controller and operation mode" перевірте щоб був такий рядок:
      GUIDRV_FlexColor_SetFunc(pDevice, &PortAPI, GUIDRV_FLEXCOLOR_F66709, GUIDRV_FLEXCOLOR_M16C0B16);
      
    14. Функція "GUIDRV_FLEXCOLOR_F66709" з попереднього пункту обирається відповідно до документації, яка знаходится в репозиторію за шляхом "...\Middlewares\ST\STemWin\Documentation\STemWin544.pdf". В таблиці 33.42 вказано яка функція відповідає якому дисплею. Наш дисплей ILI9341 має функцію "GUIDRV_FLEXCOLOR_F66709";
    15. В функцію "LCD_X_DisplayDriver" додамо виклик функції ініціалізації дисплея:
      int LCD_X_DisplayDriver(unsigned LayerIndex, unsigned Cmd, void * pData) {
        int r;
        (void) LayerIndex;
        (void) pData;
        
        switch (Cmd) {
        case LCD_X_INITCONTROLLER: {
          //
          // Called during the initialization process in order to set up the
          // display controller and put it into operation. If the display
          // controller is not initialized by any external routine this needs
          // to be adapted by the customer...
          //
          // ...
         lcdInit();
      
          return 0;
        }
        default:
          r = -1;
        }
        return r;
      }
      
    16. І на останок відкриваємо файл "stm32f4xx_it.c" і додаємо такі рядки:
      /* Private includes ----------------------------------------------------------*/
      /* USER CODE BEGIN Includes */
      #include "GUI.h"
      /* USER CODE END Includes */
      
      /* Private variables ---------------------------------------------------------*/
      /* USER CODE BEGIN PV */
      extern volatile GUI_TIMER_TIME OS_TimeMS;
      /* USER CODE END PV */
      
    17. А в функцію "SysTick_Handler" додаємо інкремент змінної "OS_TimeMS" і тепер ця функція виглядатеме так:
      /**
        * @brief This function handles System tick timer.
        */
      void SysTick_Handler(void)
      {
        /* USER CODE BEGIN SysTick_IRQn 0 */
      
        /* USER CODE END SysTick_IRQn 0 */
        HAL_IncTick();
        /* USER CODE BEGIN SysTick_IRQn 1 */
        OS_TimeMS++;
        /* USER CODE END SysTick_IRQn 1 */
      }
      
    18. Остаточно перевіряємо проект на відсутність помилок. Компілюємо "Ctrl+B". Як помилок немає переходимо до малювання.

    Невеличка перевірка роботи дисплею

    Відкриємо файл "main.c" і додамо декілька рядків для перевірки роботи дисплею і бібліотеки:
    /* USER CODE BEGIN 2 */
      GUI_Init();
      lcdBacklightOn();
      GUI_SetColor(GUI_YELLOW);
      GUI_SetBkColor(GUI_BLUE);
      GUI_Clear();
      GUI_FillCircle(GUI_GetScreenSizeX() / 2, GUI_GetScreenSizeY() / 2, 100);
      GUI_SetFont(&GUI_Font16B_1);
      GUI_SetColor(GUI_RED);
      GUI_SetBkColor(GUI_YELLOW);
      GUI_DispStringHCenterAt("Hello, World!",GUI_GetScreenSizeX() / 2, GUI_GetScreenSizeY() / 2);
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
       
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    
    Компілюємо, заливаємо, запускаємо, спостерігаємо:
    Приклад роботи

    Додавання бібліотеки touch screen

    Підключення і налаштування бібліотеки для роботи з сенсорною панеллю детально описано в статті "Бібліотека touchscreen XPT2046 по шині SPI". Повторювати не має сенсу. Переходьте за ланкою і робіть, як зазначено в статті "Бібліотека touchscreen XPT2046 по шині SPI". Все абсолютно так само. Та не забувайте, що шина SPI, на якій "сидить" touch screen має бути не швидшою за 2.5Mb/s. 

    Тестування touch screen

    Додамо до коду декілька рядків.
    Вставимо в код заголовний файл бібліотеки touch screen:
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "ili9341_light.h"
    #include "GUI.h"
    #include "XPT2046_touch.h"
    /* USER CODE END Includes */
    
    Користувацька секція 2 тепер буде виглядати так:
      /* USER CODE BEGIN 2 */
      GUI_Init();
      lcdBacklightOn();
      GUI_SetColor(GUI_YELLOW);
      GUI_SetBkColor(GUI_BLUE);
      GUI_Clear();
      GUI_SetPenSize(3);
      /* USER CODE END 2 */
    
    А в користувацьку секцію 4 додамо функцію зворотного виклику, яка викликається кожного разу коли відбувається доторкання до touch screen і ніжка T_IRQ притискається до логічного нуля:
    /* USER CODE BEGIN 4 */
    void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin)
    {
     if (GPIO_Pin == T_IRQ_Pin)
     {
      if(XPT2046_TouchPressed())
      {
       uint16_t x, y;
    
       if(XPT2046_TouchGetCoordinates(&x, &y))
       {
        GUI_DrawPoint(x, GUI_GetScreenSizeY() - y);
       }
      }
     }
    }
    /* USER CODE END 4 */
    
    Компілюємо, заливаємо, малюємо на екрані:
    Приклад роботи touch screen

    Файли

    вівторок, 29 жовтня 2019 р.

    STM32: Використання CCMRAM

    Передмова

    Почавши експериментувати і навчатись з такою платою на чипі STM32F407VE, я помітив що цей чип має в наявності два банки пам'яті: стандартний SRAM 0x20000000 - 0x20020000, та інший банк пам'яті Core-Coupled-Memory (CCM) 0x10000000 - 0x10010000. Це мене зацікавило і я трішечки почитав і розібрався, що то за пам'ять і як її задіяти в своїх проектах. Бо за замовчуванню вона не задіяна. Як комусь буде ця інформація корисною і стане в нагоді, то буду тільки радий цьому.

    Трішки теорії

    Деякі мікроконтролери STM32 мають два банки пам'яті: стандартний SRAM та інший банк пам'яті Core-Coupled-Memory (CCM) який може бути швидшим ніж стандартний SRAM, але зазвичай меншого об'єма. Інколи розміщення даних в цей регіон пам'яті може бути вигідним в плані швидкості і економії стандартної пам'яті. Пам'ять CCM під'єднана безпосередньо до шини D-Bus процесора. DMA не має доступу до цієї пам'яті. Тому є можливість розпаралелити дані свого проекту.

    Як задіяти

    Існує декілька способів. Але я опишу ті способи, що мені підійшли. Я використовую Atolic Truestudio + CubeMX і все що описано нижче - випробувано на цій IDE з генератором коду ініціалізації. Як на інших IDE буде, я не в курсі. Певен, що і на CubeIDE буде працювати все так само, бо є нащадком Atolic Truestudio з інтегрованим CubeMX.

    Розміщення всіх ініціалізованих даних у CCM:

    У вашому проекті буде файл з розширенням *.ld, наприклад, STM32F407VE_FLASH.ld. Відкрийте його на редагування та зробіть такі зміни в кінці секції ".data":
    } >RAM AT> FLASH
    
    На такий рядок:
    } >CCMRAM AT> FLASH

    Розміщення всіх нульових ініціалізованих даних у CCM:

    У вашому проекті буде файл з розширенням "*.ld", наприклад, "STM32F407VE_FLASH.ld". Відкрийте його на редагування та зробіть такі зміни в кінці секції ".bss":
    } >RAM
    
    На такий рядок:
    } >CCMRAM

    Розміщення окремих не ініціалізованих змінних у CCM:

    Ви можете дуже просто помістити не ініціалізовані змінні в CCM, додаючи до їх оголошення атрібут розділу CCMRAM. Наприклад масив "array_in_ccm" з 1024 байт даних типу "char":
    char array_in_ccm[1024] __attribute__((section(".ccmram")));
    
    Або таким чином:
    __attribute__((section(".ccmram"))) char array_in_ccm[1024];
    
    А зробивши перевизначення на початку файлу:
    #define CCMRAM __attribute__((section(".ccmram")))
    
    Можна оголошувати не ініціалізовані дані вже таким чином:
    CCMRAM char array_in_ccm[1024];
    
    Або так:
    char array_in_ccm[1024] CCMRAM;
    
    Щоб це працювало, перевірте наявність секції ".ccmram" в скрипті "STM32F407VE_FLASH.ld". За відсутності цієї секції - додайте:
    /* CCM-RAM section 
      * 
      * IMPORTANT NOTE! 
      * If initialized variables will be placed in this section,
      * the startup code needs to be modified to copy the init-values.  
      */
      .ccmram :
      {
        . = ALIGN(4);
        _sccmram = .;       /* create a global symbol at ccmram start */
        *(.ccmram)
        *(.ccmram*)
        
        . = ALIGN(4);
        _eccmram = .;       /* create a global symbol at ccmram end */
      } >CCMRAM
    
    Але будьте обережні, щоб не ініціалізувати змінну навіть з нулем. Оскільки це займе місце у FLASH.

    Розміщення окремих ініціалізованих змінних у CCM:

    І навпаки, ви можете створити ініціалізовані дані в розділі CCMRAM переконавшись що в скрипті "STM32F407VE_FLASH.ld" присутній такий запис:
     _siccmram = LOADADDR(.ccmram);
    
      /* CCM-RAM section 
      * 
      * IMPORTANT NOTE! 
      * If initialized variables will be placed in this section,
      * the startup code needs to be modified to copy the init-values.  
      */
      .ccmram :
      {
        . = ALIGN(4);
        _sccmram = .;       /* create a global symbol at ccmram start */
        *(.ccmram)
        *(.ccmram*)
        
        . = ALIGN(4);
        _eccmram = .;       /* create a global symbol at ccmram end */
      } >CCMRAM AT> FLASH
    Секція CCM-RAM в такому вигляді, дозволяє оголошувати і ініціалізовані дані, наприклад:
    char array_in_ccm[1024] CCMRAM = {0,1,2,3,4,5,6,7,8,9,0};
    
    Або так:
    CCMRAM char array_in_ccm[1024] = {0,1,2,3,4,5,6,7,8,9,0};
    
    Масив даних розташувався в пам'яті CCM:
    Зазначу, що використовуючи Atolic TrueStudio разом з CubeMX, в скрипті "STM32F407VE_FLASH.ld" секція, для розміщення ініціалізованих змінних в пам'ять CCM, вже була в наявності. І додавати чи правити нічого не знадобилось. Достатньо додати атрибут "__attribute__((section(".ccmram")))" до змінної для розміщення певних ініціалізованих даних до розділу пам'яті CCM.

    Джерело знань тут, або де інде. Шукайте, читайте, експериментуйте.