Funktionsgenerator mit dem ESP32, Display und Gehäuse - [Teil 3]

After we have added an amplifier in the second part with which you can set both the amplitude and the offset, we now want to make the handling no longer via the serial monitor, but directly on the device.  

For example, the LCD1602 display Keypad Shield, which has an LCD display with two lines of 16 characters as well as buttons for operation, is suitable for this purpose. This Shield was designed for the microcontroller Arduino. So that it can be easily used with the ESP32, we replace the ESP32 DevKit CV4 from the first part by the board ESP32 D1 R32, which has applied its connection spins just like an Arduino Uno. So we can put the Shield right on it. However, examples will not work, as the ESP32 has other IO pins than the Arduino. In addition, it should be noted that the inputs of the ESP32 only allow 3.3 V.

The illustrations show the pin assignment of the ESP32 D1 R32 board and the occupancy of the LCD-Keypad Shield. Only those pins that are really used are labeled with this.

Since all LCD pins are used as inputs of the LCD controller, we do not need to worry about the voltage, since the inputs also work safely with 3.3 V.

It looks a little different with the push-button connector. The value between 0 and 5 V, depending on the key, is supplied. At the Shield, the output is connected to the + 5V connection via a 2 kOhm resistor. If we now solder a resistance of 3.9 kOhm from the push-button connector and the 0V connection, the maximum voltage at this connection is only 5V * 3900/(2000 + 3900) = 3.3 V. On Picture 2  this resistor can be seen.

We have another challenge with the GPIO12 connector. This must be at 0V during the boot process, because otherwise the operating voltage for the flash memory will be switched to 1.8V. Here again will help a resistor of 10 kOhm between GPIO12 and GND. However, this resistor is better soldered to the ESP32 D1 R32 board, because this problem can also occur with other Arduino Shields.

Picture 1: Pin assignment of the ESP32 D1 R32

Picture 2: Pin assignment of the LCD Keypad Shield (only connectors used)

Picture 3: Rear ESP32 D1 R32 with 10 kOhm resistance between GPIO12 and GND

Required hardware

Here again all parts, including those from the second part, are listed.

Number Component Note
1 ESP32 D1 R32 Board
2 LCD Keypad Shield
1 Resistance 3,9 kOhm
1 Resistor 10 kOhm
1 LM358 Dual operational amplifier From Part2
2 Potentiometer 10 kOhm with 4mm axis From Part2
1 Resistor 1 kOhm R3 From Part2
1 Resistor 1,5 kOhm R5 From Part2
1 Resistor 2.2 kOhm R2 From Part2
2 Resistance 100 kOhm R1 and R4 From Part2
1 Pin bar 7-pole
1 Pin bar 6-pole
3 Pin Bar 3-pole From Part2
1 Pencil bar 2-pole From Part2
3 Jumper Wire Cable Female/Female 3-pole
2 Jumper Wire Cable Female/Female 2-pole
1 Printed circuit board or perforated grid plate 30 x 40 mm From Part2
1 DC-DC Boost Buck converter with positive and negative voltage input 5V, output +/-5V From Part2
1 BNC Mounting Bushing
2 Knobs for potentiometer
1 Buttons from the 3D Printer with TPU Filament
4 Housing and spacers from the 3D printer with PLA filament


8 Sheetmetal screws 2,2 x 6,5 mm
4 Sheetmetal screws 2,2 x 9,5 mm


The Software


/* 
* Funktionsgenerator für Sinus, Dreieck und Rechteck Signale
* Einstellbare Frequenz 20 Hz bis 20 KHz
* Für Dreieck und Rechteck einstellbares Tastverhältnis 0 bis 100%
*/ //Bibliotheken zum direkten Zugriff auf Steuerregister des ESP32
#include "soc/rtc_cntl_reg.h"
#include "soc/sens_reg.h"
#include "soc/rtc.h"

