Compare commits

..

1 Commits

Author SHA1 Message Date
Erki
82f62d1650 Initial commit
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-19 15:42:37 +03:00
60 changed files with 3277 additions and 1767 deletions

View File

@ -17,7 +17,7 @@ AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom BreakBeforeBraces: Custom
BraceWrapping: BraceWrapping:
AfterCaseLabel: true AfterCaseLabel: false
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: true BeforeCatch: false
BeforeElse: true BeforeElse: false
IndentBraces: false IndentBraces: false
SplitEmptyFunction: false SplitEmptyFunction: false
SplitEmptyRecord: true SplitEmptyRecord: true

29
.drone.yml Normal file
View File

@ -0,0 +1,29 @@
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

View File

@ -1,41 +0,0 @@
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,23 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR) cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
set(SKULLC_VERSION 0.1.0) set(version 0.1.0)
project(skullc project(skullc
VERSION ${SKULLC_VERSION} VERSION ${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/)
option(SKULLC_WITH_TESTS "Enable unit testing." OFF) set(CMAKE_CXX_STANDARD 17)
option(SKULLC_WITH_HAL "Enable the compiling and deployment of the HAL dependent sections." OFF) list(APPEND CMAKE_CXX_FLAGS "-Wall -Wextra")
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(WITH_DOCS "Enable building docs." OFF)
option(SKULLC_WITH_DOCS "Enable documentation building." OFF) option(WITH_TESTS "Enable unit testing." OFF)
option(WITH_HAL "Enable the compiling and deployment of the HAL dependent sections." OFF)
include(skullc-install) include(skullc-install)
@ -24,13 +25,13 @@ add_subdirectory(Peripherals)
add_subdirectory(Utility) add_subdirectory(Utility)
add_subdirectory(Messaging) add_subdirectory(Messaging)
if(SKULLC_WITH_TESTS) if(WITH_TESTS)
enable_testing() enable_testing()
add_subdirectory(Tests) add_subdirectory(Tests)
endif() endif()
if(SKULLC_WITH_DOCS) if(WITH_DOCS)
add_subdirectory(Docs) add_subdirectory(docs)
endif() endif()
## Install ## Install

View File

@ -1,53 +0,0 @@
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})

View File

@ -1,11 +0,0 @@
// 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@"

View File

@ -1,31 +0,0 @@
# 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"

View File

