I want to sample Sound input with either an ESP8266 (preferred) or ESP32. As I'm using a low pass filter anyway, a sample rate of 1000-2000hz would be totally ok. What is more important, is that it is constant. Can I use DMA for that? If so: how? Or should i go via Timer interrupts? I know about the wifi issue on the ESP8266, but I intend to just turn Wifi off as I don't need it for this project.

2 Answers 2


You have several options for controlling the sampling rate.

Software only

This is the simplest, and the previous answer got it almost right. Here is my take:

const SAMPLE_INTERVAL = 500;  // 500 microseconds

void loop() {
    static unsigned long last_sample_time;
    if (micros() - last_sample_time >= SAMPLE_INTERVAL) {  // note 1
         last_sample_time += SAMPLE_INTERVAL;  // note 2
         // Take your sample here.

Note 1: On the AVR-based boards, millis() is not accurate to the millisecond. It tends to get late, then compensate for this by jumping by two milliseconds at once. For controlling a process that should happen every 1 ms exactly, millis() is very clearly not suitable. I do not know how good is the ESP8266's millis(). It may well be better than the AVR version. However, even a perfect millis() will not be good for anything faster than 1 kHz.

The solution to this problem is pretty trivial: just count the time in microseconds instead of milliseconds. The only drawback of using micros() is that you cannot measure durations longer than about 71.6 minutes, which in turn means you cannot use it for sampling at any rate slower than 233 µHz. Obviously this is not something you should be concerned about. Note that the micros() rollover is not an issue as long as you are sampling faster than the rollover period.

Note 2: It is impossible to have a steady sampling rate if it is controlled by software. The best you can hope for is a tight control on the average sampling period. This is accomplished by updating last_sample_time as

last_sample_time += SAMPLE_INTERVAL;

Note that this variable holds the time when the last sample was supposed to be taken, not the time it was actually taken. The sample was probably taken a little bit behind schedule, as anything the CPU does takes time. Updating last_sample_time this way means that these timing errors are not cumulative. They would be cumulative if you did it in the “Blink without delay” style, namely

previousMillis = currentMillis;

The drawback of this software method is that it inherently has a significant amount of jitter. The program tests the current time only once per loop iteration, and it may have other things to do within that loop that adds to its execution time.

Software triggered by interrupts

I have never programmer an ESP timer but, if you can, it should provide you better timings than the software-only version. Have the timer deliver an interrupt at the desired sampling period, and use the ISR for triggering the ADC. You may wait for the result inside the ISR, store it in RAM, and set a flag to tell the main loop that a new sample is available, as in

volatile int analog_sample;
volatile bool analog_sample_valid;

    analog_sample = analogRead(ANALOG_PIN);
    analog_sample_valid = true;

void loop()
    if (analog_sample_valid) {
        int analog_sample_copy = analog_sample;  // note 1
        analog_sample_valid = false;
        // handle analog_sample_copy here

Note 1: On an 8 bit Arduino, you would have to do this with interrupts disabled. You shouldn't need to disable interrupts on a 32 bit platform.

This method should significantly reduce the jitter compared to the software-only version. Now the sampling doesn't have to wait for the program to run through the loop and reach the point when the time is tested. Instead, whatever the program is doing, it will be immediately interrupted for doing the sampling. Well... almost. From time to time it will happen that the interrupt fires when the program is running with interrupts disabled. This may be the case if it is already servicing another interrupt. When this happens, the sampling will have to wait until interrupts are enabled again. You end up with a jitter that is most of the time a small fraction of a microsecond, and then occasionally a sample is late by several microseconds.

Hardware only

On an AVR, you can configure the ADC to be automatically triggered by a timer, or even to automatically trigger itself in the so called “free running mode”. I do not know the specifics of the ADC in the ESP8266. You may want to read the datasheet and see if it offer similar functionality. If you can enable ADC auto-triggering, then you will have cycle-accurate timings. The steadiness of your sampling will be just as good as the quartz crystal clocking your MCU. No software solution can ever come close to this level of perfection. The only drawback of this method is that you will have to study the datasheet of your ESP in order to implement it.


Have you looked "BlinkWithoutDelay" sample on Arduino? The most valuable part of it is the following:

void loop() {

    unsigned long currentMillis = millis();   // NOTE: 1

    if (currentMillis - previousMillis >= interval) {  // NOTE: 2
         // save the last time you blinked the LED
         previousMillis = currentMillis;  //NOTE: 3
         .. do your thing here ..

Take into account the places from NOTE 1 to NOTE 3 I have marked in comments:


First thing in the loop where you mainly go is taking current time in millis. Should not take a lot of effort from Arduino to achieve.


Second thing is a comparison: is there enough time gone to make your thing ( = to take the sample) or not? If it is false, you make another round. If true, make it! Again, no big effort from computer.


Here you reset the timer, BEFORE doing anything else. So, your "sample interval clock" is quite precise, it consists of three little steps inside the computer and it should work in fractions of seconds accuracy (time estimate = (1 / Arduino clock speed) * some machine commands).

The other commands: taking sample, storing or sending it, should happen inside the one millisecond left from these commands. Do make a note, 1000 samples gives you 1ms time and 2000 samples 0.5ms time. This example fits perfectly to the 1000 samples case, as the accuracy of your clock is 1ms. I would go with that, if possible.

The time left, 1ms is quite long as a machine's executing time, but you have to make sure that some other components like a WiFi connection you mentioned, may be harmful for this approach. The other part of code inside the loop cannot take more than 1ms, otherwise it affects to the constant rate. Anything less than 1ms is ok, this algorithm makes no difference between 0.1ms or 0.9ms hard work inside the if, the accuracy of 1-10 machine words always holds.

(I don't know exactly how long command if is, nor how long is function millis(). If you want to be very precise, you may take these in account, but IMHO you can omit their importance in calculations: whether they take some time or not, anyways the execution of these may be quite well taking the same on every round - in constant time!)



  • 1
    and how exactly do you want to get above 1000hz with "millis"? :P this is a highly imprecise solution. Jun 16, 2019 at 21:14
  • This is accurate only for 1000hz, you are right.
    – mico
    Jun 17, 2019 at 5:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.