Repository: gooey-synths/audio_dev_kit_bsp
Pull request #20: 19 add hardware spi class
Author: login: alexpratte16
Created: 2025-03-14T22:32:39Z | Merged: 2025-03-29T20:33:09Z

Created Class for controlling HW SPI bus

Tested by successfully reading DEVID register of DAC60508 on SPI2.


Diff:
diff --git a/.clang-format b/.clang-format
index a6d273e..8ef265a 100644
--- a/.clang-format
+++ b/.clang-format
@@ -93,7 +93,7 @@ BreakBeforeTernaryOperators: true
 BreakConstructorInitializers: BeforeColon
 BreakInheritanceList: BeforeColon
 BreakStringLiterals: true
-ColumnLimit:     80
+ColumnLimit:     120
 CommentPragmas:  '^ IWYU pragma:'
 CompactNamespaces: false
 ConstructorInitializerIndentWidth: 4
diff --git a/base/src/CMakeLists.txt b/base/src/CMakeLists.txt
index f02e2df..63f4efb 100644
--- a/base/src/CMakeLists.txt
+++ b/base/src/CMakeLists.txt
@@ -14,6 +14,8 @@ set(SOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/tests/test_uart.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/dma/dma.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/tests/test_dma.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/spi/spi.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/tests/test_spi.cpp
 
 )
 
