Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .github/workflows/UnitTestsHOOTL.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
SCCACHE_LOG: "info"
SCCACHE_ERROR_LOG: ${{ github.workspace }}/sccache-error.log
SCCACHE_SERVER_PORT: ${{ matrix.prefix == 'Debug' && '4226' || (matrix.prefix == 'Release' && '4227' || (matrix.prefix == 'MinSizeRel' && '4228' || '4229')) }}
FETCHCONTENT_DIR: ${{ github.workspace }}/Lib/Vendor/FetchContent
steps:
- name: Checkout Repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -43,14 +44,24 @@ jobs:
with:
release: ${{ env.ARM_GCC_VERSION }}

- name: Setup FetchContent Cache
continue-on-error: true
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ env.FETCHCONTENT_DIR }}
key: fetchcontent-${{ runner.os }}-${{ hashFiles('Lib/cmake/cube.cmake', 'CMakeLists.txt', 'Testbench/testbench.cmake') }}
restore-keys: |
fetchcontent-${{ runner.os }}-

- name: Setup Cache
id: cache-setup
continue-on-error: true
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ env.SCCACHE_DIR }}
key: sccache-${{ runner.os }}-${{ env.ARM_GCC_VERSION }}-${{ github.job }}-${{ matrix.prefix }}-${{ github.ref_name }}
key: sccache-${{ runner.os }}-${{ env.ARM_GCC_VERSION }}-${{ github.job }}-${{ matrix.prefix }}-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
sccache-${{ runner.os }}-${{ env.ARM_GCC_VERSION }}-${{ github.job }}-${{ matrix.prefix }}-${{ github.ref_name }}-${{ github.sha }}
sccache-${{ runner.os }}-${{ env.ARM_GCC_VERSION }}-${{ github.job }}-${{ matrix.prefix }}-${{ github.ref_name }}
sccache-${{ runner.os }}-${{ env.ARM_GCC_VERSION }}-${{ github.job }}-${{ matrix.prefix }}-main
sccache-${{ runner.os }}-${{ env.ARM_GCC_VERSION }}-${{ github.job }}-${{ matrix.prefix }}-
Expand Down Expand Up @@ -199,12 +210,22 @@ jobs:
SCCACHE_DIR: ${{ github.workspace }}/sccache
SCCACHE_LOG: "info"
SCCACHE_ERROR_LOG: ${{ github.workspace }}/sccache-error.log
FETCHCONTENT_DIR: ${{ github.workspace }}/Lib/Vendor/FetchContent
steps:
- name: Checkout Repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1

- name: Setup FetchContent Cache
continue-on-error: true
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ env.FETCHCONTENT_DIR }}
key: fetchcontent-${{ runner.os }}-${{ hashFiles('Lib/cmake/cube.cmake', 'CMakeLists.txt', 'Testbench/testbench.cmake', 'BLINKY/blinky.cmake') }}
restore-keys: |
fetchcontent-${{ runner.os }}-

- name: Setup Cache
id: cache-setup
continue-on-error: true
Expand Down
9 changes: 5 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
cmake_minimum_required(VERSION 3.25)
project(Firmware)
enable_language(
C
ASM
)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS ON)
Expand Down Expand Up @@ -106,6 +102,11 @@ else()
message(STATUS "Not using ccache: ccache not found in PATH")
endif()

enable_language(
C
ASM
)

# Globals
set(LIB_PATH "Lib")
set(platform_path "${LIB_PATH}/Platform")
Expand Down
39 changes: 15 additions & 24 deletions Lib/Peripherals/CubeCAN/Inc/CubeCAN.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ typedef struct CubeCAN_Private_Handle CubeCAN_Handle;
* This union allows for easy access to the individual components of a CAN identifier, including the receive node ID, message ID, and transmit node ID.
* It also provides a raw 32-bit representation of the identifier which can be used to specify custom CAN IDs.
*
* @warning The values of the fields are not validated and may not represent a valid CAN identifier. It is the responsibility of the user to ensure that the values are valid and conform to the CAN protocol.
* @warning The values of the fields are not validated and may not represent a valid CAN identifier. It is the responsibility of the user to ensure that the values are valid and conform to the CAN
* protocol.
* @warning Do not use the _ field directly, as it may lead to undefined behavior. Use the provided fields instead.
*
* @note Setting any bit past the 29th bit is undefined behavior and may lead to unexpected results.
Expand All @@ -39,7 +40,7 @@ typedef union {
GRCAN_NODE_ID tx_node_id : 8;
uint32_t _ : 4;
};
} CAN_Identifier;
} CubeCAN_Identifier;

