Počítání pomocí optické závory.

Optická závora se skládá z infračervené LED diody a fototranzistoru. Pokud dopadá světlo na fototranzistor, tak je tranzistor otevřený a logická úroveň na něm je nula. Je-li světlo přerušeno, fototranzistor je zavřen a pomocí pull-up rezistoru (interní v Pico) je logická úroveň na něm jedna.

Optická závora se používá třeba k počítání otáček motoru nebo k zjištění toho, zda určitá součást dosáhla dané polohy. My ji budeme používat k počítání otáček motoru.

IMG 20251008 001728

Schéma zapojení

Schéma zapojení

Opticka zavora schema

Optická závora má tři vodiče, jeden vstupní k IR diodě, druhý výstupní z fototranzistoru a třetí je společná zem. Rezistor R1 (470 Ohm) omezuje proud tekoucí fotodiodou na přibližně 5 mA.

Test zapojení vodičů k optické závoře můžeme udělat snadno pomocí multimetru a měření diod. Pokud na dvou vodičích naměříme úbytek napětí okolo 1 V (IR dioda je v propustném směru), potom červený drát z multimetru vede k anodě fotodiody a černý drát je společná zem. Třetí drát je výstup z fototranzistoru.

Test vodičů — červený je anoda IR diody, černý je společná zem

IMG 20251008 014951

Program

Program je velmi jednoduchý. Hlídáme událost náběžná hrana (zastínění fototranzistoru) na vstupním GPIO3 pinu, což znamená že závora byla přerušena. Pomocí pinu GPIO2 můžeme optickou závoru zapínat nebo vypínat. Pokud chceme, aby byla závora zapnutá neustále, přemístíme vodič z GPIO2 pinu na 3.3V (pin 36) a můžeme odstranit kód nastavení výstupního pinu.

tzavora.c
/* Počitadlo s optickou závorou
 * tzavora.c
 * (c) Jirka Chráska 2025, <jirka@lixis.cz>
 */

#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/structs/iobank0.h"
#include <stdio.h>

#define OUT_PIN 2
#define IN_PIN  3

uint32_t pocitadlo = 0;

// přehled událostí na pinu
uint32_t gpio_get_events(uint gpio)
{
    int32_t mask = 0xF << 4 * ( gpio % 8 );
return (iobank0_hw->intr[gpio / 8] & mask) >> 4 * ( gpio % 8 );
}

// vymazání událostí na pinu
void gpio_clear_events(uint gpio, uint32_t events)
{
    gpio_acknowledge_irq(gpio, events);
}

int main()
{
    stdio_init_all();
    sleep_ms(1500);
    printf("Pocitadlo = %d\n", pocitadlo);
    // nastaveni vstupního pinu (fototranzistor)
    gpio_set_function(IN_PIN, GPIO_FUNC_SIO);
    gpio_set_dir(IN_PIN,false);                 
    gpio_pull_up(IN_PIN); // pull-up rezistor musí být nastaven
    
    // nastavení výstupního pinu - zapnutí závory
    gpio_set_function(OUT_PIN, GPIO_FUNC_SIO);
    gpio_set_dir(OUT_PIN, true);                 
    gpio_put(OUT_PIN,true); // spouštíme počítadlo
    
    
    gpio_clear_events(IN_PIN, GPIO_IRQ_EDGE_RISE);
    while (true)  {                         
        uint32_t p = pocitadlo;
        if( (gpio_get_events(IN_PIN) & GPIO_IRQ_EDGE_RISE) ) {
            pocitadlo++;
            gpio_clear_events(IN_PIN, GPIO_IRQ_EDGE_RISE);
        }
        if( pocitadlo > p ) {
            printf("Pocitadlo = %d\n",pocitadlo);
            p = pocitadlo;
        }
    }
}
src/CMakeLists.txt
cmake_minimum_required(VERSION 3.12)

include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(tzavora C CXX ASM)

