DevCore: програмна частина проекту DevBoy

Привіт, друзі!

З вами знову Микола, у минулій статті “DevBoy — як я створив проект пристрої з відкритим вихідним кодом і запустив проект на Kickstarter” наголос робився більше на зовнішньому вигляді і залозі, сьогодні поговоримо про те як це зроблено “всередині” і розберемо програмну частину.

Кому цікаво — прошу під кат.

Як було сказано раніше, проект базується на мікроконтролері STM32F415RG від STMicroelectronics на ядрі ARM Cortex-M4. Для розробки під дані мікроконтролери існує кілька різних IDE, однак для відкритого проекту потрібна як мінімум безкоштовно IDE, а краще ще і Open Source. Крім того, IDE має підтримуватися в STM32CubeMX. На той момент, коли я почав працювати над цим проектом була тільки одна IDE задовольняє всім цим вимогам — System Workbench for STM32.

На даний момент існує ще Atollic TrueStudio, стала безкоштовною після того як STMicroelectronics їх купила.

Наступна використовувана програма — це STM32CubeMX. Дана програма являє собою утиліту для конфігурування периферії мікроконтролера за допомогою графічного інтерфейсу.

Результатом є код, який включає в себе Hardware Abstraction Layer(HAL). Багато програмісти не дуже люблять це “творіння“, воно не позбавлене багів, але, тим не менше, воно значно спрощує розробку і покращує переносимість програм між різними мікроконтролерами від STMicroelectronics.

Крім того, при конфігурації можна задати використання деякого стороннього відкритого ЗА такого як FreeRTOS, FatFS і деяких інших.

Опис використовуваного ЗА закінчили, тепер переходимо до найцікавішого — до DevCore. Назва походить від “Ядро Розробки” підемо по порядку.

В першу чергу це З++ RTOS Wrapper(FreeRTOS в даному випадку). Врапперов потрібен з двох причин:

  • Набагато приємніше створити об’єкт, а потім кликати наприклад mutex.Take(), ніж створювати хендл, кликати функцію створення, а потім передавати цей хендл у всі функції роботи з мьютексами
  • У разі необхідності заміни RTOS досить замінити врапперов, а не всі виклики функцій RTOS з коду

Приводити код враппера тут не має сенсу, кому цікаво — дивимося на GitHub, а ми йдемо далі.

Наступна частина — Application Framework. Це базовий клас для всіх завдань. Оскільки це всього два відносно не великих файлу є сенс привести їх повністю:

Header

//******************************************************************************
// @file AppTask.h
// @author Nicolai Shlapunov
//
// @details DevCore: Application Task Base Class, header
//
// @section LICENSE
//
// Software License Agreement (Modified BSD License)
//
// Copyright (c) 2016, Devtronic & Nicolai Shlapunov
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following in the disclaimer
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the Devtronic nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
// 4. Redistribution and use of this software other than as permitted under
// this license is void and will automatically terminate your rights under
// this license.
//
// THIS SOFTWARE IS PROVIDED BY DEVTRONIC "AS IS" AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL DEVTRONIC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// @section SUPPORT
//
// Devtronic invests time and resources providing this open source code,
// please support Devtronic and open-source hardware/software by
// donations and/or purchasing products from Devtronic.
//
//******************************************************************************

#ifndef AppTask_h
#define AppTask_h

// *****************************************************************************
// *** Includes ************************************************************
// *****************************************************************************
#include "DevCfg.h"

// *****************************************************************************
// * AppTask class. This class is wrapper for call C++ function from class. ****
// *****************************************************************************
class AppTask
{
public:
 // *************************************************************************
 // *** Init Task *******************************************************
 // *************************************************************************
 virtual void InitTask(void) {CreateTask();}

protected:
 // *************************************************************************
 // *** Constructor *****************************************************
 // *************************************************************************
 AppTask(uint16_t stk_size, uint8_t task_prio, const char name[],
 uint16_t queue_len = 0U, uint16_t queue_msg_size = 0U,
 void* task_msg_p = nullptr, uint32_t task_interval_ms = 0U) :
 ctrl_queue((queue_len + 2U), sizeof(CtrlQueueMsg)),
 task_queue(queue_len, queue_msg_size), task_msg_ptr(task_msg_p),
 timer(task_interval_ms, RtosTimer::REPEATING, TimerCallback, (void*)this),
 stack_size(stk_size), task_priority(task_prio), task_name(name) {};

 // *************************************************************************
 // *** Virtual destructor - prevent warning ****************************
 // *************************************************************************
 virtual ~AppTask() {};

 // *************************************************************************
 // *** Create task function ********************************************
 // *************************************************************************
 // * This function creates new task in FreeRTOS, provide pointer to function
 // * and pointer to class as parameter. When TaskFunctionCallback() called
 // * from FreeRTOS, use it pointer to class parameter from to call virtual
 // * functions.
 void CreateTask();

 // *************************************************************************
 // *** Setup function **************************************************
 // *************************************************************************
 // * * virtual function - some tasks may not have Setup() actions
 virtual Result Setup() {return Result::RESULT_OK;}

 // *************************************************************************
 // *** IntervalTimerExpired function ***********************************
 // *************************************************************************
 // * Empty virtual function - some tasks may not have TimerExpired() actions
 virtual Result TimerExpired() {return Result::RESULT_OK;}

 // *************************************************************************
 // *** ProcessMessage function *****************************************
 // *************************************************************************
 // * Empty virtual function - some tasks may not have ProcessMessage() actions
 virtual Result ProcessMessage() {return Result::RESULT_OK;}

