Compare commits

..

33 Commits

Author SHA1 Message Date
Erki
af9db5a1b0 Peripherals: Fix missing include
All checks were successful
CI & Unit Tests / Unit-Tests (push) Successful in 48s
CI & Unit Tests / Docs (push) Successful in 12s
2024-03-05 20:28:23 +02:00
Erki
470ad75376 Peripherals: Begin ESP HAL
All checks were successful
CI & Unit Tests / Unit-Tests (push) Successful in 54s
CI & Unit Tests / Docs (push) Successful in 14s
2024-02-16 16:51:39 +02:00
e6f5315dac Migrate CI to use Fedora (#4)
All checks were successful
CI & Unit Tests / Unit-Tests (push) Successful in 48s
CI & Unit Tests / Docs (push) Successful in 11s
Co-authored-by: Erki <erki@skullnet.me>
Reviewed-on: #4
2024-02-07 07:27:50 +00:00
f64a92aa5c Documentation engine support (#3)
All checks were successful
CI & Unit Tests / Unit-Tests (push) Successful in 1m9s
CI & Unit Tests / Docs (push) Successful in 1m27s
Now I wonder if we'll ever use it.

Co-authored-by: erki <skull132@skullnet.me>
Reviewed-on: #3
2023-12-30 17:34:59 +00:00
0698081d7b Migrate over to gitea actions
All checks were successful
CI & Unit Tests / Unit-Tests (push) Successful in 1m13s
Co-authored-by: erki <skull132@skullnet.me>
Reviewed-on: #2
2023-05-15 20:55:21 +00:00
Erki
927b950dec Utility: update filter unit tests with decoupling test.
Some checks reported errors
continuous-integration/drone/push Build encountered an error
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-12-12 00:12:07 +02:00
Erki
47d7e87023 Utility: add filters library
Some checks reported errors
continuous-integration/drone/push Build encountered an error
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-12-11 23:58:39 +02:00
erki
a488ba66f3 Threads: fix remaining zeroInitialized reference
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-12-10 17:27:12 +02:00
erki
ea99a8a6ba Other: fix ./clang-format to apply new-lines for else statements properly
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-12-10 17:15:55 +02:00
erki
500c2704bb Peripherals, Utility: refactor out peripherals_utility.hpp into utility_bytes.hpp 2022-12-10 17:14:46 +02:00
erki
8fbb0efd5d Peripherals: fix double registration of short and long press in Button class
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-12-10 16:21:52 +02:00
Erki
a0639ec3f1 Peripherals: Better button logic
Some checks failed
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head There was a failure building this commit
Long press is now registered while the button is still held down
2022-11-04 00:17:25 +02:00
Erki
d3b85b7f6c Threads: Add missing includes
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-10-30 21:26:38 +02:00
Erki
d10675e3ec Temporary workaround for FW1.27
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-10-30 20:36:51 +02:00
Erki
6e22b02e92 Temporary IMU fixes for ICM IMUs
# Conflicts:
#	Peripherals/Inc/peripherals_imu_icm.hpp
2022-10-30 20:36:39 +02:00
Erki
458fd9e7f2 Utility: add missing include to enum helpers
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-07-15 14:17:02 +03:00
Erki
3aca35788b Utility: enum helpers library
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-07-15 14:15:04 +03:00
Erki
718b6705fd Utility: replace standard library asserts with SKULLC ones. 2022-07-15 13:54:31 +03:00
Erki
0ba9416a57 Add get() functionality to static_pointer
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-06-30 19:46:56 +03:00
Erki
eef2e1318c Make assert library print out assert expressions 2022-06-30 19:44:50 +03:00
Erki
c0a622c5e4 Utility: add mini-assert library
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-06-29 01:00:45 +03:00
Erki
e554d30bf6 Utility: add the ability to generate static functions from IFunction
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-06-26 17:53:40 +03:00
Erki
9924559fe0 Fix CXX_STANDARD on interface libraries
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-06-16 23:24:23 +03:00
Erki
54fd0e2332 Fix jenkinsfile by removing unnecessary conan reference
Some checks failed
continuous-integration/drone/push Build is failing
gitea/skullc-peripherals/pipeline/head There was a failure building this commit
2022-06-16 23:21:48 +03:00
Erki
bb220c9e92 Refactor cmake support to be less leaky
Some checks failed
continuous-integration/drone/push Build is failing
gitea/skullc-peripherals/pipeline/head There was a failure building this commit
2022-06-16 23:19:51 +03:00
Erki
69e1538cbb Merge branch 'support/jenkins'
Some checks failed
continuous-integration/drone/push Build is failing
gitea/skullc-peripherals/pipeline/head There was a failure building this commit
2022-06-16 23:03:00 +03:00
Erki
227af3c9fc now with XML
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-01-23 19:12:47 +02:00
Erki
b8c3e32fd6 yup
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
2022-01-23 18:48:39 +02:00
Erki
d909651bf1 testies
Some checks failed
gitea/skullc-peripherals/pipeline/head There was a failure building this commit
continuous-integration/drone/push Build is passing
2022-01-23 18:46:26 +02:00
Erki
dc9e159377 le woops
Some checks failed
gitea/skullc-peripherals/pipeline/head There was a failure building this commit
continuous-integration/drone/push Build is passing
2022-01-23 18:34:37 +02:00
Erki
88aa3087fe Yup, away we goo
Some checks failed
gitea/skullc-peripherals/pipeline/head There was a failure building this commit
continuous-integration/drone/push Build is passing
2022-01-23 18:33:24 +02:00
Erki
0d1ea1c1c2 Peripherals: format ICM file
All checks were successful
continuous-integration/drone/push Build is passing
2021-11-14 01:31:12 +02:00
Erki
43bc8d8265 Threads: Actors are now composable over threads 2021-11-14 01:31:01 +02:00
51 changed files with 1598 additions and 292 deletions

View File

@ -17,7 +17,7 @@ AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom BreakBeforeBraces: Custom
BraceWrapping: BraceWrapping:
AfterCaseLabel: false AfterCaseLabel: true
AfterClass: true AfterClass: true
AfterControlStatement: Always AfterControlStatement: Always
AfterEnum: true AfterEnum: true
@ -25,8 +25,8 @@ BraceWrapping:
AfterNamespace: true AfterNamespace: true
AfterUnion: true AfterUnion: true
AfterStruct: true AfterStruct: true
BeforeCatch: false BeforeCatch: true
BeforeElse: false BeforeElse: true
IndentBraces: false IndentBraces: false
SplitEmptyFunction: false SplitEmptyFunction: false
SplitEmptyRecord: true SplitEmptyRecord: true

View File

@ -1,29 +0,0 @@
kind: pipeline
type: docker
name: default
steps:
- name: build and test
pull: never
image: erki/cpp-ubuntu:latest
commands:
- mkdir -p build
- cd build
- conan install .. --build=missing
- cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=ON
- ninja
- ctest . --output-on-failure
- name: notify
image: plugins/matrix
settings:
homeserver:
from_secret: matrix_homeserver
roomid:
from_secret: matrix_roomid
username:
from_secret: matrix_username
password:
from_secret: matrix_password
when:
status:
- failure

41
.gitea/workflows/ci.yml Normal file
View File

@ -0,0 +1,41 @@
name: CI & Unit Tests
run-name: CI & Unit Tests
on: [push, pull_request]
jobs:
Unit-Tests:
runs-on: fedora-cpp
steps:
- uses: actions/checkout@v3
- name: Configure build
working-directory: ${{runner.workspace}}
run: |
cmake . -B${{runner.workspace}}/build \
-G"Ninja" \
-DCMAKE_BUILD_TYPE=Release \
-DSKULLC_WITH_TESTS=ON
- name: Build tests + lib
working-directory: ${{runner.workspace}}/build
run: ninja
- name: Run tests
working-directory: ${{runner.workspace}}/build
run: ctest . --output-on-failure
Docs:
runs-on: fedora-cpp
steps:
- uses: actions/checkout@v3
- name: Configure build
working-directory: ${{runner.workspace}}
run: |
cmake . -B${{runner.workspace}}/build \
-G"Ninja" \
-DCMAKE_BUILD_TYPE=Release \
-DSKULLC_WITH_DOCS=ON
- name: Build docs
working-directory: ${{runner.workspace}}/build
run: ninja Sphinx

View File

@ -1,22 +1,22 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR) cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
set(version 0.1.0) set(SKULLC_VERSION 0.1.0)
project(skullc project(skullc
VERSION ${version} VERSION ${SKULLC_VERSION}
LANGUAGES LANGUAGES
C C
CXX CXX
) )
list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) #list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/)
set(CMAKE_CXX_STANDARD 17) option(SKULLC_WITH_TESTS "Enable unit testing." OFF)
list(APPEND CMAKE_CXX_FLAGS "-Wall -Wextra") option(SKULLC_WITH_HAL "Enable the compiling and deployment of the HAL dependent sections." OFF)
option(SKULLC_USE_HAL_ST "Enable the ST HAl when SKULLC_WITH_HAL is enabled." OFF)
option(WITH_TESTS "Enable unit testing." OFF) option(SKULLC_USE_HAL_ESP "Enable the ESP HAL when SKULLC_WITH_HAL is enabled." OFF)
option(WITH_HAL "Enable the compiling and deployment of the HAL dependent sections." OFF) option(SKULLC_WITH_DOCS "Enable documentation building." OFF)
include(skullc-install) include(skullc-install)
@ -24,11 +24,15 @@ add_subdirectory(Peripherals)
add_subdirectory(Utility) add_subdirectory(Utility)
add_subdirectory(Messaging) add_subdirectory(Messaging)
if(WITH_TESTS) if(SKULLC_WITH_TESTS)
enable_testing() enable_testing()
add_subdirectory(Tests) add_subdirectory(Tests)
endif() endif()
if(SKULLC_WITH_DOCS)
add_subdirectory(Docs)
endif()
## Install ## Install
configure_file(skullc-config.cmake configure_file(skullc-config.cmake

53
Docs/CMakeLists.txt Normal file
View File

@ -0,0 +1,53 @@
find_package(Doxygen REQUIRED)
find_package(Sphinx REQUIRED)
file(GLOB_RECURSE SKULLC_PUBLIC_HEADERS
${PROJECT_SOURCE_DIR}/Messaging/Inc/*
${PROJECT_SOURCE_DIR}/Threads/Inc/*
${PROJECT_SOURCE_DIR}/Utility/Inc/*
${PROJECT_SOURCE_DIR}/Peripherals/Inc/*
)
set(DOXYGEN_INPUT_DIRS "${PROJECT_SOURCE_DIR}/Messaging/Inc ${PROJECT_SOURCE_DIR}/Threads/Inc ${PROJECT_SOURCE_DIR}/Utility/Inc ${PROJECT_SOURCE_DIR}/Peripherals/Inc")
set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/docs/doxygen)
set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/xml/index.xml)
set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)
file(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR}) #Doxygen won't create this for us
add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE}
DEPENDS ${SKULLC_PUBLIC_HEADERS}
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT}
MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN}
COMMENT "Generating docs"
)
add_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE})
set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR})
set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx)
set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html)
file(GLOB SPHINX_RST_FILES ${SPHINX_SOURCE}/*.rst)
# Only regenerate Sphinx when:
# - Doxygen has rerun
# - Our doc files have been updated
# - The Sphinx config has been updated
add_custom_command(OUTPUT ${SPHINX_INDEX_FILE}
COMMAND
${SPHINX_EXECUTABLE} -b html
# Tell Breathe where to find the Doxygen output
-Dbreathe_projects.SkullCPeripheralsLibrary=${DOXYGEN_OUTPUT_DIR}/xml
${SPHINX_SOURCE} ${SPHINX_BUILD}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
# Other docs files you want to track should go here (or in some variable)
${SPHINX_RST_FILES}
${DOXYGEN_INDEX_FILE}
MAIN_DEPENDENCY ${SPHINX_SOURCE}/conf.py
COMMENT "Generating documentation with Sphinx")
# Nice named target so we can run the job easily
add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE})

11
Docs/Doxyfile.in Normal file
View File

@ -0,0 +1,11 @@
// Every other setting ends up being default.
GENERATE_XML = YES
GENERATE_HTML = YES
JAVADOC_AUTOBRIEF = YES
INPUT = @DOXYGEN_INPUT_DIRS@
RECURSIVE = YES
OUTPUT_DIRECTORY = "@DOXYGEN_OUTPUT_DIR@"

31
Docs/conf.py Normal file
View File

@ -0,0 +1,31 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'SkullC Peripherals Library'
copyright = '2023, Rusted Skull'
author = 'Rusted Skull'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [ "breathe" ]
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'classic'
html_static_path = ['_static']
# -- Breathe Configuration"
breathe_default_project = "SkullCPeripheralsLibrary"

23
Docs/index.rst Normal file
View File

@ -0,0 +1,23 @@
.. SkullC Peripherals Library documentation master file, created by
sphinx-quickstart on Thu Dec 28 23:16:21 2023.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to SkullC Peripherals Library's documentation!
======================================================
.. toctree::
:maxdepth: 2
:caption: Contents:
utility
peripherals
threads
messaging
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

17
Docs/messaging.rst Normal file
View File

@ -0,0 +1,17 @@
Messaging Library
=================
A small Packet and Parser implementation for framing random message structures on the wire.
Use the :cpp:struct:`Packet` to copy in various data. Then send the serialized version of the packet
onto the wire.
:cpp:class:`Messaging::Parser` will help you put bytes coming in from the wire into coherent packets, and will serve you
these packets as you're ready for them.
.. doxygennamespace:: Messaging
:project: SkullCPeripheralsLibrary
:content-only:
:members:
:undoc-members:

9
Docs/peripherals.rst Normal file
View File

@ -0,0 +1,9 @@
Peripherals Library
=================
.. doxygennamespace:: Peripherals
:project: SkullCPeripheralsLibrary
:content-only:
:members:
:undoc-members:

9
Docs/threads.rst Normal file
View File

@ -0,0 +1,9 @@
Threads Library
=================
.. doxygennamespace:: Threads
:project: SkullCPeripheralsLibrary
:content-only:
:members:
:undoc-members:

9
Docs/utility.rst Normal file
View File

@ -0,0 +1,9 @@
Utility Library
=================
.. doxygennamespace:: Utility
:project: SkullCPeripheralsLibrary
:content-only:
:members:
:undoc-members:

View File

@ -10,4 +10,4 @@ target_include_directories(messaging
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
) )
skullc_install_packages(skullc messaging ${version}) skullc_install_packages(skullc messaging ${SKULLC_VERSION})

View File

@ -11,4 +11,4 @@ target_include_directories(peripherals
) )
## INSTALL ## INSTALL
skullc_install_packages(skullc peripherals ${version}) skullc_install_packages(skullc peripherals ${SKULLC_VERSION})

View File

@ -42,19 +42,24 @@ public:
const bool is_pressed = sw.read(); const bool is_pressed = sw.read();
ButtonPress new_state = ButtonPress::NOT_PRESSED; ButtonPress new_state = ButtonPress::NOT_PRESSED;
const std::uint32_t time_held = hal::getMillis() - time_pressed_down_;
if (is_pressed && !was_pressed_) if (is_pressed && !was_pressed_)
{ {
time_pressed_down_ = hal::getMillis(); time_pressed_down_ = hal::getMillis();
} else if (!is_pressed && was_pressed_) }
else if (!is_pressed && was_pressed_)
{ {
const std::uint32_t time_held = hal::getMillis() - time_pressed_down_; if (time_held > TIMEOUT_SHORT_PRESS && time_held <= TIMEOUT_LONG_PRESS)
if (time_held > TIMEOUT_LONG_PRESS)
new_state = ButtonPress::LONG_PRESS;
else if (time_held > TIMEOUT_SHORT_PRESS)
new_state = ButtonPress::SHORT_PRESS; new_state = ButtonPress::SHORT_PRESS;
time_pressed_down_ = 0; time_pressed_down_ = 0;
} }
else if (is_pressed && was_pressed_)
{
if (current_state_ == ButtonPress::NOT_PRESSED && time_held > TIMEOUT_LONG_PRESS)
new_state = ButtonPress::LONG_PRESS;
}
was_pressed_ = is_pressed; was_pressed_ = is_pressed;
current_state_ = new_state; current_state_ = new_state;

View File

@ -0,0 +1,37 @@
//
// Created by erki on 11/02/24.
//
#pragma once
#include <array>
#include <concepts>
#include <cstdint>
namespace Peripherals::Hal
{
template<typename T>
concept Gpio = requires (T t, const T ct) {
t.set(true);
t.toggle();
{ ct.read() } -> std::same_as<bool>;
};
template<typename T>
concept SerialInterface = requires (T t, std::uint8_t* data, const std::uint32_t len) {
{ t.transmit(data, len) } -> std::same_as<bool>;
{ t.transmit(std::array<std::uint8_t, 10>{}) } -> std::same_as<bool>;
{ t.receive(data, len) } -> std::same_as<bool>;
{ t.receive(std::array<std::uint8_t, 10>{}) } -> std::same_as<bool>;
};
template<typename T, typename RegName>
concept RegisterInterface = requires (T t, RegName reg, std::uint8_t* data, const std::uint32_t len) {
t.writerRegister(reg, std::uint8_t{});
t.writeRegisterMultibyte(reg, data, len);
{ t.readRegister(reg, std::uint32_t{}) } -> std::same_as<std::uint8_t>;
t.readRegisterMultibyte(reg, data, len, std::uint32_t{});
};
}

View File

@ -0,0 +1,51 @@
//
// Created by erki on 11/02/24.
//
#pragma once
#ifdef SKULLC_USE_HAL_ESP
#include <peripherals_hal_concepts.hpp>
#include <driver/gpio.h>
namespace Peripherals::Hal::Esp
{
struct Gpio
{
gpio_num_t gpio;
const bool inverted = false;
Gpio() = delete;
explicit Gpio(gpio_num_t gpio, const bool inverted)
: gpio(gpio), inverted(inverted)
{ }
void set(const bool& state)
{
gpio_set_level(gpio, inverted ? !state : state);
}
void toggle() { gpio_set_level(gpio, !gpio_get_level(gpio)); }
bool read() const
{
return inverted ? !bool(gpio_get_level(gpio)) : bool(gpio_get_level(gpio));
}
};
static_assert(Peripherals::Hal::Gpio<Gpio>);
#define CREATE_GPIO(name) \
Peripherals::Hal::Esp::Gpio{name, false}
#define CREATE_INV_GPIO(name) \
Peripherals::Hal::Esp::Gpio{name, true}
}
#else
# warning "ESP HAL included without SKULLC_USE_HAL_ESP being defined."
#endif /* SKULLC_USE_HAL_ESP */

View File

@ -8,9 +8,9 @@
#ifndef SKULLC_PERIPHERALS_HAL_ST_HPP_ #ifndef SKULLC_PERIPHERALS_HAL_ST_HPP_
#define SKULLC_PERIPHERALS_HAL_ST_HPP_ #define SKULLC_PERIPHERALS_HAL_ST_HPP_
#include <main.h> #ifdef SKULLC_USE_HAL_ST
#include "peripherals_utility.hpp" #include <main.h>
#include <array> #include <array>
@ -96,9 +96,15 @@ struct Gpio
}; };
#define CREATE_GPIO(name) \ #define CREATE_GPIO(name) \
Peripherals::Hal::St::Gpio { name##_GPIO_Port, name##_Pin, false } Peripherals::Hal::St::Gpio \
{ \
name##_GPIO_Port, name##_Pin, false \
}
#define CREATE_INV_GPIO(name) \ #define CREATE_INV_GPIO(name) \
Peripherals::Hal::St::Gpio { name##_GPIO_Port, name##_Pin, true } Peripherals::Hal::St::Gpio \
{ \
name##_GPIO_Port, name##_Pin, true \
}
#endif// HAL_GPIO_MODULE_ENABLED #endif// HAL_GPIO_MODULE_ENABLED
@ -262,10 +268,30 @@ struct SpiRegisters
#ifdef HAL_UART_MODULE_ENABLED #ifdef HAL_UART_MODULE_ENABLED
namespace _Details
{
/**
* @hack: Temporary workarounds to make the HAL compile for FW1.27. ST made it so that only UART libraries
* are const-correct. The others remain unconst. So these wrappers will exist until we're no longer partially
* const-correct.
*/
inline HAL_StatusTypeDef uartTransmit(UART_HandleTypeDef* huart, std::uint8_t* data, const std::uint16_t size, const std::uint32_t timeout)
{
return HAL_UART_Transmit(huart, data, size, timeout);
}
inline HAL_StatusTypeDef uartTransmitDma(UART_HandleTypeDef* huart, std::uint8_t* data, const std::uint16_t size)
{
return HAL_UART_Transmit_DMA(huart, data, size);
}
}// namespace _Details
using UartInterface = using UartInterface =
SerialInterface<UART_HandleTypeDef, HAL_UART_Transmit, HAL_UART_Receive>; SerialInterface<UART_HandleTypeDef, _Details::uartTransmit, HAL_UART_Receive>;
using UartInterfaceDMA = using UartInterfaceDMA =
SerialInterfaceAsync<UART_HandleTypeDef, HAL_UART_Transmit_DMA, SerialInterfaceAsync<UART_HandleTypeDef, _Details::uartTransmitDma,
HAL_UART_Receive_DMA>; HAL_UART_Receive_DMA>;
#endif// HAL_UART_MODULE_ENABLED #endif// HAL_UART_MODULE_ENABLED
@ -319,4 +345,8 @@ struct ItmSerialInterface
}// namespace Hal }// namespace Hal
}// namespace Peripherals }// namespace Peripherals
#else
# warning "ESP HAL included without SKULLC_USE_HAL_ESP being defined."
#endif /* SKULLC_USE_HAL_ST */
#endif /* SKULLC_PERIPHERALS_HAL_ST_HPP_ */ #endif /* SKULLC_PERIPHERALS_HAL_ST_HPP_ */

View File

@ -12,7 +12,6 @@
#include <limits> #include <limits>
#include "peripherals_imu.hpp" #include "peripherals_imu.hpp"
#include "peripherals_utility.hpp"
namespace Peripherals namespace Peripherals
{ {

View File

@ -54,7 +54,8 @@ public:
{ {
left_.forward.setCompare(left); left_.forward.setCompare(left);
left_.backward.setCompare(0); left_.backward.setCompare(0);
} else }
else
{ {
left_.forward.setCompare(0); left_.forward.setCompare(0);
left_.backward.setCompare(-1 * left); left_.backward.setCompare(-1 * left);
@ -64,7 +65,8 @@ public:
{ {
right_.forward.setCompare(right); right_.forward.setCompare(right);
right_.backward.setCompare(0); right_.backward.setCompare(0);
} else }
else
{ {
right_.forward.setCompare(0); right_.forward.setCompare(0);
right_.backward.setCompare(-1 * right); right_.backward.setCompare(-1 * right);

View File

@ -1,72 +0,0 @@
/*
* peripherals_utility.hpp
*
* Created on: Feb 24, 2021
* Author: erki
*/
#ifndef SKULLC_PERIPHERALS_UTILITY_HPP_
#define SKULLC_PERIPHERALS_UTILITY_HPP_
#include <cstdint>
#include <cstring>
#include <type_traits>
namespace Peripherals
{
#define SKULLC_CONCAT_IMPL(x, y) x##y
#define SKULLC_CONCAT(x, y) SKULLC_CONCAT_IMPL(x, y)
#define SKULLC_TAG struct SKULLC_CONCAT(SkullCTag_, __COUNTER__)
template<typename T>
constexpr const T& clamp(const T& v, const T& lo, const T& hi)
{
if (v > hi)
return hi;
else if (v < lo)
return lo;
else
return v;
}
template<typename T, std::size_t N>
T byteToTypeBE(const std::uint8_t a[N])
{
T t(0);
for (std::size_t i = 0; i < N; i++)
{
t |= a[i] << (i * 8);
}
return t;
}
template<typename T, std::size_t N>
T byteToTypeLE(const std::uint8_t a[N])
{
T t(0);
for (std::size_t i = N; i >= 0; i--)
{
t |= a[i] << ((i - 1) * 8);
}
return t;
}
template<typename T>
constexpr T zeroInitialized()
{
static_assert(std::is_trivially_default_constructible<T>::value, "Struct is not trivially default constructible.");
T t;
std::memset(&t, 0, sizeof(T));
return t;
}
}// namespace Peripherals
#endif /* SKULLC_PERIPHERALS_UTILITY_HPP_ */

View File

@ -1,10 +1,16 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR) cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
option(TESTS_WITH_SANITIZERS "Enable sanitizers for tests." ON) option(SKULLC_TESTS_WITH_SANITIZERS "Enable sanitizers for tests." ON)
find_package(Catch2 REQUIRED) include(FetchContent)
if(TESTS_WITH_SANITIZERS) FetchContent_Declare(Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v2.13.8
)
FetchContent_MakeAvailable(Catch2)
if(SKULLC_TESTS_WITH_SANITIZERS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined -fno-sanitize-recover") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined -fno-sanitize-recover")
set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined -fno-sanitize-recover") set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined -fno-sanitize-recover")
endif() endif()
@ -18,7 +24,14 @@ add_executable(tests
rand.cpp rand.cpp
fixedpoint.cpp fixedpoint.cpp
pixelbuffer.cpp pixelbuffer.cpp
pixelbuffer_effects.cpp function.cpp) pixelbuffer_effects.cpp
function.cpp
assert_ndebug.cpp
assert.cpp
enum_helpers.cpp
bytes.cpp
filters.cpp
)
target_link_libraries(tests target_link_libraries(tests
PUBLIC PUBLIC
@ -28,6 +41,12 @@ target_link_libraries(tests
Catch2::Catch2 Catch2::Catch2
) )
set_target_properties(tests
PROPERTIES
CXX_STANDARD 17
)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib)
include(CTest) include(CTest)
include(Catch) include(Catch)
catch_discover_tests(tests) catch_discover_tests(tests)

68
Tests/assert.cpp Normal file
View File

@ -0,0 +1,68 @@
//
// Created by erki on 29.06.22.
//
#include <catch2/catch.hpp>
#ifdef NDEBUG
#undef NDEBUG
#define NDEBUG_WAS_SET
#endif
#include "utility_assert.hpp"
#ifdef NDEBUG_WAS_SET
#define NDEBUG
#endif
namespace
{
bool assert_flag = false;
void assertCallback(const char*, const char*, const int)
{
assert_flag = true;
}
}// namespace
TEST_CASE("Assert debug without NDEBUG gets triggered if check is false.", "[utility],[assert]")
{
assert_flag = false;
Utility::Assert::setHandler(assertCallback);
SKULLC_ASSERT_DEBUG(false);
CHECK(assert_flag == true);
}
TEST_CASE("Assert debug without NDEBUG doesn't get triggered if check is true.", "[utility],[assert]")
{
assert_flag = false;
Utility::Assert::setHandler(assertCallback);
SKULLC_ASSERT_DEBUG(true);
CHECK(assert_flag == false);
}
TEST_CASE("Assert safe without NDEBUG gets triggered if check is false.", "[utility],[assert]")
{
assert_flag = false;
Utility::Assert::setHandler(assertCallback);
SKULLC_ASSERT_SAFE(false);
CHECK(assert_flag == true);
}
TEST_CASE("Assert safe without NDEBUG doesn't get triggered if check is true.", "[utility],[assert]")
{
assert_flag = false;
Utility::Assert::setHandler(assertCallback);
SKULLC_ASSERT_SAFE(true);
CHECK(assert_flag == false);
}

62
Tests/assert_ndebug.cpp Normal file
View File

@ -0,0 +1,62 @@
//
// Created by erki on 29.06.22.
//
#include <catch2/catch.hpp>
#ifndef NDEBUG
#define NDEBUG
#define NDEBUG_WAS_NOT_DEFINED
#endif
#include "utility_assert.hpp"
#ifdef NDEBUG_WAS_NOT_DEFINED
#undef NDEBUG
#endif
namespace
{
bool assert_flag = false;
void assertCallback(const char*, const char*, const int)
{
assert_flag = true;
}
}// namespace
TEST_CASE("Assert debug with NDEBUG is a null-opt.", "[utility],[assert]")
{
assert_flag = false;
Utility::Assert::setHandler(assertCallback);
int check_number = 0;
SKULLC_ASSERT_DEBUG(check_number++ == 0);
CHECK(check_number == 0);
CHECK(assert_flag == false);
}
TEST_CASE("Assert safe with NDEBUG gets triggered if check is false.", "[utility],[assert]")
{
assert_flag = false;
Utility::Assert::setHandler(assertCallback);
int check_number = 0;
SKULLC_ASSERT_SAFE(check_number++ == 1);
CHECK(check_number == 1);
CHECK(assert_flag == true);
}
TEST_CASE("Assert safe with NDEBUG doesn't get triggered if check is true.", "[utility],[assert]")
{
assert_flag = false;
Utility::Assert::setHandler(assertCallback);
SKULLC_ASSERT_SAFE(true);
CHECK(assert_flag == false);
}

View File

@ -109,7 +109,6 @@ TEST_CASE("Button reads presses properly.", "[peripherals],[button]")
SECTION("Long press is identified properly.") SECTION("Long press is identified properly.")
{ {
HAL::millis = Button::TIMEOUT_LONG_PRESS + 2; HAL::millis = Button::TIMEOUT_LONG_PRESS + 2;
Gpio::set = false;
button.update(); button.update();
@ -121,6 +120,16 @@ TEST_CASE("Button reads presses properly.", "[peripherals],[button]")
button.update(); button.update();
REQUIRE(button.getState() == Peripherals::ButtonPress::NOT_PRESSED); REQUIRE(button.getState() == Peripherals::ButtonPress::NOT_PRESSED);
SECTION("Releasing keeps NOT_PRESSED state.")
{
HAL::millis += 1;
Gpio::set = false;
button.update();
REQUIRE(button.getState() == Peripherals::ButtonPress::NOT_PRESSED);
}
} }
} }
} }

