[#59551] ExternalControl: Add ADC example
diff --git a/tools/external_control_client/README.md b/tools/external_control_client/README.md
index 7817bd8..7ed6864 100644
--- a/tools/external_control_client/README.md
+++ b/tools/external_control_client/README.md
@@ -68,3 +68,23 @@
* <VALUE_WITH_UNIT> is an integer with a time unit, e.g.: '100ms'
* accepted time units are 's', 'ms' and 'us' (for microseconds)
```
+
+### `adc` example
+
+The example application using Renode API can be found in `examples/adc`.
+
+It can be built in the `build` directory from the Renode repository's root directory with:
+```bash
+renode$ mkdir build && cmake -DAPP_NAME=adc -DAPP_SOURCES_DIR=tools/external_control_client/examples/adc -S tools/external_control_client -B build && cmake --build build
+```
+
+After starting the server in Renode, the `adc` application can be used multiple times to set ADC channel 0 value.
+
+The usage is:
+```
+Usage:
+ ./adc <PORT> <MACHINE_NAME> <ADC_NAME> <VALUE_WITH_UNIT>
+ where:
+ * <VALUE_WITH_UNIT> is an unsigned integer with a voltage unit, e.g.: '100mV'
+ * accepted voltage units are 'V', 'mV' and 'uV' (for microvolts)
+```
diff --git a/tools/external_control_client/examples/adc/main.c b/tools/external_control_client/examples/adc/main.c
new file mode 100644
index 0000000..4983210
--- /dev/null
+++ b/tools/external_control_client/examples/adc/main.c
@@ -0,0 +1,191 @@
+//
+// Copyright (c) 2010-2024 Antmicro
+//
+// This file is licensed under MIT License.
+// Full license text is available in 'licenses/MIT.txt' file.
+//
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "renode_api.h"
+
+void exit_with_usage_info(const char *argv0)
+{
+ fprintf(stderr,
+ "Usage:\n"
+ " %s <PORT> <MACHINE_NAME> <ADC_NAME> <VALUE_WITH_UNIT>\n"
+ " where:\n"
+ " * <VALUE_WITH_UNIT> is an unsigned integer with a voltage unit, e.g.: '100mV'\n"
+ " * accepted voltage units are 'V', 'mV' and 'uV' (for microvolts)\n",
+ argv0);
+ exit(EXIT_FAILURE);
+}
+
+char *get_error_message(renode_error_t *error)
+{
+ if (error->message == NULL)
+ {
+ return "<no message>";
+ }
+ return error->message;
+}
+
+int try_renode_disconnect(renode_t **renode)
+{
+ renode_error_t *error;
+ if ((error = renode_disconnect(renode)) != NO_ERROR) {
+ fprintf(stderr, "Disconnecting from Renode failed with: %s\n", get_error_message(error));
+ return -1;
+ }
+ return 0;
+}
+
+char *voltage_string(uint32_t value)
+{
+ static char buffer[32];
+ uint32_t integer, fraction;
+
+ if ((integer = (value / 1000000))) {
+ fraction = value % 1000000;
+ snprintf(buffer, 32, "%u.%06uV", integer, fraction);
+ }
+ else if ((integer = (value / 1000))) {
+ fraction = value % 1000;
+ snprintf(buffer, 32, "%u.%03umV", integer, fraction);
+ }
+ else {
+ snprintf(buffer, 32, "%uuV", value);
+ }
+
+ return buffer;
+}
+
+int main(int argc, char **argv)
+{
+ if (argc != 5) {
+ exit_with_usage_info(argv[0]);
+ }
+ char *machine_name = argv[2];
+ char *adc_name = argv[3];
+
+ char *endptr;
+ // base=0 tries to figure out the number's base automatically.
+ uint64_t value = strtoul(argv[4], &endptr, /* base: */ 0);
+ if (errno != 0) {
+ perror("conversion to uint32_t value");
+ exit(EXIT_FAILURE);
+ }
+
+ if (endptr == argv[4]) {
+ exit_with_usage_info(argv[0]);
+ }
+
+ switch (*endptr) {
+ case 'u':
+ // the unit used with the API
+ break;
+ case 'm':
+ value *= 1000;
+ break;
+ case 'V':
+ value *= 1000000;
+ break;
+ default:
+ exit_with_usage_info(argv[0]);
+ }
+
+ if (value > UINT32_MAX) {
+ fprintf(stderr, "Voltage value too big\n");
+ exit(EXIT_FAILURE);
+ }
+
+ // get Renode, machine and ADC instances
+
+ renode_error_t *error;
+ renode_t *renode;
+ if ((error = renode_connect(argv[1], &renode)) != NO_ERROR) {
+ fprintf(stderr, "Connecting to Renode failed with: %s\n", get_error_message(error));
+ goto fail;
+ }
+
+ renode_machine_t *machine;
+ if ((error = renode_get_machine(renode, machine_name, &machine)) != NO_ERROR) {
+ fprintf(stderr, "Getting '%s' machine object failed with: %s\n", machine_name, get_error_message(error));
+ goto fail_renode;
+ }
+
+ renode_adc_t *adc;
+ if ((error = renode_get_adc(machine, adc_name, &adc)) != NO_ERROR) {
+ fprintf(stderr, "Getting '%s' ADC object failed with: %s\n", adc_name, get_error_message(error));
+ goto fail_machine;
+ }
+
+ // assert that at least one channel exists
+
+ int32_t ch_count;
+ if ((error = renode_get_adc_channel_count(adc, &ch_count)) != NO_ERROR) {
+ fprintf(stderr, "Getting channel count for '%s' failed with: %s\n", adc_name, get_error_message(error));
+ goto fail_adc;
+ }
+
+ if (ch_count < 1) {
+ fprintf(stderr, "Expected at least one ADC channel\n");
+ goto fail_adc;
+ }
+ printf("[INFO] # of channels: %d\n", ch_count);
+
+ // get current value, set the new value and assert that new current value is set
+
+ uint32_t val0;
+ if ((error = renode_get_adc_channel_value(adc, 0, &val0)) != NO_ERROR) {
+ fprintf(stderr, "Getting channel #0 value for '%s' failed with: %s\n", adc_name, get_error_message(error));
+ goto fail_adc;
+ }
+
+ printf("ADC value: %s\n", voltage_string(val0));
+
+ uint32_t val1 = value;
+ if ((error = renode_set_adc_channel_value(adc, 0, val1)) != NO_ERROR) {
+ fprintf(stderr, "Setting channel #0 value for '%s' failed with: %s\n", adc_name, get_error_message(error));
+ goto fail_adc;
+ }
+
+ printf("ADC value set to %s\n", voltage_string(val1));
+
+ uint32_t val2;
+ if ((error = renode_get_adc_channel_value(adc, 0, &val2)) != NO_ERROR) {
+ fprintf(stderr, "Getting channel #0 value for '%s' failed with: %s\n", adc_name, get_error_message(error));
+ goto fail_adc;
+ }
+
+ printf("ADC value: %s\n", voltage_string(val2));
+
+ // clean up
+
+ free(adc);
+ free(machine);
+ if (try_renode_disconnect(&renode)) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (val1 != val2) {
+ fprintf(stderr, "ADC value doesn't match set value\n");
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+
+fail_adc:
+ free(adc);
+fail_machine:
+ free(machine);
+fail_renode:
+ try_renode_disconnect(&renode);
+ free(renode);
+fail:
+ renode_free_error(error);
+ exit(EXIT_FAILURE);
+}