Interfacing Embedded Sensors: Polling ADC Drivers

How embedded development engineers can create an interface in modern embedded applications that separates low-level driver implementation details from application code. This interface provides an architectural abstraction that improves the scalability and portability of application code by reducing the dependence on hardware.

We will now start looking at a few different ways embedded development engineers can implement ADC drivers, based on the techniques discussed in 3 Microcontroller Driver Design Techniques. In this article, we will examine in more detail how to use polling techniques and discuss the differences between blocking and non-blocking drivers.

To block or not to block, that is the question

When developing any driver for a microcontroller, embedded development engineers must decide whether their driver will be blocking or non-blocking. A blocking driver essentially suspends code execution until the driver completes its task. For example, a typical implementation of printf mapped to a UART is blocking.

When you make a call, for example:

printf(“ Hello World!”);

Embedded development engineers know that none of the lines of code after that statement will be executed until the whole "Hello World!" statement has been printed out of the UART. "Hello, World!" contains 12 bytes (96 bits), but how long the statement will block depends on the UART baud rate. For a UART configured for 1 Mbps, you'd expect about 96 microseconds. For a UART configured for 9600 bps, you'd expect about 10,000 microseconds! This varies greatly depending on the configuration of the hardware, and in cases where the UART driver is configured as a blocking driver, it can greatly affect program execution.

A non-blocking driver is one that does not halt program execution while the driver completes its task. For example, the printf and UART drivers in the previous example can be configured so that instead of blocking, the application is allowed to continue executing while each byte is transferred out of the UART. In the right circumstances, this can make the application more efficient, but requires additional setup, such as using interrupts, DMA, or at least a send buffer.

Deciding how to design your driver depends on your application and hardware. For example, if the UART is configured for 1 Mbps, writing a non-blocking driver may not provide much benefit from an efficiency standpoint, and may actually cause more problems than by increasing program complexity solve. However, if the application requires 9600 bps, and the application code is blocked for 10 ms, using a non-blocking driver can significantly improve program efficiency, and the risk of other timing complexity issues is much lower and more manageable.

Embedded ADC Driver Overview

It's important to note that I can't go through all the steps needed to write a complete ADC driver in one blog. I could easily write a 20 page paper on it, or give an entire webinar, probably not all the details, but we can at least take a look at some of the core stuff.

There are several ways we can organize ADC drivers, but the way I like to organize them requires three components:

low level driver

application code

configuration module

The low-level driver gets the configuration module during initialization and sets up the hardware according to the configuration. Low-level drivers provide a common hardware abstraction layer (HAL) that application code can then use. ADC HAL calls should be generic so that high-level applications can configure the hardware in any necessary way, making it reusable and scalable. For example, some of the ADC HAL calls I've used in the past include:

AdcError_t Adc_Init(const AdcConfig_t * Config);

AdcError_t Adc_StartConversion(void);

bool Adc_ConversionComplete(void);

void Adc_RegisterWrite(uint32_t const Address,uint32_t const Value);

uint32_t Adc_RegisterRead(uint32_t address);

void Adc_CallbackRegister(AdcCallback_t const Function,TYPE(* CallbackFunction)(type));

The first three APIs provide the following functions: initialize ADC hardware, start conversion, and check conversion status. The last three functions are designed to allow extensions to low-level hardware. For example, if the HAL does not provide options required by the application, such as converting individual ADC channels, the HAL can be extended with the Adc_RegisterRead and Adc_RegisterWrite functions. This provides flexibility based on application needs without creating overwhelming APIs.

Write a simple blocking ADC driver

We can write a very simple ADC driver on top of the hardware layer. For example, we can create a simple function called Adc_Sample that starts the ADC hardware and then stores all the results in a buffer which can then be accessed by the application. A buffer storing an analog value count value does not have to store just one value, but can store multiple values ​​which can later be averaged or filtered according to the needs of the application. A blocking version of the sample function might look like the following:

As you can see in this code, the while loop blocks execution until the ADC hardware completes its conversion and then stores the value in the application buffer.

Write a simple non-blocking ADC driver

Converting a blocking driver to non-blocking code is straightforward, but requires changes to higher-level application code. For example, today, if an application wants to sample a sensor, an embedded developer will call:

Adc_Sample();

In the non-blocking version, the embedded developer must check the return value of Adc_Sample to see if the sample is complete and ready to use. This allows the example to run in the background and the application code to continue running with the following update to our driver code:

in conclusion

As we have seen in this post, there are multiple ways of writing an ADC, and depending on our needs, the implementation can be blocking or non-blocking. Blocking drivers tend to be simpler and less complete than non-blocking drivers, but they can be less efficient. Non-blocking drivers allow other code to run while the driver is running, but application code still needs to check in state which itself would be inefficient in a polling implementation.

Embedded Internet of Things needs to learn a lot. Don't learn the wrong route and content, which will cause your salary to go up!

Share a data package with everyone, about 150 G. The learning content, face-to-face scriptures, and projects in it are relatively new and complete! (Click to find a small assistant to receive)

Guess you like

Origin blog.csdn.net/m0_70911440/article/details/132160964