diff --git a/bindings/python/icsneopy/device/idevicesettings.cpp b/bindings/python/icsneopy/device/idevicesettings.cpp index 96a92cf..7569cf0 100644 --- a/bindings/python/icsneopy/device/idevicesettings.cpp +++ b/bindings/python/icsneopy/device/idevicesettings.cpp @@ -40,6 +40,14 @@ void init_idevicesettings(pybind11::module_& m) { .value("Normal", LINMode::NORMAL_MODE) .value("Fast", LINMode::FAST_MODE); + pybind11::enum_(settings, "MiscIOAnalogVoltage") + .value("V0", MiscIOAnalogVoltage::V0) + .value("V1", MiscIOAnalogVoltage::V1) + .value("V2", MiscIOAnalogVoltage::V2) + .value("V3", MiscIOAnalogVoltage::V3) + .value("V4", MiscIOAnalogVoltage::V4) + .value("V5", MiscIOAnalogVoltage::V5); + pybind11::classh(m, "IDeviceSettings") .def("apply", &IDeviceSettings::apply, pybind11::arg("temporary") = 0, pybind11::call_guard()) .def("apply_defaults", &IDeviceSettings::applyDefaults, pybind11::arg("temporary") = 0, pybind11::call_guard()) @@ -97,6 +105,9 @@ void init_idevicesettings(pybind11::module_& m) { .def("get_t1s_burst_timer", &IDeviceSettings::getT1SBurstTimerFor, pybind11::call_guard()) .def("set_t1s_burst_timer", &IDeviceSettings::setT1SBurstTimerFor, pybind11::call_guard()) + .def("set_misc_io_analog_output_enabled", &IDeviceSettings::setMiscIOAnalogOutputEnabled, pybind11::call_guard()) + .def("set_misc_io_analog_output", &IDeviceSettings::setMiscIOAnalogOutput, pybind11::call_guard()) + // Status properties .def_readonly("disabled", &IDeviceSettings::disabled) .def_readonly("readonly", &IDeviceSettings::readonly); diff --git a/device/idevicesettings.cpp b/device/idevicesettings.cpp index 1af9c60..82eb9b4 100644 --- a/device/idevicesettings.cpp +++ b/device/idevicesettings.cpp @@ -945,4 +945,18 @@ template bool IDeviceSettings::applyStructure(const T& newStructure) memcpy(settings.data(), &newStructure, structSize); return apply(); +} + +bool IDeviceSettings::setMiscIOAnalogOutputEnabled(uint8_t pin, bool enabled) { + (void)pin; + (void)enabled; + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error); + return false; +} + +bool IDeviceSettings::setMiscIOAnalogOutput(uint8_t pin, MiscIOAnalogVoltage voltage) { + (void)pin; + (void)voltage; + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error); + return false; } \ No newline at end of file diff --git a/docs/icsneopy/examples.rst b/docs/icsneopy/examples.rst index 9551e5b..cfc5fa3 100644 --- a/docs/icsneopy/examples.rst +++ b/docs/icsneopy/examples.rst @@ -91,3 +91,11 @@ SPI Example for 10BASE-T1S .. literalinclude:: ../../examples/python/spi/spi_example.py :language: python + +Analog Output Control +===================== + +:download:`Download example <../../examples/python/analog_out/analog_out_basic.py>` + +.. literalinclude:: ../../examples/python/analog_out/analog_out_basic.py + :language: python diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index deeda90..5a915ab 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -14,6 +14,7 @@ option(LIBICSNEO_BUILD_CPP_APP_ERROR_EXAMPLE "Build the macsec example" ON) option(LIBICSNEO_BUILD_CPP_FLEXRAY_EXAMPLE "Build the FlexRay example." ON) option(LIBICSNEO_BUILD_CPP_SPI_EXAMPLE "Build the SPI example." ON) option(LIBICSNEO_BUILD_CPP_MUTEX_EXAMPLE "Build the NetworkMutex example." ON) +option(LIBICSNEO_BUILD_CPP_ANALOG_OUT_EXAMPLE "Build the analog output example." ON) add_compile_options(${LIBICSNEO_COMPILER_WARNINGS}) @@ -80,3 +81,7 @@ endif() if(LIBICSNEO_BUILD_CPP_MUTEX_EXAMPLE) add_subdirectory(cpp/mutex) endif() + +if(LIBICSNEO_BUILD_CPP_ANALOG_OUT_EXAMPLE) + add_subdirectory(cpp/analog_out) +endif() diff --git a/examples/cpp/analog_out/CMakeLists.txt b/examples/cpp/analog_out/CMakeLists.txt new file mode 100644 index 0000000..ab8a977 --- /dev/null +++ b/examples/cpp/analog_out/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(libicsneocpp-analog-out src/analog_out.cpp) +target_link_libraries(libicsneocpp-analog-out icsneocpp) diff --git a/examples/cpp/analog_out/src/analog_out.cpp b/examples/cpp/analog_out/src/analog_out.cpp new file mode 100644 index 0000000..35bcd9c --- /dev/null +++ b/examples/cpp/analog_out/src/analog_out.cpp @@ -0,0 +1,157 @@ +/** + * libicsneo Analog Output example + * + * Demonstrates how to configure and control analog outputs on supported devices + * + * Usage: libicsneo-analog-out [deviceSerial] [--yes] + * + * Arguments: + * pin: Pin number (1-3 for RAD Galaxy) + * voltage: Voltage level (0-5) + * deviceSerial: 6 character string for device serial (optional) + * --yes: Skip confirmation prompt + */ + +#include +#include +#include +#include +#include + +#include "icsneo/icsneocpp.h" + +static const std::string usage = "Usage: libicsneo-analog-out [deviceSerial] [--yes]\n\n" + "Arguments:\n" + "pin: Pin number (1-3 for RAD Galaxy)\n" + "voltage: Voltage level (0-5)\n" + "deviceSerial: 6 character string for device serial (optional)\n" + "--yes: Skip confirmation prompt\n"; + +int main(int argc, const char** argv) { + std::vector args(argv, argv + argc); + + // Parse arguments + if(args.size() < 3) { + std::cerr << "Error: Missing required arguments\n" << std::endl; + std::cerr << usage; + return -1; + } + + char* endPtr; + long pinNum = std::strtol(args[1].data(), &endPtr, 10); + if(endPtr != args[1].data() + args[1].size() || pinNum < 1 || pinNum > 3) { + std::cerr << "Error: Invalid pin number (must be 1-3)" << std::endl; + return -1; + } + + long voltageLevel = std::strtol(args[2].data(), &endPtr, 10); + if(endPtr != args[2].data() + args[2].size() || voltageLevel < 0 || voltageLevel > 5) { + std::cerr << "Error: Invalid voltage level (must be 0-5)" << std::endl; + return -1; + } + + icsneo::MiscIOAnalogVoltage voltage = static_cast(voltageLevel); + uint8_t pin = static_cast(pinNum); + + // Check for optional arguments + bool skipConfirm = false; + std::string_view serial; + for(size_t i = 3; i < args.size(); i++) { + if(args[i] == "--yes") { + skipConfirm = true; + } else if(serial.empty() && args[i].size() == 6) { + serial = args[i]; + } + } + + // Confirmation prompt + if(!skipConfirm) { + std::cout << "WARNING: This will set analog output pin " << static_cast(pin) + << " to " << voltageLevel << "V" << std::endl; + std::cout << "Make sure nothing sensitive is connected to this pin." << std::endl; + std::cout << "Continue? (yes/no): "; + std::string response; + std::getline(std::cin, response); + if(response != "yes") { + std::cout << "Aborted." << std::endl; + return 0; + } + } + + std::shared_ptr device = nullptr; + + if(!serial.empty()) { + // Find device by serial + auto devices = icsneo::FindAllDevices(); + for(auto& dev : devices) { + if(dev->getSerial() == serial) { + device = dev; + break; + } + } + + if(!device) { + std::cerr << "Device with serial " << serial << " not found" << std::endl; + return -1; + } + } else { + // Use first available device + auto devices = icsneo::FindAllDevices(); + if(devices.empty()) { + std::cerr << "No devices found" << std::endl; + return -1; + } + device = devices[0]; + } + + std::cout << "Using device: " << device->describe() << std::endl; + + if(!device->open()) { + std::cerr << "Failed to open device" << std::endl; + return -1; + } + + auto settings = device->settings; + if(!settings) { + std::cerr << "Device settings not available" << std::endl; + device->close(); + return -1; + } + + std::cout << "Refreshing device settings..." << std::endl; + if(!settings->refresh()) { + std::cerr << "Failed to refresh settings" << std::endl; + device->close(); + return -1; + } + + // Enable analog output on specified pin + std::cout << "Enabling analog output on pin " << static_cast(pin) << "..." << std::endl; + if(!settings->setMiscIOAnalogOutputEnabled(pin, true)) { + std::cerr << "Failed to enable analog output on pin " << static_cast(pin) << std::endl; + device->close(); + return -1; + } + + // Set pin to specified voltage + std::cout << "Setting pin " << static_cast(pin) << " to " << voltageLevel << "V..." << std::endl; + if(!settings->setMiscIOAnalogOutput(pin, voltage)) { + std::cerr << "Failed to set voltage on pin " << static_cast(pin) << std::endl; + device->close(); + return -1; + } + + // Apply settings + std::cout << "Applying settings..." << std::endl; + if(!settings->apply()) { + std::cerr << "Failed to apply settings" << std::endl; + device->close(); + return -1; + } + + std::cout << "Analog output configured successfully!" << std::endl; + std::cout << "Pin " << static_cast(pin) << ": Enabled at " << voltageLevel << "V" << std::endl; + + device->close(); + return 0; +} diff --git a/examples/python/analog_out/analog_out_basic.py b/examples/python/analog_out/analog_out_basic.py new file mode 100644 index 0000000..f309758 --- /dev/null +++ b/examples/python/analog_out/analog_out_basic.py @@ -0,0 +1,107 @@ +""" +Basic analog output control example using icsneopy library. + +Demonstrates how to configure and control analog outputs on supported devices. + +Usage: python analog_out_basic.py [--yes] + +Arguments: + pin: Pin number (1-3 for RAD Galaxy) + voltage: Voltage level (0-5) + --yes: Skip confirmation prompt +""" + +import sys +import icsneopy + + +def analog_output_example(pin: int, voltage: int, skip_confirm: bool = False): + """Configure and control analog outputs.""" + # Confirmation prompt + if not skip_confirm: + print(f"WARNING: This will set analog output pin {pin} to {voltage}V") + print("Make sure nothing sensitive is connected to this pin.") + response = input("Continue? (yes/no): ") + if response.lower() != "yes": + print("Aborted.") + return + + devices = icsneopy.find_all_devices() + if not devices: + raise RuntimeError("No devices found") + + device = devices[0] + + try: + if not device.open(): + raise RuntimeError("Failed to open device") + + settings = device.settings + if not settings: + raise RuntimeError("Device settings not available") + + print("Refreshing device settings...") + if not settings.refresh(): + raise RuntimeError("Failed to refresh settings") + + # Enable analog output on specified pin + print(f"Enabling analog output on pin {pin}...") + if not settings.set_misc_io_analog_output_enabled(pin, True): + raise RuntimeError(f"Failed to enable analog output on pin {pin}") + + # Map voltage level to enum + voltage_map = { + 0: icsneopy.Settings.MiscIOAnalogVoltage.V0, + 1: icsneopy.Settings.MiscIOAnalogVoltage.V1, + 2: icsneopy.Settings.MiscIOAnalogVoltage.V2, + 3: icsneopy.Settings.MiscIOAnalogVoltage.V3, + 4: icsneopy.Settings.MiscIOAnalogVoltage.V4, + 5: icsneopy.Settings.MiscIOAnalogVoltage.V5 + } + voltage_enum = voltage_map[voltage] + + # Set pin to specified voltage + print(f"Setting pin {pin} to {voltage}V...") + if not settings.set_misc_io_analog_output(pin, voltage_enum): + raise RuntimeError(f"Failed to set voltage on pin {pin}") + + # Apply settings + print("Applying settings...") + if not settings.apply(): + raise RuntimeError("Failed to apply settings") + + print("Analog output configured successfully!") + print(f"Pin {pin}: Enabled at {voltage}V") + + finally: + device.close() + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("Error: Missing required arguments\n") + print("Usage: python analog_out_basic.py [--yes]") + print("\nArguments:") + print(" pin: Pin number (1-3 for RAD Galaxy)") + print(" voltage: Voltage level (0-5)") + print(" --yes: Skip confirmation prompt") + sys.exit(1) + + try: + pin = int(sys.argv[1]) + if pin < 1 or pin > 3: + print("Error: Invalid pin number (must be 1-3)") + sys.exit(1) + + voltage = int(sys.argv[2]) + if voltage < 0 or voltage > 5: + print("Error: Invalid voltage level (must be 0-5)") + sys.exit(1) + + skip_confirm = "--yes" in sys.argv + + analog_output_example(pin, voltage, skip_confirm) + + except ValueError: + print("Error: Pin and voltage must be integers") + sys.exit(1) diff --git a/include/icsneo/device/idevicesettings.h b/include/icsneo/device/idevicesettings.h index 251dca8..2b50fa9 100644 --- a/include/icsneo/device/idevicesettings.h +++ b/include/icsneo/device/idevicesettings.h @@ -747,6 +747,16 @@ typedef struct namespace icsneo { +enum class MiscIOAnalogVoltage : uint8_t +{ + V0 = 0, + V1 = 1, + V2 = 2, + V3 = 3, + V4 = 4, + V5 = 5 +}; + class IDeviceSettings { public: using TerminationGroup = std::vector; @@ -1180,6 +1190,9 @@ public: return false; } + virtual bool setMiscIOAnalogOutputEnabled(uint8_t pin, bool enabled); + virtual bool setMiscIOAnalogOutput(uint8_t pin, MiscIOAnalogVoltage voltage); + const void* getRawStructurePointer() const { return settingsInDeviceRAM.data(); } void* getMutableRawStructurePointer() { return settings.data(); } template const T* getStructurePointer() const { return reinterpret_cast(getRawStructurePointer()); } diff --git a/include/icsneo/device/tree/radgalaxy/radgalaxysettings.h b/include/icsneo/device/tree/radgalaxy/radgalaxysettings.h index 1e43178..7861521 100644 --- a/include/icsneo/device/tree/radgalaxy/radgalaxysettings.h +++ b/include/icsneo/device/tree/radgalaxy/radgalaxysettings.h @@ -185,6 +185,92 @@ public: return nullptr; } } + + bool setMiscIOAnalogOutputEnabled(uint8_t pin, bool enabled) override { + if(!settingsLoaded) { + report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error); + return false; + } + + if(disabled) { + report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error); + return false; + } + + if(readonly) { + report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error); + return false; + } + + if(pin < 1 || pin > 2) { + report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); + return false; + } + + auto cfg = getMutableStructurePointer(); + if(cfg == nullptr) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error); + return false; + } + + const uint16_t bitMask = 1 << (pin - 1); + + if(enabled) { + // Set pin as output and enable analog mode + cfg->misc_io_initial_ddr |= bitMask; + cfg->misc_io_analog_enable |= bitMask; + } else { + // Disable analog mode (leave DDR as-is) + cfg->misc_io_analog_enable &= ~bitMask; + } + + return true; + } + + bool setMiscIOAnalogOutput(uint8_t pin, MiscIOAnalogVoltage voltage) override { + if(!settingsLoaded) { + report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error); + return false; + } + + if(disabled) { + report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error); + return false; + } + + if(readonly) { + report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error); + return false; + } + + if(pin < 1 || pin > 2) { + report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); + return false; + } + + auto cfg = getMutableStructurePointer(); + if(cfg == nullptr) { + report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error); + return false; + } + + const uint8_t dacValue = static_cast(voltage); + + if(dacValue > 5) { + report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); + return false; + } + + if(pin == 1) { + // Update low nibble of high byte (bits 8-11), preserve pin 2 value + cfg->misc_io_initial_latch = (cfg->misc_io_initial_latch & 0xF0FF) | (static_cast(dacValue) << 8); + } else { // pin == 2 + // Update high nibble of high byte (bits 12-15), preserve pin 1 value + cfg->misc_io_initial_latch = (cfg->misc_io_initial_latch & 0x0FFF) | (static_cast(dacValue) << 12); + } + + return true; + } }; }