nuttx-apps/examples/comp/comp_main.c
Daniel P. Carvalho 72cf59b647 examples/comp: add comparator example tool
Add a dedicated NSH tool for testing and interacting with analog
comparator devices (/dev/comp*). Supports reading comparator output
state, enabling/disabling via IOCTL, and performing an automated voltage
ramp sweep using a DAC device when available.

Assisted-by: Gemini:gemini-2.5-pro
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
2026-09-15 16:09:24 +08:00

625 lines
16 KiB
C

/****************************************************************************
* apps/examples/comp/comp_main.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <nuttx/analog/ioctl.h>
#ifdef CONFIG_DAC
# include <nuttx/analog/dac.h>
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifndef CONFIG_COMP
# error "COMP device support is not enabled (CONFIG_COMP)"
#endif
#ifndef CONFIG_EXAMPLES_COMP_DEVPATH
# define CONFIG_EXAMPLES_COMP_DEVPATH "/dev/comp0"
#endif
#ifdef CONFIG_DAC
# ifndef CONFIG_EXAMPLES_COMP_DACPATH
# define CONFIG_EXAMPLES_COMP_DACPATH "/dev/dac0"
# endif
#endif
#define DEFAULT_RAMP_START 0
#define DEFAULT_RAMP_END 4095
#define DEFAULT_RAMP_STEP 100
#define DEFAULT_RAMP_DELAY 5
/****************************************************************************
* Private Types
****************************************************************************/
struct comp_state_s
{
FAR const char *devpath;
#ifdef CONFIG_DAC
FAR const char *dacpath;
int ramp_start;
int ramp_end;
int ramp_step;
int ramp_delay;
#endif
};
/****************************************************************************
* Private Data
****************************************************************************/
static struct comp_state_s g_comp_state =
{
CONFIG_EXAMPLES_COMP_DEVPATH
#ifdef CONFIG_DAC
,
CONFIG_EXAMPLES_COMP_DACPATH,
DEFAULT_RAMP_START,
DEFAULT_RAMP_END,
DEFAULT_RAMP_STEP,
DEFAULT_RAMP_DELAY
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: comp_help
****************************************************************************/
static void comp_help(FAR const char *progname)
{
printf("Usage: %s [OPTIONS] [COMMAND]\n", progname);
printf("\nCommands:\n");
printf(" enable Enable the comparator\n");
printf(" disable Disable the comparator\n");
printf(" read Read comparator output state (0 or 1)\n");
printf(" test Run test sequence (ioctls, enable/disable)\n");
#ifdef CONFIG_DAC
printf(" ramp Generate DAC voltage ramp to test trip\n");
#endif
printf(" help Show this help message\n");
printf("\nOptions:\n");
printf(" -p <devpath> Comparator device path (default: %s)\n",
CONFIG_EXAMPLES_COMP_DEVPATH);
#ifdef CONFIG_DAC
printf(" -D <dacpath> DAC device path (default: %s)\n",
CONFIG_EXAMPLES_COMP_DACPATH);
printf(" -s <start> Ramp start value (default: %d)\n",
DEFAULT_RAMP_START);
printf(" -f <end> Ramp end value (default: %d)\n",
DEFAULT_RAMP_END);
printf(" -i <step> Ramp step increment (default: %d)\n",
DEFAULT_RAMP_STEP);
printf(" -w <delay> Delay per step in ms (default: %d)\n",
DEFAULT_RAMP_DELAY);
#endif
printf(" -e Same as 'enable'\n");
printf(" -d Same as 'disable'\n");
printf(" -r Same as 'read'\n");
printf(" -t Same as 'test'\n");
printf(" -h Show this help message\n");
}
/****************************************************************************
* Name: comp_do_enable
****************************************************************************/
static int comp_do_enable(FAR const char *devpath)
{
int errcode;
int fd;
int ret;
fd = open(devpath, O_RDONLY);
if (fd < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Failed to open %s: %d\n", devpath, errcode);
return -errcode;
}
ret = ioctl(fd, ANIOC_COMP_ENABLE, 0);
if (ret < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: ioctl(ANIOC_COMP_ENABLE) failed: %d\n",
errcode);
close(fd);
return -errcode;
}
printf("%s: Comparator enabled successfully\n", devpath);
close(fd);
return OK;
}
/****************************************************************************
* Name: comp_do_disable
****************************************************************************/
static int comp_do_disable(FAR const char *devpath)
{
int errcode;
int fd;
int ret;
fd = open(devpath, O_RDONLY);
if (fd < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Failed to open %s: %d\n", devpath, errcode);
return -errcode;
}
ret = ioctl(fd, ANIOC_COMP_DISABLE, 0);
if (ret < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: ioctl(ANIOC_COMP_DISABLE) failed: %d\n",
errcode);
close(fd);
return -errcode;
}
printf("%s: Comparator disabled successfully\n", devpath);
close(fd);
return OK;
}
/****************************************************************************
* Name: comp_do_read
****************************************************************************/
static int comp_do_read(FAR const char *devpath, FAR uint8_t *val)
{
int errcode;
int fd;
ssize_t nbytes;
uint8_t output = 0;
fd = open(devpath, O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Failed to open %s: %d\n", devpath, errcode);
return -errcode;
}
nbytes = read(fd, &output, 1);
if (nbytes < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: read from %s failed: %d\n", devpath, errcode);
close(fd);
return -errcode;
}
close(fd);
if (val != NULL)
{
*val = output;
}
return OK;
}
/****************************************************************************
* Name: comp_do_test
****************************************************************************/
static int comp_do_test(FAR const char *devpath)
{
int errcode;
int fd;
int ret;
uint8_t val;
ssize_t nbytes;
printf("Running comparator test on %s...\n", devpath);
fd = open(devpath, O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Failed to open %s: %d\n", devpath, errcode);
return -errcode;
}
/* 1. Read initial state */
nbytes = read(fd, &val, 1);
if (nbytes < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Initial read failed: %d\n", errcode);
close(fd);
return -errcode;
}
printf(" 1. Initial output state: %u\n", (unsigned int)val);
/* 2. Test ANIOC_COMP_DISABLE */
ret = ioctl(fd, ANIOC_COMP_DISABLE, 0);
if (ret < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: ioctl(ANIOC_COMP_DISABLE) failed: %d\n",
errcode);
close(fd);
return -errcode;
}
printf(" 2. ANIOC_COMP_DISABLE: OK\n");
/* 3. Read state while disabled */
nbytes = read(fd, &val, 1);
if (nbytes < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Read after disable failed: %d\n", errcode);
close(fd);
return -errcode;
}
printf(" 3. Output state when disabled: %u\n", (unsigned int)val);
/* 4. Test ANIOC_COMP_ENABLE */
ret = ioctl(fd, ANIOC_COMP_ENABLE, 0);
if (ret < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: ioctl(ANIOC_COMP_ENABLE) failed: %d\n",
errcode);
close(fd);
return -errcode;
}
printf(" 4. ANIOC_COMP_ENABLE: OK\n");
/* 5. Read state after re-enable */
nbytes = read(fd, &val, 1);
if (nbytes < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Read after enable failed: %d\n", errcode);
close(fd);
return -errcode;
}
printf(" 5. Output state when enabled: %u\n", (unsigned int)val);
close(fd);
printf("Comparator test PASSED on %s\n", devpath);
return OK;
}
#ifdef CONFIG_DAC
/****************************************************************************
* Name: comp_do_ramp
*
* Description:
* Generate a DAC voltage ramp and monitor comparator output transitions.
* This performs a real analog threshold comparison on hardware.
*
****************************************************************************/
static int comp_do_ramp(FAR const char *comp_path,
FAR const char *dac_path,
int start, int end, int step, int delay_ms)
{
int comp_fd;
int dac_fd;
int errcode;
int ret;
int val;
int transitions = 0;
int prev_state = -1;
uint8_t cur_state = 0;
struct dac_msg_s msg;
ssize_t nbytes;
printf("=== Comparator Ramp Test ===\n");
printf(" COMP device : %s\n", comp_path);
printf(" DAC device : %s\n", dac_path);
printf(" Sweep range : %d -> %d (step %d, delay %d ms)\n\n",
start, end, step, delay_ms);
/* Open comparator device */
comp_fd = open(comp_path, O_RDONLY | O_NONBLOCK);
if (comp_fd < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Failed to open %s: %d\n", comp_path, errcode);
return -errcode;
}
/* Ensure comparator is enabled */
ret = ioctl(comp_fd, ANIOC_COMP_ENABLE, 0);
if (ret < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Failed to enable comparator: %d\n", errcode);
close(comp_fd);
return -errcode;
}
/* Open DAC device */
dac_fd = open(dac_path, O_WRONLY | O_NONBLOCK);
if (dac_fd < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: Failed to open DAC %s: %d\n",
dac_path, errcode);
close(comp_fd);
return -errcode;
}
/* Sweep DAC values */
for (val = start; val <= end; val += step)
{
uint32_t mv;
msg.am_channel = 0;
msg.am_data = val;
nbytes = write(dac_fd, &msg, sizeof(msg));
if (nbytes < 0 && errno != EAGAIN)
{
errcode = errno;
fprintf(stderr, "ERROR: DAC write failed: %d\n", errcode);
break;
}
usleep(delay_ms * 1000);
nbytes = read(comp_fd, &cur_state, 1);
if (nbytes < 0)
{
errcode = errno;
fprintf(stderr, "ERROR: COMP read failed: %d\n", errcode);
break;
}
mv = (uint32_t)((val * 3300ULL) / 4095ULL);
if (prev_state >= 0 && (int)cur_state != prev_state)
{
transitions++;
printf(" -> [TRIP EVENT #%d] DAC=%4d (~%4lu mV) | "
"COMP output: %d -> %d\n",
transitions, val, (unsigned long)mv, prev_state, cur_state);
}
else if (val == start || val + step > end || (val % (step * 5)) == 0)
{
printf(" DAC=%4d (~%4lu mV) | COMP output: %d\n",
val, (unsigned long)mv, (int)cur_state);
}
prev_state = (int)cur_state;
}
close(dac_fd);
close(comp_fd);
printf("\n=== Test Summary ===\n");
if (transitions > 0)
{
printf("SUCCESS: Detected %d comparator transition(s)!\n",
transitions);
ret = OK;
}
else
{
printf("COMPLETED: Output stayed at %d across entire sweep range.\n",
prev_state);
printf("Note: Ensure INP pin is biased within 0..3.3V.\n");
ret = OK;
}
return ret;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: main
****************************************************************************/
int main(int argc, FAR char *argv[])
{
int opt;
int ret = OK;
uint8_t val = 0;
FAR const char *cmd = NULL;
g_comp_state.devpath = CONFIG_EXAMPLES_COMP_DEVPATH;
#ifdef CONFIG_DAC
g_comp_state.dacpath = CONFIG_EXAMPLES_COMP_DACPATH;
g_comp_state.ramp_start = DEFAULT_RAMP_START;
g_comp_state.ramp_end = DEFAULT_RAMP_END;
g_comp_state.ramp_step = DEFAULT_RAMP_STEP;
g_comp_state.ramp_delay = DEFAULT_RAMP_DELAY;
#endif
if (argc > 1 && argv[1][0] != '-')
{
cmd = argv[1];
argc--;
argv++;
}
#ifdef CONFIG_DAC
while ((opt = getopt(argc, argv, "p:D:s:f:i:w:edrth")) != -1)
#else
while ((opt = getopt(argc, argv, "p:edrth")) != -1)
#endif
{
switch (opt)
{
case 'p':
g_comp_state.devpath = optarg;
break;
#ifdef CONFIG_DAC
case 'D':
g_comp_state.dacpath = optarg;
break;
case 's':
g_comp_state.ramp_start = atoi(optarg);
break;
case 'f':
g_comp_state.ramp_end = atoi(optarg);
break;
case 'i':
g_comp_state.ramp_step = atoi(optarg);
break;
case 'w':
g_comp_state.ramp_delay = atoi(optarg);
break;
#endif
case 'e':
cmd = "enable";
break;
case 'd':
cmd = "disable";
break;
case 'r':
cmd = "read";
break;
case 't':
cmd = "test";
break;
case 'h':
comp_help(argv[0]);
return OK;
default:
comp_help(argv[0]);
return -EINVAL;
}
}
if (cmd == NULL && optind < argc)
{
cmd = argv[optind];
}
if (cmd == NULL || strcmp(cmd, "test") == 0)
{
ret = comp_do_test(g_comp_state.devpath);
#ifdef CONFIG_DAC
if (ret == OK)
{
printf("\n");
ret = comp_do_ramp(g_comp_state.devpath,
g_comp_state.dacpath,
g_comp_state.ramp_start,
g_comp_state.ramp_end,
g_comp_state.ramp_step,
g_comp_state.ramp_delay);
}
#endif
}
#ifdef CONFIG_DAC
else if (strcmp(cmd, "ramp") == 0)
{
ret = comp_do_ramp(g_comp_state.devpath,
g_comp_state.dacpath,
g_comp_state.ramp_start,
g_comp_state.ramp_end,
g_comp_state.ramp_step,
g_comp_state.ramp_delay);
}
#endif
else if (strcmp(cmd, "enable") == 0)
{
ret = comp_do_enable(g_comp_state.devpath);
}
else if (strcmp(cmd, "disable") == 0)
{
ret = comp_do_disable(g_comp_state.devpath);
}
else if (strcmp(cmd, "read") == 0)
{
ret = comp_do_read(g_comp_state.devpath, &val);
if (ret == OK)
{
printf("%s: output = %u\n", g_comp_state.devpath,
(unsigned int)val);
}
}
else if (strcmp(cmd, "help") == 0)
{
comp_help(argv[0]);
ret = OK;
}
else
{
fprintf(stderr, "Unknown command: %s\n", cmd);
comp_help(argv[0]);
ret = -EINVAL;
}
return ret;
}