STM Урок 79. HAL. LTDC. EmWin. Подключаем акселерометр LSM6DS0



 

Урок 79

 

HAL. LTDC. EmWin. Подключаем акселерометр LSM6DS0

 

Продолжим работу с библиотекой EmWin.

И сегодня мы попробуем добавить в наш проект график. И не просто график, а живой график, который нам будет в реальном времени показыавать состояние определённых показателей. А в качестве этих определённых показателей мы возьмём показания осей акселерометра LSM6DS0, который мы уже разобрали вдоль и поперёк и который установлен на плате расширения X-NUCLEO-IKS01A1, подключаемую через стандартный разъём Arduino, также присутствующий на нашей отладочной плате STM32F746G-DISCO снизу.

Только вот, есть небольшая проблема — наша плата расширения не воткнётся в разъём платы отладочной, так как будет мешать раъём LAN. Поэтому пришлось сочинить вот такие вот переходники, которые я впоследствии спаял из штырьевых линеек и гнездовых линеек. Пусть это не очень красиво, зато проблема была решена

 

image07

 

Вот таким вот образом наши переходнички выглядят в разъёме отладочной платы

 

image08

 

А вот так вместе с платой расширения

 

image09

 

Также мы прекрасно помним, что данный датчик может подключаться как по шине I2C, так и по шине SPI. Ну, так как мы его уже подключали по первой шине, то и давайте не будем нарушать этих традиций.

Для этого посмотрим схему платы расширения и узнаем, на какие ножки разъёма у нас попадают контакты I2C

 

image00

 

Ну вот и хорошо. Уже что-то есть. Также по схеме можно убедиться что больше ничего такого интересного ни на какие ножки у нас не попадает, что позволяет нам быть уверенными, что у нас ничего не зашунтируется и не повлияет на работу нашей общей схемы. Теперь узнаем что приходит на те же ножки у отладочной платы STM32F746G-DISCO

 

image01

 

Ну вот практически вся нужная нам информация у нас есть.

Проект мы создадим на основе проекта урока 78 EMWIN_WM01 и назовём его EMWIN_Accel_LSM6DS0.

Запустим проект в Cube MX и включим шину I2C1

 

image03

 

Ножки также при добавлении сошлись с образцовыми

 

image04

 

В Configuration в I2C1 настроим скорость

 

image05

 

и включим прерывания

 

image06

 

Сгенерируем проект, подключим его в System Workbench, настроим его так же. как и в предыдущих уроках по EmWin и попробуем его собрать и прошить.

Если всё нормально работает, скопируем наши файлы для акселерометра из проекта урока 42 lsm6ds0.h и lsm6ds0.c в наш проект в соответствующие папки.

Сделаем проекту refresh и подключим нашу библиотеку в файле main.h

 

#include "DIALOG.h"

#include "lsm6ds0.h"

#include "stdint.h"

 

В файле main.c в главной функции main() вызовем инициализацию датчика

 

GUI_Clear();

Accel_Ini();

HAL_TIM_Base_Start_IT(&htim6);

 

Перейдём в файл lsm6ds0.h и подправим там подключенный файл, так как у нас серия седьмая контроллера, а заодно и библиотеку подключим

 

#include "stm32f7xx_hal.h"

#include "GUI.h"

 

Уберем дефайны светодиода

 

#define LD2_Pin GPIO_PIN_5

#define LD2_GPIO_Port GPIOA

#define LD2_ON HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET) //GREEN

#define LD2_OFF HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET)

 

 

В файле  удалим подключение шины USART

 

extern UART_HandleTypeDef huart2;

 

Функцию Error переименуем , так как у нас такая есть в другом модуле, а в её теле будем окрашивать экран дисплея в красный цвет и уходить в бесконечный цикл

 

void ErrorAcc(void)

{

  GUI_SelectLayer(1);

  GUI_SetBkColor(GUI_RED);

  GUI_Clear();

  while(1)

    ;

}

 

Удалим некоторый код из функции Accel_ReadAcc

 

buf2[7]=(uint8_t)zval;

HAL_UART_Transmit_DMA(&huart2,buf2,8);

if(xval>1500)

{

  LD2_ON;

}

else

{

  LD2_OFF;

}

HAL_Delay(20);

 

Удалим объявления переменных значений осей из функции и сделаем их глобальными

 

char str1[30]={0};
int16_t xval, yval, zval;

 

Немного изменим код в функции Accel_Ini

 