diff --git a/base/src/dma/dma.hpp b/base/src/dma/dma.hpp
index c0bbe5f..fcd4f05 100644
--- a/base/src/dma/dma.hpp
+++ b/base/src/dma/dma.hpp
@@ -6,11 +6,25 @@
 
 namespace dma{
 
-#define DMA1_NUM_CHANNELS 8
+constexpr size_t DMA1_NUM_CHANNELS = 8; ///< Number of channels that each DMA controller has.
 
-#if 0
-#define DMA1_CLEAR_INT_FLAG(channel, int_type) DMA1->IFCR |= 1 << ((channel-1)*4+(int_type+1))
-#endif
+constexpr uint8_t SPI1_TX_REQ = 38; // Request number for SPI TX DMA requests.
+constexpr uint8_t SPI1_RX_REQ = 37; // Request number for SPI RX DMA requests.
+
+constexpr uint8_t SPI2_TX_REQ = 40; // Request number for SPI TX DMA requests.
+constexpr uint8_t SPI2_RX_REQ = 39; // Request number for SPI RX DMA requests.
+
+constexpr uint8_t SPI3_TX_REQ = 62; // Request number for SPI TX DMA requests.
+constexpr uint8_t SPI3_RX_REQ = 61; // Request number for SPI RX DMA requests.
+
+constexpr uint8_t SPI4_TX_REQ = 84; // Request number for SPI TX DMA requests.
+constexpr uint8_t SPI4_RX_REQ = 83; // Request number for SPI RX DMA requests.
+
+constexpr uint8_t SPI5_TX_REQ = 86; // Request number for SPI TX DMA requests.
+constexpr uint8_t SPI5_RX_REQ = 85; // Request number for SPI RX DMA requests.
+
+constexpr uint8_t SPI6_TX_REQ = 12; // Request number for SPI TX DMA requests.
+constexpr uint8_t SPI6_RX_REQ = 11; // Request number for SPI RX DMA requests.
 
 enum eDmaTransferType{
 	PER2MEM = 0,
@@ -75,17 +89,16 @@ class DmaController{
 			return mStreamHw->NDTR;
 		}
 
-	protected:
+	private:
+		friend class DmaController;
+		void claim();
+		void release();
+
 		uint8_t mChannelNum; ///< Channel number, 1 indexed to align with reference manual
 		uint8_t mChannelClaimed; ///< 1 if channel is claimed, 0 otherwise
 		uint8_t mChannelPriority; ///< Priority of channel
 		DmaController* mChannelController; ///< Pointer to controller for this channel
 		DMA_Stream_TypeDef* mStreamHw; ///< pointer to either DMA1 or DMA2 strean
-		void claim();
-		void release();
-
-	private:
-		friend class DmaController;
 		eDmaTransferType mTransferType; ///< Transfer type that the channel is configured for
 	};
 	
diff --git a/base/src/init/main.cpp b/base/src/init/main.cpp
index 3d802b3..90bee48 100644
--- a/base/src/init/main.cpp
+++ b/base/src/init/main.cpp
@@ -1,6 +1,5 @@
 #include "../tests/tests.hpp"
 
 int main(){
-    test_gpio_blinky();
-    return 0;
+    test_spi_hw_transaction();
 }
diff --git a/base/src/init/startup.c b/base/src/init/startup.c
index f777509..fa12d69 100644
--- a/base/src/init/startup.c
+++ b/base/src/init/startup.c
@@ -5,7 +5,7 @@
 // prototypes
 void reset_handler();
 void halt();
-extern int main(void); // defined in main.c
+extern int main(); // defined in main.c
 
 #define ISR_NOT_IMPL ((uint32_t*) halt)
 
@@ -191,6 +191,8 @@ uint32_t* vector_table[] = {
     ISR_NOT_IMPL  /* Interrupt for all 6 wake-up pins */
 };
 
+uint32_t* vector_table_ram[166]__attribute__((aligned (512))); // Remapped vector table must be aligned to rounded up power of 2
+
 ///
 /// Enable the FPU coprocessor
 ///
@@ -316,18 +318,22 @@ void start_clocks(){
     RCC->PLLCKSELR =
         2 << 0 |  // HSE as PLL clock
         1 << 4;   // Prescale PLL1 by 1
-    uint8_t pll1_divn = (uint32_t)D1_TARGET/XTAL_FREQ;
+    uint16_t pll1_divn = (uint32_t)D1_TARGET/XTAL_FREQ; // This sets our VCO frequency.
+    uint8_t  pll1_divp = (uint32_t)D1_TARGET/PLL1_DIVP_TARGET; // We will be using DIVP as our D1 target.
+    uint8_t  pll1_divq = (uint32_t)D1_TARGET/PLL1_DIVQ_TARGET;
 
     RCC->PLLCFGR = 
-        //1 << 18 | // Enable pll1 divr
-        //1 << 17 | // Enable pll1 divq
-        1 << 16 | // Enable pll1 divp
+        //1 << 18 | // ENABLE pll1 divr
+        1 << 17 | // ENABLE pll1 divq
+        1 << 16 | // ENABLE pll1 divp
         3 << 2  | // clock rate frequency is between 8 an 16MHz TODO: calculate clock range beforehand
         0 << 1  | // Wide VCO range 192 to 836 MHz
         0 << 0;  // No fractional divider.
 
-    RCC->PLL1DIVR = 
-        pll1_divn -1 << 0; // Set PLL1 divN divider
+    RCC->PLL1DIVR =
+        ((pll1_divn - 1) & 0x1FF) << 0 | // Set PLL1 divN divider
+        ((pll1_divp - 1) & 0x7F)  << 9 | // Set PLL1 divP divider
+        ((pll1_divq - 1) & 0x7F) << 16;  // Set PLL1 divQ divider
 
     RCC->CR |= 1 << 24; // Enable PLL1
 
@@ -404,6 +410,14 @@ __attribute__ ((noreturn)) void reset_handler(){
         *bss_ptr++ = 0;
     }
 
+    // Copy vector table to RAM location
+    for(unsigned int i = 0; i < sizeof(vector_table)/sizeof(*vector_table); i++){
+        vector_table_ram[i] = vector_table[i];
+    }
+
+    // Remap vector table to RAM location
+    SCB->VTOR = (uint32_t)vector_table_ram; //NOLINT
+
     enable_fpu();
 
     start_clocks();
@@ -418,4 +432,4 @@ __attribute__ ((noreturn)) void reset_handler(){
 ///
 __attribute__ ((noreturn)) void halt(){
     while(1){;}
-}
+}
\ No newline at end of file
diff --git a/base/src/spi/spi.cpp b/base/src/spi/spi.cpp
new file mode 100644
index 0000000..19fffac
--- /dev/null
+++ b/base/src/spi/spi.cpp
@@ -0,0 +1,228 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi {
+
+SpiBusBase *sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num)
+    : mIsActive(false), mSpiNum(instance_num), mKerClkFreq(0),
+      // SPI 6 is connected to DMA2
+      mTxDma(dma::DmaController::getInstance(1 + (size_t)(instance_num > 5))->claimAvailableChannel()),
+      mRxDma(dma::DmaController::getInstance(1 + (size_t)(instance_num > 5))->claimAvailableChannel()) {
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch (instance_num) {
+    case 1:
+        mSpiHw = SPI1;
+        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+        mKerClkFreq = PLL1_DIVQ_TARGET;
+        mSpiIrqN = SPI1_IRQn;
+        RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+        RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+        set_vector_table_entry((int)mSpiIrqN + 16, (uint32_t *)spi1Isr);
+        mTxDma->setRequest(dma::SPI1_TX_REQ);
+        mRxDma->setRequest(dma::SPI1_RX_REQ);
+        break;
+    case 2:
+        mSpiHw = SPI2;
+        RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+        mKerClkFreq = PLL1_DIVQ_TARGET;
+        mSpiIrqN = SPI2_IRQn;
+        RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+        RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+        set_vector_table_entry((int)mSpiIrqN + 16, (uint32_t *)spi2Isr);
+        mTxDma->setRequest(dma::SPI2_TX_REQ);
+        mRxDma->setRequest(dma::SPI2_RX_REQ);
+        break;
+    case 3:
+        mSpiHw = SPI3;
+        RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+        mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+        mSpiIrqN = SPI3_IRQn;
+        RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+        RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+        set_vector_table_entry((int)mSpiIrqN + 16, (uint32_t *)spi3Isr);
+        mTxDma->setRequest(dma::SPI3_TX_REQ);
+        mRxDma->setRequest(dma::SPI3_RX_REQ);
+        break;
+    case 4:
+        mSpiHw = SPI4;
+        RCC->APB2ENR |= RCC_APB2ENR_SPI4EN;
+        mKerClkFreq = 0; // todo: Update if needed.
+        mSpiIrqN = SPI4_IRQn;
+        RCC->APB2RSTR |= RCC_APB2RSTR_SPI4RST;
+        RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI4RST);
+        set_vector_table_entry((int)mSpiIrqN + 16, (uint32_t *)spi4Isr);
+        mTxDma->setRequest(dma::SPI4_TX_REQ);
+        mRxDma->setRequest(dma::SPI4_RX_REQ);
+        break;
+    case 5:
+        mSpiHw = SPI5;
+        RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
+        mKerClkFreq = 0;
+        mSpiIrqN = SPI5_IRQn;
+        RCC->APB2RSTR |= RCC_APB2RSTR_SPI5RST;
+        RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI5RST);
+        set_vector_table_entry((int)mSpiIrqN + 16, (uint32_t *)spi5Isr);
+        mTxDma->setRequest(dma::SPI5_TX_REQ);
+        mRxDma->setRequest(dma::SPI5_RX_REQ);
+        break;
+    case 6:
+        mSpiHw = SPI6;
+        RCC->APB4ENR |= RCC_APB4ENR_SPI6EN;
+        mKerClkFreq = 0; // todo: Update if needed.
+        mSpiIrqN = SPI6_IRQn;
+        RCC->APB4RSTR |= RCC_APB4RSTR_SPI6RST;
+        RCC->APB4RSTR &= ~(RCC_APB4RSTR_SPI6RST);
+        set_vector_table_entry((int)mSpiIrqN + 16, (uint32_t *)spi6Isr);
+        mTxDma->setRequest(dma::SPI6_TX_REQ);
+        mRxDma->setRequest(dma::SPI6_RX_REQ);
+        break;
+    }
+
+    NVIC_DisableIRQ(mSpiIrqN);
+
+    sInstances[mSpiNum - 1] = this;
+
+    mSpiHw->CR1 &= ~SPI_CR1_SPE;                     // disable SPI
+    mSpiHw->IER |= (SPI_IER_TXPIE | SPI_IER_RXPIE);  // Enable tx and rx interrupt.
+    mSpiHw->CR1 |= SPI_CR1_SSI;                      // Set CS pin high
+    mSpiHw->IFCR |= 0x1FF << SPI_IFCR_EOTC_Pos;      // Clear all interrupts
+    mSpiHw->CFG2 |= SPI_CFG2_MASTER | SPI_CFG2_SSOE; // SPI master, SS output enable
+
+    mTxDma->setTransferType(dma::MEM2PER, true);
+    mRxDma->setTransferType(dma::PER2MEM, true);
+}
+
+///
+/// Configure the SPI bus.
+/// @param conf SPI bus configuration.
+/// @note This might undershoot target frequency.
+///
+void SpiBusBase::configure(SpiBusConfig conf) {
+
+    // Calculate and set divider for target frequency
+    for (uint8_t iDiv = 0; iDiv <= 7; iDiv++) {
+        mSpiHw->CFG1 &= ~(SPI_CFG1_MBR_Msk);      // Clear baud rate bits
+        mSpiHw->CFG1 |= iDiv << SPI_CFG1_MBR_Pos; // Set baud rate bits
+        if (mKerClkFreq / (1 << iDiv) <= conf.mFreq / 4) {
+            break;
+        }
+    }
+
+    // Configure phase
+    if (conf.mPhase) {
+        mSpiHw->CFG2 |= (SPI_CFG2_CPHA);
+    } else {
+        mSpiHw->CFG2 &= ~(SPI_CFG2_CPHA);
+    }
+
+    // Configure polarity
+    if (conf.mPolarity) {
+        mSpiHw->CFG2 |= (SPI_CFG2_CPOL);
+    } else {
+        mSpiHw->CFG2 &= ~(SPI_CFG2_CPOL);
+    }
+
+    // Configure IO swap
+    if (conf.mIoSwap) {
+        mSpiHw->CFG2 |= (SPI_CFG2_IOSWP);
+    } else {
+        mSpiHw->CFG2 &= ~(SPI_CFG2_IOSWP);
+    }
+
+    // Configure word size
+    mSpiHw->CFG1 &= ~(SPI_CFG1_DSIZE_Msk);                             // Clear word size bits
+    mSpiHw->CFG1 |= (conf.mWordSize - 1 & 0x1F) << SPI_CFG1_DSIZE_Pos; // Set word size bits
+
+    // Configure master inter-data-idleness
+    mSpiHw->CFG2 &= ~(SPI_CFG2_MIDI_Msk);                      // Clear MIDI
+    mSpiHw->CFG2 |= ((conf.mMidi & 0xF) << SPI_CFG2_MIDI_Pos); // Set MIDI
+}
+
+///
+/// Get the current bus configuration.
+/// @return The current bus configuration.
+///
+SpiBusConfig SpiBusBase::getConfiguration() {
+    SpiBusConfig ret;
+
+    uint32_t div = mSpiHw->CFG1 & (SPI_CFG1_MBR_Msk) >> SPI_CFG1_MBR_Pos;
+    ret.mFreq = mKerClkFreq / (1 << (div + 1));
+    ret.mPhase = !!(mSpiHw->CFG2 & (SPI_CFG2_CPHA));
+    ret.mPolarity = !!(mSpiHw->CFG2 & (SPI_CFG2_CPOL));
+    ret.mIoSwap = !!(mSpiHw->CFG2 & (SPI_CFG2_IOSWP));
+    ret.mWordSize = 1 + (mSpiHw->CFG1 & SPI_CFG1_DSIZE_Msk) >> SPI_CFG1_DSIZE_Pos;
+    ret.mMidi = (mSpiHw->CFG2 & SPI_CFG2_MIDI_Msk) >> SPI_CFG2_MIDI_Pos;
+
+    return ret;
+}
+
+///
+/// Initialize HW control of the CS pin.
+///
+void HwCsSpiBus::HwCsSpiBusInit() {
+    mSpiHw->CFG2 &= ~(SPI_CFG2_SSM); // Clear software management of CS pin.
+    mSpiHw->CFG2 |= (SPI_CFG2_SSOM); //  Set SSOM, allows for inter-data idleness
+    mSpiHw->CFG2 |= SPI_CFG2_IOSWP;  //  Swap CIPO and COPI
+}
+
+///
+/// Inherit documentation.
+/// @todo add ability to only receive or transmit.
+///
+void HwCsSpiBus::prepare(void *txBuff, void *rxBuff, size_t bufLen, size_t cs, size_t dataSize) {
+    (void)cs;
+
+    mRxDma->disable();
+    mTxDma->disable();
+
+    // Tx and Rx
+    mTxDma->setSource(txBuff, dataSize, 1);
+    mTxDma->setDest((void *)&mSpiHw->TXDR, dataSize, 0);
+    mTxDma->setNumTransfers(bufLen, 0);
+
+    mRxDma->setSource((void *)&mSpiHw->RXDR, dataSize, 0);
+    mRxDma->setDest(rxBuff, dataSize, 1);
+    mRxDma->setNumTransfers(bufLen, 0);
+
+    mSpiHw->CR2 = bufLen / dataSize;
+}
+
+///
+/// Inherit documentation.
+///
+void HwCsSpiBus::transact() {
+    mSpiHw->CR1 &= ~(SPI_CR1_SPE); // Disable SPI
+
+    mSpiHw->IER |= SPI_IER_TXPIE | SPI_IER_RXPIE; // TX and RX interrupt
+
+    mSpiHw->CFG1 &= ~(SPI_CFG1_TXDMAEN | SPI_CFG1_RXDMAEN); // Disable RX and TX DMA requests
+
+    mSpiHw->IFCR |= 0x1FF << SPI_IFCR_EOTC_Pos; // Clear interrupt flags
+
+    mTxDma->begin();
+    mRxDma->begin();
+
+    mSpiHw->CFG1 |= SPI_CFG1_TXDMAEN | SPI_CFG1_RXDMAEN; // Enable RX and TX DMA requests
+
+    mSpiHw->CR1 |= SPI_CR1_SPE; // enable SPI
+
+    mSpiHw->CR1 &= ~(SPI_CR1_SSI_Msk); // bring the CS pin low.
+
+    mSpiHw->CR1 |= SPI_CR1_CSTART; // START
+
+    mIsActive = true;
+    NVIC_EnableIRQ(mSpiIrqN);
+}
+
+}; // namespace spi
\ No newline at end of file
diff --git a/base/src/spi/spi.hpp b/base/src/spi/spi.hpp
new file mode 100644
index 0000000..96486bb
--- /dev/null
+++ b/base/src/spi/spi.hpp
@@ -0,0 +1,165 @@
+#ifndef SPI_HPP
+#define SPI_HPP
+
+#include "../dma/dma.hpp"
+#include "../gpio/gpio.hpp"
+#include "../system/board_defs.h"
+#include "../system/stm32h750xx.h"
+#include "spibus_interface.hpp"
+
+#include <cassert>
+#include <stdint.h>
+
+namespace spi {
+
+#define NUM_SPI_CONTROLLERS 6 ///< Number of spi controllers on the stm32h750.
+
+class SpiBusBase; ///< forward reference.
+
+extern SpiBusBase *sInstances[NUM_SPI_CONTROLLERS]; ///< instances of the spi buses
+
+///
+/// Base class for SPI buses.
+/// @todo CRC?
+///
+class SpiBusBase : ISpiBus {
+
+  public:
+    SpiBusBase(size_t);
+
+    ///
+    /// Destructor.
+    ///
+    virtual ~SpiBusBase() { sInstances[mSpiNum] = NULL; }
+
+    void configure(SpiBusConfig conf) override;
+
+    SpiBusConfig getConfiguration() override;
+
+    ///
+    /// Wait for the SPI bus to complete.
+    ///
+    void waitForCompletion() override {
+        while (mIsActive) {
+            ; // Do nothing
+        }
+    }
+
+  protected:
+    ///
+    /// SPI interrupt.
+    ///
+    void interrupt() {
+
+        // transfer complete flag
+        if (mSpiHw->SR & SPI_SR_EOT) {
+            cleanup();
+
+            NVIC_DisableIRQ(mSpiIrqN);
+
+            mIsActive = false;
+
+            mSpiHw->IFCR |= 0x1FF << 3; // clear interrupt flags
+        }
+    }
+
+    static void spi1Isr() {
+        if (sInstances[0] != NULL) {
+            sInstances[0]->interrupt();
+        }
+    }
+    static void spi2Isr() {
+        if (sInstances[1] != NULL) {
+            sInstances[1]->interrupt();
+        }
+    }
+    static void spi3Isr() {
+        if (sInstances[2] != NULL) {
+            sInstances[2]->interrupt();
+        }
+    }
+    static void spi4Isr() {
+        if (sInstances[3] != NULL) {
+            sInstances[3]->interrupt();
+        }
+    }
+    static void spi5Isr() {
+        if (sInstances[4] != NULL) {
+            sInstances[4]->interrupt();
+        }
+    }
+    static void spi6Isr() {
+        if (sInstances[5] != NULL) {
+            sInstances[5]->interrupt();
+        }
+    }
+
+    uint8_t mSpiNum;                        ///< Number of the SPI bus.
+    volatile bool mIsActive;                ///< True if the SPI is currently active.
+    IRQn_Type mSpiIrqN;                     ///< Interrupt number for the SPI controller.
+    SPI_TypeDef *mSpiHw;                    ///< SPI peripheral pointer.
+    SpiBusConfig mConfig;                   ///< SPI bus configuration.
+    uint32_t mKerClkFreq;                   ///< Frequency of the SPI_KER_CLK.
+    dma::DmaController::DmaChannel *mTxDma; ///< TX DMA channel
+    dma::DmaController::DmaChannel *mRxDma; ///< RX DMA channel
+
+  private:
+    ///
+    /// Throw an exception if multiple instances are decalared of the same SPI bus.
+    /// @param instance_num Spi bus number
+    /// @todo Add exception.
+    ///
+    static void errorIfTaken(size_t instanceNum) {
+        instanceNum -= 1; // 1 index to 0 index conversion
+        if (instanceNum > NUM_SPI_CONTROLLERS || sInstances[instanceNum]) {
+            assert(false);
+        }
+    }
+};
+
+extern SpiBusBase *sInstances[NUM_SPI_CONTROLLERS]; ///< instances of the spi buses
+
+///
+/// Hw controlled chip select spi bus
+/// @note Can only control 1 chip select line
+///
+class HwCsSpiBus : public SpiBusBase {
+
+  public:
+    ///
+    /// Constructor.
+    /// @param instance_num SPI bus number.
+    ///
+    HwCsSpiBus(size_t instance_num) : SpiBusBase(instance_num) { HwCsSpiBusInit(); }
+
+    ///
+    /// Inherit documentation.
+    ///
+    void prepare(void *txBuff, void *rxBuff, size_t bufLen, size_t cs, size_t dataSize = sizeof(uint8_t)) override;
+
+    ///
+    /// Inherit documentation.
+    ///
+    void transact() override;
+
+  protected:
+    ///
+    /// Inherit documentation.
+    ///
+    virtual void cleanup() override {
+        mSpiHw->CR1 |= SPI_CR1_SSI; // Set chip select high.
+    }
+
+  private:
+    void HwCsSpiBusInit();
+};
+
+///
+/// Sw controlled chip select spi bus
+/// @todo fill this in.
+///
+class SwCsSpiBus : SpiBusBase {};
+
+} // namespace spi
+
+#endif // SPI_HPP
\ No newline at end of file
diff --git a/base/src/spi/spibus_interface.hpp b/base/src/spi/spibus_interface.hpp
new file mode 100644
index 0000000..bb29a33
--- /dev/null
+++ b/base/src/spi/spibus_interface.hpp
@@ -0,0 +1,35 @@
+#ifndef SPIBUSINTERFACE_HPP
+#define SPIBUSINTERFACE_HPP
+
+#include <cstdint>
+
+namespace spi {
+
+// Struct for SPI bus configuration
+struct SpiBusConfig {
+    bool mPolarity;    ///< Clock polarity
+    bool mPhase;       ///< Clock phase
+    bool mIoSwap;      ///< True if IO lines should be swapped
+    uint32_t mFreq;    ///< Clock frequency
+    uint8_t mWordSize; ///< Data word size in bits
+    uint8_t mMidi;     ///< Master inter-data idleness (HW SPI only)
+};
+
+// SPI bus interface
+class ISpiBus {
+  public:
+    virtual ~ISpiBus() = default;
+    virtual void configure(SpiBusConfig conf) = 0;
+    virtual SpiBusConfig getConfiguration() = 0;
+    virtual void prepare(void *txBuff, void *rxBuff, size_t bufLen, size_t cs, size_t dataSize) = 0;
+    virtual void transact() = 0;
+    virtual void waitForCompletion() = 0;
+
+  protected:
+    virtual void interrupt() = 0;
+    virtual void cleanup() = 0;
+};
+
+} // namespace spi
+
+#endif // SPIBUSINTERFACE_HPP
\ No newline at end of file
diff --git a/base/src/system/board_defs.h b/base/src/system/board_defs.h
index d604051..afde227 100644
--- a/base/src/system/board_defs.h
+++ b/base/src/system/board_defs.h
@@ -11,6 +11,9 @@
 #define APB1_TARGET (AHB_AXI_TARGET/4)   //< Target Hz for APB1 peripherals
 #define APB2_TARGET (AHB_AXI_TARGET/4)   //< Target Hz for APB2 peripherals
 #define APB4_TARGET (AHB_AXI_TARGET/4)   //< Target Hz for APB4 peripherals
+/* PLL targets */
+#define PLL1_DIVP_TARGET (D1_TARGET)     //< Frequency of PLL1 div P
+#define PLL1_DIVQ_TARGET (XTAL_FREQ*10)  //< Frequency of PLL1 div Q
 
 /* Pin definitions found in pin_defs.c */
 
@@ -26,4 +29,64 @@ extern GPIOConf uart_1_tx_conf;
 extern GPIOPin  uart_1_rx_pin;
 extern GPIOConf uart_1_rx_conf;
 
+extern GPIOPin spi2_clk_pin;
+extern GPIOConf spi2_clk_conf;
+
+extern GPIOPin spi2_cipo_pin;
+extern GPIOConf spi2_cipo_conf;
+
+extern GPIOPin spi2_copi_pin;
+extern GPIOConf spi2_copi_conf;
+
+extern GPIOPin spi2_cs0_pin;
+extern GPIOConf spi2_cs0_conf;
+
+extern GPIOPin spi3_clk_pin;
+extern GPIOConf spi3_clk_conf;
+
+extern GPIOPin spi3_cipo_pin;
+extern GPIOConf spi3_cipo_conf;
+
+extern GPIOPin spi3_copi_pin;
+extern GPIOConf spi3_copi_conf;
+
+extern GPIOPin spi3_cs0_pin;
+extern GPIOConf spi3_cs0_conf;
+
+extern GPIOPin spi3_cs1_pin;
+extern GPIOConf spi3_cs1_conf;
+
+extern GPIOPin spi3_cs2_pin;
+extern GPIOConf spi3_cs2_conf;
+
+extern GPIOPin spi2_clk_pin;
+extern GPIOConf spi2_clk_conf;
+
+extern GPIOPin spi2_cipo_pin;
+extern GPIOConf spi2_cipo_conf;
+
+extern GPIOPin spi2_copi_pin;
+extern GPIOConf spi2_copi_conf;
+
+extern GPIOPin spi2_cs0_pin;
+extern GPIOConf spi2_cs0_conf;
+
+extern GPIOPin spi3_clk_pin;
+extern GPIOConf spi3_clk_conf;
+
+extern GPIOPin spi3_cipo_pin;
+extern GPIOConf spi3_cipo_conf;
+
+extern GPIOPin spi3_copi_pin;
+extern GPIOConf spi3_copi_conf;
+
+extern GPIOPin spi3_cs0_pin;
+extern GPIOConf spi3_cs0_conf;
+
+extern GPIOPin spi3_cs1_pin;
+extern GPIOConf spi3_cs1_conf;
+
+extern GPIOPin spi3_cs2_pin;
+extern GPIOConf spi3_cs2_conf;
+
 #endif