68
Tests/bytes.cpp Normal file
View File

@ -0,0 +1,68 @@
//
// Created by erki on 10/12/22.
//
#include <catch2/catch.hpp>
#include "utility_bytes.hpp"
TEST_CASE("zeroInitialized creates an appropriate struct.")
{
struct TestStruct
{
int a;
char b;
int c;
};
SECTION("Return by value variant returns a struct where all members are 0.")
{
const TestStruct test = Utility::zeroInitialized<TestStruct>();
REQUIRE(test.a == 0);
REQUIRE(test.b == 0);
REQUIRE(test.c == 0);
}
SECTION("Pass by reference variant initializes the struct to all 0's.")
{
TestStruct test;
Utility::zeroInitialized(test);
REQUIRE(test.a == 0);
REQUIRE(test.b == 0);
REQUIRE(test.c == 0);
}
SECTION("Pass by pointer variant initializes the struct to all 0's.")
{
TestStruct test;
Utility::zeroInitialized(&test);
REQUIRE(test.a == 0);
REQUIRE(test.b == 0);
REQUIRE(test.c == 0);
}
}
TEST_CASE("arrayToType constructs a type appropriately.")
{
const std::uint32_t initial_value = 0x12345678;
SECTION("Using C-arrays the operation works appropriately.")
{
std::uint8_t raw_data[4] = {0};
std::memcpy(raw_data, &initial_value, 4);
const std::uint32_t output = Utility::arrayToType<std::uint32_t, 4>(raw_data);
REQUIRE(output == initial_value);
}
SECTION("Using C-arrays the operation works appropriately.")
{
std::array<std::uint8_t, 4> raw_data = {0, 0, 0, 0};
std::memcpy(raw_data.data(), &initial_value, 4);
const std::uint32_t output = Utility::arrayToType<std::uint32_t>(raw_data);
REQUIRE(output == initial_value);
}
}

