From db6bafafb9c130be1b3d9c5d12ede75cf83716a2 Mon Sep 17 00:00:00 2001 From: wispl Date: Sun, 16 Aug 2026 16:34:30 -0400 Subject: [PATCH 1/6] feat: use c++ Discussed internally, this is a rather large change. --- .clang-format | 14 + .github/workflows/ci.yml | 34 +- CMakeLists.txt | 107 +++-- README.md | 2 +- include/CMakeLists.txt | 6 + include/defs.h | 56 --- include/flash.h | 20 - include/flash/flash.h | 80 ++++ include/flash/gd5f1gq5xe.h | 16 +- include/hal.h | 16 + include/protocols/i2c.h | 30 ++ include/protocols/protocol.h | 143 +++++++ include/protocols/qspi.h | 57 +++ include/protocols/spi.h | 41 ++ include/protocols/uart.h | 26 ++ include/sensor.h | 44 -- include/sensors/CD-PA1616S.h | 25 -- include/sensors/bmi088.h | 31 +- include/sensors/bmp581.h | 33 +- include/sensors/cd-pa1616s.h | 33 ++ include/sensors/sensor.h | 56 +++ project.yml | 411 ------------------- src/CMakeLists.txt | 3 + src/flash.c | 99 ----- src/flash/CMakeLists.txt | 4 + src/flash/flash.cpp | 97 +++++ src/flash/gd5f1gq5xe.c | 329 --------------- src/flash/gd5f1gq5xe.cpp | 267 ++++++++++++ src/protocols/CMakeLists.txt | 33 ++ src/protocols/i2c.cpp | 46 +++ src/protocols/qspi.cpp | 98 +++++ src/protocols/spi.cpp | 41 ++ src/protocols/uart.cpp | 19 + src/sensors/CMakeLists.txt | 5 + src/sensors/bmi088.c | 236 ----------- src/sensors/bmi088.cpp | 176 ++++++++ src/sensors/bmp581.c | 190 --------- src/sensors/bmp581.cpp | 145 +++++++ src/sensors/{CD-PA1616S.c => cd-pa1616s.cpp} | 89 ++-- test/sensors/test_bmp581.c | 116 ------ test/support/stub_hal.h | 25 -- tests/CMakeLists.txt | 11 + tests/mocks/CMakeLists.txt | 1 + tests/mocks/protocol_mock.h | 17 + tests/sensors/CMakeLists.txt | 1 + tests/sensors/bmp581_test.cpp | 135 ++++++ 46 files changed, 1781 insertions(+), 1683 deletions(-) create mode 100644 .clang-format create mode 100644 include/CMakeLists.txt delete mode 100644 include/defs.h delete mode 100644 include/flash.h create mode 100644 include/flash/flash.h create mode 100644 include/hal.h create mode 100644 include/protocols/i2c.h create mode 100644 include/protocols/protocol.h create mode 100644 include/protocols/qspi.h create mode 100644 include/protocols/spi.h create mode 100644 include/protocols/uart.h delete mode 100644 include/sensor.h delete mode 100644 include/sensors/CD-PA1616S.h create mode 100644 include/sensors/cd-pa1616s.h create mode 100644 include/sensors/sensor.h delete mode 100644 project.yml create mode 100644 src/CMakeLists.txt delete mode 100644 src/flash.c create mode 100644 src/flash/CMakeLists.txt create mode 100644 src/flash/flash.cpp delete mode 100644 src/flash/gd5f1gq5xe.c create mode 100644 src/flash/gd5f1gq5xe.cpp create mode 100644 src/protocols/CMakeLists.txt create mode 100644 src/protocols/i2c.cpp create mode 100644 src/protocols/qspi.cpp create mode 100644 src/protocols/spi.cpp create mode 100644 src/protocols/uart.cpp create mode 100644 src/sensors/CMakeLists.txt delete mode 100644 src/sensors/bmi088.c create mode 100644 src/sensors/bmi088.cpp delete mode 100644 src/sensors/bmp581.c create mode 100644 src/sensors/bmp581.cpp rename src/sensors/{CD-PA1616S.c => cd-pa1616s.cpp} (65%) delete mode 100644 test/sensors/test_bmp581.c delete mode 100644 test/support/stub_hal.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/mocks/CMakeLists.txt create mode 100644 tests/mocks/protocol_mock.h create mode 100644 tests/sensors/CMakeLists.txt create mode 100644 tests/sensors/bmp581_test.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b003f0c --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +BasedOnStyle: LLVM +IndentWidth: 4 + +AccessModifierOffset: -4 +Language: Cpp + +DerivePointerAlignment: false +PointerAlignment: Left + +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: true + AcrossComments: true + AlignCaseColons: false \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2093919..85b84db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,28 +14,24 @@ jobs: - name: Checkout code uses: actions/checkout@v6 - - name: Install Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '4.0' - - - name: Install Build Tools and Ceedling + - name: Install Build Tools run: | sudo apt-get update - sudo apt-get install -y gcc cmake ninja-build gcovr - gem install ceedling + sudo apt-get install -y gcc cmake ninja-build + pip install gcovr - - name: Fetch Dependencies - run: cmake -B build + - name: Configure cmake and run builds + run: | + cmake -B build -S . + cmake --build build - - name: Run Tests - run: ceedling gcov:all + - name: Run Tests and create coverage + run: | + cmake --build build --target test + cmake --build build --target coverage - - name: Upload Coverage - uses: actions/upload-artifact@v7 + - name: Render Coverage Summary in CI if: always() - with: - name: code-coverage-report - path: build/ceedling/artifacts/gcov/gcovr/GcovCoverageResults.html - archive: false - retention-days: 7 + run: | + echo "## Code Coverage Summary" >> $GITHUB_STEP_SUMMARY + gcovr -r . -e "tests/" -e "build/" --markdown >> $GITHUB_STEP_SUMMARY diff --git a/CMakeLists.txt b/CMakeLists.txt index aceb612..1a04ea1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ cmake_minimum_required(VERSION 3.22) -enable_language(C) +project(CommonDrivers) +enable_language(C CXX) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) include(FetchContent) @@ -13,14 +17,6 @@ FetchContent_Declare(bmi08 GIT_TAG master ) FetchContent_MakeAvailable(bmi08) -add_library(bmi08 STATIC - ${bmi08_SOURCE_DIR}/bmi08a.c - ${bmi08_SOURCE_DIR}/bmi08g.c - ${bmi08_SOURCE_DIR}/bmi08xa.c - ${bmi08_SOURCE_DIR}/bmi088_mma.c - ${bmi08_SOURCE_DIR}/bmi088_anymotiona.c -) -target_include_directories(bmi08 PUBLIC ${bmi08_SOURCE_DIR}) message(STATUS "Resolving bmp5 sensors api dependency...") FetchContent_Declare(bmp5 @@ -29,8 +25,6 @@ FetchContent_Declare(bmp5 GIT_TAG master ) FetchContent_MakeAvailable(bmp5) -add_library(bmp5 STATIC ${bmp5_SOURCE_DIR}/bmp5.c) -target_include_directories(bmp5 PUBLIC ${bmp5_SOURCE_DIR}) message(STATUS "Resolving littlefs dependency...") FetchContent_Declare(littlefs @@ -39,32 +33,79 @@ FetchContent_Declare(littlefs GIT_TAG master ) FetchContent_MakeAvailable(littlefs) -add_library(littlefs STATIC - ${littlefs_SOURCE_DIR}/lfs.c - ${littlefs_SOURCE_DIR}/lfs_util.c -) -target_include_directories(littlefs PUBLIC ${littlefs_SOURCE_DIR}) - -message(STATUS "Dependencies resolved, now creating library...") -add_library(common_drivers STATIC - "${CMAKE_CURRENT_SOURCE_DIR}/src/flash.c" - "${CMAKE_CURRENT_SOURCE_DIR}/src/flash/gd5f1gq5xe.c" - "${CMAKE_CURRENT_SOURCE_DIR}/src/sensors/bmp581.c" - "${CMAKE_CURRENT_SOURCE_DIR}/src/sensors/bmi088.c" - "${CMAKE_CURRENT_SOURCE_DIR}/src/sensors/CD-PA1616S.c" + +message(STATUS "Resolving gtest dependency...") +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.18.0 ) -target_include_directories(common_drivers PUBLIC - "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_SOURCE_DIR}/include/flash" - "${CMAKE_CURRENT_SOURCE_DIR}/include/sensors" +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +message(STATUS "Resolving fff dependency...") +FetchContent_Declare( + fff + GIT_REPOSITORY https://github.com/meekrosoft/fff.git + GIT_TAG master ) -message(STATUS "Linking libary to dependencies...") -target_link_libraries(common_drivers PUBLIC bmi08 bmp5 littlefs) +add_library(common_drivers STATIC) +if(TARGET stm32cubemx) + message(STATUS "Compiling for target host (MCU), linking libraries...") + + add_library(bmi08 STATIC + ${bmi08_SOURCE_DIR}/bmi08a.c + ${bmi08_SOURCE_DIR}/bmi08g.c + ${bmi08_SOURCE_DIR}/bmi08xa.c + ${bmi08_SOURCE_DIR}/bmi088_mma.c + ${bmi08_SOURCE_DIR}/bmi088_anymotiona.c + ) + target_include_directories(bmi08 PUBLIC ${bmi08_SOURCE_DIR}) + + add_library(bmp5 STATIC ${bmp5_SOURCE_DIR}/bmp5.c) + target_include_directories(bmp5 PUBLIC ${bmp5_SOURCE_DIR}) + + add_library(littlefs STATIC + ${littlefs_SOURCE_DIR}/lfs.c + ${littlefs_SOURCE_DIR}/lfs_util.c + ) + target_include_directories(littlefs PUBLIC ${littlefs_SOURCE_DIR}) + + message(STATUS "Linking dependencies to library...") + target_link_libraries(common_drivers PUBLIC bmi08 bmp5 littlefs) -if(NOT TARGET stm32cubemx) - # Empty for now, will add stuff for tests later -else() message(STATUS "Linking stm32 drivers to library....") target_link_libraries(common_drivers PUBLIC stm32cubemx) +else() + enable_testing() + message(STATUS "Compiling for tests, linking gtest, fff, and mocks to library...") + FetchContent_MakeAvailable(googletest) + FetchContent_MakeAvailable(fff) + + add_library(bmp5 INTERFACE) + target_include_directories(bmp5 INTERFACE ${bmp5_SOURCE_DIR}) + + add_library(littlefs INTERFACE) + target_include_directories(littlefs INTERFACE ${littlefs_SOURCE_DIR}) + + add_library(bmi08 INTERFACE) + target_include_directories(bmi08 INTERFACE ${bmi08_SOURCE_DIR}) + + message(STATUS "Linking mock dependencies to library...") + target_link_libraries(common_drivers PUBLIC bmp5 littlefs bmi08) + + message(STATUS "Creating tests for library...") + add_compile_definitions(TEST) + add_subdirectory(tests) endif() + +message(STATUS "Adding source files and headers to library...") +add_subdirectory(include) +add_subdirectory(src) + +add_custom_target(coverage + COMMAND mkdir -p coverage + COMMAND gcovr -r ${CMAKE_SOURCE_DIR} -e "${CMAKE_SOURCE_DIR}/tests/" -e "${CMAKE_BINARY_DIR}/" --html-details -o coverage/index.html . + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) diff --git a/README.md b/README.md index cc8853a..feb3f83 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ To write a sensor, you must initialize a struct of the form ```c struct sensor { - bool (*read)(void*, struct packet*); + bool (*read)(void*, Packet*); void* ctx; }; ``` diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..d1c2f37 --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,6 @@ +target_include_directories(common_drivers PUBLIC + "." + "sensors" + "protocols" + "flash" +) diff --git a/include/defs.h b/include/defs.h deleted file mode 100644 index 648dc8d..0000000 --- a/include/defs.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef DEFS_H -#define DEFS_H - -#ifdef TEST -// Mark functions you want to unit test with STATIC. We expose everything -// through `struct sensor` and `struct flash` to avoid leaking implmentation. -// -// Of course we still need to test the read function so we use the STATIC hack -// It is static in production, but non static when running tests. -#define STATIC -// Stub HAL file to satisfy compilation during tests, eventually we might write -// and use a thin abstraction on top of the HAL. -#include "stub_hal.h" -#else -// Make static work as usual -#define STATIC static -// TODO: Abstracts over hal series -#ifdef USE_STM32_H7XX -#include "stm32h7xx_hal.h" -#elif USE_STM32_L4XX -#include "stm32l4xx_hal.h" -#else -#include "stm32f4xx_hal.h" -#endif -#endif // end TEST - -/// Common abstraction over SPI, UART, I2C -/// Use this handle struct when a sensor could be configured to use more than of -/// the protocols or if the sensor uses a protocol that might be disabled, like -/// UART or I2C. This helps isolate ifdefs to only implementation files. -enum protocol { SPI, UART, I2C }; -struct handle { - enum protocol protocol; - union { -#ifdef HAL_I2C_MODULE_ENABLED - struct handle_i2c { - I2C_HandleTypeDef *handle; - uint32_t address; - } i2c; -#endif - // We always have spi present, so we don't have to gate it - struct handle_spi { - SPI_HandleTypeDef *handle; - GPIO_TypeDef *port; - uint8_t pin; - } spi; -#ifdef HAL_UART_MODULE_ENABLED - struct handle_uart { - UART_HandleTypeDef *handle; - } uart; -#endif - } def; -}; - - -#endif // end DEFS_H diff --git a/include/flash.h b/include/flash.h deleted file mode 100644 index f7cec3c..0000000 --- a/include/flash.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef FLASH_H -#define FLASH_H - -#include "lfs.h" - -#include - -struct flash { - struct lfs_config config; - lfs_t lfs; -}; - -uint32_t flash_mount(struct flash *flash); -int flash_unmount(struct flash *flash); -uint32_t flash_boot_count(struct flash *flash, bool update); -uint32_t flash_open(struct flash *flash, lfs_file_t *file, const char *filename); -bool flash_append(struct flash *flash, lfs_file_t *file, const uint8_t *bytes, size_t size); -int flash_close(struct flash *flash, lfs_file_t *file); - -#endif diff --git a/include/flash/flash.h b/include/flash/flash.h new file mode 100644 index 0000000..a6ecc19 --- /dev/null +++ b/include/flash/flash.h @@ -0,0 +1,80 @@ +#ifndef FLASH_H +#define FLASH_H + +#include "lfs.h" + +#include + +/// A lightweight LittleFS wrapper. We mainly deal with NAND flashes, though Nor +/// flashes are possible as well. You can find differences between the two +/// online but generally NOR is better for reads while NAND is better for +/// writes. There are also differences regarding power efficiency but that is +/// not too relevant for us. +/// +/// When constructing a class which subclasses from this, you must fill out the +/// lfs_config instance. Then You must implement the `init` method, in which you +/// must startup actions such as +/// +/// * checking for the id of the device +/// * unlocking the device for writes (if needed) +/// * unlocking the device for features like QSPI (if needed) +/// +/// Then you must call `mount` to mount the filesystem. Only after that may you +/// use other functions like `bootcount` or `append`. It is a good idea to call +/// `unmount` when you no longer need to write the the flash. +namespace Platform { +class Flash { +protected: + struct lfs_config config; + lfs_t lfs; // Don't touch this, this is managed by lfs itself + +public: + virtual ~Flash() = default; + + /// @brief initialize the flash + /// @return true if success, false if error + virtual bool init() = 0; + + /// @brief mounts the filesystem + /// + /// Must be called after `init` and before you use any of the other + /// functions. If the size is negative then it is a good indicator that + /// something is wrong. + /// + /// @return the size of the filesystem + uint32_t mount(); + + /// @brief unmounts the filesystem + /// + /// Closes all files and releases all resources. + /// + /// @return lfs status code + uint32_t unmount(); + + /// @brief gets or increments the bootcount + /// + /// @param update if true, increments the bootcount by one + /// @return lfs status code + uint32_t bootcount(bool update); + + /// @brief opens a file, creating it if it doesn't exist + /// @param file the lfs file to open/create + /// @param file_name the name of the file to open/create + /// @return lfs status code + uint32_t open(lfs_file_t* file, const char* filename); + + /// @brief closes the given file + /// @param file the opened lfs file to close + /// @return lfs status code + uint32_t close(lfs_file_t* file); + + /// @brief appends bytes to the given file (must be open) + /// @param file an opened lfs file + /// @param bytes buffer of bytes to append to the file + /// @param size size of the buffer + /// @return true on success, false on error + bool append(lfs_file_t* file, const uint8_t* bytes, size_t size); +}; +} // namespace Platform + +#endif diff --git a/include/flash/gd5f1gq5xe.h b/include/flash/gd5f1gq5xe.h index 0e7feb9..c52e37e 100644 --- a/include/flash/gd5f1gq5xe.h +++ b/include/flash/gd5f1gq5xe.h @@ -2,11 +2,21 @@ #define GD5F1GQ5XE_H #include "flash.h" -#include "defs.h" +#include "hal.h" +#include "protocol.h" -#include #include +#include + +namespace Platform { +class GD5F1GQ5XE final : public Flash { +private: + Protocol& protocol; -bool gd5f1gq5xe_init(struct flash *flash, struct handle_spi *spi); +public: + GD5F1GQ5XE(Protocol& protocol_); + bool init() override; +}; +} // namespace Platform #endif diff --git a/include/hal.h b/include/hal.h new file mode 100644 index 0000000..535145b --- /dev/null +++ b/include/hal.h @@ -0,0 +1,16 @@ +#ifndef HAL_H +#define HAL_H + +#ifndef TEST +#ifdef USE_STM32_H7XX +#include "stm32h7xx_hal.h" +#elif USE_STM32_L4XX +#include "stm32l4xx_hal.h" +#else +#include "stm32f4xx_hal.h" +#endif +#else +#define HAL_Delay(...) (0) +#endif + +#endif diff --git a/include/protocols/i2c.h b/include/protocols/i2c.h new file mode 100644 index 0000000..4e23e3a --- /dev/null +++ b/include/protocols/i2c.h @@ -0,0 +1,30 @@ +#ifndef PROTOCOLS_I2C_H +#define PROTOCOLS_I2C_H + +#include "protocol.h" + +/// I2C tends to be register and address heavy. This really isn't really used +/// for command based sensors so the cmd buffer tends to just be the address +/// of the register to read from instead. +/// +/// The AddressSize gets directly converted to I2C's equivalent. Note that +/// unlike qspi, I2C does not support addresses greater than two bytes. +/// We use the `mem` equivalents of I2C because "most" sensors seem to +/// to prefer the pattern of specifying an address to target, followed +/// by a write or read. The `mem` I2C functions do this in one step instead +/// of two. This contrasts with SPI where we are forced to do it in two. +namespace Platform { +class I2C final : public Protocol { +private: + I2C_HandleTypeDef* handle; + const uint32_t address; + +public: + I2C(I2C_HandleTypeDef* handle_, uin32_t address_) + : Protocol{ProtocolType::I2C}, handle{handle_}, address{address_} {} + bool read(ConstSpan cmd, Span buffer, AddressSize size) override; + bool write(ConstSpan cmd, ConstSpan buffer, AddressSize size) override; +}; +} // namespace Platform + +#endif diff --git a/include/protocols/protocol.h b/include/protocols/protocol.h new file mode 100644 index 0000000..3a350ed --- /dev/null +++ b/include/protocols/protocol.h @@ -0,0 +1,143 @@ +#ifndef PROTOCOLS_PROTOCOL_H +#define PROTOCOLS_PROTOCOL_H + +#include +#include + +/// This is our thin abstraction over the HAL. We implement a base `Protocol` +/// class which protocols like I2C, QSPI, SPI, and UART inherit from. Then +/// throughout the sensor code, we will use the `protocol.read()` and +/// `protocol.write()` methods instead. See the documentation below +/// for more information on how to implement them. And see the documentation +/// in `i2c.h`, `qspi.h`, `spi.h`, and `uart.h` for further information +/// on their implementations . +/// +/// The Protocol class was designed following the assumptions that it is going +/// to only be used for sensor and flash devices. The behavior of these devices +/// can be roughly categorized into three types +/// +/// * Register based: these sensors require you first send a register address, +/// then writes will write to that register while reads will +/// read from that register +/// * Protocol based: these sensors require you to just send some form of +/// standardized or in-house messaging format +/// * Command based: usually more complex devices like flash, they are like +/// register based, but instead requires you to send some +/// form of op code, followed by addresses or arguments. +/// +/// One can argue that command-based is a special case of register based, but +/// instead taking arguments in addition to the initial address. The bosch +/// sensors are examples of register based while the gps (cd-pa1616s) is an +/// example of protocol based. Most flash devices will be command based. +/// +/// Command based devices tend to also allow QSPI, which if you look at its +/// implementation actually has commands baked into its core. The complex +/// devices tend to require dummy cycles. You should specify dummy cycles using +/// empty bytes `0x00`, look at the spreadsheets to see how many you need. +namespace Platform { + +/// Span --- +/// A quick primer on span, it just a regular C buffer but it also +/// includes the size, really convenient, we will be using this a lot +using Span = std::span; +using ConstSpan = std::span; + +/// The type of the protocol, used by `Protocol` when specific protocols have +/// different code paths. This is common for QSPI where extra configuration +/// is often neccessary. +enum class ProtocolType { SPI, UART, I2C, QSPI }; + +/// The size/width of the address. Some protocols like I2C and QSPI do require +/// this information so it is good to supply it. `Byte1` corresponds to an +/// adress with 8 bits, `Byte2` an address with 16 bits and so on. You may use +/// `None` if no address is sent. +enum class AddressSize : std::size_t { + None = 0, + Byte = 1, + Byte2 = 2, + Byte3 = 3, + Byte4 = 4 +}; + +/// Some protocols require configuration, use this with the `configure` method +/// as a super lightweight method to configure them. In each subclass, make sure +/// you handle the config accordingly. The config can be changed at run time. +enum class Config { + QSPI_Data1, // QSPI receive data across one line + QSPI_Data4, // QSPI receive data across four lines + QSPI_Address1, // QSPI send address across one line + QSPI_Address4 // QSPI send address across four lines +}; + +/// Protocol --- +/// Every peripheral (SPI, I2C, UART, etc...) inherits from this base +/// instance. They MUST implment two methods, `read` and `write` +/// A couple of notes on implmentation (look in respective files for more info) +/// cmd: sends the address or register to perform the action on, or a command +/// code buffer: the buffer to read into or write into dummy_cycles: only used +/// by QSPI +/// For writes, you may set size of the buffer to zero to not transmit +/// anything. This is more relevant for flash which might just need to +/// send a single command and not receive anything afterwards. +class Protocol { +protected: + ProtocolType ptype; + +public: + /// @brief Initializes the Protocol + /// + /// Subclasses must use this constructor to define their type + /// + /// @param type must be one of SPI, UART, I2C, QSPI + explicit Protocol(ProtocolType type) : ptype{type} {} + virtual ~Protocol() = default; + + /// @brief Reads from the protocol after sending cmd + /// + /// The cmd buffer may include an command as its first element + /// or an address, handle accordingly based on the protocol. You + /// must handle dummy bytes and clocks accordingly as well. + /// + /// @param cmd span holding the address/cmd to send + /// @param buffer buffer to read into + /// @param address_size size of the address + /// @return true on error, false on success + virtual bool read(ConstSpan cmd, Span buffer, AddressSize size) = 0; + + /// @brief Writes using the protocol after sending cmd + /// + /// The cmd buffer may include an command as its first element + /// or an address, handle accordingly based on the protocol. You + /// must handle dummy bytes and clocks accordingly as well. + /// + /// @param cmd span holding the address/cmd to send + /// @param buffer buffer to write from, set to `{}` to write + /// nothing + /// @param address_size size of the address + /// @return true on error, false on success + virtual bool write(ConstSpan cmd, ConstSpan buffer, AddressSize size) = 0; + + /// @brief Configures the given protocol using the given config + /// + /// All subclasses must ignore the config if they don't + /// recognize it, otherwise they must handle the config + /// accordingly. + /// + /// @param config config object + /// @return true on error, false on success + virtual bool configure(Config config) { return false; }; + + /// @brief non-blocking read using DMA + /// + /// Not currently implemented for all protocols, you should read the + /// relevant protocol's documentation for how to use this as it differs + /// between them. + virtual bool readDMA(Span buffer) { return true; }; + + /// @brief returns the type of the protocol + /// @return protocol type + ProtocolType type() const { return ptype; } +}; +} // namespace Platform + +#endif // PROTOCOLS_PROTOCOL_H diff --git a/include/protocols/qspi.h b/include/protocols/qspi.h new file mode 100644 index 0000000..34b849e --- /dev/null +++ b/include/protocols/qspi.h @@ -0,0 +1,57 @@ +#ifndef PROTOCOLS_QSPI_H +#define PROTOCOLS_QSPI_H + +#include "hal.h" +#include "protocol.h" + +/// By far the most complicated protocol. This one enforces a command based +/// communication system. You need to fill out a `QSPI_CommandTypeDef` and +/// send it before you are able to do anything. With this you can really +/// see the command and address separation. There are several important partsjko +/// +/// 1. Command: this is the op code to send +/// 2. Address: address (argument), make sure to set the appropriate +/// AddressSize, +/// you can also configure how many lines to send the address +/// over +/// 3. Data: how to receive the data, you also need to set the data buffer +/// and size of course, and how many line to get/write data over +/// +/// You can also configure the dummy cycles as well as alternate byte mode. +/// The dummy cycle is more important; I haven't seen any usage of the alternate +/// byte mode yet. Sometimes you can only recieve data over a single line, so +/// a method is provided in order to configure the dataMode, usually at startup +/// only. You can do so using `configure` method with the appropriate `Config`. +namespace Platform { +class QSPI final : public Protocol { +private: + QSPI_HandleTypeDef* handle; + uint32_t data_mode = QSPI_DATA_4_LINES; + uint32_t address_mode = QSPI_ADDRESS_1_LINE; + +public: + QSPI(QSPI_HandleTypeDef* handle_) + : Protocol{ProtocolType::QSPI} handle{handle_} {} + bool read(ConstSpan cmd, Span buffer, AddressSize size) override; + bool write(ConstSpan cmd, ConstSpan buffer, AddressSize size) override; + bool configure(Config config) override { + switch (config) { + case Config::QSPI_Data1: + data_mode = QSPI_DATA_1_LINE; + return false; + case Config::QSPI_Data4: + data_mode = QSPI_DATA_4_LINES; + return false; + case Config::QSPI_Address1: + address_mode = QSPI_ADDRESS_1_LINE; + return false; + case Config::QSPI_Address4: + address_mode = QSPI_ADDRESS_4_LINES; + return false; + } + return true; + }; +}; +} // namespace Platform + +#endif diff --git a/include/protocols/spi.h b/include/protocols/spi.h new file mode 100644 index 0000000..855e0e8 --- /dev/null +++ b/include/protocols/spi.h @@ -0,0 +1,41 @@ +#ifndef PROTOCOLS_SPI_H +#define PROTOCOLS_SPI_H + +#include "hal.h" +#include "protocol.h" + +/// SPI generally doesn't care too much about your underlying device. It can +/// handle both register and command based devices well. For Register based, you +/// generally must transmit the register address, and then you may read from or +/// write to it. For command based devices, you may group the command with +/// any arguments you want and then send the command buffer. You can then choose +/// to read or write or do nothing based on the command. Note that you must do a +/// chip select before you are able to communicate anything. This is handled for +/// you already by this implementation so you don't have to worry about it. +/// +/// SPI doesn't really care about the address size, it just sends whatever it +/// wants. Kind of demure isn't it? Hence in our implementations you may just +/// ignore the `AddressSize`, but it is good idea to still specify it since the +/// other protocols do the same. To send dummy bytes you should do this +/// +/// auto cmd_buffer = { my_command_code, some_parameters, 0x00 }; +/// +/// The 0x00 is eight bytes of dummy. SPI will just send it as is and everything +/// should work well. +namespace Platform { +class SPI final : public Protocol { +private: + SPI_HandleTypeDef* handle; + GPIO_TypeDef* port; + uint8_t pin; + +public: + SPI(SPI_HandleTypeDef* handle_, GPIO_TypeDef* port_, uint8_t pin_) + : Protocol{ProtocolType::SPI}, handle{handle_}, port{port_}, pin{pin_} { + } + bool read(ConstSpan cmd, Span buffer, AddressSize size) override; + bool write(ConstSpan cmd, ConstSpan buffer, AddressSize size) override; +}; +} // namespace Platform + +#endif diff --git a/include/protocols/uart.h b/include/protocols/uart.h new file mode 100644 index 0000000..f343842 --- /dev/null +++ b/include/protocols/uart.h @@ -0,0 +1,26 @@ +#ifndef PROTOCOLS_UART_H +#define PROTOCOLS_UART_H + +#include "protocol.h" + +/// Arguably the simplest protocol. UART just sends stuff, much like SPI, but +/// you don't have to deal with chip selects with UART, so it is even more fire +/// and forget. Since we don't have any usages of a UART sensor transmitting +/// commands, this current implementation just writes and reads directly, +/// with no intermediate step. The `cmd` buffer is ignored, just write +/// using the buffer. +namespace Platform { +class UART final : public Protocol { +private: + UART_HandleTypeDef* handle; + +public: + UART(UART_HandleTypeDef* handle_) + : Protocol{ProtocolType::UART}, handle{handle_} {} + bool read(ConstSpan cmd, Span buffer, AddressSize size) override; + bool write(ConstSpan cmd, ConstSpan buffer, AddressSize size) override; + bool readDMA(Span buffer) override; +}; +} // namespace Platform + +#endif diff --git a/include/sensor.h b/include/sensor.h deleted file mode 100644 index a6afa78..0000000 --- a/include/sensor.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef SENSOR_H -#define SENSOR_H - -#include -#include - -struct __attribute__((packed)) packet { - int16_t magic; // 2 bytes - uint32_t status; // 4 bytes - uint32_t time_us; // 4 bytes - float main_voltage_v; // 4 bytes - float pyro_voltage_v; // 4 bytes - uint8_t numSatellites; // 1 byte - uint8_t gpsFixType; // 1 byte 0 = no fix, 1 = fix, 2 = DGPS fix, etc. - float latitude_degrees; // 4 bytes - float longitude_degrees; // 4 bytes - float gps_hMSL_m; // 4 bytes altitude above mean sea level - float barometer_hMSL_m; // 4 bytes - float temperature_c; // 4 bytes - float acceleration_x_mss; // 4 bytes - float acceleration_y_mss; // 4 bytes - float acceleration_z_mss; // 4 bytes - float angular_velocity_x_rads; // 4 bytes - float angular_velocity_y_rads; // 4 bytes - float angular_velocity_z_rads; // 4 bytes - float gauss_x; // 4 bytes - float gauss_y; // 4 bytes - float gauss_z; // 4 bytes - float kf_acceleration_mss; // 4 bytes - float kf_velocity_ms; // 4 bytes - float kf_position_m; // 4 bytes - float w; // 4 bytes - float x; // 4 bytes - float y; // 4 bytes - float z; // 4 bytes - uint32_t checksum; // 4 bytes -}; - -struct sensor { - bool (*read)(void*, struct packet*); - void* ctx; -}; - -#endif // SENSOR_H diff --git a/include/sensors/CD-PA1616S.h b/include/sensors/CD-PA1616S.h deleted file mode 100644 index 8fc1c3a..0000000 --- a/include/sensors/CD-PA1616S.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * CD-PA1616S.h - * - * Created on: Feb 21, 2025 - * Author: Mahir Shah - */ - -#ifndef INC_CD_PA1616S_H_ -#define INC_CD_PA1616S_H_ - -#include "defs.h" -#include "sensor.h" - -#include -#include - -#define BUFFER_SIZE 128 - -struct gps_ctx { - uint8_t buffer[BUFFER_SIZE]; // used for DMA reception buffer - struct handle handle; -}; -bool gps_init(struct gps_ctx *ctx, struct sensor *sensor); - -#endif /* INC_CD_PA1616S_H_ */ diff --git a/include/sensors/bmi088.h b/include/sensors/bmi088.h index 51f45db..39235f6 100644 --- a/include/sensors/bmi088.h +++ b/include/sensors/bmi088.h @@ -5,20 +5,29 @@ * Author: Dhruv Shah */ -#ifndef INC_BMI088_H_ -#define INC_BMI088_H_ +#ifndef SENSORS_BMI088_H +#define SENSORS_BMI088_H -#include "defs.h" -#include "sensor.h" #include "bmi08_defs.h" +#include "sensor.h" + +#include -#include +namespace Platform { +class BMI088 final : public Sensor { +private: + struct bmi08_dev dev; + Protocol& accel; + Protocol& gyro; -struct bmi088_ctx { - struct bmi08_dev dev; - struct handle_spi accel_spi; - struct handle_spi gyro_spi; +public: + BMI088(Protocol& accel_, Protocol& gyro_) : accel{accel_}, gyro{gyro_} { + assert(accel.type() == ProtocolType::SPI); + assert(gyro.type() == ProtocolType::SPI); + } + bool init() override; + bool read(Packet& packet) override; }; -int8_t bmi088_init(struct bmi088_ctx *ctx, struct sensor *sensor); +} // namespace Platform -#endif /* INC_BMI088_H_ */ +#endif diff --git a/include/sensors/bmp581.h b/include/sensors/bmp581.h index 1acd4eb..333b6a0 100644 --- a/include/sensors/bmp581.h +++ b/include/sensors/bmp581.h @@ -1,20 +1,27 @@ -#ifndef BMP581_H -#define BMP581_H +#ifndef SENSORS_BMP581_H +#define SENSORS_BMP581_H -#include "defs.h" -#include "sensor.h" #include "bmp5_defs.h" +#include "protocol.h" +#include "sensor.h" -#include +#include -struct bmp581_ctx { - struct bmp5_dev dev; - struct bmp5_osr_odr_press_config odr_config; - struct bmp5_int_source_select int_config; - struct handle handle; -}; +namespace Platform { +class BMP581 final : public Sensor { +private: + struct bmp5_dev dev; + struct bmp5_osr_odr_press_config odr_config; + struct bmp5_int_source_select int_config; + struct Protocol& api; -int8_t bmp581_init(struct bmp581_ctx *ctx, struct sensor *sensor); -int8_t bmp581_get_power_mode(struct bmp581_ctx *ctx, enum bmp5_powermode *powermode); +public: + BMP581(Protocol& api_) : api(api_) { + assert(api.type() == ProtocolType::SPI); + }; + bool init() override; + bool read(Packet& packet) override; +}; +} // namespace Platform #endif diff --git a/include/sensors/cd-pa1616s.h b/include/sensors/cd-pa1616s.h new file mode 100644 index 0000000..e8f0889 --- /dev/null +++ b/include/sensors/cd-pa1616s.h @@ -0,0 +1,33 @@ +/* + * CD-PA1616S.h + * + * Created on: Feb 21, 2025 + * Author: Mahir Shah + */ + +#ifndef SENSORS_CD_PA1616S_H +#define SENSORS_CD_PA1616S_H + +#include "protocol.h" +#include "sensor.h" + +#include +#include + +namespace Platform { +class GPS final : public Sensor { +private: + // used for DMA reception buffer + uint8_t buffer[128]; + Protocol& protocol; + +public: + GPS(Protocol& protocol_) : protocol(protocol_) { + assert(protocol.type() == ProtocolType::UART); + }; + bool init() override; + bool read(Packet& packet) override; +}; +} // namespace Platform + +#endif /* INC_CD_PA1616S_H_ */ diff --git a/include/sensors/sensor.h b/include/sensors/sensor.h new file mode 100644 index 0000000..ac54c1c --- /dev/null +++ b/include/sensors/sensor.h @@ -0,0 +1,56 @@ +#ifndef SENSORS_SENSOR_H +#define SENSORS_SENSOR_H + +#include +#include + +namespace Platform { +struct __attribute__((packed)) Packet { + int16_t magic = 0xBEEF; // 2 bytes + uint32_t status = 0; // 4 bytes + uint32_t time_us = 0; // 4 bytes + float main_voltage_v = 0.0f; // 4 bytes + float pyro_voltage_v = 0.0f; // 4 bytes + uint8_t numSatellites = 0; // 1 byte + uint8_t gpsFixType = 0; // 1 byte 0 = no fix, 1 = fix, 2 = DGPS fix, etc. + float latitude_degrees = 0.0f; // 4 bytes + float longitude_degrees = 0.0f; // 4 bytes + float gps_hMSL_m = 0.0f; // 4 bytes altitude above mean sea level + float barometer_hMSL_m = 0.0f; // 4 bytes + float temperature_c = 0.0f; // 4 bytes + float acceleration_x_mss = 0.0f; // 4 bytes + float acceleration_y_mss = 0.0f; // 4 bytes + float acceleration_z_mss = 0.0f; // 4 bytes + float angular_velocity_x_rads = 0.0f; // 4 bytes + float angular_velocity_y_rads = 0.0f; // 4 bytes + float angular_velocity_z_rads = 0.0f; // 4 bytes + float gauss_x = 0.0f; // 4 bytes + float gauss_y = 0.0f; // 4 bytes + float gauss_z = 0.0f; // 4 bytes + float kf_acceleration_mss = 0.0f; // 4 bytes + float kf_velocity_ms = 0.0f; // 4 bytes + float kf_position_m = 0.0f; // 4 bytes + float w = 0.0f; // 4 bytes + float x = 0.0f; // 4 bytes + float y = 0.0f; // 4 bytes + float z = 0.0f; // 4 bytes + uint32_t checksum = 0; // 4 bytes + Packet() = default; +}; + +/// Sensor --- +/// To implement a sensor, you must implement a `read` method which updates +/// the packet in place after it reads from the hardware. +/// You must also implement an init method, which allows for retrying. +/// +/// Both methods must return `true` on success. +class Sensor { +protected: +public: + virtual ~Sensor() = default; + virtual bool read(Packet& packet) = 0; + virtual bool init() = 0; +}; +} // namespace Platform + +#endif // SENSORS_SENSOR_H diff --git a/project.yml b/project.yml deleted file mode 100644 index 1446d9e..0000000 --- a/project.yml +++ /dev/null @@ -1,411 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - ---- -:project: - # how to use ceedling. If you're not sure, leave this as `gem` and `?` - :which_ceedling: gem - :ceedling_version: 1.0.1 - - # optional features. If you don't need them, keep them turned off for performance - :use_mocks: TRUE - :use_test_preprocessor: :none # options are :none, :mocks, :tests, or :all - :use_deep_preprocessor: :none # options are :none, :mocks, :tests, or :all - :use_backtrace: :simple # options are :none, :simple, or :gdb - :use_decorators: :auto # decorate Ceedling's output text. options are :auto, :all, or :none - - # tweak the way ceedling handles automatic tasks - :build_root: build/ceedling - :test_file_prefix: test_ - :default_tasks: - - test:all - - # performance options. If your tools start giving mysterious errors, consider - # dropping this to 1 to force single-tasking - :test_threads: 8 - :compile_threads: 8 - - # enable release build (more details in release_build section below) - :release_build: FALSE - -# Specify where to find mixins and any that should be enabled automatically -:mixins: - :enabled: [] - :load_paths: [] - -# further details to configure the way Ceedling handles test code -:test_build: - :use_assembly: FALSE - -# further details to configure the way Ceedling handles release code -:release_build: - :output: MyApp.out - :use_assembly: FALSE - :artifacts: [] - -# Plugins are optional Ceedling features which can be enabled. Ceedling supports -# a variety of plugins which may effect the way things are compiled, reported, -# or may provide new command options. Refer to the readme in each plugin for -# details on how to use it. -:plugins: - :load_paths: [] - :enabled: - #- beep # beeps when finished, so you don't waste time waiting for ceedling - - module_generator # handy for quickly creating source, header, and test templates - - gcov # test coverage using gcov. Requires gcc, gcov, and a coverage analyzer like gcovr - #- bullseye # test coverage using bullseye. Requires bullseye for your platform - - command_hooks # write custom actions to be called at different points during the build process - #- compile_commands_json_db # generate a compile_commands.json file - #- dependencies # automatically fetch 3rd party libraries, etc. - #- subprojects # managing builds and test for static libraries - #- fake_function_framework # use FFF instead of CMock - - # Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired) - #- report_build_warnings_log - #- report_tests_gtestlike_stdout - #- report_tests_ide_stdout - #- report_tests_log_factory - - report_tests_pretty_stdout - #- report_tests_raw_output_log - #- report_tests_teamcity_stdout - -# Specify which reports you'd like from the log factory -:report_tests_log_factory: - :reports: - - json - - junit - - cppunit - - html - -# override the default extensions for your system and toolchain -:extension: - #:header: .h - #:source: .c - #:assembly: .s - #:dependencies: .d - #:object: .o - :executable: .out - #:testpass: .pass - #:testfail: .fail - #:subprojects: .a - -# This is where Ceedling should look for your source and test files. -# see documentation for the many options for specifying this. -:paths: - :test: - - +:test/** - - -:test/support - :source: - - src/** - :include: - - include/** - - build/_deps/** - :support: - - test/support - :libraries: [] - -# You can even specify specific files to add or remove from your test -# and release collections. Usually it's better to use paths and let -# Ceedling do the work for you! -:files: - :test: [] - :source: [] - -# Compilation symbols to be injected into builds -# See documentation for advanced options: -# - Test name matchers for different symbols per test executable build -# - Referencing symbols in multiple lists using advanced YAML -# - Specifiying symbols used during test preprocessing -:defines: - :test: - - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables - :release: [] - - # Enable to inject name of a test as a unique compilation symbol into its respective executable build. - :use_test_definition: FALSE - -# Configure additional command line flags provided to tools used in each build step -# :flags: -# :release: -# :compile: # Add '-Wall' and '--02' to compilation of all files in release target -# - -Wall -# - --O2 -# :test: -# :compile: -# '(_|-)special': # Add '-pedantic' to compilation of all files in all test executables with '_special' or '-special' in their names -# - -pedantic -# '*': # Add '-foo' to compilation of all files in all test executables -# - -foo - -# Configuration Options specific to CMock. See CMock docs for details -:cmock: - # Core conffiguration - :plugins: # What plugins should be used by CMock? - - :ignore - - :callback - - :expect_any_args - - :return_thru_ptr - :verbosity: 2 # the options being 0 errors only, 1 warnings and errors, 2 normal info, 3 verbose - :when_no_prototypes: :warn # the options being :ignore, :warn, or :erro - - # File configuration - :skeleton_path: '' # Subdirectory to store stubs when generated (default: '') - :mock_prefix: 'mock_' # Prefix to append to filenames for mocks - :mock_suffix: '' # Suffix to append to filenames for mocks - - # Parser configuration - :strippables: ['(?:__attribute__\s*\([ (]*.*?[ )]*\)+)'] - :attributes: - - __ramfunc - - __irq - - __fiq - - register - - extern - :c_calling_conventions: - - __stdcall - - __cdecl - - __fastcall - :treat_externs: :exclude # the options being :include or :exclud - :treat_inlines: :exclude # the options being :include or :exclud - - # Type handling configuration - #:unity_helper_path: '' # specify a string of where to find a unity_helper.h file to discover custom type assertions - :treat_as: # optionally add additional types to map custom types - uint8: HEX8 - uint16: HEX16 - uint32: UINT32 - int8: INT8 - bool: UINT8 - #:treat_as_array: {} # hint to cmock that these types are pointers to something - #:treat_as_void: [] # hint to cmock that these types are actually aliases of void - :memcmp_if_unknown: true # allow cmock to use the memory comparison assertions for unknown types - :when_ptr: :compare_data # hint to cmock how to handle pointers in general, the options being :compare_ptr, :compare_data, or :smart - - # Mock generation configuration - :weak: '' # Symbol to use to declare weak functions - :enforce_strict_ordering: true # Do we want cmock to enforce ordering of all function calls? - :fail_on_unexpected_calls: true # Do we want cmock to fail when it encounters a function call that wasn't expected? - :callback_include_count: true # Do we want cmock to include the number of calls to this callback, when using callbacks? - :callback_after_arg_check: false # Do we want cmock to enforce an argument check first when using a callback? - #:includes: [] # You can add additional includes here, or specify the location with the options below - #:includes_h_pre_orig_header: [] - #:includes_h_post_orig_header: [] - #:includes_c_pre_header: [] - #:includes_c_post_header: [] - #:array_size_type: [] # Specify a type or types that should be used for array lengths - #:array_size_name: 'size|len' # Specify a name or names that CMock might automatically recognize as the length of an array - :exclude_setjmp_h: false # Don't use setjmp when running CMock. Note that this might result in late reporting or out-of-order failures. - -# Configuration options specific to Unity. -:unity: - :defines: - # - UNITY_EXCLUDE_FLOAT - -# You can optionally have ceedling create environment variables for you before -# performing the rest of its tasks. -:environment: [] -# :environment: -# # List enforces order allowing later to reference earlier with inline Ruby substitution -# - :var1: value -# - :var2: another value -# - :path: # Special PATH handling with platform-specific path separators -# - #{ENV['PATH']} # Environment variables can use inline Ruby substitution -# - /another/path/to/include - -# LIBRARIES -# These libraries are automatically injected into the build process. Those specified as -# common will be used in all types of builds. Otherwise, libraries can be injected in just -# tests or releases. These options are MERGED with the options in supplemental yaml files. -:libraries: - :placement: :end - :flag: "-l${1}" - :path_flag: "-L ${1}" - :system: ['m'] # for example, you might list 'm' to grab the math library - :test: [] - :release: [] - -################################################################ -# PLUGIN CONFIGURATION -################################################################ - -# Add -gcov to the plugins list to make sure of the gcov plugin -# You will need to have gcov and gcovr both installed to make it work. -# For more information on these options, see docs in plugins/gcov -:gcov: - :summaries: TRUE # Enable simple coverage summaries to console after tests - :report_task: FALSE # Disabled dedicated report generation task (this enables automatic report generation) - :utilities: - - gcovr # Use gcovr to create the specified reports (default). - #- ReportGenerator # Use ReportGenerator to create the specified reports. - :reports: # Specify one or more reports to generate. - # Make an HTML summary report. - - HtmlBasic - # - HtmlDetailed - # - Text - # - Cobertura - # - SonarQube - # - JSON - # - HtmlInline - # - HtmlInlineAzure - # - HtmlInlineAzureDark - # - HtmlChart - # - MHtml - # - Badges - # - CsvSummary - # - Latex - # - LatexSummary - # - PngChart - # - TeamCitySummary - # - lcov - # - Xml - # - XmlSummary - :gcovr: - # :html_artifact_filename: TestCoverageReport.html - # :html_title: Test Coverage Report - :html_medium_threshold: 75 - :html_high_threshold: 90 - # :html_absolute_paths: TRUE - # :html_encoding: UTF-8 - -# :module_generator: -# :naming: :snake #options: :bumpy, :camel, :caps, or :snake -# :includes: -# :tst: [] -# :src: [] -# :boilerplates: -# :src: "" -# :inc: "" -# :tst: "" - -# :dependencies: -# :libraries: -# - :name: WolfSSL -# :source_path: third_party/wolfssl/source -# :build_path: third_party/wolfssl/build -# :artifact_path: third_party/wolfssl/install -# :fetch: -# :method: :zip -# :source: \\shared_drive\third_party_libs\wolfssl\wolfssl-4.2.0.zip -# :environment: -# - CFLAGS+=-DWOLFSSL_DTLS_ALLOW_FUTURE -# :build: -# - "autoreconf -i" -# - "./configure --enable-tls13 --enable-singlethreaded" -# - make -# - make install -# :artifacts: -# :static_libraries: -# - lib/wolfssl.a -# :dynamic_libraries: -# - lib/wolfssl.so -# :includes: -# - include/** - -# :subprojects: -# :paths: -# - :name: libprojectA -# :source: -# - ./subprojectA/source -# :include: -# - ./subprojectA/include -# :build_root: ./subprojectA/build -# :defines: [] - -:command_hooks: - :post_error: - :executable: chmod - :arguments: - - "-R" - - "+w" - - "./build/ceedling/vendor" -# :pre_mock_preprocess: -# :post_mock_preprocess: -# :pre_test_preprocess: -# :post_test_preprocess: -# :pre_mock_generate: -# :post_mock_generate: -# :pre_runner_generate: -# :post_runner_generate: -# :pre_compile_execute: -# :post_compile_execute: -# :pre_link_execute: -# :post_link_execute: -# :pre_test_fixture_execute: -# :post_test_fixture_execute: -# :pre_test: -# :post_test: -# :pre_release: -# :post_release: -# :pre_build: -# :post_build: -# :post_error: - -################################################################ -# TOOLCHAIN CONFIGURATION -################################################################ - -#:tools: -# Ceedling defaults to using gcc for compiling, linking, etc. -# As [:tools] is blank, gcc will be used (so long as it's in your system path) -# See documentation to configure a given toolchain for use -# :tools: -# :test_compiler: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :test_linker: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :test_assembler: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :test_fixture: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :test_includes_preprocessor: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :test_file_preprocessor: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :test_file_preprocessor_directives: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :release_compiler: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :release_linker: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :release_assembler: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -# :release_dependencies_generator: -# :executable: -# :arguments: [] -# :name: -# :optional: FALSE -... diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..1c7f308 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(flash) +add_subdirectory(protocols) +add_subdirectory(sensors) diff --git a/src/flash.c b/src/flash.c deleted file mode 100644 index 1684c27..0000000 --- a/src/flash.c +++ /dev/null @@ -1,99 +0,0 @@ -#include "flash.h" - -#include "lfs.h" - -#include - -// See: https://github.com/littlefs-project/littlefs/issues/564#issuecomment-2363032827 -// This gives how many bytes are needed before we need to do a fsync. Since we -// want to sync at the end of a block, this just gives offset from the end. -// -// We don't want to sync too often, especially for NAND flashes which have -// larger block sizes because littlefs does a whole scan and write of the -// partial block, which is very time-consuming. -static inline uint32_t offset(uint32_t filesize, uint32_t blocksize) -{ - // Edge case for the first block - if (filesize < blocksize) { - return blocksize - filesize; - } - - const uint32_t w = sizeof(uint32_t); - uint32_t pop = __builtin_popcount((filesize / (blocksize - (2*w))) - 1); - uint32_t n = (filesize - (w * (pop + 2))) / (blocksize - 2*w); - uint32_t offset = filesize - (blocksize - 2*w)*n - w*__builtin_popcount(n); - return blocksize - offset; -} - -uint32_t flash_mount(struct flash *flash) -{ - int err = lfs_mount(&flash->lfs, &flash->config); - if (err) { - lfs_format(&flash->lfs, &flash->config); - int res = lfs_mount(&flash->lfs, &flash->config); - if (res < 0) return -1; - } - flash_boot_count(flash, true); - return lfs_fs_size(&(flash->lfs)); -} - -int flash_unmount(struct flash *flash) -{ - return lfs_unmount(&flash->lfs); -} - -uint32_t flash_boot_count(struct flash *flash, bool update) -{ - lfs_file_t file; - uint32_t boot_count = 0; - - lfs_file_open(&flash->lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); - lfs_file_read(&flash->lfs, &file, &boot_count, sizeof(boot_count)); - - if (update) { - ++boot_count; - lfs_file_rewind(&flash->lfs, &file); - lfs_file_write(&flash->lfs, &file, &boot_count, sizeof(boot_count)); - } - - lfs_file_close(&(flash->lfs), &file); - return boot_count; -} - -uint32_t flash_open(struct flash *flash, lfs_file_t *file, const char *filename) -{ - lfs_file_open(&flash->lfs, file, filename, LFS_O_RDWR | LFS_O_CREAT | LFS_O_APPEND); - return lfs_file_size(&flash->lfs, file); -} - -bool flash_append(struct flash *flash, lfs_file_t *file, const uint8_t *bytes, size_t size) -{ - uint8_t *buf = (uint8_t *) bytes; - uint32_t filesize = lfs_file_size(&flash->lfs, file); - uint32_t off = offset(filesize, flash->lfs.cfg->block_size); - - while (size > 0) { - uint32_t write_size = size < off ? size : off; - if (off == 0) { - // Time for a new block, sync and just write whatever - lfs_file_sync(&flash->lfs, file); - // Due to constraints, the size is guaranteed to be less - // than a block size, so we can just safely do this - write_size = size; - } - - uint32_t res = lfs_file_write(&flash->lfs, file, buf, write_size); - if (res != write_size) return false; - - size -= res; - filesize += res; - buf += res; - off = offset(filesize, flash->lfs.cfg->block_size); - } - return true; -} - -int flash_close(struct flash *flash, lfs_file_t *file) -{ - return lfs_file_close(&flash->lfs, file); -} diff --git a/src/flash/CMakeLists.txt b/src/flash/CMakeLists.txt new file mode 100644 index 0000000..1d0359b --- /dev/null +++ b/src/flash/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(common_drivers PRIVATE + "flash.cpp" + "gd5f1gq5xe.cpp" +) diff --git a/src/flash/flash.cpp b/src/flash/flash.cpp new file mode 100644 index 0000000..cf3f988 --- /dev/null +++ b/src/flash/flash.cpp @@ -0,0 +1,97 @@ +#include "flash.h" + +#include "lfs.h" + +#include + +namespace { +// See: +// https://github.com/littlefs-project/littlefs/issues/564#issuecomment-2363032827 +// This gives how many bytes are needed before we need to do a fsync. Since we +// want to sync at the end of a block, this just gives offset from the end. +// +// We don't want to sync too often, especially for NAND flashes which have +// larger block sizes because littlefs does a whole scan and write of the +// partial block, which is very time-consuming. +inline uint32_t offset(uint32_t filesize, uint32_t blocksize) { + // Edge case for the first block + if (filesize < blocksize) { + return blocksize - filesize; + } + + const uint32_t w = sizeof(uint32_t); + uint32_t pop = __builtin_popcount((filesize / (blocksize - (2 * w))) - 1); + uint32_t n = (filesize - (w * (pop + 2))) / (blocksize - 2 * w); + uint32_t offset = + filesize - (blocksize - 2 * w) * n - w * __builtin_popcount(n); + return blocksize - offset; +} +} // namespace + +namespace Platform { +uint32_t Flash::mount() { + int err = lfs_mount(&lfs, &config); + if (err) { + lfs_format(&lfs, &config); + int res = lfs_mount(&lfs, &config); + if (res < 0) + return -1; + } + bootcount(true); + return lfs_fs_size(&lfs); +} + +uint32_t Flash::unmount() { return lfs_unmount(&lfs); } + +uint32_t Flash::bootcount(bool update) { + lfs_file_t file; + uint32_t current_count = 0; + + lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); + lfs_file_read(&lfs, &file, ¤t_count, sizeof(current_count)); + + if (update) { + ++current_count; + lfs_file_rewind(&lfs, &file); + lfs_file_write(&lfs, &file, ¤t_count, sizeof(current_count)); + } + + lfs_file_close(&lfs, &file); + return current_count; +} + +uint32_t Flash::open(lfs_file_t* file, const char* filename) { + lfs_file_open(&lfs, file, filename, + LFS_O_RDWR | LFS_O_CREAT | LFS_O_APPEND); + return lfs_file_size(&lfs, file); +} + +uint32_t Flash::close(lfs_file_t* file) { return lfs_file_close(&lfs, file); } + +bool Flash::append(lfs_file_t* file, const uint8_t* bytes, size_t size) { + uint8_t* buf = (uint8_t*)bytes; + uint32_t filesize = lfs_file_size(&lfs, file); + uint32_t off = offset(filesize, lfs.cfg->block_size); + + while (size > 0) { + uint32_t write_size = size < off ? size : off; + if (off == 0) { + // Time for a new block, sync and just write whatever + lfs_file_sync(&lfs, file); + // Due to constraints, the size is guaranteed to be less + // than a block size, so we can just safely do this + write_size = size; + } + + uint32_t res = lfs_file_write(&lfs, file, buf, write_size); + if (res != write_size) + return false; + + size -= res; + filesize += res; + buf += res; + off = offset(filesize, lfs.cfg->block_size); + } + return true; +} +} // namespace Platform diff --git a/src/flash/gd5f1gq5xe.c b/src/flash/gd5f1gq5xe.c deleted file mode 100644 index 288586f..0000000 --- a/src/flash/gd5f1gq5xe.c +++ /dev/null @@ -1,329 +0,0 @@ -#include "gd5f1gq5xe.h" - -#include "defs.h" - -// Flash Commands -#define GD5F_SET_FEATURE 0x1F -#define GD5F_WRITE_ENABLE 0x06 -#define GD5F_WRITE_DISABLE 0x04 -#define GD5F_READ_TO_CACHE 0x13 -#define GD5F_READ_FROM_CACHE 0x03 -#define GD5F_PROGRAM_LOAD 0x02 -#define GD5F_PROGRAM_EXECUTE 0x10 -#define GD5F_ERASE 0xD8 -#define GD5F_READ_ID 0x9F - -// Flash Sizes -#define GD5F_BLOCK_COUNT 1024 -#define GD5F_PAGES_PER_BLOCK 64 -#define GD5F_PAGE_SIZE 2048 -#define GD5F_BLOCK_SIZE GD5F_PAGES_PER_BLOCK * GD5F_PAGE_SIZE - -// This is a NAND flash which uses 3-byte addressing, this means we have -// -// | block address | page address | -// | bytes <15-6> | bytes <5-0> | -// -// NAND flashes employ a two step read and progamming process. For reading we -// first fetch the page using the 3-byte addressing to the cache, and then use a -// second command to read from the cache, here we can index the bytes using a -// column address 12 bytes. -// -// For writing, we do the inverse. We populate the cache with bytes using the index. -// After it is filled, we can then write the page to the flash using the 3-byte addressing -// -// Note that this flash has a page size of 2048, so we actually only need 11 bytes for -// the column address. - -static void chip_select(struct handle_spi *spi) -{ - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_RESET); -} - -static void chip_deselect(struct handle_spi *spi) -{ - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_SET); -} - -static bool spi_transmit(struct handle_spi *spi, void *buffer, const size_t size) -{ - return HAL_SPI_Transmit(spi->handle, (uint8_t*) buffer, size, HAL_MAX_DELAY); -} - -static bool spi_receive(struct handle_spi *spi, void *buffer, const size_t size) -{ - return HAL_SPI_Receive(spi->handle, (uint8_t*) buffer, size, HAL_MAX_DELAY); -} - -static int write_enable(struct handle_spi *spi) -{ - uint8_t tx = GD5F_WRITE_ENABLE; - chip_select(spi); - if (spi_transmit(spi, &tx, sizeof(tx)) != 0) { - chip_deselect(spi); - return 1; - } - chip_deselect(spi); - return 0; -} - -static bool check_id(struct handle_spi *spi) -{ - chip_select(spi); - uint8_t cmd[] = { GD5F_READ_ID, 0x00 }; - uint8_t data[] = { 0, 0 }; - if (spi_transmit(spi, cmd, sizeof(cmd)) != 0) { - chip_deselect(spi); - } - spi_receive(spi, data, sizeof(data)); - chip_deselect(spi); - // Sometimes the check is not consistent - // Investigate for now - return data[0] == 0xC8 && data[1] == 0x31; - /* assert(data[0] == 0xC8); */ - /* assert(data[1] == 0x31); */ -} - -/// Read a page from the flash into the `buffer`. -/// -/// `block` must be less than the block-count -/// `offset` must be less than than the block-size -/// `size` can be larger than the page-size, but this function will only -/// read up to the page-size boundary -/// -/// Returns the number of bytes read. -static uint32_t read_page(struct handle_spi *spi, const uint32_t block, - const uint32_t offset, void *buffer, uint32_t size) -{ - assert(block < GD5F_BLOCK_COUNT); - assert(offset < GD5F_BLOCK_SIZE); - - // This combines the block and offset to create our 3-byte address - // Notice that `block * GD5F_PAGES_PER_BLOCK` is equivalent to - // `block << 6`, which is why this code here works. - uint32_t addr = block * GD5F_PAGES_PER_BLOCK + (offset / GD5F_PAGE_SIZE); - // We can then use the column addreess to read offsets into the page - uint16_t col = offset % GD5F_PAGE_SIZE; - - uint8_t tx1[] = { - GD5F_READ_TO_CACHE, - (addr & 0xFF0000) >> 16, - (addr & 0x00FF00) >> 8, - (addr & 0x0000FF) - }; - chip_select(spi); - if (spi_transmit(spi, tx1, sizeof(tx1)) != 0) { - chip_deselect(spi); - return 0; - } - chip_deselect(spi); - HAL_Delay(2); // Required delay for cache read - - uint8_t tx2[] = { - GD5F_READ_FROM_CACHE, - (col & 0x0F00) >> 8, // first 4 bytes are not needed, - (col & 0x00FF), // remember that we only need 12 bytes - 0x00 // we need a dummy byte (from datasheet) - }; - chip_select(spi); - if (spi_transmit(spi, tx2, sizeof(tx2)) != 0) { - chip_deselect(spi); - return 0; - }; - uint32_t read_size = size <= GD5F_PAGE_SIZE - col ? size : GD5F_PAGE_SIZE - col; - if (spi_receive(spi, buffer, read_size) != 0) { - chip_deselect(spi); - return 0; - } - chip_deselect(spi); - return read_size; -} - -/// Write a page from the `buffer` into the flash -/// -/// `block` must be less than the block-count -/// `offset` must be less than than the block-size -/// `size` can be larger than the page-size, but this function will only -/// write up to the page-size boundary -/// -/// Returns the number of bytes written. -static uint32_t write_page(struct handle_spi *spi, const uint32_t block, - const uint32_t offset, const void *buffer, const uint32_t size) -{ - assert(block < GD5F_BLOCK_COUNT); - assert(offset < GD5F_BLOCK_SIZE); - - uint32_t addr = block * GD5F_PAGES_PER_BLOCK + (offset / GD5F_PAGE_SIZE); - uint16_t col = offset % GD5F_PAGE_SIZE; - - uint8_t tx1[] = { - GD5F_PROGRAM_LOAD, - (col & 0x0F00) >> 8, // similar to before, the first 4 bytes - (col & 0x00FF) // are not needed, hence the 0x0F00 - }; - chip_select(spi); - if (spi_transmit(spi, tx1, sizeof(tx1)) != 0) { - chip_deselect(spi); - return 0; - } - uint32_t write_size = size <= GD5F_PAGE_SIZE - col ? size : GD5F_PAGE_SIZE - col; - if (spi_transmit(spi, buffer, write_size) != 0) { - chip_deselect(spi); - return 0; - }; - chip_deselect(spi); - - if (write_enable(spi) != 0) { - chip_deselect(spi); - return 0; - } - - uint8_t tx2[] = { - GD5F_PROGRAM_EXECUTE, - (addr & 0xFF0000) >> 16, - (addr & 0x00FF00) >> 8, - (addr & 0x0000FF) - }; - chip_select(spi); - if (spi_transmit(spi, tx2, sizeof(tx2)) != 0) { - chip_deselect(spi); - return 0; - } - chip_deselect(spi); - - HAL_Delay(1); - return write_size; -} - -static bool read(struct handle_spi *spi, uint32_t block, uint32_t offset, void *buffer, uint32_t size) -{ - uint8_t *buf = (uint8_t *) buffer; - while (size > 0) { - uint32_t s = read_page(spi, block, offset, buf, size); - if (s == 0) return false; - size -= s; - offset += s; - buf += s; - } - return true; -} - -static bool write(struct handle_spi *spi, uint32_t block, uint32_t offset, void *buffer, uint32_t size) -{ - uint8_t *buf = (uint8_t *) buffer; - while (size > 0) { - uint32_t s = write_page(spi, block, offset, buf, size); - if (s == 0) return false; - size -= s; - offset += s; - buf += s; - } - return true; -} - -static bool erase(struct handle_spi *spi, uint32_t block) -{ - // Erase acts on blocks and not pages, so we should only have the block - // section of the address set and not the page section. - uint32_t addr = block * GD5F_PAGES_PER_BLOCK; - - if (write_enable(spi) != 0) { - chip_deselect(spi); - return false; - } - - uint8_t tx[] = { - GD5F_ERASE, - (addr & 0xFF0000) >> 16, - (addr & 0x00FF00) >> 8, - (addr & 0x0000FF) - }; - chip_select(spi); - if (spi_transmit(spi, tx, sizeof(tx)) != 0) { - chip_deselect(spi); - return false; - } - chip_deselect(spi); - - HAL_Delay(12); - return true; -} - -static bool unlock(struct handle_spi *spi) -{ - // Needed for some reason, I don't know why - HAL_Delay(5000); - if (!check_id(spi)) return false; - if (write_enable(spi) != 0) { - chip_deselect(spi); - return false; - } - - uint8_t tx[] = { - GD5F_SET_FEATURE, - 0xA0, - 0x00, - }; - chip_select(spi); - if (spi_transmit(spi, tx, sizeof(tx)) != 0) { - chip_deselect(spi); - return false; - } - chip_deselect(spi); - - HAL_Delay(5000); - return true; -} - -static int lfs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t offset, - void *data, lfs_size_t size) -{ - struct handle_spi *spi = (struct handle_spi*) c->context; - if (!read(spi, block, offset, data, size)) return LFS_ERR_IO; - return LFS_ERR_OK; -} - -static int lfs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t offset, - const void *data, lfs_size_t size) -{ - struct handle_spi *spi = (struct handle_spi*) c->context; - if (!write(spi, block, offset, data, size)) return LFS_ERR_IO; - return LFS_ERR_OK; -} - -static int lfs_erase(const struct lfs_config *c, lfs_block_t block) -{ - struct handle_spi *spi = (struct handle_spi*) c->context; - if (!erase(spi, block)) return LFS_ERR_IO; - return LFS_ERR_OK; -} - -static int lfs_sync(const struct lfs_config *c) -{ - return LFS_ERR_OK; -} - -bool gd5f1gq5xe_init(struct flash *flash, struct handle_spi *spi) -{ - assert(spi->handle != NULL); - flash->config = (struct lfs_config ) { - .context = spi, - .read = lfs_read, - .prog = lfs_prog, - .erase = lfs_erase, - .sync = lfs_sync, - - .read_size = GD5F_PAGE_SIZE, - .prog_size = GD5F_PAGE_SIZE, - .block_size = GD5F_BLOCK_SIZE, - .block_count = GD5F_BLOCK_COUNT, - .cache_size = GD5F_PAGE_SIZE, - .lookahead_size = 128, - .block_cycles = 512, - }; - if (!unlock(spi)) { - return false; - } - - return true; -} diff --git a/src/flash/gd5f1gq5xe.cpp b/src/flash/gd5f1gq5xe.cpp new file mode 100644 index 0000000..1326a64 --- /dev/null +++ b/src/flash/gd5f1gq5xe.cpp @@ -0,0 +1,267 @@ +#include "gd5f1gq5xe.h" + +#include "hal.h" +#include "protocol.h" + +// Flash Commands +constexpr uint8_t GD5F_SET_FEATURE = 0x1F; +constexpr uint8_t GD5F_WRITE_ENABLE = 0x06; +constexpr uint8_t GD5F_WRITE_DISABLE = 0x04; +constexpr uint8_t GD5F_READ_TO_CACHE = 0x13; +constexpr uint8_t GD5F_READ_FROM_CACHE = 0x03; +constexpr uint8_t GD5F_PROGRAM_LOAD = 0x02; +constexpr uint8_t GD5F_PROGRAM_EXECUTE = 0x10; +constexpr uint8_t GD5F_ERASE = 0xD8; +constexpr uint8_t GD5F_READ_ID = 0x9F; + +// QSPI Flash commands +constexpr uint8_t GD5F_READ_FROM_CACHE_QUAD_IO = 0xEB; +constexpr uint8_t GD5F_PROGRAM_LOAD_QUAD = 0x32; + +// Flash Sizes +constexpr uint32_t GD5F_BLOCK_COUNT = 1024; +constexpr uint32_t GD5F_PAGES_PER_BLOCK = 64; +constexpr uint32_t GD5F_PAGE_SIZE = 2048; +constexpr uint32_t GD5F_BLOCK_SIZE = GD5F_PAGES_PER_BLOCK * GD5F_PAGE_SIZE; + +// This is a NAND flash which uses 3-byte addressing, this means we have +// +// | block address | page address | +// | bytes <15-6> | bytes <5-0> | +// +// NAND flashes employ a two step read and progamming process. For reading we +// first fetch the page using the 3-byte addressing to the cache, and then use a +// second command to read from the cache, here we can index the bytes using a +// column address 12 bytes. +// +// For writing, we do the inverse. We populate the cache with bytes using the +// index. After it is filled, we can then write the page to the flash using the +// 3-byte addressing +// +// Note that this flash has a page size of 2048, so we actually only need 11 +// bytes for the column address. +namespace { +using namespace Platform; + +/// Generally needed when you want to write to the flash using execute +int write_enable(Protocol* protocol) { + uint8_t cmd[] = {GD5F_WRITE_ENABLE}; + return protocol->write(ConstSpan(cmd), {}, AddressSize::None); +} + +/// Read a page from the flash into the `buffer`. +/// +/// `block` must be less than the block-count +/// `offset` must be less than than the block-size +/// `size` can be larger than the page-size, but this function will only +/// read up to the page-size boundary +/// +/// Returns the number of bytes read. +uint32_t read_page(Protocol* protocol, const uint32_t block, + const uint32_t offset, void* buffer, uint32_t size) { + assert(block < GD5F_BLOCK_COUNT); + assert(offset < GD5F_BLOCK_SIZE); + + // This combines the block and offset to create our 3-byte address + // Notice that `block * GD5F_PAGES_PER_BLOCK` is equivalent to + // `block << 6`, which is why this code here works. + uint32_t addr = block * GD5F_PAGES_PER_BLOCK + (offset / GD5F_PAGE_SIZE); + uint8_t cmd1[] = { + GD5F_READ_TO_CACHE, // our op code + (addr & 0xFF0000) >> 16, // first byte of the address + (addr & 0x00FF00) >> 8, // second byte + (addr & 0x0000FF) // third byte + }; + protocol->write(ConstSpan(cmd1), {}, AddressSize::Byte3); + HAL_Delay(2); // maximum delay for reading to cache + + // We can then use the column address to read offsets into the page + uint16_t col = offset % GD5F_PAGE_SIZE; + uint32_t read_size = std::min(size, GD5F_PAGE_SIZE - col); + auto cmdlen = protocol->type() == ProtocolType::QSPI ? 5 : 4; + uint8_t cmd2[] = { + protocol->type() == ProtocolType::QSPI ? GD5F_READ_FROM_CACHE_QUAD_IO + : GD5F_READ_FROM_CACHE, + (col & 0x0F00) >> 8, // first 4 bytes are not needed, + (col & 0x00FF), // remember that we only need 12 bytes + 0x00, // we need a dummy byte (from datasheet) + 0x00 // for qspi we need another dummy byte (for 4 clock cycles) + }; + // We use the QUAD IO feature of our flash for even more read speed This + // requires us to use four address lines though + protocol->configure(Config::QSPI_Address4); + auto* buf = static_cast(buffer); + protocol->read(ConstSpan(cmd2, cmdlen), Span(buf, read_size), + AddressSize::Byte2); + // Unset it now that we are done + protocol->configure(Config::QSPI_Address1); + + return read_size; +} + +/// Write a page from the `buffer` into the flash +/// +/// `block` must be less than the block-count +/// `offset` must be less than than the block-size +/// `size` can be larger than the page-size, but this function will only +/// write up to the page-size boundary +/// +/// Returns the number of bytes written. +uint32_t write_page(Protocol* protocol, const uint32_t block, + const uint32_t offset, const void* buffer, + const uint32_t size) { + assert(block < GD5F_BLOCK_COUNT); + assert(offset < GD5F_BLOCK_SIZE); + + // Note that there isn't a QUAD IO load command so we don't have to + // configure four address lines like we did before. + uint16_t col = offset % GD5F_PAGE_SIZE; + uint32_t write_size = std::min(size, GD5F_PAGE_SIZE - col); + uint8_t cmd1[] = { + protocol->type() == ProtocolType::QSPI ? GD5F_PROGRAM_LOAD_QUAD + : GD5F_PROGRAM_LOAD, + (col & 0x0F00) >> 8, // similar to before, the first 4 bytes + (col & 0x00FF) // are not needed, hence the 0x0F00 + }; + auto* buf = static_cast(buffer); + protocol->write(ConstSpan(cmd1), ConstSpan(buf, write_size), + AddressSize::Byte2); + + if (write_enable(protocol) != 0) + return 0; + uint32_t addr = block * GD5F_PAGES_PER_BLOCK + (offset / GD5F_PAGE_SIZE); + uint8_t cmd2[] = { + GD5F_PROGRAM_EXECUTE, + (addr & 0xFF0000) >> 16, // execute essentially writes the page + (addr & 0x00FF00) >> 8, // to the flash, so you use load + (addr & 0x0000FF) // to fill a page, and execute to write it + }; + protocol->write(ConstSpan(cmd2), {}, AddressSize::Byte3); + HAL_Delay(1); // maximum delay required for executing + + return write_size; +} + +/// LFS can handle a size which is larger than a page, so we need to handle +/// that in both our writes and reads. This is why we need these next +/// two functions even though we already have page read and writes +bool read(Protocol* protocol, uint32_t block, uint32_t offset, void* buffer, + uint32_t size) { + uint8_t* buf = (uint8_t*)buffer; + while (size > 0) { + uint32_t s = read_page(protocol, block, offset, buf, size); + if (s == 0) + return false; + size -= s; + offset += s; + buf += s; + } + return true; +} + +bool write(Protocol* protocol, uint32_t block, uint32_t offset, + const void* buffer, uint32_t size) { + uint8_t* buf = (uint8_t*)buffer; + while (size > 0) { + uint32_t s = write_page(protocol, block, offset, buf, size); + if (s == 0) + return false; + size -= s; + offset += s; + buf += s; + } + return true; +} + +bool erase(Protocol* protocol, uint32_t block) { + // Erase acts on blocks and not pages, so we should only have the block + // section of the address set and not the page section. + uint32_t addr = block * GD5F_PAGES_PER_BLOCK; + if (write_enable(protocol) != 0) + return false; + uint8_t cmd[] = { + GD5F_ERASE, + (addr & 0xFF0000) >> 16, // Erase only takes the address + (addr & 0x00FF00) >> 8, // remember that this is because + (addr & 0x0000FF) // it operates on whiole blocks + }; + protocol->write(ConstSpan(cmd), {}, AddressSize::Byte3); + HAL_Delay(12); // maximum delay required for erases + return true; +} + +int lfs_read(const struct lfs_config* c, lfs_block_t block, lfs_off_t offset, + void* data, lfs_size_t size) { + auto* protocol = static_cast(c->context); + if (!read(protocol, block, offset, data, size)) + return LFS_ERR_IO; + return LFS_ERR_OK; +} + +int lfs_prog(const struct lfs_config* c, lfs_block_t block, lfs_off_t offset, + const void* data, lfs_size_t size) { + auto* protocol = static_cast(c->context); + if (!write(protocol, block, offset, data, size)) + return LFS_ERR_IO; + return LFS_ERR_OK; +} + +int lfs_erase(const struct lfs_config* c, lfs_block_t block) { + auto* protocol = static_cast(c->context); + if (!erase(protocol, block)) + return LFS_ERR_IO; + return LFS_ERR_OK; +} + +/// No-op, not required for flash +int lfs_sync(const struct lfs_config* c) { return LFS_ERR_OK; } +} // namespace + +namespace Platform { +GD5F1GQ5XE::GD5F1GQ5XE(Protocol& protocol_) : protocol(protocol_) { + assert(protocol.type() == ProtocolType::SPI || + protocol.type() == ProtocolType::QSPI); + config.context = &protocol; + config.read = lfs_read; + config.prog = lfs_prog; + config.erase = lfs_erase; + config.sync = lfs_sync; + + config.read_size = GD5F_PAGE_SIZE; + config.prog_size = GD5F_PAGE_SIZE; + config.block_size = GD5F_BLOCK_SIZE; + config.block_count = GD5F_BLOCK_COUNT; + config.cache_size = GD5F_PAGE_SIZE; + config.lookahead_size = 128; + config.block_cycles = 512; +} + +bool GD5F1GQ5XE::init() { + // If using QSPI, we have to set this for some commands + protocol.configure(Config::QSPI_Data1); + // Check if the id matches + uint8_t cmd1[] = {GD5F_READ_ID, 0x00}; + uint8_t data[] = {0, 0}; + protocol.read(ConstSpan(cmd1), Span(data), AddressSize::None); + if (data[0] != 0xC8 && data[1] != 0x31) + return false; + + // Enable writes to the flash as a whole, by setting Protection + // to 0x00, so nothing is protected. + if (write_enable(&protocol) != 0) + return false; + uint8_t cmd2[] = {GD5F_SET_FEATURE, 0xA0, 0x00}; + protocol.write(ConstSpan(cmd2), {}, AddressSize::Byte2); + HAL_Delay(5000); + + if (protocol.type() == ProtocolType::QSPI) { + // Enable QE feature so we can quad reads and writes + uint8_t cmd3[] = {GD5F_SET_FEATURE, 0xB0, 0x01}; + protocol.write(ConstSpan(cmd3), {}, AddressSize::Byte2); + // All done, set it back to quad + protocol.configure(Config::QSPI_Data4); + } + + return true; +} +} // namespace Platform diff --git a/src/protocols/CMakeLists.txt b/src/protocols/CMakeLists.txt new file mode 100644 index 0000000..c17a781 --- /dev/null +++ b/src/protocols/CMakeLists.txt @@ -0,0 +1,33 @@ +# What this basically does is conditionally include the i2c, qspi, spi, and uart +# files if and only if they are enabled in cubemx. +# +# When cubemx enables the perhipherals, it adds something like this +# ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_spi.c +# to STM32_Drivers_Src. Therefore we can just iterate through it and find if the +# given drivers exist, and if they do, we can safely include our thin HAL abstraction +# for the driver. +set(PATTERNS + "_hal_i2c\\.c$" + "_hal_qspi\\.c$" + "_hal_spi\\.c$" + "_hal_uart\\.c$" +) + +set(SOURCES + "i2c.cpp" + "qspi.cpp" + "spi.cpp" + "uart.cpp" +) + +if(TARGET STM32_Drivers) + get_target_property(STM32_Drivers_Src STM32_Drivers SOURCES) + foreach(PATTERN SOURCE IN ZIP_LISTS PATTERNS SOURCES) + string(REGEX MATCH "${PATTERN}" FOUND_MATCH "${STM32_Drivers_Src}") + if(FOUND_MATCH) + # If the first pattern gets matched, then we add our file to the library + message(STATUS "Including " ${SOURCE} " in build") + target_sources(common_drivers PRIVATE "${SOURCE}") + endif() + endforeach() +endif() diff --git a/src/protocols/i2c.cpp b/src/protocols/i2c.cpp new file mode 100644 index 0000000..3912019 --- /dev/null +++ b/src/protocols/i2c.cpp @@ -0,0 +1,46 @@ +#include "i2c.h" + +#include "hal.h" +#include "protocol.h" + +#include +#include + +namespace { +uint32_t get_address_size(Platform::AddressSize size) { + assert((size == Platform::AddressSize::Byte || + size == Platform::AddressSize::Byte2) && + "I2C addressses must one or two bytes"); + switch (size) { + case Platform::AddressSize::Byte: + return I2C_MEMADD_SIZE_8BIT; + case Platform::AddressSize::Byte2: + return I2C_MEMADD_SIZE_16BIT; + default: + return 0; // shouldn't be reached + } +} + +uint16_t get_address(Platform::ConstSpan cmd, Platform::AddressSize size) { + assert(!cmd.empty() && "Address cannot be empty"); + if (size == Platform::AddressSize::Byte) + return cmd[0]; + assert((cmd.size() == 2) && "Address length must be be two bytes bytes"); + return static_cast((cmd[0] << 8) | cmd[1]); +} +} // namespace + +namespace Platform { +bool I2C::read(ConstSpan cmd, Span buffer, AddressSize size) { + auto asize = get_address_size(size); + return HAL_I2C_Mem_Read(handle, address << 1, get_address(cmd, size), asize, + buffer.data(), buffer.size(), HAL_MAX_DELAY); +} + +bool I2C::write(ConstSpan cmd, ConstSpan buffer, AddressSize size) { + auto asize = get_address_size(size); + return HAL_I2C_Mem_Write(handle, address << 1, get_address(cmd, size), + asize, buffer.data(), buffer.size(), + HAL_MAX_DELAY); +} +} // namespace Platform diff --git a/src/protocols/qspi.cpp b/src/protocols/qspi.cpp new file mode 100644 index 0000000..3cbab7e --- /dev/null +++ b/src/protocols/qspi.cpp @@ -0,0 +1,98 @@ +#include "qspi.h" + +#include + +namespace { +uint32_t get_address_size(Platform::AddressSize size) { + switch (size) { + case Platform::AddressSize::Byte: + return QSPI_ADDRESS_8_BITS; + case Platform::AddressSize::Byte2: + return QSPI_ADDRESS_16_BITS; + case Platform::AddressSize::Byte3: + return QSPI_ADDRESS_24_BITS; + case Platform::AddressSize::Byte4: + return QSPI_ADDRESS_32_BITS; + case Platform::AddressSize::None: + return QSPI_ADDRESS_NONE; + } +} + +uint32_t dummycycle_per_byte(uint32_t datalines) { + switch (datalines) { + case QSPI_ADDRESS_1_LINE: + return 4; + case QSPI_ADDRESS_4_LINES: + return 2; + default: + assert(0 && "Invalid datalines setting"); + } +} + +uint32_t get_address(Platform::ConstSpan cmd, Platform::AddressSize size) { + auto numbytes = static_cast(size); + uint32_t address = 0; + assert(cmd.size() > numbytes && "You misconfigured your address size"); + for (int i = 0; i < numbytes; ++i) + address |= static_cast(cmd[numbytes - i]) << (8 * i); + return address; +} + +void configure_cmd(Platform::ConstSpan cmd, Platform::AddressSize size, + QSPI_CommandTypeDef& qcmd) { + // Configure instruction and address + qcmd.Instruction = cmd[0]; + qcmd.AddressMode = address_mode; + qcmd.AddressSize = get_address_size(size); + if (qcmd.AddressSize != QSPI_ADDRESS_NONE) + qcmd.Address = get_address(cmd, size); + else + qcmd.AddressMode = QSPI_ADDRESS_NONE; + + // Calculate dummy cycles from the number of empty bytes empty bytes are the + // bytes that are not part of the address and not part of the command, so we + // do `- 1` for the command and another subtraction based on the address width. + auto mult = dummycycle_per_byte(data_mode); + qcmd.DummyCycles = (cmd.size() - 1 - static_cast(size)) * mult; + // Keep this here for now, should never trigger + assert(qcmd.DummyCycles < 12); + + // Configure data mode + qcmd.DataMode = data_mode; + if (!buffer.empty()) + qcmd.NbData = buffer.size(); + else + qcmd.DataMode = QSPI_DATA_NONE; + + // These are generally the most common configuration, you can + // probably deviate from it but then you get some weird confusing + // stuff that we probably shouldn't deal with + qcmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; + qcmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; +} +} // namespace + +namespace Platform { +bool QSPI::read(ConstSpan cmd, Span buffer, AddressSize size) { + QSPI_CommandTypeDef qcmd; + configure_cmd(cmd, size, qcmd); + + auto res = HAL_QSPI_Command(handle, &qcmd, HAL_MAX_DELAY); + if (res != HAL_OK) + return res; + return HAL_QSPI_Receive(handle, buffer.data(), HAL_MAX_DELAY); +} + +bool QSPI::write(ConstSpan cmd, ConstSpan buffer, AddressSize size) { + QSPI_CommandTypeDef qcmd; + configure_cmd(cmd, size, qcmd); + + auto res = HAL_QSPI_Command(handle, &qcmd, HAL_MAX_DELAY); + if (res != HAL_OK) + return res; + + if (!buffer.empty()) + return HAL_QSPI_Transmit(handle, buffer.data(), HAL_MAX_DELAY); + return res; +} +} // namespace Platform diff --git a/src/protocols/spi.cpp b/src/protocols/spi.cpp new file mode 100644 index 0000000..d0ba320 --- /dev/null +++ b/src/protocols/spi.cpp @@ -0,0 +1,41 @@ +#include "spi.h" + +#include + +namespace { +/// This handy guard makes sure we call chip deselect after we return +/// from the function, either due to an error or due to a success. +struct Guard { + GPIO_TypeDef* port; + uint16_t pin; + + Guard(GPIO_TypeDef* port, uint16_t pin) : port(port), pin(pin) { + HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET); + } + + ~Guard() { HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET); } +}; +} // namespace + +namespace Platform { +bool SPI::read(ConstSpan cmd, Span buffer, AddressSize size) { + assert(!cmd.empty() && "Command buffer should not be empty"); + Guard cs = Guard(port, pin); + auto res = HAL_SPI_Transmit(handle, cmd.data(), cmd.size(), HAL_MAX_DELAY); + if (res != HAL_OK) + return res; + return HAL_SPI_Receive(handle, buffer.data(), buffer.size(), HAL_MAX_DELAY); +} + +bool SPI::write(ConstSpan cmd, ConstSpan buffer, AddressSize size) { + assert(!cmd.empty() && "Command buffer should not be empty"); + Guard cs = Guard(port, pin); + auto res = HAL_SPI_Transmit(handle, cmd.data(), cmd.size(), HAL_MAX_DELAY); + if (res != HAL_OK) + return res; + if (!buffer.empty()) + return HAL_SPI_Transmit(handle, buffer.data(), buffer.size(), + HAL_MAX_DELAY); + return res; +} +} // namespace Platform diff --git a/src/protocols/uart.cpp b/src/protocols/uart.cpp new file mode 100644 index 0000000..b40597f --- /dev/null +++ b/src/protocols/uart.cpp @@ -0,0 +1,19 @@ +#include "uart.h" + +namespace Platform { +bool UART::read(ConstSpan cmd, Span buffer, AddressSize size) { + assert(cmd.empty() && "Just read using the buffer"); + return HAL_UART_Receive(handle, buffer.data(), buffer.size(), + HAL_MAX_DELAY); +} + +bool UART::write(ConstSpan cmd, ConstSpan buffer, AddressSize size) { + assert(cmd.empty() && "Just write using the buffer"); + return HAL_UART_Transmit(handle, buffer.data(), buffer.size(), + HAL_MAX_DELAY); +} + +bool UART::readDMA(Span buffer) { + return HAL_UARTEx_ReceiveToIdle_DMA(handle, buffer.data(), buffer.size()); +} +} // namespace Platform diff --git a/src/sensors/CMakeLists.txt b/src/sensors/CMakeLists.txt new file mode 100644 index 0000000..c83a310 --- /dev/null +++ b/src/sensors/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(common_drivers PRIVATE + "bmi088.cpp" + "bmp581.cpp" + "cd-pa1616s.cpp" +) diff --git a/src/sensors/bmi088.c b/src/sensors/bmi088.c deleted file mode 100644 index dc59e8c..0000000 --- a/src/sensors/bmi088.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * bmi088.c - * - * Created on: Oct 26, 2025 - * Author: Dhruv Shah - */ - -#include "sensor.h" -#include "defs.h" - -#include "bmi08_defs.h" -#include "bmi088.h" -#include "bmi08x.h" -#include "bmi08.h" - -#include -#include -#include - -#define CONVERT_GYRO_RAW_RANGE(raw, range) ((((float)raw * (float)range) / 32768.0f) * (M_PI / 180.0f)) - -static BMI08_INTF_RET_TYPE bmi088_read_spi(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, void *intf_ptr) // GCOVR_EXCL_FUNCTION -{ - struct handle_spi* spi = (struct handle_spi*) intf_ptr; - - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_RESET); - - HAL_StatusTypeDef ret = HAL_OK; - - ret = HAL_SPI_Transmit(spi->handle, ®_addr, 1, HAL_MAX_DELAY); - - if (ret != HAL_OK) { - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_SET); - return ret; - } - - ret = HAL_SPI_Receive(spi->handle, reg_data, len, HAL_MAX_DELAY); - - if (ret != HAL_OK) { - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_SET); - return ret; - } - - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_SET); - - return 0; -} - -static BMI08_INTF_RET_TYPE bmi088_write_spi(uint8_t reg_addr, const uint8_t *reg_data, uint32_t len, void *intf_ptr) // GCOVR_EXCL_FUNCTION -{ - struct handle_spi* spi = (struct handle_spi*) intf_ptr; - - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_RESET); - - HAL_StatusTypeDef ret = HAL_OK; - - ret = HAL_SPI_Transmit(spi->handle, ®_addr, 1, HAL_MAX_DELAY); - - if (ret != HAL_OK) { - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_SET); - return ret; - } - - ret = HAL_SPI_Transmit(spi->handle, reg_data, len, HAL_MAX_DELAY); - - if (ret != HAL_OK) { - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_SET); - return ret; - } - - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_SET); - - return 0; -} - -static void bmi088_delay_us(uint32_t period, void *intf_ptr) // GCOVR_EXCL_FUNCTION -{ - HAL_Delay(ceil((double)(period)/(1000.0))); -} - -static float bmi088_convert_accel_axis_data(struct bmi088_ctx *ctx, int16_t axis_data) -{ - return ((float)axis_data / 32768.0f * 1000 * pow(2, ctx->dev.accel_cfg.range + 1) * 1.5) * 0.00981; -} - -static float bmi088_convert_gyro_axis_data(struct bmi088_ctx *ctx, int16_t axis_data) -{ - switch (ctx->dev.gyro_cfg.range) { - case BMI08_GYRO_RANGE_2000_DPS: - return CONVERT_GYRO_RAW_RANGE(axis_data, 2000); - case BMI08_GYRO_RANGE_1000_DPS: - return CONVERT_GYRO_RAW_RANGE(axis_data, 1000); - case BMI08_GYRO_RANGE_500_DPS: - return CONVERT_GYRO_RAW_RANGE(axis_data, 500); - case BMI08_GYRO_RANGE_250_DPS: - return CONVERT_GYRO_RAW_RANGE(axis_data, 250); - case BMI08_GYRO_RANGE_125_DPS: - return CONVERT_GYRO_RAW_RANGE(axis_data, 125); - default: - return 0.0f; - } -} - -STATIC bool bmi088_read(void *context, struct packet *packet) -{ - struct bmi088_ctx *ctx = (struct bmi088_ctx*) context; - struct bmi08_sensor_data gyro_data; - struct bmi08_sensor_data accel_data; - - bmi08a_get_data(&accel_data, &ctx->dev); - bmi08g_get_data(&gyro_data, &ctx->dev); - - packet->acceleration_x_mss = bmi088_convert_accel_axis_data(ctx, accel_data.x); - packet->acceleration_y_mss = bmi088_convert_accel_axis_data(ctx, accel_data.y); - packet->acceleration_z_mss = bmi088_convert_accel_axis_data(ctx, accel_data.z); - - packet->angular_velocity_x_rads = bmi088_convert_gyro_axis_data(ctx, gyro_data.x); - packet->angular_velocity_y_rads = bmi088_convert_gyro_axis_data(ctx, gyro_data.y); - packet->angular_velocity_z_rads = bmi088_convert_gyro_axis_data(ctx, gyro_data.z); - return true; -} - -int8_t bmi088_init(struct bmi088_ctx *ctx, struct sensor *sensor) // GCOVR_EXCL_FUNCTION -{ - assert(ctx->accel_spi.handle != NULL); - assert(ctx->gyro_spi.handle != NULL); - - HAL_GPIO_WritePin(ctx->accel_spi.port, ctx->accel_spi.pin, GPIO_PIN_SET); - HAL_GPIO_WritePin(ctx->gyro_spi.port, ctx->gyro_spi.pin, GPIO_PIN_SET); - - struct bmi08_accel_int_channel_cfg accel_new_data_int_cfg; - struct bmi08_gyro_int_channel_cfg gyro_new_data_int_cfg; - - ctx->dev.intf_ptr_accel = &ctx->accel_spi; - ctx->dev.intf_ptr_gyro = &ctx->gyro_spi; - ctx->dev.intf = BMI08_SPI_INTF; - ctx->dev.variant = BMI088_VARIANT; - ctx->dev.read_write_len = 8; - ctx->dev.read = bmi088_read_spi; - ctx->dev.write = bmi088_write_spi; - ctx->dev.delay_us = bmi088_delay_us; - - ctx->dev.accel_cfg.power = BMI08_ACCEL_PM_ACTIVE; - ctx->dev.accel_cfg.range = BMI088_ACCEL_RANGE_24G; - ctx->dev.accel_cfg.bw = BMI08_ACCEL_BW_NORMAL; - ctx->dev.accel_cfg.odr = BMI08_ACCEL_ODR_800_HZ; - - ctx->dev.gyro_cfg.power = BMI08_GYRO_PM_NORMAL; - ctx->dev.gyro_cfg.range = BMI08_GYRO_RANGE_2000_DPS; - ctx->dev.gyro_cfg.bw = BMI08_GYRO_BW_116_ODR_1000_HZ; - ctx->dev.gyro_cfg.odr = BMI08_GYRO_BW_116_ODR_1000_HZ; - - accel_new_data_int_cfg.int_channel = BMI08_INT_CHANNEL_1; - accel_new_data_int_cfg.int_type = BMI08_ACCEL_INT_DATA_RDY; - accel_new_data_int_cfg.int_pin_cfg.output_mode = BMI08_INT_MODE_PUSH_PULL; - accel_new_data_int_cfg.int_pin_cfg.lvl = BMI08_INT_ACTIVE_HIGH; - accel_new_data_int_cfg.int_pin_cfg.enable_int_pin = BMI08_ENABLE; - - gyro_new_data_int_cfg.int_channel = BMI08_INT_CHANNEL_3; - gyro_new_data_int_cfg.int_type = BMI08_GYRO_INT_DATA_RDY; - gyro_new_data_int_cfg.int_pin_cfg.output_mode = BMI08_INT_MODE_PUSH_PULL; - gyro_new_data_int_cfg.int_pin_cfg.lvl = BMI08_INT_ACTIVE_HIGH; - gyro_new_data_int_cfg.int_pin_cfg.enable_int_pin = BMI08_ENABLE; - - int8_t ret = BMI08_OK; - - ret = bmi08a_soft_reset(&ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08xa_init(&ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08a_load_config_file(&ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08a_set_power_mode(&ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08xa_set_meas_conf(&ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08a_set_int_config(&accel_new_data_int_cfg, &ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08g_soft_reset(&ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08g_init(&ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08g_set_power_mode(&ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08g_set_meas_conf(&ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - - ret = bmi08g_set_int_config(&gyro_new_data_int_cfg, &ctx->dev); - - if (ret != BMI08_OK) { - return ret; - } - sensor->ctx = ctx; - sensor->read = bmi088_read; - - return BMI08_OK; -} diff --git a/src/sensors/bmi088.cpp b/src/sensors/bmi088.cpp new file mode 100644 index 0000000..0e02843 --- /dev/null +++ b/src/sensors/bmi088.cpp @@ -0,0 +1,176 @@ +/* + * bmi088.c + * + * Created on: Oct 26, 2025 + * Author: Dhruv Shah + */ + +#include "hal.h" +#include "protocol.h" +#include "sensor.h" + +#include "bmi08.h" +#include "bmi088.h" +#include "bmi08_defs.h" +#include "bmi08x.h" + +#include +#include +#include + +#define CONVERT_GYRO_RAW_RANGE(raw, range) \ + ((((float)raw * (float)range) / 32768.0f) * (std::numbers::pi / 180.0f)) + +namespace { +BMI08_INTF_RET_TYPE bosch_read(uint8_t reg_addr, uint8_t* reg_data, + uint32_t length, void* intf_ptr) { + auto* protocol = static_cast(intf_ptr); + return protocol->read(Platform::ConstSpan(®_addr, 1), + Platform::Span(reg_data, length), + Platform::AddressSize::Byte); +} + +BMI08_INTF_RET_TYPE bosch_write(uint8_t reg_addr, const uint8_t* reg_data, + uint32_t length, void* intf_ptr) { + auto* protocol = static_cast(intf_ptr); + return protocol->write(Platform::ConstSpan(®_addr, 1), + Platform::ConstSpan(reg_data, length), + Platform::AddressSize::Byte); +} + +void bosch_delay(uint32_t period, void* intf_ptr) { + HAL_Delay(ceil(static_cast(period) / (1000.0))); +} + +float bmi088_convert_accel_axis_data(struct bmi08_dev& dev, int16_t axis_data) { + return (static_cast(axis_data) / 32768.0f * 1000 * + pow(2, dev.accel_cfg.range + 1) * 1.5) * + 0.00981; +} + +float bmi088_convert_gyro_axis_data(struct bmi08_dev& dev, int16_t axis_data) { + switch (dev.gyro_cfg.range) { + case BMI08_GYRO_RANGE_2000_DPS: + return CONVERT_GYRO_RAW_RANGE(axis_data, 2000); + case BMI08_GYRO_RANGE_1000_DPS: + return CONVERT_GYRO_RAW_RANGE(axis_data, 1000); + case BMI08_GYRO_RANGE_500_DPS: + return CONVERT_GYRO_RAW_RANGE(axis_data, 500); + case BMI08_GYRO_RANGE_250_DPS: + return CONVERT_GYRO_RAW_RANGE(axis_data, 250); + case BMI08_GYRO_RANGE_125_DPS: + return CONVERT_GYRO_RAW_RANGE(axis_data, 125); + default: + return 0.0f; + } +} +} // namespace + +namespace Platform { +bool BMI088::init() { + struct bmi08_accel_int_channel_cfg accel_new_data_int_cfg; + struct bmi08_gyro_int_channel_cfg gyro_new_data_int_cfg; + + dev.intf_ptr_accel = &accel; + dev.intf_ptr_gyro = &gyro; + dev.intf = BMI08_SPI_INTF; + dev.variant = BMI088_VARIANT; + dev.read_write_len = 8; + dev.read = bosch_read; + dev.write = bosch_write; + dev.delay_us = bosch_delay; + + dev.accel_cfg.power = BMI08_ACCEL_PM_ACTIVE; + dev.accel_cfg.range = BMI088_ACCEL_RANGE_24G; + dev.accel_cfg.bw = BMI08_ACCEL_BW_NORMAL; + dev.accel_cfg.odr = BMI08_ACCEL_ODR_800_HZ; + + dev.gyro_cfg.power = BMI08_GYRO_PM_NORMAL; + dev.gyro_cfg.range = BMI08_GYRO_RANGE_2000_DPS; + dev.gyro_cfg.bw = BMI08_GYRO_BW_116_ODR_1000_HZ; + dev.gyro_cfg.odr = BMI08_GYRO_BW_116_ODR_1000_HZ; + + accel_new_data_int_cfg.int_channel = BMI08_INT_CHANNEL_1; + accel_new_data_int_cfg.int_type = BMI08_ACCEL_INT_DATA_RDY; + accel_new_data_int_cfg.int_pin_cfg.output_mode = BMI08_INT_MODE_PUSH_PULL; + accel_new_data_int_cfg.int_pin_cfg.lvl = BMI08_INT_ACTIVE_HIGH; + accel_new_data_int_cfg.int_pin_cfg.enable_int_pin = BMI08_ENABLE; + + gyro_new_data_int_cfg.int_channel = BMI08_INT_CHANNEL_3; + gyro_new_data_int_cfg.int_type = BMI08_GYRO_INT_DATA_RDY; + gyro_new_data_int_cfg.int_pin_cfg.output_mode = BMI08_INT_MODE_PUSH_PULL; + gyro_new_data_int_cfg.int_pin_cfg.lvl = BMI08_INT_ACTIVE_HIGH; + gyro_new_data_int_cfg.int_pin_cfg.enable_int_pin = BMI08_ENABLE; + + int8_t ret = BMI08_OK; + + ret = bmi08a_soft_reset(&dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08xa_init(&dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08a_load_config_file(&dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08a_set_power_mode(&dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08xa_set_meas_conf(&dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08a_set_int_config(&accel_new_data_int_cfg, &dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08g_soft_reset(&dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08g_init(&dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08g_set_power_mode(&dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08g_set_meas_conf(&dev); + if (ret != BMI08_OK) + return false; + + ret = bmi08g_set_int_config(&gyro_new_data_int_cfg, &dev); + if (ret != BMI08_OK) + return false; + + return true; +} + +bool BMI088::read(Packet& packet) { + struct bmi08_sensor_data gyro_data; + struct bmi08_sensor_data accel_data; + + bmi08a_get_data(&accel_data, &dev); + bmi08g_get_data(&gyro_data, &dev); + + packet.acceleration_x_mss = + bmi088_convert_accel_axis_data(dev, accel_data.x); + packet.acceleration_y_mss = + bmi088_convert_accel_axis_data(dev, accel_data.y); + packet.acceleration_z_mss = + bmi088_convert_accel_axis_data(dev, accel_data.z); + + packet.angular_velocity_x_rads = + bmi088_convert_gyro_axis_data(dev, gyro_data.x); + packet.angular_velocity_y_rads = + bmi088_convert_gyro_axis_data(dev, gyro_data.y); + packet.angular_velocity_z_rads = + bmi088_convert_gyro_axis_data(dev, gyro_data.z); + return true; +} +} // namespace Platform diff --git a/src/sensors/bmp581.c b/src/sensors/bmp581.c deleted file mode 100644 index 4ae2890..0000000 --- a/src/sensors/bmp581.c +++ /dev/null @@ -1,190 +0,0 @@ -#include "bmp581.h" - -#include "sensor.h" -#include "defs.h" - -#include "bmp5.h" -#include "bmp5_defs.h" - -#include -#include -#include - -#define GRAVITY_ACCEL 9.80665f // m/s^2 -#define AIR_MOLAR_MASS 0.0289644f // kg/mol -#define GAS_CONSTANT 8.31446f // J/(mol*K) - -#define TROPOPAUSE_PRESSURE 22630.0f // Pa -#define STRATOSPHERE_MIDDLE_PRESSURE 5475.0f // Pa -#define STANDARD_SEA_LEVEL_PRESSURE 101325.0f // Pa - -#define STANDARD_SEA_LEVEL_TEMP 288.15f // K -#define STRATOSPHERE_BASE_TEMP 216.65f // K - -#define TROPOPAUSE_BASE_ALTITUDE 11000.0f // m -#define STRATOSPHERE_MIDDLE_BASE_ALTITUDE 20000.0f // m - -#define TROPOSPHERE_LAPSE_RATE -0.0065f // K/m -#define UPPER_STRATOSPHERE_LAPSE_RATE 0.001f // K/m - -static inline float calc_altitude_troposphere_msl(float pressure) -{ - float exponent = (-GAS_CONSTANT * TROPOSPHERE_LAPSE_RATE) / (GRAVITY_ACCEL * AIR_MOLAR_MASS); - float pressure_ratio = pressure / STANDARD_SEA_LEVEL_PRESSURE; - float power_term = pow(pressure_ratio, exponent) - 1; - - return (STANDARD_SEA_LEVEL_TEMP / TROPOSPHERE_LAPSE_RATE) * power_term; -} - -static inline float calc_altitude_lower_stratosphere_msl(float pressure) -{ - float log_ratio = log(pressure / TROPOPAUSE_PRESSURE); - float scale_factor = (GAS_CONSTANT * STRATOSPHERE_BASE_TEMP) / (GRAVITY_ACCEL * AIR_MOLAR_MASS); - - return TROPOPAUSE_BASE_ALTITUDE - (scale_factor * log_ratio); -} - -static inline float calc_altitude_upper_stratosphere_msl(float pressure) -{ - float exponent = (-GAS_CONSTANT * UPPER_STRATOSPHERE_LAPSE_RATE) / (GRAVITY_ACCEL * AIR_MOLAR_MASS); - float pressure_ratio = pressure / STRATOSPHERE_MIDDLE_PRESSURE; - float power_term = pow(pressure_ratio, exponent) - 1; - - return STRATOSPHERE_MIDDLE_BASE_ALTITUDE + (STRATOSPHERE_BASE_TEMP / UPPER_STRATOSPHERE_LAPSE_RATE) * power_term; -} - -static inline float bmp581_estimate_altitude_msl(struct bmp5_sensor_data *data) -{ - if (data->pressure > TROPOPAUSE_PRESSURE) { - return calc_altitude_troposphere_msl(data->pressure); - } else if (data->pressure > STRATOSPHERE_MIDDLE_PRESSURE) { - return calc_altitude_lower_stratosphere_msl(data->pressure); - } else { - return calc_altitude_upper_stratosphere_msl(data->pressure); - } -} - -#ifdef HAL_I2C_MODULE_ENABLED -static BMP5_INTF_RET_TYPE read_i2c(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, void *intf_ptr) // GCOVR_EXCL_FUNCTION -{ - struct handle_i2c *i2c = (struct handle_i2c*) intf_ptr; - HAL_StatusTypeDef res = HAL_I2C_Mem_Read(i2c->handle, i2c->address << 1, reg_addr, I2C_MEMADD_SIZE_8BIT, reg_data, length, HAL_MAX_DELAY); - return res; -} - -static BMP5_INTF_RET_TYPE write_i2c(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, void *intf_ptr) // GCOVR_EXCL_FUNCTION -{ - struct handle_i2c *i2c = (struct handle_i2c*) intf_ptr; - HAL_StatusTypeDef res = HAL_I2C_Mem_Write(i2c->handle, i2c->address << 1, reg_addr, I2C_MEMADD_SIZE_8BIT, reg_data, length, HAL_MAX_DELAY); - return res; -} -#endif - -static BMP5_INTF_RET_TYPE read_spi(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, void *intf_ptr) // GCOVR_EXCL_FUNCTION -{ - struct handle_spi *spi = (struct handle_spi*) intf_ptr; - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_RESET); - HAL_SPI_Transmit(spi->handle, ®_addr, 1, HAL_MAX_DELAY); - - HAL_StatusTypeDef res = HAL_SPI_Receive(spi->handle, reg_data, length, HAL_MAX_DELAY); - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_SET); - return res; -} - -static BMP5_INTF_RET_TYPE write_spi(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, void *intf_ptr) // GCOVR_EXCL_FUNCTION -{ - struct handle_spi *spi = (struct handle_spi*) intf_ptr; - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_RESET); - HAL_SPI_Transmit(spi->handle, ®_addr, 1, HAL_MAX_DELAY); - - HAL_StatusTypeDef res = HAL_SPI_Transmit(spi->handle, reg_data, length, HAL_MAX_DELAY); - HAL_GPIO_WritePin(spi->port, spi->pin, GPIO_PIN_SET); - return res; -} - -static void delay(uint32_t period, void *intf_ptr) // GCOVR_EXCL_FUNCTION -{ - HAL_Delay(ceil((double)(period)/(1000.0))); -} - -STATIC bool bmp581_read(void *context, struct packet *packet) -{ - struct bmp581_ctx *ctx = (struct bmp581_ctx*) context; - struct bmp5_sensor_data data; - - bmp5_get_sensor_data(&data, &(ctx->odr_config), &(ctx->dev)); - - packet->barometer_hMSL_m = bmp581_estimate_altitude_msl(&data); - packet->temperature_c = data.temperature; - packet->kf_position_m = data.pressure; - return true; -} - -int8_t bmp581_init(struct bmp581_ctx *ctx, struct sensor *sensor) // GCOVR_EXCL_FUNCTION -{ - int8_t result = BMP5_OK; - - switch (ctx->handle.protocol) { - case SPI: - ctx->dev.intf_ptr = &ctx->handle.def.spi; - ctx->dev.intf = BMP5_SPI_INTF; - ctx->dev.read = read_spi; - ctx->dev.write = write_spi; - break; - case I2C: -#ifdef HAL_I2C_MODULE_ENABLED - ctx->dev.intf_ptr = &ctx->handle.def.i2c; - ctx->dev.intf = BMP5_I2C_INTF; - ctx->dev.read = read_i2c; - ctx->dev.write = write_i2c; - break; -#else - assert("I2C module is not enabled! Please choose SPI for BMP581"); - break; -#endif - default: assert("Invalid interface for bmp581, must choose either SPI or I2C"); - }; - - ctx->dev.delay_us = delay; - ctx->odr_config.odr = BMP5_ODR_240_HZ; - ctx->odr_config.press_en = BMP5_ENABLE; - ctx->int_config.drdy_en = BMP5_ENABLE; - ctx->int_config.fifo_full_en = BMP5_DISABLE; - ctx->int_config.fifo_thres_en = BMP5_DISABLE; - ctx->int_config.oor_press_en = BMP5_DISABLE; - - sensor->ctx = ctx; - sensor->read = bmp581_read; - - bmp5_soft_reset(&(ctx->dev)); - - // Initialize the device - result = bmp5_init(&(ctx->dev)); - if (result != BMP5_OK) { - return result; - } - - // Set odr frequency - result = bmp5_set_osr_odr_press_config(&(ctx->odr_config), &(ctx->dev)); - if (result != BMP5_OK) { - return result; - } - - result = bmp5_int_source_select(&(ctx->int_config), &(ctx->dev)); - if (result != BMP5_OK) { - return result; - } - // Enable interrupt handler - result = bmp5_configure_interrupt(BMP5_PULSED, BMP5_ACTIVE_HIGH, BMP5_INTR_PUSH_PULL, BMP5_INTR_ENABLE, &(ctx->dev)); - if (result != BMP5_OK) { - return result; - } - - result = bmp5_set_power_mode(BMP5_POWERMODE_NORMAL, &(ctx->dev)); - return result; -} - -int8_t bmp581_get_power_mode(struct bmp581_ctx *ctx, enum bmp5_powermode *powermode) // GCOVR_EXCL_FUNCTION -{ - return bmp5_get_power_mode(powermode, &(ctx->dev)); -} diff --git a/src/sensors/bmp581.cpp b/src/sensors/bmp581.cpp new file mode 100644 index 0000000..90ac392 --- /dev/null +++ b/src/sensors/bmp581.cpp @@ -0,0 +1,145 @@ +#include "bmp581.h" + +#include "hal.h" +#include "protocol.h" +#include "sensor.h" + +#include "bmp5.h" +#include "bmp5_defs.h" + +#include +#include +#include + +constexpr auto GRAVITY_ACCEL = 9.80665f; // m/s^2 +constexpr auto AIR_MOLAR_MASS = 0.0289644f; // kg/mol +constexpr auto GAS_CONSTANT = 8.31446f; // J/(mol*K) + +constexpr auto TROPOPAUSE_PRESSURE = 22630.0f; // Pa +constexpr auto STRATOSPHERE_MIDDLE_PRESSURE = 5475.0f; // Pa +constexpr auto STANDARD_SEA_LEVEL_PRESSURE = 101325.0f; // Pa + +constexpr auto STANDARD_SEA_LEVEL_TEMP = 288.15f; // K +constexpr auto STRATOSPHERE_BASE_TEMP = 216.65f; // K + +constexpr auto TROPOPAUSE_BASE_ALTITUDE = 11000.0f; // m +constexpr auto STRATOSPHERE_MIDDLE_BASE_ALTITUDE = 20000.0f; // m + +constexpr auto TROPOSPHERE_LAPSE_RATE = -0.0065f; // K/m +constexpr auto UPPER_STRATOSPHERE_LAPSE_RATE = 0.001f; // K/m + +namespace { +inline float calc_altitude_troposphere_msl(float pressure) { + constexpr auto exponent = (-GAS_CONSTANT * TROPOSPHERE_LAPSE_RATE) / + (GRAVITY_ACCEL * AIR_MOLAR_MASS); + float pressure_ratio = pressure / STANDARD_SEA_LEVEL_PRESSURE; + float power_term = pow(pressure_ratio, exponent) - 1; + + return (STANDARD_SEA_LEVEL_TEMP / TROPOSPHERE_LAPSE_RATE) * power_term; +} + +inline float calc_altitude_lower_stratosphere_msl(float pressure) { + constexpr auto scale_factor = (GAS_CONSTANT * STRATOSPHERE_BASE_TEMP) / + (GRAVITY_ACCEL * AIR_MOLAR_MASS); + float log_ratio = log(pressure / TROPOPAUSE_PRESSURE); + + return TROPOPAUSE_BASE_ALTITUDE - (scale_factor * log_ratio); +} + +inline float calc_altitude_upper_stratosphere_msl(float pressure) { + constexpr auto exponent = (-GAS_CONSTANT * UPPER_STRATOSPHERE_LAPSE_RATE) / + (GRAVITY_ACCEL * AIR_MOLAR_MASS); + float pressure_ratio = pressure / STRATOSPHERE_MIDDLE_PRESSURE; + float power_term = pow(pressure_ratio, exponent) - 1; + + return STRATOSPHERE_MIDDLE_BASE_ALTITUDE + + (STRATOSPHERE_BASE_TEMP / UPPER_STRATOSPHERE_LAPSE_RATE) * + power_term; +} + +inline float bmp581_estimate_altitude_msl(struct bmp5_sensor_data* data) { + if (data->pressure > TROPOPAUSE_PRESSURE) + return calc_altitude_troposphere_msl(data->pressure); + else if (data->pressure > STRATOSPHERE_MIDDLE_PRESSURE) + return calc_altitude_lower_stratosphere_msl(data->pressure); + else + return calc_altitude_upper_stratosphere_msl(data->pressure); +} + +BMP5_INTF_RET_TYPE bosch_read(uint8_t reg_addr, uint8_t* reg_data, + uint32_t length, void* intf_ptr) { + auto* protocol = static_cast(intf_ptr); + return protocol->read(Platform::ConstSpan(®_addr, 1), + Platform::Span(reg_data, length), + Platform::AddressSize::Byte); +} + +BMP5_INTF_RET_TYPE bosch_write(uint8_t reg_addr, const uint8_t* reg_data, + uint32_t length, void* intf_ptr) { + auto* protocol = static_cast(intf_ptr); + return protocol->write(Platform::ConstSpan(®_addr, 1), + Platform::ConstSpan(reg_data, length), + Platform::AddressSize::Byte); +} + +void bosch_delay(uint32_t period, void* intf_ptr) { + HAL_Delay(ceil((double)(period) / (1000.0))); +} +} // namespace + +namespace Platform { +bool BMP581::init() { + int8_t result = BMP5_OK; + dev.intf_ptr = &api; + dev.intf = BMP5_SPI_INTF; + dev.read = bosch_read; + dev.write = bosch_write; + dev.delay_us = bosch_delay; + + odr_config.odr = BMP5_ODR_240_HZ; + odr_config.press_en = BMP5_ENABLE; + int_config.drdy_en = BMP5_ENABLE; + int_config.fifo_full_en = BMP5_DISABLE; + int_config.fifo_thres_en = BMP5_DISABLE; + int_config.oor_press_en = BMP5_DISABLE; + + bmp5_soft_reset(&dev); + + // Initialize the device + result = bmp5_init(&dev); + if (result != BMP5_OK) + return false; + + // Set odr frequency + result = bmp5_set_osr_odr_press_config(&odr_config, &dev); + if (result != BMP5_OK) + return false; + + result = bmp5_int_source_select(&int_config, &dev); + if (result != BMP5_OK) + return false; + + // Enable interrupt handler + result = + bmp5_configure_interrupt(BMP5_PULSED, BMP5_ACTIVE_HIGH, + BMP5_INTR_PUSH_PULL, BMP5_INTR_ENABLE, &dev); + if (result != BMP5_OK) + return false; + + result = bmp5_set_power_mode(BMP5_POWERMODE_NORMAL, &dev); + if (result != BMP5_OK) + return false; + + return true; +} + +bool BMP581::read(Packet& packet) { + struct bmp5_sensor_data data; + + bmp5_get_sensor_data(&data, &odr_config, &dev); + packet.barometer_hMSL_m = bmp581_estimate_altitude_msl(&data); + packet.temperature_c = data.temperature; + packet.kf_position_m = data.pressure; + return true; +} +} // namespace Platform diff --git a/src/sensors/CD-PA1616S.c b/src/sensors/cd-pa1616s.cpp similarity index 65% rename from src/sensors/CD-PA1616S.c rename to src/sensors/cd-pa1616s.cpp index 7716ba7..3c5ca72 100644 --- a/src/sensors/CD-PA1616S.c +++ b/src/sensors/cd-pa1616s.cpp @@ -5,34 +5,39 @@ * Author: Mahir Shah */ -#include "CD-PA1616S.h" - -#include "sensor.h" -#include "defs.h" +#include "cd-pa1616s.h" +#include "hal.h" #include -#include -#include #include +#include +#include + +namespace Platform { +bool GPS::init() { + static constexpr uint8_t command[] = + "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\x0d\x0a"; + // Initialize GPS DMA Reception + if (!protocol.write({}, ConstSpan(command), AddressSize::None)) + return false; + if (!protocol.readDMA(Span(buffer))) + return false; + return true; +} // ParseGPSData: Single function to find and parse $GNGGA / $GPGGA // Returns 1 if successful, 0 otherwise -STATIC bool gps_read(void *context, struct packet *packet) -{ - struct gps_ctx *ctx = (struct gps_ctx*) context; - char *buffer = (char*) ctx->buffer; - +bool GPS::read(Packet& packet) { // Search manually for either "$GNGGA" or "$GPGGA" in buffer - const char *gga_start = NULL; + const uint8_t* gga_start = NULL; for (int i = 0; buffer[i] != '\0'; i++) { if (buffer[i] == '$') { // Check if we match "$GNGGA" or "$GPGGA" - if (strncmp(&buffer[i], "$GNGGA", 6) == 0 || - strncmp(&buffer[i], "$GPGGA", 6) == 0) - { - gga_start = &buffer[i]; - break; - } + if (memcmp(&buffer[i], "$GNGGA", 6) == 0 || + memcmp(&buffer[i], "$GPGGA", 6) == 0) { + gga_start = &buffer[i]; + break; + } } } @@ -44,14 +49,11 @@ STATIC bool gps_read(void *context, struct packet *packet) // Copy one line (until CR or LF) into a local buffer char line[120]; int idx = 0; - while (gga_start[idx] != '\0' && - gga_start[idx] != '\r' && - gga_start[idx] != '\n' && - idx < (int)sizeof(line) - 1) - { - line[idx] = gga_start[idx]; - idx++; - } + while (gga_start[idx] != '\0' && gga_start[idx] != '\r' && + gga_start[idx] != '\n' && idx < (int)sizeof(line) - 1) { + line[idx] = gga_start[idx]; + idx++; + } line[idx] = '\0'; // Split into fields by commas. We'll store them in fields[0..]. @@ -62,10 +64,11 @@ STATIC bool gps_read(void *context, struct packet *packet) for (int j = 0; j < idx; j++) { if (line[j] == ',') { - fields[fieldIndex][charIndex] = '\0'; // end current field + fields[fieldIndex][charIndex] = '\0'; // end current field fieldIndex++; charIndex = 0; - if (fieldIndex >= 20) break; + if (fieldIndex >= 20) + break; } else { if (charIndex < 19) { fields[fieldIndex][charIndex++] = line[j]; @@ -135,29 +138,11 @@ STATIC bool gps_read(void *context, struct packet *packet) uint8_t sats = (uint8_t)atoi(fields[7]); float alt = (fields[9][0] != '\0') ? atof(fields[9]) : 0.0f; - packet->latitude_degrees = lat; - packet->longitude_degrees = lon; - packet->gpsFixType = fix; - packet->numSatellites = sats; - packet->gps_hMSL_m = alt; - return true; -} - -bool gps_init(struct gps_ctx *ctx, struct sensor *sensor) -{ -#ifdef HAL_UART_MODULE_ENABLED - assert(ctx->handle.def.uart.handle != NULL); - - sensor->ctx = ctx; - sensor->read = gps_read; - - // Initialize GPS DMA Reception - char command[] = "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\x0d\x0a"; - HAL_UART_Transmit(ctx->handle.def.uart.handle, (uint8_t*) command, sizeof(command) - 1, HAL_MAX_DELAY); - HAL_UARTEx_ReceiveToIdle_DMA(ctx->handle.def.uart.handle, ctx->buffer, BUFFER_SIZE); + packet.latitude_degrees = lat; + packet.longitude_degrees = lon; + packet.gpsFixType = fix; + packet.numSatellites = sats; + packet.gps_hMSL_m = alt; return true; -#else - assert("UART module is not enabled! CD-PA161 only supports UART!"); - return false; -#endif } +} // namespace Platform diff --git a/test/sensors/test_bmp581.c b/test/sensors/test_bmp581.c deleted file mode 100644 index 2012156..0000000 --- a/test/sensors/test_bmp581.c +++ /dev/null @@ -1,116 +0,0 @@ -#include "unity.h" -#include "cmock.h" - -#include "mock_bmp5.h" - -#include "bmp581.h" -#include "sensor.h" - -bool bmp581_read(void *context, struct packet *packet); - -void set_up() {} -void tear_down() {} - -// Try using both calculators. First one seems to be for tropo only -// https://codingace.net/physics/barometric_pressure_to_altitude.html -// https://www.sensorsone.com/us-standard-atmosphere-altitude-pressure-calculator/ -// Select lapse-rate model and enter your mock pressure. For tests, -// we make the assumption of standard sea level conditions, ie -// pressure at 101325 Pa and temperature of around 15 celcius. - -void test_bmp581_read_sea() -{ - struct bmp581_ctx ctx = {0}; - struct packet packet; - - float fake_pressure = 101325.0 - 3.0; - float fake_temperature = 15.3; - struct bmp5_sensor_data fake = { - .pressure = fake_pressure, - .temperature = fake_temperature, - }; - - bmp5_get_sensor_data_ExpectAnyArgsAndReturn(true); - bmp5_get_sensor_data_ReturnMemThruPtr_sensor_data(&fake, sizeof(fake)); - - bool result = bmp581_read(&ctx, &packet); - - float expected_alt = 0.249734; - TEST_ASSERT_EQUAL_FLOAT(fake_temperature, packet.temperature_c); - TEST_ASSERT_EQUAL_FLOAT(fake_pressure, packet.kf_position_m); - TEST_ASSERT_FLOAT_WITHIN(0.001, expected_alt, packet.barometer_hMSL_m); -} - -void test_bmp581_read_tropo() -{ - struct bmp581_ctx ctx = {0}; - struct packet packet; - - float fake_pressure = 22630.0f + 41.0; - float fake_temperature = -51.23; - struct bmp5_sensor_data fake = { - .pressure = fake_pressure, - .temperature = fake_temperature, - }; - - bmp5_get_sensor_data_ExpectAnyArgsAndReturn(true); - bmp5_get_sensor_data_ReturnMemThruPtr_sensor_data(&fake, sizeof(fake)); - - bool result = bmp581_read(&ctx, &packet); - - float expected_alt = 10989.260441; - TEST_ASSERT_EQUAL_FLOAT(fake_temperature, packet.temperature_c); - TEST_ASSERT_EQUAL_FLOAT(fake_pressure, packet.kf_position_m); - // Formula gets less accurate higher up, so it is fine to be less strict - TEST_ASSERT_FLOAT_WITHIN(1, expected_alt, packet.barometer_hMSL_m); -} - - -// Lower stratosphere is isothermal as oppose to lapse (upper stratosphere) -void test_bmp581_read_strato_lower() -{ - struct bmp581_ctx ctx = {0}; - struct packet packet; - - float fake_pressure = 5475.0f + 1030.0; - float fake_temperature = -51.23; - struct bmp5_sensor_data fake = { - .pressure = fake_pressure, - .temperature = fake_temperature, - }; - - bmp5_get_sensor_data_ExpectAnyArgsAndReturn(true); - bmp5_get_sensor_data_ReturnMemThruPtr_sensor_data(&fake, sizeof(fake)); - - bool result = bmp581_read(&ctx, &packet); - - float expected_alt = 18906.71; - TEST_ASSERT_EQUAL_FLOAT(fake_temperature, packet.temperature_c); - TEST_ASSERT_EQUAL_FLOAT(fake_pressure, packet.kf_position_m); - // Formula gets less accurate higher up, so it is fine to be less strict - TEST_ASSERT_FLOAT_WITHIN(1, expected_alt, packet.barometer_hMSL_m); -} - -void test_bmp581_read_strato_upper() -{ - struct bmp581_ctx ctx = {0}; - struct packet packet; - - float fake_pressure = 5475.0f - 43.0; - float fake_temperature = -56.23; - struct bmp5_sensor_data fake = { - .pressure = fake_pressure, - .temperature = fake_temperature, - }; - - bmp5_get_sensor_data_ExpectAnyArgsAndReturn(true); - bmp5_get_sensor_data_ReturnMemThruPtr_sensor_data(&fake, sizeof(fake)); - - bool result = bmp581_read(&ctx, &packet); - - float expected_alt = 20049.8797; - TEST_ASSERT_EQUAL_FLOAT(fake_temperature, packet.temperature_c); - TEST_ASSERT_EQUAL_FLOAT(fake_pressure, packet.kf_position_m); - TEST_ASSERT_FLOAT_WITHIN(1, expected_alt, packet.barometer_hMSL_m); -} - diff --git a/test/support/stub_hal.h b/test/support/stub_hal.h deleted file mode 100644 index c732ff9..0000000 --- a/test/support/stub_hal.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef FAKE_STM32F4XX_HAL_H -#define FAKE_STM32F4XX_HAL_H - -#include - -typedef int HAL_StatusTypeDef; -#define HAL_OK 0 -#define HAL_MAX_DELAY 0 - -typedef struct { uint32_t dummy; } I2C_HandleTypeDef; -typedef struct { uint32_t dummy; } SPI_HandleTypeDef; -typedef struct { uint32_t dummy; } GPIO_TypeDef; -typedef struct { uint32_t dummy; } UART_HandleTypeDef; - -#define HAL_I2C_Mem_Read(...) (0) -#define HAL_I2C_Mem_Write(...) (0) -#define HAL_I2C_GetError(...) (0) -#define HAL_SPI_Transmit(...) (0) -#define HAL_SPI_Receive(...) (0) -#define HAL_GPIO_WritePin(...) (0) -#define HAL_GPIO_ReadPin(...) (0) -#define HAL_UART_Transmit(...) (0) -#define HAL_Delay(...) (0) - -#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..4b03e4f --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(common_drivers_tests) + +add_subdirectory(sensors) +add_subdirectory(mocks) +target_link_libraries(common_drivers_tests PRIVATE common_drivers fff GTest::gtest_main gmock) + +target_compile_options(common_drivers_tests PRIVATE --coverage) +target_link_options(common_drivers_tests PRIVATE --coverage) + +include(GoogleTest) +gtest_discover_tests(common_drivers_tests) diff --git a/tests/mocks/CMakeLists.txt b/tests/mocks/CMakeLists.txt new file mode 100644 index 0000000..66520dd --- /dev/null +++ b/tests/mocks/CMakeLists.txt @@ -0,0 +1 @@ +target_include_directories(common_drivers PUBLIC ".") diff --git a/tests/mocks/protocol_mock.h b/tests/mocks/protocol_mock.h new file mode 100644 index 0000000..0c028c6 --- /dev/null +++ b/tests/mocks/protocol_mock.h @@ -0,0 +1,17 @@ +#include "protocol.h" + +#include + +namespace Platform { +class MockProtocol : public Protocol { +public: + explicit MockProtocol(ProtocolType type) : Protocol(type) {} + MOCK_METHOD(bool, read, (ConstSpan cmd, Span buffer, AddressSize size), + (override)); + MOCK_METHOD(bool, write, + (ConstSpan cmd, ConstSpan buffer, AddressSize size), + (override)); + MOCK_METHOD(bool, readDMA, (Span buffer), (override)); + MOCK_METHOD(bool, configure, (Config config), (override)); +}; +} // namespace Platform diff --git a/tests/sensors/CMakeLists.txt b/tests/sensors/CMakeLists.txt new file mode 100644 index 0000000..f4f9808 --- /dev/null +++ b/tests/sensors/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(common_drivers_tests PRIVATE "bmp581_test.cpp") diff --git a/tests/sensors/bmp581_test.cpp b/tests/sensors/bmp581_test.cpp new file mode 100644 index 0000000..83271f6 --- /dev/null +++ b/tests/sensors/bmp581_test.cpp @@ -0,0 +1,135 @@ +#include "bmp5.h" +#include "bmp581.h" +#include "protocol_mock.h" + +#include +#include +#include + +/* Configure FFF to use std::function, which enables capturing lambdas */ +#define CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN, FUNCNAME, ...) \ + std::function FUNCNAME +#include "fff.h" + +// Mock the bosch functions, since they are C, we can't touch them with gtest +// We assume bosch tests their functions already and so we only care about +// the logic in our code rather than in theirs. +// +// For some of our other drivers where we write more of the logic, we might +// want to properly mock the HAL as well, but we don't have to here. +DEFINE_FFF_GLOBALS; +extern "C" { +#include "bmp5.h" +#include "bmp5_defs.h" + +FAKE_VALUE_FUNC(int8_t, bmp5_soft_reset, struct bmp5_dev*); +FAKE_VALUE_FUNC(int8_t, bmp5_init, struct bmp5_dev*); +FAKE_VALUE_FUNC(int8_t, bmp5_set_osr_odr_press_config, + const struct bmp5_osr_odr_press_config*, struct bmp5_dev*); +FAKE_VALUE_FUNC(int8_t, bmp5_int_source_select, + const struct bmp5_int_source_select*, struct bmp5_dev*); +FAKE_VALUE_FUNC(int8_t, bmp5_configure_interrupt, enum bmp5_intr_mode, + enum bmp5_intr_polarity, enum bmp5_intr_drive, + enum bmp5_intr_en_dis, struct bmp5_dev*); +FAKE_VALUE_FUNC(int8_t, bmp5_get_sensor_data, struct bmp5_sensor_data*, + const struct bmp5_osr_odr_press_config*, struct bmp5_dev*); +FAKE_VALUE_FUNC(int8_t, bmp5_set_power_mode, enum bmp5_powermode, + struct bmp5_dev*); +} + +// This lets us return a value by reference using fff +auto mock_sensor_data(float pressure, float temp) { + return [=](struct bmp5_sensor_data* out, auto, auto) { + *out = {.pressure = pressure, .temperature = temp}; + return BMP5_OK; + }; +} + +// Try using both calculators to get atmosphere data and verify if our +// algorithms are correct. First one seems to be for tropo only. +// +// https://codingace.net/physics/barometric_pressure_to_altitude.html +// https://www.sensorsone.com/us-standard-atmosphere-altitude-pressure-calculator/ +// +// Select lapse-rate model and enter your mock pressure. For tests, +// we make the assumption of standard sea level conditions, ie +// pressure at 101325 Pa and temperature of around 15 celcius. +namespace Platform { +class BMP581Test : public testing::Test { + protected: + MockProtocol protocol = MockProtocol(ProtocolType::SPI); + + BMP581Test() {} + void SetUp() override { + FFF_RESET_HISTORY(); + RESET_FAKE(bmp5_get_sensor_data); + } +}; + +TEST_F(BMP581Test, ShouldReadSeaLevelCorrectly) { + float fake_pressure = 101325.0 - 3.0; + float fake_temperature = 15.3; + bmp5_get_sensor_data_fake.custom_fake = + mock_sensor_data(fake_pressure, fake_temperature); + + auto packet = Packet(); + auto sensor = BMP581(protocol); + sensor.read(packet); + + float expected_alt = 0.249734; + EXPECT_FLOAT_EQ(fake_temperature, packet.temperature_c); + EXPECT_FLOAT_EQ(fake_pressure, packet.kf_position_m); + EXPECT_NEAR(expected_alt, packet.barometer_hMSL_m, 0.01); +} + +// Formula gets less accurate higher up, so it is fine to be less strict +// with regards the the altitude check +TEST_F(BMP581Test, ShouldReadTroposphereCorrectly) { + float fake_pressure = 22630.0f + 41.0; + float fake_temperature = -51.23; + bmp5_get_sensor_data_fake.custom_fake = + mock_sensor_data(fake_pressure, fake_temperature); + + auto packet = Packet(); + auto sensor = BMP581(protocol); + sensor.read(packet); + + float expected_alt = 10989.260441; + EXPECT_FLOAT_EQ(fake_temperature, packet.temperature_c); + EXPECT_FLOAT_EQ(fake_pressure, packet.kf_position_m); + EXPECT_NEAR(expected_alt, packet.barometer_hMSL_m, 1); +} + +// Lower stratosphere is isothermal as oppose to lapse (upper stratosphere) +TEST_F(BMP581Test, ShouldReadLowerStratosphereCorrectly) { + float fake_pressure = 5475.0f + 1030.0; + float fake_temperature = -51.23; + bmp5_get_sensor_data_fake.custom_fake = + mock_sensor_data(fake_pressure, fake_temperature); + + auto packet = Packet(); + auto sensor = BMP581(protocol); + sensor.read(packet); + + float expected_alt = 18906.71; + EXPECT_FLOAT_EQ(fake_temperature, packet.temperature_c); + EXPECT_FLOAT_EQ(fake_pressure, packet.kf_position_m); + EXPECT_NEAR(expected_alt, packet.barometer_hMSL_m, 1); +} + +TEST_F(BMP581Test, ShouldReadUpperStratosphereCorrectly) { + float fake_pressure = 5475.0f - 43.0; + float fake_temperature = -56.23; + bmp5_get_sensor_data_fake.custom_fake = + mock_sensor_data(fake_pressure, fake_temperature); + + auto packet = Packet(); + auto sensor = BMP581(protocol); + sensor.read(packet); + + float expected_alt = 20049.8797; + EXPECT_FLOAT_EQ(fake_temperature, packet.temperature_c); + EXPECT_FLOAT_EQ(fake_pressure, packet.kf_position_m); + EXPECT_NEAR(expected_alt, packet.barometer_hMSL_m, 1); +} +} // namespace Platform From 9cf181cdaf7254477d8397002cc5c2353c65f731 Mon Sep 17 00:00:00 2001 From: wispl Date: Sun, 23 Aug 2026 19:55:08 -0400 Subject: [PATCH 2/6] feat: add logging --- include/log.h | 8 ++++++++ src/CMakeLists.txt | 2 ++ src/flash/flash.cpp | 4 ++++ src/flash/gd5f1gq5xe.cpp | 2 ++ src/log.cpp | 33 +++++++++++++++++++++++++++++++++ src/sensors/bmi088.cpp | 12 ++++++++++++ src/sensors/bmp581.cpp | 13 ++++++++++++- 7 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 include/log.h create mode 100644 src/log.cpp diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..bb4c2b0 --- /dev/null +++ b/include/log.h @@ -0,0 +1,8 @@ +#ifndef LOGGER_H +#define LOGGER_H + +namespace Platform { + void log(const char* fmt, ...); +} // namespace Platform + +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1c7f308..c172a6c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,5 @@ add_subdirectory(flash) add_subdirectory(protocols) add_subdirectory(sensors) + +target_sources(common_drivers PRIVATE "log.cpp") diff --git a/src/flash/flash.cpp b/src/flash/flash.cpp index cf3f988..c67cd6f 100644 --- a/src/flash/flash.cpp +++ b/src/flash/flash.cpp @@ -1,5 +1,6 @@ #include "flash.h" +#include "log.h" #include "lfs.h" #include @@ -81,12 +82,15 @@ bool Flash::append(lfs_file_t* file, const uint8_t* bytes, size_t size) { // Due to constraints, the size is guaranteed to be less // than a block size, so we can just safely do this write_size = size; + log("Flash synced", size); } uint32_t res = lfs_file_write(&lfs, file, buf, write_size); if (res != write_size) return false; + Platform::log("Flash wrote %u\r\n", res); + size -= res; filesize += res; buf += res; diff --git a/src/flash/gd5f1gq5xe.cpp b/src/flash/gd5f1gq5xe.cpp index 1326a64..24c3233 100644 --- a/src/flash/gd5f1gq5xe.cpp +++ b/src/flash/gd5f1gq5xe.cpp @@ -1,5 +1,6 @@ #include "gd5f1gq5xe.h" +#include "log.h" #include "hal.h" #include "protocol.h" @@ -262,6 +263,7 @@ bool GD5F1GQ5XE::init() { protocol.configure(Config::QSPI_Data4); } + log("Flash init finished!"); return true; } } // namespace Platform diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..303cc42 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,33 @@ +#include "log.h" + +#ifdef USE_SWO_DEBUG +#include "hal.h" +#else +#include "usbd_cdc_if.h" +#endif + +#include +#include +#include + +namespace Platform { +// TODO: compile this out for release builds +void log(const char* fmt, ...) { + char buf[256]; + va_list args; + va_start(args, fmt); + int len = vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + if (len <= 0) + return; + +#ifdef USE_SWO_DEBUG + for (int i = 0; i < len; ++i) + ITM_SendChar(buf[i]); +#else + while (CDC_Transmit_FS(reinterpret_cast(buf), len) == USBD_BUSY) + HAL_Delay(1); +#endif +} +} // namespace Platform diff --git a/src/sensors/bmi088.cpp b/src/sensors/bmi088.cpp index 0e02843..ee6813a 100644 --- a/src/sensors/bmi088.cpp +++ b/src/sensors/bmi088.cpp @@ -5,6 +5,7 @@ * Author: Dhruv Shah */ +#include "log.h" #include "hal.h" #include "protocol.h" #include "sensor.h" @@ -105,46 +106,57 @@ bool BMI088::init() { int8_t ret = BMI08_OK; ret = bmi08a_soft_reset(&dev); + log("BMI088a soft reset returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08xa_init(&dev); + log("BMI088a init returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08a_load_config_file(&dev); + log("BMI088a load returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08a_set_power_mode(&dev); + log("BMI088a power returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08xa_set_meas_conf(&dev); + log("BMI088a meas returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08a_set_int_config(&accel_new_data_int_cfg, &dev); + log("BMI088a interrupt returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_soft_reset(&dev); + log("BMI088g reset returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_init(&dev); + log("BMI088g init returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_set_power_mode(&dev); + log("BMI088g power returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_set_meas_conf(&dev); + log("BMI088g meas returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_set_int_config(&gyro_new_data_int_cfg, &dev); + log("BMI088g interrupt returned %d\r\n", ret); if (ret != BMI08_OK) return false; diff --git a/src/sensors/bmp581.cpp b/src/sensors/bmp581.cpp index 90ac392..ff31c66 100644 --- a/src/sensors/bmp581.cpp +++ b/src/sensors/bmp581.cpp @@ -1,6 +1,7 @@ #include "bmp581.h" #include "hal.h" +#include "log.h" #include "protocol.h" #include "sensor.h" @@ -11,6 +12,8 @@ #include #include +#include "usbd_cdc_if.h" + constexpr auto GRAVITY_ACCEL = 9.80665f; // m/s^2 constexpr auto AIR_MOLAR_MASS = 0.0289644f; // kg/mol constexpr auto GAS_CONSTANT = 8.31446f; // J/(mol*K) @@ -103,19 +106,25 @@ bool BMP581::init() { int_config.fifo_thres_en = BMP5_DISABLE; int_config.oor_press_en = BMP5_DISABLE; - bmp5_soft_reset(&dev); + result = bmp5_soft_reset(&dev); + log("BMP581 reset returned %d\r\n", result); + if (result != BMP5_OK) + return false; // Initialize the device result = bmp5_init(&dev); + log("BMP581 init returned %d\r\n", result); if (result != BMP5_OK) return false; // Set odr frequency result = bmp5_set_osr_odr_press_config(&odr_config, &dev); + log("BMP581 odr returned %d\r\n", result); if (result != BMP5_OK) return false; result = bmp5_int_source_select(&int_config, &dev); + log("BMP581 source returned %d\r\n", result); if (result != BMP5_OK) return false; @@ -123,10 +132,12 @@ bool BMP581::init() { result = bmp5_configure_interrupt(BMP5_PULSED, BMP5_ACTIVE_HIGH, BMP5_INTR_PUSH_PULL, BMP5_INTR_ENABLE, &dev); + log("BMP581 interrupt returned %d\r\n", result); if (result != BMP5_OK) return false; result = bmp5_set_power_mode(BMP5_POWERMODE_NORMAL, &dev); + log("BMP581 power returned %d\r\n", result); if (result != BMP5_OK) return false; From feb3eaac3251fb838e0acf3248d0736b1ed89c6b Mon Sep 17 00:00:00 2001 From: wispl Date: Mon, 24 Aug 2026 00:24:22 -0400 Subject: [PATCH 3/6] feat: auto detect hal header file Using a similar cmake trick to conditionally compile in peripheral code, we can also use it to pull in the right header file automatically. Also reorganize imports while we are at it. --- CMakeLists.txt | 50 ++++++++++++++++++++++++++++++++++++ include/hal.h | 18 ++++++++----- src/protocols/CMakeLists.txt | 40 +++++------------------------ 3 files changed, 68 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a04ea1..b3129eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,56 @@ add_library(common_drivers STATIC) if(TARGET stm32cubemx) message(STATUS "Compiling for target host (MCU), linking libraries...") + if(NOT TARGET STM32_Drivers) + message(FATAL_ERROR "STM32_Drivers source was not added, something is wrong!") + endif() + get_target_property(STM32_Drivers_Src STM32_Drivers SOURCES) + + # Autodetect series + # STM32 includes a file of the form (depending on the series) + # ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/system_stm32f4xx.c + # to sources, which we can use regex detection to get the series + string(REGEX MATCH "stm32([a-z0-9]+)\\.c" FOUND_MATCH "${STM32_Drivers_Src}") + if(FOUND_MATCH) + # We then can use use this `HAL_STM32_SERIES` in + # hal.h to automatically include the right hal file. + message(STATUS "Detected " ${CMAKE_MATCH_1} " series...") + target_compile_definitions(common_drivers PUBLIC HAL_STM32_SERIES=${CMAKE_MATCH_1}) + else() + message(FATAL_ERROR "Unable to autodetect STM32 series!") + endif() + + # Autodetect perhipherals + # What this basically does is conditionally include the i2c, qspi, spi, and uart + # files if and only if they are enabled in cubemx. + # + # When cubemx enables the perhipherals, it adds something like this + # ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_spi.c + # to STM32_Drivers_Src. Therefore we can just iterate through it and find if the + # given drivers exist, and if they do, we can safely include our thin HAL abstraction + # for the driver. + set(PATTERNS + "_hal_i2c\\.c" + "_hal_qspi\\.c" + "_hal_spi\\.c" + "_hal_uart\\.c" + ) + set(DEFINES + I2C + QSPI + SPI + UART + ) + + foreach(PATTERN DEFINE IN ZIP_LISTS PATTERNS DEFINES) + string(REGEX MATCH "${PATTERN}" FOUND_MATCH "${STM32_Drivers_Src}") + if(FOUND_MATCH) + message(STATUS "Adding " ${DEFINE} " to build...") + set_property(GLOBAL APPEND PROPERTY PERHIPHERALS "${DEFINE}") + endif() + endforeach() + + # Add libaries add_library(bmi08 STATIC ${bmi08_SOURCE_DIR}/bmi08a.c ${bmi08_SOURCE_DIR}/bmi08g.c diff --git a/include/hal.h b/include/hal.h index 535145b..ed59d9a 100644 --- a/include/hal.h +++ b/include/hal.h @@ -2,14 +2,18 @@ #define HAL_H #ifndef TEST -#ifdef USE_STM32_H7XX -#include "stm32h7xx_hal.h" -#elif USE_STM32_L4XX -#include "stm32l4xx_hal.h" -#else -#include "stm32f4xx_hal.h" -#endif +// Stringifies the macro, since #include requires a string +// NOTE: this trick was once used in the linux kernel via __gcc_header, go check +// it out here. Note that it is no longer in use now. +// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/compiler-gcc.h?id=f6d133f877c8bb0a0934dc8c521c758ee771e901#n125 +#define _hal_header(x) #x +#define __hal_header(x) _hal_header(stm32 ## x.h) +#define hal_header(x) __hal_header(x) +// This will include the correct hal header, see the root CMakeLists.txt +// for more information on how we get `HAL_STM32_SERIES` +#include hal_header(HAL_STM32_SERIES) #else +// Dummy stub used for testing #define HAL_Delay(...) (0) #endif diff --git a/src/protocols/CMakeLists.txt b/src/protocols/CMakeLists.txt index c17a781..25104db 100644 --- a/src/protocols/CMakeLists.txt +++ b/src/protocols/CMakeLists.txt @@ -1,33 +1,7 @@ -# What this basically does is conditionally include the i2c, qspi, spi, and uart -# files if and only if they are enabled in cubemx. -# -# When cubemx enables the perhipherals, it adds something like this -# ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_spi.c -# to STM32_Drivers_Src. Therefore we can just iterate through it and find if the -# given drivers exist, and if they do, we can safely include our thin HAL abstraction -# for the driver. -set(PATTERNS - "_hal_i2c\\.c$" - "_hal_qspi\\.c$" - "_hal_spi\\.c$" - "_hal_uart\\.c$" -) - -set(SOURCES - "i2c.cpp" - "qspi.cpp" - "spi.cpp" - "uart.cpp" -) - -if(TARGET STM32_Drivers) - get_target_property(STM32_Drivers_Src STM32_Drivers SOURCES) - foreach(PATTERN SOURCE IN ZIP_LISTS PATTERNS SOURCES) - string(REGEX MATCH "${PATTERN}" FOUND_MATCH "${STM32_Drivers_Src}") - if(FOUND_MATCH) - # If the first pattern gets matched, then we add our file to the library - message(STATUS "Including " ${SOURCE} " in build") - target_sources(common_drivers PRIVATE "${SOURCE}") - endif() - endforeach() -endif() +get_property(perhipherals GLOBAL PROPERTY PERHIPHERALS) +foreach(peripheral IN LISTS perhipherals) + string(TOLOWER "${peripheral}" file) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${file}.cpp") + target_sources(common_drivers PRIVATE "${file}.cpp") + endif() +endforeach() From c33d48c0edd584ff1f0180165b8f7c52484b1a4e Mon Sep 17 00:00:00 2001 From: wispl Date: Mon, 24 Aug 2026 01:03:18 -0400 Subject: [PATCH 4/6] feat: use vTaskDelay when using RTOS --- CMakeLists.txt | 14 +++++++++++--- include/hal.h | 18 +++++++++++++++--- include/log.h | 4 ++-- src/flash/gd5f1gq5xe.cpp | 8 ++++---- src/log.cpp | 6 +++--- src/sensors/bmi088.cpp | 2 +- src/sensors/bmp581.cpp | 2 +- 7 files changed, 37 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b3129eb..d52afdf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,11 +68,20 @@ if(TARGET stm32cubemx) # We then can use use this `HAL_STM32_SERIES` in # hal.h to automatically include the right hal file. message(STATUS "Detected " ${CMAKE_MATCH_1} " series...") - target_compile_definitions(common_drivers PUBLIC HAL_STM32_SERIES=${CMAKE_MATCH_1}) + target_compile_definitions(common_drivers PUBLIC PLATFORM_STM32_SERIES=${CMAKE_MATCH_1}) else() message(FATAL_ERROR "Unable to autodetect STM32 series!") endif() + # Autodetect RTOS + # Note that STM32's HAL files do have a USE_RTOS definition. However relying + # on it is not a good idea because it is not specified for public + # use. Therefore we use our own detection and compile definition here + if(TARGET FreeRTOS) + message(STATUS "Detected FreeRTOS...") + target_compile_definitions(common_drivers PUBLIC PLATFORM_USE_RTOS) + endif() + # Autodetect perhipherals # What this basically does is conditionally include the i2c, qspi, spi, and uart # files if and only if they are enabled in cubemx. @@ -94,7 +103,6 @@ if(TARGET stm32cubemx) SPI UART ) - foreach(PATTERN DEFINE IN ZIP_LISTS PATTERNS DEFINES) string(REGEX MATCH "${PATTERN}" FOUND_MATCH "${STM32_Drivers_Src}") if(FOUND_MATCH) @@ -146,7 +154,7 @@ else() target_link_libraries(common_drivers PUBLIC bmp5 littlefs bmi08) message(STATUS "Creating tests for library...") - add_compile_definitions(TEST) + target_compile_definitions(common_drivers PUBLIC TEST) add_subdirectory(tests) endif() diff --git a/include/hal.h b/include/hal.h index ed59d9a..327603c 100644 --- a/include/hal.h +++ b/include/hal.h @@ -10,11 +10,23 @@ #define __hal_header(x) _hal_header(stm32 ## x.h) #define hal_header(x) __hal_header(x) // This will include the correct hal header, see the root CMakeLists.txt -// for more information on how we get `HAL_STM32_SERIES` -#include hal_header(HAL_STM32_SERIES) +// for more information on how we get `PLATFORM_STM32_SERIES` +#include hal_header(PLATFORM_STM32_SERIES) #else // Dummy stub used for testing #define HAL_Delay(...) (0) -#endif +#endif // end TEST +// This might be used in non-cpp files so no cpp features will be used +// Of course that also means no namespace usage +// TODO: decide if we do want to stuff this in a namespace, or if we should add +// a prefix so there is no name collision +#ifdef PLATFORM_USE_RTOS +#include "FreeRTOS.h" +#include "task.h" +#define Delay(x) vTaskDelay(pdMS_TO_TICKS(x)) +#else +#define Delay(x) HAL_Delay(x) #endif + +#endif // end HAL_H diff --git a/include/log.h b/include/log.h index bb4c2b0..1f1cb71 100644 --- a/include/log.h +++ b/include/log.h @@ -1,5 +1,5 @@ -#ifndef LOGGER_H -#define LOGGER_H +#ifndef LOG_H +#define LOG_H namespace Platform { void log(const char* fmt, ...); diff --git a/src/flash/gd5f1gq5xe.cpp b/src/flash/gd5f1gq5xe.cpp index 24c3233..4823b0f 100644 --- a/src/flash/gd5f1gq5xe.cpp +++ b/src/flash/gd5f1gq5xe.cpp @@ -74,7 +74,7 @@ uint32_t read_page(Protocol* protocol, const uint32_t block, (addr & 0x0000FF) // third byte }; protocol->write(ConstSpan(cmd1), {}, AddressSize::Byte3); - HAL_Delay(2); // maximum delay for reading to cache + Delay(2); // maximum delay for reading to cache // We can then use the column address to read offsets into the page uint16_t col = offset % GD5F_PAGE_SIZE; @@ -138,7 +138,7 @@ uint32_t write_page(Protocol* protocol, const uint32_t block, (addr & 0x0000FF) // to fill a page, and execute to write it }; protocol->write(ConstSpan(cmd2), {}, AddressSize::Byte3); - HAL_Delay(1); // maximum delay required for executing + Delay(1); // maximum delay required for executing return write_size; } @@ -187,7 +187,7 @@ bool erase(Protocol* protocol, uint32_t block) { (addr & 0x0000FF) // it operates on whiole blocks }; protocol->write(ConstSpan(cmd), {}, AddressSize::Byte3); - HAL_Delay(12); // maximum delay required for erases + Delay(12); // maximum delay required for erases return true; } @@ -253,7 +253,7 @@ bool GD5F1GQ5XE::init() { return false; uint8_t cmd2[] = {GD5F_SET_FEATURE, 0xA0, 0x00}; protocol.write(ConstSpan(cmd2), {}, AddressSize::Byte2); - HAL_Delay(5000); + Delay(5000); if (protocol.type() == ProtocolType::QSPI) { // Enable QE feature so we can quad reads and writes diff --git a/src/log.cpp b/src/log.cpp index 303cc42..bf7c3ae 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,8 +1,8 @@ #include "log.h" -#ifdef USE_SWO_DEBUG #include "hal.h" -#else + +#ifndef USE_SWO_DEBUG #include "usbd_cdc_if.h" #endif @@ -27,7 +27,7 @@ void log(const char* fmt, ...) { ITM_SendChar(buf[i]); #else while (CDC_Transmit_FS(reinterpret_cast(buf), len) == USBD_BUSY) - HAL_Delay(1); + Delay(1); #endif } } // namespace Platform diff --git a/src/sensors/bmi088.cpp b/src/sensors/bmi088.cpp index ee6813a..8006c01 100644 --- a/src/sensors/bmi088.cpp +++ b/src/sensors/bmi088.cpp @@ -40,7 +40,7 @@ BMI08_INTF_RET_TYPE bosch_write(uint8_t reg_addr, const uint8_t* reg_data, } void bosch_delay(uint32_t period, void* intf_ptr) { - HAL_Delay(ceil(static_cast(period) / (1000.0))); + Delay(ceil(static_cast(period) / (1000.0))); } float bmi088_convert_accel_axis_data(struct bmi08_dev& dev, int16_t axis_data) { diff --git a/src/sensors/bmp581.cpp b/src/sensors/bmp581.cpp index ff31c66..718ba9c 100644 --- a/src/sensors/bmp581.cpp +++ b/src/sensors/bmp581.cpp @@ -86,7 +86,7 @@ BMP5_INTF_RET_TYPE bosch_write(uint8_t reg_addr, const uint8_t* reg_data, } void bosch_delay(uint32_t period, void* intf_ptr) { - HAL_Delay(ceil((double)(period) / (1000.0))); + Delay(ceil((double)(period) / (1000.0))); } } // namespace From e63e69c4e04c017d01e656b9eb00d36bdf578f34 Mon Sep 17 00:00:00 2001 From: wispl Date: Mon, 24 Aug 2026 01:12:36 -0400 Subject: [PATCH 5/6] feat: compile out log statements in release and tests Note that tests in this case means unit tests using the drivers, not the DEBUG build. That will still have debug statements available. We want to compile these out for performance and storage reasons. --- include/log.h | 2 +- src/flash/flash.cpp | 4 ++-- src/flash/gd5f1gq5xe.cpp | 4 ++-- src/log.cpp | 20 ++++++++++++-------- src/sensors/bmi088.cpp | 24 ++++++++++++------------ src/sensors/bmp581.cpp | 14 ++++++-------- 6 files changed, 35 insertions(+), 33 deletions(-) diff --git a/include/log.h b/include/log.h index 1f1cb71..e2858b9 100644 --- a/include/log.h +++ b/include/log.h @@ -2,7 +2,7 @@ #define LOG_H namespace Platform { - void log(const char* fmt, ...); + void LOG(const char* fmt, ...); } // namespace Platform #endif diff --git a/src/flash/flash.cpp b/src/flash/flash.cpp index c67cd6f..da67bcb 100644 --- a/src/flash/flash.cpp +++ b/src/flash/flash.cpp @@ -82,14 +82,14 @@ bool Flash::append(lfs_file_t* file, const uint8_t* bytes, size_t size) { // Due to constraints, the size is guaranteed to be less // than a block size, so we can just safely do this write_size = size; - log("Flash synced", size); + LOG("Flash synced", size); } uint32_t res = lfs_file_write(&lfs, file, buf, write_size); if (res != write_size) return false; - Platform::log("Flash wrote %u\r\n", res); + Platform::LOG("Flash wrote %u\r\n", res); size -= res; filesize += res; diff --git a/src/flash/gd5f1gq5xe.cpp b/src/flash/gd5f1gq5xe.cpp index 4823b0f..1c1aefb 100644 --- a/src/flash/gd5f1gq5xe.cpp +++ b/src/flash/gd5f1gq5xe.cpp @@ -1,7 +1,7 @@ #include "gd5f1gq5xe.h" -#include "log.h" #include "hal.h" +#include "log.h" #include "protocol.h" // Flash Commands @@ -263,7 +263,7 @@ bool GD5F1GQ5XE::init() { protocol.configure(Config::QSPI_Data4); } - log("Flash init finished!"); + LOG("Flash init finished!"); return true; } } // namespace Platform diff --git a/src/log.cpp b/src/log.cpp index bf7c3ae..2371249 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -2,7 +2,7 @@ #include "hal.h" -#ifndef USE_SWO_DEBUG +#ifdef USE_CDC_DEBUG #include "usbd_cdc_if.h" #endif @@ -11,8 +11,11 @@ #include namespace Platform { -// TODO: compile this out for release builds -void log(const char* fmt, ...) { +#if defined(TEST) || defined(RELEASE) +// TODO: I think the static strings are not compiled out, need to investigate +void LOG(const char* fmt, ...) {} +#else +void LOG(const char* fmt, ...) { char buf[256]; va_list args; va_start(args, fmt); @@ -22,12 +25,13 @@ void log(const char* fmt, ...) { if (len <= 0) return; -#ifdef USE_SWO_DEBUG - for (int i = 0; i < len; ++i) - ITM_SendChar(buf[i]); -#else +#ifdef USE_CDC_DEBUG while (CDC_Transmit_FS(reinterpret_cast(buf), len) == USBD_BUSY) Delay(1); -#endif +#else + for (int i = 0; i < len; ++i) + ITM_SendChar(buf[i]); +#endif // end USE_CDC_DEBUG } +#endif // end defined(TEST) || defined(RELEASE) } // namespace Platform diff --git a/src/sensors/bmi088.cpp b/src/sensors/bmi088.cpp index 8006c01..2bbeba6 100644 --- a/src/sensors/bmi088.cpp +++ b/src/sensors/bmi088.cpp @@ -5,8 +5,8 @@ * Author: Dhruv Shah */ -#include "log.h" #include "hal.h" +#include "log.h" #include "protocol.h" #include "sensor.h" @@ -106,57 +106,57 @@ bool BMI088::init() { int8_t ret = BMI08_OK; ret = bmi08a_soft_reset(&dev); - log("BMI088a soft reset returned %d\r\n", ret); + LOG("BMI088a soft reset returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08xa_init(&dev); - log("BMI088a init returned %d\r\n", ret); + LOG("BMI088a init returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08a_load_config_file(&dev); - log("BMI088a load returned %d\r\n", ret); + LOG("BMI088a load returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08a_set_power_mode(&dev); - log("BMI088a power returned %d\r\n", ret); + LOG("BMI088a power returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08xa_set_meas_conf(&dev); - log("BMI088a meas returned %d\r\n", ret); + LOG("BMI088a meas returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08a_set_int_config(&accel_new_data_int_cfg, &dev); - log("BMI088a interrupt returned %d\r\n", ret); + LOG("BMI088a interrupt returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_soft_reset(&dev); - log("BMI088g reset returned %d\r\n", ret); + LOG("BMI088g reset returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_init(&dev); - log("BMI088g init returned %d\r\n", ret); + LOG("BMI088g init returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_set_power_mode(&dev); - log("BMI088g power returned %d\r\n", ret); + LOG("BMI088g power returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_set_meas_conf(&dev); - log("BMI088g meas returned %d\r\n", ret); + LOG("BMI088g meas returned %d\r\n", ret); if (ret != BMI08_OK) return false; ret = bmi08g_set_int_config(&gyro_new_data_int_cfg, &dev); - log("BMI088g interrupt returned %d\r\n", ret); + LOG("BMI088g interrupt returned %d\r\n", ret); if (ret != BMI08_OK) return false; diff --git a/src/sensors/bmp581.cpp b/src/sensors/bmp581.cpp index 718ba9c..f40d7f9 100644 --- a/src/sensors/bmp581.cpp +++ b/src/sensors/bmp581.cpp @@ -12,8 +12,6 @@ #include #include -#include "usbd_cdc_if.h" - constexpr auto GRAVITY_ACCEL = 9.80665f; // m/s^2 constexpr auto AIR_MOLAR_MASS = 0.0289644f; // kg/mol constexpr auto GAS_CONSTANT = 8.31446f; // J/(mol*K) @@ -107,24 +105,24 @@ bool BMP581::init() { int_config.oor_press_en = BMP5_DISABLE; result = bmp5_soft_reset(&dev); - log("BMP581 reset returned %d\r\n", result); + LOG("BMP581 reset returned %d\r\n", result); if (result != BMP5_OK) return false; // Initialize the device result = bmp5_init(&dev); - log("BMP581 init returned %d\r\n", result); + LOG("BMP581 init returned %d\r\n", result); if (result != BMP5_OK) return false; // Set odr frequency result = bmp5_set_osr_odr_press_config(&odr_config, &dev); - log("BMP581 odr returned %d\r\n", result); + LOG("BMP581 odr returned %d\r\n", result); if (result != BMP5_OK) return false; result = bmp5_int_source_select(&int_config, &dev); - log("BMP581 source returned %d\r\n", result); + LOG("BMP581 source returned %d\r\n", result); if (result != BMP5_OK) return false; @@ -132,12 +130,12 @@ bool BMP581::init() { result = bmp5_configure_interrupt(BMP5_PULSED, BMP5_ACTIVE_HIGH, BMP5_INTR_PUSH_PULL, BMP5_INTR_ENABLE, &dev); - log("BMP581 interrupt returned %d\r\n", result); + LOG("BMP581 interrupt returned %d\r\n", result); if (result != BMP5_OK) return false; result = bmp5_set_power_mode(BMP5_POWERMODE_NORMAL, &dev); - log("BMP581 power returned %d\r\n", result); + LOG("BMP581 power returned %d\r\n", result); if (result != BMP5_OK) return false; From 15247f3dd0fc8e89de85ee7e971ca366dd57a848 Mon Sep 17 00:00:00 2001 From: wispl Date: Mon, 24 Aug 2026 19:04:37 -0400 Subject: [PATCH 6/6] chore: rename Platform to Common --- include/flash/flash.h | 4 ++-- include/flash/gd5f1gq5xe.h | 4 ++-- include/log.h | 4 ++-- include/protocols/i2c.h | 4 ++-- include/protocols/protocol.h | 4 ++-- include/protocols/qspi.h | 4 ++-- include/protocols/spi.h | 4 ++-- include/protocols/uart.h | 4 ++-- include/sensors/bmi088.h | 4 ++-- include/sensors/bmp581.h | 4 ++-- include/sensors/cd-pa1616s.h | 4 ++-- include/sensors/sensor.h | 4 ++-- src/flash/flash.cpp | 6 +++--- src/flash/gd5f1gq5xe.cpp | 6 +++--- src/log.cpp | 4 ++-- src/protocols/i2c.cpp | 18 +++++++++--------- src/protocols/qspi.cpp | 20 ++++++++++---------- src/protocols/spi.cpp | 4 ++-- src/protocols/uart.cpp | 4 ++-- src/sensors/bmi088.cpp | 20 ++++++++++---------- src/sensors/bmp581.cpp | 20 ++++++++++---------- src/sensors/cd-pa1616s.cpp | 4 ++-- tests/mocks/protocol_mock.h | 4 ++-- tests/sensors/bmp581_test.cpp | 4 ++-- 24 files changed, 81 insertions(+), 81 deletions(-) diff --git a/include/flash/flash.h b/include/flash/flash.h index a6ecc19..f8c1cda 100644 --- a/include/flash/flash.h +++ b/include/flash/flash.h @@ -22,7 +22,7 @@ /// Then you must call `mount` to mount the filesystem. Only after that may you /// use other functions like `bootcount` or `append`. It is a good idea to call /// `unmount` when you no longer need to write the the flash. -namespace Platform { +namespace Common { class Flash { protected: struct lfs_config config; @@ -75,6 +75,6 @@ class Flash { /// @return true on success, false on error bool append(lfs_file_t* file, const uint8_t* bytes, size_t size); }; -} // namespace Platform +} // namespace Common #endif diff --git a/include/flash/gd5f1gq5xe.h b/include/flash/gd5f1gq5xe.h index c52e37e..dfe24a7 100644 --- a/include/flash/gd5f1gq5xe.h +++ b/include/flash/gd5f1gq5xe.h @@ -8,7 +8,7 @@ #include #include -namespace Platform { +namespace Common { class GD5F1GQ5XE final : public Flash { private: Protocol& protocol; @@ -17,6 +17,6 @@ class GD5F1GQ5XE final : public Flash { GD5F1GQ5XE(Protocol& protocol_); bool init() override; }; -} // namespace Platform +} // namespace Common #endif diff --git a/include/log.h b/include/log.h index e2858b9..f028d21 100644 --- a/include/log.h +++ b/include/log.h @@ -1,8 +1,8 @@ #ifndef LOG_H #define LOG_H -namespace Platform { +namespace Common { void LOG(const char* fmt, ...); -} // namespace Platform +} // namespace Common #endif diff --git a/include/protocols/i2c.h b/include/protocols/i2c.h index 4e23e3a..4cf4aa1 100644 --- a/include/protocols/i2c.h +++ b/include/protocols/i2c.h @@ -13,7 +13,7 @@ /// to prefer the pattern of specifying an address to target, followed /// by a write or read. The `mem` I2C functions do this in one step instead /// of two. This contrasts with SPI where we are forced to do it in two. -namespace Platform { +namespace Common { class I2C final : public Protocol { private: I2C_HandleTypeDef* handle; @@ -25,6 +25,6 @@ class I2C final : public Protocol { bool read(ConstSpan cmd, Span buffer, AddressSize size) override; bool write(ConstSpan cmd, ConstSpan buffer, AddressSize size) override; }; -} // namespace Platform +} // namespace Common #endif diff --git a/include/protocols/protocol.h b/include/protocols/protocol.h index 3a350ed..ffa2594 100644 --- a/include/protocols/protocol.h +++ b/include/protocols/protocol.h @@ -34,7 +34,7 @@ /// implementation actually has commands baked into its core. The complex /// devices tend to require dummy cycles. You should specify dummy cycles using /// empty bytes `0x00`, look at the spreadsheets to see how many you need. -namespace Platform { +namespace Common { /// Span --- /// A quick primer on span, it just a regular C buffer but it also @@ -138,6 +138,6 @@ class Protocol { /// @return protocol type ProtocolType type() const { return ptype; } }; -} // namespace Platform +} // namespace Common #endif // PROTOCOLS_PROTOCOL_H diff --git a/include/protocols/qspi.h b/include/protocols/qspi.h index 34b849e..aa77324 100644 --- a/include/protocols/qspi.h +++ b/include/protocols/qspi.h @@ -22,7 +22,7 @@ /// byte mode yet. Sometimes you can only recieve data over a single line, so /// a method is provided in order to configure the dataMode, usually at startup /// only. You can do so using `configure` method with the appropriate `Config`. -namespace Platform { +namespace Common { class QSPI final : public Protocol { private: QSPI_HandleTypeDef* handle; @@ -52,6 +52,6 @@ class QSPI final : public Protocol { return true; }; }; -} // namespace Platform +} // namespace Common #endif diff --git a/include/protocols/spi.h b/include/protocols/spi.h index 855e0e8..e7284fe 100644 --- a/include/protocols/spi.h +++ b/include/protocols/spi.h @@ -22,7 +22,7 @@ /// /// The 0x00 is eight bytes of dummy. SPI will just send it as is and everything /// should work well. -namespace Platform { +namespace Common { class SPI final : public Protocol { private: SPI_HandleTypeDef* handle; @@ -36,6 +36,6 @@ class SPI final : public Protocol { bool read(ConstSpan cmd, Span buffer, AddressSize size) override; bool write(ConstSpan cmd, ConstSpan buffer, AddressSize size) override; }; -} // namespace Platform +} // namespace Common #endif diff --git a/include/protocols/uart.h b/include/protocols/uart.h index f343842..32932cb 100644 --- a/include/protocols/uart.h +++ b/include/protocols/uart.h @@ -9,7 +9,7 @@ /// commands, this current implementation just writes and reads directly, /// with no intermediate step. The `cmd` buffer is ignored, just write /// using the buffer. -namespace Platform { +namespace Common { class UART final : public Protocol { private: UART_HandleTypeDef* handle; @@ -21,6 +21,6 @@ class UART final : public Protocol { bool write(ConstSpan cmd, ConstSpan buffer, AddressSize size) override; bool readDMA(Span buffer) override; }; -} // namespace Platform +} // namespace Common #endif diff --git a/include/sensors/bmi088.h b/include/sensors/bmi088.h index 39235f6..b8700e0 100644 --- a/include/sensors/bmi088.h +++ b/include/sensors/bmi088.h @@ -13,7 +13,7 @@ #include -namespace Platform { +namespace Common { class BMI088 final : public Sensor { private: struct bmi08_dev dev; @@ -28,6 +28,6 @@ class BMI088 final : public Sensor { bool init() override; bool read(Packet& packet) override; }; -} // namespace Platform +} // namespace Common #endif diff --git a/include/sensors/bmp581.h b/include/sensors/bmp581.h index 333b6a0..8c4d953 100644 --- a/include/sensors/bmp581.h +++ b/include/sensors/bmp581.h @@ -7,7 +7,7 @@ #include -namespace Platform { +namespace Common { class BMP581 final : public Sensor { private: struct bmp5_dev dev; @@ -22,6 +22,6 @@ class BMP581 final : public Sensor { bool init() override; bool read(Packet& packet) override; }; -} // namespace Platform +} // namespace Common #endif diff --git a/include/sensors/cd-pa1616s.h b/include/sensors/cd-pa1616s.h index e8f0889..5d6185c 100644 --- a/include/sensors/cd-pa1616s.h +++ b/include/sensors/cd-pa1616s.h @@ -14,7 +14,7 @@ #include #include -namespace Platform { +namespace Common { class GPS final : public Sensor { private: // used for DMA reception buffer @@ -28,6 +28,6 @@ class GPS final : public Sensor { bool init() override; bool read(Packet& packet) override; }; -} // namespace Platform +} // namespace Common #endif /* INC_CD_PA1616S_H_ */ diff --git a/include/sensors/sensor.h b/include/sensors/sensor.h index ac54c1c..da867b0 100644 --- a/include/sensors/sensor.h +++ b/include/sensors/sensor.h @@ -4,7 +4,7 @@ #include #include -namespace Platform { +namespace Common { struct __attribute__((packed)) Packet { int16_t magic = 0xBEEF; // 2 bytes uint32_t status = 0; // 4 bytes @@ -51,6 +51,6 @@ class Sensor { virtual bool read(Packet& packet) = 0; virtual bool init() = 0; }; -} // namespace Platform +} // namespace Common #endif // SENSORS_SENSOR_H diff --git a/src/flash/flash.cpp b/src/flash/flash.cpp index da67bcb..56ab62b 100644 --- a/src/flash/flash.cpp +++ b/src/flash/flash.cpp @@ -29,7 +29,7 @@ inline uint32_t offset(uint32_t filesize, uint32_t blocksize) { } } // namespace -namespace Platform { +namespace Common { uint32_t Flash::mount() { int err = lfs_mount(&lfs, &config); if (err) { @@ -89,7 +89,7 @@ bool Flash::append(lfs_file_t* file, const uint8_t* bytes, size_t size) { if (res != write_size) return false; - Platform::LOG("Flash wrote %u\r\n", res); + Common::LOG("Flash wrote %u\r\n", res); size -= res; filesize += res; @@ -98,4 +98,4 @@ bool Flash::append(lfs_file_t* file, const uint8_t* bytes, size_t size) { } return true; } -} // namespace Platform +} // namespace Common diff --git a/src/flash/gd5f1gq5xe.cpp b/src/flash/gd5f1gq5xe.cpp index 1c1aefb..5942a25 100644 --- a/src/flash/gd5f1gq5xe.cpp +++ b/src/flash/gd5f1gq5xe.cpp @@ -42,7 +42,7 @@ constexpr uint32_t GD5F_BLOCK_SIZE = GD5F_PAGES_PER_BLOCK * GD5F_PAGE_SIZE; // Note that this flash has a page size of 2048, so we actually only need 11 // bytes for the column address. namespace { -using namespace Platform; +using namespace Common; /// Generally needed when you want to write to the flash using execute int write_enable(Protocol* protocol) { @@ -218,7 +218,7 @@ int lfs_erase(const struct lfs_config* c, lfs_block_t block) { int lfs_sync(const struct lfs_config* c) { return LFS_ERR_OK; } } // namespace -namespace Platform { +namespace Common { GD5F1GQ5XE::GD5F1GQ5XE(Protocol& protocol_) : protocol(protocol_) { assert(protocol.type() == ProtocolType::SPI || protocol.type() == ProtocolType::QSPI); @@ -266,4 +266,4 @@ bool GD5F1GQ5XE::init() { LOG("Flash init finished!"); return true; } -} // namespace Platform +} // namespace Common diff --git a/src/log.cpp b/src/log.cpp index 2371249..d155f8e 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -10,7 +10,7 @@ #include #include -namespace Platform { +namespace Common { #if defined(TEST) || defined(RELEASE) // TODO: I think the static strings are not compiled out, need to investigate void LOG(const char* fmt, ...) {} @@ -34,4 +34,4 @@ void LOG(const char* fmt, ...) { #endif // end USE_CDC_DEBUG } #endif // end defined(TEST) || defined(RELEASE) -} // namespace Platform +} // namespace Common diff --git a/src/protocols/i2c.cpp b/src/protocols/i2c.cpp index 3912019..4832cd5 100644 --- a/src/protocols/i2c.cpp +++ b/src/protocols/i2c.cpp @@ -7,30 +7,30 @@ #include namespace { -uint32_t get_address_size(Platform::AddressSize size) { - assert((size == Platform::AddressSize::Byte || - size == Platform::AddressSize::Byte2) && +uint32_t get_address_size(Common::AddressSize size) { + assert((size == Common::AddressSize::Byte || + size == Common::AddressSize::Byte2) && "I2C addressses must one or two bytes"); switch (size) { - case Platform::AddressSize::Byte: + case Common::AddressSize::Byte: return I2C_MEMADD_SIZE_8BIT; - case Platform::AddressSize::Byte2: + case Common::AddressSize::Byte2: return I2C_MEMADD_SIZE_16BIT; default: return 0; // shouldn't be reached } } -uint16_t get_address(Platform::ConstSpan cmd, Platform::AddressSize size) { +uint16_t get_address(Common::ConstSpan cmd, Common::AddressSize size) { assert(!cmd.empty() && "Address cannot be empty"); - if (size == Platform::AddressSize::Byte) + if (size == Common::AddressSize::Byte) return cmd[0]; assert((cmd.size() == 2) && "Address length must be be two bytes bytes"); return static_cast((cmd[0] << 8) | cmd[1]); } } // namespace -namespace Platform { +namespace Common { bool I2C::read(ConstSpan cmd, Span buffer, AddressSize size) { auto asize = get_address_size(size); return HAL_I2C_Mem_Read(handle, address << 1, get_address(cmd, size), asize, @@ -43,4 +43,4 @@ bool I2C::write(ConstSpan cmd, ConstSpan buffer, AddressSize size) { asize, buffer.data(), buffer.size(), HAL_MAX_DELAY); } -} // namespace Platform +} // namespace Common diff --git a/src/protocols/qspi.cpp b/src/protocols/qspi.cpp index 3cbab7e..7047838 100644 --- a/src/protocols/qspi.cpp +++ b/src/protocols/qspi.cpp @@ -3,17 +3,17 @@ #include namespace { -uint32_t get_address_size(Platform::AddressSize size) { +uint32_t get_address_size(Common::AddressSize size) { switch (size) { - case Platform::AddressSize::Byte: + case Common::AddressSize::Byte: return QSPI_ADDRESS_8_BITS; - case Platform::AddressSize::Byte2: + case Common::AddressSize::Byte2: return QSPI_ADDRESS_16_BITS; - case Platform::AddressSize::Byte3: + case Common::AddressSize::Byte3: return QSPI_ADDRESS_24_BITS; - case Platform::AddressSize::Byte4: + case Common::AddressSize::Byte4: return QSPI_ADDRESS_32_BITS; - case Platform::AddressSize::None: + case Common::AddressSize::None: return QSPI_ADDRESS_NONE; } } @@ -29,7 +29,7 @@ uint32_t dummycycle_per_byte(uint32_t datalines) { } } -uint32_t get_address(Platform::ConstSpan cmd, Platform::AddressSize size) { +uint32_t get_address(Common::ConstSpan cmd, Common::AddressSize size) { auto numbytes = static_cast(size); uint32_t address = 0; assert(cmd.size() > numbytes && "You misconfigured your address size"); @@ -38,7 +38,7 @@ uint32_t get_address(Platform::ConstSpan cmd, Platform::AddressSize size) { return address; } -void configure_cmd(Platform::ConstSpan cmd, Platform::AddressSize size, +void configure_cmd(Common::ConstSpan cmd, Common::AddressSize size, QSPI_CommandTypeDef& qcmd) { // Configure instruction and address qcmd.Instruction = cmd[0]; @@ -72,7 +72,7 @@ void configure_cmd(Platform::ConstSpan cmd, Platform::AddressSize size, } } // namespace -namespace Platform { +namespace Common { bool QSPI::read(ConstSpan cmd, Span buffer, AddressSize size) { QSPI_CommandTypeDef qcmd; configure_cmd(cmd, size, qcmd); @@ -95,4 +95,4 @@ bool QSPI::write(ConstSpan cmd, ConstSpan buffer, AddressSize size) { return HAL_QSPI_Transmit(handle, buffer.data(), HAL_MAX_DELAY); return res; } -} // namespace Platform +} // namespace Common diff --git a/src/protocols/spi.cpp b/src/protocols/spi.cpp index d0ba320..5c451d6 100644 --- a/src/protocols/spi.cpp +++ b/src/protocols/spi.cpp @@ -17,7 +17,7 @@ struct Guard { }; } // namespace -namespace Platform { +namespace Common { bool SPI::read(ConstSpan cmd, Span buffer, AddressSize size) { assert(!cmd.empty() && "Command buffer should not be empty"); Guard cs = Guard(port, pin); @@ -38,4 +38,4 @@ bool SPI::write(ConstSpan cmd, ConstSpan buffer, AddressSize size) { HAL_MAX_DELAY); return res; } -} // namespace Platform +} // namespace Common diff --git a/src/protocols/uart.cpp b/src/protocols/uart.cpp index b40597f..c4644e2 100644 --- a/src/protocols/uart.cpp +++ b/src/protocols/uart.cpp @@ -1,6 +1,6 @@ #include "uart.h" -namespace Platform { +namespace Common { bool UART::read(ConstSpan cmd, Span buffer, AddressSize size) { assert(cmd.empty() && "Just read using the buffer"); return HAL_UART_Receive(handle, buffer.data(), buffer.size(), @@ -16,4 +16,4 @@ bool UART::write(ConstSpan cmd, ConstSpan buffer, AddressSize size) { bool UART::readDMA(Span buffer) { return HAL_UARTEx_ReceiveToIdle_DMA(handle, buffer.data(), buffer.size()); } -} // namespace Platform +} // namespace Common diff --git a/src/sensors/bmi088.cpp b/src/sensors/bmi088.cpp index 2bbeba6..699da57 100644 --- a/src/sensors/bmi088.cpp +++ b/src/sensors/bmi088.cpp @@ -25,18 +25,18 @@ namespace { BMI08_INTF_RET_TYPE bosch_read(uint8_t reg_addr, uint8_t* reg_data, uint32_t length, void* intf_ptr) { - auto* protocol = static_cast(intf_ptr); - return protocol->read(Platform::ConstSpan(®_addr, 1), - Platform::Span(reg_data, length), - Platform::AddressSize::Byte); + auto* protocol = static_cast(intf_ptr); + return protocol->read(Common::ConstSpan(®_addr, 1), + Common::Span(reg_data, length), + Common::AddressSize::Byte); } BMI08_INTF_RET_TYPE bosch_write(uint8_t reg_addr, const uint8_t* reg_data, uint32_t length, void* intf_ptr) { - auto* protocol = static_cast(intf_ptr); - return protocol->write(Platform::ConstSpan(®_addr, 1), - Platform::ConstSpan(reg_data, length), - Platform::AddressSize::Byte); + auto* protocol = static_cast(intf_ptr); + return protocol->write(Common::ConstSpan(®_addr, 1), + Common::ConstSpan(reg_data, length), + Common::AddressSize::Byte); } void bosch_delay(uint32_t period, void* intf_ptr) { @@ -67,7 +67,7 @@ float bmi088_convert_gyro_axis_data(struct bmi08_dev& dev, int16_t axis_data) { } } // namespace -namespace Platform { +namespace Common { bool BMI088::init() { struct bmi08_accel_int_channel_cfg accel_new_data_int_cfg; struct bmi08_gyro_int_channel_cfg gyro_new_data_int_cfg; @@ -185,4 +185,4 @@ bool BMI088::read(Packet& packet) { bmi088_convert_gyro_axis_data(dev, gyro_data.z); return true; } -} // namespace Platform +} // namespace Common diff --git a/src/sensors/bmp581.cpp b/src/sensors/bmp581.cpp index f40d7f9..85f5de2 100644 --- a/src/sensors/bmp581.cpp +++ b/src/sensors/bmp581.cpp @@ -69,18 +69,18 @@ inline float bmp581_estimate_altitude_msl(struct bmp5_sensor_data* data) { BMP5_INTF_RET_TYPE bosch_read(uint8_t reg_addr, uint8_t* reg_data, uint32_t length, void* intf_ptr) { - auto* protocol = static_cast(intf_ptr); - return protocol->read(Platform::ConstSpan(®_addr, 1), - Platform::Span(reg_data, length), - Platform::AddressSize::Byte); + auto* protocol = static_cast(intf_ptr); + return protocol->read(Common::ConstSpan(®_addr, 1), + Common::Span(reg_data, length), + Common::AddressSize::Byte); } BMP5_INTF_RET_TYPE bosch_write(uint8_t reg_addr, const uint8_t* reg_data, uint32_t length, void* intf_ptr) { - auto* protocol = static_cast(intf_ptr); - return protocol->write(Platform::ConstSpan(®_addr, 1), - Platform::ConstSpan(reg_data, length), - Platform::AddressSize::Byte); + auto* protocol = static_cast(intf_ptr); + return protocol->write(Common::ConstSpan(®_addr, 1), + Common::ConstSpan(reg_data, length), + Common::AddressSize::Byte); } void bosch_delay(uint32_t period, void* intf_ptr) { @@ -88,7 +88,7 @@ void bosch_delay(uint32_t period, void* intf_ptr) { } } // namespace -namespace Platform { +namespace Common { bool BMP581::init() { int8_t result = BMP5_OK; dev.intf_ptr = &api; @@ -151,4 +151,4 @@ bool BMP581::read(Packet& packet) { packet.kf_position_m = data.pressure; return true; } -} // namespace Platform +} // namespace Common diff --git a/src/sensors/cd-pa1616s.cpp b/src/sensors/cd-pa1616s.cpp index 3c5ca72..03e0f09 100644 --- a/src/sensors/cd-pa1616s.cpp +++ b/src/sensors/cd-pa1616s.cpp @@ -13,7 +13,7 @@ #include #include -namespace Platform { +namespace Common { bool GPS::init() { static constexpr uint8_t command[] = "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\x0d\x0a"; @@ -145,4 +145,4 @@ bool GPS::read(Packet& packet) { packet.gps_hMSL_m = alt; return true; } -} // namespace Platform +} // namespace Common diff --git a/tests/mocks/protocol_mock.h b/tests/mocks/protocol_mock.h index 0c028c6..5a50b26 100644 --- a/tests/mocks/protocol_mock.h +++ b/tests/mocks/protocol_mock.h @@ -2,7 +2,7 @@ #include -namespace Platform { +namespace Common { class MockProtocol : public Protocol { public: explicit MockProtocol(ProtocolType type) : Protocol(type) {} @@ -14,4 +14,4 @@ class MockProtocol : public Protocol { MOCK_METHOD(bool, readDMA, (Span buffer), (override)); MOCK_METHOD(bool, configure, (Config config), (override)); }; -} // namespace Platform +} // namespace Common diff --git a/tests/sensors/bmp581_test.cpp b/tests/sensors/bmp581_test.cpp index 83271f6..97f532a 100644 --- a/tests/sensors/bmp581_test.cpp +++ b/tests/sensors/bmp581_test.cpp @@ -54,7 +54,7 @@ auto mock_sensor_data(float pressure, float temp) { // Select lapse-rate model and enter your mock pressure. For tests, // we make the assumption of standard sea level conditions, ie // pressure at 101325 Pa and temperature of around 15 celcius. -namespace Platform { +namespace Common { class BMP581Test : public testing::Test { protected: MockProtocol protocol = MockProtocol(ProtocolType::SPI); @@ -132,4 +132,4 @@ TEST_F(BMP581Test, ShouldReadUpperStratosphereCorrectly) { EXPECT_FLOAT_EQ(fake_pressure, packet.kf_position_m); EXPECT_NEAR(expected_alt, packet.barometer_hMSL_m, 1); } -} // namespace Platform +} // namespace Common