RISC-V based ESP32c3 with ESP-IDF part 3, OLED screen, and potentiometer

Table of Content

* Introduction
* ADC Limitations on some ESP32 SoCs
* Potentiometer
* OLED I²C Screen
* Building the project and flashing


This piece of software was done for new year 2022, but procrastination helped me to delay the release of the tutorial, it continue the traditionnal (but with detailed explanations) LED blinking introduction tutorial. The goal of this tutorial is to learn to use potentiometer and little I²C screens (4 pins are I²C only, SPI versions use more pins) in EPE-IDF, with ESP32 microcontroller SoC based. I use here a really cheap (<5€) but powerful AI thinker ESP-C3-32S, that use an efficient low power RISC-V microcontroller.

Full schema with part 2 and 3

You can find the complete sources files and prebuild RISC-V firmware for ESP32-C3 on my files repository.

This example contain two main parts in the single file adc/esp32c3/adc/main/adc_dma_example_main.c, called in app_main(void), at the end of the file :
* One simple example single_read(NULL);, that make only one read of the state of the ADC, it uses ADC 1, channels 2,3,4) and ADC 2 (channel 0) and display datas on terminal.
* One more complex example continuous_read(NULL);, that reads 256 times the state of the channels and display them in the console, and then make continuous reading and change the onboard RGB Led blue colour light intensity.

The official documentation of the ADC.

ADC Limitations on some ESP32 SoCs

Limitations differs depending on ESP SoC, they have both 2 ADC, and one can’t be used when using WiFi:
* ESP32, based on Xtensa LX6 has 10 channels on ADC1 and 8 channels on ADC2, and ADC2 is used when Wifi is on.
* ESP32-S2 (no WiFi/BT) and ESP32-S3 (Wifi/BT), based on Xtensa LX7 (this last one has a RISC-V coprocessor for a more efficient ULP deep sleep mode), has 10 channels on both ADC, and ADC2 can’t be used when WiFi is on.
* ESP32-C3, based on RISC-V, ADC1 can’t be used with WiFi on, both ADC1 and ADC2 can’t be read simultaneously, you must read them alternately. ADC1 have 6 channels (6 pins) and ADC2 only one.

NodeMCU-series ESP-C3-32S-kit pinoutESP-C3-32S kit Pinout schema from JC François, with ADC pins in pink.

Full schema with part 2 and 3
Whole Breadboard montage with previous part tutorial and this one.

Potentiometer and OLED screen connexions
Connexions of potentiometer and OLED screen.


* The first top-left pin (ADC1_CH0 / ADC_CHECK in pink) is connected to the middle pin of the potentiometer (et right on the picture) using the white wire.
* The  3.3V , here 5th pin starting from the bottom left, but other 3.3V can be choosen, is connected to the left pin of the potentiometer (at right on the picture).
* The  GND , here 6th pin starting from the bottom left, but any ground pin can be used is connected using black wire to the right pin of the potentiometer.

V Red and GND black-blue breadboard lanes
Red lane and blue/black lane of the bread board.

Both  Vcc  and Ground are transiting by dedicated lane of the breadboard, on the top of the picture painted with red (meaning Vcc) and blue (meaning for black/Ground) lines. It is very important to keep black and red wire to these roles to avoid to burn components, any other colour can be used for data links. There is another lane at bottom. This is not clear on the picture, but the screen is connected on but on the first row of the inner part.

We need to include the adc.h headers, and we also add esp_log.h header here for debug purpose.

#include "esp_log.h"
#include "driver/adc.h"

Here are the presets used for potentiometer ADC (Analog-Digital Converter) in the source code.

/* ADC vars */

esp_err_t ret;
int adc1_reading[1] = {0xcc};
int adc2_reading[1] = {0xcc};
const char TAG_CH[][9] = {"ADC1_CH0", "ADC2_CH0"};

void init_adc()
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0);
  adc2_config_channel_atten(ADC2_CHANNEL_0, ADC_ATTEN_DB_0);

OLED I²C Screen

