Konstrukce kuchyňského teploměru s pomocníkem na vaření vajíček naměkko.

Schéma zapojení

Operační zesilovač U1 v invertujícím zapojení slouží jako zdroj konstatního proudu pro termistor TH1.

\(U_{výst1} = - (\frac{R_{th}}{R_1})\cdot U_{vst} = -(\frac{R_{th}}{22 \Omega})\cdot 3.3V\)

Operační zesilovač U2 v neinvertujícím zapojení zesiluje napětí z U1, aby bylo dobře měřitelné pomocí ADC na Picu.

\(U_{výst2} = U_{výst1}\cdot(1+\frac{R_3}{R_2}) = -(\frac{R_{th}}{22\Omega})\cdot 3.3V \cdot (1 + \frac{10000\Omega}{510\Omega}) \approx -3.091\cdot R_{th} \)

Napětí, které budeme měřit pomocí ADC je lineárně závislé na odporu termistoru.

Problém je v tom, že napětí 3.3V, které dostáváme z Pica není přesné ani není dostatečně stabilní. Stabilitu 3.3V vylepšíme nastavením měniče RPi Pico do neúsporného režimu a dodatečnými filtrovacími kondenzátory.

Závislost odporu termistoru na teplotě není lineární a je daná tabulkou termistorů uvedenou níže. Abychom zlepšili přesnost a stabilitu měření teploty, provedeme si vlastní cejchování teploměru a pomocí lineární regrese si stanovíme funkční závislost

Pro předběžné cejchování jsem použil místo termistoru víceotáčkový potenciometr, měření napětí a odporu bylo provedeno multimetrem UNI-T UT61E+ s přesností 0.01 %.

Tabulka 1. Předběžné cejchování
napětí na ADC [V] odpor termistoru podle tabulky [Ohm] teplota [˚C]

2.707

803

-50

2.570

842

-40

2.446

882

-30

2.338

921

-20

2.238

960

-10

2.1464

1000

0

2.0556

1039

10

1.9827

1077

20

1.9441

1097

25

1.9076

1116

30

1.6167

1308.9

80

1.5249

1385

100

Z tohoto předběžného cejchování nám vypadla regresní funkce závislosti napětí na teplotě:

\(t(V) = -124.45\cdot V + 273.81\)

Počítal jsem to programem Geogebra, soubor funkce_teplota_napeti.ggb.

Tabulka termistorů

Pt1000 Temperature Sensor Datasheet 3474

teplota [˚C] PT100 [\(\Omega\)] PT500 [\(\Omega\)] PT1000 [\(\Omega\)] NTV5K [\(\Omega\)] NTC10K [\(\Omega\)] NTC20K [\(\Omega\)] NI500 [\(\Omega\)] NI1000 [\(\Omega\)] TK5000 [\(\Omega\)] KTY81-110 [\(\Omega\)] KTY81-210 [\(\Omega\)]

-50

80.31

401.54

803.10

333.914

667.83

1667.57

371.50

743.00

790.88

515.00

1030

-40

84.27

420.01

842.70

167.835

335.67

813.44

395.50

791.00

830.83

567.00

1135

-30

88.22

435.48

882.20

88.342

176.68

415.48

421.48

842.00

871.69

624.00

1247

-20

92.16

460.80

921.60

48.487

96.79

221.30

446.50

893.00

913.48

684.00

1396

-10

96.09

480.43

960.90

27.649

55.30

122.47

473.00

946.00

956.24

747.00

1495

0

100.00

500.00

1000.00

16.325

32.65

70.20

500.00

1000.00

1000.00

815.00

1630

10

103.90

519.51

1039.00

9.952

19.90

41.56

528.00

1056.00

1044.79

886.00

1772

20

107.79

538.97

1077.90

6.246

12.49

25.35

556.00

1112.00

1090.65

961.00

1922

25

109.74

548.65

1097.40

5.00

10.00

20.00

570.00

1141.00

1113.65

1000.00