void Accel_Ini(void)

{

  uint16_t ctrl = 0x0000;

  HAL_Delay(1000);

  if(Accel_ReadID()!=0x68) ErrorAcc();

  AccInit(ctrl);

}

 

Ну и во всех местах, где встретится функция Error, переименуем её в ErrorAcc.

В GuiBuilder сочиним следующий диалог, добавив в него график, кнопку и три текстовых поля

 

image06

 

Вы можете не мучиться и открыть файл диалога из проекта.

Затем сохраним изменения в файл, скопируем файл в наш проект и откроем его (WindowDLG.c) в System Workbench и подключим три заголовочных файла

 

// USER START (Optionally insert additional includes)

#include "stm32f7xx_hal.h"

#include "math.h"

#include "lsm6ds0.h"

// USER END

 

Соберём проект и прошьём контроллер, чтобы убедиться что наш диалог у нас уже на экране

 

image10

 

Подключим значения наших осей

 

// USER START (Optionally insert additional static data)

extern int16_t xval, yval, zval;

// USER END

 

 

Добавим таймер для того, чтобы наш график и изменения текстовых полей оживили. Пока добавим для него глобальную переменную

 

extern int16_t xval, yval, zval;

WM_HTIMER hTimer1;

 

Инициализируем его в кейсе инициализации диалога WM_INIT_DIALOG

 

// USER START (Optionally insert additional code for further widget initialization)

hTimer1 = WM_CreateTimer(pMsg->hWin, 0, 100, 0);

// USER END

 

Для тех, кто не знаком с оконными приложения расскажу, что мы данной функцией создали таймер. Во входящих указателях у нас его хендл, идентификатор, период (у нас 100 милисекунд) и режим (у нас нулевой, так как режимы для таймера пока не придуманы и данный аргумент просто зарезервирован).

Ну и, как водится, создадим для таймера обработчик с помощью отдельного кейса и в этом обработчике соответственно будем вызывать опрос осей датчика

 

case WM_TIMER:

  Accel_ReadAcc();

  break;

 

Добавим глобальный текстовый массив

 

WM_HTIMER hTimer1;

char str01[10]={0};

 

В этом же обработчике покажем значения осей в наших текстовых полях, а также вызовем функцию GuiExec для обновления данных на экране

 

Accel_ReadAcc();

hItem = WM_GetDialogItem(pMsg->hWin, ID_TEXT_0);

sprintf(str01,"X: %05d", xval);

TEXT_SetText(hItem, str01);

hItem = WM_GetDialogItem(pMsg->hWin, ID_TEXT_1);

sprintf(str01,"Y: %05d", yval);

TEXT_SetText(hItem, str01);

hItem = WM_GetDialogItem(pMsg->hWin, ID_TEXT_2);

sprintf(str01,"Z: %05d", zval);

TEXT_SetText(hItem, str01);

GUI_Exec();

 

И в конце обработчика мы должны заново стартовать таймер, иначе работать не будет, здесь не Windows API. Такой порядок. Добавим сначала глобальную переменную

 

char str01[10]={0};

static int data_v;

 

Ну и вернёмся в процедуру обработки и запустим таймер заново

 

GUI_Exec();

data_v = pMsg->Data.v;

WM_RestartTimer(data_v, 10);

break;

 

Данная функция заставляет таймер запуститься через 10 милисекунд.

Соберём код и прошьём контроллер, теперь всё должно работать

 

image11

 

Теперь нам хочется, конечно, чтобы график наш тоже работал. Это намного сложнее, но мы этого не боимся.

Для начала в инициализации диалога получим хендл на график и настроим некоторые его свойства (до создания таймера)

 

// USER START (Optionally insert additional code for further widget initialization)

hItem = WM_GetDialogItem(pMsg->hWin, ID_GRAPH_0);

GRAPH_SetColor(hItem,GUI_LIGHTBLUE,GRAPH_CI_BK);

GRAPH_SetColor(hItem,GUI_GREEN,GRAPH_CI_FRAME);

GRAPH_SetColor(hItem,GUI_LIGHTMAGENTA,GRAPH_CI_GRID);

GRAPH_SetLineStyleH(hItem,GUI_LS_DASH);

GRAPH_SetLineStyleV(hItem,GUI_LS_DASH);

GRAPH_SetGridVis(hItem,1);

GRAPH_SetGridDistY(hItem,25);

hTimer1 = WM_CreateTimer(pMsg->hWin, 0, 100, 0);

 

