ESP-IDF: ESP32 GPIO – Read Analog Input (ADC – Analog to Digital Converter)

In this guide, you’ll learn how to set and read the ESP32 board’s GPIO pins as ADC (Analog to Digital Converter) with ESP-IDF (Espressif IoT Development Framework).

The ESP32 is a microcontroller that offers several General Purpose Input/Output (GPIO) pins that can be configured as either inputs or outputs. With the GPIOs set as analog inputs, you can read the analog voltage of devices like potentiometers, sensors, or other peripherals by reading the pin’s voltage from 0 to 3.3V (any value in between).

ESP-IDF ESP32 GPIO Read Analog Input ADC Analog to Digital Converter

Prerequisites

Before following this guide, you need to install the ESP-IDF extension on VS Code IDE (Microsoft Visual Studio Code). Follow the next guide to install it, if you haven’t already:

You will also need an ESP32 development board model of your choice.

ESP32 ADC (Analog to Digital Converter) Modes

In ESP-IDF, you can use the ESP32 GPIOs as ADC (Analog to Digital Converter) in three different modes: One-Shot mode, Continuous Mode or Calibration Mode. In the following table, we break down the different ADC Modes and their corresponding features.

ADC ModeOne-Shot ModeContinuous ModeReads the ADC continuously at high speed
How It WorksReturns a single readingReads the ADC contiguously at high speedCalibrates the pins for higher accuracy
ApplicationOccasional measurements (battery level check, basic sensor reading, etc…)Real-time fast analog readings (audio sampling)Accurate voltage measurements (fix sensor readings)
ImplementationEasy to implementSlightly more complex to implement, it requires DMA configurationModerate to implement
ADC GPIOsCompatible with ADC1 and ADC2Compatible only with ADC1Compatible with ADC1 and ADC2

ESP32 ADC Range

Reading an analog value with the ESP32 means you can measure varying voltage levels between 0V and 3.3V.

The voltage measured is then assigned to a value between 0 and 4095 (using a 12-bit resolution), in which 0V corresponds to 0, and 3.3V corresponds to 4095. Any voltage between 0V and 3.3V will be given the corresponding value in between.

ESP32 ADC Range 12 bit Resolution

ESP32 ADC is Non-linear

Ideally, you would expect a linear behavior when using the ESP32 ADC pins. However, that doesn’t happen. What you’ll get is a behavior as shown in the following chart:

This behavior means that your ESP32 is not able to distinguish 3.3V from 3.2V. You’ll get the same value for both voltages: 4095 (if you are using a 12-bit resolution). The same happens for very low voltage values: for 0V and 0.1V you’ll get the same value: 0. You need to keep this in mind when using the ESP32 ADC pins.

ESP32 ADC GPIOs

The ESP32 supports measurements in 18 different channels. Only 15 are available in the DEVKIT V1 DOIT board (version with 30 GPIOs). Grab your ESP32 board pinout and locate the ADC pins. These are highlighted with a red border in the figure below.

ESP32 ADC GPIOs Pins

Learn more about the ESP32 GPIOs: ESP32 Pinout Reference.

In case of the ESP32-S3 boards, the ADC GPIOs are highlighted in purple color and labelled as ADCX_X.

Learn more about the ESP32-S3 GPIOs: ESP32-S3 GPIO Reference Guide.

Note: ADC2 pins cannot be used when Wi-Fi is used. So, if you’re using Wi-Fi and you’re having trouble getting the value from an ADC2 GPIO, you may consider using an ADC1 GPIO instead, that should solve your problem.

Creating an ESP-IDF Template App Project for the ESP32

The ESP-IDF extension provides an easy way to create a project from scratch with all the required files and configurations generated automatically.

To create a new ESP-IDF project on VS Code, follow these steps:

  1. Open the ESP-IDF Espressif extension
  2. Expand the “Advanced” menu
  3. Click the “New Project Wizard” option
  4. Choose the “Use ESP-IDF v5.4.1” to select the framework version