\ No newline at end of file
diff --git a/base/src/system/core_cm7.h b/base/src/system/core_cm7.h
index 6596ea2..b3a625f 100644
--- a/base/src/system/core_cm7.h
+++ b/base/src/system/core_cm7.h
@@ -34,11 +34,16 @@
 #define __CORE_CM7_H_GENERIC
 
 #include <stdint.h>
+#include "stm32h750xx.h"
 
 #ifdef __cplusplus
  extern "C" {
 #endif
 
+#define __NOP() __asm volatile ("nop")
+#define __ISB() __asm volatile ("isb")
+#define __DSB() __asm volatile ("dsb")
+
 /**
   \page CMSIS_MISRA_Exceptions  MISRA-C:2004 Compliance Exceptions
   CMSIS violates the following MISRA-C:2004 rules:
@@ -1793,7 +1798,6 @@ typedef struct
   \defgroup CMSIS_Core_FunctionInterface Functions and Instructions Reference
 */
 
-#if 0
 
 /* ##########################   NVIC functions  #################################### */
 /**
@@ -2622,7 +2626,6 @@ __STATIC_INLINE int32_t ITM_CheckChar (void)
     return (1);                              /*    character available */
   }
 }
-#endif
 
 /*@} end of CMSIS_core_DebugFunctions */
 