 // *************************************************************************
 // *** Loop function ***************************************************
 // *************************************************************************
 // * Empty virtual function - some tasks may not have Loop() actions
 virtual Result Loop() {return Result::RESULT_OK;}

 // *************************************************************************
 // *** SendTaskMessage function ****************************************
 // *************************************************************************
 Result SendTaskMessage(const void* task_msg, bool is_priority = false);

private:
 // Task control message queue types
 enum CtrlQueueMsgType
{
CTRL_TIMER_MSG,
CTRL_TASK_QUEUE_MSG
};
 // Task control message queue struct
 struct CtrlQueueMsg
{
 CtrlQueueMsgType type;
};
 // Task control queue
 RtosQueue ctrl_queue;

 // Task queue
 RtosQueue task_queue;
 // Pointer to receive message buffer
 void* task_msg_ptr;

 // Timer object
 RtosTimer timer;

 // Task stack size
 uint16_t stack_size;
 // Task priority
 uint8_t task_priority;
 // Pointer to the task name
 const char* task_name;

 // *************************************************************************
 // *** IntLoop function ************************************************
 // *************************************************************************
 Result IntLoop();

 // *************************************************************************
 // *** TaskFunctionCallback ********************************************
 // *************************************************************************
 static void TaskFunctionCallback(void* ptr);

 // *************************************************************************
 // *** IntervalTimerCallback function **********************************
 // *************************************************************************
 static void TimerCallback(void* ptr);

 // *************************************************************************
 // *** SendControlMessage function *************************************
 // *************************************************************************
 Result SendControlMessage(const CtrlQueueMsg& ctrl_msg, bool is_priority = false);

 // *************************************************************************
 // *** Change counter **************************************************
 // *************************************************************************
 static void ChangeCnt(bool is_up);

 // *************************************************************************
 // *** Private constructor and assign operator - prevent copying *******
 // *************************************************************************
AppTask();
 AppTask(const AppTask&);
 AppTask& operator=(const AppTask&);
};

#endif

Code

//******************************************************************************
// @file AppTask.cpp
// @author Nicolai Shlapunov
//
// @details DevCore: Application Task Base Class, implementation
//
// @copyright Copyright (c) 2016, Devtronic & Nicolai Shlapunov
// All rights reserved.
//
// @section SUPPORT
//
// Devtronic invests time and resources providing this open source code,
// please support Devtronic and open-source hardware/software by
// donations and/or purchasing products from Devtronic.
//
//******************************************************************************

// *****************************************************************************
// *** Includes ************************************************************
// *****************************************************************************
#include "AppTask.h"
#include "RtosMutex.h"

// *****************************************************************************
// *** Static variables ****************************************************
// *****************************************************************************
static RtosMutex startup_mutex;
static uint32_t startup_cnt = 0U;

// *****************************************************************************
// *** Create task function ************************************************
// *****************************************************************************
void AppTask::CreateTask()
{
 Result result = Result::RESULT_OK;

 // If interval timer period isn't zero or task queue present
 if((timer.GetTimerPeriod() != 0U) || (task_queue.GetQueueLen() != 0U))
{
 // Control Set Queue name
 ctrl_queue.SetName(task_name, "Ctrl");
 // Create control queue
 result = ctrl_queue.Create();
}
 // If task queue present
 if(task_queue.GetQueueLen() != 0U)
{
 // Set Task Queue name
 task_queue.SetName(task_name, "Task");
 // Create task queue
 result |= task_queue.Create();
}
 // If interval timer period isn't zero
 if(timer.GetTimerPeriod() != 0U)
{
 // Create timer
 result |= timer.Create();
}
 // Create task: function - TaskFunctionCallback(), parameter - pointer to this"
 result |= Rtos::TaskCreate(TaskFunctionCallback, task_name, stack_size, this, task_priority);

 // Check result
if(result.IsBad())
{
 // TODO: implement error handling
Break();
}
}

// *****************************************************************************
// *** SendTaskMessage function ********************************************
// *****************************************************************************
Result AppTask::SendTaskMessage(const void* task_msg, bool is_priority)
{
 Result result = Result::RESULT_OK;

 // Send task message to front or back of task queue
 if(is_priority == true)
{
 result = task_queue.SendToFront(task_msg);
}
else
{
 result = task_queue.SendToBack(task_msg);
}

 // If successful - send message to the control queue
if(result.IsGood())
{
 CtrlQueueMsg ctrl_msg;
 ctrl_msg.type = CTRL_TASK_QUEUE_MSG;
 result = SendControlMessage(ctrl_msg, is_priority);
}

 return result;
}

// *****************************************************************************
// *** IntLoop function ****************************************************
// *****************************************************************************
Result AppTask::IntLoop()
{
 Result result = Result::RESULT_OK;

while(result.IsGood())
{
 // Buffer for control message
 CtrlQueueMsg ctrl_msg;
 // Read on the control queue
 result = ctrl_queue.Receive(&ctrl_msg, timer.GetTimerPeriod() * 2U);
 // If successful
if(result.IsGood())
{
 // Check message type
switch(ctrl_msg.type)
{
 case CTRL_TIMER_MSG:
 result = TimerExpired();
break;

 case CTRL_TASK_QUEUE_MSG:
{
 // Non blocking read from the task queue
 result = task_queue.Receive(task_msg_ptr, 0U);
 // If successful
if(result.IsGood())
{
 // Process it!
 result = ProcessMessage();
}
break;
}

default:
 result = Result::ERR_INVALID_ITEM;
break;
}
}
}

 return result;
}

// *****************************************************************************
// *** TaskFunctionCallback ************************************************
// *****************************************************************************
void AppTask::TaskFunctionCallback(void* ptr)
{
 Result result = Result::ERR_NULL_PTR;

 if(ptr != nullptr)
{
 // Set good result
 result = Result::RESULT_OK;
 // Get reference to the task object
 AppTask& app_task = *(static_cast<AppTask*>(ptr));

 // Increment counter call before Setup()
ChangeCnt(true);
 // Call virtual Setup() function from class AppTask
app_task.Setup();
 // Decrement counter after call Setup()
ChangeCnt(false);
 // Pause for give other tasks run Setup()
RtosTick::DelayTicks(1U);
 // Pause while other tasks run Setup() before executing any Loop()
 while(startup_cnt) RtosTick::DelayTicks(1U);

 // If no timer or queue - just call Loop() function
 if((app_task.timer.GetTimerPeriod() == 0U) && (app_task.task_queue.GetQueueLen() == 0U))
{
 // Call virtual Loop() function from class AppTask
 while(app_task.Loop() == Result::RESULT_OK);
}
else
{
 // Start task timer if needed
 if(app_task.timer.GetTimerPeriod() != 0U)
{
 result = app_task.timer.Start();
}
 // Check result
if(result.IsGood())
{
 // Call internal AppTask function
 result = app_task.IntLoop();
}
 // Stop task timer if needed
 if(app_task.timer.GetTimerPeriod() != 0U)
{
 result |= app_task.timer.Stop();
}
}
}

 // Check result
if(result.IsBad())
{
 // TODO: implement error handling
Break();
}

 // Delete task exit after
Rtos::TaskDelete();
}

// *****************************************************************************
// *** TimerCallback function **********************************************
// *****************************************************************************
void AppTask::TimerCallback(void* ptr)
{
 Result result = Result::ERR_NULL_PTR;

 if(ptr != nullptr)
{
 // Get reference to the task object
 AppTask& task = *((AppTask*)ptr);

 // Create timer control message
 CtrlQueueMsg timer_msg;
 timer_msg.type = CTRL_TIMER_MSG;

 // Send message to the control queue
 result = task.SendControlMessage(timer_msg);
}

 // Check result
if(result.IsBad())
{
 // TODO: implement error handling
Break();
}
}

// *****************************************************************************
// *** SendControlMessage function *****************************************
// *****************************************************************************
Result AppTask::SendControlMessage(const CtrlQueueMsg& ctrl_msg, bool is_priority)
{
 Result result;

 if(is_priority == true)
{
 result = ctrl_queue.SendToFront(&ctrl_msg);
}
else
{
 result = ctrl_queue.SendToBack(&ctrl_msg);
}

 return result;
}

// *****************************************************************************
// *** Change counter ******************************************************
// *****************************************************************************
void AppTask::ChangeCnt(bool is_up)
{
 // Take semaphore before change counter
startup_mutex.Lock();
 // Check direction
 if(is_up == true)
{
 // Increment counter
startup_cnt++;
}
else
{
 // Decrement counter
startup_cnt--;
}
 // Give semaphore after changes
startup_mutex.Release();
}

Успадковані класи можуть перевизначити 4 віртуальні функції:

  • Setup() — функція викликається перед запуском завдання. Гарантується завершення коду у всіх даних функціях всіх завдань перед початком виконання основних циклів.
  • Loop() — основний цикл завдання, де завдання сама організовує що вона хоче. Не може використовуватися спільно з двома наступними функціями.
  • TimerExpired() — функція викликається періодично з заданим інтервалом. Зручно для реалізації опитування датчика наприклад.
  • ProcessMessage() — функція обробки повідомлення від інших завдань.
Читайте також  UE4 | Інвентар для Multiplayer #2 | Підключення Blueprint до C++

Дві перші функції реалізують “Arduino-Style” для завдань.

Дві наступні реалізують “подієву” систему спрощує взаємодія задач. При такому підході завдання реалізує зовнішній інтерфейс у вигляді функцій, які відправляють відправляють дані задачі через внутрішню поштову скриньку. При такому підході використовує даний інтерфейс не потрібно турбуватися в якому контексті виконуються дії. Правда це можливо тільки для сеттерів або команд. Для геттеров краще всього використовувати м’ютекси і копіювання даних для запобігання захоплення м’ютексу надовго.

Даний підхід підглянули коли я розробляв для медичного обладнання. Мікроконтроллер має одну «сторожового пса»(watchdog) і у випадку безлічі завдань потрібно відстежувати їх усіх. Для цього була окрема задача яка обслуговувала watchdog і отримувала повідомлення від інших завдань які висилаються з функції TimerExpired(). Якщо протягом періоду таймера завдання * n не приходили повідомлення — завдання померла, гасимо світло приймаємо дії з відключення всіх залозок впливають на пацієнта.

Всі завдання являють собою синглтоны, створити безпосередньо їх не можна, але можна отримати посилання на задачу. Для цього кожна задача реалізує статичний метод GetInstance():

// *****************************************************************************
// *** Get Instance ********************************************************
// *****************************************************************************
Application& Application::GetInstance(void)
{
 static Application application;
 return application;
}

Так само до складу входять завдання для виводу звуку, для модулів вводу і для обслуговування екрану.

Завдання виведення звуку досить проста — отримує масив частот і тривалостей і просто періодично змінює налаштування таймера для генерації прямокутних імпульсів певної частоти.

Header

//******************************************************************************
// @file SoundDrv.h
// @author Nicolai Shlapunov
//
// @details DevCore: Sound Driver Class, header
//
// @section LICENSE
//
// Software License Agreement (Modified BSD License)
//
// Copyright (c) 2016, Devtronic & Nicolai Shlapunov
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following in the disclaimer
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the Devtronic nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
// 4. Redistribution and use of this software other than as permitted under
// this license is void and will automatically terminate your rights under
// this license.
//
// THIS SOFTWARE IS PROVIDED BY DEVTRONIC "AS IS" AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL DEVTRONIC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// @section SUPPORT
//
// Devtronic invests time and resources providing this open source code,
// please support Devtronic and open-source hardware/software by
// donations and/or purchasing products from Devtronic.
//
//******************************************************************************

#ifndef SoundDrv_h
#define SoundDrv_h

// *****************************************************************************
// *** Includes ************************************************************
// *****************************************************************************
#include "DevCfg.h"
#include "AppTask.h"
#include "RtosMutex.h"
#include "RtosSemaphore.h"

// *****************************************************************************
// *** Sound Driver Class. This class implement work with sound. ***********
// *****************************************************************************
class SoundDrv : public AppTask
{
public:
 // *************************************************************************
 // *** Get Instance ****************************************************
 // *************************************************************************
 // * This class is singleton. For use this class you must call GetInstance()
 // * to receive reference to Sound Driver class
 static SoundDrv& GetInstance(void);

 // *************************************************************************
 // *** Init Sound Driver Task ******************************************
 // *************************************************************************
 virtual void InitTask(TIM_HandleTypeDef *htm);

 // *************************************************************************
 // *** Sound Driver Setup **********************************************
 // *************************************************************************
 virtual Result Setup();

 // *************************************************************************
 // *** Sound Driver Loop ***********************************************
 // *************************************************************************
 virtual Result Loop();

 // *************************************************************************
 // *** Beep function ***************************************************
 // *************************************************************************
 void Beep(uint16_t freq, uint16_t del, bool pause_after_play = false);

 // *************************************************************************
 // *** Play sound function *********************************************
 // *************************************************************************
 void PlaySound(const uint16_t* melody, uint16_t size, uint16_t temp_ms = 100U, bool rep = false);

 // *************************************************************************
 // *** Stop sound function *********************************************
 // *************************************************************************
 void StopSound(void);

 // *************************************************************************
 // *** Mute sound function *********************************************
 // *************************************************************************
 void Mute(bool mute_flag);

 // *************************************************************************
 // *** Is sound played function ****************************************
 // *************************************************************************
 bool IsSoundPlayed(void);

private:
 // Timer handle
 TIM_HandleTypeDef* htim = SOUND_HTIM;
 // Timer channel
 uint32_t channel = SOUND_CHANNEL;

 // Ticks variable
 uint32_t last_wake_ticks = 0U;

 // Pointer to table contains melody
 const uint16_t* sound_table = nullptr;
 // Size of table
 uint16_t sound_table_size = 0U;
 // Current position
 uint16_t sound_table_position = 0U;
 // Current frequency delay
 uint16_t current_delay = 0U;
 // Time for one frequency in ms
 uint32_t delay_ms = 100U;
 // Repeat flag
 bool repeat = false;

 // Mute flag
 bool mute = false;

 // Mutex to synchronize when playing melody frames
 RtosMutex melody_mutex;

 // Semaphore for start play sound
 RtosSemaphore sound_update;

 // *************************************************************************
 // *** Process Button Input function ***********************************
 // *************************************************************************
 void Tone(uint16_t freq);

 // *************************************************************************
 // ** Private constructor. Only GetInstance() allow to access this class. **
 // *************************************************************************
 SoundDrv() : AppTask(SOUND_DRV_TASK_STACK_SIZE, SOUND_DRV_TASK_PRIORITY,
 "SoundDrv") {};
};

#endif

Завдання обслуговування модулів воода теж досить проста. З цікавих моментів автовизначення модуля: спочатку за допомогою АЦП вимірюємо напругу, якщо воно в межах від 25% до 75% від живлячої напруги — вставлений аналоговий джойстик, інакше кнопки або енкодер. Якщо це не джойстик, перевіряємо четверту лінію I/O модуля: якщо на ній високий рівень — це кнопки(всі кнопки підтягнуті до харчування і при натисканні кнопки замикаються на землю), якщо на ній низький рівень — це енкодер(маленька кнопка «підтягнута» до землі і при натисканні замикається на харчування).

Header

//******************************************************************************
// @file InputDrv.h
// @author Nicolai Shlapunov
//
// @details DevCore: Input Class Driver, header
//
// @section LICENSE
//
// Software License Agreement (Modified BSD License)
//
// Copyright (c) 2016, Devtronic & Nicolai Shlapunov
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following in the disclaimer
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the Devtronic nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
// 4. Redistribution and use of this software other than as permitted under
// this license is void and will automatically terminate your rights under
// this license.
//
// THIS SOFTWARE IS PROVIDED BY DEVTRONIC "AS IS" AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL DEVTRONIC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// @section SUPPORT
//
// Devtronic invests time and resources providing this open source code,
// please support Devtronic and open-source hardware/software by
// donations and/or purchasing products from Devtronic.
//
//******************************************************************************