Теперь давайте разберёмся, что мы тут намутили.

Сначала, естественно, мы получили хендл нашего графика, установили фоновый цвет, цвет бордюра, цвет сетки, назначили на вертикальные и горизонтальные линии сетки пунктир, чтобы не кидалась в глаза и не отвлекала от основных линий графика, сделали сетку видимой, а также назначили рассотяние между горизонтальными линиями в 25 пикселей (по умолчанию, если не назначать, то будет 50).

Условимся, что по вертикали наш график будет показывать значение осей, не важно в каких единицах, нам не нужно пока чтобы было именно в G. Нам нужно попробовать как работает график и будет ли что-то подвисать и мерцать. А по горизонтали соответственно будет время.

Далее создадим три глобальные переменные для хендлов наших осей

 

static int data_v;

GRAPH_DATA_Handle hData1,hData2,hData3;

// USER END

 

Создадим теперь хендлы для данных, подключим их к графику и произведём некоторые настройки для них

 

GRAPH_SetGridDistY(hItem,25);

hData1 = GRAPH_DATA_YT_Create(GUI_YELLOW, 400, 0, 1);

hData2 = GRAPH_DATA_YT_Create(GUI_RED, 400, 0, 1);

hData3 = GRAPH_DATA_YT_Create(GUI_GREEN, 400, 0, 1);

GRAPH_AttachData(hItem, hData1);

GRAPH_AttachData(hItem, hData2);

GRAPH_AttachData(hItem, hData3);

GRAPH_DATA_XY_SetOffY(hData1,100);

GRAPH_DATA_XY_SetOffY(hData2,100);

GRAPH_DATA_XY_SetOffY(hData3,100);

 

Проделываем все действия для всех осей. Сначала создаём хендлы для данных, назначая для их графиков различные цвета, делаем максимальную длину 400 пикселей, так как графики будут двигаться справа налево и они не должны доходить до вертикальной оси. Затем мы подключаем оси к графику, и делаем вертикальное смещение, чтобы ноль у нас был по центру, а не снизу, так как данные наши могут принимать и отрицаетльные значения. Попробуем это увидеть, собрав проект и прошив контроллер

 

image12

 

Вот так вот всё красиво. А на яву ещё красивей. Здесь у нас не очень заметны пунктирные желтые линии сетки. В видеоуроке это намного заметнее, так что смотрите.

Теперь поработаем со шкалой графика. Это отдельный пункт и без него ничего не получится. Добавим глобальную переменную для хендла шкалы

 

GRAPH_DATA_Handle hData1,hData2,hData3;

GRAPH_SCALE_Handle hScale;

 

Вернёмся в инициализацию, получим данный хендл и установим для шкалы определённые свойства

 

GRAPH_DATA_XY_SetOffY(hData3,100);

hScale = GRAPH_SCALE_Create(25, GUI_TA_RIGHT, GRAPH_SCALE_CF_VERTICAL, 25);

GRAPH_AttachScale(hItem, hScale);

GRAPH_SCALE_SetTextColor(hScale,GUI_BLACK);

 

То есть здесь мы получили хендл на шкалу, подключили её к графику и уставновили черный цвет. Вот результат, если мы прошьём контроллер, собрав соответственно проект

 

image13

 

У нас появилась вертикальная шкала, но как мы помним, данные у нас смещены на 100 пикселей (а также и пунктов) вверх. Давайте и шкалу тоже сместим, благо для этого в библиотеке emWin также припасена отдельная функция

 

GRAPH_SCALE_SetTextColor(hScale,GUI_BLACK);

GRAPH_SCALE_SetOff(hScale,100);

hTimer1 = WM_CreateTimer(pMsg->hWin, 0, 100, 0);

 

Соберём код и прошьём контроллер. Теперь другое дело!

 

image14

 

Ну вот. Теперь дело подошло к тому, что нам пора уже увидеть наш долгожданный график в реальности, то есть увидеть в реальном времени изменения показаний осей акселерометра.

Для этого зайдём в обработчик событий таймера (case WM_TIMER:) и добавим там следующий код

 

Accel_ReadAcc();

GRAPH_DATA_YT_AddValue(hData1, xval/170);

GRAPH_DATA_YT_AddValue(hData2, yval/170);

GRAPH_DATA_YT_AddValue(hData3, zval/170);

 