ESP-IDF ESP32 Create Open New Project Wizard Menu

A new window opens, you need to fill in these fields:

  • Project Name: type the desired project name;
  • Enter Project Directory: click the folder icon and select the target folder to save all your project files. You can use any directory. Note: do NOT use a Google Drive / One Drive / Dropbox folder, because it will write/create many files during the building process—if it’s on a cloud folder, this process might be extremely slow;
  • ESP-IDF Target: select the target device chip, I’m using an ESP32 with the esp32s3 chip;
  • ESP-IDF Board: for the esp32s3 chip, I also need to select the configuration: ESP32-S chip (via builtin USB-JTAG);
  • Serial Port: while having your ESP32 board connected to your computer, select the correct COM port number that refers to your ESP32;
  • Choose Template: click the blue button to create a new project using a template.
ESP-IDF ESP32 Create Open New Project Wizard Menu Select Directory Board Template

In the menu, select the “template-app” sample project and press the “Create project using template template-app” button.

ESP-IDF ESP32 Create New Project Select Template App Option

Opening the ESP-IDF Project on VS Code

After a few seconds, a small notification will appear at the bottom right corner of VS Code. You can click “Yes” to open the newly created ESP-IDF project template.

ESP-IDF ESP32 Open Project in a New Window VS Code

IMPORTANT: if you didn’t see the notification that allows you to automatically open the ESP-IDF project on VS Code, you can easily do it by following these instructions:

Go to File > Open Folder…

ESP-IDF ESP32 Open Project Folder VS Code File Menu

Browse on your computer for the esp-idf-project folder (your project folder name that you’ve previously defined) and “Select Folder“.

ESP-IDF ESP32 Open Project VS Code Select Folder

That’s it! Your new ESP-IDF project template has been successfully created and opened.

ESP-IDF generates many files, folders, and subfolders for your project. For this guide, I recommend keeping all the default files unchanged; we will only modify the main.c file.

The example codes will be written in the main.c file. To open it, follow these instructions:

  1. Open the project explorer by clicking the first icon on the left sidebar.
  2. Select your project folder name, in my case it’s “ESP-IDF-PROJECT“.
  3. Expand the “main” folder.
  4. Click the “main.c” file.
  5. The default main.c template file loads in the code window.
ESP-IDF ESP32 Open Project in a VS Code Browse to Main C File

Example #1: Read ESP32 Analog Inputs with ESP-IDF – One-Shot Mode

Copy the following code to the main.c file. This code takes a reading of a potentiometer connected to ESP32 ADC1 on Channel 7 using the adc_oneshot_read() function:

/*  
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp-idf-esp32-gpio-analog-adc/
*/

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_adc/adc_oneshot.h>
#include <esp_log.h>
#include "sdkconfig.h"

#define ADC_PIN       ADC_CHANNEL_7     // Channel 7 - Check ESP32 Pinout for the GPIO Number
#define ADC_UNIT      ADC_UNIT_1        // ADC1
#define ADC_BITWIDTH  ADC_BITWIDTH_12   // 12-bit resolution (0-4095)
#define ADC_ATTEN     ADC_ATTEN_DB_12   // ~3.3V full-scale voltage

void app_main(void)
{
    int adc_value;
    adc_oneshot_unit_handle_t adc_handle;

    // Initialize ADC Oneshot Mode Driver on the ADC Unit
    adc_oneshot_unit_init_cfg_t init_config = {
        .unit_id = ADC_UNIT,
        .clk_src = ADC_RTC_CLK_SRC_DEFAULT,
    };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle));

    // Configure ADC channel
    adc_oneshot_chan_cfg_t config = {
        .bitwidth = ADC_BITWIDTH,
        .atten = ADC_ATTEN,
    };
    ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_PIN, &config));

    // ADC Oneshot Analog Read loop
    while (1) {
        // Read ADC value with Oneshot
        ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_PIN, &adc_value));
        // Print ADC value
        ESP_LOGI("ADC Value", "%d", adc_value);
        // Delay 1 second
        vTaskDelay(1000 / portTICK_PERIOD_MS); 
    }
}