2000

30

111.67

558.36

1116.70

4.028

8.06

15.89

585.50

1171.00

1137.61

1040.00

2080

40

115.54

577.70

1155.40

2.552

5.32

10.21

615.00

1230.00

1185.71

1122.00

2245

50

119.40

596.98

1194.00

1.800

3.60

6.72

645.50

1291.00

1234.97

1209.00

2417

60

123.24

616.20

1232.40

1.2435

2.49

4.52

676.50

1353.00

1285.40

1299.00

2597

70

127.07

635.36

1270.00

0.8758

1.75

3.10

708.50

1417.00

1337.14

1392.00

2785

80

130.89

654.47

1308.90

0.6281

1.26

2.12

741.50

1483.00

1390.12

1490.00

2980

90

134.70

673.51

1347.00

0.4581

0.92

1.54

774.50

1549.00

1444.39

1591.00

3118

100

138.50

692.50

1385.00

0.3393

0.68

1.12

809.50

1618.00

1500.00

1696.00

3382

110

142.29

711.43

1422.00

0.2550

0.51

0.82

844.00

1688.00

1556.98

1805.00

3607

120

146.06

730.31

1460.00

0.1943

0.39

0.61

880.00

1760.00

1615.36

1915.00

3817

130

149.82

749.12

1498.20

0.1499

0.30

0.46

941.50

1883.00

1675.18

2023.00

4008

140

153.58

767.88

1535.80

0.1170

0.23

0.35

954.50

1909.00

1736.47

2124.00

4166

150

157.31

786.58

1573.10

0.0923

0.18

0.27

993.50

1987.00

1799.26

2211.00

4280

Zdrojové kódy v0.1

Zde jde o ověření měření teploty a pokusy se sirénou.

teplomerk_0.1.c
/* teplomerk.c
 * (c) Jirka Chráska 2026, <jirka@lixis.cz>
 * BSD 3 clause licence
 */
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/i2c.h"
#include "hardware/pwm.h"
#include "lib/ssd1306.h"
#include "lib/font_spleen_8x5.h"
#include "lib/font_spleen_16x8a.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "pico/time.h"
#include "pico/types.h"

#define PIN_BZUCAK  15 
#define PIN_LED     14


// Funkce pro zvuk sirény
void hraj_ton(uint frekvence) 
{
    if (frekvence == 0) {
        pwm_set_gpio_level(PIN_BZUCAK, 0);
        return;
    }
    uint slice_num = pwm_gpio_to_slice_num(PIN_BZUCAK);
    uint32_t clock = 125000000;
    uint32_t divider = clock / (frekvence * 4096) + 1;
    uint32_t top = clock / (divider * frekvence) - 1;
    pwm_set_clkdiv(slice_num, divider);
    pwm_set_wrap(slice_num, top);
    pwm_set_gpio_level(PIN_BZUCAK, top / 2);
}

void sirena(void)
{
int i = 0;
    while(i<20) {
        gpio_put(PIN_LED, i%2);
        hraj_ton(600);
        sleep_ms(80);
        hraj_ton(500);
        sleep_ms(80);
        hraj_ton(450);
        sleep_ms(80);
        hraj_ton(0);
        i++;
    }
    gpio_put(PIN_LED,0);
}

// pin pro měření napětí termistoru
#define PIN_TEMP    26

#define DISPLAY_WIDTH   128
#define DISPLAY_HEIGHT  32
#define I2C_ADDRESS     0x3C
#define I2C_FREQ        400000
#define SDA_PIN         4
#define SDC_PIN         5
#define I2C             i2c0

static ssd1306_t disp;

// nastavení I2C sběrnice
void setup_i2c(void)
{
    // nelze použít i2c_default -- aplikace bude chodit nekorektně (zajímavá chyba)
    i2c_init(I2C, I2C_FREQ);
    gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(SDC_PIN, GPIO_FUNC_I2C);
    // Pull-up rezistory jsou v displeji, netřeba nastavovat
    gpio_pull_up(SDA_PIN);
    gpio_pull_up(SDC_PIN);
}