#ifndef InputDrv_h
#define InputDrv_h

// *****************************************************************************
// *** Includes ************************************************************
// *****************************************************************************
#include "DevCfg.h"
#include "AppTask.h"

// *****************************************************************************
// * Input Class Driver. This class implement work with user input elements like 
// * buttons and encoders.
class InputDrv : public AppTask
{
public:
 // *************************************************************************
 // *** Enum with all buttons *******************************************
 // *************************************************************************
 typedef enum 
{
 EXT_LEFT, // Left port ext
 EXT_RIGHT, // Right port ext
 EXT_MAX // Ext port count
 } PortType;

 // *************************************************************************
 // *** Enum with all devices types *************************************
 // *************************************************************************
 typedef enum 
{
 EXT_DEV_NONE, // No device
 EXT_DEV_BTN, // Buttons(cross)
 EXT_DEV_ENC, // Encoder
 EXT_DEV_JOY, // Joystick
 EXT_DEV_MAX // Device types count
 } ExtDeviceType;

 // *************************************************************************
 // *** Enum with all buttons *******************************************
 // *************************************************************************
 typedef enum 
{
 BTN_UP, // button Up
 BTN_LEFT, // Left button
 BTN_DOWN, // button Down
 BTN_RIGHT, // Right button
 BTN_MAX // Buttons count
 } ButtonType;

 // *************************************************************************
 // *** Enum with all encoder buttons ***********************************
 // *************************************************************************
 typedef enum
{
 ENC_BTN_ENT, // Press on the knob
 ENC_BTN_BACK, // Small button
 ENC_BTN_MAX // Buttons count
 } EncButtonType;

 // *************************************************************************
 // *** Get Instance ****************************************************
 // *************************************************************************
 // * This class is singleton. For use this class you must call GetInstance()
 // * to receive reference to Input class Driver
 static InputDrv& GetInstance(void);

 // *************************************************************************
 // *** Init Input Driver Task ******************************************
 // *************************************************************************
 // * This function initialize Input class Driver. If htim provided, this 
 // * timer will be used instead FreeRTOS task.
 virtual void InitTask(TIM_HandleTypeDef* htm, ADC_HandleTypeDef* had);

 // *************************************************************************
 // *** Input Driver Setup **********************************************
 // *************************************************************************
 virtual Result Setup();

 // *************************************************************************
 // *** Input Driver Loop ***********************************************
 // *************************************************************************
 // * If FreeRTOS task used, this function just call ProcessInput() with 1 ms
 // * period. If FreeRTOS tick is 1 ms - this task must have highest priority 
 virtual Result Loop();

 // *************************************************************************
 // *** Process Input function ******************************************
 // *************************************************************************
 // * Main class function - must call periodically for process user input.
 // * If timer used, this function must be called from interrupt handler.
 void ProcessInput(void);

 // *************************************************************************
 // *** Process Encoders Input function *********************************
 // *************************************************************************
 void ProcessEncodersInput(void);

 // *************************************************************************
 // *** Get device type *************************************************
 // *************************************************************************
 ExtDeviceType GetDeviceType(PortType port);

 // *************************************************************************
 // *** Get button state ************************************************
 // *************************************************************************
 // Return button state: true - pressed, false - unpressed
 bool GetButtonState(PortType port, ButtonType button);

 // *************************************************************************
 // *** Get button state ************************************************
 // *************************************************************************
 // Return button state change flag: true - changed, false - not changed
 bool GetButtonState(PortType port, ButtonType button, bool& btn_state);

 // *************************************************************************
 // *** Get encoder counts from last call *******************************
 // *************************************************************************
 // * Return state of encoder. Class counts encoder clicks and stored inside.
 // * This function substract from current encoder counter last_enc_val and
 // * return it to user. Before return last_enc_val will be assigned to
 // * current encoder counter.
 int32_t GetEncoderState(PortType port, int32_t& last_enc_val);

 // *************************************************************************
 // *** Get button state ************************************************
 // *************************************************************************
 // Return button state: true - pressed, false - unpressed
 bool GetEncoderButtonState(PortType port, EncButtonType button);

 // *************************************************************************
 // *** Get encoder button state ****************************************
 // *************************************************************************
 // Return button state: true - pressed, false - unpressed
 bool GetEncoderButtonState(PortType port, EncButtonType button, bool& btn_state);

 // *************************************************************************
 // *** Get joystick counts from last call ******************************
 // *************************************************************************
 void GetJoystickState(PortType port, int32_t& x, int32_t& y);

 // *************************************************************************
 // *** SetJoystickCalibrationConsts ************************************
 // *************************************************************************
 // * Set calibration constants. Must be call for calibration joystick.
 void SetJoystickCalibrationConsts(PortType port, int32_t x_mid,
 int32_t x_kmin, int32_t x_kmax,
 int32_t y_mid, int32_t y_kmin,
 int32_t y_kmax);

 // *************************************************************************
 // *** Get joystick button state ***************************************
 // *************************************************************************
 // Return button state: true - pressed, false - unpressed
 bool GetJoystickButtonState(PortType port);

 // *************************************************************************
 // *** Get joystick button state ***************************************
 // *************************************************************************
 // Return button state: true - pressed, false - unpressed
 bool GetJoystickButtonState(PortType port, bool& btn_state);

private:
 // How many cycles button must change state before state will be changed in
 // result returned by GetButtonState() function. For reduce debouncing
 static const uint32_t BUTTON_READ_DELAY = 4U;
 // Coefficient for calibration
 static const int32_t COEF = 100;