//Bibliotheken zur Verwendung des Digital zu Analog Konverters und für den I2S-Bus
#include "driver/dac.h"
#include "driver/i2s.h"

//Bibliothek für das LCD Display
#include <LiquidCrystal.h>
#define SINFAKT 127.0 //gemessen für Schrittweite = 1 und kein Vorteiler (8.3MHz)
#define SIGNALOUT 26 //Pin für die Signalausgabe
//LCD Pins
#define PIN_RS 12 //Registerselect 0=Befehle 1=Daten
#define PIN_EN 13 //Enable Takt zum Schreiben
#define PIN_D4 17 //Datenbit
#define PIN_D5 16 //Datenbit
#define PIN_D6 27 //Datenbit
#define PIN_D7 14 //Datenbit
#define PIN_BL 5 //Hintergrundbeleuchtung 0=aus //Analog PIN für Tasten
#define KEYS A12 //Spannungsteiler für Tasten
#define Rv 2000 //Vorwiderstand
#define R3 3900 //Schutzwiderstand für maximal 3.3 V
#define Rp 1000 //Widerstand GPIO2 gegen Masse
#define Rr 0 //Spannungsteiler bei gedrückter RIGHT Taste
#define Ru 330 //Spannungsteiler bei gedrückter UP Taste
#define Rd 950 //Spannungsteiler bei gedrückter DOWN Taste
#define Rl 1950 //Spannungsteiler bei gedrückter LEFT Taste
#define Rs 5250 //Spannungsteiler bei gedrückter SELECT Taste //Tasten Codes
#define NONE 0
#define LEFT 1
#define RIGHT 2
#define UP 3
#define DOWN 4
#define SELECT 5

//Betriebsarten
#define MSINUS 0
#define MRECTANGLE 1
#define MTRIANGLE 2

//Änderungstypen
#define EMODE 0
#define EFREQUENCY 1
#define ERATIO 2 // Init I2C LCD
LiquidCrystal lcd(PIN_RS, PIN_EN, PIN_D4, PIN_D5, PIN_D6, PIN_D7);

//Variablen zum Speichern der Schwellwerte für Taster
uint16_t Ur, Uu, Ud, Ul, Us;

//Buffer zum Erstellen der Dreieckfunktion
uint32_t buf[128];

//Einstellwerte für Kurvenform, Frequenz und Tastverhältnis
int8_t mode = MSINUS; //0=Sinus, 1=Rechteck, 2=Dreieck
float frequency = 1000; //20 bis 200000 Hz
int8_t ratio = 50; //Tastverhältnis 0 bis 100%

int8_t edit = EMODE; //was wird verändert 0=Mode 1=Frequenz 2=Tastverhältnis

uint32_t tic; //Für Wartezeit int8_t lastKey = 0; //zuletzt ermittelte Taste oder 0 wenn keine
uint16_t step = 0; //Schrittweite für Frequenzerhöhung
float ftmp; //Variable zum Speichern der Frequenz während der Einstellung
int16_t rtmp; //Variable zum Speichern des Tastverhältnis während der Einstellung
int8_t mtmp; //Variable zum Speichern der Betriebsart während der Einstellung