void zapnuti_displeje( void )
{
    ssd1306_init(&disp, DISPLAY_WIDTH, DISPLAY_HEIGHT, I2C_ADDRESS, I2C); // inicializace
    ssd1306_poweron(&disp); // zapnutí displeje
    ssd1306_clear(&disp);   // vymazání displeje
    ssd1306_contrast(&disp,0x50);
    ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, "Mereni teploty.");
    ssd1306_show(&disp); // zobrazíme písmenka na displeji
}

uint32_t cteni_napeti(void)
{
uint32_t sum = 0;
uint32_t avg = 0;
uint16_t h[64] = {0};

    for(int i=0; i<64; i++) {
        h[i] = adc_read();
        sum += h[i];
    }
    avg = sum*10/64;
return avg;
}

static char buf1[128];
static char buf2[128];

int main()
{
float temp_voltage = 0.0;
float temp_voltage_old = 0.0;
float teplota = 0.0;
float teplota_old = 0.0;

    //stdio_init_all();
    // linearni regrese:     t = -124.4532*u + 273.80
    // polynomialni regrese: t = -30.401 * u^3 + 255.088 * u^2 - 787.39 *u + 815.054

    memset(buf1,'\0',128);
    memset(buf2,'\0',128);

// nastavení displeje SSD1306
    setup_i2c();
    zapnuti_displeje();
    sleep_ms(10);
// nastavení PWM měniče na malý šum
    gpio_set_function( 23, GPIO_FUNC_SIO );
    gpio_set_dir( 23, true );
    gpio_put(23, 1);

//  bzučák
    gpio_init(PIN_LED);
    gpio_set_dir(PIN_LED, GPIO_OUT);
    gpio_set_function(PIN_BZUCAK, GPIO_FUNC_PWM);
    uint slice_num = pwm_gpio_to_slice_num(PIN_BZUCAK);
    pwm_set_enabled(slice_num, true);
    
// nastavení ADC
    adc_init();
    adc_gpio_init(PIN_TEMP);
    adc_select_input(0);
    // hodnota napětí na termistoru
    temp_voltage_old = cteni_napeti() * 0.3265f / (1 << 12);
    teplota_old = temp_voltage_old*(-124.4532) + 265.80;
    sprintf(buf1, "Nap:%7.4f V", temp_voltage_old);
    sprintf(buf2, "Tep:%5.1f C", temp_voltage_old);
    // výstup na displej
    ssd1306_clear(&disp);
    ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, buf1);
    ssd1306_draw_string_with_font(&disp, 0, 16, 1, font_spleen_16x8a, buf2);
    ssd1306_show(&disp); // zobrazíme písmenka na displeji

    while ( true ) {
        adc_select_input(0);
        temp_voltage = cteni_napeti() * 0.3265f / (1<<12);
        teplota = temp_voltage*(-124.4532) + 265.80;
        if( temp_voltage != temp_voltage_old ) {
            memset(buf1,'\0',128);
            memset(buf2,'\0',128);
            sprintf(buf1, "Nap:%7.4f V", temp_voltage);
            sprintf(buf2, "Tep:%5.1f C", teplota);
            // výstup na displej
            ssd1306_clear(&disp);
            ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, buf1);
            ssd1306_draw_string_with_font(&disp, 0, 16, 1, font_spleen_16x8a, buf2);
            ssd1306_show(&disp); // zobrazíme písmenka na displeji
        }
        temp_voltage_old = temp_voltage;
        teplota_old = teplota;
        if( teplota > 30.0 && teplota < 45.0) {
            sirena();
        }
        sleep_ms(1000);
    }
    ssd1306_deinit(&disp);
    
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(teplomerk C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(teplomerk
    teplomerk.c
    lib/ssd1306.c
)
target_link_libraries(
    teplomerk
    hardware_pwm
    hardware_i2c
    hardware_adc
    hardware_irq
    pico_time
    pico_multicore
    pico_stdlib
    )
pico_enable_stdio_usb(teplomerk 0)
pico_enable_stdio_uart(teplomerk 0)

pico_add_extra_outputs(teplomerk)

Konstrukce v0.1

IMG 20260228 173131

IMG 20260228 173124

IMG 20260228 173141

IMG 20260228 173141

Zdrojové kódy v0.2

Nastavení budíku a spouštění sirény se děje na core1. Tlačítkem se zapíná program vaření vajíček naměkko (zatím při teplotě 30˚C po dobu 20 sekund). Program funguje korektně.

teplomerk.c
/* teplomerk.c verze 0.2
 * (c) Jirka Chráska 2026, <jirka@lixis.cz>
 * BSD 3 clause licence
 */
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/i2c.h"
#include "hardware/pwm.h"
#include "lib/ssd1306.h"
#include "lib/font_spleen_8x5.h"
#include "lib/font_spleen_16x8a.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "pico/time.h"
#include "pico/types.h"
#include "pico/multicore.h" // pico multicore knihovna
#include "hardware/structs/iobank0.h"
#include "hardware/timer.h"
#include "hardware/irq.h"



#define PIN_BZUCAK  15 
#define PIN_LED     14
#define PIN_VAJICKA 16


// budík
volatile bool spustit_sirenu = false;
static   int  vajicka_namekko_timeout = 20; // normálně 2 minuty 50 sekund
// Použijeme budík 0
#define ALARM_NUM 0
#define ALARM_IRQ timer_hardware_alarm_get_irq_num(timer_hw, ALARM_NUM)

// obsluha přerušení vyvolaná budíkem
static void alarm_irq(void) {
    // Vynulujeme IRQ budíku
    hw_clear_bits(&timer_hw->intr, 1u << ALARM_NUM);

    spustit_sirenu = true;
}

// budík
// maximální budíku doba je 72 minut
static void alarm_in_us(uint32_t delay_us) {
    // Nastavíme přerušení pro náš budík 
    hw_set_bits(&timer_hw->inte, 1u << ALARM_NUM);
    // Nastavíme obsluhu přerušení 
    irq_set_exclusive_handler(ALARM_IRQ, alarm_irq);
    // Spustíme přerušení 
    irq_set_enabled(ALARM_IRQ, true);
    
    // Alarm má pouze 32 bitů, takže pokud se snažíte zpoždění o více
    // než je tato délka, je třeba být opatrný a sledovat horní
    // bity
    uint64_t target = timer_hw->timerawl + delay_us;

    // zapíšeme spodních 32 bitů do cílového času, kde se má přerušení spustit
    timer_hw->alarm[ALARM_NUM] = (uint32_t) target;
}

// gpio události
uint32_t gpio_get_events(uint gpio)
{
int32_t mask = 0xF << 4 * ( gpio % 8 );
return (io_bank0_hw->intr[gpio / 8] & mask) >> 4 * ( gpio % 8 );
}

void gpio_clear_events(uint gpio, uint32_t events)
{
    gpio_acknowledge_irq(gpio, events);
}


// Funkce pro zvuk sirény
void hraj_ton(uint frekvence) 
{
    if (frekvence == 0) {
        pwm_set_gpio_level(PIN_BZUCAK, 0);
        return;
    }
    uint slice_num = pwm_gpio_to_slice_num(PIN_BZUCAK);
    uint32_t clock = 125000000;
    uint32_t divider = clock / (frekvence * 4096) + 1;
    uint32_t top = clock / (divider * frekvence) - 1;
    pwm_set_clkdiv(slice_num, divider);
    pwm_set_wrap(slice_num, top);
    pwm_set_gpio_level(PIN_BZUCAK, top / 2);
}

void sirena(void)
{
int i = 0;
    while(spustit_sirenu) {
        gpio_put(PIN_LED, i%2);
        hraj_ton(600);
        sleep_ms(80);
        hraj_ton(500);
        sleep_ms(80);
        hraj_ton(450);
        sleep_ms(80);
        hraj_ton(0);
        i++;
    }
    gpio_put(PIN_LED,0);
}

// pin pro měření napětí termistoru
#define PIN_TEMP    26

// pro OLED displej SSD1306
#define DISPLAY_WIDTH   128
#define DISPLAY_HEIGHT  32
#define I2C_ADDRESS     0x3C
#define I2C_FREQ        400000
#define SDA_PIN         4
#define SDC_PIN         5
#define I2C             i2c0

static ssd1306_t disp;

// nastavení I2C sběrnice
void setup_i2c(void)
{
    // nelze použít i2c_default -- aplikace bude chodit nekorektně (zajímavá chyba)
    i2c_init(I2C, I2C_FREQ);
    gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(SDC_PIN, GPIO_FUNC_I2C);
    // Pull-up rezistory jsou v displeji, netřeba nastavovat
    gpio_pull_up(SDA_PIN);
    gpio_pull_up(SDC_PIN);
}

void zapnuti_displeje( void )
{
    ssd1306_init(&disp, DISPLAY_WIDTH, DISPLAY_HEIGHT, I2C_ADDRESS, I2C); // inicializace
    ssd1306_poweron(&disp); // zapnutí displeje
    ssd1306_clear(&disp);   // vymazání displeje
    ssd1306_contrast(&disp,0x50);
    ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, 
                                  "M\xec\xf8" "en\xed teploty.");
    ssd1306_show(&disp); // zobrazíme písmenka na displeji
}