View raw code

How the Code Works

In this section, we’ll take a look at the code to see how it works.

Libraries

We start by including the required libraries:

  • FreeRTOS.h – provides the core FreeRTOS types and functions;
  • task.h – allows to use of the non-blocking delay function vTaskDelay;
  • esp_adc/adc_oneshot.h – includes the functions required to configure the ESP32’s ADC in One-Shot mode to take analog readings from specific channels;
  • esp_log.h – offers a framework to format log messages in the serial monitor for debugging;
  • sdkconfig.h – includes the project’s configuration file.
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_adc/adc_oneshot.h>
#include <esp_log.h>
#include "sdkconfig.h"

Definitions

These lines define the ADC channel, ADC unit, set the 12-bit resolution (values between 0-4095) and set the attenuation.

#define ADC_PIN       ADC_CHANNEL_7    // Channel 7 - Check ESP32 Pinout for the GPIO Number
#define ADC_UNIT      ADC_UNIT_1        // ADC1
#define ADC_BITWIDTH  ADC_BITWIDTH_12   // 12-bit resolution (0-4095)
#define ADC_ATTEN     ADC_ATTEN_DB_12    // ~3.3V full-scale voltage

app_main(void)

When creating an ESP-IDF project, this function will always be called to run. This function is where you need to write your code for any ESP-IDF applications; it is the equivalent of the setup() in Arduino programming. When the ESP32 boots, the ESP-IDF framework calls app_main.

void app_main(void)
{
    // your code goes here
}

In the app_main(void) function, you start by initializing the adc_value and the adc_handle variables

int adc_value;
adc_oneshot_unit_handle_t adc_handle;

Then, initialize the adc_oneshot driver with the ADC_UNIT and CLK:

adc_oneshot_unit_init_cfg_t init_config = {
    .unit_id = ADC_UNIT,
    .clk_src = ADC_RTC_CLK_SRC_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle));

Configure the ADC channel with the ADC_BITWIDTH and ADC_ATTEN.

adc_oneshot_chan_cfg_t config = {
    .bitwidth = ADC_BITWIDTH,
    .atten = ADC_ATTEN,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_PIN, &config));

The while(1) runs an infinite loop that ensures that you are constantly checking the current GPIO voltage value.

while (1) {
    // Read ADC value with Oneshot
    ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_PIN, &adc_value));
    // Print ADC value
    ESP_LOGI("ADC Value", "%d", adc_value);
    // Delay 1 second
    vTaskDelay(1000 / portTICK_PERIOD_MS); 
}

Inside the infinite loop, you read the current GPIO voltage using the adc_oneshot_read() function and store it in the adc_value variable.

ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_PIN, &adc_value));

You can print the ADC value returned in the Serial Monitor using the ESP_LOGI() function.

ESP_LOGI("ADC Value", "%d", adc_value);

Finally, add a 1 second delay:

vTaskDelay(1000 / portTICK_PERIOD_MS); 

Build and Flash Code to the ESP32 Board

To build and flash ESP-IDF code to the ESP32, you always need to follow this procedure. You need to select the flash method (UART), the COM port number, the target device (ESP32), build the code, and finally, flash it to the board. All these commands are available in the bottom menu bar of VS Code.

Make sure all your options are correct (they may already be properly configured if you used the project wizard).

VS Code ESP-IDF Check All the Configured Settings UART COM Port Target Board

However, if your setup is not correct, follow the next instructions to ensure everything is set up correctly. First, click the “Star” icon and select the flash method as UART.

VS Code ESP-IDF Select Flash UART Option to Program Flash ESP32

While the ESP32 board is connected to your computer, click the COM Port (plug icon) and select the correct port number that refers to your ESP32.

VS Code ESP-IDF Programming ESP32 Board Select Correct COM Port Number