/**
* @brief Union for user-defined context data in CubeCAN configuration.
Expand All @@ -64,7 +65,7 @@ typedef union {
* @warning The callback function should not call any CubeCAN functions, as it may lead to undefined behavior.
* @warning It is the responsibility of the callback function to verify the integrity of the received data and handle any errors or null inputs.
*/
typedef void (*CubeCAN_RxCallback)(const CubeCAN_Config_Context *const context, const CAN_Identifier *const identifier, const uint8_t *const data, const uint8_t size);
typedef void (*CubeCAN_RxCallback)(const CubeCAN_Config_Context *const context, const CubeCAN_Identifier *const identifier, const uint8_t *const data, const uint8_t size);

/**
* @brief Configuration structure for CubeCAN CAN.
Expand Down Expand Up @@ -112,6 +113,17 @@ HAL_StatusTypeDef CubeCAN_Exit(CubeCAN_Handle *handle);
*/
void CubeCAN_Tick(void);

/**
* @brief Sends a CAN message using the CubeCAN CAN handle, with the specified custom identifier, data payload, and size.
* @param handle Pointer to the CubeCAN CAN handle.
* @param custom_id The custom identifier for the message.
* @param data Pointer to the data payload of the message.
* @param size The size of the data payload in bytes.
* @return HAL_StatusTypeDef indicating the success or failure of the operation.
* @note The size of the data payload must not exceed FDCAN_MAX_DATA_BYTES (64 bytes). If the size exceeds this limit, the function will return HAL_ERROR.
*/
HAL_StatusTypeDef CubeCAN_Send_Custom(CubeCAN_Handle *const handle, const GRCAN_CUSTOM_ID custom_id, const void *const data, const uint8_t size);

/**
* @brief Sends a CAN message using the CubeCAN CAN handle, with the specified receive node, message ID, data payload, and size.
* @param handle Pointer to the CubeCAN CAN handle.
Expand All @@ -124,25 +136,4 @@ void CubeCAN_Tick(void);
*/
HAL_StatusTypeDef CubeCAN_Send(CubeCAN_Handle *const handle, const GRCAN_NODE_ID rx_node, const GRCAN_MSG_ID msg_id, const void *const data, const uint8_t size);

/**
* @brief Constructs a CAN message identifier from the given transmitting node ID, receiving node ID, and message ID.
*
* @param identifier Pointer to the CAN_Identifier structure containing the node and message IDs.
*
* @return The constructed 29-bit CAN message extended identifier.
*/
uint32_t CubeCAN_Construct_Identifier(const CAN_Identifier *const identifier);

/**
* @brief Deconstructs a 29-bit CAN message extended identifier into its constituent transmitting node ID, receiving node ID, and message ID.
*
* @param message_id The 29-bit CAN message extended identifier to be deconstructed.
*
* @return A CAN_Identifier structure containing the deconstructed node and message IDs.
*
* @warning The function does not guarantee that the returned structure will represent a valid CAN message identifier.
* @warning The function does not support custom IDs.
*/
CAN_Identifier CubeCAN_Deconstruct_Identifier(const uint32_t message_id);