// čtení napětí z ADC převodníku, průměr ze 64 hodnot 
uint32_t cteni_napeti(void)
{
uint32_t sum = 0;
uint32_t avg = 0;
uint16_t h[64] = {0};

    for(int i=0; i<64; i++) {
        h[i] = adc_read();
        sum += h[i];
    }
    avg = sum*10/64;
return avg;
}

static char buf1[128];
static char buf2[128];

static float teplota = 0.0;
static float teplota_old = 0.0;
static float teplota_vajicka = 30.0;
static bool  vajicka_namekko = false;   // pro program vajíčka naměkko
static bool  budik_nastaven = false;
//static bool  budik_zvoni = false;

// tady poběží obsluha při programu vajíčka naměkko
void core1_entry()
{
    while(1) {
        if( vajicka_namekko ) {
            if( teplota > teplota_vajicka) { 
                gpio_put(PIN_LED, 1);
                if(budik_nastaven == false) { // nastavíme budík
                   spustit_sirenu = false;
                   alarm_in_us(1000000*vajicka_namekko_timeout);
                   budik_nastaven = true;
                }
                
            } else {
                gpio_put(PIN_LED,0);
            }
            if( spustit_sirenu ) {
                sirena();    
            }
        } 
        sleep_ms(100);
    }
}