You also need to select the target device. Click on the chip icon at the bottom bar. In my case, I have an ESP32 with the esp32s3 chip.

VS Code Select the ESP32 S3 or Correct Target Device ESP-IDF

For this board, I also need to select the configuration: ESP32-S3 chip (via builtin USB-JTAG).

VS Code ESP-IDF Select the ESP32 S3 chip via built in USB JTAG Target Device

Finally, your command bar at the bottom of VS Code should have similar options selected.

VS Code ESP-IDF Check All the Configured Settings UART COM Port Target Board

Now, you can build the project by clicking the wrench icon (Build Project) as shown in the image below.

VS Code Build Project Example Code ESP32 ESP-IDF

The first time you build a project, it usually takes a bit more time. Once completed, it should print a similar message in the Terminal menu and show a “Build Successfully” message.

VS Code Build Example Project ESP32 ESP-IDF Success Message

This is the final step. You can now flash the ESP-IDF project to the ESP32 by clicking the “Flash Device” button (thunder icon).

VS Code Flash Hello World Code Project to ESP32 ESP-IDF

Depending on your board, you might need to hold down the on-board BOOT button on your ESP32 to put it into flashing mode. Once the process is completed, it will pop-up a info message saying “Flash Done“.

VS Code Flash Hello World Project to ESP32 ESP-IDF Done Success Message

Schematic Diagram

Here’s a list of the parts you need to build the circuit:

Connect a potentiometer to ADC1 Channel 7 (in this case, it corresponds to GPIO 8) as shown in the schematic diagram below for a board with ESP32-S3 chip.

ESP32-S3 Potentiometer Circuit Analog Input ADC

Demonstration

If you followed all the steps, the example should be running successfully on your board. Open your Terminal window — click the “Monitor Device” tool that is illustrated with a screen icon.

VS Code Open Terminal Window Monitor Device ESP32 ESP-IDF

Rotate the potentiometer knob from one side to the other:

ESP-IDF ESP32 Read Potentiometer adc_oneshot

The terminal should be printing a message saying “ADC Value: 0“, “ADC Value: 407“, “ADC Value: 4095” according to the current position of the potentiometer knob:

ESP-IDF ESP32 Analog Read Potentiometer Value adc_oneshot Demonstration

Example #2: Read ESP32 Analog Inputs with ESP-IDF – ADC Continuous Mode

Our example is based on the original documentation provided by ESP-IDF. You need to copy the code below to the main.c file.

This code is constantly taking new readings of 2 ADC pins that have potentiometers attached to them and prints the values using the continuous mode using the adc_continuous_read() function:

/*  
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp-idf-esp32-gpio-analog-adc/
  
  Based on this example: https://raw.githubusercontent.com/espressif/esp-idf/refs/heads/master/examples/peripherals/adc/continuous_read/main/continuous_read_main.c
*/

#include <string.h>
#include <stdio.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <esp_adc/adc_continuous.h>
#include "sdkconfig.h"


#define EXAMPLE_ADC_UNIT                    ADC_UNIT_1
#define _EXAMPLE_ADC_UNIT_STR(unit)         #unit
#define EXAMPLE_ADC_UNIT_STR(unit)          _EXAMPLE_ADC_UNIT_STR(unit)
#define EXAMPLE_ADC_CONV_MODE               ADC_CONV_SINGLE_UNIT_1
#define EXAMPLE_ADC_ATTEN                   ADC_ATTEN_DB_0
#define EXAMPLE_ADC_BIT_WIDTH               SOC_ADC_DIGI_MAX_BITWIDTH