diff --git a/base/src/system/pin_defs.c b/base/src/system/pin_defs.c
index 0064f3b..a0a0a4a 100644
--- a/base/src/system/pin_defs.c
+++ b/base/src/system/pin_defs.c
@@ -34,7 +34,7 @@ const GPIOConf mco2_pin_conf = {
 
 const GPIOPin uart_1_tx_pin = {
     .port = 1,
-    .pin = 14
+    .pin = 6
 };
 
 const GPIOConf uart_1_tx_conf = {
@@ -42,12 +42,12 @@ const GPIOConf uart_1_tx_conf = {
     .mode = ALTERNATE,
     .speed = VERY_HIGH_SPEED,
     .pull = NO_PULL,
-    .alternate_function = 4
+    .alternate_function = 7
 };
 
 const GPIOPin uart_1_rx_pin = {
     .port = 1,
-    .pin = 15
+    .pin = 7
 };
 
 const GPIOConf uart_1_rx_conf = {
@@ -55,5 +55,135 @@ const GPIOConf uart_1_rx_conf = {
     .mode = ALTERNATE,
     .speed = VERY_HIGH_SPEED,
     .pull = NO_PULL,
-    .alternate_function = 4
+    .alternate_function = 7
+};
+
+// SPI pins
+
+const GPIOPin spi2_clk_pin = {
+    .port = 1,
+    .pin = 13
+};
+
+const GPIOConf spi2_clk_conf = {
+    .type = PUSH_PULL_TYPE,
+    .mode = ALTERNATE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
+    .alternate_function = 5
+};
+
+const GPIOPin spi2_cipo_pin = {
+    .port = 1,
+    .pin = 15
+};
+
+const GPIOConf spi2_cipo_conf = {
+    .type = PUSH_PULL_TYPE,
+    .mode = ALTERNATE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
+    .alternate_function = 5
+};
+
+const GPIOPin spi2_copi_pin = {
+    .port = 1,
+    .pin = 14
+};
+
+const GPIOConf spi2_copi_conf = {
+    .type = PUSH_PULL_TYPE,
+    .mode = ALTERNATE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
+    .alternate_function = 5
+};
+
+const GPIOPin spi2_cs0_pin = {
+    .port = 1,
+    .pin = 12
+};
+
+const GPIOConf spi2_cs0_conf = {
+    .type = PUSH_PULL_TYPE,
+    .mode = ALTERNATE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
+    .alternate_function = 5
+};
+
+const GPIOPin spi3_clk_pin = {
+    .port = 2,
+    .pin = 10
+};
+
+const GPIOConf spi3_clk_conf = {
+    .type = PUSH_PULL_TYPE,
+    .mode = INPUT_MODE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
+    .alternate_function = 6
+};
+
+const GPIOPin spi3_cipo_pin = {
+    .port = 2,
+    .pin = 11
+};
+
+const GPIOConf spi3_cipo_conf = {
+    .type = OPEN_DRAIN_TYPE,
+    .mode = INPUT_MODE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
+    .alternate_function = 6
+};
+
+const GPIOPin spi3_copi_pin = {
+    .port = 2,
+    .pin = 12
+};
+
+const GPIOConf spi3_copi_conf = {
+    .type = OPEN_DRAIN_TYPE,
+    .mode = INPUT_MODE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
+    .alternate_function = 6
+};
+
+const GPIOPin spi3_cs0_pin = {
+    .port = 2,
+    .pin = 6
+};
+
+const GPIOConf spi3_cs0_conf = {
+    .type = OPEN_DRAIN_TYPE,
+    .mode = INPUT_MODE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
+};
+
+
+const GPIOPin spi3_cs1_pin = {
+    .port = 2,
+    .pin = 7
+};
+
+const GPIOConf spi3_cs1_conf = {
+    .type = OPEN_DRAIN_TYPE,
+    .mode = INPUT_MODE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
+};
+
+const GPIOPin spi3_cs2_pin = {
+    .port = 2,
+    .pin = 8
+};
+
+const GPIOConf spi3_cs2_conf = {
+    .type = OPEN_DRAIN_TYPE,
+    .mode = INPUT_MODE,
+    .speed = VERY_HIGH_SPEED,
+    .pull = NO_PULL,
 };
diff --git a/base/src/tests/test_spi.cpp b/base/src/tests/test_spi.cpp
new file mode 100644
index 0000000..f07e0cc
--- /dev/null
+++ b/base/src/tests/test_spi.cpp
@@ -0,0 +1,90 @@
+#include "../spi/spi.hpp"
+#include "../uart/uart.hpp"
+#include "../system/board_defs.h"
+#include "test_helper.hpp"
+
+using namespace spi;
+
+#define BUFFER_SIZE 3
+
+uint8_t txBuff[BUFFER_SIZE];
+uint8_t rxBuff[BUFFER_SIZE];
+
+
+///
+/// Setup SPI pins
+///
+static void setup_pins(){
+    gpio::GPIOController* gpio_controller = gpio::GPIOController::getInstance();
+
+    gpio_controller->setConfig(&spi2_clk_pin,  &spi2_clk_conf);
+    gpio_controller->setConfig(&spi2_cipo_pin, &spi2_cipo_conf);
+    gpio_controller->setConfig(&spi2_copi_pin, &spi2_copi_conf);
+    gpio_controller->setConfig(&spi2_cs0_pin,  &spi2_cs0_conf);
+
+    gpio_controller->setConfig(&spi3_clk_pin,  &spi3_clk_conf);
+    gpio_controller->setConfig(&spi3_cipo_pin, &spi3_cipo_conf);
+    gpio_controller->setConfig(&spi3_copi_pin, &spi3_copi_conf);
+    gpio_controller->setConfig(&spi3_cs0_pin,  &spi3_cs0_conf);
+    gpio_controller->setConfig(&spi3_cs1_pin,  &spi3_cs1_conf);
+    gpio_controller->setConfig(&spi3_cs2_pin,  &spi3_cs2_conf);
+
+    gpio_controller->setConfig(&uart_1_tx_pin, &uart_1_tx_conf);
+    gpio_controller->setConfig(&uart_1_rx_pin, &uart_1_rx_conf);
+}
+
+///
+/// Test a transaction on the hardware spi bus
+///
+void test_spi_hw_transaction(){
+    setup_pins();
+
+    HwCsSpiBus spiBus(2);
+
+    uart::UartController uart1(1);
+
+    SpiBusConfig conf;
+    conf.mFreq = 3000000U;
+    conf.mPhase = 0;
+    conf.mPolarity = 1;
+    conf.mWordSize = 8;
+    conf.mIoSwap = true;
+    conf.mMidi = 0;
+
+    // Setup tx buffer to read device ID from DAC
+    txBuff[0] = 0x81;
+    txBuff[1] = 0x00;
+    txBuff[2] = 0x00;
+
+    spiBus.configure(conf);
+
+    SpiBusConfig gotConf = spiBus.getConfiguration();
+
+    assert(gotConf.mFreq <= conf.mFreq);
+    assert(gotConf.mPhase == conf.mPhase);
+    assert(gotConf.mPolarity == conf.mPolarity);
+    assert(gotConf.mWordSize == conf.mWordSize);
+    assert(gotConf.mIoSwap == conf.mIoSwap);
+    assert(gotConf.mMidi == conf.mMidi);
+
+    spiBus.prepare(txBuff, rxBuff, BUFFER_SIZE, 0, sizeof(*txBuff));
+
+    while(1){
+        // setup rx buffer
+        for(size_t i = 0; i < BUFFER_SIZE; i++){
+            rxBuff[i] = 1;
+        }
+
+        spiBus.transact();
+        
+        spiBus.waitForCompletion();
+
+        uint16_t devId = (uint16_t)rxBuff[1] << 8 | rxBuff[2];
+        print_buffer(&uart1, (void*)&devId, sizeof(devId), 1);
+
+
+        for(int i = 0; i < 0x1FFFFF; i++); //delay...s
+
+    }
+
+}
diff --git a/base/src/tests/tests.hpp b/base/src/tests/tests.hpp
index 4e306bf..11cfc54 100644
--- a/base/src/tests/tests.hpp
+++ b/base/src/tests/tests.hpp
@@ -10,3 +10,6 @@ extern void test_dma_single_buffer();
 // UART test
 extern void test_uart_hello_world();
 extern void test_uart_echo();
+
+// SPI tests
+extern void test_spi_hw_transaction();
\ No newline at end of file
diff --git a/base/src/util/util.h b/base/src/util/util.h
new file mode 100644
index 0000000..085e785
--- /dev/null
+++ b/base/src/util/util.h
@@ -0,0 +1,22 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stdint.h>
+#include "../system/stm32h750xx.h"
+
+extern uint32_t* vector_table_ram[]; // Remapped vector table defined in init.c
+
+///
+/// Set a handler in the vector table
+/// @param index Index of vector to set
+/// @param handler pointer to interrupt handler function
+///
+void set_vector_table_entry(int index, uint32_t* handler){
+	if(index < 0 || index > 166){
+		return;
+	}
+	vector_table_ram[index] = handler;
+}
+
+
+#endif // UTIL_H


Reviews:
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T23:14:05Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T23:16:10Z
user: login: goughjames45
body: Looks like the linter is calling out a couple things. Also, it would be great if we could add some unit tests to the spi class
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:24:24Z
user: login: goughjames45
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:24:57Z
user: login: goughjames45
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:25:05Z
user: login: goughjames45
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:25:13Z
user: login: goughjames45
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:25:19Z
user: login: goughjames45
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:39:24Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T19:20:53Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T19:22:07Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T19:26:53Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T19:28:28Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T19:34:14Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T19:45:28Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:02:50Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:13:46Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:13:48Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:14:27Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:14:36Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:14:40Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:14:43Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:14:47Z
user: login: goughjames45
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T18:04:47Z
user: login: goughjames45
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T18:28:41Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T01:54:12Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T02:18:39Z
user: login: goughjames45
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T00:09:49Z
user: login: Christopher-Finelli
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T00:30:08Z
user: login: carllb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-27T01:02:59Z
user: login: carllb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-27T01:07:29Z
user: login: carllb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-27T01:14:04Z
user: login: carllb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-27T01:15:12Z
user: login: carllb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-27T01:25:44Z
user: login: carllb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-27T01:28:25Z
user: login: carllb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-27T01:33:13Z
user: login: carllb
body: I made some comments throughout the commit
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-27T01:34:37Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T01:41:23Z
user: login: alexpratte16
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T02:19:58Z
user: login: carllb
state: APPROVED
author_association: NONE
submitted_at: 2025-03-29T20:24:00Z

Review comments:
diff_hunk: @@ -0,0 +1,191 @@
+#ifndef SPI_HPP
+#define SPI_HPP
+
+#include "../system/stm32h750xx.h"
+#include "../system/board_defs.h"
+#include "../dma/dma.hpp"
+#include "../gpio/gpio.hpp"
+
+#include <cassert>
+#include <stdint.h>
+
+
+namespace spi{
+
+#define NUM_SPI_CONTROLLERS 6 ///< Number of spi controllers on the stm32h750.
+
+
+class SpiBusBase; ///< forward reference.
+
+extern SpiBusBase* sInstances[NUM_SPI_CONTROLLERS]; ///< instances of the spi buses 
+
+///
+/// Configuration for SPI buses.
+///
+struct SpiBusConfig{
+    bool mPolarity;             ///< Clock polarity
+    bool mPhase;                ///< Clock phase
+    uint32_t mFreq;             ///< Clock frequency
+    uint8_t mWordSize;          ///< Data word size in bits
+    uint8_t mMidi;              ///< Master inter-data idleness. Only valid for HW spi bus
+};
+
+
+
+///
+/// Base class for SPI buses.
+/// @todo CRC?
+///
+class SpiBusBase{
+
+public:
+
+    SpiBusBase(size_t);
+
+    ///
+    /// Destructor.
+    ///
+    virtual ~SpiBusBase(){
+        sInstances[mSpiNum] = NULL;
+    }
+
+    ///
+    /// Configure the bus
+    /// @param conf SPI bus configuration to set.
+    ///
+    void configure(SpiBusConfig conf);
+
+    ///
+    /// Prepare for a transaction.
+    /// @param txBuff Pointer to transmission bufffer.
+    /// @param rxBuff Pointer to reception buffer.
+    /// @param buffLen BuffLen Length of buffer in bytes.
+    /// @param cs Chip select index to assert.
+    /// @param dataSize Size of a single data packet. 1,2,4 bytes.
+    /// @note Buffers must stay valid until transaction is finished.
+    ///
+    virtual void prepare(void* txBuff, void* rxBuff, size_t bufLen, size_t cs, size_t dataSize) = 0;
+
+    ///
+    /// Perform a transaction on the SPI bus
+    /// @note The bus must have been previously prepared using @ref prepare. This method can be called multiple times for the same transaction.
+    ///
+    virtual void transact() = 0;
+
+
+    ///
+    /// Wait for the SPI bus to complete.
+    ///
+    void waitForCompletion(){
+        while(mIsActive){
+            ; // Do nothing
+        }
+    }
+
+protected:
+
+    ///
+    /// SPI interrupt.
+    ///
+    void interrupt(){
+
+        // transfer complete flag
+        if(mSpiHw->SR & SPI_SR_EOT){
+            cleanup();
+
+            NVIC_DisableIRQ(mSpiIrqN);
+
+            mIsActive = false;
+
+            mSpiHw->IFCR |= 0x1FF << 3; // clear interrupt flags
+
+        }
+
+    }
+
+    ///
+    /// This function is called at the end of a transaction
+    ///
+    virtual void cleanup() = 0;
+
+    static void spi1Isr(){if(sInstances[0] != NULL){sInstances[0]->interrupt();}}
+    static void spi2Isr(){if(sInstances[1] != NULL){sInstances[1]->interrupt();}}
+    static void spi3Isr(){if(sInstances[2] != NULL){sInstances[2]->interrupt();}}
+    static void spi4Isr(){if(sInstances[3] != NULL){sInstances[3]->interrupt();}}
+    static void spi5Isr(){if(sInstances[4] != NULL){sInstances[4]->interrupt();}}
+    static void spi6Isr(){if(sInstances[5] != NULL){sInstances[5]->interrupt();}}
+
+    uint8_t mSpiNum;         ///< Number of the SPI bus.
+    volatile bool mIsActive; ///< True if the SPI is currently active.
+    IRQn_Type mSpiIrqN;      ///< Interrupt number for the SPI controller.
+    SPI_TypeDef* mSpiHw;     ///< SPI peripheral pointer.
+    SpiBusConfig mConfig;    ///< SPI bus configuration.
+    uint32_t mKerClkFreq;    ///< Frequency of the SPI_KER_CLK.
+    dma::DmaController::DmaChannel* mTxDma; ///< TX DMA channel
+    dma::DmaController::DmaChannel* mRxDma; ///< RX DMA channel
+
+
+private:
+
+    ///
+    /// Throw an exception if multiple instances are decalared of the same SPI bus.
+    /// @param instance_num Spi bus number
+    /// @todo Add exception.
+    ///
+    static void errorIfTaken(size_t instanceNum){
+        instanceNum -= 1; // 1 index to 0 index conversion
+        if(instanceNum > NUM_SPI_CONTROLLERS || sInstances[instanceNum]){
+            assert(false);
+        }
+    }
+};
+
+
+extern SpiBusBase* sInstances[NUM_SPI_CONTROLLERS]; ///< instances of the spi buses 
+
+///
+/// Hw controlled chip select spi bus
+/// @note Can only control 1 chip select line
+///
+class HwSpiBus : public SpiBusBase{
+
+public:
+    ///
+    /// Constructor.
+    /// @param instance_num SPI bus number.
+    ///
+    HwSpiBus(size_t instance_num):
+        SpiBusBase(instance_num)
+    {
+        HwSpiInit();
+    }
+
+    ///
+    /// Inherit documentation.
+    /// @note Buffer lengths < 4 have shown to have issues?
path: base/src/spi/spi.hpp
user: login: alexpratte16
body: This note can be removed
created_at: 2025-03-14T23:14:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
+            break;
+        case 3:
+            mSpiHw = SPI3;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+            mSpiIrqN = SPI3_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi3Isr);
+            mTxDma->setRequest(61);
+            mRxDma->setRequest(62);
+            break;
+        case 4:
+            mSpiHw = SPI4;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI4EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI4_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI4RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI4RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi4Isr);
+            mTxDma->setRequest(84);
+            mRxDma->setRequest(83);
+            break;
+        case 5:
+            mSpiHw = SPI5;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
+            mKerClkFreq = 0;
+            mSpiIrqN = SPI5_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI5RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI5RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi5Isr);
+            mTxDma->setRequest(86);
+            mRxDma->setRequest(85);
+            break;
+        case 6:
+            mSpiHw = SPI6;
+            RCC->APB4ENR |= RCC_APB4ENR_SPI6EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI6_IRQn;
+            RCC->APB4RSTR |= RCC_APB4RSTR_SPI6RST;
+            RCC->APB4RSTR &= ~(RCC_APB4RSTR_SPI6RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi6Isr);
+            mTxDma->setRequest(12);
+            mRxDma->setRequest(11);
+            break;
+    }
+
+    NVIC_DisableIRQ(mSpiIrqN);
+
+    sInstances[mSpiNum-1] = this;
+
+    mSpiHw->CR1 &= ~SPI_CR1_SPE; // disable SPI
+    mSpiHw->IER |= (SPI_IER_TXPIE | SPI_IER_RXPIE); // Enable tx and rx interrupt.
+    mSpiHw->CR1 |= SPI_CR1_SSI; // Set CS pin high
+    mSpiHw->IFCR |= 0x1FF << SPI_IFCR_EOTC_Pos; // Clear all interrupts
+    mSpiHw->CFG2 |= SPI_CFG2_MASTER | SPI_CFG2_SSOE; // SPI master, SS output enable
+
+    mTxDma->setTransferType(dma::MEM2PER, true);
+    mRxDma->setTransferType(dma::PER2MEM, true);
+
+    mTxDma->setDest((void*)&mSpiHw->TXDR, sizeof(uint32_t), 0);
+    mRxDma->setSource((void*)&mSpiHw->RXDR, sizeof(uint32_t), 0);
+}
+
+
+
+///
+/// Configure the SPI bus.
+/// @param conf SPI bus configuration.
+/// @note This might undershoot target frequency.
+///
+void SpiBusBase::configure(SpiBusConfig conf){
+
+    // Calculate and set divider for target frequency
+    for(uint8_t iDiv = 0; iDiv <= 7; iDiv++){
+        mSpiHw->CFG1 &= ~(SPI_CFG1_MBR_Msk);  // Clear baud rate bits
+        mSpiHw->CFG1 |= iDiv << SPI_CFG1_MBR_Pos; // Set baud rate bits
+        if(mKerClkFreq/(1<<iDiv) <= conf.mFreq/4){
+            break;
+        }
+    }
+
+    // Configure phase
+    if(conf.mPhase){
+        mSpiHw->CFG2 |= (SPI_CFG2_CPHA);
+    }
+    else{
+        mSpiHw->CFG2 &= ~(SPI_CFG2_CPHA);
+    }
+
+    // Configure polarity
+    if(conf.mPolarity){
+        mSpiHw->CFG2 |= (SPI_CFG2_CPOL);
+    }
+    else{
+        mSpiHw->CFG2 &= ~(SPI_CFG2_CPOL);
+    }
+
+    // Configure word size
+    mSpiHw->CFG1 &= ~(SPI_CFG1_DSIZE_Msk); // Clear word size bits
+    mSpiHw->CFG1 |= (conf.mWordSize-1 & 0x1F) << SPI_CFG1_DSIZE_Pos; // Set word size bits
+
+    // Configure master inter-data-idleness
+    mSpiHw->CFG2 &= ~(SPI_CFG2_MIDI_Msk); // Clear MIDI
+    mSpiHw->CFG2 |= ((conf.mMidi & 0xF) << SPI_CFG2_MIDI_Pos); // Set MIDI
+
+}
+
+
+///
+/// Initialize HW control of the CS pin.
+///
+void HwSpiBus::HwSpiInit(){
+    mSpiHw->CFG2 &= ~(SPI_CFG2_SSM); // Clear software management of CS pin.
+    mSpiHw->CFG2 |= (SPI_CFG2_SSOM); //  Set SSOM, allows for inter-data idleness
+    mSpiHw->CFG2 |= SPI_CFG2_IOSWP; //  Swap CIPO and COPI
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: Perhaps change IOSWP to be part of SPI config struct.
created_at: 2025-03-14T23:16:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
path: base/src/spi/spi.cpp
user: login: goughjames45
body: What does the lowercase m represent?
created_at: 2025-03-15T17:52:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
path: base/src/spi/spi.cpp
user: login: goughjames45
body: What does lowercase s represent?
created_at: 2025-03-15T17:54:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
path: base/src/spi/spi.cpp
user: login: goughjames45
body: What is 1+(size_t)(instance_num>5)? What is the difference between mTxDma and mRxDma?
created_at: 2025-03-15T17:57:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
path: base/src/spi/spi.cpp
user: login: goughjames45
body: 37 and 38 look like magic numbers. Can these be given a name?
created_at: 2025-03-15T17:58:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
path: base/src/spi/spi.cpp
user: login: goughjames45
body: Same comment as above
created_at: 2025-03-15T17:59:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
path: base/src/spi/spi.cpp
user: login: goughjames45
body: Why do the different instances need to be handled differently?
created_at: 2025-03-15T18:00:09Z
author_association: CONTRIBUTOR
path: base/src/spi/spi.hpp
user: login: goughjames45
body: It feels like this class is doing a lot. What if we split it up into an SPI class and an SPI instance manager class. Would that achieve better separation of concerns?
created_at: 2025-03-15T18:15:01Z
author_association: CONTRIBUTOR
path: base/src/system/pin_defs.c
user: login: goughjames45
body: How come some of the pins / confs are marked as const, but others are not? Is there ever a points where they shouldn't be const?
created_at: 2025-03-15T18:18:38Z
author_association: CONTRIBUTOR
path: base/src/tests/test_spi.cpp
user: login: goughjames45
body: I like the tests here. Can we add some sad path ones as well?
created_at: 2025-03-15T18:22:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
+            break;
+        case 3:
+            mSpiHw = SPI3;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+            mSpiIrqN = SPI3_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi3Isr);
+            mTxDma->setRequest(61);
+            mRxDma->setRequest(62);
path: base/src/spi/spi.cpp
user: login: goughjames45
body: same as above
created_at: 2025-03-15T18:24:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
+            break;
+        case 3:
+            mSpiHw = SPI3;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+            mSpiIrqN = SPI3_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi3Isr);
+            mTxDma->setRequest(61);
+            mRxDma->setRequest(62);
+            break;
+        case 4:
+            mSpiHw = SPI4;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI4EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI4_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI4RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI4RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi4Isr);
+            mTxDma->setRequest(84);
+            mRxDma->setRequest(83);
path: base/src/spi/spi.cpp
user: login: goughjames45
body: same
created_at: 2025-03-15T18:25:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
+            break;
+        case 3:
+            mSpiHw = SPI3;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+            mSpiIrqN = SPI3_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi3Isr);
+            mTxDma->setRequest(61);
+            mRxDma->setRequest(62);
+            break;
+        case 4:
+            mSpiHw = SPI4;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI4EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI4_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI4RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI4RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi4Isr);
+            mTxDma->setRequest(84);
+            mRxDma->setRequest(83);
+            break;
+        case 5:
+            mSpiHw = SPI5;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
+            mKerClkFreq = 0;
+            mSpiIrqN = SPI5_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI5RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI5RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi5Isr);
+            mTxDma->setRequest(86);
+            mRxDma->setRequest(85);
path: base/src/spi/spi.cpp
user: login: goughjames45
body: same
created_at: 2025-03-15T18:25:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
+            break;
+        case 3:
+            mSpiHw = SPI3;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+            mSpiIrqN = SPI3_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi3Isr);
+            mTxDma->setRequest(61);
+            mRxDma->setRequest(62);
+            break;
+        case 4:
+            mSpiHw = SPI4;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI4EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI4_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI4RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI4RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi4Isr);
+            mTxDma->setRequest(84);
+            mRxDma->setRequest(83);
+            break;
+        case 5:
+            mSpiHw = SPI5;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
+            mKerClkFreq = 0;
+            mSpiIrqN = SPI5_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI5RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI5RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi5Isr);
+            mTxDma->setRequest(86);
+            mRxDma->setRequest(85);
+            break;
+        case 6:
+            mSpiHw = SPI6;
+            RCC->APB4ENR |= RCC_APB4ENR_SPI6EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI6_IRQn;
+            RCC->APB4RSTR |= RCC_APB4RSTR_SPI6RST;
+            RCC->APB4RSTR &= ~(RCC_APB4RSTR_SPI6RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi6Isr);
+            mTxDma->setRequest(12);
+            mRxDma->setRequest(11);
path: base/src/spi/spi.cpp
user: login: goughjames45
body: same
created_at: 2025-03-15T18:25:19Z
author_association: CONTRIBUTOR
path: base/src/tests/test_spi.cpp
user: login: goughjames45
body: Would it also be possible to test the other SPIs?
created_at: 2025-03-15T18:39:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: s prefix refers to a static variable of the class.
created_at: 2025-03-15T19:20:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: m Prefix refers to a member variable of a class.
created_at: 2025-03-15T19:22:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
path: base/src/spi/spi.cpp
user: login: alexpratte16
body:   // SPI 6 is connected to DMA2
  
  if instance_num <= 5 then use DMA controller 1
  if instance_num > 5 use DMA controller 2 
  
  
  RX stands for receive This is the direct memory access channel that will move the received bytes from the SPI controllers receive register into the buffer
  
    
  TX stands for transmit This is the direct memory access channel that will move bytes to transmit from the buffer into the SPI controllers transmit register