41
Tests/enum_helpers.cpp Normal file
View File

@ -0,0 +1,41 @@
//
// Created by erki on 15.07.22.
//
#include <catch2/catch.hpp>
#include "utility_enum_helpers.hpp"
namespace
{
enum class TestEnum : unsigned
{
FLAG_0 = 1,
FLAG_1 = 2,
FLAG_2 = 4
};
SKULLC_ENUM_DECLARE_BITFLAG_OPERATORS(TestEnum)
}// namespace
TEST_CASE("OR operator works as expected.", "[utility],[enum_helpers]")
{
const TestEnum a = TestEnum::FLAG_0;
const TestEnum b = TestEnum::FLAG_1;
const TestEnum sum = a | b;
CHECK(unsigned(sum) == 3u);
}
TEST_CASE("AND operator works as expected.", "[utility],[enum_helpers]")
{
const TestEnum a = TestEnum::FLAG_0 | TestEnum::FLAG_1;
const TestEnum masked_1 = a & TestEnum::FLAG_0;
CHECK(masked_1 == TestEnum::FLAG_0);
const TestEnum masked_2 = a & TestEnum::FLAG_2;
CHECK(masked_2 != TestEnum::FLAG_2);
}

191
Tests/filters.cpp Normal file
View File

@ -0,0 +1,191 @@
//
// Created by erki on 12/11/22.
//
#include <catch2/catch.hpp>
#include "utility_filters.hpp"
TEST_CASE("LowPassFilter works as expected.")
{
const int threshold = 50;
Utility::LowPassFilter<int> filter{threshold};
SECTION("Initial value is 0.")
{
REQUIRE(filter.currentValue() == 0);
}
SECTION("Passing a number higher than threshold gets filtered.")
{
filter.update(60);
REQUIRE(filter.currentValue() == 0);
SECTION("Filter updates state as expected.")
{
filter.update(40);
REQUIRE(filter.currentValue() == 40);
}
}
SECTION("Passing a number lower than threshold passes filter.")
{
filter.update(40);
REQUIRE(filter.currentValue() == 40);
SECTION("Filter retains state as expected.")
{
filter.update(60);
REQUIRE(filter.currentValue() == 40);
}
}
}
TEST_CASE("HighPassFilter works as expected.")
{
const int threshold = 50;
Utility::HighPassFilter<int> filter{threshold};
SECTION("Initial value is 0.")
{
REQUIRE(filter.currentValue() == 0);
}
SECTION("Passing a number higher than threshold passes filter.")
{
filter.update(60);
REQUIRE(filter.currentValue() == 60);
SECTION("Filter retains state as expected.")
{
filter.update(40);
REQUIRE(filter.currentValue() == 60);
}
}
SECTION("Passing a number lower than threshold gets filtered.")
{
filter.update(40);
REQUIRE(filter.currentValue() == 0);
SECTION("Filter updates state as expected.")
{
filter.update(60);
REQUIRE(filter.currentValue() == 60);
}
}
}
TEST_CASE("Two filters can be linked together.")
{
const int threshold_low = 40;
const int threshold_high = 50;
Utility::LowPassFilter<int> low_pass{threshold_high};
Utility::HighPassFilter<int> high_pass{threshold_low};
high_pass.assignPrecedingFilter(low_pass);
SECTION("Initial value is 0.")
{
REQUIRE(high_pass.currentValue() == 0);
}
SECTION("Values outside of band are filtered out.")
{
high_pass.update(30);
REQUIRE(high_pass.currentValue() == 0);
high_pass.update(60);
REQUIRE(high_pass.currentValue() == 0);
SECTION("Filter updates with in-band value.")
{
high_pass.update(45);
REQUIRE(high_pass.currentValue() == 45);
}
}
SECTION("Values inside of band are passed through.")
{
high_pass.update(44);
REQUIRE(high_pass.currentValue() == 44);
high_pass.update(46);
REQUIRE(high_pass.currentValue() == 46);
SECTION("Out of band values are filtered out.")
{
high_pass.update(30);
REQUIRE(high_pass.currentValue() == 46);
high_pass.update(60);
REQUIRE(high_pass.currentValue() == 46);
}
}
SECTION("Decoupling filters works as expected.")
{
high_pass.update(60);
REQUIRE(high_pass.currentValue() == 0);
high_pass.clearPrecedingFilter();
high_pass.update(60);
REQUIRE(high_pass.currentValue() == 60);
high_pass.update(30);
REQUIRE(high_pass.currentValue() == 60);
}
}
TEST_CASE("Median filter works as expected.")
{
Utility::MedianFilter<int, 5> filter;
SECTION("Initial value is 0.")
{
REQUIRE(filter.currentValue() == 0);
}
SECTION("The median is filtered appropriately.")
{
std::array<int, 5> data = {5, 1, 4, 3, 2};
std::array<int, 5> expected_median = {0, 0, 1, 3, 3};
for (int i = 0; i < 5; i++)
{
const int x = data[i];
filter.update(x);
REQUIRE(filter.currentValue() == expected_median[i]);
}
}
}
TEST_CASE("Rolling average filter works as expected.")
{
Utility::RollingAverageFilter<int> filter{5};
SECTION("Initial value is 0.")
{
REQUIRE(filter.currentValue() == 0);
}
SECTION("Value below step size is filtered out for int filter.")
{
filter.update(4);
REQUIRE(filter.currentValue() == 0);
}
SECTION("Value is updated appropriately.")
{
filter.update(20);
REQUIRE(filter.currentValue() == 20 / 5);
SECTION("Saturation is reached appropriately")
{
for (int i = 0; i < 5 * 2 - 1; i++)
filter.update(20);
REQUIRE(filter.currentValue() == 20);
}
}
}

