Compare commits

..

37 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
Erki
fb319fd21f Merge branch 'other/mousetrap_fixes'
All checks were successful
continuous-integration/drone/push Build is passing
2021-11-12 23:25:21 +02:00
806416e943 Feature: actor output (#1)
Some checks failed
continuous-integration/drone/push Build is failing
Co-authored-by: Erki <erki@skullnet.me>
Reviewed-on: #1
Co-authored-by: erki <erki.meinberg@gmail.com>
Co-committed-by: erki <erki.meinberg@gmail.com>
2021-11-12 21:23:12 +00:00
Erki
0762d5c9cd Partial fixes to ICM reading
Some checks failed
continuous-integration/drone/push Build is failing
TODO: Fix the actual conversions
2021-11-04 03:02:10 +02:00
Erki
92b18cae83 Threads: fix signal.hpp missing includes 2021-11-04 03:01:20 +02:00
60 changed files with 1766 additions and 3276 deletions

View File

@ -17,7 +17,7 @@ AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
@ -25,8 +25,8 @@ BraceWrapping:
AfterNamespace: true
AfterUnion: true
AfterStruct: true
BeforeCatch: false
BeforeElse: false
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
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,23 +1,22 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
set(version 0.1.0)
set(SKULLC_VERSION 0.1.0)
project(skullc
VERSION ${version}
VERSION ${SKULLC_VERSION}
LANGUAGES
C
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/)
set(CMAKE_CXX_STANDARD 17)
list(APPEND CMAKE_CXX_FLAGS "-Wall -Wextra")
option(WITH_DOCS "Enable building docs." OFF)
option(WITH_TESTS "Enable unit testing." OFF)
option(WITH_HAL "Enable the compiling and deployment of the HAL dependent sections." OFF)
option(SKULLC_WITH_TESTS "Enable unit testing." OFF)
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(SKULLC_USE_HAL_ESP "Enable the ESP HAL when SKULLC_WITH_HAL is enabled." OFF)
option(SKULLC_WITH_DOCS "Enable documentation building." OFF)
include(skullc-install)
@ -25,13 +24,13 @@ add_subdirectory(Peripherals)
add_subdirectory(Utility)
add_subdirectory(Messaging)
if(WITH_TESTS)
if(SKULLC_WITH_TESTS)
enable_testing()
add_subdirectory(Tests)
endif()
if(WITH_DOCS)
add_subdirectory(docs)
if(SKULLC_WITH_DOCS)
add_subdirectory(Docs)
endif()
## Install

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

@ -5,9 +5,9 @@ add_library(messaging INTERFACE)
add_library(skullc::messaging ALIAS messaging)
target_include_directories(messaging
INTERFACE
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>
$<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
skullc_install_packages(skullc peripherals ${version})
skullc_install_packages(skullc peripherals ${SKULLC_VERSION})

View File

@ -20,17 +20,6 @@ enum class ButtonPress : std::uint32_t
LONG_PRESS
};
/**
* A class to abstract a button!
*
* Usage example:
* @code
* int a = 4;
* @endcode
*
* @tparam G The class to use for GPIO. Must have members @c set(bool).
* @tparam H The HAL class to use.
*/
template<typename G, typename H>
class Button
{
@ -38,7 +27,7 @@ public:
using gpio = G;
using hal = H;
gpio sw; ///< A switch instance.
gpio sw;
static constexpr std::uint32_t TIMEOUT_SHORT_PRESS = 50;
static constexpr std::uint32_t TIMEOUT_LONG_PRESS = 500;
@ -48,27 +37,29 @@ public:
: sw(sw)
{}
/**
* Does the do.
*/
void update()
{
const bool is_pressed = sw.read();
ButtonPress new_state = ButtonPress::NOT_PRESSED;
const std::uint32_t time_held = hal::getMillis() - time_pressed_down_;
if (is_pressed && !was_pressed_)
{
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_LONG_PRESS)
new_state = ButtonPress::LONG_PRESS;
else if (time_held > TIMEOUT_SHORT_PRESS)
if (time_held > TIMEOUT_SHORT_PRESS && time_held <= TIMEOUT_LONG_PRESS)
new_state = ButtonPress::SHORT_PRESS;
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;
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_
#define SKULLC_PERIPHERALS_HAL_ST_HPP_
#include <main.h>
#ifdef SKULLC_USE_HAL_ST
#include "peripherals_utility.hpp"
#include <main.h>
#include <array>
@ -95,10 +95,16 @@ struct Gpio
bool read() const { return inverted ? !bool(HAL_GPIO_ReadPin(port, pin)) : bool(HAL_GPIO_ReadPin(port, pin)); }
};
#define CREATE_GPIO(name) \
Peripherals::Hal::St::Gpio { name##_GPIO_Port, name##_Pin, false }
#define CREATE_INV_GPIO(name) \
Peripherals::Hal::St::Gpio { name##_GPIO_Port, name##_Pin, true }
#define CREATE_GPIO(name) \
Peripherals::Hal::St::Gpio \
{ \
name##_GPIO_Port, name##_Pin, false \
}
#define CREATE_INV_GPIO(name) \
Peripherals::Hal::St::Gpio \
{ \
name##_GPIO_Port, name##_Pin, true \
}
#endif// HAL_GPIO_MODULE_ENABLED
@ -262,10 +268,30 @@ struct SpiRegisters
#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 =
SerialInterface<UART_HandleTypeDef, HAL_UART_Transmit, HAL_UART_Receive>;
SerialInterface<UART_HandleTypeDef, _Details::uartTransmit, HAL_UART_Receive>;
using UartInterfaceDMA =
SerialInterfaceAsync<UART_HandleTypeDef, HAL_UART_Transmit_DMA,
SerialInterfaceAsync<UART_HandleTypeDef, _Details::uartTransmitDma,
HAL_UART_Receive_DMA>;
#endif// HAL_UART_MODULE_ENABLED
@ -319,4 +345,8 @@ struct ItmSerialInterface
}// namespace Hal
}// 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_ */

View File

@ -12,7 +12,6 @@
#include <limits>
#include "peripherals_imu.hpp"
#include "peripherals_utility.hpp"
namespace Peripherals
{
@ -43,7 +42,11 @@ public:
registers_handle registers;
ImuIcm() = delete;
ImuIcm(const registers_handle& registers) : registers(registers) {}
ImuIcm(const registers_handle& registers) : registers(registers)
{
bias_accel_.fill(0);
bias_gyro_.fill(0);
}
void setup() override
{
@ -68,9 +71,13 @@ public:
// rate = 1KHz, temp filter = 42
hal::delay(10);
setGyroscopeScale(scale_gyro_);
const std::uint8_t new_gyro_conf = (std::uint32_t(scale_gyro_) << 3);
registers.writeRegister(Registers_::GYRO_CONFIG & Registers_::WRITE_MASK,
new_gyro_conf);
setAccelerometerScale(scale_accel_);
const std::uint8_t new_accel_config = (std::uint32_t(scale_accel_) << 3);
registers.writeRegister(Registers_::ACCEL_CONFIG & Registers_::WRITE_MASK,
new_accel_config);
// ACCEL_FCHOICE_B = 0, A_DLPF_CFG = 3 filter=44.8/61.5 rate=1KHz
registers.writeRegister(Registers_::ACCEL_CONFIG2 & Registers_::WRITE_MASK,
@ -96,33 +103,43 @@ public:
hal::delay(10);
}
std::array<std::int16_t, 3>& gyroBias()
{
return bias_gyro_;
}
std::array<std::int16_t, 3>& accelBias()
{
return bias_accel_;
}
void calibrate(const std::uint32_t samples) override
{
std::array<std::int32_t, 3> avg_gyro;
std::array<std::int32_t, 3> avg_accel;
avg_gyro.fill(0);
avg_accel.fill(0);
for (std::uint32_t i = 0; i < samples; i++)
{
std::array<std::int16_t, 3> raw;
auto add_to_avg = [&raw](std::array<std::int32_t, 3>& out) {
for (std::uint32_t j = 0; j < 3; j++)
out[j] += raw[j];
};
readGyroRaw(raw.data());
add_to_avg(avg_gyro);
for (std::uint32_t j = 0; j < 3; j++)
avg_gyro[j] += raw[j];
readAccelerometerRaw(raw.data());
add_to_avg(avg_accel);
for (std::uint32_t j = 0; j < 3; j++)
avg_accel[j] += raw[j];
hal::delay(2);
}
for (std::uint32_t i = 0; i < 3; i++)
{
const std::int32_t max = std::numeric_limits<std::int16_t>::max();
const std::int32_t min = std::numeric_limits<std::int16_t>::min();
bias_gyro_[i] = clamp(avg_gyro[i], max, min);
bias_accel_[i] = clamp(avg_accel[i], max, min);
bias_gyro_[i] = avg_gyro[i] / std::int32_t(samples);
bias_accel_[i] = avg_accel[i] / std::int32_t(samples);
}
bias_accel_[2] -= accelerometerReadingToRaw(1);
@ -158,11 +175,9 @@ public:
registers.readRegisterMultibyte(
Registers_::GYRO_XOUT_H | Registers_::READ_MASK, data, 6);
for (std::uint32_t i = 0; i < 3; i++)
{
const std::int16_t bit = byteToTypeBE<std::int16_t, 2>(&data[i * 2]);
output[i] = gyroRawToReading(bit);
}
output[0] = gyroRawToReading(std::int16_t((data[0] << 8) | data[1]), 0);
output[1] = gyroRawToReading(std::int16_t((data[2] << 8) | data[3]), 1);
output[2] = gyroRawToReading(std::int16_t((data[4] << 8) | data[5]), 2);
}
void readGyroRaw(std::int16_t* output) override
@ -171,10 +186,9 @@ public:
registers.readRegisterMultibyte(
Registers_::GYRO_XOUT_H | Registers_::READ_MASK, data, 6);
for (std::uint32_t i = 0; i < 3; i++)
{
output[i] = byteToTypeBE<std::int16_t, 2>(&data[i * 2]);
}
output[0] = std::int16_t((data[0] << 8) | data[1]);
output[1] = std::int16_t((data[2] << 8) | data[3]);
output[2] = std::int16_t((data[4] << 8) | data[5]);
}
void readAccelerometer(float* output) override
@ -183,11 +197,9 @@ public:
registers.readRegisterMultibyte(
Registers_::ACCEL_XOUT_H | Registers_::READ_MASK, data, 6);
for (std::uint32_t i = 0; i < 3; i++)
{
const std::int16_t bit = byteToTypeBE<std::int16_t, 2>(&data[i * 2]);
output[i] = accelerometerRawToReading(bit);
}
output[0] = accelerometerRawToReading(std::int16_t((data[0] << 8) | data[1]), 0);
output[1] = accelerometerRawToReading(std::int16_t((data[2] << 8) | data[3]), 1);
output[2] = accelerometerRawToReading(std::int16_t((data[4] << 8) | data[5]), 2);
}
void readAccelerometerRaw(std::int16_t* output) override
@ -196,10 +208,10 @@ public:
registers.readRegisterMultibyte(
Registers_::ACCEL_XOUT_H | Registers_::READ_MASK, data, 6);
for (std::uint32_t i = 0; i < 3; i++)
{
output[i] = byteToTypeBE<std::int16_t, 2>(&data[i * 2]);
}
output[0] = std::int16_t((data[0] << 8) | data[1]);
output[1] = std::int16_t((data[2] << 8) | data[3]);
output[2] = std::int16_t((data[4] << 8) | data[5]);
}
std::int16_t accelerometerReadingToRaw(const float& fs) const
@ -212,14 +224,14 @@ public:
return fs / gyro_fs_to_bit_constants_[std::uint32_t(scale_gyro_)];
}
float accelerometerRawToReading(const std::int16_t bit) const
float accelerometerRawToReading(const std::int16_t bit, const std::size_t axis) const
{
return (float(bit) * accel_fs_to_bit_constants_[std::uint32_t(scale_accel_)]) * 9.8f;
return (float(bit - bias_accel_[axis]) * accel_fs_to_bit_constants_[std::uint32_t(scale_accel_)]) * 9.8f;
}
float gyroRawToReading(const std::int16_t bit) const
float gyroRawToReading(const std::int16_t bit, const std::size_t axis) const
{
return float(bit) * gyro_fs_to_bit_constants_[std::uint32_t(scale_gyro_)];
return float(bit - bias_gyro_[axis]) * gyro_fs_to_bit_constants_[std::uint32_t(scale_gyro_)];
}
std::uint8_t readProductId()
@ -231,9 +243,6 @@ private:
GyroScale scale_gyro_ = GyroScale::DPS_2000;
AccelerometerScale scale_accel_ = AccelerometerScale::G16;
std::array<std::int16_t, 3> bias_gyro_;
std::array<std::int16_t, 3> bias_accel_;
static constexpr float accel_fs_to_bit_constants_[4] = {
(2.0f / 32768.0f), (4.0f / 32768.0f), (8.0f / 32768.0f),
(16.0f / 32768.0f)};
@ -242,6 +251,9 @@ private:
(250.0f / 32768.0f), (500.0f / 32768.0f), (1000.0f / 32768.0f),
(2000.0f / 32768.0f)};
std::array<std::int16_t, 3> bias_gyro_;
std::array<std::int16_t, 3> bias_accel_;
struct Registers_
{
static constexpr std::uint32_t ICM20689_ID = 0x98;

View File

@ -54,7 +54,8 @@ public:
{
left_.forward.setCompare(left);
left_.backward.setCompare(0);
} else
}
else
{
left_.forward.setCompare(0);
left_.backward.setCompare(-1 * left);
@ -64,7 +65,8 @@ public:
{
right_.forward.setCompare(right);
right_.backward.setCompare(0);
} else
}
else
{
right_.forward.setCompare(0);
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)
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_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined -fno-sanitize-recover")
endif()
@ -18,7 +24,14 @@ add_executable(tests
rand.cpp
fixedpoint.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
PUBLIC
@ -28,6 +41,12 @@ target_link_libraries(tests
Catch2::Catch2
)
set_target_properties(tests
PROPERTIES
CXX_STANDARD 17
)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib)
include(CTest)
include(Catch)
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.")
{
HAL::millis = Button::TIMEOUT_LONG_PRESS + 2;
Gpio::set = false;
button.update();
@ -121,6 +120,16 @@ TEST_CASE("Button reads presses properly.", "[peripherals],[button]")
button.update();
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 <utility_function.hpp>
#include <utility_tag.hpp>
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);
}
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]")
{
struct S
@ -82,3 +103,29 @@ TEST_CASE("FunctionOwned passes arguments appropriately.", "[utility],[function]
REQUIRE(subject.int_to_set == 10);
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 <variant>
#include "threads_primitivethread.hpp"
#include "threads_signal.hpp"
#include "threads_timer.hpp"
#include <threads_actor_thread.hpp>
#include <threads_iactor.hpp>
#include <threads_signal.hpp>
namespace Threads
{
template<typename CRTP, typename... Ts>
class Actor
class Actor : public IActor
{
public:
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_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)
: thread_(&Actor<CRTP, Ts...>::threadHandler_, this, name, priority, stack_size),
msg_queue_(xQueueCreate(queue_size, sizeof(value_type))), signal_(this)
{
assert(msg_queue_);
}
Actor() : IActor(sizeof(value_type)),
signal_(this)
{}
virtual ~Actor() {}
~Actor() override {}
template<typename T>
struct Signal : Signallable<T>
@ -51,20 +48,21 @@ public:
: q_(q)
{}
Signal(const Signal&) = default;
Signal(Signal&&) = default;
Signal& operator=(const Signal&) = default;
Signal& operator=(Signal&&) = default;
void emit(const T& data) override
{
parent::value_type to_send = data;
xQueueSend(q_->msg_queue_, &to_send, 0);
q_->dispatchEvent(to_send);
}
BaseType_t emitFromIsr(const T& data) override
{
parent::value_type to_send = data;
BaseType_t was_awoken = pdFALSE;
xQueueSendFromISR(q_->msg_queue_, &to_send, &was_awoken);
return was_awoken;
return q_->dispatchEventFromIsr(to_send);
}
private:
@ -79,18 +77,25 @@ public:
return Signal<U>(this);
}
template<typename U>
Signal<U>* getAllocatedSignal()
{
static_assert(std::is_convertible_v<U, value_type>, "value_type cannot be constructed from U.");
return new Signal<U>(this);
}
Signallable<value_type>* getStaticSignal()
{
return &signal_;
}
protected:
virtual void init() = 0;
void dispatchSignal(const char* data) final
{
dispatchImpl_(data);
}
private:
PrimitiveThread thread_;
QueueHandle_t msg_queue_;
struct Visitor_
{
using parent = Actor<CRTP, Ts...>;
@ -108,113 +113,15 @@ private:
}
};
void operator()()
void dispatchImpl_(const char* data)
{
init();
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)();
const value_type* signal_data = reinterpret_cast<const value_type*>(data);
std::visit(Visitor_{this}, *signal_data);
}
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

