V tomto článku se naučíme automaticky zapínat a vypínat světla podle okolního osvětlení a detekce pohybu osob v prostoru.
Princip měření intenzity osvětlení spočívá v tom, že měříme napětí pomocí ADC na děliči s fotorezistorem (R1 a R2) a současně měříme napětí na proměnném referenčním děliči napětí (RV1). Tato napětí potom budeme v programu porovnávat. Odpor fotorezistoru závisí na vnějším osvětlení. Osvětlený fotorezistor má malý odpor (stovky Ohmů), fotorezistor ve tmě má odpor okolo 15 kiloOhmů. Bude-li napětí na fotorezistoru větší než na referenčním děliči, víme že je tma. Bude-li napětí na fotorezistoru menší než na referenčním děliči, víme že je světlo. Potenciometr RV1 referenčního dělič nám umožňuje nastavit hranici mezi světlem a tmou jak potřebujeme.
Světla budeme spínat pomocí 2 kanálového reléového modulu. (Je možné použít i jenom jedno relé, ale neměl jsem ho k dispozici v době konstrukce).
Pohyb osob v osvětlovaném prostoru budeme detekovat mikrovlnným Doplerovským radarem HW-MS03.
Schéma zapojení
Program
Spínání světla je řešeno v obsluze přerušení PohybIRQHandler().
Při detekci pohybu se spustí počítání času (ať už na začátku nebo na konci).
// čas
static uint64_t t = 0;
// časová prodleva mezi posledním pohybem a zhasnutím světla
static uint64_t TIMEOUT = 50000000;
void PohybIRQHandler( uint gpio, uint32_t events )
{
if( (gpio == POHYB_PIN) && (events & GPIO_IRQ_EDGE_FALL) ) {
// detekován začátek pohybu
zacatek_pohybu = true;
gpio_put( POHYB_LED, 1 );
// zapneme počítání času
t = time_us_64();
if( tma ) {
svitim = true;
// zaplneme relé pokud je tma
gpio_put(RELE_PIN, RELE_ON);
}
}
if( (gpio == POHYB_PIN) && (events & GPIO_IRQ_EDGE_RISE) ) {
// konec detekce pohybu
konec_pohybu = true;
gpio_put( POHYB_LED, 0 );
// zapneme počítání času
t = time_us_64();
}
}
Pokud ve funkci main() vyprší nastavený časový interval, zhasneme světla.
while(1) {
// ...
if( svitim ) {
if( (time_us_64() - t) > TIMEOUT ) {
// uplynul timeout, vypínáme relé
gpio_put( RELE_PIN, RELE_OFF );
svitim = false;
konec_pohybu = false;
zacatek_pohybu = false;
}
}
}
Jako vylepšení programu by se mohl TIMEOUT nastavovat tlačítky, připojené na nějaké GPIO piny.
Potom by ale bylo nutné ukládat nastavenou hodnotu na flash pamět Pica.
Jak se to dělá je popsáno zde.
svetlo.c
/* Automatické rozsvěcení světel
* svetlo.c
* (c) Jirka Chráska 2026, <jirka@lixis.cz>
* BSD 3-Clause License
Copyright (c) 2026, Jirka Chráska
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/adc.h"
#include "hardware/structs/iobank0.h"
// pro OLED displej SSD1306
#include "ssd1306.h"
#include "font_spleen_8x5.h"
#include "font_spleen_16x8a.h"
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 32
#define I2C_ADDRESS 0x3C
#define I2C_FREQ 400000
#define SLEEPTIME 55
#define SDA_PIN 4
#define SDC_PIN 5
#define I2C i2c0
ssd1306_t disp;
// nastavení I2C sběrnice pro displej OLED SSD1306
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
}
// zapnutí a test OLED displeje SSD1306
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);
// háčkovaná a čátkovaná písmena je nutno zadávat v ISO-8859-2
ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a,
"Osv" "\xec" "tlen" "\xed" ".");
ssd1306_show(&disp); // zobrazíme písmenka na displeji
}
// události na GPIO
uint32_t gpio_get_events(uint gpio)
{
int32_t mask = 0xF << 4 * ( gpio % 8 );
return (iobank0_hw->intr[gpio / 8] & mask) >> 4 * ( gpio % 8 );
}
void gpio_clear_events(uint gpio, uint32_t events)
{
gpio_acknowledge_irq(gpio, events);
}
// pin pro dělič s fotoresistorem
#define MERENI_PIN 26
#define ADC_INPUT 0
// pin pro nastavení úrovně osvětlení
#define REFERENCE_PIN 27
#define REFERENCE_INPUT 1
#define REF_VOLTAGE 3.25f
// výstupní pin pro relé
#define RELE_PIN 22
// relé má obrácenou logiku
#define RELE_ON 0
#define RELE_OFF 1
#define POHYB_PIN 16
#define POHYB_LED 25
// zprůměrované měřění napětí
float cti_napeti(uint vstup)
{
float napeti[10];
float vysledek = 0.0f;
adc_select_input(vstup);
for(int i=0; i<10; i++) {
napeti[i] = adc_read() * REF_VOLTAGE / (1<<12);
sleep_us(10);
}
for(int j=0; j<10; j++) {
vysledek += napeti[j];
}
vysledek /= 10.0;
return vysledek;
}
static bool svitim = false;
static bool tma = false;
static bool zacatek_pohybu = false;
static bool konec_pohybu = false;
static uint64_t t = 0;
// obsluha přerušení na pinu POHYB_PIN od radarového čidla
void PohybIRQHandler( uint gpio, uint32_t events )
{
if( (gpio == POHYB_PIN) && (events & GPIO_IRQ_EDGE_FALL) ) {
// detekován začátek pohybu
zacatek_pohybu = true;
gpio_put( POHYB_LED, 1 );
// zapneme počítání času
t = time_us_64();
if( tma ) {
svitim = true;
gpio_put(RELE_PIN, RELE_ON);
}
}
if( (gpio == POHYB_PIN) && (events & GPIO_IRQ_EDGE_RISE) ) {
// konec detekce pohybu
konec_pohybu = true;
gpio_put( POHYB_LED, 0 );
// zapneme počítání času
t = time_us_64();
}
}
// časová prodleva mezi posledním pohybem a zhasnutím světla
static uint64_t TIMEOUT = 50000000;
int main()
{
char buffer[128];
uint16_t raw;
const float conversion_factor = 3.3f / (1<<12);
float reference = 0.0;
float napeti = 0.0;
// debugování přes USB
stdio_init_all();
sleep_ms(1200);
memset(buffer,'\0',128);
// nastavení výstupu na relé
gpio_init(RELE_PIN);
gpio_set_dir(RELE_PIN,GPIO_OUT);
gpio_put(RELE_PIN,RELE_OFF);
// nastavení vstupu od radaru
gpio_init(POHYB_PIN);
gpio_set_dir(POHYB_PIN,GPIO_IN);
gpio_clear_events( POHYB_PIN, GPIO_IRQ_LEVEL_LOW | GPIO_IRQ_EDGE_RISE
| GPIO_IRQ_LEVEL_HIGH | GPIO_IRQ_EDGE_FALL );
// nastavíme přerušní a jeho obsluhu
gpio_set_irq_enabled_with_callback( POHYB_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL,
true, &PohybIRQHandler );
// indikace pohybu
gpio_init(POHYB_LED);
gpio_set_dir(POHYB_LED, GPIO_OUT);
gpio_put(POHYB_LED,1);
sleep_ms(500);
gpio_put(POHYB_LED,0);
// inicializace a konfigurace 0LED displeje
// nastavení displeje SSD1306
setup_i2c();
zapnuti_displeje();
sleep_ms(10);
// nastavení ADC
adc_init();
adc_gpio_init(MERENI_PIN);
adc_gpio_init(REFERENCE_PIN);
printf("\nAutomatické rozsvícení světel.\n");
// měření osvetleni
while(1) {
memset(buffer,'\0',128);
napeti = cti_napeti(ADC_INPUT);
reference = cti_napeti(REFERENCE_INPUT);
if( napeti > reference ) {
tma = true;
} else {
tma = false;
}
ssd1306_clear(&disp);
sprintf(buffer,"S:%4.2fV %s", napeti, napeti > reference ? "temno" : "sv\xectlo");
ssd1306_draw_string_with_font(&disp,0,0,1, font_spleen_8x5, buffer);
sprintf(buffer,"R:%4.2fV %s%s%s",reference,
zacatek_pohybu?"Z":" ", konec_pohybu?"K":" ", svitim?"S":" ");
ssd1306_draw_string_with_font(&disp,0,10,1, font_spleen_8x5, buffer);
sprintf(buffer,"t:%lld",time_us_64() - t);
ssd1306_draw_string_with_font(&disp,0,20,1, font_spleen_8x5, buffer);
ssd1306_show(&disp);
if( svitim ) {
if( (time_us_64() - t) > TIMEOUT ) {
// uplynul timeout, vypínáme relé
gpio_put( RELE_PIN, RELE_OFF );
svitim = false;
konec_pohybu = false;
zacatek_pohybu = false;
}
}
sleep_ms( 200 );
}
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
# initialize the SDK based on PICO_SDK_PATH
# note: this must happen before project()
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(osvetleni)
set(CMAKE_C_STANDARD 11)
# initialize the Raspberry Pi Pico SDK
pico_sdk_init()
# rest of your project
add_executable(osvetleni
svetlo.c
disp/ssd1306.c
)
target_include_directories(osvetleni
PUBLIC
${CMAKE_CURRENT_LIST_DIR}/disp
)
target_link_libraries(osvetleni
pico_stdlib
hardware_i2c
hardware_adc
)
pico_enable_stdio_usb(osvetleni 1)
pico_enable_stdio_uart(osvetleni 0)
# create map/bin/hex/uf2 file in addition to ELF.
pico_add_extra_outputs(osvetleni)
Knihovna pro OLED displej SSD1306
Konstrukce
Prototyp spíná 12V LED lampu o příkonu 1.7W napájenou z laboratorního zdoje. Reléovým modulem můžeme spínat do napětí 250V střídavého a proudy do 10 A. Druhé relé můžeme klidně připojit na GPIO pin 22 a spínat současně ještě něco jiného.