pico_sdk_init()

# pridani zdrojovych kodu
add_executable(tzavora
    tzavora.c
)

# enable usb output, disable uart output
pico_enable_stdio_usb(tzavora 1)
pico_enable_stdio_uart(tzavora 0)

# create map/bin/hex/uf2 file etc.
pico_add_extra_outputs(tzavora)

target_link_libraries(tzavora pico_stdlib hardware_gpio)

Abychom mohli počítat otáčky motoru, bude potřeba přesunout hlídání náběžné hrany na vstupním pinu na druhé jádro procesoru, abchom mohli na první jádro procesoru osvobodit od nekonečné smyčky a místo toho na něm mohli provozovat řídící program, což bude v další verzi programu.

Nezávislé počítadlo

Problém prvního programu je v tom, že počítadlo neustále testuje náběžnou hranu a Pico tak nemá moc času na jiné věci. Necháme druhé jádro (core1) procerosu RP2040 aby se zabývalo testováním náběžné hrany a zapisovalo impulsy do globální proměnné pocitadlo1. Na prvním jádru (core0) potom může spokojeně běžet náš program a číst si hodnotu počitadla, kdy sám potřebuje.

Globální proměnnou volatile uint32_t pocitadlo1 musíme zabezpečit mutexem (výhradním přístupem) pocitadlo1_mutex. To znamená, že core0 nemůže číst hodnotu pocitadlo1 v ten samý okamžik, když do proměnné pocitadlo1 zapisuje core1. Kdybychom to neudělali a stalo by se že ve stejný okamžik bude core0 číst a core1 zapisovat, tak core0 může přečíst nesmysly.

Slovo volatile v definici proměnné pocitadlo1 říká kompilátoru tolik, že proměnná se může měnit kdykoliv a tudíž ji program musí vždy přečíst znovu a nespoléhat na nějakou zapamatovanou hodnotu v rámci optimalizace.

Inicializace mutexu makrem
auto_init_mutex(pocitadlo1_mutex);

Chce-li core1 zapisovat do proměnné pocitadlo1, musí nejprve získat přístup k proměné voláním funkce mutex_try_enter(). Získá-li mutex, zvětší hodnotu proměnné pocitadlo1 o jedničku a potom mutex uvolní voláním funkce mutex_exit().

Získání mutexu, změna hodnoty a uvolnění mutexu
if( mutex_try_enter(&pocitadlo1_mutex,&owner) ) {
    pocitadlo1++;
    mutex_exit(&pocitadlo1_mutex);
}

Obdobně postupuje core0. Chce-li číst hodnotu proměnné pocitadlo1, musí nejprve získat přístup voláním funkce mutex_try_enter(). Když získá mutex, přečte si hodnotu a mutex potom uvolní voláním funkce mutex_exit().

Mutexy fungují pěkně, pokud po získání mutexu ho zase rychle uvolníme. Pak se nám nestane, že by obě jádra čekala na mutex a program se takzvaně "kousnul".
Získání mutexu, čtení hodnoty a uvolnění mutexu
if(mutex_try_enter(&pocitadlo1_mutex, &owner) ) {
    p = pocitadlo1;
    mutex_exit( &pocitadlo1_mutex );
} else {
    printf("Zamčený mutex\n");
}

Funkci, která má běžet na druhém jádru spouštíme takto:

multicore_launch_core1(pocitadlo);
Celý program src2/zavora_multicore.c
/* Počitadlo s optickou závorou na core1 a s mutexy 
 * zavora_multicore.c
 * (c) Jirka Chráska 2025, <jirka@lixis.cz>
 */

#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/structs/iobank0.h"
#include <stdio.h>
#include "pico/multicore.h"

#define OUT_PIN 2
#define IN_PIN  3

#define LED_PIN 25

volatile uint32_t pocitadlo1 = 0;

auto_init_mutex(pocitadlo1_mutex);

// přehled událostí na pinu
uint32_t gpio_get_events(uint gpio)
{
    int32_t mask = 0xF << 4 * ( gpio % 8 );
return (iobank0_hw->intr[gpio / 8] & mask) >> 4 * ( gpio % 8 );
}

// vymazání událostí na pinu
void gpio_clear_events(uint gpio, uint32_t events)
{
    gpio_acknowledge_irq(gpio, events);
}

// funkce pro pocitadlo pobezi na core1 
void pocitadlo()
{
uint32_t owner;
uint32_t p = 0;
    gpio_clear_events(IN_PIN, GPIO_IRQ_EDGE_RISE);
    while (true)  {                         
        if( (gpio_get_events(IN_PIN) & GPIO_IRQ_EDGE_RISE) ) {
            p++;
            gpio_clear_events(IN_PIN, GPIO_IRQ_EDGE_RISE);
            if( mutex_try_enter(&pocitadlo1_mutex,&owner) ) {
                pocitadlo1 = p;
                mutex_exit(&pocitadlo1_mutex);
            }
        }
    }
}

int main()
{
bool led_stav = true;
uint32_t p;
uint32_t owner;
    
    stdio_init_all();
    sleep_ms(1500);
    printf("Pocitadlo = %d\n", pocitadlo);
    // nastaveni vstupního pinu (fototranzistor)
    gpio_set_function(IN_PIN, GPIO_FUNC_SIO);
    gpio_set_dir(IN_PIN,false);                 
    gpio_pull_up(IN_PIN); // pull-up rezistor musí být nastaven
    
    // nastavení výstupního pinu - zapnutí závory
    gpio_set_function(OUT_PIN, GPIO_FUNC_SIO);
    gpio_set_dir(OUT_PIN, true);                 
    gpio_put(OUT_PIN,true); 
    
    // nastaveni LED
    gpio_set_function(LED_PIN, GPIO_FUNC_SIO);
    gpio_set_dir(LED_PIN, true);                 
    gpio_put(LED_PIN,led_stav); 
    
    // spouštíme počítadlo na druhém jádru (core1)
    multicore_launch_core1(pocitadlo);
    
    // tady si můžeme dělat co chceme
    // po jedné sekundě vypisujeme stav počítadla a blikáme LEDkou
    while( true ) {
        // pokud chceme cist pocitadlo1, musime ziskat mutex
        if(mutex_try_enter(&pocitadlo1_mutex, &owner) ) {
            p = pocitadlo1;
            mutex_exit( &pocitadlo1_mutex );
        } else {
            printf("Zamčený mutex\n");
        }
        printf("Stav pocitadla je: %d\n",p);
        gpio_put(LED_PIN, led_stav);
        led_stav = led_stav ? false : true;
        sleep_ms(1000);
    }
return 0;     
}
src2/CMakeLists.txt pro multicore verzi
cmake_minimum_required(VERSION 3.12)

include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(tzavora2 C CXX ASM)

pico_sdk_init()

# pridani zdrojovych kodu
add_executable(tzavora2
    zavora_multicore.c
)

# enable usb output, disable uart output
pico_enable_stdio_usb(tzavora2 1)
pico_enable_stdio_uart(tzavora2 0)

# create map/bin/hex/uf2 file etc.
pico_add_extra_outputs(tzavora2)

target_link_libraries(tzavora2 pico_stdlib hardware_gpio pico_multicore)

Použití kódu pro odrazové čidlo

Kód lze beze změn pro odrazové čidlo na obrázcích.

IMG 20251009 022157

IMG 20251009 022140

Potenciometrem nastavíme citlivost čidla.

Tabulka 1. Zapojení čidla
čidlo RPi Pico

VCC

3.3V

GND

0V zem

D0

GPIO2

A0

GPIO3 nebo nic

Zdroje a odkazy

pico_e-paper  — rychloměr na kolo.