created_at: 2025-03-15T19:26:52Z
author_association: CONTRIBUTOR
path: base/src/tests/test_spi.cpp
user: login: alexpratte16
body: What is a sad path?
created_at: 2025-03-15T19:28:21Z
author_association: CONTRIBUTOR
path: base/src/tests/test_spi.cpp
user: login: alexpratte16
body: Unfortunately it is not. Our hardware is limited to utilizing SPI 2 and 3

SPI 3 will be tested when the SwSpiBus class is implemented.
created_at: 2025-03-15T19:34:14Z
author_association: CONTRIBUTOR
path: base/src/spi/spi.hpp
user: login: alexpratte16
body: Out of an abundance of laziness I will defer that to another ticket

https://github.com/gooey-synths/audio_dev_kit_bsp/issues/28 
created_at: 2025-03-15T19:45:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: You will need to ask ST (the designer of our microcontroller) about that

Each instance has a unique combination of bits inside registers the need to be written to in order to enable them
I was unable to deduce a general formula for the statements inside the switch case.
created_at: 2025-03-15T20:02:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: Modified to refer to constexpr in the dma namespace.
created_at: 2025-03-15T20:13:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: Modified to refer to constexpr in the dma namespace.
created_at: 2025-03-15T20:13:48Z
author_association: CONTRIBUTOR
path: base/src/system/pin_defs.c
user: login: alexpratte16
body: modified to all be const.
created_at: 2025-03-15T20:14:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
+            break;
+        case 3:
+            mSpiHw = SPI3;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+            mSpiIrqN = SPI3_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi3Isr);
+            mTxDma->setRequest(61);
+            mRxDma->setRequest(62);
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: Modified to refer to constexpr in the dma namespace.
created_at: 2025-03-15T20:14:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
+            break;
+        case 3:
+            mSpiHw = SPI3;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+            mSpiIrqN = SPI3_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi3Isr);
+            mTxDma->setRequest(61);
+            mRxDma->setRequest(62);
+            break;
+        case 4:
+            mSpiHw = SPI4;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI4EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI4_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI4RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI4RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi4Isr);
+            mTxDma->setRequest(84);
+            mRxDma->setRequest(83);
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: Modified to refer to constexpr in the dma namespace.
created_at: 2025-03-15T20:14:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
+            break;
+        case 3:
+            mSpiHw = SPI3;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+            mSpiIrqN = SPI3_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi3Isr);
+            mTxDma->setRequest(61);
+            mRxDma->setRequest(62);
+            break;
+        case 4:
+            mSpiHw = SPI4;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI4EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI4_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI4RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI4RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi4Isr);
+            mTxDma->setRequest(84);
+            mRxDma->setRequest(83);
+            break;
+        case 5:
+            mSpiHw = SPI5;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
+            mKerClkFreq = 0;
+            mSpiIrqN = SPI5_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI5RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI5RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi5Isr);
+            mTxDma->setRequest(86);
+            mRxDma->setRequest(85);
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: Modified to refer to constexpr in the dma namespace.
created_at: 2025-03-15T20:14:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+#include "spi.hpp"
+
+#include "../util/util.h"
+
+namespace spi{
+
+SpiBusBase* sInstances[NUM_SPI_CONTROLLERS];
+
+///
+/// Constructor.
+/// @param instance_num SPI bus number.
+/// @todo reset for each bus.
+///
+SpiBusBase::SpiBusBase(size_t instance_num):
+    mIsActive(false),
+    mSpiNum(instance_num),
+    mKerClkFreq(0),
+    // SPI 6 is connected to DMA2
+    mTxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel()),
+    mRxDma(dma::DmaController::getInstance(1+(size_t)(instance_num>5))->claimAvailableChannel())
+{
+    errorIfTaken(instance_num);
+
+    // enable and set SPI hardware.
+
+    switch(instance_num){
+        case 1:
+            mSpiHw = SPI1;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI1_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi1Isr);
+            mTxDma->setRequest(38);
+            mRxDma->setRequest(37);
+            break;
+        case 2:
+            mSpiHw = SPI2;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI2EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET;
+            mSpiIrqN = SPI2_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI2RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI2RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi2Isr);
+            mTxDma->setRequest(40);
+            mRxDma->setRequest(39);
+            break;
+        case 3:
+            mSpiHw = SPI3;
+            RCC->APB1LENR |= RCC_APB1LENR_SPI3EN;
+            mKerClkFreq = PLL1_DIVQ_TARGET; // todo: Update if needed.
+            mSpiIrqN = SPI3_IRQn;
+            RCC->APB1LRSTR |= RCC_APB1LRSTR_SPI3RST;
+            RCC->APB1LRSTR &= ~(RCC_APB1LRSTR_SPI3RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi3Isr);
+            mTxDma->setRequest(61);
+            mRxDma->setRequest(62);
+            break;
+        case 4:
+            mSpiHw = SPI4;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI4EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI4_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI4RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI4RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi4Isr);
+            mTxDma->setRequest(84);
+            mRxDma->setRequest(83);
+            break;
+        case 5:
+            mSpiHw = SPI5;
+            RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
+            mKerClkFreq = 0;
+            mSpiIrqN = SPI5_IRQn;
+            RCC->APB2RSTR |= RCC_APB2RSTR_SPI5RST;
+            RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI5RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi5Isr);
+            mTxDma->setRequest(86);
+            mRxDma->setRequest(85);
+            break;
+        case 6:
+            mSpiHw = SPI6;
+            RCC->APB4ENR |= RCC_APB4ENR_SPI6EN;
+            mKerClkFreq = 0; // todo: Update if needed.
+            mSpiIrqN = SPI6_IRQn;
+            RCC->APB4RSTR |= RCC_APB4RSTR_SPI6RST;
+            RCC->APB4RSTR &= ~(RCC_APB4RSTR_SPI6RST);
+            set_vector_table_entry((int)mSpiIrqN+16, (uint32_t*)spi6Isr);
+            mTxDma->setRequest(12);
+            mRxDma->setRequest(11);
path: base/src/spi/spi.cpp
user: login: alexpratte16
body: Modified to refer to constexpr in the dma namespace.
created_at: 2025-03-15T20:14:47Z
author_association: CONTRIBUTOR
path: base/src/tests/test_spi.cpp
user: login: goughjames45
body: I was looking at the errorIfTaken function and wondering if we could write a test to trigger an exception to validate that its being handled correctly.
created_at: 2025-03-16T18:04:47Z
author_association: CONTRIBUTOR
path: base/src/spi/spi.hpp
user: login: goughjames45
body: As I'm reading through this more, I think that the SpiBusBase should be an abstract class with all pure virtual functions and the HwSpiBus should contain concreate implementations for everything. 
created_at: 2025-03-16T18:28:41Z
author_association: CONTRIBUTOR
path: base/src/tests/test_spi.cpp
user: login: alexpratte16
body: At this moment our standard toolchain does not allow for the use of exceptions. I would like to explore a custom toolchain that would allow for exceptions.