//Flag Ist wahr, wenn die Initialisierung bereits erfolgte
bool initDone = false; //Konfiguration für den I2S Bus
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), //Betriebsart
.sample_rate = 100000, //Abtastrate
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // der DAC verwendet nur 8 Bit des MSB
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // Kanalformat ESP32 unterstützt nur Stereo
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB, //Standard Format für I2S
.intr_alloc_flags = 0, // Standard Interrupt
.dma_buf_count = 2, //Anzahl der FIFO Buffer
.dma_buf_len = 32, //Größe der FIFO Buffer
.use_apll = 0 //Taktquelle
}; //Buffer für Dreieck Wellenform füllen
//Parameter up ist die Dauer für den Anstieg in Prozent
//Parameter sz gibt die Buffergröße für eine Periode an
//es werden die Werte für eine Periode in den Buffer geschrieben
void fillBuffer(uint8_t up, uint8_t sz) {
uint8_t down; //Zeit für die fallende Flanke in %
uint32_t sample; //32Bit Datenwort (I2S benötigt zwei Kanäle mit je 16 Bit
float du,dd,val; //Hilfsvariablen
down=100-up;
//Anzahl der Schritte für Anstieg und Abfall berechnen
uint16_t stup = round(1.0*sz/100 * up);
uint16_t stdwn = round(1.0*sz/100*down);
uint16_t i;
if ((stup + stdwn) < sz) stup++;//Ausgleich eventueller Rundungsfehler
//Amplitudenänderung pro Schritt für Anstieg und Abfall
du = 256.0/stup;
dd = 256.0/stdwn;
//füllen des Buffers
val = 0; //Anstieg beginnt mit 0
for (i=0; i<stup; i++) {
sample = val;
sample = sample << 8; //Byte in das höherwertige Byte verschieben
buf[i]=sample;
val = val+du; //Wert erhöhen
}
val=255; //Abfallende Flanke beginnt mit Maximalwert
//Rest wie bei der ansteigenden Flanke
for (i=0; i<stdwn; i++) {
sample = val;
sample = sample << 8;
buf[i+stup]=sample;
val = val-dd;
}
}
//Alle Ausgänge stoppen
void stopAll(){
ledcDetachPin(SIGNALOUT);
i2s_driver_uninstall((i2s_port_t)0);
dac_output_disable(DAC_CHANNEL_2);
dac_i2s_disable();
initDone=false;
}

//Kurvenform Rechteck starten
//Pin für Signalausgang zuweisen
void startRectangle(){
ledcAttachPin(SIGNALOUT,1 );
initDone=true;
} //Frequenz für Rechteck setzen mit entsprechendem Tastverhältnis
void rectangleSetFrequency(double frequency,uint8_t ratio)
{
ledcSetup(1,frequency,7); //Wir nutzen die LEDC Funktion mit 7 bit Auflösung
ledcWrite(1,127.0*ratio/100); //Berechnung der Schrittanzahl für Zustand = 1
}


//Dreiecksignal starten
void startTriangle(){
i2s_set_pin((i2s_port_t)0, NULL); //I2S wird mit dem DAC genutzt
initDone=true;
}