View File

@ -5,6 +5,7 @@
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
#include <utility_function.hpp> #include <utility_function.hpp>
#include <utility_tag.hpp>
TEST_CASE("Function calls function appropriately.", "[utility],[function]") TEST_CASE("Function calls function appropriately.", "[utility],[function]")
{ {
@ -38,6 +39,26 @@ TEST_CASE("Function passes arguments appropriately.", "[utility],[function]")
REQUIRE(bool_to_set == true); REQUIRE(bool_to_set == true);
} }
TEST_CASE("Function works with toStaticFunction.", "[utility],[function]")
{
static int int_to_set = 0;
static bool bool_to_set = false;
auto func_to_call = [](const int i, const bool b) {
int_to_set = i;
bool_to_set = b;
};
Utility::Function<void(int, bool)> function(func_to_call);
using signature = void (*)(int, bool);
signature func_pointer = function.toStaticFunction<SKULLC_TAG>();
func_pointer(10, true);
REQUIRE(int_to_set == 10);
REQUIRE(bool_to_set == true);
}
TEST_CASE("FunctionOwned calls function appropriately.", "[utility],[function]") TEST_CASE("FunctionOwned calls function appropriately.", "[utility],[function]")
{ {
struct S struct S
@ -82,3 +103,29 @@ TEST_CASE("FunctionOwned passes arguments appropriately.", "[utility],[function]
REQUIRE(subject.int_to_set == 10); REQUIRE(subject.int_to_set == 10);
REQUIRE(subject.bool_to_set == false); REQUIRE(subject.bool_to_set == false);
} }
TEST_CASE("FunctionOwned works with toStaticFunction.", "[utility],[function]")
{
struct S
{
int int_to_set = 0;
bool bool_to_set = true;
void toCall(const int i, const bool b)
{
int_to_set = i;
bool_to_set = b;
}
};
S subject;
Utility::FunctionOwned<S, void(int, bool)> function(subject, &S::toCall);
using signature = void (*)(int, bool);
signature func_pointer = function.toStaticFunction<SKULLC_TAG>();
func_pointer(10, true);
REQUIRE(subject.int_to_set == 10);
REQUIRE(subject.bool_to_set == true);
}

View File

@ -15,15 +15,15 @@
#include <type_traits> #include <type_traits>
#include <variant> #include <variant>
#include "threads_primitivethread.hpp" #include <threads_actor_thread.hpp>
#include "threads_signal.hpp" #include <threads_iactor.hpp>
#include "threads_timer.hpp" #include <threads_signal.hpp>
namespace Threads namespace Threads
{ {
template<typename CRTP, typename... Ts> template<typename CRTP, typename... Ts>
class Actor class Actor : public IActor
{ {
public: public:
using value_type = std::variant<Ts...>; using value_type = std::variant<Ts...>;
@ -31,14 +31,11 @@ public:
static_assert(std::is_default_constructible_v<value_type>, "Ts must be default constructible."); static_assert(std::is_default_constructible_v<value_type>, "Ts must be default constructible.");
static_assert(std::is_trivially_copyable_v<value_type>, "Ts must be trivially copyable."); static_assert(std::is_trivially_copyable_v<value_type>, "Ts must be trivially copyable.");
Actor(const std::uint32_t queue_size, const char* name, const osPriority_t priority, const std::uint32_t stack_size) Actor() : IActor(sizeof(value_type)),
: thread_(&Actor<CRTP, Ts...>::threadHandler_, this, name, priority, stack_size), signal_(this)
msg_queue_(xQueueCreate(queue_size, sizeof(value_type))), signal_(this) {}
{
assert(msg_queue_);
}
virtual ~Actor() {} ~Actor() override {}
template<typename T> template<typename T>
struct Signal : Signallable<T> struct Signal : Signallable<T>
@ -59,17 +56,13 @@ public:
void emit(const T& data) override void emit(const T& data) override
{ {
parent::value_type to_send = data; parent::value_type to_send = data;
xQueueSend(q_->msg_queue_, &to_send, 0); q_->dispatchEvent(to_send);
} }
BaseType_t emitFromIsr(const T& data) override BaseType_t emitFromIsr(const T& data) override
{ {
parent::value_type to_send = data; parent::value_type to_send = data;
BaseType_t was_awoken = pdFALSE; return q_->dispatchEventFromIsr(to_send);
xQueueSendFromISR(q_->msg_queue_, &to_send, &was_awoken);
return was_awoken;
} }
private: private:
@ -97,13 +90,12 @@ public:
return &signal_; return &signal_;
} }
protected: void dispatchSignal(const char* data) final
virtual void init() = 0; {
dispatchImpl_(data);
}
private: private:
PrimitiveThread thread_;
QueueHandle_t msg_queue_;
struct Visitor_ struct Visitor_
{ {
using parent = Actor<CRTP, Ts...>; using parent = Actor<CRTP, Ts...>;
@ -121,113 +113,15 @@ private:
} }
}; };
void operator()() void dispatchImpl_(const char* data)
{ {
init(); const value_type* signal_data = reinterpret_cast<const value_type*>(data);
std::visit(Visitor_{this}, *signal_data);
value_type data{};
while (true)
{
const BaseType_t got = xQueueReceive(msg_queue_, &data, portMAX_DELAY);
if (got == pdTRUE)
{
std::visit(Visitor_{this}, data);
}
}
}
static void threadHandler_(void* d)
{
auto* dd = static_cast<Actor<CRTP, Ts...>*>(d);
(*dd)();
} }
Signal<value_type> signal_; Signal<value_type> signal_;
}; };
template<typename CRTP>
class Actor<CRTP, void>
{
public:
using value_type = void;
Actor(const std::uint32_t queue_size, const char* name, const osPriority_t priority, const std::uint32_t stack_size)
: thread_([](void* d) {
auto* dd = static_cast<Actor<CRTP, value_type>*>(d);
(*dd)();
},
this, name, priority, stack_size),
msg_queue_(xQueueCreate(queue_size, sizeof(int))), signal_(*this)
{
assert(msg_queue_);
}
virtual ~Actor() {}
Signallable<value_type>* getSignal()
{
return &signal_;
}
protected:
virtual void init() = 0;
private:
PrimitiveThread thread_;
QueueHandle_t msg_queue_;
void operator()()
{
init();
int data;
while (true)
{
const BaseType_t got = xQueueReceive(msg_queue_, &data, portMAX_DELAY);
if (got == pdTRUE)
{
auto* crtp = static_cast<CRTP*>(this);
crtp->onSignal();
}
}
}
friend struct Signal_;
struct Signal_ : Signallable<value_type>
{
using parent = Actor<CRTP, void>;
parent& q;
explicit Signal_(parent& q)
: q(q)
{}
void emit() override
{
const int data = 0;
xQueueSend(q.msg_queue_, &data, 0);
}
BaseType_t emitFromIsr() override
{
const int data = 0;
BaseType_t was_awoken = pdFALSE;
xQueueSendFromISR(q.msg_queue_, &data, &was_awoken);
return was_awoken;
}
};
Signal_ signal_;
};
}// namespace Threads }// namespace Threads

