Skip to content

RFC: Expand ADC API to support batch_mode samplingย #55783

@heinwessels

Description

@heinwessels

Introduction

This RFC entails expanding the ADC API with support for batch_sampling sampling. This will allow a driver to, for example, sample an ADC continiously using a DMA and a circular buffer without ever triggering an interrupt a large amount of samples continiously using extra_samples and then only generate a single interrupt. This allows a driver to optionally offload more processing to the silicon if possible, f.i. using a DMA.

Initially mentioned in #39640. I'm creating a new issue as that discussion seems to be more driver specific and includes DMA changes. This RFC is purely for the ADC API.

Problem description

In time constraint scenarios where the MCU needs to take high frequency ADC readings it's benificial to offload as much processing to the silicon as possible, instead of handling the data through code and interrupts.

The ideal solution is to allow the ADC to sample continously, and the DMA continuously places new samples into a circular buffer, without the need of interrupts or polling. The sampling is completely handled by the silicon. This will be hard to implement with the current infrastructure, but a balance can be found by allow the DMA to sample 1 + extra_samples samples continuously, all with a DMA if possible, and only raise a single callback after it's done.

In our use-case we have a STM32H7 that needs to sample 16 channels at 100Hz on two ADCs. The addition of sampling multiple channels (#53534) along with DMA support (#52965) already improves this. But it's still interrupt based and creates way too many interrupts, about 200 per second, which is causing many stability issues. This proposed change would allow us to expand the ADC driver to remove all these interrupts, except one, without reducing the amount of samples.

This additional will cause minimal change to existing drivers.

Proposed change

Expand the existing ADC API to support continous sampling. The actual implementation will be driver dependent and is not part of this RFC.

Detailed RFC

Proposed change (Detailed)


Edit: Updated proposed solution as mentioned in #55783 (comment).

/**
 * Sets ADC reading in batch_mode mode
 * The driver will complete reading of all channels and sample required extra_samples
 * in one shot, and only afterwards call the callback.
 * The driver will attempt to offload as processing to the silicon as possible.
 * For example, if coupled with DMA, this entire read sequence can be completed
 * without any interrupts, meaning context changes, except for when it's completed.
 */
 bool batch_mode;

This can then be called in sync or async mode as the normal read, and will act accordingly. The adc_context will have to modified slightly to not call callbacks until the all samples has been taken if this option is enabled. The actual implementation of this is ofloaded to the driver, and can function without DMA to remain portable.

When implemented in STM32 with DMA: The DMA will not be set in circular buffer mode, but rather have a buffer of length 1 + extra_samples, and attempt to fill it. It will only raise an ISR when this buffer is full, which will then call adc_context, which will raise the callback.

Alternative naming suggestion of bool one_shot, but I prefer continuous.

Pros:

  • The buffer will be safe to use after a read until the user explicitly starts a new read.
  • Devices with different ADCs of varying capability can handle each case correctly internally.
  • Portable, as the driver can choose to implement it in any way.
  • Existing drivers will still function normally, as the main change is made in adc_context. A driver can optionally add additional functionality to offload more processing to the silicon.

Cons:


First, the API needs a way to start a continuous read. My first thought was to expand adc_sequence_options with a bool continuous variable. However, this might not be a good solution, because then it can theorically be used with both adc_read and adc_read_async, which would not make sense. Continuous mode would always be async. It will also mean the bool can be set on a driver which does not implement it yet, without letting the user know.

A better solution would be to a dedicated function:

/**
 * @brief Start a continuous ADC read.
 *
 * @note This function is available only if @kconfig{CONFIG_ADC_CONTINUOUS}
 * is selected.
 *
 * @note This function does not support callbacks, or a given interval.
 *
 * @param dev       Pointer to the device structure for the driver instance.
 * @param sequence  Structure specifying requested sequence of samplings.
 *
 * @returns 0 on success, negative error code otherwise.
 *          See adc_read() for a list of possible error codes.
 *
 */
__syscall int adc_read_continuous(const struct device *dev,
			     const struct adc_sequence *sequence);


#ifdef CONFIG_ADC_CONTINUOUS
static inline int z_impl_adc_read_continuous(const struct device *dev,
					const struct adc_sequence *sequence)
{
	const struct adc_driver_api *api =
				(const struct adc_driver_api *)dev->api;

	return api->read_continuous(dev, sequence);
}
#endif /* CONFIG_ADC_CONTINUOUS */

This is better because:

  • It which will follow the convention used by existing and similar adc_read_async.
  • This means that if the function is called on a driver which it's not implemented on yet it will cause a linker error, instead of undefined behaviour.
  • The execution of a continiuous read will be vary significantly from adc_read and adc_read_async, because it won't need adc_context, which means a seperate function will be more intuitive.

This means a new kconfig value will be introduced, something this is adc/kconfig:

config ADC_CONTINUOUS
	bool "Continuous sampling support"
	help
	  This option enables the continuous ADC sampling.

The driver would also need some way to stop the continuous sampling.

__syscall int adc_stop_continuous_read(const struct device *dev);


#ifdef CONFIG_ADC_CONTINUOUS
static inline int z_impl_adc_stop_continuous_read(const struct device *dev)
{
	const struct adc_driver_api *api =
				(const struct adc_driver_api *)dev->api;

	return api->stop_continuous_read(dev);
}
#endif /* CONFIG_ADC_CONTINUOUS */

The circular buffer size can be set to hold 1 + extra_samples samples. It's up to the caller to provide this buffer through the existing adc_sequence.buffer.

The adc_context does not have to be changed, because it will not be used. It's main purpose is managing callbacks and the order of samples. This RFC does not use callbacks, and the sampling is explicitly handled by the silicon.

Dependencies

This change will not affect existing drivers. Drivers will need to add explicit support for it to function.

Concerns and Unresolved Questions

  • Should CONFIG_ADC_CONTINUOUS depend on CONFIG_DMA? Are there boards where DMA is not required for continuous sampling? Ideally this should be board specific, but I don't see how a board specific kconfig value could force CONFIG_ADC_CONTINUOUS=n if CONFIG_DMA=n for example.
  • The adc_stop_continuous_read could be be made more generic, as in adc_stop_read and possibly allow stopping an ongoing adc_read_async. However, I do not think this is the right direction.
  • In STM32: Add DMA+ADC supportย #39640 they wanted to add a callback on DMA buffer half-full, and this RFC currently doesn't include supporting callbacks. In our opinion it would defeat the purpose of ofloading the ADC sampling to the silicon to prevent unnecessary interrupts. And adding the ability for optional callbacks would bring in unnecessary complexity, and the user might use it without realizing the effect it might have on performance.
  • While the user is reading the circular buffer then interrupts should be disabled using irq_lock() to ensure the data remains unchanged while being accessed. This may lead to unexpected behaviour when not used correctly. IMO we can leave this caveat as-is, however I would love to hear if there is a better way to do it.

Alternatives

No alternatives were considered.

Metadata

Metadata

Assignees

Labels

RFCRequest For Comments: want input from the communityarea: ADCAnalog-to-Digital Converter (ADC)

Type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions