Skip to content
Draft
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
2 changes: 2 additions & 0 deletions src/platform/h563xx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ target_sources(pslab-platform
adc_ll.c
led_ll.c
platform.c
spi_ll.c
tim_ll.c
uart_ll.c
usb_ll.c
Expand Down Expand Up @@ -32,6 +33,7 @@ target_link_libraries(pslab-platform
HAL::STM32::H5::GPIO
HAL::STM32::H5::RCCEx
HAL::STM32::H5::PWREx
HAL::STM32::H5::SPIEx
HAL::STM32::H5::TIMEx
HAL::STM32::H5::UARTEx
tinyusb
Expand Down
307 changes: 307 additions & 0 deletions src/platform/h563xx/spi_ll.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
/**
* @file spi_ll.c
* @brief SPI hardware implementation for STM32H563xx
*
* This module handles initialization and operation of the SPI peripheral of
* the STM32H5 microcontroller. It configures the hardware and dispatches SPI
* interrupts to the hardware-independent SPI implementation.
*
* Implementation Details:
* - Supports single SPI instance (SPI1)
* - Configured for 8-bit data size
* - DMA-based transmission and reception
*
* @author Tejas Garg
* @date 2024-10-09
*/

#include "stm32h5xx_hal.h"

#include "util/error.h"

#include "spi_ll.h"

enum { SPI_IRQ_PRIO = 4 }; // NVIC priority for SPI interrupts

/* SPI instance structure */
typedef struct {
SPI_HandleTypeDef *hspi;
bool initialized;
bool tx_in_progress;
bool rx_in_progress;
void (*tx_complete_callback)(void);
void (*rx_complete_callback)(void);
} SPIInstance;

/* HAL SPI handle */
static SPI_HandleTypeDef g_hspi1 = { nullptr };
static SPI_HandleTypeDef g_hspi2 = { nullptr };

/* DMA handles */
static DMA_HandleTypeDef g_hdma_spi1_tx = { nullptr };
static DMA_HandleTypeDef g_hdma_spi1_rx = { nullptr };
static DMA_HandleTypeDef g_hdma_spi2_tx = { nullptr };
static DMA_HandleTypeDef g_hdma_spi2_rx = { nullptr };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get it working without DMA first.


/* SPI instance configuration */
static SPIInstance g_spi_instances[2] = {
[SPI_BUS_0] = {
.hspi = &g_hspi1,
},
[SPI_BUS_1] = {
.hspi = &g_hspi2,
},
};

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef gpio_init = { 0 };

if (hspi->Instance == SPI1) {
/* SPI1 clock enable */
__HAL_RCC_SPI1_CLK_ENABLE();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPI has no clock source. It must be enabled in system_clock_config in platform.c. E.g.

--- a/src/platform/h563xx/platform.c
+++ b/src/platform/h563xx/platform.c
@@ -137,8 +137,9 @@ static void system_clock_config(void)

     /* Configure peripheral clocks - Set ADC clock source to PLL2R (75 MHz) */
     RCC_PeriphCLKInitTypeDef periph_clk_init = { 0 };
-    periph_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADCDAC;
+    periph_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADCDAC | RCC_PERIPHCLK_SPI1;
     periph_clk_init.AdcDacClockSelection = RCC_ADCDACCLKSOURCE_PLL2R;
+    periph_clk_init.Spi1ClockSelection = RCC_SPI1CLKSOURCE_PLL2P;

     // Configure PLL2 structure to match our desired configuration
     // This is required because HAL_RCCEx_PeriphCLKConfig calls
@@ -147,15 +148,17 @@ static void system_clock_config(void)
     periph_clk_init.PLL2.PLL2M = 2; // 8 MHz / 2 = 4 MHz
     // NOLINTNEXTLINE: readability-magic-numbers
     periph_clk_init.PLL2.PLL2N = 75; // 4 MHz * 75 = 300 MHz VCO
-    periph_clk_init.PLL2.PLL2P = 2; // 300 MHz / 2 = 150 MHz
+    periph_clk_init.PLL2.PLL2P = 3; // 300 MHz / 3 = 100 MHz (for SPI1)
     periph_clk_init.PLL2.PLL2Q = 2; // 300 MHz / 2 = 150 MHz
     periph_clk_init.PLL2.PLL2R = 4; // 300 MHz / 4 = 75 MHz (for ADC)
     periph_clk_init.PLL2.PLL2RGE = RCC_PLL2_VCIRANGE_1; // 2-4 MHz input range
     periph_clk_init.PLL2.PLL2VCOSEL =
         RCC_PLL2_VCORANGE_MEDIUM; // 150-420 MHz VCO
     periph_clk_init.PLL2.PLL2FRACN = 0;
+    // Enable PLL2 clock outputs
     periph_clk_init.PLL2.PLL2ClockOut =
-        RCC_PLL2_DIVR; // Enable PLL2R output for ADC
+        RCC_PLL2_DIVR | // Enable PLL2R output for ADC
+        RCC_PLL2_DIVP; // Enable PLL2P output for SPI1

     if (HAL_RCCEx_PeriphCLKConfig(&periph_clk_init) != HAL_OK) {
         /* ADC clock configuration failed */

__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPDMA1_CLK_ENABLE();

/* SPI1 GPIO Configuration: PA5=SCK, PA6=MISO, PA7=MOSI */
gpio_init.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
gpio_init.Mode = GPIO_MODE_AF_PP;
gpio_init.Pull = GPIO_NOPULL;
gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOA, &gpio_init);

/* Configure DMA for TX */
g_hdma_spi1_tx.Instance = GPDMA1_Channel6;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GPDMA1 channels 6 and 7 are used by ADC. 2,3,4,5 are free.

Copy link
Contributor

@bessman bessman Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scratch that, they're all in use by UART. Skip DMA for now, we may need to use DMA2 for this.

g_hdma_spi1_tx.Init.Request = GPDMA1_REQUEST_SPI1_TX;
g_hdma_spi1_tx.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
g_hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
g_hdma_spi1_tx.Init.SrcInc = DMA_SINC_INCREMENTED;
g_hdma_spi1_tx.Init.DestInc = DMA_DINC_FIXED;
g_hdma_spi1_tx.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
g_hdma_spi1_tx.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
g_hdma_spi1_tx.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
g_hdma_spi1_tx.Init.SrcBurstLength = 1;
g_hdma_spi1_tx.Init.DestBurstLength = 1;
g_hdma_spi1_tx.Init.TransferAllocatedPort =
(DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT0);
g_hdma_spi1_tx.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
g_hdma_spi1_tx.Init.Mode = DMA_NORMAL;
HAL_DMA_Init(&g_hdma_spi1_tx);
__HAL_LINKDMA(hspi, hdmatx, g_hdma_spi1_tx);

/* Configure DMA for RX */
g_hdma_spi1_rx.Instance = GPDMA1_Channel7;
g_hdma_spi1_rx.Init.Request = GPDMA1_REQUEST_SPI1_RX;
g_hdma_spi1_rx.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
g_hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
g_hdma_spi1_rx.Init.SrcInc = DMA_SINC_FIXED;
g_hdma_spi1_rx.Init.DestInc = DMA_DINC_INCREMENTED;
g_hdma_spi1_rx.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
g_hdma_spi1_rx.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
g_hdma_spi1_rx.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
g_hdma_spi1_rx.Init.SrcBurstLength = 1;
g_hdma_spi1_rx.Init.DestBurstLength = 1;
g_hdma_spi1_rx.Init.TransferAllocatedPort =
(DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT0);
g_hdma_spi1_rx.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
g_hdma_spi1_rx.Init.Mode = DMA_NORMAL;
HAL_DMA_Init(&g_hdma_spi1_rx);
__HAL_LINKDMA(hspi, hdmarx, g_hdma_spi1_rx);

/* SPI1 interrupt init */
HAL_NVIC_SetPriority(SPI1_IRQn, SPI_IRQ_PRIO, 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get it working without interrupts first.

HAL_NVIC_EnableIRQ(SPI1_IRQn);

/* DMA interrupt init */
HAL_NVIC_SetPriority(GPDMA1_Channel6_IRQn, SPI_IRQ_PRIO, 1);
HAL_NVIC_EnableIRQ(GPDMA1_Channel6_IRQn);
HAL_NVIC_SetPriority(GPDMA1_Channel7_IRQn, SPI_IRQ_PRIO, 1);
HAL_NVIC_EnableIRQ(GPDMA1_Channel7_IRQn);

} else if (hspi->Instance == SPI2) {
/* SPI2 GPIO Configuration: PB13=SCK, PB14=MISO, PB15=MOSI */
gpio_init.Pin = GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
gpio_init.Mode = GPIO_MODE_AF_PP;
gpio_init.Pull = GPIO_NOPULL;
gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(GPIOB, &gpio_init);

/* Configure DMA for TX */
g_hdma_spi2_tx.Instance = GPDMA1_Channel6;
g_hdma_spi2_tx.Init.Request = GPDMA1_REQUEST_SPI2_TX;
g_hdma_spi2_tx.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
g_hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
g_hdma_spi2_tx.Init.SrcInc = DMA_SINC_INCREMENTED;
g_hdma_spi2_tx.Init.DestInc = DMA_DINC_FIXED;
g_hdma_spi2_tx.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
g_hdma_spi2_tx.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
g_hdma_spi2_tx.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
g_hdma_spi2_tx.Init.SrcBurstLength = 1;
g_hdma_spi2_tx.Init.DestBurstLength = 1;
g_hdma_spi2_tx.Init.TransferAllocatedPort =
(DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT0);
g_hdma_spi2_tx.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
g_hdma_spi2_tx.Init.Mode = DMA_NORMAL;
HAL_DMA_Init(&g_hdma_spi2_tx);
__HAL_LINKDMA(hspi, hdmatx, g_hdma_spi2_tx);

/* Configure DMA for RX */
g_hdma_spi2_rx.Instance = GPDMA1_Channel7;
g_hdma_spi2_rx.Init.Request = GPDMA1_REQUEST_SPI2_RX;
g_hdma_spi2_rx.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
g_hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
g_hdma_spi2_rx.Init.SrcInc = DMA_SINC_FIXED;
g_hdma_spi2_rx.Init.DestInc = DMA_DINC_INCREMENTED;
g_hdma_spi2_rx.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
g_hdma_spi2_rx.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
g_hdma_spi2_rx.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
g_hdma_spi2_rx.Init.SrcBurstLength = 1;
g_hdma_spi2_rx.Init.DestBurstLength = 1;
g_hdma_spi2_rx.Init.TransferAllocatedPort =
(DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT0);
g_hdma_spi2_rx.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
g_hdma_spi2_rx.Init.Mode = DMA_NORMAL;
HAL_DMA_Init(&g_hdma_spi2_rx);
__HAL_LINKDMA(hspi, hdmarx, g_hdma_spi2_rx);

/* SPI2 interrupt init */
HAL_NVIC_SetPriority(SPI2_IRQn, SPI_IRQ_PRIO, 1);
HAL_NVIC_EnableIRQ(SPI2_IRQn);

/* DMA interrupt init */
HAL_NVIC_SetPriority(GPDMA1_Channel6_IRQn, SPI_IRQ_PRIO, 1);
HAL_NVIC_EnableIRQ(GPDMA1_Channel6_IRQn);
HAL_NVIC_SetPriority(GPDMA1_Channel7_IRQn, SPI_IRQ_PRIO, 1);
HAL_NVIC_EnableIRQ(GPDMA1_Channel7_IRQn);
}
}

/**
* @brief Initialize the SPI peripheral.
*
* @param bus SPI bus instance to initialize
*/
void SPI_LL_init(SPI_Bus bus)
{
if (bus >= SPI_BUS_COUNT) {
THROW(ERROR_INVALID_ARGUMENT);
}

SPIInstance *instance = &g_spi_instances[bus];
if (instance->initialized) {
return;
}
SPI_TypeDef *spi_instance[SPI_BUS_COUNT] = {
[SPI_BUS_0] = SPI1,
[SPI_BUS_1] = SPI2,
};

/* Initialize SPI1 */
instance->hspi->Instance = spi_instance[bus];

instance->hspi->Init.Mode = SPI_MODE_MASTER;
instance->hspi->Init.Direction = SPI_DIRECTION_2LINES;
instance->hspi->Init.DataSize = SPI_DATASIZE_8BIT;
instance->hspi->Init.CLKPolarity = SPI_POLARITY_LOW;
instance->hspi->Init.CLKPhase = SPI_PHASE_1EDGE;
instance->hspi->Init.NSS = SPI_NSS_SOFT;
instance->hspi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
instance->hspi->Init.FirstBit = SPI_FIRSTBIT_MSB;
instance->hspi->Init.TIMode = SPI_TIMODE_DISABLE;
instance->hspi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;

if (HAL_SPI_Init(instance->hspi) != HAL_OK) {
THROW(ERROR_HARDWARE_FAULT);
}

/* Configure NVIC for SPI interrupts */
HAL_NVIC_SetPriority(SPI1_IRQn, SPI_IRQ_PRIO, 0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);

instance->tx_in_progress = false;
instance->rx_in_progress = false;
instance->tx_complete_callback = nullptr;
instance->rx_complete_callback = nullptr;
instance->initialized = true;
}

/**
* @brief Deinitialize the SPI peripheral.
*
* @param bus SPI bus instance to deinitialize
*/
void SPI_LL_deinit(SPI_Bus bus)
{
if (bus >= SPI_BUS_COUNT) {
THROW(ERROR_INVALID_ARGUMENT);
}

SPIInstance *instance = &g_spi_instances[bus];
if (!instance->initialized) {
return;
}

/* Disable interrupts */
if (bus == SPI_BUS_0) {
HAL_NVIC_DisableIRQ(SPI1_IRQn);
} else if (bus == SPI_BUS_1) {
HAL_NVIC_DisableIRQ(SPI2_IRQn);
}

/* Deinitialize SPI */
if (HAL_SPI_DeInit(instance->hspi) != HAL_OK) {
THROW(ERROR_HARDWARE_FAULT);
}

instance->tx_in_progress = false;
instance->rx_in_progress = false;
instance->tx_complete_callback = nullptr;
instance->rx_complete_callback = nullptr;
instance->initialized = false;
}

/**
* @brief Transmit data over SPI.
*
* @param bus SPI bus instance to use
* @param data Pointer to the data buffer to transmit
* @param size Size of the data buffer
*/
void SPI_LL_transmit(SPI_Bus bus, uint8_t const *data, size_t size)
{
if (bus >= SPI_BUS_COUNT) {
THROW(ERROR_INVALID_ARGUMENT);
}

SPIInstance *instance = &g_spi_instances[bus];
if (!instance->initialized) {
THROW(ERROR_INVALID_ARGUMENT);
}

// Start the transmission
HAL_SPI_Transmit(instance->hspi, (uint8_t *)data, size, HAL_MAX_DELAY);
}
/**
* @brief Receive data over SPI.
*
* @param bus SPI bus instance to use
* @param data Pointer to the buffer to store received data
* @param size Size of the data buffer
*/

void SPI_LL_receive(SPI_Bus bus, uint8_t *data, size_t size)
{
if (bus >= SPI_BUS_COUNT) {
THROW(ERROR_INVALID_ARGUMENT);
}

SPIInstance *instance = &g_spi_instances[bus];
if (!instance->initialized) {
THROW(ERROR_INVALID_ARGUMENT);
}

// Start the reception
HAL_SPI_Receive(instance->hspi, data, size, HAL_MAX_DELAY);
}
2 changes: 1 addition & 1 deletion src/platform/h563xx/stm32h5xx_hal_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ extern "C" {
// #define HAL_SDRAM_MODULE_ENABLED
// #define HAL_SMARTCARD_MODULE_ENABLED
// #define HAL_SMBUS_MODULE_ENABLED
// #define HAL_SPI_MODULE_ENABLED
#define HAL_SPI_MODULE_ENABLED
// #define HAL_SRAM_MODULE_ENABLED
#define HAL_TIM_MODULE_ENABLED
#define HAL_UART_MODULE_ENABLED
Expand Down
35 changes: 35 additions & 0 deletions src/platform/spi_ll.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @file spi_ll.h
* @brief SPI low-level hardware interface for STM32H563xx
*/

#ifndef SPI_LL_H
#define SPI_LL_H

#include "stm32h5xx_hal.h"

/**
* @brief SPI bus instance enumeration
*/
typedef enum { SPI_BUS_0 = 0, SPI_BUS_1 = 1, SPI_BUS_COUNT = 2 } SPI_Bus;

/**
* @brief Initialize the SPI peripheral.
*/
void spi_ll_init(void);

/**
* @brief Transmit data over SPI.
* @param data Pointer to the data buffer to transmit.
* @param size Size of the data buffer.
*/
void spi_ll_transmit(uint8_t *data, uint16_t size);

/**
* @brief Receive data over SPI.
* @param data Pointer to the buffer to store received data.
* @param size Size of the data buffer.
*/
void spi_ll_receive(uint8_t *data, uint16_t size);

#endif /* SPI_LL_H */
Loading