#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define EXAMPLE_ADC_OUTPUT_TYPE             ADC_DIGI_OUTPUT_FORMAT_TYPE1
#define EXAMPLE_ADC_GET_CHANNEL(p_data)     ((p_data)->type1.channel)
#define EXAMPLE_ADC_GET_DATA(p_data)        ((p_data)->type1.data)
#else
#define EXAMPLE_ADC_OUTPUT_TYPE             ADC_DIGI_OUTPUT_FORMAT_TYPE2
#define EXAMPLE_ADC_GET_CHANNEL(p_data)     ((p_data)->type2.channel)
#define EXAMPLE_ADC_GET_DATA(p_data)        ((p_data)->type2.data)
#endif

#define EXAMPLE_READ_LEN                    256

static adc_channel_t channel[2] = {ADC_CHANNEL_7, ADC_CHANNEL_4};

static TaskHandle_t s_task_handle;
static const char *TAG = "POTENTIOMETER";

static bool IRAM_ATTR s_conv_done_cb(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *user_data)
{
    BaseType_t mustYield = pdFALSE;
    // Notify that ADC continuous driver has done enough number of conversions
    vTaskNotifyGiveFromISR(s_task_handle, &mustYield);

    return (mustYield == pdTRUE);
}

static void continuous_adc_init(adc_channel_t *channel, uint8_t channel_num, adc_continuous_handle_t *out_handle)
{
    adc_continuous_handle_t handle = NULL;

    adc_continuous_handle_cfg_t adc_config = {
        .max_store_buf_size = 1024,
        .conv_frame_size = EXAMPLE_READ_LEN,
    };
    ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &handle));

    adc_continuous_config_t dig_cfg = {
        .sample_freq_hz = 20 * 1000,
        .conv_mode = EXAMPLE_ADC_CONV_MODE,
        .format = EXAMPLE_ADC_OUTPUT_TYPE,
    };

    adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0};
    dig_cfg.pattern_num = channel_num;
    for (int i = 0; i < channel_num; i++) {
        adc_pattern[i].atten = ADC_ATTEN_DB_12;
        adc_pattern[i].channel = channel[i] & 0x7;
        adc_pattern[i].unit = EXAMPLE_ADC_UNIT;
        adc_pattern[i].bit_width = ADC_BITWIDTH_12;

        ESP_LOGI(TAG, "adc_pattern[%d].atten is :%"PRIx8, i, adc_pattern[i].atten);
        ESP_LOGI(TAG, "adc_pattern[%d].channel is :%"PRIx8, i, adc_pattern[i].channel);
        ESP_LOGI(TAG, "adc_pattern[%d].unit is :%"PRIx8, i, adc_pattern[i].unit);
    }
    dig_cfg.adc_pattern = adc_pattern;
    ESP_ERROR_CHECK(adc_continuous_config(handle, &dig_cfg));

    *out_handle = handle;
}