#endif
12 changes: 6 additions & 6 deletions Lib/Peripherals/CubeCAN/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Add a file named `CubeCAN_Config.h` to your project, generally `Application/Inc/

#### Configuration Requirements

- `CUBEMX_CAN_TX_QUEUE_SIZE`: Must be a power of two. Defines the transmission queue depth; when the queue fills, the oldest message is dropped and `CubeCAN_Send()` returns `HAL_BUSY`.
- `CUBEMX_CAN_TX_QUEUE_SIZE`: Must be a power of two. Defines the transmission queue depth; when the queue fills, the oldest message is dropped and `CubeCAN_Send()` and `CubeCAN_Send_Custom()` return `HAL_BUSY`.
- `CUBEMX_CAN_MAX_INSTANCES`: Must be 1-3 depending on your chip's available FDCAN peripherals. Omit this definition to automatically select the maximum supported by your MCU.

From there just ensure that you have configured CAN through STM32CubeMX and setup a global variable for each `CubeCAN_Handle` you wish to use.
Expand All @@ -59,7 +59,7 @@ Ensure you have setup some mechanism to call `CubeCAN_Tick()` frequently (a STM3

#### Transmission

Call `CubeCAN_Send()` to queue a message for transmission. Messages are queued in a lock-free ring buffer and processed in FIFO order.
Call `CubeCAN_Send()` or `CubeCAN_Send_Custom()` to queue a message for transmission. Messages are queued in a lock-free ring buffer and processed in FIFO order.

- **Returns `HAL_OK`**: Message successfully queued
- **Returns `HAL_BUSY`**: Transmission queue is full; the oldest queued message was dropped to make room for the new one
Expand Down Expand Up @@ -91,7 +91,7 @@ flowchart TD
Entrance --> Choice{"Functions"}
Choice --> Exit(["CubeCAN_Exit()"])
Exit -.-> |"AVOID"| Entrance
Choice --> Send(["CubeCAN_Send()"])
Choice --> Send(["CubeCAN_Send()\nCubeCAN_Send_Custom()"])
Choice --> |"Call Often\n(User Setup)"| Tick(["CubeCAN_Tick()"])
Send -.-> Queue("CubeCAN_Private_QueueTx()")
Data -.-> Tick
Expand Down Expand Up @@ -126,7 +126,7 @@ CubeCAN is designed to handle concurrent access from multiple execution contexts
#### Overview

- `tx_head` and `tx_tail` are atomic `uint32_t` variables that implement a lock-free ring buffer
- `CubeCAN_Send()` and `CubeCAN_Tick()` use critical sections (interrupt blocking) when necessary
- `CubeCAN_Send()`, `CubeCAN_Send_Custom()`, and `CubeCAN_Tick()` use critical sections (interrupt blocking) when necessary
- Atomic operations use `memory_order_relaxed` within ISRs and `memory_order_release`/`memory_order_acquire` for inter-context synchronization
- The queue size must be a power of two, allowing fast wraparound via bitwise AND instead of expensive modulo operations

Expand All @@ -139,7 +139,7 @@ Interrupt protection is used only in:

#### ISR Interaction

- Safe to call `CubeCAN_Send()` from `main()` or any ISR
- Safe to call `CubeCAN_Send()` or `CubeCAN_Send_Custom()` from `main()` or any ISR
- Safe to call `CubeCAN_Tick()` from `main()` or any ISR
- Do not call any CubeCAN function from `CubeCAN_RxCallback()`, use it only to write data to shared memory (with concurrency safety as needed)

Expand Down Expand Up @@ -184,7 +184,7 @@ We currently provide compile time static assertions to validate:
- `CUBEMX_CAN_TX_QUEUE_SIZE` is a power of two and non-zero (allows faster ring-buffer wrapping)
- `CUBEMX_CAN_MAX_INSTANCES` requires management of 1 to 3 interfaces (optional parameter, otherwise automatically chooses the maximum possible)

A single runtime check `CubeCAN_Private_InternalOneTimeChecks()` is called once on first call to `CubeCAN_Entrance()` which validates that the compiled bit-packing for `CAN_Identifier` is correct, if it is not the CAN peripheral entrance fails and returns `null`. Sadly this cannot be a compile-time check.
A single runtime check `CubeCAN_Private_InternalOneTimeChecks()` is called once on first call to `CubeCAN_Entrance()` which validates that the compiled bit-packing for `CubeCAN_Identifier` is correct, if it is not the CAN peripheral entrance fails and returns `null`. Sadly this cannot be a compile-time check.

### Globals

Expand Down
13 changes: 12 additions & 1 deletion Lib/Peripherals/CubeCAN/Src/PrivateInc/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ HAL_StatusTypeDef CubeCAN_Private_InternalOneTimeChecks(void);
* @param handle Pointer to the CubeCAN CAN handle.
* @return HAL_StatusTypeDef indicating the success or failure of the operation.
*/
HAL_StatusTypeDef CubeCAN_Private_SendQueuedMessage(const CubeCAN_Handle *const handle);
HAL_StatusTypeDef CubeCAN_Private_SendQueuedMessage(CubeCAN_Handle *const handle);

/**
* @brief Attempts to recover the FDCAN peripheral associated with the given CubeCAN CAN handle.
Expand Down Expand Up @@ -193,4 +193,15 @@ HAL_StatusTypeDef CubeCAN_Private_Stop(CubeCAN_Handle *const handle);
*/
HAL_StatusTypeDef CubeCAN_Private_Release(CubeCAN_Handle *const handle);

/**
* @brief Sends a CAN message using the CubeCAN CAN handle, with the specified identifier, data payload, and size.
* @param handle Pointer to the CubeCAN CAN handle.
* @param id The identifier for the message.
* @param data Pointer to the data payload of the message.
* @param size The size of the data payload in bytes.
* @return HAL_StatusTypeDef indicating the success or failure of the operation.
* @note The size of the data payload must not exceed FDCAN_MAX_DATA_BYTES (64 bytes). If the size exceeds this limit, the function will return HAL_ERROR.
*/
HAL_StatusTypeDef CubeCAN_Private_Send(CubeCAN_Handle *const handle, const CubeCAN_Identifier id, const void *const data, const uint8_t size);

#endif
2 changes: 1 addition & 1 deletion Lib/Peripherals/CubeCAN/Src/can_assert.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ static_assert(CUBEMX_CAN_MAX_INSTANCES >= 1 && CUBEMX_CAN_MAX_INSTANCES <= 3, "C

static_assert(sizeof(GRCAN_BUS_ID) <= sizeof(void *), "GRCAN_BUS_ID must fit within a pointer-sized type to support being unioned with a pointer in CubeCAN_Config");

static_assert(sizeof(CAN_Identifier) == sizeof(uint32_t), "CAN_Identifier must be 32 bits in size");
static_assert(sizeof(CubeCAN_Identifier) == sizeof(uint32_t), "CAN_Identifier must be 32 bits in size");
10 changes: 7 additions & 3 deletions Lib/Peripherals/CubeCAN/Src/can_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ static bool cubecan_internal_checks_passed = false;

HAL_StatusTypeDef CubeCAN_Private_InternalOneTimeChecks(void)
{
CAN_Identifier test_id = {.raw_id = 0x01122233};
CubeCAN_Identifier test_id = {.raw_id = 0x01122233};
if (test_id._ != 0 || test_id.tx_node_id != 0x11 || test_id.msg_id != 0x222 || test_id.rx_node_id != 0x33) {
LOGOMATIC_ERROR("CubeCAN_Private_InternalOneTimeChecks: CAN_Identifier bitfield order is incorrect on this platform, expected TX ID 0x11, MSG ID 0x222, RX ID 0x33 for raw_id 0x01122233, got TX ID 0x%02X, MSG ID 0x%03X, RX ID 0x%02X\n", test_id.tx_node_id, test_id.msg_id, test_id.rx_node_id);
LOGOMATIC_ERROR("CubeCAN_Private_InternalOneTimeChecks: CAN_Identifier bitfield order is incorrect on this platform, expected TX ID 0x11, MSG ID 0x222, RX ID 0x33 for raw_id "
"0x01122233, got TX ID 0x%02X, MSG ID 0x%03X, RX ID 0x%02X\n",
test_id.tx_node_id, test_id.msg_id, test_id.rx_node_id);
return HAL_ERROR;
}

test_id.raw_id = 0xA013FF;
if (test_id._ != 0 || test_id.tx_node_id != 0x0A || test_id.msg_id != 0x013 || test_id.rx_node_id != 0xFF) {
LOGOMATIC_ERROR("CubeCAN_Private_InternalOneTimeChecks: CAN_Identifier bitfield order is incorrect on this platform, expected TX ID 0x0A, MSG ID 0x013, RX ID 0xFF for raw_id 0x0A013FF, got TX ID 0x%02X, MSG ID 0x%03X, RX ID 0x%02X\n", test_id.tx_node_id, test_id.msg_id, test_id.rx_node_id);
LOGOMATIC_ERROR("CubeCAN_Private_InternalOneTimeChecks: CAN_Identifier bitfield order is incorrect on this platform, expected TX ID 0x0A, MSG ID 0x013, RX ID 0xFF for raw_id "
"0x0A013FF, got TX ID 0x%02X, MSG ID 0x%03X, RX ID 0x%02X\n",
test_id.tx_node_id, test_id.msg_id, test_id.rx_node_id);
return HAL_ERROR;
}

Expand Down
2 changes: 1 addition & 1 deletion Lib/Peripherals/CubeCAN/Src/can_rx.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)

while (HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FDCAN_RX_FIFO0) > 0U) {
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &rx_header, rx_data) == HAL_OK) {
CAN_Identifier id = {.raw_id = rx_header.Identifier};
CubeCAN_Identifier id = {.raw_id = rx_header.Identifier};
uint8_t size = CubeCAN_Private_DlcToBytes(rx_header.DataLength);
rx_callback(&context, &id, rx_data, size);
} else {
Expand Down
25 changes: 20 additions & 5 deletions Lib/Peripherals/CubeCAN/Src/can_tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "CriticalSection.h"
#include "CubeCAN.h"
#include "CubeCAN_Config.h"
#include "GRCAN_CUSTOM_ID.h"
#include "Logomatic.h"
#include "PrivateInc/internal.h"
#include "main.h"
Expand Down Expand Up @@ -71,7 +72,7 @@ void CubeCAN_Tick(void)
}
}

HAL_StatusTypeDef CubeCAN_Send(CubeCAN_Handle *const handle, const GRCAN_NODE_ID rx_node, const GRCAN_MSG_ID msg_id, const void *const data, const uint8_t size)
HAL_StatusTypeDef CubeCAN_Private_Send(CubeCAN_Handle *const handle, const CubeCAN_Identifier id, const void *const data, const uint8_t size)
{
if (handle == NULL || data == NULL || size > FDCAN_MAX_DATA_BYTES) {
return HAL_ERROR;
Expand Down Expand Up @@ -103,13 +104,11 @@ HAL_StatusTypeDef CubeCAN_Send(CubeCAN_Handle *const handle, const GRCAN_NODE_ID
return HAL_ERROR;
}

const CAN_Identifier identifier_struct = {.tx_node_id = handle->config.sending_node_id, .rx_node_id = rx_node, .msg_id = msg_id};

const FDCAN_TxHeaderTypeDef header = {.BitRateSwitch = brs,
.DataLength = dlc,
.ErrorStateIndicator = FDCAN_ESI_ACTIVE,
.FDFormat = fdformat,
.Identifier = identifier_struct.raw_id,
.Identifier = id.raw_id,
.IdType = FDCAN_EXTENDED_ID,
.MessageMarker = 0U, // TODO We can do cool things with this to track transmission queue statistics
.TxEventFifoControl = FDCAN_NO_TX_EVENTS,
Expand All @@ -121,6 +120,22 @@ HAL_StatusTypeDef CubeCAN_Send(CubeCAN_Handle *const handle, const GRCAN_NODE_ID
return CubeCAN_Private_QueueTx(handle, &message);
}

HAL_StatusTypeDef CubeCAN_Send_Custom(CubeCAN_Handle *const handle, const GRCAN_CUSTOM_ID custom_id, const void *const data, const uint8_t size)
{
CubeCAN_Identifier id = {.raw_id = custom_id};
return CubeCAN_Private_Send(handle, id, data, size);
}

HAL_StatusTypeDef CubeCAN_Send(CubeCAN_Handle *const handle, const GRCAN_NODE_ID rx_node, const GRCAN_MSG_ID msg_id, const void *const data, const uint8_t size)
{
if (handle == NULL) {
return HAL_ERROR;
}

CubeCAN_Identifier id = {.rx_node_id = rx_node, .msg_id = msg_id, .tx_node_id = handle->config.sending_node_id};
return CubeCAN_Private_Send(handle, id, data, size);
}

HAL_StatusTypeDef CubeCAN_Private_QueueTx(CubeCAN_Handle *handle, const GRCAN_Private_TxMessage *message)
{
if (handle == NULL || message == NULL) {
Expand All @@ -147,7 +162,7 @@ HAL_StatusTypeDef CubeCAN_Private_QueueTx(CubeCAN_Handle *handle, const GRCAN_Pr
return status;
}

HAL_StatusTypeDef CubeCAN_Private_SendQueuedMessage(const CubeCAN_Handle *const handle)
HAL_StatusTypeDef CubeCAN_Private_SendQueuedMessage(CubeCAN_Handle *const handle)
{
if (handle == NULL || handle->hfdcan == NULL) {
LOGOMATIC_ERROR("CubeCAN_Private_SendQueuedMessage: invalid null parameter\n");
Expand Down
2 changes: 1 addition & 1 deletion Lib/Peripherals/CubeCAN/Test/cubecan_rx_failure_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

static uint32_t callback_count;

static void receive_callback(const CubeCAN_Config_Context *context, const CAN_Identifier *identifier, const uint8_t *data, uint8_t size)
static void receive_callback(const CubeCAN_Config_Context *context, const CubeCAN_Identifier *identifier, const uint8_t *data, uint8_t size)
{
UNUSED(context);
UNUSED(identifier);
Expand Down
4 changes: 2 additions & 2 deletions Lib/Peripherals/CubeCAN/Test/cubecan_rx_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
#include "main.h"

static uint32_t callback_count;
static CAN_Identifier callback_ids[2];
static CubeCAN_Identifier callback_ids[2];
static uint8_t callback_data[2][64];
static uint8_t callback_sizes[2];
static uint32_t callback_context;

static void receive_callback(const CubeCAN_Config_Context *context, const CAN_Identifier *identifier, const uint8_t *data, uint8_t size)
static void receive_callback(const CubeCAN_Config_Context *context, const CubeCAN_Identifier *identifier, const uint8_t *data, uint8_t size)
{
const uint32_t index = callback_count++;
callback_context = context->busid_user_context;
Expand Down
Loading
Loading