It should be noted that the use of exceptions is quite an intensive process so care needs to be taken in where they are used to maximize code efficiency. 
created_at: 2025-03-17T01:54:12Z
author_association: CONTRIBUTOR
path: base/src/spi/spi.hpp
user: login: alexpratte16
body: The implementation that exist within SpiBusBase are meant to support the  2 classes that are intended to derive from it:

- HwSpiBus
- SwSpiBus

These names may not do a great job at explaining what they are doing:

The HwSpiBus allows for the SPI hardware of the STM32H750 to control the chip select line of the SPI bus
This allows for 2 benefits:
-  Lower CPU load as the CPU does not need to toggle the CS pin during the transaction
-  Allowing for Master interdata idleness which allows the CS pin to be toggle in the middle of a transaction which can allow for speedups for our DAC60508 chip

However this comes a cost of flexibility where the can only be ONE chip on this bus due to only having 1 chip select pin

The SwSpiBus allows for the firmware to control the chip select line of the SPI bus which allows for using more than one chip on the bus at the cost of CPU cycles to toggle the appropriate chip select lines before and after a transaction.

We can always create a pure abstract base class but the HwSpiBus and SwSpiBus have identical implentations outside of their CS pin handling so to encourage code reuse I think they should inherit from the same class at some point in the inheritance chain