void app_main(void)
{
    esp_err_t ret;
    uint32_t ret_num = 0;
    uint8_t result[EXAMPLE_READ_LEN] = {0};
    memset(result, 0xcc, EXAMPLE_READ_LEN);

    s_task_handle = xTaskGetCurrentTaskHandle();

    adc_continuous_handle_t handle = NULL;
    continuous_adc_init(channel, sizeof(channel) / sizeof(adc_channel_t), &handle);

    adc_continuous_evt_cbs_t cbs = {
        .on_conv_done = s_conv_done_cb,
    };
    ESP_ERROR_CHECK(adc_continuous_register_event_callbacks(handle, &cbs, NULL));
    ESP_ERROR_CHECK(adc_continuous_start(handle));

    while (1) {

         // This is to show you the way to use the ADC continuous mode driver event callback.
         // This `ulTaskNotifyTake` will block when the data processing in the task is fast.
         // However in this example, the data processing (print) is slow, so you barely block here.
         // Without using this event callback (to notify this task), you can still just call
         // adc_continuous_read() here in a loop, with/without a certain block timeout.
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

        char unit[] = EXAMPLE_ADC_UNIT_STR(EXAMPLE_ADC_UNIT);

        while (1) {
            ret = adc_continuous_read(handle, result, EXAMPLE_READ_LEN, &ret_num, 0);
            if (ret == ESP_OK) {
                ESP_LOGI("TASK", "ret is %x, ret_num is %"PRIu32" bytes", ret, ret_num);
                for (int i = 0; i < ret_num; i += SOC_ADC_DIGI_RESULT_BYTES) {
                    adc_digi_output_data_t *p = (adc_digi_output_data_t*)&result[i];
                    uint32_t chan_num = EXAMPLE_ADC_GET_CHANNEL(p);
                    uint32_t data = EXAMPLE_ADC_GET_DATA(p);
                    // Check the channel number validation, the data is invalid if the channel num exceed the maximum channel 
                    if (chan_num < SOC_ADC_CHANNEL_NUM(EXAMPLE_ADC_UNIT)) {
                        //ESP_LOGI(TAG, "Unit: %s, Channel: %"PRIu32", Value: %"PRIx32, unit, chan_num, data);
                        ESP_LOGI(TAG, "Unit: %s, Channel: %"PRIu32", Value: %lu", unit, chan_num, data);
                    } else {
                        ESP_LOGW(TAG, "Invalid data [%s_%"PRIu32"_%"PRIx32"]", unit, chan_num, data);
                    }
                }
                //  Because printing is slow, so every time you call `ulTaskNotifyTake`, it will immediately return.
                //  To avoid a task watchdog timeout, add a delay here. When you replace the way you process the data,
                //  usually you don't need this delay (as this task will block for a while).
                
                vTaskDelay(1);
            } else if (ret == ESP_ERR_TIMEOUT) {
                // We try to read `EXAMPLE_READ_LEN` until API returns timeout, which means there's no available data
                break;
            }
        }
    }
    ESP_ERROR_CHECK(adc_continuous_stop(handle));
    ESP_ERROR_CHECK(adc_continuous_deinit(handle));
}

View raw code

Code Overview

This ESP-IDF code sets up an ESP32 to continuously read analog signals from two ADC channels using the ADC in continuous mode (with adc_continuous_read() function).

In this section, we’ll take a look quick overview of how the code works, but we won’t explain it line by line.

  • Start by configuring the ADC in continuous mode to sample analog signals from two ADC channels on the ADC1 unit at 20 kHz;
  • The ADC driver in continuous mode stores results in an internal buffer;
  • When you collect the readings, it uses FreeRTOS tasks and notifications to handle ADC data;
  • The process of sampling and logging ADC readings in the Terminal repeats indefinitely.

This code is only compatible with ADC1 pins. It’s slightly more complex to implement compared to the One-Shot mode, because it requires DMA configuration. This code can be used in an application that requires real-time fast continuous analog readings (like audio sampling).

Schematic Diagram

Here’s a list of the parts you need to build the circuit:

Connect one potentiometer to ADC1 Channel 7 and the other potentiometer to ADC1 Channel 4 as shown in the schematic diagram below for a board with ESP32-S3 chip.

ESP32-S3 Two Potentiometers Wiring Circuit Analog Input ADC

Demonstration

If you followed all the steps, the example should be running successfully on your board. Open your Terminal window — click the “Monitor Device” tool that is illustrated with a screen icon.

VS Code Open Terminal Window Monitor Device ESP32 ESP-IDF

Rotate the two potentiometer knobs from one side to the other side:

ESP-IDF ESP32 Read Potentiometer adc_continuous

The terminal window should be printing a similar message saying “POTENTIOMETER: Unit: ADC_Unit_1, Channel 7, Value: 4095” according to the current position of the potentiometer knob:

ESP-IDF ESP32 Analog Read Two Potentiometers Values with adc_continuous Demonstration

Wrapping Up

In this tutorial, you learned how to program the ESP32 with the ESP-IDF framework using VS Code to read the voltage of the GPIO using the ADC. In the next guide, we’ll cover ESP-IDF GPIO Interrupts.

Other ESP-IDF guides:

Meanwhile, you can check our ESP32 resources (with Arduino IDE) to learn more about the ESP32 board:

Thanks for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.