@ -1,23 +0,0 @@
.. 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`

View File

@ -1,17 +0,0 @@
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:

View File

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

View File

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

View File

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

View File

@ -8,6 +8,6 @@ target_include_directories(messaging
INTERFACE INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
) )
skullc_install_packages(skullc messaging ${SKULLC_VERSION}) skullc_install_packages(skullc messaging ${version})

View File

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

View File

@ -20,6 +20,17 @@ enum class ButtonPress : std::uint32_t
LONG_PRESS 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> template<typename G, typename H>
class Button class Button
{ {
@ -27,7 +38,7 @@ public:
using gpio = G; using gpio = G;
using hal = H; using hal = H;
gpio sw; gpio sw; ///< A switch instance.
static constexpr std::uint32_t TIMEOUT_SHORT_PRESS = 50; static constexpr std::uint32_t TIMEOUT_SHORT_PRESS = 50;
static constexpr std::uint32_t TIMEOUT_LONG_PRESS = 500; static constexpr std::uint32_t TIMEOUT_LONG_PRESS = 500;
@ -37,29 +48,27 @@ public:
: sw(sw) : sw(sw)
{} {}
/**
* Does the do.
*/
void update() void update()
{ {
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_)
{ {
if (time_held > TIMEOUT_SHORT_PRESS && time_held <= TIMEOUT_LONG_PRESS) 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)
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

@ -1,37 +0,0 @@
//
// 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

@ -1,51 +0,0 @@
//
// 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,10 +8,10 @@
#ifndef SKULLC_PERIPHERALS_HAL_ST_HPP_ #ifndef SKULLC_PERIPHERALS_HAL_ST_HPP_
#define SKULLC_PERIPHERALS_HAL_ST_HPP_ #define SKULLC_PERIPHERALS_HAL_ST_HPP_
#ifdef SKULLC_USE_HAL_ST
#include <main.h> #include <main.h>
#include "peripherals_utility.hpp"
#include <array> #include <array>
#define USE_DELAY_US #define USE_DELAY_US
@ -96,15 +96,9 @@ struct Gpio
}; };
#define CREATE_GPIO(name) \ #define CREATE_GPIO(name) \
Peripherals::Hal::St::Gpio \ Peripherals::Hal::St::Gpio { name##_GPIO_Port, name##_Pin, false }
{ \
name##_GPIO_Port, name##_Pin, false \
}
#define CREATE_INV_GPIO(name) \ #define CREATE_INV_GPIO(name) \
Peripherals::Hal::St::Gpio \ Peripherals::Hal::St::Gpio { name##_GPIO_Port, name##_Pin, true }
{ \
name##_GPIO_Port, name##_Pin, true \
}
#endif// HAL_GPIO_MODULE_ENABLED #endif// HAL_GPIO_MODULE_ENABLED
@ -268,30 +262,10 @@ 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, _Details::uartTransmit, HAL_UART_Receive>; SerialInterface<UART_HandleTypeDef, HAL_UART_Transmit, HAL_UART_Receive>;
using UartInterfaceDMA = using UartInterfaceDMA =
SerialInterfaceAsync<UART_HandleTypeDef, _Details::uartTransmitDma, SerialInterfaceAsync<UART_HandleTypeDef, HAL_UART_Transmit_DMA,
HAL_UART_Receive_DMA>; HAL_UART_Receive_DMA>;
#endif// HAL_UART_MODULE_ENABLED #endif// HAL_UART_MODULE_ENABLED
@ -345,8 +319,4 @@ 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,6 +12,7 @@
#include <limits> #include <limits>
#include "peripherals_imu.hpp" #include "peripherals_imu.hpp"
#include "peripherals_utility.hpp"
namespace Peripherals namespace Peripherals
{ {
@ -42,11 +43,7 @@ public:
registers_handle registers; registers_handle registers;
ImuIcm() = delete; 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 void setup() override
{ {
@ -71,13 +68,9 @@ public:
// rate = 1KHz, temp filter = 42 // rate = 1KHz, temp filter = 42
hal::delay(10); hal::delay(10);
const std::uint8_t new_gyro_conf = (std::uint32_t(scale_gyro_) << 3); setGyroscopeScale(scale_gyro_);
registers.writeRegister(Registers_::GYRO_CONFIG & Registers_::WRITE_MASK,
new_gyro_conf);
const std::uint8_t new_accel_config = (std::uint32_t(scale_accel_) << 3); setAccelerometerScale(scale_accel_);
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 // ACCEL_FCHOICE_B = 0, A_DLPF_CFG = 3 filter=44.8/61.5 rate=1KHz
registers.writeRegister(Registers_::ACCEL_CONFIG2 & Registers_::WRITE_MASK, registers.writeRegister(Registers_::ACCEL_CONFIG2 & Registers_::WRITE_MASK,
@ -103,43 +96,33 @@ public:
hal::delay(10); 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 void calibrate(const std::uint32_t samples) override
{ {
std::array<std::int32_t, 3> avg_gyro; std::array<std::int32_t, 3> avg_gyro;
std::array<std::int32_t, 3> avg_accel; 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++) for (std::uint32_t i = 0; i < samples; i++)
{ {
std::array<std::int16_t, 3> raw; 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()); readGyroRaw(raw.data());
for (std::uint32_t j = 0; j < 3; j++) add_to_avg(avg_gyro);
avg_gyro[j] += raw[j];
readAccelerometerRaw(raw.data()); readAccelerometerRaw(raw.data());
for (std::uint32_t j = 0; j < 3; j++) add_to_avg(avg_accel);
avg_accel[j] += raw[j];
hal::delay(2);
} }
for (std::uint32_t i = 0; i < 3; i++) for (std::uint32_t i = 0; i < 3; i++)
{ {
bias_gyro_[i] = avg_gyro[i] / std::int32_t(samples); const std::int32_t max = std::numeric_limits<std::int16_t>::max();
bias_accel_[i] = avg_accel[i] / std::int32_t(samples); 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_accel_[2] -= accelerometerReadingToRaw(1); bias_accel_[2] -= accelerometerReadingToRaw(1);
@ -175,9 +158,11 @@ public:
registers.readRegisterMultibyte( registers.readRegisterMultibyte(
Registers_::GYRO_XOUT_H | Registers_::READ_MASK, data, 6); Registers_::GYRO_XOUT_H | Registers_::READ_MASK, data, 6);
output[0] = gyroRawToReading(std::int16_t((data[0] << 8) | data[1]), 0); for (std::uint32_t i = 0; i < 3; i++)
output[1] = gyroRawToReading(std::int16_t((data[2] << 8) | data[3]), 1); {
output[2] = gyroRawToReading(std::int16_t((data[4] << 8) | data[5]), 2); const std::int16_t bit = byteToTypeBE<std::int16_t, 2>(&data[i * 2]);
output[i] = gyroRawToReading(bit);
}
} }
void readGyroRaw(std::int16_t* output) override void readGyroRaw(std::int16_t* output) override
@ -186,9 +171,10 @@ public:
registers.readRegisterMultibyte( registers.readRegisterMultibyte(
Registers_::GYRO_XOUT_H | Registers_::READ_MASK, data, 6); Registers_::GYRO_XOUT_H | Registers_::READ_MASK, data, 6);
output[0] = std::int16_t((data[0] << 8) | data[1]); for (std::uint32_t i = 0; i < 3; i++)
output[1] = std::int16_t((data[2] << 8) | data[3]); {
output[2] = std::int16_t((data[4] << 8) | data[5]); output[i] = byteToTypeBE<std::int16_t, 2>(&data[i * 2]);
}
} }
void readAccelerometer(float* output) override void readAccelerometer(float* output) override
@ -197,9 +183,11 @@ public:
registers.readRegisterMultibyte( registers.readRegisterMultibyte(
Registers_::ACCEL_XOUT_H | Registers_::READ_MASK, data, 6); Registers_::ACCEL_XOUT_H | Registers_::READ_MASK, data, 6);
output[0] = accelerometerRawToReading(std::int16_t((data[0] << 8) | data[1]), 0); for (std::uint32_t i = 0; i < 3; i++)
output[1] = accelerometerRawToReading(std::int16_t((data[2] << 8) | data[3]), 1); {
output[2] = accelerometerRawToReading(std::int16_t((data[4] << 8) | data[5]), 2); const std::int16_t bit = byteToTypeBE<std::int16_t, 2>(&data[i * 2]);
output[i] = accelerometerRawToReading(bit);
}
} }
void readAccelerometerRaw(std::int16_t* output) override void readAccelerometerRaw(std::int16_t* output) override
@ -208,10 +196,10 @@ public:
registers.readRegisterMultibyte( registers.readRegisterMultibyte(
Registers_::ACCEL_XOUT_H | Registers_::READ_MASK, data, 6); Registers_::ACCEL_XOUT_H | Registers_::READ_MASK, data, 6);
for (std::uint32_t i = 0; i < 3; i++)
output[0] = std::int16_t((data[0] << 8) | data[1]); {
output[1] = std::int16_t((data[2] << 8) | data[3]); output[i] = byteToTypeBE<std::int16_t, 2>(&data[i * 2]);
output[2] = std::int16_t((data[4] << 8) | data[5]); }
} }
std::int16_t accelerometerReadingToRaw(const float& fs) const std::int16_t accelerometerReadingToRaw(const float& fs) const
@ -224,14 +212,14 @@ public:
return fs / gyro_fs_to_bit_constants_[std::uint32_t(scale_gyro_)]; return fs / gyro_fs_to_bit_constants_[std::uint32_t(scale_gyro_)];
} }
float accelerometerRawToReading(const std::int16_t bit, const std::size_t axis) const float accelerometerRawToReading(const std::int16_t bit) const
{ {
return (float(bit - bias_accel_[axis]) * accel_fs_to_bit_constants_[std::uint32_t(scale_accel_)]) * 9.8f; return (float(bit) * accel_fs_to_bit_constants_[std::uint32_t(scale_accel_)]) * 9.8f;
} }
float gyroRawToReading(const std::int16_t bit, const std::size_t axis) const float gyroRawToReading(const std::int16_t bit) const
{ {
return float(bit - bias_gyro_[axis]) * gyro_fs_to_bit_constants_[std::uint32_t(scale_gyro_)]; return float(bit) * gyro_fs_to_bit_constants_[std::uint32_t(scale_gyro_)];
} }
std::uint8_t readProductId() std::uint8_t readProductId()
@ -243,6 +231,9 @@ private:
GyroScale scale_gyro_ = GyroScale::DPS_2000; GyroScale scale_gyro_ = GyroScale::DPS_2000;
AccelerometerScale scale_accel_ = AccelerometerScale::G16; 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] = { static constexpr float accel_fs_to_bit_constants_[4] = {
(2.0f / 32768.0f), (4.0f / 32768.0f), (8.0f / 32768.0f), (2.0f / 32768.0f), (4.0f / 32768.0f), (8.0f / 32768.0f),
(16.0f / 32768.0f)}; (16.0f / 32768.0f)};
@ -251,9 +242,6 @@ private:
(250.0f / 32768.0f), (500.0f / 32768.0f), (1000.0f / 32768.0f), (250.0f / 32768.0f), (500.0f / 32768.0f), (1000.0f / 32768.0f),
(2000.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_ struct Registers_
{ {
static constexpr std::uint32_t ICM20689_ID = 0x98; static constexpr std::uint32_t ICM20689_ID = 0x98;

View File

@ -54,8 +54,7 @@ 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);
@ -65,8 +64,7 @@ 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

@ -0,0 +1,72 @@
/*
* 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,16 +1,10 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR) cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
option(SKULLC_TESTS_WITH_SANITIZERS "Enable sanitizers for tests." ON) option(TESTS_WITH_SANITIZERS "Enable sanitizers for tests." ON)
include(FetchContent) find_package(Catch2 REQUIRED)
FetchContent_Declare(Catch2 if(TESTS_WITH_SANITIZERS)
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()
@ -24,14 +18,7 @@ add_executable(tests
rand.cpp rand.cpp
fixedpoint.cpp fixedpoint.cpp
pixelbuffer.cpp pixelbuffer.cpp
pixelbuffer_effects.cpp pixelbuffer_effects.cpp function.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
@ -41,12 +28,6 @@ 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)

View File

@ -1,68 +0,0 @@
//
// 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);
}

View File

@ -1,62 +0,0 @@
//
// 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,6 +109,7 @@ 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();
@ -120,16 +121,6 @@ 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);
}
} }
} }
} }

View File

@ -1,68 +0,0 @@
//
// 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);
}
}

View File

@ -1,41 +0,0 @@
//
// 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);
}

View File

@ -1,191 +0,0 @@
//
// 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,7 +5,6 @@
#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]")
{ {
@ -39,26 +38,6 @@ 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
@ -103,29 +82,3 @@ 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_actor_thread.hpp> #include "threads_primitivethread.hpp"
#include <threads_iactor.hpp> #include "threads_signal.hpp"
#include <threads_signal.hpp> #include "threads_timer.hpp"
namespace Threads namespace Threads
{ {
template<typename CRTP, typename... Ts> template<typename CRTP, typename... Ts>
class Actor : public IActor class Actor
{ {
public: public:
using value_type = std::variant<Ts...>; using value_type = std::variant<Ts...>;
@ -31,11 +31,14 @@ 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() : IActor(sizeof(value_type)), Actor(const std::uint32_t queue_size, const char* name, const osPriority_t priority, const std::uint32_t stack_size)
signal_(this) : thread_(&Actor<CRTP, Ts...>::threadHandler_, this, name, priority, stack_size),
{} msg_queue_(xQueueCreate(queue_size, sizeof(value_type))), signal_(this)
{
assert(msg_queue_);
}
~Actor() override {} virtual ~Actor() {}
template<typename T> template<typename T>
struct Signal : Signallable<T> struct Signal : Signallable<T>
@ -48,21 +51,20 @@ public:
: q_(q) : q_(q)
{} {}
Signal(const Signal&) = default;
Signal(Signal&&) = default;
Signal& operator=(const Signal&) = default;
Signal& operator=(Signal&&) = default;
void emit(const T& data) override void emit(const T& data) override
{ {
parent::value_type to_send = data; parent::value_type to_send = data;
q_->dispatchEvent(to_send); xQueueSend(q_->msg_queue_, &to_send, 0);
} }
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;
return q_->dispatchEventFromIsr(to_send); BaseType_t was_awoken = pdFALSE;
xQueueSendFromISR(q_->msg_queue_, &to_send, &was_awoken);
return was_awoken;
} }
private: private:
@ -77,25 +79,18 @@ public:
return Signal<U>(this); 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() Signallable<value_type>* getStaticSignal()
{ {
return &signal_; return &signal_;
} }
void dispatchSignal(const char* data) final protected:
{ 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...>;
@ -113,15 +108,113 @@ private:
} }
}; };
void dispatchImpl_(const char* data) void operator()()
{ {
const value_type* signal_data = reinterpret_cast<const value_type*>(data); init();
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

@ -1,117 +0,0 @@
/*
* 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

@ -1,50 +0,0 @@
/*
* 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,8 +65,7 @@ public:
if (!notified) if (!notified)
{ {
return std::nullopt; return std::nullopt;
} } else
else
{ {
return current_value_; return current_value_;
} }
@ -79,8 +78,7 @@ 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);
} }
@ -93,8 +91,7 @@ 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;
@ -161,8 +158,7 @@ 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

@ -1,68 +0,0 @@
/*
* 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,7 +11,6 @@
#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>

View File

@ -8,10 +8,7 @@
#ifndef THREADS_INC_THREADS_SIGNAL_HPP_ #ifndef THREADS_INC_THREADS_SIGNAL_HPP_
#define THREADS_INC_THREADS_SIGNAL_HPP_ #define THREADS_INC_THREADS_SIGNAL_HPP_
#include <type_traits>
#include <freertos_os2.h> #include <freertos_os2.h>
#include <threads_primitivethread.hpp>
namespace Threads namespace Threads
{ {

View File

@ -1,110 +0,0 @@
/*
* 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

@ -1,55 +0,0 @@
/*
* 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,9 +7,7 @@
#include "threads_primitivethread.hpp" #include "threads_primitivethread.hpp"
#include "utility_bytes.hpp" #include "peripherals_utility.hpp"
#include <limits>
#ifdef INCLUDE_xTaskGetCurrentTaskHandle #ifdef INCLUDE_xTaskGetCurrentTaskHandle
#define ASSERT_IS_CURRENT() \ #define ASSERT_IS_CURRENT() \
@ -101,14 +99,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(Utility::zeroInitialized<osThreadAttr_t>()) : thread_id(static_cast<osThreadId_t>(threadHandle)), attributes(Peripherals::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 = Utility::zeroInitialized<osThreadAttr_t>(); auto attrs = Peripherals::zeroInitialized<osThreadAttr_t>();
attrs.name = name; attrs.name = name;
attrs.priority = priority; attrs.priority = priority;

View File

@ -1,9 +1,13 @@
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}
) )
@ -14,9 +18,5 @@ 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 ${SKULLC_VERSION}) skullc_install_packages(skullc utility ${version})

View File

@ -1,33 +1,40 @@
// /*
// Created by erki on 29.06.22. * utility_assert.hpp
// *
* Created on: Jun 25, 2021
* Author: erki
*/
#ifndef SKULLC_UTILITY_ASSERT_HPP_ #ifndef UTILITY_INC_UTILITY_ASSERT_HPP_
#define SKULLC_UTILITY_ASSERT_HPP_ #define UTILITY_INC_UTILITY_ASSERT_HPP_
namespace Utility::Assert::Detail #ifdef __cplusplus
{ extern "C" {
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 #endif
#define SKULLC_ASSERT_SAFE(e) (!(e) ? Utility::Assert::Detail::assertImpl(#e, __FILE__, __LINE__) : ((void) 0)) __attribute__((weak)) void SkullC_AssertHandler(const char* file, int line, const char* func, const char* failed_expr)
namespace Utility::Assert
{ {
}
using assert_cb = void (*)(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();
#else
__disable_irq();
__asm("bkpt");
void setHandler(assert_cb callback); #ifdef SKULLC_UTILITY_ASSERT_CUSTOM_HANDLER
assert_cb getHandler(); SkullC_AssertHandler(file, line, func, failed_expr);
#endif
}// namespace Utility::Assert while (1)
;
#endif
}
#endif// SKULLC_UTILITY_ASSERT_HPP_ #ifdef __cplusplus
}
#endif
#endif /* UTILITY_INC_UTILITY_ASSERT_HPP_ */

View File

@ -1,69 +0,0 @@
//
// 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

@ -1,22 +0,0 @@
//
// 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

@ -1,124 +0,0 @@
//
// 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,8 +8,6 @@
#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
{ {
@ -21,19 +19,11 @@ 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>
@ -51,7 +41,7 @@ public:
explicit Function(signature callable) explicit Function(signature callable)
: callable_(callable) : callable_(callable)
{ {
SKULLC_ASSERT_DEBUG(callable_); assert(callable_);
} }
~Function() override ~Function() override
@ -83,7 +73,7 @@ public:
explicit FunctionOwned(source& src, signature callable) explicit FunctionOwned(source& src, signature callable)
: src_(&src), callable_(callable) : src_(&src), callable_(callable)
{ {
SKULLC_ASSERT_DEBUG(callable_); assert(callable_);
} }
~FunctionOwned() override ~FunctionOwned() override

View File

@ -1,49 +0,0 @@
//
// 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,18 +158,15 @@ 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;
@ -182,8 +179,7 @@ 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;
} }
@ -221,8 +217,7 @@ 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,8 +68,7 @@ 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,
@ -83,8 +82,7 @@ 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,
@ -204,8 +202,7 @@ public:
if (distance > 0) if (distance > 0)
{ {
return distance; return distance;
} } else
else
{ {
return head_ - tail_ + 1; return head_ - tail_ + 1;
} }

View File

@ -8,11 +8,8 @@
#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
{ {
@ -60,18 +57,6 @@ 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

@ -1,12 +0,0 @@
//
// 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

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

5
conanfile.txt Normal file
View File

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

44
docs/CMakeLists.txt Normal file
View File

@ -0,0 +1,44 @@
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})

2579
docs/Doxyfile.in Normal file

File diff suppressed because it is too large Load Diff

57
docs/conf.py Normal file
View File

@ -0,0 +1,57 @@
# 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"

50
docs/index.rst Normal file
View File

@ -0,0 +1,50 @@
.. 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

161
docs/peripherals.rst Normal file
View File

@ -0,0 +1,161 @@
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:

4
docs/threads.rst Normal file
View File

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

4
docs/utility.rst Normal file
View File

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