Pro testovaní přerušení jsem potřeboval generátor obdélníkových puslů s nastavitelnou frekvencí a střídou pulsů. Nechtělo se mi čekat, až mi přijde poštou, tak jsem si ho sestrojil. Potřeboval jsem Pico, LCD displej 1602, 4 tlačítka, 2 rezistory 12k, breadboard a několik drátků. Displej jsem už měl naprogramovaný z dřívějška, takže stačilo přidat do programu jeden céčkový zdroják a zapojit displej. Žlutými tlačítky měním frekvenci po 1 Hz. Červenými tlačítky měním střídu po 1%. Stav tlačítek je zpracováván pomocí přerušení.

Zapojení

IMG 20241022 170642

Pico vlevo nahoře je pro testování, Pico vpravo nahoře je datový analyzátor a Pico vpravo dole je generátor signálu.

Zdrojové soubory

CMakeLists.txt
cmake_minimum_required(VERSION 3.12)

include(pico_sdk_import.cmake)

project(generator)

pico_sdk_init()

add_executable(generator
    generator1.c
    lcd_czech_chars.c
)

target_link_libraries(generator
    pico_stdlib
    hardware_pwm
    hardware_i2c
)

pico_enable_stdio_usb(generator 1)
pico_enable_stdio_uart(generator 0)

pico_add_extra_outputs(generator)
generator1.c
/* generator.c
 * Generator PWM pulsu s ovladanim RPi Pico
 * (c) Jirka Chráska 2024, <jirka@lixis.cz> All rights reserved.
 *
 * BSD licence
 */

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "hardware/gpio.h"
#include "lcd_czech_chars.h"

const uint FREQ_PLUS  = 12; // tlačítko pro přidání frekvence
const uint FREQ_MINUS = 13; // tlačítko pro ubrání frekvence
const uint DUTY_PLUS  = 14; // tlačítko pro přidání střídy
const uint DUTY_MINUS = 15; // tlačítko pro ubrání střídy
const uint PWM_PIN    = 16; // GPIO pin, kde se generují pulsy

static uint freq = 1000;
static uint duty = 50;
static bool zmena = false;

uint32_t pwm_set_freq_duty( uint slice_num, uint chan, uint32_t f, int d)
{
    uint32_t clock     = 125000000;
    uint32_t divider16 = clock / f / 4096 + (clock % (f * 4096) != 0);
    if( divider16 / 16 == 0) {
        divider16 = 16;
    }
    uint32_t wrap = clock * 16 / divider16 / f - 1;
    pwm_set_clkdiv_int_frac( slice_num, divider16/16, divider16 & 0xf );
    pwm_set_wrap( slice_num, wrap );
    pwm_set_chan_level( slice_num, chan, wrap * d/100 );
    return wrap;
}

void MyIRQHandler( uint gpio, uint32_t events )
{
    switch( gpio ) {
        case FREQ_PLUS:  freq++; zmena=true; break;
        case FREQ_MINUS: if(freq > 0) {freq--; zmena=true;} break;
        case DUTY_PLUS:  if(duty < 101) {duty++; zmena=true;} break;
        case DUTY_MINUS: if(duty > 0) {duty--; zmena=true; } break;
        default: zmena=false; return;
    }
}

int udelej_zmeny()
{
char buffer[40];
    // zmena pwm
    uint slice   = pwm_gpio_to_slice_num (PWM_PIN);
    uint channel = pwm_gpio_to_channel (PWM_PIN);
    pwm_set_freq_duty(slice, channel, freq, duty);
    pwm_set_enabled (slice, true);
    // zobraz na displeji
    snprintf(buffer,40,"Freq: %5d Hz", freq);
    printf("%s ", buffer);
    lcd_home();
    lcd_set_cursor(0,0);
    cz_print(buffer);
    snprintf(buffer,40,"Duty:   %3d %%", duty);
    lcd_set_cursor(1,0);
    cz_print(buffer);
    printf("%s \n", buffer);
    zmena = false;
}

int main(void)
{
    // konfigurace debugování přes USB
    stdio_init_all();
    sleep_ms(2000);
    // konfigurace displeje
    init_gpio();
    lcd_characters_init();
    lcd_init();
    lcd_clear();

    // pwm
    gpio_set_function(PWM_PIN, GPIO_FUNC_PWM);
    zmena = true;
    udelej_zmeny();

    // frekvence přidat
    gpio_set_function( FREQ_PLUS, GPIO_FUNC_SIO );
    gpio_set_dir( FREQ_PLUS, false );
    gpio_pull_up( FREQ_PLUS );
    gpio_set_irq_enabled_with_callback( FREQ_PLUS, GPIO_IRQ_EDGE_FALL, true, &MyIRQHandler );
    // frekvence ubrat
    gpio_set_function( FREQ_MINUS, GPIO_FUNC_SIO );
    gpio_set_dir( FREQ_MINUS, false );
    gpio_pull_up( FREQ_MINUS );
    gpio_set_irq_enabled_with_callback( FREQ_MINUS, GPIO_IRQ_EDGE_FALL, true, &MyIRQHandler );

    // duty přidat
    gpio_set_function( DUTY_PLUS, GPIO_FUNC_SIO );
    gpio_set_dir( DUTY_PLUS, false );
    gpio_pull_up( DUTY_PLUS );
    gpio_set_irq_enabled_with_callback( DUTY_PLUS, GPIO_IRQ_EDGE_FALL, true, &MyIRQHandler );
    // duty ubrat
    gpio_set_function( DUTY_MINUS, GPIO_FUNC_SIO );
    gpio_set_dir( DUTY_MINUS, false );
    gpio_pull_up( DUTY_MINUS );
    gpio_set_irq_enabled_with_callback( DUTY_MINUS, GPIO_IRQ_EDGE_FALL, true, &MyIRQHandler );

    // nekonečná smyčka
    while(1){
        if( zmena ) {
            udelej_zmeny();
        }
        sleep_ms(200);
    }
}
Implementace displeje

lcd_czech_chars.c

Hlavičkový soubor dipleje

lcd_czech_chars.h

Problémy

Řízení pomocí tlačítek s interupty není moc dobré, protože při zákmitu tlačítka se vygeneruje několik přerušení a veličina poskočí o víc, než o jedničku. Budu to muset předělat na řízení pomocí čtecí smyčky, protože potom budu moci delším stisknutím tlačítka měnit veličinu o víc než jedna a plynule.

Možná bude dobré si uložit nastavenou frekvenci a střídu do flash paměti na Picu, abych ji nemusel při každém zapnutí nastavovat znovu. Jak to udělat je popsáno zde: Čtení a zápis dat na vestavěnou flash paměť Raspberry Pi Pico

Jak zjistit velikost programu
$ objdump --all generator.elf | grep flash_binary_end
10008e60 g       .ARM.attributes	00000000 __flash_binary_end