From 32d9834542ca0dae4f31539ec80716ba77f939cc Mon Sep 17 00:00:00 2001 From: Tom Lonergan Date: Sun, 28 May 2023 23:28:20 +0100 Subject: [PATCH 1/4] repl support for gpio --- config/template.json | 5 ++ lib/debug/repl.cpp | 107 ++++++++++++++++++++++++++++++++++++++++++- lib/debug/repl.hpp | 5 +- 3 files changed, 115 insertions(+), 2 deletions(-) diff --git a/config/template.json b/config/template.json index deace561..58d701ac 100644 --- a/config/template.json +++ b/config/template.json @@ -11,6 +11,11 @@ "can1" ] }, + "gpio": { + "enabled": false, + "read_pins": [], + "write_pins": [] + }, "i2c": { "enabled": false, "buses": [] diff --git a/lib/debug/repl.cpp b/lib/debug/repl.cpp index bfe780cc..c615cd89 100644 --- a/lib/debug/repl.cpp +++ b/lib/debug/repl.cpp @@ -11,7 +11,14 @@ namespace hyped::debug { -Repl::Repl(core::ILogger &logger) : logger_(logger), i2c_(), spi_(), pwm_(), adc_(), uart_() +Repl::Repl(core::ILogger &logger) + : logger_(logger), + i2c_(), + spi_(), + pwm_(), + adc_(), + uart_(), + gpio_(io::HardwareGpio(logger)) { } @@ -117,6 +124,36 @@ std::optional> Repl::fromFile(const std::string &path) return std::nullopt; } } + if (!io.HasMember("gpio")) { + logger_.log(core::LogLevel::kFatal, "Missing required field 'io.gpio' in configuration file"); + return std::nullopt; + } + const auto gpio = io["gpio"].GetObject(); + if (!gpio.HasMember("enabled")) { + logger_.log(core::LogLevel::kFatal, + "Missing required field 'io.gpio.enabled' in configuration file"); + return std::nullopt; + } + if (gpio["enabled"].GetBool()) { + if (!gpio.HasMember("read_pins")) { + logger_.log(core::LogLevel::kFatal, + "Missing required field 'io.gpio.read_pins' in configuration file"); + return std::nullopt; + } + const auto read_pins = gpio["read_pins"].GetArray(); + for (auto &pin : read_pins) { + repl->addGpioReadCommands(pin.GetUint()); + } + if (!gpio.HasMember("write_pins")) { + logger_.log(core::LogLevel::kFatal, + "Missing required field 'io.gpio.write_pins' in configuration file"); + return std::nullopt; + } + const auto write_pins = gpio["write_pins"].GetArray(); + for (auto &pin : write_pins) { + repl->addGpioWriteCommands(pin.GetUint()); + } + } const auto buses = i2c["buses"].GetArray(); for (auto &bus : buses) { repl->addI2cCommands(bus.GetUint()); @@ -393,6 +430,74 @@ void Repl::addCanCommands(const std::string &bus) addCommand(can_write_command); } +void Repl::addGpioReadCommands(const std::uint8_t pin) +{ + const auto optional_gpio_reader = gpio_.getReader(pin); + if (!optional_gpio_reader) { + logger_.log(core::LogLevel::kFatal, "Failed to create GPIO reader on pin %d", pin); + return; + } + const auto gpio_reader = std::move(*optional_gpio_reader); + Command gpio_read_command; + std::stringstream identifier; + identifier << "gpio " << static_cast(pin) << " read"; + gpio_read_command.name = identifier.str(); + std::stringstream description; + description << "Read from GPIO pin " << static_cast(pin); + gpio_read_command.description = description.str(); + gpio_read_command.handler = [this, gpio_reader, pin]() { + const auto value = gpio_reader->read(); + if (!value) { + logger_.log(core::LogLevel::kFatal, "Failed to read from GPIO pin %d", pin); + return; + } + logger_.log( + core::LogLevel::kDebug, "GPIO value from pin %d: %d", pin, static_cast(*value)); + }; +} + +void Repl::addGpioWriteCommands(const std::uint8_t pin) +{ + const auto optional_gpio_writer = gpio_.getWriter(pin); + if (!optional_gpio_writer) { + logger_.log(core::LogLevel::kFatal, "Failed to create GPIO writer on pin %d", pin); + return; + } + const auto gpio_writer = std::move(*optional_gpio_writer); + Command gpio_write_command; + std::stringstream identifier; + identifier << "gpio " << static_cast(pin) << " write"; + gpio_write_command.name = identifier.str(); + std::stringstream description; + description << "Write to GPIO pin " << static_cast(pin); + gpio_write_command.description = description.str(); + gpio_write_command.handler = [this, gpio_writer, pin]() { + std::cout << "Enter GPIO value: "; + std::uint8_t value; + std::cin >> std::hex >> value; + std::cin.ignore(std::numeric_limits::max(), '\n'); + core::DigitalSignal signal; + switch (value) { + case 0: + signal = core::DigitalSignal::kLow; + break; + case 1: + signal = core::DigitalSignal::kHigh; + break; + default: + logger_.log(core::LogLevel::kFatal, "Invalid GPIO value: %d, must be 0 or 1", value); + break; + } + const auto result = gpio_writer->write(signal); + if (result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to write to GPIO pin %d", pin); + return; + } + logger_.log(core::LogLevel::kDebug, "Wrote %d to GPIO pin %d", value, pin); + }; + addCommand(gpio_write_command); +} + void Repl::addI2cCommands(const std::uint8_t bus) { const auto optional_i2c = getI2c(bus); diff --git a/lib/debug/repl.hpp b/lib/debug/repl.hpp index c949308f..cb1e85ff 100644 --- a/lib/debug/repl.hpp +++ b/lib/debug/repl.hpp @@ -49,6 +49,8 @@ class Repl { void addHelpCommand(); void addAdcCommands(const std::uint8_t pin); void addCanCommands(const std::string &bus); + void addGpioReadCommands(const std::uint8_t pin); + void addGpioWriteCommands(const std::uint8_t pin); void addI2cCommands(const std::uint8_t bus); void addPwmCommands(const std::uint8_t module, const std::uint32_t period); void addSpiCommands(const std::uint8_t bus); @@ -125,8 +127,9 @@ class Repl { core::ILogger &logger_; std::map command_map_; - std::unordered_map> can_; std::unordered_map> adc_; + std::unordered_map> can_; + io::HardwareGpio gpio_; std::unordered_map> i2c_; std::unordered_map> pwm_; std::unordered_map> spi_; From 3893f0acfea586f15621314f6274daf99144df24 Mon Sep 17 00:00:00 2001 From: Tom Lonergan Date: Sun, 28 May 2023 23:30:03 +0100 Subject: [PATCH 2/4] std check --- lib/debug/repl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/debug/repl.cpp b/lib/debug/repl.cpp index c615cd89..0c2af5eb 100644 --- a/lib/debug/repl.cpp +++ b/lib/debug/repl.cpp @@ -452,7 +452,7 @@ void Repl::addGpioReadCommands(const std::uint8_t pin) return; } logger_.log( - core::LogLevel::kDebug, "GPIO value from pin %d: %d", pin, static_cast(*value)); + core::LogLevel::kDebug, "GPIO value from pin %d: %d", pin, static_cast(*value)); }; } From 507288511f307fc790ccddede1119e572fc8c474 Mon Sep 17 00:00:00 2001 From: Tom Lonergan Date: Fri, 2 Jun 2023 13:24:55 +0100 Subject: [PATCH 3/4] make gpio work --- lib/debug/repl.cpp | 6 +- lib/io/hardware_gpio.cpp | 221 +++++++++++++++++++++++++++++++--- lib/io/hardware_gpio.hpp | 63 ++++++++-- lib/io/hardware_gpio_alt.cpp | 225 ----------------------------------- lib/io/hardware_gpio_alt.hpp | 89 -------------- src/pod/main.cpp | 2 +- 6 files changed, 265 insertions(+), 341 deletions(-) delete mode 100644 lib/io/hardware_gpio_alt.cpp delete mode 100644 lib/io/hardware_gpio_alt.hpp diff --git a/lib/debug/repl.cpp b/lib/debug/repl.cpp index 0c2af5eb..1e287628 100644 --- a/lib/debug/repl.cpp +++ b/lib/debug/repl.cpp @@ -432,7 +432,7 @@ void Repl::addCanCommands(const std::string &bus) void Repl::addGpioReadCommands(const std::uint8_t pin) { - const auto optional_gpio_reader = gpio_.getReader(pin); + const auto optional_gpio_reader = gpio_.getReader(pin, io::Edge::kNone); if (!optional_gpio_reader) { logger_.log(core::LogLevel::kFatal, "Failed to create GPIO reader on pin %d", pin); return; @@ -458,7 +458,7 @@ void Repl::addGpioReadCommands(const std::uint8_t pin) void Repl::addGpioWriteCommands(const std::uint8_t pin) { - const auto optional_gpio_writer = gpio_.getWriter(pin); + const auto optional_gpio_writer = gpio_.getWriter(pin, io::Edge::kNone); if (!optional_gpio_writer) { logger_.log(core::LogLevel::kFatal, "Failed to create GPIO writer on pin %d", pin); return; @@ -473,7 +473,7 @@ void Repl::addGpioWriteCommands(const std::uint8_t pin) gpio_write_command.description = description.str(); gpio_write_command.handler = [this, gpio_writer, pin]() { std::cout << "Enter GPIO value: "; - std::uint8_t value; + std::uint16_t value; std::cin >> std::hex >> value; std::cin.ignore(std::numeric_limits::max(), '\n'); core::DigitalSignal signal; diff --git a/lib/io/hardware_gpio.cpp b/lib/io/hardware_gpio.cpp index f62184f9..0916c5f0 100644 --- a/lib/io/hardware_gpio.cpp +++ b/lib/io/hardware_gpio.cpp @@ -1,36 +1,229 @@ #include "hardware_gpio.hpp" +#include +#include + namespace hyped::io { +HardwareGpioReader::HardwareGpioReader(core::ILogger &log, const int read_file_descriptor) + : logger_(log), + read_file_descriptor_(read_file_descriptor) +{ +} + +HardwareGpioReader::~HardwareGpioReader() +{ + close(read_file_descriptor_); +} + std::optional HardwareGpioReader::read() { - // TODOLater: implement - throw -1; + // Read the value from the file + char read_buffer[2]; + const off_t offset = lseek(read_file_descriptor_, 0, SEEK_SET); + if (offset != 0) { + logger_.log(core::LogLevel::kFatal, "Failed to reset file offset"); + return std::nullopt; + } + const ssize_t read_result = ::read(read_file_descriptor_, read_buffer, sizeof(read_buffer)); + if (read_result != sizeof(read_buffer)) { + logger_.log(core::LogLevel::kFatal, "Failed to read GPIO value"); + return std::nullopt; + } + // Convert the read value to a DigitalSignal + const int value = std::atoi(read_buffer); + if (value == 0) { + return core::DigitalSignal::kLow; + } else if (value == 1) { + return core::DigitalSignal::kHigh; + } else { + logger_.log(core::LogLevel::kFatal, "Invalid GPIO value read"); + return std::nullopt; + } +} + +HardwareGpioWriter::HardwareGpioWriter(core::ILogger &log, const int write_file_descriptor) + : logger_(log), + write_file_descriptor_(write_file_descriptor) +{ +} + +HardwareGpioWriter::~HardwareGpioWriter() +{ + close(write_file_descriptor_); } core::Result HardwareGpioWriter::write(const core::DigitalSignal state) { - // TODOLater: implement - throw -1; + // Convert DigitalSignal to a string + const std::uint8_t signal_value = static_cast(state); + char write_buffer[2]; + snprintf(write_buffer, sizeof(write_buffer), "%d", signal_value); + // Write the value to the file + const ssize_t write_result = ::write(write_file_descriptor_, write_buffer, sizeof(write_buffer)); + if (write_result != sizeof(write_buffer)) { + logger_.log(core::LogLevel::kFatal, "Failed to write GPIO value"); + return core::Result::kFailure; + } + logger_.log(core::LogLevel::kDebug, "Wrote %d to GPIO", signal_value); + return core::Result::kSuccess; +} + +HardwareGpio::HardwareGpio(core::ILogger &log) : logger_(log) +{ +} + +std::optional> HardwareGpio::getReader(const std::uint8_t pin, + const Edge edge) +{ + const core::Result initialise_result = initialisePin(pin, edge, Direction::kIn); + if (initialise_result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to initialise GPIO %d", pin); + return std::nullopt; + } + const int read_file_descriptor = getFileDescriptor(pin, Direction::kIn); + if (read_file_descriptor < 0) { + logger_.log(core::LogLevel::kFatal, "Failed to get file descriptor for GPIO %d", pin); + return std::nullopt; + } + return std::make_shared(logger_, read_file_descriptor); +} + +std::optional> HardwareGpio::getWriter(const std::uint8_t pin, + const Edge edge) +{ + const core::Result initialise_result = initialisePin(pin, edge, Direction::kOut); + if (initialise_result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to initialise GPIO %d", pin); + return std::nullopt; + } + const int write_file_descriptor = getFileDescriptor(pin, Direction::kOut); + if (write_file_descriptor < 0) { + logger_.log(core::LogLevel::kFatal, "Failed to get file descriptor for GPIO %d", pin); + return std::nullopt; + } + return std::make_shared(logger_, write_file_descriptor); +} + +core::Result HardwareGpio::exportPin(const std::uint8_t pin) +{ + const int export_file_descriptor = open("/sys/class/gpio/export", O_WRONLY); + if (export_file_descriptor < 0) { + logger_.log(core::LogLevel::kFatal, "Failed to open GPIO export file"); + return core::Result::kFailure; + } + char write_buffer[4]; + snprintf(write_buffer, sizeof(write_buffer), "%d", pin); + const ssize_t write_result = write(export_file_descriptor, write_buffer, sizeof(write_buffer)); + close(export_file_descriptor); + if (write_result != sizeof(write_buffer)) { + logger_.log(core::LogLevel::kFatal, "Failed to export GPIO %d", pin); + return core::Result::kFailure; + } + logger_.log(core::LogLevel::kDebug, "Successfully exported GPIO %d", pin); + return core::Result::kSuccess; +} + +core::Result HardwareGpio::initialisePin(const std::uint8_t pin, + const Edge edge, + const Direction direction) +{ + // First check if the pin is already exported, and export it if not + char file_path_buffer[50]; + snprintf(file_path_buffer, sizeof(file_path_buffer), "/sys/class/gpio/gpio%d", pin); + const int access_result = access(file_path_buffer, F_OK); // Check if the file exists + if (access_result < 0) { + logger_.log(core::LogLevel::kDebug, "GPIO %d not exported, exporting", pin); + const core::Result export_result = exportPin(pin); + if (export_result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to export GPIO %d while initialising", pin); + return core::Result::kFailure; + } + } + // Then set the direction + snprintf(file_path_buffer, sizeof(file_path_buffer), "/sys/class/gpio/gpio%d/direction", pin); + const int direction_file_descriptor = open(file_path_buffer, O_WRONLY); + if (direction_file_descriptor < 0) { + logger_.log(core::LogLevel::kFatal, "Failed to open GPIO direction file"); + return core::Result::kFailure; + } + const std::string direction_string = getDirectionString(direction); + const ssize_t direction_write_result + = write(direction_file_descriptor, direction_string.c_str(), direction_string.size() + 1); + close(direction_file_descriptor); + if (direction_write_result < 0) { + logger_.log(core::LogLevel::kFatal, + "Failed to set GPIO %d, direction error %d, errno %d", + pin, + direction_write_result, + errno); + return core::Result::kFailure; + } + // Finally set the edge + snprintf(file_path_buffer, sizeof(file_path_buffer), "/sys/class/gpio/gpio%d/edge", pin); + const int edge_file_descriptor = open(file_path_buffer, O_WRONLY); + if (edge_file_descriptor < 0) { + logger_.log(core::LogLevel::kFatal, "Failed to open GPIO edge file"); + return core::Result::kFailure; + } + const std::string edge_string = getEdgeString(edge); + const ssize_t edge_write_result + = write(edge_file_descriptor, edge_string.c_str(), edge_string.size() + 1); + close(edge_file_descriptor); + if (edge_write_result < 0) { + logger_.log(core::LogLevel::kFatal, "Failed to set the edge for GPIO %d, errno %d", pin, errno); + return core::Result::kFailure; + } + logger_.log(core::LogLevel::kDebug, "Successfully initialised GPIO %d", pin); + return core::Result::kSuccess; } -HardwareGpio::HardwareGpio(core::ILogger &log) : log_(log) +int HardwareGpio::getFileDescriptor(const std::uint8_t pin, const Direction direction) { - // TODOLater: implement + // Set up the file path + char value_file_path[64]; + snprintf(value_file_path, sizeof(value_file_path), "/sys/class/gpio/gpio%d/value", pin); + // Set up the file descriptor + int file_descriptor; + // The direction is used to determine whether the file descriptor is opened for reading or writing + if (direction == Direction::kIn) { + file_descriptor = open(value_file_path, O_RDONLY); + } else { + file_descriptor = open(value_file_path, O_WRONLY); + } + if (file_descriptor < 0) { + logger_.log(core::LogLevel::kFatal, "Failed to open GPIO value file"); + return -1; + } + return file_descriptor; } -std::optional> HardwareGpio::getReader(const std::uint8_t pin) +const std::string HardwareGpio::getEdgeString(const Edge edge) { - // TODOLater: implement - log_.log(core::LogLevel::kFatal, "GPIO reader not implemented"); - return std::nullopt; + switch (edge) { + case Edge::kNone: + return "none"; + case Edge::kRising: + return "rising"; + case Edge::kFalling: + return "falling"; + case Edge::kBoth: + return "both"; + default: // for compiler + return ""; + } } -std::optional> HardwareGpio::getWriter(const std::uint8_t pin) +const std::string HardwareGpio::getDirectionString(const Direction direction) { - // TODOLater: implement - log_.log(core::LogLevel::kFatal, "GPIO writer not implemented"); - return std::nullopt; + switch (direction) { + case Direction::kIn: + return "in"; + case Direction::kOut: + return "out"; + default: // for compiler + return ""; + } } } // namespace hyped::io \ No newline at end of file diff --git a/lib/io/hardware_gpio.hpp b/lib/io/hardware_gpio.hpp index 9217b671..f5c431e5 100644 --- a/lib/io/hardware_gpio.hpp +++ b/lib/io/hardware_gpio.hpp @@ -2,45 +2,90 @@ #include "gpio.hpp" +#include + #include #include +#include -#include #include namespace hyped::io { +// Edge is used to set the interrupt trigger for the pin. +enum class Edge { kNone = 0, kRising, kFalling, kBoth }; +enum class Direction { kIn = 0, kOut }; + class HardwareGpioReader : public IGpioReader { public: + HardwareGpioReader(core::ILogger &logger, const int read_file_descritor); + ~HardwareGpioReader(); + /** + * @brief Read a high or low from the GPIO pin. + */ virtual std::optional read(); private: - HardwareGpioReader(); - friend class HardwareGpio; + core::ILogger &logger_; + const int read_file_descriptor_; }; class HardwareGpioWriter : public IGpioWriter { public: + HardwareGpioWriter(core::ILogger &logger, const int write_file_descriptor); + ~HardwareGpioWriter(); + + /** + * @brief Writes a high or low to the GPIO pin. + * @param state The digital signal to write to the pin. + */ virtual core::Result write(const core::DigitalSignal state); private: - HardwareGpioWriter(const std::uint8_t pin); - friend class HardwareGpio; + core::ILogger &logger_; + const int write_file_descriptor_; }; /** * Hardware GPIO interface, requires physical GPIO pins to be present. This should only * be instantiated at the top level and then provided to users through the IGpio interface. + * Ensure inputted pins are defined as pin = 32*X + Y (GPIOX_Y) */ class HardwareGpio { public: - HardwareGpio(core::ILogger &log); + HardwareGpio(core::ILogger &logger); - virtual std::optional> getReader(const std::uint8_t pin); - virtual std::optional> getWriter(const std::uint8_t pin); + virtual std::optional> getReader(const std::uint8_t pin, + const Edge edge); + virtual std::optional> getWriter(const std::uint8_t pin, + const Edge edge); private: - core::ILogger &log_; + /** + * @brief Initialises the GPIO pin for reading or writing. + * @param pin The pin to initialise. + * @param edge The edge to trigger on. Defaults to "both". + * @param direction The direction of the pin. + */ + core::Result initialisePin(const std::uint8_t pin, const Edge edge, const Direction direction); + + /** + * @brief Exports the GPIO pin to the filesystem. + * @details This is required to be able to access the pin. Normally hidden from userspace. + * @param pin The pin to export. + */ + core::Result exportPin(const std::uint8_t pin); + + /** + * @brief Get the file descriptor for the pin depending on if we are reading or writing. + */ + int getFileDescriptor(const std::uint8_t pin, const Direction direction); + + // Helper functions to get the string representation of the edge and direction. + static const std::string getEdgeString(const Edge edge); + static const std::string getDirectionString(const Direction direction); + + core::ILogger &logger_; }; } // namespace hyped::io \ No newline at end of file diff --git a/lib/io/hardware_gpio_alt.cpp b/lib/io/hardware_gpio_alt.cpp deleted file mode 100644 index 2626ad40..00000000 --- a/lib/io/hardware_gpio_alt.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#include "hardware_gpio_alt.hpp" - -#include -#include - -namespace hyped::io { - -HardwareGpioReader::HardwareGpioReader(core::ILogger &log, const int read_file_descriptor) - : logger_(log), - read_file_descriptor_(read_file_descriptor) -{ -} - -HardwareGpioReader::~HardwareGpioReader() -{ - close(read_file_descriptor_); -} - -std::optional HardwareGpioReader::read() -{ - // Read the value from the file - char read_buffer[2]; - const off_t offset = lseek(read_file_descriptor_, 0, SEEK_SET); - if (offset != 0) { - logger_.log(core::LogLevel::kFatal, "Failed to reset file offset"); - return std::nullopt; - } - const ssize_t read_result = ::read(read_file_descriptor_, read_buffer, sizeof(read_buffer)); - if (read_result != sizeof(read_buffer)) { - logger_.log(core::LogLevel::kFatal, "Failed to read GPIO value"); - return std::nullopt; - } - // Convert the read value to a DigitalSignal - const int value = std::atoi(read_buffer); - if (value == 0) { - return core::DigitalSignal::kLow; - } else if (value == 1) { - return core::DigitalSignal::kHigh; - } else { - logger_.log(core::LogLevel::kFatal, "Invalid GPIO value read"); - return std::nullopt; - } -} - -HardwareGpioWriter::HardwareGpioWriter(core::ILogger &log, const int write_file_descriptor) - : logger_(log), - write_file_descriptor_(write_file_descriptor) -{ -} - -HardwareGpioWriter::~HardwareGpioWriter() -{ - close(write_file_descriptor_); -} - -core::Result HardwareGpioWriter::write(const core::DigitalSignal state) -{ - // Convert DigitalSignal to a string - const std::uint8_t signal_value = static_cast(state); - char write_buffer[2]; - snprintf(write_buffer, sizeof(write_buffer), "%d", signal_value); - // Write the value to the file - const ssize_t write_result = ::write(write_file_descriptor_, write_buffer, sizeof(write_buffer)); - if (write_result != sizeof(write_buffer)) { - logger_.log(core::LogLevel::kFatal, "Failed to write GPIO value"); - return core::Result::kFailure; - } - logger_.log(core::LogLevel::kDebug, "Wrote %d to GPIO", signal_value); - return core::Result::kSuccess; -} - -HardwareGpio::HardwareGpio(core::ILogger &log) : logger_(log) -{ -} - -std::optional> HardwareGpio::getReader(const std::uint8_t pin, - const Edge edge) -{ - const core::Result initialise_result = initialisePin(pin, edge, Direction::kIn); - if (initialise_result == core::Result::kFailure) { - logger_.log(core::LogLevel::kFatal, "Failed to initialise GPIO %d", pin); - return std::nullopt; - } - const int read_file_descriptor = getFileDescriptor(pin, Direction::kIn); - if (read_file_descriptor < 0) { - logger_.log(core::LogLevel::kFatal, "Failed to get file descriptor for GPIO %d", pin); - return std::nullopt; - } - return std::make_shared(logger_, read_file_descriptor); -} - -std::optional> HardwareGpio::getWriter(const std::uint8_t pin, - const Edge edge) -{ - const core::Result initialise_result = initialisePin(pin, edge, Direction::kOut); - if (initialise_result == core::Result::kFailure) { - logger_.log(core::LogLevel::kFatal, "Failed to initialise GPIO %d", pin); - return std::nullopt; - } - const int write_file_descriptor = getFileDescriptor(pin, Direction::kOut); - if (write_file_descriptor < 0) { - logger_.log(core::LogLevel::kFatal, "Failed to get file descriptor for GPIO %d", pin); - return std::nullopt; - } - return std::make_shared(logger_, write_file_descriptor); -} - -core::Result HardwareGpio::exportPin(const std::uint8_t pin) -{ - const int export_file_descriptor = open("/sys/class/gpio/export", O_WRONLY); - if (export_file_descriptor < 0) { - logger_.log(core::LogLevel::kFatal, "Failed to open GPIO export file"); - return core::Result::kFailure; - } - char write_buffer[4]; - snprintf(write_buffer, sizeof(write_buffer), "%d", pin); - const ssize_t write_result = write(export_file_descriptor, write_buffer, sizeof(write_buffer)); - close(export_file_descriptor); - if (write_result != sizeof(write_buffer)) { - logger_.log(core::LogLevel::kFatal, "Failed to export GPIO %d", pin); - return core::Result::kFailure; - } - logger_.log(core::LogLevel::kDebug, "Successfully exported GPIO %d", pin); - return core::Result::kSuccess; -} - -core::Result HardwareGpio::initialisePin(const std::uint8_t pin, - const Edge edge, - const Direction direction) -{ - // First check if the pin is already exported, and export it if not - char file_path_buffer[50]; - snprintf(file_path_buffer, sizeof(file_path_buffer), "/sys/class/gpio/gpio%d", pin); - const int access_result = access(file_path_buffer, F_OK); // Check if the file exists - if (access_result < 0) { - logger_.log(core::LogLevel::kDebug, "GPIO %d not exported, exporting", pin); - const core::Result export_result = exportPin(pin); - if (export_result == core::Result::kFailure) { - logger_.log(core::LogLevel::kFatal, "Failed to export GPIO %d while initialising", pin); - return core::Result::kFailure; - } - } - // Then set the direction - snprintf(file_path_buffer, sizeof(file_path_buffer), "/sys/class/gpio/gpio%d/direction", pin); - const int direction_file_descriptor = open(file_path_buffer, O_WRONLY); - if (direction_file_descriptor < 0) { - logger_.log(core::LogLevel::kFatal, "Failed to open GPIO direction file"); - return core::Result::kFailure; - } - const std::string direction_string = getDirectionString(direction); - const ssize_t direction_write_result - = write(direction_file_descriptor, direction_string.c_str(), direction_string.size() + 1); - close(direction_file_descriptor); - if (direction_write_result != 3) { - logger_.log(core::LogLevel::kFatal, "Failed to set GPIO %d direction", pin); - return core::Result::kFailure; - } - // Finally set the edge - snprintf(file_path_buffer, sizeof(file_path_buffer), "/sys/class/gpio/gpio%d/edge", pin); - const int edge_file_descriptor = open(file_path_buffer, O_WRONLY); - if (edge_file_descriptor < 0) { - logger_.log(core::LogLevel::kFatal, "Failed to open GPIO edge file"); - return core::Result::kFailure; - } - const std::string edge_string = getEdgeString(edge); - const ssize_t edge_write_result - = write(edge_file_descriptor, edge_string.c_str(), edge_string.size() + 1); - close(edge_file_descriptor); - if (edge_write_result != edge_string.size() + 1) { - logger_.log(core::LogLevel::kFatal, "Failed to set the edge for GPIO %d", pin); - return core::Result::kFailure; - } - logger_.log(core::LogLevel::kDebug, "Successfully initialised GPIO %d", pin); - return core::Result::kSuccess; -} - -int HardwareGpio::getFileDescriptor(const std::uint8_t pin, const Direction direction) -{ - // Set up the file path - char value_file_path[64]; - snprintf(value_file_path, sizeof(value_file_path), "/sys/class/gpio/gpio%d/value", pin); - // Set up the file descriptor - int file_descriptor; - // The direction is used to determine whether the file descriptor is opened for reading or writing - if (direction == Direction::kIn) { - file_descriptor = open(value_file_path, O_RDONLY); - } else { - file_descriptor = open(value_file_path, O_WRONLY); - } - if (file_descriptor < 0) { - logger_.log(core::LogLevel::kFatal, "Failed to open GPIO value file"); - return -1; - } - return file_descriptor; -} - -const std::string HardwareGpio::getEdgeString(const Edge edge) -{ - switch (edge) { - case Edge::kNone: - return "none"; - case Edge::kRising: - return "rising"; - case Edge::kFalling: - return "falling"; - case Edge::kBoth: - return "both"; - default: // for compiler - return ""; - } -} - -const std::string HardwareGpio::getDirectionString(const Direction direction) -{ - switch (direction) { - case Direction::kIn: - return "in"; - case Direction::kOut: - return "out"; - default: // for compiler - return ""; - } -} - -} // namespace hyped::io \ No newline at end of file diff --git a/lib/io/hardware_gpio_alt.hpp b/lib/io/hardware_gpio_alt.hpp deleted file mode 100644 index 0732a848..00000000 --- a/lib/io/hardware_gpio_alt.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include "gpio.hpp" - -#include -#include -#include - -#include - -namespace hyped::io { - -// Edge is used to set the interrupt trigger for the pin. -enum class Edge { kNone = 0, kRising, kFalling, kBoth }; -enum class Direction { kIn = 0, kOut }; - -class HardwareGpioReader : public IGpioReader { - public: - HardwareGpioReader(core::ILogger &logger, const int read_file_descritor); - ~HardwareGpioReader(); - /** - * @brief Read a high or low from the GPIO pin. - */ - virtual std::optional read(); - - private: - core::ILogger &logger_; - const int read_file_descriptor_; -}; - -class HardwareGpioWriter : public IGpioWriter { - public: - HardwareGpioWriter(core::ILogger &logger, const int write_file_descriptor); - ~HardwareGpioWriter(); - - /** - * @brief Writes a high or low to the GPIO pin. - * @param state The digital signal to write to the pin. - */ - virtual core::Result write(const core::DigitalSignal state); - - private: - core::ILogger &logger_; - const int write_file_descriptor_; -}; - -/** - * Hardware GPIO interface, requires physical GPIO pins to be present. This should only - * be instantiated at the top level and then provided to users through the IGpio interface. - * Ensure inputted pins are defined as pin = 32*X + Y (GPIOX_Y) - */ -class HardwareGpio { - public: - HardwareGpio(core::ILogger &logger); - - virtual std::optional> getReader(const std::uint8_t pin, - const Edge edge); - virtual std::optional> getWriter(const std::uint8_t pin, - const Edge edge); - - private: - /** - * @brief Initialises the GPIO pin for reading or writing. - * @param pin The pin to initialise. - * @param edge The edge to trigger on. Defaults to "both". - * @param direction The direction of the pin. - */ - core::Result initialisePin(const std::uint8_t pin, const Edge edge, const Direction direction); - - /** - * @brief Exports the GPIO pin to the filesystem. - * @details This is required to be able to access the pin. Normally hidden from userspace. - * @param pin The pin to export. - */ - core::Result exportPin(const std::uint8_t pin); - - /** - * @brief Get the file descriptor for the pin depending on if we are reading or writing. - */ - int getFileDescriptor(const std::uint8_t pin, const Direction direction); - - // Helper functions to get the string representation of the edge and direction. - static const std::string getEdgeString(const Edge edge); - static const std::string getDirectionString(const Direction direction); - - core::ILogger &logger_; -}; - -} // namespace hyped::io \ No newline at end of file diff --git a/src/pod/main.cpp b/src/pod/main.cpp index a76d45af..e5d24f0c 100644 --- a/src/pod/main.cpp +++ b/src/pod/main.cpp @@ -13,7 +13,7 @@ int main(int argc, char **argv) const auto execution_time = timer.measureExecutionTime([time]() { hyped::core::Logger logger("GPIO", hyped::core::LogLevel::kDebug, time); hyped::io::HardwareGpio gpio(logger); - auto gpio_reader_opt = gpio.getReader(0); + auto gpio_reader_opt = gpio.getReader(0, hyped::io::Edge::kNone); if (!gpio_reader_opt) { logger.log(hyped::core::LogLevel::kFatal, "Error"); return; From c62c749281dafbef60942c3afe2e83babc0346fd Mon Sep 17 00:00:00 2001 From: Tom Lonergan Date: Thu, 8 Jun 2023 14:36:13 +0100 Subject: [PATCH 4/4] add support for testrig gpio --- config/template.json | 8 +- lib/debug/repl.cpp | 167 +++++++++++++++++++ lib/debug/repl.hpp | 3 + lib/io/hardware_gpio.cpp | 1 + lib/motors/constant_frequency_calculator.cpp | 2 +- lib/motors/constant_frequency_calculator.hpp | 2 +- 6 files changed, 180 insertions(+), 3 deletions(-) diff --git a/config/template.json b/config/template.json index 58d701ac..0744a860 100644 --- a/config/template.json +++ b/config/template.json @@ -6,7 +6,7 @@ "pins": [] }, "can": { - "enabled": true, + "enabled": false, "buses": [ "can1" ] @@ -50,6 +50,12 @@ "motor_controller": { "enabled": true, "bus": "can1" + }, + "testrig": { + "enabled": true, + "precharge_relay_gpio_pin": 30, + "controller_relay_gpio_pin": 60, + "main_relay_gpio_pin": 61 } } } diff --git a/lib/debug/repl.cpp b/lib/debug/repl.cpp index 1e287628..4f8567a2 100644 --- a/lib/debug/repl.cpp +++ b/lib/debug/repl.cpp @@ -320,6 +320,41 @@ std::optional> Repl::fromFile(const std::string &path) const auto bus = motor_controller["bus"].GetString(); repl->addMotorControllerCommands(bus); } + if (!motors.HasMember("testrig")) { + logger_.log(core::LogLevel::kFatal, + "Missing required field 'debugger.testrig' in configuration file"); + return std::nullopt; + } + const auto testrig = motors["testrig"].GetObject(); + if (!testrig.HasMember("enabled")) { + logger_.log(core::LogLevel::kFatal, + "Missing required field 'testrig.enabled' in configuration file"); + return std::nullopt; + } + if (testrig["enabled"].GetBool()) { + if (!testrig.HasMember("precharge_relay_gpio_pin")) { + logger_.log(core::LogLevel::kFatal, + "Missing required field 'testrig.precharge_relay_gpio_pin' in configuration " + "file"); + return std::nullopt; + } + const auto precharge_relay_gpio_pin = testrig["precharge_relay_gpio_pin"].GetUint(); + if (!testrig.HasMember("controller_relay_gpio_pin")) { + logger_.log( + core::LogLevel::kFatal, + "Missing required field 'testrig.controller_relay_gpio_pin' in configuration file"); + return std::nullopt; + } + const auto controller_relay_gpio_pin = testrig["controller_relay_gpio_pin"].GetUint(); + if (!testrig.HasMember("main_relay_gpio_pin")) { + logger_.log(core::LogLevel::kFatal, + "Missing required field 'testrig.main_relay_gpio_pin' in configuration file"); + return std::nullopt; + } + const auto main_relay_gpio_pin = testrig["main_relay_gpio_pin"].GetUint(); + repl->addMotorTestrigCommands( + precharge_relay_gpio_pin, controller_relay_gpio_pin, main_relay_gpio_pin); + } return repl; } @@ -984,6 +1019,138 @@ void Repl::addMotorControllerCommands(const std::string &bus) addCommand(frequency_time_command); } +void Repl::addMotorTestrigCommands(const std::uint8_t precharge_relay_gpio_pin, + const std::uint8_t controller_relay_gpio_pin, + const std::uint8_t main_relay_gpio_pin) +{ + const auto optional_precharge_relay_gpio + = gpio_.getWriter(precharge_relay_gpio_pin, io::Edge::kNone); + if (!optional_precharge_relay_gpio) { + logger_.log( + core::LogLevel::kFatal, "Failed to get GPIO instance on pin %d", precharge_relay_gpio_pin); + return; + } + const auto precharge_relay_gpio = std::move(*optional_precharge_relay_gpio); + const auto optional_controller_relay_gpio + = gpio_.getWriter(controller_relay_gpio_pin, io::Edge::kNone); + if (!optional_controller_relay_gpio) { + logger_.log( + core::LogLevel::kFatal, "Failed to get GPIO instance on pin %d", controller_relay_gpio_pin); + return; + } + const auto controller_relay_gpio = std::move(*optional_controller_relay_gpio); + const auto optional_main_relay_gpio = gpio_.getWriter(main_relay_gpio_pin, io::Edge::kNone); + if (!optional_main_relay_gpio) { + logger_.log( + core::LogLevel::kFatal, "Failed to get GPIO instance on pin %d", main_relay_gpio_pin); + return; + } + const auto main_relay_gpio = std::move(*optional_main_relay_gpio); + Command testrig_precharge_relay_gpio_command; + testrig_precharge_relay_gpio_command.name = "set precharge relay gpio"; + testrig_precharge_relay_gpio_command.description = "Set precharge gpio to value"; + testrig_precharge_relay_gpio_command.handler = [this, precharge_relay_gpio] { + std::uint16_t value; + std::cout << "Value: "; + std::cin >> value; + std::cin.ignore(std::numeric_limits::max(), '\n'); + core::DigitalSignal signal; + switch (value) { + case 0: + signal = core::DigitalSignal::kLow; + break; + case 1: + signal = core::DigitalSignal::kHigh; + break; + default: + logger_.log(core::LogLevel::kFatal, "Invalid GPIO value: %d, must be 0 or 1", value); + break; + } + const core::Result result = precharge_relay_gpio->write(signal); + if (result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to set GPIO 1"); + return; + } + }; + addCommand(testrig_precharge_relay_gpio_command); + Command testrig_controller_relay_gpio_command; + testrig_controller_relay_gpio_command.name = "set controller relay gpio"; + testrig_controller_relay_gpio_command.description = "Set controller relay gpio to value"; + testrig_controller_relay_gpio_command.handler = [this, controller_relay_gpio] { + std::uint16_t value; + std::cout << "Value: "; + std::cin >> value; + std::cin.ignore(std::numeric_limits::max(), '\n'); + core::DigitalSignal signal; + switch (value) { + case 0: + signal = core::DigitalSignal::kLow; + break; + case 1: + signal = core::DigitalSignal::kHigh; + break; + default: + logger_.log(core::LogLevel::kFatal, "Invalid GPIO value: %d, must be 0 or 1", value); + break; + } + const core::Result result = controller_relay_gpio->write(signal); + if (result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to set GPIO 2"); + return; + } + }; + addCommand(testrig_controller_relay_gpio_command); + Command testrig_main_relay_gpio_command; + testrig_main_relay_gpio_command.name = "set main relay gpio"; + testrig_main_relay_gpio_command.description = "Set main relay gpio to value"; + testrig_main_relay_gpio_command.handler = [this, main_relay_gpio] { + std::uint16_t value; + std::cout << "Value: "; + std::cin >> value; + std::cin.ignore(std::numeric_limits::max(), '\n'); + core::DigitalSignal signal; + switch (value) { + case 0: + signal = core::DigitalSignal::kLow; + break; + case 1: + signal = core::DigitalSignal::kHigh; + break; + default: + logger_.log(core::LogLevel::kFatal, "Invalid GPIO value: %d, must be 0 or 1", value); + break; + } + const core::Result result = main_relay_gpio->write(signal); + if (result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to set GPIO 3"); + return; + } + }; + addCommand(testrig_main_relay_gpio_command); + Command testrig_stop_all_command; + testrig_stop_all_command.name = "stop"; + testrig_stop_all_command.description = "Stop all relays"; + testrig_stop_all_command.handler = + [this, precharge_relay_gpio, controller_relay_gpio, main_relay_gpio] { + const core::Result precharge_result = precharge_relay_gpio->write(core::DigitalSignal::kLow); + if (precharge_result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to set GPIO 1"); + return; + } + const core::Result controller_result + = controller_relay_gpio->write(core::DigitalSignal::kLow); + if (controller_result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to set GPIO 2"); + return; + } + const core::Result main_result = main_relay_gpio->write(core::DigitalSignal::kLow); + if (main_result == core::Result::kFailure) { + logger_.log(core::LogLevel::kFatal, "Failed to set GPIO 3"); + return; + } + }; +} + std::optional> Repl::getAdc(const std::uint8_t bus) { const auto adc = adc_.find(bus); diff --git a/lib/debug/repl.hpp b/lib/debug/repl.hpp index cb1e85ff..d186db27 100644 --- a/lib/debug/repl.hpp +++ b/lib/debug/repl.hpp @@ -58,6 +58,9 @@ class Repl { void addTemperatureCommands(const std::uint8_t bus, const std::uint8_t device_address); void addUartCommands(const std::uint8_t bus); void addMotorControllerCommands(const std::string &bus); + void addMotorTestrigCommands(const std::uint8_t precharge_relay_gpio_pin, + const std::uint8_t gpio_pin_2, + const std::uint8_t gpio_pin_3); /** * @brief Get the Adc object associated with the given pin or create a new one if it doesn't exist diff --git a/lib/io/hardware_gpio.cpp b/lib/io/hardware_gpio.cpp index 0916c5f0..b97f939c 100644 --- a/lib/io/hardware_gpio.cpp +++ b/lib/io/hardware_gpio.cpp @@ -50,6 +50,7 @@ HardwareGpioWriter::HardwareGpioWriter(core::ILogger &log, const int write_file_ HardwareGpioWriter::~HardwareGpioWriter() { + write(core::DigitalSignal::kLow); close(write_file_descriptor_); } diff --git a/lib/motors/constant_frequency_calculator.cpp b/lib/motors/constant_frequency_calculator.cpp index 22b1de15..761a6213 100644 --- a/lib/motors/constant_frequency_calculator.cpp +++ b/lib/motors/constant_frequency_calculator.cpp @@ -11,7 +11,7 @@ std::uint32_t ConstantFrequencyCalculator::calculateFrequency(core::Float veloci return frequency_; } -void ConstantFrequencyCalculator::setFrequency(std::uint16_t frequency) +void ConstantFrequencyCalculator::setFrequency(std::uint32_t frequency) { frequency_ = frequency; } diff --git a/lib/motors/constant_frequency_calculator.hpp b/lib/motors/constant_frequency_calculator.hpp index aee7f46c..cfd6b9b9 100644 --- a/lib/motors/constant_frequency_calculator.hpp +++ b/lib/motors/constant_frequency_calculator.hpp @@ -25,7 +25,7 @@ class ConstantFrequencyCalculator : public IFrequencyCalculator { * * @param frequency the frequency to be returned by calculateFrequency */ - void setFrequency(std::uint16_t frequency); + void setFrequency(std::uint32_t frequency); private: core::ILogger &logger_;