An author made an interesting list of available colour display managed by ESP32 on Instructables.

I used the driver esp-idf-ssd1306 by nopnop2002 available on github (local archive)

There are 4 connector pins on the I²C only version:
*  GND , I use black wire and connect it to GND lane.
*  VCC , I use red wire and connect it to Vcc lane.
*  VCL  (sometimes VCK, VCLK as V clock), is for the clock signal, I choose green colour wire here, I connect it to  GPIO9 , at 4th pin starting from top right.
*  VDA  (VDA as V data), I choose white colour wire here, I connect it to  GPIO10 , at 6th pin starting from top right.

The SDA/SCL GPIO can be set by two way:

By editing the sdkconfig file at the root of the project and changing the following values to the values you want:


Or in the menu using:

make menuconfig

Then go to submenu SSD1306 Configuration ---> Then defining the number in (9) SCL GPIO number and (10) SDA GPIO number field.

By default, this application print the current settings in the monitor console via these functions:

        ESP_LOGI(tag, "INTERFACE is i2c");
        i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);

The example of the driver is used for the screen. It pre-include the necessary headers files. ssd1306.h is the driver itself, font8x8_basic.h is a 8×8 pixels ASCII font set, and driver/i2c.h is the i²C protocol header, used to communicate with the screen microcontroller.

#include "ssd1306.h"
#include "font8x8_basic.h"
#include "driver/i2c.h"

I removed the demo, and set all the specific code in the #if CONFIG_SSD1306_128x64 section, as this is the model of my screen.

#if CONFIG_SSD1306_128x64
        top = 2;
        center = 3;
        bottom = 8; // 8 lines
        int n=200;
        int pos=0; // initial position = 0

Le main loop (while(n) {}):
I read ADC1 channel 0 (first pin) of the potentiometer and print the current value into the console

  adc1_reading[0] = adc1_get_raw(ADC1_CHANNEL_0);
  printf("chan[%d] 0x%x = %d\n", 0, adc1_reading[0],adc1_reading[0]);

Then I compute the current p position after a constant I predetermined, after test I seen that the specific potentiometer I use, as values in range [20 ; 2920]. And I have 8 text lines on screen, so I rounded to 3000/8 = 375. Output value / 8 compute the current line on screen.

  pos=adc1_reading[0]/375; // 20~2920  => need to calibrate 3000/8=375

Clearing the 8 text lines of the buffer, but the current line

  for (int i=0;i<8;i++) {
    if ( i != pos) {
      ssd1306_clear_line(&dev, i,false);

Printing 2022!! at the current line. the two space, allow to center a bit the text.

  ssd1306_display_text(&dev, pos, "  2022!!", 11, false);

And finally, wait a delay of 50 milliseconds before refreshing to avoid uselessly saturating processor and overloading.

  vTaskDelay(50 / portTICK_PERIOD_MS);

That's all ! We just have to build the project and put in on the board now.

Building the project and flashing

Build the example for AI thinker ESP-C3-32S

Initialising esp-idf:

. $ESPIDF/export.sh

Then go the the project root:

cd myproject/
 idf.py set-target esp32c3

If you have the following error:

Adding "set-target"'s dependency "fullclean" to list of commands with default set of options.
Executing action: fullclean
Directory '/data/arc/esp/esp-idf/test/adc/esp32c3/adc/build' doesn't seem to be a CMake build directory. Refusing to automatically delete files in this directory. Delete the directory manually to 'clean' it:

You simply need to clean build subdirectory if it exists

rm -R build
mkdir build

and in any case to create the CMake:

cd build
cmake ..
cd ..

Then configure the project for your SoC target, in ESP32-C3 case:

idf.py set-target esp32c3

If you need to change some settings of your porject, like GPIO ports for screen driver, you can edit the sdkconfig file or use make menuconfig now.

you will not have to redo all this procedure at each rebuild now, you can play with source code and build or rebuild/flash with the following last command:

idf.py build flash monitor
You can quit the monitor by making the CTRL + ] keys combination.