int main()
{
float temp_voltage = 0.0;
float temp_voltage_old = 0.0;

// linearni regrese:     t = -124.4532*u + 273.80
// polynomialni regrese: t = -30.401 * u^3 + 255.088 * u^2 - 787.39 *u + 815.054

    memset(buf1,'\0',128);
    memset(buf2,'\0',128);

// nastavení displeje SSD1306
    setup_i2c();
    zapnuti_displeje();
    sleep_ms(10);
// nastavení PWM měniče na malý šum
    gpio_set_function( 23, GPIO_FUNC_SIO );
    gpio_set_dir( 23, true );
    gpio_put(23, 1);

//  bzučák
    gpio_init(PIN_LED);
    gpio_set_dir(PIN_LED, GPIO_OUT);
    gpio_set_function(PIN_BZUCAK, GPIO_FUNC_PWM);
    uint slice_num = pwm_gpio_to_slice_num(PIN_BZUCAK);
    pwm_set_enabled(slice_num, true);
    
// tlačítko spuštění programu na vaření vajíček naměkko
// pin je na jedničce, při stisknutí tlačítka jde na nulu
    gpio_init(PIN_VAJICKA);
    gpio_clear_events( PIN_VAJICKA, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL );
    gpio_set_dir(PIN_VAJICKA, GPIO_IN);
    gpio_pull_up(PIN_VAJICKA);
    
// nastavení ADC
    adc_init();
    adc_gpio_init(PIN_TEMP);
    adc_select_input(0);
    // hodnota napětí na termistoru
    temp_voltage_old = cteni_napeti() * 0.3265f / (1 << 12);
    teplota_old = temp_voltage_old*(-124.4532) + 265.80;
    sprintf(buf1, "Nap:%7.4f V", temp_voltage_old);
    sprintf(buf2, "Tep:%5.1f C", temp_voltage_old);
    // výstup na displej
    ssd1306_clear(&disp);
    ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, buf1);
    ssd1306_draw_string_with_font(&disp, 0, 16, 1, font_spleen_16x8a, buf2);
    ssd1306_show(&disp); // zobrazíme písmenka na displeji