Please let me know your thoughts and if this explanation made sense.

![image](https://github.com/user-attachments/assets/c9573c18-883c-4289-b242-b949c6842fdb)
Note: CS and SS are the same thing.
created_at: 2025-03-17T02:18:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,19 +5,19 @@
 // prototypes
 void reset_handler();
 void halt();
-extern int main(void); // defined in main.c
+extern int main(); // defined in main.c
 
 #define ISR_NOT_IMPL ((uint32_t*) halt)
 
  /* These are defined in the linker script */
-extern uint32_t _stext;  //< Start of text section
-extern uint32_t _etext;  //< End of text section
-extern uint32_t _sbss;   //< Start of BSS section
-extern uint32_t _ebss;   //< End of BSS section
-extern uint32_t _sdata;  //< Start of data section
-extern uint32_t _edata;  //< End of data section
-extern uint32_t _sstack; //< Start of stack section
-extern uint32_t _estack; //< End of stack section
+extern const uint32_t _stext;  //< Start of text section
path: base/src/init/startup.c
user: login: carllb
body: _stext and the others are AT the memory location so they shouldn't be const
created_at: 2025-03-27T01:02:59Z
author_association: NONE
diff_hunk: @@ -404,6 +408,14 @@ __attribute__ ((noreturn)) void reset_handler(){
         *bss_ptr++ = 0;
     }
 
+    // Copy vector table to RAM location
+    for(unsigned int i = 0; i < sizeof(vector_table)/sizeof(*vector_table); i++){
+        vector_table_ram[i] = vector_table[i];
+    }
+
+    // Remap vector table to RAM location
+    SCB->VTOR = (uint32_t)vector_table_ram;
path: base/src/init/startup.c
user: login: carllb
body: probably add //NOLINT because clang things pointers are 64 bit
created_at: 2025-03-27T01:07:29Z
author_association: NONE
path: base/src/system/core_cm7.h
user: login: carllb
body: IRQn_Type is defined in stm32h750xx.h, that file should be included in this file so that the function args types are satisfied
created_at: 2025-03-27T01:14:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,22 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stdint.h>
+#include "../system/stm32h750xx.h"
+
+extern uint32_t* vector_table_ram[]; // Remapped veector table defined in init.c
path: base/src/util/util.h
user: login: carllb
body: typo 'veector' is misspelled 
created_at: 2025-03-27T01:15:12Z
author_association: NONE
path: base/src/system/core_cm7.h
user: login: carllb
body: Another option might be to try to forward reference the enumeration: https://stackoverflow.com/questions/71416/forward-declaring-an-enum-in-c
created_at: 2025-03-27T01:25:44Z
author_association: NONE
diff_hunk: @@ -0,0 +1,36 @@
+#ifndef SPIBUSINTERFACE_HPP
+#define SPIBUSINTERFACE_HPP
+
+#include <cstdint>
+
+namespace spi{
+
+// Struct for SPI bus configuration
+struct SpiBusConfig {
+    bool mPolarity;     ///< Clock polarity
+    bool mPhase;        ///< Clock phase
+    bool mIoSwap;       ///< True if IO lines should be swapped
+    uint32_t mFreq;     ///< Clock frequency
+    uint8_t mWordSize;  ///< Data word size in bits
+    uint8_t mMidi;      ///< Master inter-data idleness (HW SPI only)
+};
+
+// SPI bus interface
+class ISpiBus {
+public:
+    virtual ~ISpiBus() = default;
+    virtual void configure(SpiBusConfig conf) = 0;
+    virtual SpiBusConfig getConfiguration() = 0;
+    virtual void prepare(void* txBuff, void* rxBuff, size_t bufLen, size_t cs, size_t dataSize) = 0;
path: base/src/spi/spibus_interface.hpp
user: login: carllb
body: can we try replacing with std::size_t to make the linter happy?
created_at: 2025-03-27T01:28:25Z
author_association: NONE
path: base/src/dma/dma.hpp
user: login: carllb
body: members should be private
created_at: 2025-03-27T01:33:13Z
author_association: NONE
path: base/src/system/core_cm7.h
user: login: alexpratte16
body: I think forward reference is the way to go because I don't think CM7 should depend on STM32H750
created_at: 2025-03-27T01:41:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+#ifndef SPIBUSINTERFACE_HPP
+#define SPIBUSINTERFACE_HPP
+
+#include <cstdint>
+
+namespace spi{
+
+// Struct for SPI bus configuration
+struct SpiBusConfig {
+    bool mPolarity;     ///< Clock polarity
+    bool mPhase;        ///< Clock phase
+    bool mIoSwap;       ///< True if IO lines should be swapped
+    uint32_t mFreq;     ///< Clock frequency
+    uint8_t mWordSize;  ///< Data word size in bits
+    uint8_t mMidi;      ///< Master inter-data idleness (HW SPI only)
+};
+
+// SPI bus interface
+class ISpiBus {
+public:
+    virtual ~ISpiBus() = default;
+    virtual void configure(SpiBusConfig conf) = 0;
+    virtual SpiBusConfig getConfiguration() = 0;
+    virtual void prepare(void* txBuff, void* rxBuff, size_t bufLen, size_t cs, size_t dataSize) = 0;
path: base/src/spi/spibus_interface.hpp
user: login: alexpratte16
body: I like using just size_t, I think we should adjust the linter.
created_at: 2025-03-27T02:19:58Z
author_association: CONTRIBUTOR

PR comments:
user: login: alexpratte16
created_at: 2025-03-15T20:16:21Z
body: > Looks like the linter is calling out a couple things. Also, it would be great if we could add some unit tests to the spi class

Do you have an idea of what these unit tests would look like?
author_association: CONTRIBUTOR
user: login: goughjames45
created_at: 2025-03-16T18:35:05Z
body: > > Looks like the linter is calling out a couple things. Also, it would be great if we could add some unit tests to the spi class
> 
> Do you have an idea of what these unit tests would look like?

Generally they can follow the format 1. Arrange, 2. Act, 3. Assert. So, for example maybe something like 1. Arrange a config. 2. Call configure with the config. 3. Assert that the member variables have the correct values. 
author_association: CONTRIBUTOR
user: login: alexpratte16
created_at: 2025-03-17T02:04:59Z
body: > > > Looks like the linter is calling out a couple things. Also, it would be great if we could add some unit tests to the spi class
> > 
> > 
> > Do you have an idea of what these unit tests would look like?
> 
> Generally they can follow the format 1. Arrange, 2. Act, 3. Assert. So, for example maybe something like 1. Arrange a config. 2. Call configure with the config. 3. Assert that the member variables have the correct values.

I do agree with the want for unit tests and one thing that can be done is adding a getConfig and setConfig function to test that the config is being load properly

However there are a lot of things that cannot be tested in a vacuum due to the interaction with hardware. We only have 2 physical SPI buses on the board and for example to test if the baud rate of the SPI bus is correct it is very difficult without the use of external equipment such as an oscilloscope.

I suppose one way to test baud rate is to setup a timer at a given frequency and measure how many ticks it takes to perform a given transaction to ensure that we are hitting that given baud rate. how does that sound?

Sad path testing will most likely need to wait until we have a toolchain that supports exceptions as stated above.
author_association: CONTRIBUTOR