View File

@ -0,0 +1,117 @@
/*
* threads_actor_output.hpp
*
* Created on: Oct 24, 2021
* Author: erki
*/
#ifndef SKULLC_THREADS_ACTOR_OUTPUT_HPP_
#define SKULLC_THREADS_ACTOR_OUTPUT_HPP_
#include <array>
#include <optional>
#include <variant>
#include <threads_signal.hpp>
namespace Threads
{
template<std::size_t N, typename... Ts>
class ActorOutput
{
private:
using signal_variant = std::variant<std::monostate, Signallable<Ts>*...>;
std::array<signal_variant, N> stored_signals_;
public:
ActorOutput() = default;
ActorOutput(const ActorOutput&) = delete;
ActorOutput(ActorOutput&&) = delete;
ActorOutput& operator=(const ActorOutput&) = delete;
ActorOutput& operator=(ActorOutput&&) = delete;
using connection_type = std::optional<typename decltype(stored_signals_)::iterator>;
template<typename T>
connection_type addConnection(Signallable<T>* signal)
{
static_assert(std::disjunction_v<std::is_same<T, Ts>...>, "T is not a member of Ts.");
for (auto it = stored_signals_.begin(); it != stored_signals_.end(); ++it)
{
auto& storage = *it;
if (std::get_if<std::monostate>(&storage))
{
storage = signal_variant{signal};
return it;
}
}
return std::nullopt;
}
bool removeConnection(connection_type& connection)
{
if (!connection)
return true;
for (auto it = stored_signals_.begin(); it != stored_signals_.end(); ++it)
{
if (it == *connection)
{
delete *it;
*it = std::monostate{};
return true;
}
}
return false;
}
protected:
template<typename T>
void emitOutput(const T& data)
{
static_assert(std::disjunction_v<std::is_same<T, Ts>...>, "T is not a member of Ts.");
for (auto& s : stored_signals_)
{
if (auto** signal = std::get_if<Signallable<T>*>(&s))
(*signal)->emit(data);
}
}
template<typename T>
void emitOutputFromIsr(const T& data)
{
static_assert(std::disjunction_v<std::is_same<T, Ts>...>, "T is not a member of Ts.");
for (auto& s : stored_signals_)
{
if (auto** signal = std::get_if<Signallable<T>*>(&s))
(*signal)->emitFromIsr(data);
}
}
};
template<typename T, typename Sender, typename Receiver>
auto connectActors(Sender* sender, Receiver* receiver)
{
auto* signal = receiver->template getAllocatedSignal<T>();
return sender->addConnection(signal);
}
template<typename T, typename Sender, typename Receiver>
auto connectActors(Sender& sender, Receiver& receiver)
{
auto* signal = receiver.template getAllocatedSignal<T>();
return sender.addConnection(signal);
}
}// namespace Threads
#endif// SKULLC_THREADS_ACTOR_OUTPUT_HPP_

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)
{
return std::nullopt;
} else
}
else
{
return current_value_;
}
@ -78,7 +79,8 @@ public:
if (!waiting_thread_)
{
return false;
} else
}
else
{
return waiting_thread_->notify(0, eNoAction);
}
@ -91,7 +93,8 @@ public:
if (!waiting_thread_)
{
return {false, pdFALSE};
} else
}
else
{
auto const [discard, was_notified] = waiting_thread_->notifyFromIsr(0, eNoAction);
(void) discard;
@ -158,7 +161,8 @@ struct ExclusiveSignal<void> : public Signallable<void>
if (!waiting_thread_)
{
return pdFALSE;
} else
}
else
{
auto const [discard, was_notified] = waiting_thread_->notifyFromIsr(0, eNoAction);
(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,6 +11,7 @@
#include <cmsis_os.h>
#include <freertos_os2.h>
#include <cstdint>
#include <tuple>
#include <type_traits>

View File

@ -8,7 +8,10 @@
#ifndef THREADS_INC_THREADS_SIGNAL_HPP_
#define THREADS_INC_THREADS_SIGNAL_HPP_
#include <type_traits>
#include <freertos_os2.h>
#include <threads_primitivethread.hpp>
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,9 @@
#include "threads_primitivethread.hpp"
#include "peripherals_utility.hpp"
#include "utility_bytes.hpp"
#include <limits>
#ifdef INCLUDE_xTaskGetCurrentTaskHandle
#define ASSERT_IS_CURRENT() \
@ -99,14 +101,14 @@ PrimitiveThread::PrimitiveThread(osThreadFunc_t function, void* runner_data, con
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);
}
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.priority = priority;

View File

@ -1,13 +1,9 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
#if(WITH_HAL)
# set(additional_sources
# )
#endif()
add_library(utility STATIC
Src/utility_logging.cpp
Src/utility_rand.cpp
Src/utility_assert.cpp
${additional_sources}
)
@ -18,5 +14,9 @@ target_include_directories(utility
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>
$<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

@ -1,40 +1,33 @@
/*
* utility_assert.hpp
*
* Created on: Jun 25, 2021
* Author: erki
*/
//
// Created by erki on 29.06.22.
//
#ifndef UTILITY_INC_UTILITY_ASSERT_HPP_
#define UTILITY_INC_UTILITY_ASSERT_HPP_
#ifndef SKULLC_UTILITY_ASSERT_HPP_
#define SKULLC_UTILITY_ASSERT_HPP_
#ifdef __cplusplus
extern "C" {
#endif
__attribute__((weak)) void SkullC_AssertHandler(const char* file, int line, const char* func, const char* failed_expr)
namespace Utility::Assert::Detail
{
void assertImpl(const char* expression, const char* file, const int line);
}
void __assert_func(const char* file, int line, const char* func, const char* failed_expr)
{
#ifdef SKULLC_UTILITY_ASSERT_TO_ERRORHANDLER
Error_Handler();
#ifndef NDEBUG
#define SKULLC_ASSERT_DEBUG(e) (!(e) ? Utility::Assert::Detail::assertImpl(#e, __FILE__, __LINE__) : ((void) 0))
#else
__disable_irq();
__asm("bkpt");
#ifdef SKULLC_UTILITY_ASSERT_CUSTOM_HANDLER
SkullC_AssertHandler(file, line, func, failed_expr);
#define SKULLC_ASSERT_DEBUG(e)
#endif
while (1)
;
#endif
}
#define SKULLC_ASSERT_SAFE(e) (!(e) ? Utility::Assert::Detail::assertImpl(#e, __FILE__, __LINE__) : ((void) 0))
#ifdef __cplusplus
}
#endif
namespace Utility::Assert
{
#endif /* UTILITY_INC_UTILITY_ASSERT_HPP_ */
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_
#define SKULLC_UTILITY_FUNCTION_HPP_
#include <utility_assert.hpp>
namespace Utility
{
@ -19,11 +21,19 @@ class IFunction<R(Args...)>
{
public:
using result_type = R;
using static_signature = R (*)(Args...);
virtual ~IFunction()
{}
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>
@ -41,7 +51,7 @@ public:
explicit Function(signature callable)
: callable_(callable)
{
assert(callable_);
SKULLC_ASSERT_DEBUG(callable_);
}
~Function() override
@ -73,7 +83,7 @@ public:
explicit FunctionOwned(source& src, signature callable)
: src_(&src), callable_(callable)
{
assert(callable_);
SKULLC_ASSERT_DEBUG(callable_);
}
~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)
{
at(start) = color;
} else if (dx == 0)
}
else if (dx == 0)
{
for (; y < y_(stop); y++)
at({x, y}) = color;
} else if (dy == 0)
}
else if (dy == 0)
{
for (; x < x_(stop); x++)
at({x, y}) = color;
} else
}
else
{
// Bresenham's algorithm.
std::int32_t p = 2 * dy - dx;
@ -179,7 +182,8 @@ public:
{
y++;
p = p + 2 * dy - 2 * dx;
} else
}
else
{
p = p + 2 * dy;
}
@ -217,7 +221,8 @@ public:
{
y--;
d = d + 4 * (x - y) + 10;
} else
}
else
{
d = d + 4 * x + 6;
}