 // ADC max value - 12 bit
 static const int32_t ADC_MAX_VAL = 0xFFF;
 // Joystich threshold
 static const int32_t JOY_THRESHOLD = 1000;

 // Ticks variable
 uint32_t last_wake_ticks = 0U;

 // *************************************************************************
 // *** Structure to describe button ************************************
 // *************************************************************************
 typedef struct
{
 bool btn_state; // Button state returned by GetButtonState() function
 bool btn_state_tmp; // Temporary button state for reduce debouncing
 uint8_t btn_state_cnt; // for Counter reduce debouncing
 GPIO_TypeDef* button_port;// Button port
 uint16_t button_pin; // Button pin
 GPIO_PinState pin_state; // High/low on input treated as pressed
 } ButtonProfile;

 // *************************************************************************
 // *** Structure to describe encoder ***********************************
 // *************************************************************************
 typedef struct
{
 // Encoder rotation
 int32_t enc_cnt; // Encoder counter
 uint8_t enc_state; // Current state of encder clock & data pins
 GPIO_TypeDef* enc_clk_port; // Encoder clock port
 uint16_t enc_clk_pin; // Encoder clock pin
 GPIO_TypeDef* enc_data_port;// Encoder data port
 uint16_t enc_data_pin; // Encoder data pin
 } EncoderProfile;

 // *************************************************************************
 // *** Structure to describe joysticks *********************************
 // *************************************************************************
 typedef struct
{
 int32_t x_ch_val; // Joystick X axis value
 uint32_t x_channel; // Joystick X axis ADC channel
 GPIO_TypeDef* x_port; // Joystick X axis port
 uint16_t x_pin; // Joystick X axis pin 
 int32_t bx; // Joystick X offset
 int32_t kxmin; // Joystick X coefficient
 int32_t kxmax; // Joystick X coefficient
 bool x_inverted; // Joystick X inverted flag
 int32_t y_ch_val; // Joystick Y axis value
 uint32_t y_channel; // Joystick Y axis ADC channel
 GPIO_TypeDef* y_port; // Joystick Y axis port
 uint16_t y_pin; // Joystick Y axis pin
 int32_t by; // Joystick Y offset
 int32_t kymin; // Joystick Y coefficient
 int32_t kymax; // Joystick Y coefficient
 bool y_inverted; // Joystick Y inverted flag
 } JoystickProfile;

 // *************************************************************************
 // *** Structure to describe encoders **********************************
 // *************************************************************************
 typedef struct
{
 EncoderProfile enc;
 ButtonProfile btn[ENC_BTN_MAX];
 } DevEncoders;

 // *************************************************************************
 // *** Structure to describe encoders **********************************
 // *************************************************************************
 typedef struct
{
 JoystickProfile joy;
 ButtonProfile btn;
 } DevJoysticks;

 // *************************************************************************
 // *** Structure to describe buttons ***********************************
 // *************************************************************************
 typedef struct
{
 ButtonProfile button[BTN_MAX];
 } DevButtons;

 // *** Array describes types of connected devices ***********************
 ExtDeviceType devices[EXT_MAX];

 // *** Structures array for describe buttons inputs *********************
 DevButtons buttons[EXT_MAX] =
{
 // Left device
 {{{false, false, 0, EXT_L1_GPIO_Port, EXT_L1_Pin, GPIO_PIN_RESET},
 {false, false, 0, EXT_L2_GPIO_Port, EXT_L2_Pin, GPIO_PIN_RESET},
 {false, false, 0, EXT_L3_GPIO_Port, EXT_L3_Pin, GPIO_PIN_RESET},
 {false, false, 0, EXT_L4_GPIO_Port, EXT_L4_Pin, GPIO_PIN_RESET}}},
 // Right device
 {{{false, false, 0, EXT_R1_GPIO_Port, EXT_R1_Pin, GPIO_PIN_RESET},
 {false, false, 0, EXT_R2_GPIO_Port, EXT_R2_Pin, GPIO_PIN_RESET},
 {false, false, 0, EXT_R3_GPIO_Port, EXT_R3_Pin, GPIO_PIN_RESET},
 {false, false, 0, EXT_R4_GPIO_Port, EXT_R4_Pin, GPIO_PIN_RESET}}}
};

 // *** Structures array for describe encoders inputs ********************
 DevEncoders encoders[EXT_MAX] =
{
 // Left device
 {{0, 0, EXT_L1_GPIO_Port, EXT_L1_Pin, EXT_L2_GPIO_Port, EXT_L2_Pin}, // Encoder
 {{false, false, 0, EXT_L3_GPIO_Port, EXT_L3_Pin, GPIO_PIN_RESET}, // Button Enter
 {false, false, 0, EXT_L4_GPIO_Port, EXT_L4_Pin, GPIO_PIN_SET}}}, // Back Button
 // Right device
 {{0, 0, EXT_R1_GPIO_Port, EXT_R1_Pin, EXT_R2_GPIO_Port, EXT_R2_Pin}, // Encoder
 {{false, false, 0, EXT_R3_GPIO_Port, EXT_R3_Pin, GPIO_PIN_RESET}, // Button Enter
 {false, false, 0, EXT_R4_GPIO_Port, EXT_R4_Pin, GPIO_PIN_SET}}} // Back Button
};