Код достаточно прост. Данная функция добавляет точку в самую правую часть графика, по высоте совпадающую со значением, переданным во втором входящем аргументе, а весь остальной график при этом сдвигает влево. всё это происходит раз в 10 милисекунд. так как мы выставили именно это значение в перезапуск таймера. На 170 мы делим потому, что показания у нас колеблятся от -16384 до +16383, а такие цифры в наш диапазон графика не влезут. Соберём код и прошьём контроллер. А вот и результат

 

image15

 

Всё замечательно работает. А зачем же кнопка? А затем, что порой нам требуется повнимательней изучить какие-то процессы и мы не успеваем оценить всё это в реальном времени, так как показания на графике постоянно движутся. Поэтому иногда требуется этот процесс остановить. Для того и кнопка, которая также затем должна уметь данный процесс возобновить.

Нам нужен для начала будет статус, ну или состояние графика, чтобы программа знала, в данный момент график у нас движется или остановлен. Добавим такую переменную в глобальных

 

GRAPH_SCALE_Handle hScale;

static U8 graph_stat=0;

 

Теперь в конце кейса событий от таймера мы будем таймер уже стартовать только по условию, что график у нас не остановлен

 

if(graph_stat==0) WM_RestartTimer(data_v, 10);

 

Ну и, соответственно, напишем реакцию на отжатие нашей кнопки

 

case WM_NOTIFICATION_RELEASED:

  // USER START (Optionally insert code for reacting on notification message)

  hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0);

  if(graph_stat==0)

  {

    graph_stat=1;

    BUTTON_SetText(hItem, "Start");

  }

  else

  {

    graph_stat=0;

    BUTTON_SetText(hItem, "Stop");

    WM_RestartTimer(data_v, 10);

  }

  // USER END

 

Теперь кратко по реакции. Если у нас график движется (статус 0), то мы устанавливаем статус 1 (график остановлен), а также меняем надпись на кнопке в «Start«, а если у нас статус 1, то также меняем статус, меняем надпись на кнопке, возвращая ей первоначальное значение, а также стартуем таймер, иначе мы никогда не попадём в обработчик таймера. Вот так всё оказалось просто. А вот и результат

 

image16

 

Таким образом, мы теперь умеем не только просто работать с элементами управления окон, но и умеем это делать грамотно, доказав это написанием проекта работы сразу с тремя линиями графика, показывающими изменение показателей от трёх источников именно в реальном времени. То есть мы создали такой проект, который можно уже применить в конкретной ситуации.

Всем спасибо за внимание и удачи в постижении такой нелёгкой науки под названием «программирование«!

 

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY

Оценочную плату можно приобрести здесь STM32 X-NUCLEO-IKS01A1

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM HAL. LTDC. EmWin. Подключаем акселерометр LSM6DS0

2 комментария на “STM Урок 79. HAL. LTDC. EmWin. Подключаем акселерометр LSM6DS0
  1. evorontsov:

    Спасибо большое! Все понятно…. Пока сам не начал переписывать под свои задачи и свое железо.
    Уткнулся с таймеры и не могу сдвинуться с места.

    static void _cbDialog(WM_MESSAGE * pMsg) {
    WM_HWIN hItem;
    int NCode;
    int Id;
    // USER START (Optionally insert additional variables)
    // USER END

    switch (pMsg->MsgId) {
    case WM_INIT_DIALOG:
    //
    // Initialization of 'Graph'
    //

    hItem = WM_GetDialogItem(pMsg->hWin, ID_GRAPH_0);
    GRAPH_SetBorder(hItem, 3, 3, 3, 3);
    // USER START (Optionally insert additional code for further widget initialization)

    hTimer = WM_CreateTimer(pMsg->hWin, 0, 100, 0);

    // USER END
    break;

    case WM_TIMER:
    GUI_Exec();
    WM_RestartTimer(pMsg->hWin, 100);
    break;

    case WM_NOTIFY_PARENT:
    Id = WM_GetId(pMsg->hWinSrc);
    NCode = pMsg->Data.v;
    switch(Id) {
    case ID_BUTTON_0: // Notifications sent by '/2'
    switch(NCode) {

    Почему может не работать таймер? Кнопки работают

    • evorontsov:

      Потерял полдня, но разобрался….
      Может кому полезно будет:

      stm32f4xx_it.c

      /* Private user code ———————————————————*/
      /* USER CODE BEGIN 0 */
      #include «GUI.h»
      volatile GUI_TIMER_TIME OS_TimeMS;
      /* USER CODE END 0 */

      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 */
      }

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*