// resetujeme  core1 a uvedeme funkci, která bude spuštěna: core1_entry 
    multicore_launch_core1(core1_entry); 
    gpio_clear_events( PIN_VAJICKA, GPIO_IRQ_EDGE_FALL );
    
    while ( true ) {
        if( gpio_get(PIN_VAJICKA)==0) {
            gpio_clear_events(PIN_VAJICKA, GPIO_IRQ_EDGE_FALL);
            vajicka_namekko = (vajicka_namekko==true) ? false : true;
            if( vajicka_namekko ) {
                budik_nastaven = false;
            } else {
                budik_nastaven = false;
                spustit_sirenu = false;
            }
            ssd1306_clear(&disp);
            ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, 
                                          "Vaj\xed\xe8ka nam\xecko");
            ssd1306_draw_string_with_font(&disp, 0, 16, 1, font_spleen_16x8a, 
                                          vajicka_namekko?"zapnuto":"vypnuto");
            ssd1306_show(&disp); // zobrazíme písmenka na displeji
            sleep_ms(1500);
        }
        adc_select_input(0);
        temp_voltage = cteni_napeti() * 0.3265f / (1<<12);
        teplota = temp_voltage*(-124.4532) + 265.80;
        if( temp_voltage != temp_voltage_old ) {
            memset(buf1,'\0',128);
            memset(buf2,'\0',128);
            sprintf(buf1, "Nap:%7.4f V", temp_voltage);
            sprintf(buf2, "Tep:%5.1f \xb0" "C", teplota);
            // výstup na displej
            ssd1306_clear(&disp);
            ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, buf1);
            ssd1306_draw_string_with_font(&disp, 0, 16, 1, font_spleen_16x8a, buf2);
            ssd1306_show(&disp); // zobrazíme písmenka na displeji
        }
        temp_voltage_old = temp_voltage;
        teplota_old = teplota;
        
        sleep_ms(100);
    }
    ssd1306_deinit(&disp);
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(teplomerk C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(teplomerk
    teplomerk.c
    lib/ssd1306.c
)
target_link_libraries(
    teplomerk
    hardware_pwm
    hardware_i2c
    hardware_adc
    hardware_irq
    pico_time
    pico_multicore
    pico_stdlib
    )
pico_enable_stdio_usb(teplomerk 0)
pico_enable_stdio_uart(teplomerk 0)

pico_add_extra_outputs(teplomerk)

Konstrukce v0.2

Doplněné schéma zapojení verze 0.2

schema zapojeni2

Konstrukce verze 0.2

IMG 20260301 002855

Problém je se sirénou. Jakmile začne siréna řvát, tak se zblázní napájecí napětí 3.3V a teploměr ukazuje nesmysly. Občas se nechce rozsvítit displej. Mám takový pocit, že nepájivé pole je poněkud nekvalitní anebo mám příliš dlouhé dráty.

Zdroje a odkazy