diff --git a/lib/mcal/cli/spi.hpp b/lib/mcal/cli/spi.hpp new file mode 100644 index 000000000..34155a428 --- /dev/null +++ b/lib/mcal/cli/spi.hpp @@ -0,0 +1,133 @@ +/// @author Priyal Taneja +/// @date 2025-10-19 + +#pragma once +#include +#include +#include +#include +#include + +#include "periph/spi.hpp" + +namespace mcal::cli { + +class SpiMaster : public macfe::periph::SpiMaster { +private: + std::string name_; + +public: + SpiMaster(std::string name) : name_(name) { + std::cout << std::format("Created SPI Master {}", name_) << std::endl; + } + + void Transmit(uint8_t* tx_data, size_t length) override { + std::cout << std::format("SPI {} Transmitting {} bytes: ", name_, + length); + for (size_t i = 0; i < length && i < 16; ++i) { + std::cout << std::format("{:02X} ", tx_data[i]); + } + if (length > 16) { + std::cout << "..."; + } + std::cout << std::endl; + } + + void Receive(uint8_t* rx_data, size_t length) override { + std::cout << std::format("SPI {} Receiving {} bytes", name_, length) + << std::endl; + std::cout << " | Enter hex values (e.g., FF 00 A5) or press Enter for " + "default 0xFF: "; + + // read line of input + std::string line; + std::getline(std::cin >> std::ws, line); + + if (line.empty()) { + // default: fill with 0xFF + memset(rx_data, 0xFF, length); + } else { + // parse hex values from input + size_t idx = 0; + size_t pos = 0; + while (pos < line.length() && idx < length) { + if (std::isxdigit(line[pos])) { + unsigned int value; + std::sscanf(line.c_str() + pos, "%2x", &value); + rx_data[idx++] = static_cast(value); + pos += 2; + } else { + pos++; + } + } + // fill remaining data with 0xFF + while (idx < length) { + rx_data[idx++] = 0xFF; + } + } + + std::cout << " | Received: "; + for (size_t i = 0; i < length && i < 16; ++i) { + std::cout << std::format("{:02X} ", rx_data[i]); + } + if (length > 16) { + std::cout << "..."; + } + std::cout << std::endl; + } + + void TransmitReceive(uint8_t* tx_data, uint8_t* rx_data, + size_t length) override { + std::cout << std::format("SPI {} TransmitReceive {} bytes", name_, + length) + << std::endl; + std::cout << " | TX: "; + for (size_t i = 0; i < length && i < 16; ++i) { + std::cout << std::format("{:02X} ", tx_data[i]); + } + if (length > 16) { + std::cout << "..."; + } + std::cout << std::endl; + + std::cout << " | Enter RX hex values or press Enter for default 0xFF: "; + + // read line of input + std::string line; + std::getline(std::cin >> std::ws, line); + + if (line.empty()) { + // default: fill with 0xFF + memset(rx_data, 0xFF, length); + } else { + // parse hex values from input + size_t idx = 0; + size_t pos = 0; + while (pos < line.length() && idx < length) { + if (std::isxdigit(line[pos])) { + unsigned int value; + std::sscanf(line.c_str() + pos, "%2x", &value); + rx_data[idx++] = static_cast(value); + pos += 2; + } else { + pos++; + } + } + // fill remaining data with 0xFF + while (idx < length) { + rx_data[idx++] = 0xFF; + } + } + + std::cout << " | RX: "; + for (size_t i = 0; i < length && i < 16; ++i) { + std::cout << std::format("{:02X} ", rx_data[i]); + } + if (length > 16) { + std::cout << "..."; + } + std::cout << std::endl; + } +}; + +} // namespace mcal::cli \ No newline at end of file diff --git a/lib/mcal/linux/spi.hpp b/lib/mcal/linux/spi.hpp new file mode 100644 index 000000000..8ff61f506 --- /dev/null +++ b/lib/mcal/linux/spi.hpp @@ -0,0 +1,133 @@ +/// @author Priyal Taneja +/// @date 2025-11-06 + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include "periph/spi.hpp" + +namespace mcal::lnx { + +class SpiMaster : public macfe::periph::SpiMaster { +private: + int fd_; + std::string device_path_; + uint32_t speed_hz_; + uint8_t mode_; + uint8_t bits_per_word_; + bool initialized_; + +public: + /// constructor + /// @param device_path path to spi device (e.g., "/dev/spidev0.0") + /// @param speed_hz spi clock speed in Hz (default: 1 MHz) + /// @param mode spi mode 0-3 (default: SPI_MODE_0) + /// @param bits_per_word bits per word (default: 8) + SpiMaster(std::string device_path, uint32_t speed_hz = 1000000, + uint8_t mode = SPI_MODE_0, uint8_t bits_per_word = 8) + : fd_(-1), + device_path_(device_path), + speed_hz_(speed_hz), + mode_(mode), + bits_per_word_(bits_per_word), + initialized_(false) {} + + ~SpiMaster() { + if (fd_ >= 0) { + close(fd_); + } + } + + /// initialize the spi device + /// opens the device file and configures spi parameters + void Setup() { + if (initialized_) { + return; + } + + // open spi device + fd_ = open(device_path_.c_str(), O_RDWR); + if (fd_ < 0) { + return; + } + + // set spi mode + if (ioctl(fd_, SPI_IOC_WR_MODE, &mode_) < 0) { + close(fd_); + fd_ = -1; + return; + } + + // set bits per word + if (ioctl(fd_, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word_) < 0) { + close(fd_); + fd_ = -1; + return; + } + + // set max speed + if (ioctl(fd_, SPI_IOC_WR_MAX_SPEED_HZ, &speed_hz_) < 0) { + close(fd_); + fd_ = -1; + return; + } + + initialized_ = true; + } + + void Transmit(uint8_t* tx_data, size_t length) override { + if (!initialized_ || fd_ < 0) { + return; + } + + struct spi_ioc_transfer transfer = {0}; + transfer.tx_buf = reinterpret_cast<__u64>(tx_data); + transfer.rx_buf = 0; + transfer.len = length; + transfer.speed_hz = speed_hz_; + transfer.bits_per_word = bits_per_word_; + + ioctl(fd_, SPI_IOC_MESSAGE(1), &transfer); + } + + void Receive(uint8_t* rx_data, size_t length) override { + if (!initialized_ || fd_ < 0) { + return; + } + + struct spi_ioc_transfer transfer = {0}; + transfer.tx_buf = 0; + transfer.rx_buf = reinterpret_cast<__u64>(rx_data); + transfer.len = length; + transfer.speed_hz = speed_hz_; + transfer.bits_per_word = bits_per_word_; + + ioctl(fd_, SPI_IOC_MESSAGE(1), &transfer); + } + + void TransmitReceive(uint8_t* tx_data, uint8_t* rx_data, + size_t length) override { + if (!initialized_ || fd_ < 0) { + return; + } + + struct spi_ioc_transfer transfer = {0}; + transfer.tx_buf = reinterpret_cast<__u64>(tx_data); + transfer.rx_buf = reinterpret_cast<__u64>(rx_data); + transfer.len = length; + transfer.speed_hz = speed_hz_; + transfer.bits_per_word = bits_per_word_; + + ioctl(fd_, SPI_IOC_MESSAGE(1), &transfer); + } +}; + +} // namespace mcal::lnx \ No newline at end of file diff --git a/lib/mcal/stm32f/spi.hpp b/lib/mcal/stm32f/spi.hpp new file mode 100644 index 000000000..710c3bf73 --- /dev/null +++ b/lib/mcal/stm32f/spi.hpp @@ -0,0 +1,117 @@ +/// @author Priyal Taneja +/// @date 2025-10-19 + +#pragma once + +#include + +#include "periph/spi.hpp" + +#ifdef STM32F7 +#include "stm32f7xx_hal.h" +#elif defined(STM32F4) +#include "stm32f4xx_hal.h" +#endif + +#ifdef HAL_SPI_MODULE_ENABLED + +namespace mcal::stm32f { + +class SpiMaster : public macfe::periph::SpiMaster { +private: + SPI_HandleTypeDef* hspi_; + GPIO_TypeDef* cs_port_; + uint16_t cs_pin_; + uint32_t timeout_ms_; + bool initialized_; + + void ChipSelectLow() { + HAL_GPIO_WritePin(cs_port_, cs_pin_, GPIO_PIN_RESET); + } + + void ChipSelectHigh() { + HAL_GPIO_WritePin(cs_port_, cs_pin_, GPIO_PIN_SET); + } + +public: + /// constructor + /// @param hspi pointer to STM32 HAL SPI handle + /// @param cs_port gpio port for chip select pin (e.g., GPIOA) + /// @param cs_pin gpio pin number for chip select (e.g., GPIO_PIN_4) + /// @param timeout_ms timeout for SPI operations in milliseconds (default: + /// 100ms) + SpiMaster(SPI_HandleTypeDef* hspi, GPIO_TypeDef* cs_port, uint16_t cs_pin, + uint32_t timeout_ms = 100) + : hspi_(hspi), + cs_port_(cs_port), + cs_pin_(cs_pin), + timeout_ms_(timeout_ms), + initialized_(false) {} + + /// initialize the SPI peripheral + /// must be called before using any transmission methods + /// typically called from bindings::Initialize() + void Init() { + if (initialized_) { + return; + } + + // initialize chip select pin as output, set high (inactive) + GPIO_InitTypeDef GPIO_InitStruct = {0}; + GPIO_InitStruct.Pin = cs_pin_; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(cs_port_, &GPIO_InitStruct); + + // set CS high (inactive) by default + ChipSelectHigh(); + + initialized_ = true; + } + + void Transmit(uint8_t* tx_data, size_t length) override { + if (!initialized_ || hspi_ == nullptr) { + return; + } + + ChipSelectLow(); + HAL_SPI_Transmit(hspi_, tx_data, length, timeout_ms_); + ChipSelectHigh(); + } + + void Receive(uint8_t* rx_data, size_t length) override { + if (!initialized_ || hspi_ == nullptr) { + return; + } + + ChipSelectLow(); + HAL_SPI_Receive(hspi_, rx_data, length, timeout_ms_); + ChipSelectHigh(); + } + + void TransmitReceive(uint8_t* tx_data, uint8_t* rx_data, + size_t length) override { + if (!initialized_ || hspi_ == nullptr) { + return; + } + + ChipSelectLow(); + HAL_SPI_TransmitReceive(hspi_, tx_data, rx_data, length, timeout_ms_); + ChipSelectHigh(); + } + + /// check if the spi peripheral has been initialized + bool IsInitialized() const { + return initialized_; + } + + /// get the underlying hal spi handle + SPI_HandleTypeDef* GetHandle() const { + return hspi_; + } +}; + +} // namespace mcal::stm32f + +#endif // HAL_SPI_MODULE_ENABLED \ No newline at end of file