 // *** Structures array for describe encoders inputs ********************
 DevJoysticks joysticks[EXT_MAX] =
{
 // Left device
 {{0, ADC_CHANNEL_11, EXT_L2_GPIO_Port, EXT_L2_Pin, 0, COEF, COEF, false, // Joystick
 0, ADC_CHANNEL_10, EXT_L1_GPIO_Port, EXT_L1_Pin, 0, COEF, COEF, true},
 {false, false, 0, EXT_L3_GPIO_Port, EXT_L3_Pin, GPIO_PIN_RESET}}, // Button
 // Right device
 {{0, ADC_CHANNEL_13, EXT_R2_GPIO_Port, EXT_R2_Pin, 0, COEF, COEF, false, // Joystick
 0, ADC_CHANNEL_12, EXT_R1_GPIO_Port, EXT_R1_Pin, 0, COEF, COEF, true},
 {false, false, 0, EXT_R3_GPIO_Port, EXT_R3_Pin, GPIO_PIN_RESET}} // Button
};

 // Handle to timer used for process encoders input
 TIM_HandleTypeDef* htim = nullptr;
 // Handle to timer used for process encoders input
 ADC_HandleTypeDef* hadc = nullptr;

 // *************************************************************************
 // *** Process Button Input function ***********************************
 // *************************************************************************
 void ProcessButtonInput(ButtonProfile& button);

 // *************************************************************************
 // *** Process Encoder Input function **********************************
 // *************************************************************************
 void ProcessEncoderInput(EncoderProfile& encoder);

 // *************************************************************************
 // *** Process Joystick Input function *********************************
 // *************************************************************************
 void ProcessJoystickInput(JoystickProfile& joysticks, PortType port);

 // *************************************************************************
 // *** Emulate buttons using joystick function *************************
 // *************************************************************************
 void EmulateButtonsByJoystick(PortType port);

 // *************************************************************************
 // *** Emulate encoders using buttons function *************************
 // *************************************************************************
 void EmulateEncodersByButtons(PortType port);

 // *************************************************************************
 // *** Configure inputs devices types **********************************
 // *************************************************************************
 ExtDeviceType DetectDeviceType(PortType port);

 // *************************************************************************
 // *** Configure ADC ***************************************************
 // *************************************************************************
 void ConfigADC(ExtDeviceType dev_left, ExtDeviceType dev_right);

 // *************************************************************************
 // *** Configure inputs for read digital/analog data *******************
 // *************************************************************************
 void ConfigInputIO(bool is_digital, PortType port);

 // *************************************************************************
 // ** Private constructor. Only GetInstance() allow to access this class. **
 // *************************************************************************
 InputDrv() : AppTask(INPUT_DRV_TASK_STACK_SIZE, INPUT_DRV_TASK_PRIORITY,
 "InputDrv") {};
};

#endif

Завдання обслуговування екрану — найцікавіша завдання. Почнемо з того, що екран 320x240x16bit, разом потрібно 153600 байт для фреймбуфер. Це не просто багато, це просто величезна — у даному мікроконтролері всього 192к ОЗП, а в мікроконтролерах простіше може взагалі не виявитися потрібного розміру. Як же бути? Відповідь проста: малювати екран по частинах! Але малювати можна по різному…

Читайте також  Швидкий старт з ARM Mbed: розробка на сучасних мікроконтролерах для початківців

Рішення яке я застосував для цієї задачі просто як і все геніальне. Вона має буфер на дві екранні рядка. В один рядок промальовуємо все, що повинно бути, відправляємо її на екран через SPI в режимі DMA, а самі в цей час може готувати інший рядок.

Звідки ж завдання знає що повинно бути в рядку і як її намалювати? А вона не знає! Але у неї є список об’єктів, які знають як намалювати себе. Кожен такий об’єкт успадковується від класу VisObject.

Header

//******************************************************************************
// @file VisObject.h
// @author Nicolai Shlapunov
//
// @details DevCore: Visual Object Base Class, header
//
// @section LICENSE
//
// Software License Agreement (BSD License)
//
// Copyright (c) 2016, Devtronic & Nicolai Shlapunov
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following in the disclaimer
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the Devtronic nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY DEVTRONIC "AS IS" AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL DEVTRONIC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//******************************************************************************

#ifndef VisObject_h
#define VisObject_h

// *****************************************************************************
// *** Includes ************************************************************
// *****************************************************************************
#include "DevCfg.h"

// *****************************************************************************
// * VisObject class. This class implements base Visual Objects properties.
class VisObject
{
public:
 // *************************************************************************
 // *** Action **********************************************************
 // ************************************************************************* 
 typedef enum
{
 ACT_TOUCH, // When object touched
 ACT_UNTOUCH, // When object detouched
 ACT_MOVE, // When object moved on object
 ACT_MOVEIN, // When object moved in to object
 ACT_MOVEOUT, // When object moved out of object
 ACT_MAX // Total possible actions
 } ActionType;

 // *************************************************************************
 // *** VisObject *******************************************************
 // ************************************************************************* 
 VisObject() {};

 // *************************************************************************
 // *** ~VisObject ******************************************************
 // *************************************************************************
 // * Destructor. Call DelVisObjectFromList() from DisplayDrv class for
 // * remove from list before delete and delete semaphore.
 virtual ~VisObject();

 // *************************************************************************
 // *** LockVisObject ***************************************************
 // *************************************************************************
 void LockVisObject();

 // *************************************************************************
 // *** UnlockVisObject *************************************************
 // *************************************************************************
 void UnlockVisObject();