//Frequenz für Dreieck setzen mit entsprechendem Tastverhältnis
double triangleSetFrequency(double frequency,uint8_t ratio)
{ { int size=64;
//zuerst wird die geeignete Buffergröße ermittelt
//damit die Ausgabe funktionier muss die I2S Abtastrate zwischen
//5200 und 650000 liegen
if (frequency<5000) {
size = 64;
} else if (frequency<10000) {
size = 32;
} else if (frequency<20000) {
size = 16;
} else {
size = 8;
}
//Abtastrate muss in einer Periode beide Buffer ausgeben
uint32_t rate = frequency * 2 * size;
//Die Abtastrate darf nur innerhalb der Grenzwerte liegen
if (rate < 5200) rate = 5200;
if (rate > 650000) rate = 650000;
//wirklichen Frequenzwert setzen
frequency = rate / 2 / size; //I2S Treiber entfernen
i2s_driver_uninstall((i2s_port_t)0);
//Konfiguration anpassen
i2s_config.sample_rate = rate;
i2s_config.dma_buf_len = size;
//und mit der neuen Konfiguration installieren
i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
//Abtastrate einstellen
i2s_set_sample_rates((i2s_port_t)0, rate);
//Buffer füllen
fillBuffer(ratio,size*2);
//und einmal ausgeben
i2s_write_bytes((i2s_port_t)0, (const char *)&buf, size*8, 100);
return frequency;
}
//Sinusausgabe vorbereiten
void startSinus(){
//Ausgang für Signalausgang freigeben
dac_output_enable(DAC_CHANNEL_2);
// Sinusgenerator aktivieren
SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN);
// Ausgabe auf Kanal 1 starten
SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN2_M);
// Vorzeichenbit umkehren
SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV2, 2, SENS_DAC_INV2_S);
initDone=true;
} //Frequenz für Sinus setzen
double sinusSetFrequency(double frequency)
{
//Formel f = s * SINFAKT / v
//s sind die Schritte pro Taktimpuls
//v ist der Vorteiler für den 8MHz Takt
//Es gibt 8 Vorteiler von 1 bis 1/8 um die Kombination Vorteiler und
//Schrittanzahl zu finden, testen wir alle acht Vorteiler Varianten
//Die Kombination mit der geringsten Frequenzabweichung wird gewählt

double f,delta,delta_min = 999999999.0;
uint16_t divi=0, step=1, s;
uint8_t clk_8m_div = 0;//0 bis 7
for (uint8_t div = 1; div<9; div++){
s=round(frequency * div/SINFAKT);
if ((s>0) && ((div == 1) || (s<1024))) {
f= SINFAKT*s/div;
/*
Serial.print(f); Serial.print(" ");
Serial.print(div); Serial.print(" ");
Serial.println(s);
*/
delta = abs(f-frequency);
if (delta < delta_min) { //Abweichung geringer -> aktuelle Werte merken
step = s; divi = div-1; delta_min = delta;
}
}
}    //wirklichen Frequenzwert setzen
frequency = SINFAKT * step / (divi+1);
// Vorteiler einstellen
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, divi);
// Schritte pro Taktimpuls einstellen
SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL1_REG, SENS_SW_FSTEP, step, SENS_SW_FSTEP_S);
return frequency;
} //Einstellungsänderungen durchführen
void controlGenerator() {
switch (mode) {
case MSINUS: if (!initDone) startSinus();
frequency = sinusSetFrequency(frequency);
break;
case MRECTANGLE : if (!initDone) startRectangle();
rectangleSetFrequency(frequency,ratio);
break;
case MTRIANGLE : if (!initDone) startTriangle();
frequency = triangleSetFrequency(frequency,ratio);
break;
}
} //Anzeige aktualisieren
//Wenn monitor wahr ist, erfolgt die Ausgabe
//auch auf die serielle Schnittstelle
void displayValues(boolean monitor) {
char buf[15];
//aktuelle Werte ausgeben
String ba;
switch (mode) {
case MSINUS: ba="Sinus "; break;
case MRECTANGLE: ba="Rechteck "; break;
case MTRIANGLE: ba="Dreieck "; break;
} //Betriebsart ausgeben
lcd.setCursor(0,0);
lcd.print(" ");
lcd.print(ba);
if (monitor) {
Serial.println("**************** Eingestellte Werte *************************");
Serial.print("Betriebsart = "); Serial.println(ba);
}
//Frequenz je nach Wert als Hz oder kHz
if (frequency < 1000){
sprintf(buf,"%6.2f Hz",frequency);
} else {
sprintf(buf,"%6.2fkHz",frequency/1000);
}
//Frequenz ausgeben
lcd.setCursor(0,1);
lcd.print(" F"); lcd.print(buf);
if (monitor) {
Serial.print("Frequenz = "); Serial.println(buf);
}
sprintf(buf,"%2i%%",ratio);
//Tastverhältnis ausgeben
lcd.setCursor(11,1);
lcd.print(" T"); lcd.print(buf);
if (monitor) {
Serial.print("Tastverhältnis = "); Serial.println(buf);
Serial.println();
}
//je nach Edit-Mode Pfeilzeichen ausgeben
switch (edit) {
case EMODE: lcd.setCursor(0,0); break;
case EFREQUENCY: lcd.setCursor(0,1); break;
case ERATIO: lcd.setCursor(11,1); break;
}
lcd.print(char(126));
} //Edit Mode ändern mit UP und DOWN Taste
void changeEdit(boolean up) {
//je nach Richtung Schritte positiv oder negativ
int s = up?1:-1;
edit += s;
//am Ende wieder auf Anfang springen
if (edit < 0) edit = 2;
if (edit > 2) edit = 0;
//die aktuellen Werte in die temporären Werte
//für die Änderung kopieren
ftmp = frequency;
rtmp = ratio;
mtmp = mode;
//Geänderte Editmode ausgeben
Serial.print("Mode = ");Serial.println(mode);
//Anzeige aktualisieren ohne Ausgabe auf serielle
//Schnittstelle
displayValues(false);
} //Betriebsart ändern mit RIGHT und LEFT Taste
void changeMode(boolean up) {
//je nach Richtung Schritte positiv oder negativ
int s = up?1:-1;
//temporäre Betriebsart ändern
mtmp += s;
//Wenn das Ende erreicht wird wieder auf Anfang springen
if (mtmp < 0) mtmp = 2;
if (mtmp > 2) mtmp = 0;
//Geänderte Betriebsart am Display anzeigen
lcd.setCursor(1,0);
switch (mtmp) {
case 0: lcd.print("Sinus "); break;
case 1: lcd.print("Rechteck "); break;
case 2: lcd.print("Dreieck "); break;
}
} //Frequenz ändern mit RIGHT und LEFT Taste
void changeFrequency(boolean up) {
//war die Taste vorher nicht gedrückt, wird die Schrittweite auf 1 gesetzt
//während die Taste gedrückt bleibt, wird die Schrittweite laufend
//verdoppelt bis eine maximale Schrittweite erreicht wurde
step = (lastKey == NONE)?1:step*2;
if (step > 1024) step = 1024;
//Richtungsfaktor bestimmen
int16_t s = up?1:-1;
//temporäre Frequenz ändern
ftmp = ftmp+s*step;
//auf Minimal- und Maximalwerte prüfen
if (ftmp < 20) ftmp=20;
if (ftmp > 20000) ftmp = 20000;
char buf[15];
//Für die Anzeige Hz oder kHz
if (ftmp > 999) {
sprintf(buf,"%6.2fkHz",ftmp/1000.0);
} else {
sprintf(buf,"%6.2f Hz",ftmp*1.0);
}
//Geänderte Frequenz am Display anzeigen
lcd.setCursor(2,1);
lcd.print(buf);
} //Tastverhältnis ändern mit RIGHT und LEFT Taste
void changeRatio(boolean up) {
//Richtung festlegen
int8_t jx = up?1:-1;
//Temporäres Tastverhältnis ändern
rtmp = rtmp+jx;
//auf Minimal- und Maximalwerte prüfen
if (rtmp < 0) rtmp=0;
if (rtmp > 100) rtmp = 100;
char buf[15];
//Geändertes Tastverhältnis am Display anzeigen
sprintf(buf,"%2i%%",rtmp);
lcd.setCursor(13,1);
lcd.print(buf);
} //der Funktionsgenerator wird auf die geänderte Einstellung gesetzt
//die temporären Werte werden in die Aktuellen Werte übernommen
void setValues() {
Serial.print("Set values edit = "); Serial.println(edit);
switch (edit) {
case EMODE: stopAll(); mode = mtmp; break;
case EFREQUENCY: frequency = ftmp; break;
case ERATIO: ratio = rtmp; break;
}
//Funktionsgenerator selber ändern
controlGenerator();
displayValues(true);
}

