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() — функція обробки повідомлення від інших завдань.
Дві перші функції реалізують “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к ОЗП, а в мікроконтролерах простіше може взагалі не виявитися потрібного розміру. Як же бути? Відповідь проста: малювати екран по частинах! Але малювати можна по різному…
Рішення яке я застосував для цієї задачі просто як і все геніальне. Вона має буфер на дві екранні рядка. В один рядок промальовуємо все, що повинно бути, відправляємо її на екран через 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 доларів від людини, напевно, дізнався про цей проект з статті на Хабре (Спасибі тобі, Андрію!) і інше від мого колеги з сусіднього кюбикла.
Не зібрати гроші — це не проблема. Проблема — відсутність інтересу до проекту…