Hi everyone,
I was thinking about how to implement the software for my project
(control module for a Formula SAE car) and realized that the basic ADC
driver model is a bit inconvenient and creates a lot of extra overhead
in userspace threads when implementing advanced features. Some
features I think would make sense in an abstract “channels” driver
model that would benefit many controls applications:
- Only wake up a thread when a value has changed "significantly."
- Support channels generated from bilinear/bicubic interpolation using a table.
- Share the same ADC values among multiple threads with different
priorities, like a high-priority safing algorithm, a medium priority
control algorithm, and a low-priority CAN broadcast thread.
- Share the same ADC values among threads with different needs—the
three listed above only care about the latest value, while a
datalogging thread that saves to flash memory would want to save data
in chunks.
- Implement software filters and allow binding to userspace control
algorithms for Kalman filtering.
- Allow different threads to subscribe to epoll events at different rates.
- Allow data received via an offboard, runtime-configurable serial bus
like CAN or UART to be assigned a meaningful name and seamlessly used
for userspace control functions just like ADC measurements, with a
userspace helper thread de-serializing the messages as they arrive.
- Allow the application to wait for two or more channels to be consistent.
- Avoid unnecessary copies and support ADC DMA transfers.
~~~~~~~~
The model I have in mind is something like this:
- Channels are char-devices; e.g. /dev/channels/throttle-position-1
- A userspace application that only cares about the latest measurement
would do something like this to get a pointer that always points to
the latest channel value and avoid the overhead of read():
uint32_t *throttle_position_1;
int tps1_fd;
tps1_fd = open("/dev/channels/throttle-position-1");
ioctl(tps1_fd, CHANIOC_SUBSCRIBE, (unsigned long)&throttle_position_1);
ioctl(tps1_fd, CHANIOC_SET_FILTER, ...);
poll(...);
// Do some calculations with *throttle_position_1
// Drive some output
- A periodic userspace task that works with bulk data would read()
from the ring buffer, or get a pointer to the ring buffer with an
ioctl (not sure which would be better)
- New data is copied to the ring buffer in the low- or high-priority work queue.
- Boardctl or board-init logic is responsible for setting up special
ADC triggering (e.g. from a PWM module) and telling the ADC backend
driver to register itself as a channel. DMA can be set up so that ADC
measurements are directly copied to the "latest value" location if no
filtering or conversion is needed.
- Software-generated channels are created with a syscall; the
userspace thread can write to the resulting char device and the data
will be stored in the ring buffer and wake up any threads waiting on
the software-defined channel.
~~~~
Looking for prior art, I noticed that there are the Common Sensor
Register Interace and Sensor Cluster Driver Interface
(drivers/sensors/README.txt). However, these don't cover ADCs or
address the overhead of having a multiple-input control thread needing
to use both epoll() and read(). Nor do they support filtering or
multiple userspace threads accessing the same sensor in a convenient
way.
However, something like this might already exist, just not in mainline
NuttX. If you know of such a thing or have feedback on this idea,
please let me know. Otherwise, I'll move ahead with working on it.
Best regards,
Matt