//Tastaturspannung einlesen und auswerten
void handleKeys() {
//Tastaturspannung einlesen
int x=analogRead(KEYS);
uint8_t key = NONE;
if (x < Ur) { key = RIGHT; }
else if (x < Uu) { key = UP; }
else if (x < Ud){ key = DOWN; }
else if (x < Ul){ key = LEFT; }
else if (x < Us){ key = SELECT; }
else {key = NONE;}
if (((key == UP) || (key == DOWN)) && (lastKey == NONE)) changeEdit(key == DOWN);
if ((key == LEFT) || (key == RIGHT)) {
switch (edit) {
case EMODE: if (lastKey == NONE) changeMode(key == RIGHT);
break;
case EFREQUENCY: changeFrequency(key == RIGHT);
break;
case ERATIO: changeRatio(key == RIGHT);
break;
}
} if ((key == SELECT) && (lastKey == NONE)) setValues();
lastKey = key;
tic = millis();
}
//Serielle Schnittstelle aktivieren und
//Defaulteinstellungen 1kHz Sinus setzen
//Schwellwerte für Tastatur festlegen
void setup()
{    Serial.begin(115200);
controlGenerator();
lcd.begin(16,2); // initialisiere LCD I2C Anzeige
lcd.clear();
displayValues(true);
tic = millis();
Serial.print("Kommando M,F,A,T,O : ");
pinMode(2,INPUT);
//Schwellwerte für Taster berechnen
//Diese Berechnung ist notwendig, da die Toleranzen sehr
//gering sind, und die Schwellwerte von der Betriebsspannung
//abhängen.
//Versorgungsspannung ohne Taste ermitteln
int x=analogRead(A12);
float Rin = R3 * Rp / (R3 + Rp);
float Ub = x / Rin * (Rin + Rv);
//Schwellspannungen ermitteln
float Uup,Udn,Ulf,Usl,Rtmp;
//Mittelwert für UP Taste
Rtmp = Rin * Ru / (Rin + Ru);
Uup = Ub * Rtmp / (Rtmp + Rv);
//Mittelwert für DOWN Taste
Rtmp = Rin * Rd / (Rin + Rd);
Udn = Ub * Rtmp / (Rtmp + Rv);
//Mittelwert für LEFT Taste
Rtmp = Rin * Rl / (Rin + Rl);
Ulf = Ub * Rtmp / (Rtmp + Rv);
//Mittelwert für Select Taste
Rtmp = Rin * Rs / (Rin + Rs);
Usl = Ub * Rtmp / (Rtmp + Rv);
//eigentliche Schwellwerte berechnen
//immer in die Mitte zwischen zwei Mittelwerten
Ur = Uup/2;
Uu = Uup + (Udn - Uup) / 2;
Ud = Udn + (Ulf - Udn) / 2;
Ul = Ulf + (Usl - Ulf) / 2;
Us = Usl + (x-Usl) /2;
//Schwellwerte auf die serielle Schnittstelle ausgeben
Serial.printf("Schwellwerte: right %i, up %i, down %i, left %i, select %i\n",Ur,Uu,Ud,Ul,Us);

} void loop(){
if ((millis()-tic) > 200) handleKeys();
//Serielle Schnittstelle abfragen
if (Serial.available() > 0) {
//Befehl von der Schnittstelle einlesen
String inp = Serial.readStringUntil('\n');
//und zur Kontrolle ausgeben
Serial.println(inp);
char cmd = inp[0]; //erstes Zeichen ist das Kommando
if ((cmd == 'M') || (cmd == 'm')) { //war das Zeichen 'M' wird die Betriebsart eingestellt
char newMode = inp[1]; //zweites Zeichen ist die Betriebsart
uint8_t nm=0;
switch (newMode) {
case 's':
case 'S': nm=0; break;
case 'r':
case 'R': nm=1; break;
case 't':
case 'T': nm=2; break;
}
if (nm != mode) { //Nur wenn eine Änderung vorliegt, muss was getan werden
stopAll();
mode=nm;
controlGenerator();
}
} else {
//bei den anderen Befehlen folgt ein Zahlenwert
String dat = inp.substring(1);
//je nach Befehl, werden die Daten geändert
switch (cmd) {
case 'F' :
case 'f' :frequency = dat.toDouble(); break; //Frequenz
case 'T' :
case 't' :ratio = dat.toInt(); break; //Tastverhältnis
}
//Grenzwerte werden überprüft
if (ratio > 100) ratio = 100;
if (frequency < 20) frequency = 20;
if (frequency > 20000) frequency = 20000;
controlGenerator();
}
//aktuelle Werte ausgeben
displayValues(true);
Serial.print("Kommando M,F,T : ");
}
}