View File

@ -0,0 +1,50 @@
/*
* threads_actor_thread.hpp
*
* Created on: Nov 13, 2021
* Author: erki
*/
#ifndef SKULLC_THREADS_ACTOR_THREAD_HPP_
#define SKULLC_THREADS_ACTOR_THREAD_HPP_
#include <array>
#include <cstdint>
#include <freertos_os2.h>
#include <queue.h>
#include <threads_thread.hpp>
namespace Threads
{
class IActor;
class ActorThread : public Thread<ActorThread>
{
public:
ActorThread(const std::size_t queue_length, const char* name, const osPriority_t priority, const std::uint32_t stack_size);
void operator()();
private:
void init_();
void doMessageLoop_();
friend class IActor;
std::size_t acceptActor_(IActor* actor);
std::size_t queue_length_;
std::size_t queue_item_length_ = 0;
QueueHandle_t queue_ = nullptr;
char* rx_buffer_ = nullptr;
char* tx_buffer_ = nullptr;
std::array<IActor*, 32> actors_;
};
}// namespace Threads
#endif /* SKULLC_THREADS_ACTOR_THREAD_HPP_ */

View File

@ -65,7 +65,8 @@ public:
if (!notified) if (!notified)
{ {
return std::nullopt; return std::nullopt;
} else }
else
{ {
return current_value_; return current_value_;
} }
@ -78,7 +79,8 @@ public:
if (!waiting_thread_) if (!waiting_thread_)
{ {
return false; return false;
} else }
else
{ {
return waiting_thread_->notify(0, eNoAction); return waiting_thread_->notify(0, eNoAction);
} }
@ -91,7 +93,8 @@ public:
if (!waiting_thread_) if (!waiting_thread_)
{ {
return {false, pdFALSE}; return {false, pdFALSE};
} else }
else
{ {
auto const [discard, was_notified] = waiting_thread_->notifyFromIsr(0, eNoAction); auto const [discard, was_notified] = waiting_thread_->notifyFromIsr(0, eNoAction);
(void) discard; (void) discard;
@ -158,7 +161,8 @@ struct ExclusiveSignal<void> : public Signallable<void>
if (!waiting_thread_) if (!waiting_thread_)
{ {
return pdFALSE; return pdFALSE;
} else }
else
{ {
auto const [discard, was_notified] = waiting_thread_->notifyFromIsr(0, eNoAction); auto const [discard, was_notified] = waiting_thread_->notifyFromIsr(0, eNoAction);
(void) discard; (void) discard;

View File

@ -0,0 +1,68 @@
/*
* threads_iactor.hpp
*
* Created on: Nov 14, 2021
* Author: erki
*/
#ifndef SKULLC_THREADS_IACTOR_HPP_
#define SKULLC_THREADS_IACTOR_HPP_
#include <cstdint>
#include <freertos_os2.h>
namespace Threads
{
class ActorThread;
class IActor
{
public:
IActor(const std::size_t signal_size)
: signal_size_(signal_size)
{}
virtual ~IActor() {}
virtual void init() = 0;
virtual void dispatchSignal(const char* data) = 0;
void moveToThread(ActorThread* thread);
std::size_t actorIndex() const
{
return actor_index_;
}
std::size_t signalSize() const
{
return signal_size_;
}
protected:
template<typename T>
void dispatchEvent(const T& data)
{
dispatchEventImpl_(&data, sizeof(T));
}
template<typename T>
BaseType_t dispatchEventFromIsr(const T& data)
{
return dispatchEventFromIsrImpl_(&data, sizeof(T));
}
private:
ActorThread* parent_thread_ = nullptr;
std::size_t actor_index_ = 0;
std::size_t signal_size_;
void dispatchEventImpl_(const void* data, const std::size_t data_size);
BaseType_t dispatchEventFromIsrImpl_(const void* data, const std::size_t data_size);
};
}// namespace Threads
#endif /* SKULLC_THREADS_IACTOR_HPP_ */

View File

@ -11,9 +11,9 @@
#include <cmsis_os.h> #include <cmsis_os.h>
#include <freertos_os2.h> #include <freertos_os2.h>
#include <cstdint>
#include <tuple> #include <tuple>
#include <type_traits> #include <type_traits>
#include <cstdint>
namespace Threads namespace Threads
{ {

View File

@ -0,0 +1,110 @@
/*
* threads_actor_thread.cpp
*
* Created on: Nov 13, 2021
* Author: erki
*/
#include "threads_actor_thread.hpp"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <threads_iactor.hpp>
namespace
{
struct SignalMessage
{
std::size_t actor_index;
const char* data;
static SignalMessage fromData(char* data)
{
SignalMessage message;
std::memcpy(&message.actor_index, data, sizeof(message.actor_index));
message.data = data + sizeof(message.actor_index);
return message;
}
};
}// namespace
namespace Threads
{
ActorThread::ActorThread(const std::size_t queue_length, const char* name, const osPriority_t priority, const std::uint32_t stack_size)
: Thread<ActorThread>(name, priority, stack_size), queue_length_(queue_length), queue_(nullptr)
{
actors_.fill(nullptr);
}
void ActorThread::operator()()
{
init_();
osThreadYield();
while (true)
{
doMessageLoop_();
osThreadYield();
}
}
void ActorThread::init_()
{
for (IActor* actor : actors_)
{
if (actor != nullptr)
{
queue_item_length_ = std::max(queue_item_length_, actor->signalSize());
actor->init();
}
}
queue_ = xQueueCreate(queue_length_, queue_item_length_ + sizeof(std::size_t));
assert(queue_ != nullptr);
rx_buffer_ = new char[queue_item_length_ + sizeof(std::size_t)];
tx_buffer_ = new char[queue_item_length_ + sizeof(std::size_t)];
}
void ActorThread::doMessageLoop_()
{
const BaseType_t has_data = xQueueReceive(queue_, rx_buffer_, portMAX_DELAY);
if (has_data == pdTRUE)
{
const SignalMessage message = SignalMessage::fromData(rx_buffer_);
assert(message.actor_index <= actors_.size());
IActor* actor = actors_[message.actor_index];
assert(actor != nullptr);
actor->dispatchSignal(message.data);
}
}
std::size_t ActorThread::acceptActor_(IActor* actor)
{
for (auto it = actors_.begin(); it != actors_.end(); ++it)
{
if (*it == nullptr)
{
*it = actor;
return std::distance(actors_.begin(), it);
}
}
assert(false);
return -1;
}
}// namespace Threads

View File

@ -0,0 +1,55 @@
/*
* threads_iactor.cpp
*
* Created on: Nov 14, 2021
* Author: erki
*/
#include "threads_iactor.hpp"
#include <cstring>
#include <queue.h>
#include "threads_actor_thread.hpp"
namespace Threads
{
void IActor::moveToThread(ActorThread* thread)
{
actor_index_ = thread->acceptActor_(this);
parent_thread_ = thread;
}
void IActor::dispatchEventImpl_(const void* data, const std::size_t data_size)
{
taskENTER_CRITICAL();
std::memcpy(parent_thread_->tx_buffer_, &actor_index_, sizeof(std::size_t));
char* data_dest = parent_thread_->tx_buffer_ + sizeof(std::size_t);
std::memcpy(data_dest, data, data_size);
xQueueSend(parent_thread_->queue_, parent_thread_->tx_buffer_, 0);
taskEXIT_CRITICAL();
}
BaseType_t IActor::dispatchEventFromIsrImpl_(const void* data, const std::size_t data_size)
{
const auto isr_data = taskENTER_CRITICAL_FROM_ISR();
std::memcpy(parent_thread_->tx_buffer_, &actor_index_, sizeof(std::size_t));
char* data_dest = parent_thread_->tx_buffer_ + sizeof(std::size_t);
std::memcpy(data_dest, data, data_size);
BaseType_t task_awoken = pdFALSE;
xQueueSendFromISR(parent_thread_->queue_, parent_thread_->tx_buffer_, &task_awoken);
taskEXIT_CRITICAL_FROM_ISR(isr_data);
return task_awoken;
}
}// namespace Threads

View File

@ -7,7 +7,7 @@
#include "threads_primitivethread.hpp" #include "threads_primitivethread.hpp"
#include "peripherals_utility.hpp" #include "utility_bytes.hpp"
#include <limits> #include <limits>
@ -101,14 +101,14 @@ PrimitiveThread::PrimitiveThread(osThreadFunc_t function, void* runner_data, con
PrimitiveThread::PrimitiveThread(TaskHandle_t threadHandle) PrimitiveThread::PrimitiveThread(TaskHandle_t threadHandle)
: thread_id(static_cast<osThreadId_t>(threadHandle)), attributes(Peripherals::zeroInitialized<osThreadAttr_t>()) : thread_id(static_cast<osThreadId_t>(threadHandle)), attributes(Utility::zeroInitialized<osThreadAttr_t>())
{ {
assert(thread_id != nullptr); assert(thread_id != nullptr);
} }
osThreadAttr_t PrimitiveThread::constructThreadAttrs_(const char* name, const osPriority_t priority, const std::uint32_t stack_size) osThreadAttr_t PrimitiveThread::constructThreadAttrs_(const char* name, const osPriority_t priority, const std::uint32_t stack_size)
{ {
auto attrs = Peripherals::zeroInitialized<osThreadAttr_t>(); auto attrs = Utility::zeroInitialized<osThreadAttr_t>();
attrs.name = name; attrs.name = name;
attrs.priority = priority; attrs.priority = priority;

View File

@ -1,13 +1,9 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR) cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
#if(WITH_HAL)
# set(additional_sources
# )
#endif()
add_library(utility STATIC add_library(utility STATIC
Src/utility_logging.cpp Src/utility_logging.cpp
Src/utility_rand.cpp Src/utility_rand.cpp
Src/utility_assert.cpp
${additional_sources} ${additional_sources}
) )
@ -18,5 +14,9 @@ target_include_directories(utility
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
) )
set_target_properties(utility
PROPERTIES
CXX_STANDARD 17
)
skullc_install_packages(skullc utility ${version}) skullc_install_packages(skullc utility ${SKULLC_VERSION})

View File

@ -0,0 +1,33 @@
//
// Created by erki on 29.06.22.
//
#ifndef SKULLC_UTILITY_ASSERT_HPP_
#define SKULLC_UTILITY_ASSERT_HPP_
namespace Utility::Assert::Detail
{
void assertImpl(const char* expression, const char* file, const int line);
}
#ifndef NDEBUG
#define SKULLC_ASSERT_DEBUG(e) (!(e) ? Utility::Assert::Detail::assertImpl(#e, __FILE__, __LINE__) : ((void) 0))
#else
#define SKULLC_ASSERT_DEBUG(e)
#endif
#define SKULLC_ASSERT_SAFE(e) (!(e) ? Utility::Assert::Detail::assertImpl(#e, __FILE__, __LINE__) : ((void) 0))
namespace Utility::Assert
{
using assert_cb = void (*)(const char* expression, const char* file, const int line);
void setHandler(assert_cb callback);
assert_cb getHandler();
}// namespace Utility::Assert
#endif// SKULLC_UTILITY_ASSERT_HPP_

View File

@ -0,0 +1,69 @@
//
// Created by erki on 10/12/22.
//
#ifndef SKULLC_UTILITY_BYTES_HPP_
#define SKULLC_UTILITY_BYTES_HPP_
#include <array>
#include <cstdint>
#include <cstring>
#include <type_traits>
namespace Utility
{
template<typename T, std::size_t N>
T arrayToType(const std::uint8_t raw[N])
{
static_assert(std::is_trivially_default_constructible<T>::value, "Struct is not trivially default constructible.");
static_assert(sizeof(T) == N, "The raw data array is not the same size as T.");
T t;
std::memcpy(&t, raw, sizeof(T));
return t;
}
template<typename T, std::size_t N>
T arrayToType(const std::array<std::uint8_t, N> raw)
{
static_assert(std::is_trivially_default_constructible<T>::value, "Struct is not trivially default constructible.");
static_assert(sizeof(T) == N, "The raw data array is not the same size as T.");
T t;
std::memcpy(&t, raw.data(), sizeof(T));
return t;
}
template<typename T>
constexpr T zeroInitialized()
{
static_assert(std::is_trivially_default_constructible<T>::value, "Struct is not trivially default constructible.");
T t;
std::memset(&t, 0, sizeof(T));
return t;
}
template<typename T>
void zeroInitialized(T& t)
{
static_assert(std::is_trivially_default_constructible<T>::value, "Struct is not trivially default constructible.");
std::memset(&t, 0, sizeof(T));
}
template<typename T>
void zeroInitialized(T* t)
{
static_assert(std::is_trivially_default_constructible<T>::value, "Struct is not trivially default constructible.");
std::memset(t, 0, sizeof(T));
}
}// namespace Utility
#endif//SKULLC_UTILITY_BYTES_HPP_

View File

@ -0,0 +1,22 @@
//
// Created by erki on 15.07.22.
//
#ifndef SKULLC_UTILITY_ENUM_HELPERS_HPP_
#define SKULLC_UTILITY_ENUM_HELPERS_HPP_
#include <type_traits>
#define SKULLC_ENUM_DECLARE_BITFLAG_OPERATORS(E) \
inline E operator|(const E& lhs, const E& rhs) \
{ \
using T = std::underlying_type_t<E>; \
return static_cast<E>(static_cast<T>(lhs) | static_cast<T>(rhs)); \
} \
inline E operator&(const E& lhs, const E& rhs) \
{ \
using T = std::underlying_type_t<E>; \
return static_cast<E>(static_cast<T>(lhs) & static_cast<T>(rhs)); \
}
#endif//SKULLC_UTILITY_ENUM_HELPERS_HPP_

View File

@ -0,0 +1,124 @@
//
// Created by erki on 12/11/22.
//
#ifndef SKULLC_UTILITY_FILTERS_HPP_
#define SKULLC_UTILITY_FILTERS_HPP_
#include <algorithm>
#include <array>
#include "utility_ifilter.hpp"
namespace Utility
{
template<typename T>
class RollingAverageFilter : public IFilter<T>
{
public:
RollingAverageFilter() = delete;
explicit RollingAverageFilter(const std::size_t step)
: step_size_(step)
{}
T currentValue() const override
{
return value_;
}
protected:
void updateImpl_(const T& data) override
{
value_ -= value_ / step_size_;
value_ += data / step_size_;
}
private:
std::size_t step_size_;
T value_ = 0;
};
template<typename T, std::size_t N>
class MedianFilter : public IFilter<T>
{
public:
MedianFilter()
{
data_sequence_.fill(T(0));
}
T currentValue() const override
{
auto sorted = data_sequence_;
std::sort(sorted.begin(), sorted.end());
return sorted[N / 2];
}
protected:
void updateImpl_(const T& data) override
{
std::move(data_sequence_.begin() + 1, data_sequence_.end(), data_sequence_.begin());
data_sequence_[N - 1] = data;
}
private:
std::array<T, N> data_sequence_;
};
template<typename T>
class LowPassFilter : public IFilter<T>
{
public:
LowPassFilter() = delete;
explicit LowPassFilter(const T threshold)
: threshold_(threshold)
{}
T currentValue() const override
{
return value_;
}
protected:
void updateImpl_(const T& data) override
{
if (data < threshold_)
value_ = data;
}
private:
T threshold_;
T value_ = 0;
};
template<typename T>
class HighPassFilter : public IFilter<T>
{
public:
HighPassFilter() = delete;
explicit HighPassFilter(const T threshold)
: threshold_(threshold)
{}
T currentValue() const override
{
return value_;
}
protected:
void updateImpl_(const T& data) override
{
if (data > threshold_)
value_ = data;
}
private:
T threshold_;
T value_ = 0;
};
}// namespace Utility
#endif//SKULLC_UTILITY_FILTERS_HPP_

View File

@ -8,6 +8,8 @@
#ifndef SKULLC_UTILITY_FUNCTION_HPP_ #ifndef SKULLC_UTILITY_FUNCTION_HPP_
#define SKULLC_UTILITY_FUNCTION_HPP_ #define SKULLC_UTILITY_FUNCTION_HPP_
#include <utility_assert.hpp>
namespace Utility namespace Utility
{ {
@ -19,11 +21,19 @@ class IFunction<R(Args...)>
{ {
public: public:
using result_type = R; using result_type = R;
using static_signature = R (*)(Args...);
virtual ~IFunction() virtual ~IFunction()
{} {}
virtual result_type operator()(Args... args) = 0; virtual result_type operator()(Args... args) = 0;
template<typename Tag>
static_signature toStaticFunction()
{
static decltype(this) h = this;
return +[](Args... args) { (*h)(args...); };
}
}; };
template<class> template<class>
@ -41,7 +51,7 @@ public:
explicit Function(signature callable) explicit Function(signature callable)
: callable_(callable) : callable_(callable)
{ {
assert(callable_); SKULLC_ASSERT_DEBUG(callable_);
} }
~Function() override ~Function() override
@ -73,7 +83,7 @@ public:
explicit FunctionOwned(source& src, signature callable) explicit FunctionOwned(source& src, signature callable)
: src_(&src), callable_(callable) : src_(&src), callable_(callable)
{ {
assert(callable_); SKULLC_ASSERT_DEBUG(callable_);
} }
~FunctionOwned() override ~FunctionOwned() override

View File

@ -0,0 +1,49 @@
//
// Created by erki on 12/11/22.
//
#ifndef SKULLC_UTILITY_IFILTER_HPP_
#define SKULLC_UTILITY_IFILTER_HPP_
namespace Utility
{
template<typename T>
class IFilter
{
public:
virtual ~IFilter() = default;
void assignPrecedingFilter(IFilter<T>& filter)
{
preceding_filter_ = &filter;
}
void clearPrecedingFilter()
{
preceding_filter_ = nullptr;
}
void update(T data)
{
if (preceding_filter_)
{
preceding_filter_->update(data);
data = preceding_filter_->currentValue();
}
updateImpl_(data);
}
virtual T currentValue() const = 0;
protected:
virtual void updateImpl_(const T& data) = 0;
private:
IFilter<T>* preceding_filter_ = nullptr;
};
}// namespace Utility
#endif//SKULLC_UTILITY_IFILTER_HPP_

View File

@ -158,15 +158,18 @@ public:
if (dx == 0 && dy == 0) if (dx == 0 && dy == 0)
{ {
at(start) = color; at(start) = color;
} else if (dx == 0) }
else if (dx == 0)
{ {
for (; y < y_(stop); y++) for (; y < y_(stop); y++)
at({x, y}) = color; at({x, y}) = color;
} else if (dy == 0) }
else if (dy == 0)
{ {
for (; x < x_(stop); x++) for (; x < x_(stop); x++)
at({x, y}) = color; at({x, y}) = color;
} else }
else
{ {
// Bresenham's algorithm. // Bresenham's algorithm.
std::int32_t p = 2 * dy - dx; std::int32_t p = 2 * dy - dx;
@ -179,7 +182,8 @@ public:
{ {
y++; y++;
p = p + 2 * dy - 2 * dx; p = p + 2 * dy - 2 * dx;
} else }
else
{ {
p = p + 2 * dy; p = p + 2 * dy;
} }
@ -217,7 +221,8 @@ public:
{ {
y--; y--;
d = d + 4 * (x - y) + 10; d = d + 4 * (x - y) + 10;
} else }
else
{ {
d = d + 4 * x + 6; d = d + 4 * x + 6;
} }

View File

@ -68,7 +68,8 @@ public:
if (naive < _arr_end) if (naive < _arr_end)
{ {
return iterator(naive, _arr_begin, _arr_end); return iterator(naive, _arr_begin, _arr_end);
} else }
else
{ {
const pointer remainder = pointer(naive - _arr_end); const pointer remainder = pointer(naive - _arr_end);
return iterator(_arr_begin + difference_type(remainder), _arr_begin, return iterator(_arr_begin + difference_type(remainder), _arr_begin,
@ -82,7 +83,8 @@ public:
if (naive >= _arr_begin) if (naive >= _arr_begin)
{ {
return iterator(naive, _arr_begin, _arr_end); return iterator(naive, _arr_begin, _arr_end);
} else }
else
{ {
const pointer remainder = pointer(_arr_begin - naive); const pointer remainder = pointer(_arr_begin - naive);
return iterator(_arr_end - difference_type(remainder), _arr_begin, return iterator(_arr_end - difference_type(remainder), _arr_begin,
@ -202,7 +204,8 @@ public:
if (distance > 0) if (distance > 0)
{ {
return distance; return distance;
} else }
else
{ {
return head_ - tail_ + 1; return head_ - tail_ + 1;
} }

View File

@ -8,8 +8,11 @@
#ifndef SKULLC_UTILITY_STATICPOINTER_HPP_ #ifndef SKULLC_UTILITY_STATICPOINTER_HPP_
#define SKULLC_UTILITY_STATICPOINTER_HPP_ #define SKULLC_UTILITY_STATICPOINTER_HPP_
#include <new>
#include <utility> #include <utility>
#include <utility_assert.hpp>
namespace Utility namespace Utility
{ {
@ -57,6 +60,18 @@ struct StaticPointer
return initialized_; return initialized_;
} }
value_type* get()
{
SKULLC_ASSERT_DEBUG(initialized_);
return reinterpret_cast<value_type*>(storage);
}
const value_type* get() const
{
SKULLC_ASSERT_DEBUG(initialized_);
return reinterpret_cast<const value_type*>(storage);
}
private: private:
bool initialized_ = false; bool initialized_ = false;
}; };

View File

@ -0,0 +1,12 @@
//
// Created by erki on 26.06.22.
//
#ifndef SKULLC_UTILITY_TAG_HPP_
#define SKULLC_UTILITY_TAG_HPP_
#define SKULLC_CONCAT_IMPL(x, y) x##y
#define SKULLC_CONCAT(x, y) SKULLC_CONCAT_IMPL(x, y)
#define SKULLC_TAG struct SKULLC_CONCAT(SkullCTag_, __COUNTER__)
#endif// SKULLC_UTILITY_TAG_HPP_

View File

@ -0,0 +1,42 @@
//
// Created by erki on 29.06.22.
//
#include "utility_assert.hpp"
#include <exception>
namespace
{
Utility::Assert::assert_cb INSTALLED_HANDLER = nullptr;
}
namespace Utility::Assert
{
namespace Detail
{
void assertImpl(const char* expression, const char* file, const int line)
{
if (INSTALLED_HANDLER)
INSTALLED_HANDLER(expression, file, line);
else
std::terminate();
}
}// namespace Detail
void setHandler(assert_cb callback)
{
INSTALLED_HANDLER = callback;
}
assert_cb getHandler()
{
return INSTALLED_HANDLER;
}
}// namespace Utility::Assert

11
cmake/FindSphinx.cmake Normal file
View File

@ -0,0 +1,11 @@
#Look for an executable called sphinx-build
find_program(SPHINX_EXECUTABLE
NAMES sphinx-build
DOC "Path to sphinx-build executable")
include(FindPackageHandleStandardArgs)
#Handle standard arguments to find_package like REQUIRED and QUIET
find_package_handle_standard_args(Sphinx
"Failed to find sphinx-build executable"
SPHINX_EXECUTABLE)

View File

@ -1,5 +0,0 @@
[requires]
catch2/[>=2.0.0,<3.0.0]
[generators]
cmake_find_package