-
Notifications
You must be signed in to change notification settings - Fork 2
feat(mcal): implement SpiMaster peripheral for stm32f, lnx, and cli #539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| /// @author Priyal Taneja | ||
| /// @date 2025-10-19 | ||
|
|
||
| #pragma once | ||
| #include <cstdint> | ||
| #include <cstring> | ||
| #include <format> | ||
| #include <iostream> | ||
| #include <string> | ||
|
|
||
| #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<uint8_t>(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<uint8_t>(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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| /// @author Priyal Taneja | ||
| /// @date 2025-11-06 | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <fcntl.h> | ||
| #include <linux/spi/spidev.h> | ||
| #include <sys/ioctl.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include <cstdint> | ||
| #include <cstring> | ||
| #include <string> | ||
|
|
||
| #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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| /// @author Priyal Taneja | ||
| /// @date 2025-10-19 | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <cstdint> | ||
|
|
||
| #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() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the Init() function! it's a good idea since we want to guarantee that the CS pin is high on startup I've seen the We don't need to protect the developer from forgetting to call |
||
| if (initialized_) { | ||
| return; | ||
| } | ||
|
|
||
| // initialize chip select pin as output, set high (inactive) | ||
| GPIO_InitTypeDef GPIO_InitStruct = {0}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. glad you learned stm HAL pin initialization, but you don't need to configure the chip select pin manually. we will configure in in CubeMX so you can assume the Port/Pin is is initialized already |
||
| 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 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't need this, see comment above |
||
| return initialized_; | ||
| } | ||
|
|
||
| /// get the underlying hal spi handle | ||
| SPI_HandleTypeDef* GetHandle() const { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need this. When would it be called? |
||
| return hspi_; | ||
| } | ||
| }; | ||
|
|
||
| } // namespace mcal::stm32f | ||
|
|
||
| #endif // HAL_SPI_MODULE_ENABLED | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see this comment https://github.com/macformula/racecar/pull/539/files#r2528588442, don't need the initialized field & checks