Download sketch:

The part for the signal generator and for the operation via the serial interface is identical to the Sketch from Part 1. New is the handling via the buttons on the Display Keypad Shield. In particular, the evaluation of the keys requires special attention.

Picture 4: voltage divider for the keys on the LCD Keypad Shield and left the ESP32 D1 R32 board with the parallel resistor

The voltage divider is designed so that the difference between the individual voltages is approximately 1 Volt. However, since we have installed the 3900 Ohm protective resistor so that the output can never reach more than 3.3 V, this difference is reduced. But the problem is getting bigger. The ESP32 D1 R32 board has a 1kOhm resistor parallel to the GPIO2 input, so that for flashing only the GPIO0 has to be connected to GND. Due to this resistor, the voltage difference is reduced further. The table shows the voltages obtained from the push buttons (without parallel resistor, with 3900 Ohm parallel and with 3900 Ohm and 1000 Ohm parallel.

Taster Without parallel resistor 3900 Ohm 3900 Ohm and 1000 Ohm
No taster 5.00 V 3.30 V 2.20 V
SELECT 3.60 V 2.60 V 2.04 V
LEFT 2.50 V 1.97 V 1.10 V
DOWN 1.60 V 1.4 V 0.89 V
UP 0.70 V 0.66 V 0.52 V
RIGHT 0.00 V 0.00 V 0.00 V


Tests have shown that the 5V voltage is not very accurate. With an external power supply, the voltage was 4.4V, with the power supply via USB between 5.0V on a computer connection and 5.2V on a USB charger.

If one would now work with fixed threshold values for each push-button, the device would operate according to operating voltage or not. In order to ensure a safe function, the operating voltage is therefore determined when the key is switched on without a push-button being pressed. From this, the average voltages per key can then be calculated. Since the query in the key evaluation is smaller, the best result is obtained if the half of the distance to the next higher mean value is added to the average voltage in order to obtain the threshold value.

The enclosure

In order to increase the usability, we have designed a housing that can be made with a 3D printer. The housing is constructed in such a way that all parts can be found in it.

Picture 5: Housing from the 3D printer

On the floor, you can see the rectangular recess for the display and the openings for the buttons. On the right side the two holes for the potentiometers. In the right wall is a hole for the BNC socket and on the left wall is a rectangular recess for the USB plug and for the power supply plug.

For the fastening of the printed circuit boards there are in each case four distance cylinders with bores.

The matching lid does not have any recesses and can easily be plugged in.

The knobs are arranged on a common base plate for easier assembly. In order for them to be pressed individually, they must be printed with an elastic filament, for example TPU. If this is not possible, the individual buttons must be separated from the base plate after printing, so that they can be operated individually.

Picture 6: Buttons from the 3D printer

Finally, there are two spacers, which are to be inserted between the shield and the display, in order to prevent a bracing of the display during the tightening of the screw.

Picture 7: LCD Keypad Shield, installation of the spacers


Links to download the 3D print files:

Bottom housing, Lid, Buttons, Distance piece 1, Distance piece 2

Assembly

The LCD Keypad Shield has, in addition to the pin strips used for the connection to the ESP32 D1 R32 board, still free soldering points, which are connected in parallel with the pin strips. We use this to connect the external modules to the board. To do this, we also have to equip them with pen strips.

Picture 8: Additional pen bars on the LCD Keypad Shield

Likewise, the 3.9 kOhm protective resistance should not be forgotten.

We start the assembly with the buttons you will be inserted into the corresponding recesses in the housing.  The two potentiometers with connection cables follow. For the connection cables we halve a 3-pin jumper wire cable and soldered it with the potentiometers. Strain relief with a cable tie is an advantage.

Figure 9: Installation of the buttons in the housing

Figure 10: Installation of the potentiometer and the BNC socket (signal output)

A 2-pin jumper wire cable is also halved and soldered to the BNC socket.

If the two potentiometers and the BNC socket are installed, the LCD-Keypad Shield can be fixed in the housing with four screws. Do not forget to insert the two distance pieces between the display and the board. The voltage converter module and the amplifier module can also be fastened with four screws each.

Picture 11: Housing with LCD Keypad Shield, DC-DC- converters, operational amplifiers and cabling

The cables can now be plugged in according to the wiring plan.

Image 12: wiring plan

Finally, the ESP32 D1 R32 board is plugged onto the LCD-Keypad Shield.

Picture 13: Part of the housing with the complete structure


Now the lid is still on it, and buttons for the potentiometers are mounted, then our function generator is ready for use. For power supply we can optionally use a USB charger or a power supply with 6 to 12 V.

Picture 14: Complete function generator on the oscilloscope

A lot of success in rebuilding.

Link to the blog post as PDF


DisplaysEsp-32Projekte für fortgeschrittene

4 comments

Gerald Lechner

Gerald Lechner

Hallo Claus, GPIO12 ist schon richtig. Es ist nicht GPIO02 gemeint. Der Pin GPIO12 dient beim ESP32 dazu, die Versorgungsspannung für den Flash-Speicher zu steuern. Ist GPIO12 beim Bootvorgang auf High, beträgt die Versorgungsspannung 1.8 V, bei LOW 3.3V. Daher muss GPIO12 beim Booten auf LOW sein.
Das mit dem Widerstand ist richtig. Beim ESP32 Board könnte er weggelassen werden. Ich habe ihn auf das Display gelötet, da ich dieses auch mit der ESP8266 Version genutzt habe. Bei diesem Board ist dieser Anschluss mit A0 verbunden. A0 ist aber ein hochohmiger Eingang, der dann mit 5V beschädigt würde. Mit dem 3.9 kOhm Widerstand ist man auf der sicheren Seite und es funktionieren die Tasten trotzdem problemlos. Der Funktionsgenerator funktioniert natürlich nur mit dem ESP32!

Claus Teubner

Claus Teubner

Hallo,
wie immer, sehr schöner Artikel.
Heute habe ich den versteckten Tipfehler :-) gefunden.
‘…Ein weiteres Problem haben wir mit dem GPIO12 Anschluss. Der muss während des Bootvorgangs auf 0V liegen…’
Sollte doch GPIO2 statt GPIO12 Anschluss sein.
Außerdem sollte doch der 3,9kOhm Widerstand direkt am ESP32 reichen, dann bleibt das Display unverändert und wenn beides zusammengesteckt ist, ist sichergestellt, dass nur max. 3,3V am ESP anliegen. Falls die 3,9kOhm zum booten nicht reicht, tut es auch der 1kOhm alleine, dann sind die Taster-Spannungen etwas größer.

Bernd Albrecht

Bernd Albrecht

@Sven: Der D1 R32 ist “baulich” am Uno angelehnt. Die Uno Shields können also aufgesteckt werden. Aber es gibt deutliche Unterschiede bei der Pinbelegung, der gravierendste ist die zulässige Spannung von 3,3V beim D1 R32. Um das LCD Keypad Shield zu verwenden, müssen deshalb wie beschrieben die zusätzlichen Widerstände angelötet werden.

Sven Waibel

Sven Waibel

Hallo,
wieso steht beim LCD, den ihr verlinkt habt, dass dieser nicht mit dem D1 R32 kompatibel ist?
Grüße
Sven

Leave a comment

All comments are moderated before being published