View File

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

View File

@ -8,8 +8,11 @@
#ifndef SKULLC_UTILITY_STATICPOINTER_HPP_
#define SKULLC_UTILITY_STATICPOINTER_HPP_
#include <new>
#include <utility>
#include <utility_assert.hpp>
namespace Utility
{
@ -57,6 +60,18 @@ struct StaticPointer
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:
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

View File

@ -1,7 +1,11 @@
#Look for an executable called sphinx-build
find_program(SPHINX_EXECUTABLE
NAMES sphinx-build
DOC "Path to sphinx-build executable")
include(FindPackageHandleStandardArgs)
find_package_handle_Standard_args(Sphinx "Failed to find sphinx-build executable" SPHINX_EXECUTABLE)
#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

View File

@ -1,44 +0,0 @@
find_package(Doxygen REQUIRED)
find_package(Sphinx REQUIRED)
file(GLOB_RECURSE PERIPHERALS_HEADERS ${CMAKE_SOURCE_DIR}/Peripherals/Inc/*.hpp)
set(DOXYGEN_INPUT_DIR "${PROJECT_SOURCE_DIR}/Threads/Inc ${PROJECT_SOURCE_DIR}/Peripherals/Inc ${PROJECT_SOURCE_DIR}/Utility/Inc")
set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/docs/doxygen)
set(DOXYGEN_INDEX_FILE ${CMAKE_CURRENT_SOURCE_DIR}/html/index.html)
set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
#Replace variables inside @@ with the current values
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 ${PERIPHERALS_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}/docs/sphinx)
set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html)
set(SPHINX_SOURCE_LIST
${CMAKE_CURRENT_SOURCE_DIR}/index.rst
${CMAKE_CURRENT_SOURCE_DIR}/peripherals.rst
${CMAKE_CURRENT_SOURCE_DIR}/utility.rst
${CMAKE_CURRENT_SOURCE_DIR}/threads.rst
)
add_custom_command(OUTPUT ${SPHINX_INDEX_FILE}
COMMAND
${SPHINX_EXECUTABLE} -b html
-Dbreathe_projects.SkullC=${DOXYGEN_OUTPUT_DIR}/xml
${SPHINX_SOURCE} ${SPHINX_BUILD}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${SPHINX_SOURCE_LIST} ${PERIPHERALS_HEADERS}
MAIN_DEPENDENCY ${SPHINX_SOURCE}/conf.py
COMMENT "Generating documentation with Sphinx")
add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE})

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'SkullC'
copyright = '2021, Erki (Skull132)'
author = 'Erki (Skull132)'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"breathe"
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for breathe plugin -----------------------------------------------
breathe_default_project = "SkullC"

View File

@ -1,50 +0,0 @@
.. SkullC documentation master file, created by
sphinx-quickstart on Wed Jul 7 13:46:52 2021.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to SkullC's documentation!
==================================
**SkullC** is a C++17 library, composed of various modules, meant for **embedded software** development. The code is presently
licensed under MIT and can be freely found in `the Git repository here <https://git.skullnet.me/erki/skullc-peripherals>`_.
The library contains abstractions for commonly used peripheral devices, general utility functions, and an abstraction over
FreeRTOS for RTOS based programming. A lot of it is developed as I go, with some components having more questionable design
as others (since some are implemented as thought-exercises at first). This also speaks about API stability: things may change.
Most of this library has been tested and built for the STM32 boards. Though in theory, the code is not reliant on any specific
HAL and can be expanded to work on any system for which you implement the required interfaces.
Contents
--------
You will find 3 large modules here.
The :doc:`peripherals` contains both abstractions over the ST HAL and various other peripheral devices, such as IMUs,
displays, etc.
The :doc:`utility` contains various "software" helpers, abstractions, etcetera, that don't necessarily interface with
hardware, but in general provide helpful tools for embedded development. This includes logging functionality, a drawing
abstraction, and so forth.
The :doc:`threads` is a set of various C++ abstractions over FreeRTOS. With the key highlight being an implementation of
a signalling system, and the actor model of asynchronous processing.
Usage
-----
The library is made to be as build-tool agnostic as possible. All modules can simply be dropped into whatever IDE/project
structure you have, and then used. No major configuration is necessary. Most modules are header-only, some require additional
source files to be compiled and linked.
For development use, cmake is used to enable unit testing (and should also function for integration with cmake projects).
Conan and catch2 are additional dependencies for unit testing and CI work.
.. toctree::
:maxdepth: 2
:caption: Contents:
peripherals.rst
utility.rst
threads.rst

View File

@ -1,161 +0,0 @@
Peripherals Module
==================
The peripherals module contains various external devices for which (hopefully) reusable abstractions have been created.
All interfacing with hardware is done through static dispatch: hardware wrappers are to be passed in as copy-constructible
handle objects, with the objects themselves being specified as template parameters. This includes a static HAL object,
should the class need generic HAL features, like a delay function.
HAL Abstraction
---------------
As mentioned earlier, the HAL gets abstracted with simple wrapper classes that are passed into the consuming object as
template parameters. There's a special class to encapsulate more static HAL functions, such as delays, which is referred
to as the "static HAL" struct.
An example implementation of mildly fleshed out HAL abstraction can be found in the "Peripherals::Hal::St" namespace.
To make peripheral devices reusable and also configurable, the library composes all HAL-dependencies as templated members.
A few common interfaces have come from this, specifically the GPIO and Serial interfaces. Along with the static HAL itself.
Requirements for these common classes can be outlined as follows:
.. code-block:: c++
// A class for statically composing "static" features of a HAL.
struct StaticHal
{
static std::uint32_t getMillis();
static void delay(const std::uint32_t milliseconds);
static void delayUs(const std::uint32_t micros);
static void enableInterrupts();
static void disableInterrupts();
};
.. code-block:: c++
// A common serial interface, for sending and receiving data.
struct SerialInterface
{
bool transmit(std::uint8_t* data, const std::uint32_t data_len);
template<typename Td, std::size_t N>
bool transmit(std::array<Td, N>& array);
bool receive(std::uint8_t* data, const std::uint32_t data_len);
template<typename Td, std::size_t N>
bool receive(std::array<Td, N>& array);
};
.. code-block:: c++
// A common GPIO interface.
struct Gpio
{
void set(const bool& state);
void toggle();
bool read() const;
};
Usage Notes
-----------
It would be recommended to type-def whatever configurations you use, and go from there. The peripherals themselves should
be passed around as pointers or references -- they don't typically play well with copy constructors. And really, one object
per physical peripheral should exist.
It should also be noted that **constructors for peripherals can be non-trivial**. (Peripherals in this case being the non-HAL
ones.) They will do initialization in there, so that the object is usable right after successful construction. This means
you have to ensure that HAL setup is completed *before* the instances are constructed. Having them as global instances
will be affected by this, a quick fix is to just dynamically allocate them. Or use other means of deferred construction.
A few examples follow, based on the STM32 HAL.
Using a simple main, where the constructor is placed after all of the init functions:
.. code-block:: c++
#include <peripherals_button.hpp>
#include <peripherals_hal_st.hpp>
using Button
= Peripherals::Button<Peripherals::Hal::St::Gpio, Peripherals::Hal::St::StaticHal>;
void useButton(Button& b);
int main() {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
Button b1({ BTN_GPIO_Port, BTN_Pin, false });
while (true) {
useButton(b1);
}
}
Using a scenario where the class instance is kept as a global. In this case, dynamic allocation is used to achieve proper
constructor order:
.. code-block:: c++
#include <peripherals_button.hpp>
#include <peripherals_hal_st.hpp>
using Button
= Peripherals::Button<Peripherals::Hal::St::Gpio, Peripherals::Hal::St::StaticHal>;
Button* b1;
void useButton(Button& b);
int main() {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
b1 = new Button({ BTN_GPIO_Port, BTN_Pin, false });
while (true) {
useButton(*b1);
}
}
Using the StaticPointer class from the Utility module of this library is also a way of deferring the construction, while
maintaining complete static allocation:
.. code-block:: c++
#include <peripherals_button.hpp>
#include <peripherals_hal_st.hpp>
#include <utility_staticpointer.hpp>
using Button
= Peripherals::Button<Peripherals::Hal::St::Gpio, Peripherals::Hal::St::StaticHal>;
Utility::StaticPointer<Button> b1;
void useButton(Button& b);
int main() {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
b1.setup({ BTN_GPIO_Port, BTN_Pin, false });
while (true) {
useButton(*b1);
}
}
Public Members
--------------
.. doxygennamespace:: Peripherals
:content-only:
:undoc-members:
:members:

View File

@ -1,4 +0,0 @@
Threads Module
==============
asdasd

View File

@ -1,4 +0,0 @@
Utility Module
==============
szdf