 // *************************************************************************
 // *** Show ************************************************************
 // *************************************************************************
 // * Show VisObject on screen. This function call AddVisObjectToList() from
 // * DisplayDrv class. When this function calls first time, user must
 // * provide Z level. In future user can call this function without
 // * parameters - previously set Z will be used.
 virtual void Show(uint32_t z_pos = 0);

 // *************************************************************************
 // *** Hide ************************************************************
 // *************************************************************************
 // * Hide VisObject from screen. This function call DelVisObjectFromList()
 // * from DisplayDrv class.
 virtual void Hide(void);

 // *************************************************************************
 // *** IsShow **********************************************************
 // *************************************************************************
 // * Check status of Show Visual Object. Return true if object in DisplayDrv list.
 віртуальний bool IsShow(void);

 // *************************************************************************
 // *** Move ************************************************************
 // *************************************************************************
 // * Move object on screen. Set new x and y coordinates. If flag is set - 
 // * move is relative, not absolute.
 virtual void Move(int32_t x, int32_t y, bool is_delta = false);

 // *************************************************************************
 // *** DrawInBufH ******************************************************
 // *************************************************************************
 // * Draw one horizontal line of object in specified buffer.
 // * Each class derived must implement this function.
 virtual void DrawInBufH(uint16_t* buf, int32_t n, int32_t row, int32_t start_y = 0) = 0;

 // *************************************************************************
 // *** DrawInBufW ******************************************************
 // *************************************************************************
 // * Draw one vertical line of object in specified buffer.
 // * Each class derived must implement this function.
 virtual void DrawInBufW(uint16_t* buf, int32_t n, int32_t line, int32_t start_x = 0) = 0;

 // *************************************************************************
 // *** Action **********************************************************
 // *************************************************************************
 virtual void Action(ActionType action, int32_t tx, int32_t ty);

 // *************************************************************************
 // *** Return Start X coordinate ***************************************
 // *************************************************************************
 virtual int32_t GetStartX(void) {return x_start;};

 // *************************************************************************
 // *** Return Start Y coordinate ***************************************
 // *************************************************************************
 virtual int32_t GetStartY(void) {return y_start;};

 // *************************************************************************
 // *** Return End X coordinate *****************************************
 // *************************************************************************
 virtual int32_t GetEndX(void) {return x_end;};

 // *************************************************************************
 // *** Return End Y coordinate *****************************************
 // *************************************************************************
 virtual int32_t GetEndY(void) {return y_end;};

 // *************************************************************************
 // *** Return Width of object ******************************************
 // *************************************************************************
 virtual int32_t GetWidth(void) {return width;};

 // *************************************************************************
 // *** Return Height of object *****************************************
 // *************************************************************************
 virtual int32_t GetHeight(void) {return height;};

protected:
 // *************************************************************************
 // *** Object parameters ***********************************************
 // *************************************************************************

 // X and Y start coordinates of object
 int16_t x_start = 0, y_start = 0;
 // X and Y end coordinates of object
 int16_t x_end = 0, y_end = 0;
 // Width and Height of object
 int16_t width = 0, height = 0;
 // Rotation of object
 int8_t rotation = 0;
 // Active Object
 bool active = false;

private:
 // *************************************************************************
 // *** Object parameters ***********************************************
 // *************************************************************************
 // * Only base class and DisplayDrv have access to this parameters

 // Z position of object
 uint16_t z = 0;
 // Pointer to next object. This pointer need to maker object list. Object
 // can only be added to one list.
 VisObject* p_next = nullptr;
 // Pointer to next object. This pointer need to maker object list. Object
 // can only be added to one list.
 VisObject* p_prev = nullptr;

 // DisplayDrv is friend for access to pointers and Z
 friend class DisplayDrv;
};

#endif

Завдання обслуговування екрану для кожного рядка проходиться за списком об’єктів і викликає функцію DrawInBufW() передаючи їй покажчик на буфер, кількість точок, рисуемую лінію і початкову позицію(поки не використовується була ідея використовувати режим екрану контролера для оновлення «вікна»). Фактично кожен об’єкт малює себе сам поверх інших вже намальованих і легко розташувати об’єкти в потрібному порядку, просто розміщуючи їх у потрібну позицію списку.

Читайте також  Заявка на фреймворк для мультиплатформного бота

Крім того, такий підхід дозволяє легко інтегрувати обробку активних об’єктів — отримавши координати від контролера тачскріна завдання обслуговування екрану може пройтися по листу з кінця в пошуку активного об’єкта потрапляє в координати натискання. У разі перебування такого об’єкта викликається віртуальна функція Action() для даного об’єкта.

На даний момент є об’єкти для рядків, примітивів(лінія, квадрат, коло), картинок і карти плиток(для створення ігор).

Так само в DevCore входить код для деяких елементів UI(наприклад меню), інтерфейс для драйвера I2C, драйвер I2C та бібліотеки для роботи з датчиком BME280 і EEPROM 24С256, але це не так цікаво і поки описувати не буду — і так вийшло досить об’ємно.

Повний код доступний без реєстрації і СМС на GitHub: https://github.com/nickshl/devboy

P. S. Судячи з усього, компанія йде до Epic Fail’у. За перший тиждень всього три бакера, з них долар від якогось “Інноваційного фонду“, 180 доларів від людини, напевно, дізнався про цей проект з статті на Хабре (Спасибі тобі, Андрію!) і інше від мого колеги з сусіднього кюбикла.

Не зібрати гроші — це не проблема. Проблема — відсутність інтересу до проекту…

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *