Hobby serva, jaká se používají v modelech na dálkové ovládání, jsou velmi levná a snadno se používají a připojují se standardním PWM protokolem. Serva nejsou hnací motory, ale polohovací motory. To znamená, že se neotáčejí nastavenou rychlostí, ale pohybují se do určitého úhlu nebo polohy.

Servo je motor, obvykle kartáčový stejnosměrný motor, se zpětnovazebním senzorem pro polohu, obvykle jednoduchým proměnným rezistorem (potenciometrem) připojeným k hřídeli. Výstup je obvykle přes sadu ozubených kol, která snižuje rychlost otáčení a zvyšuje točivý moment. Motor otáčí ozubenými koly, a tím i hřídelí, dokud potenciometr nedosáhne požadovaného nastavení, a hřídel tak nedosáhne požadovaného úhlu/polohy. Základní servo má pouze tři připojení: zem, napájecí vodič a signální vodič. Použité barvy se liší, ale napájecí vodič je obvykle červený, zem je obvykle černá nebo hnědá a signální vodič je bílá, žlutá nebo oranžová. Pokud je namontován standardní J-konektor, pak je vodič nejblíže k zářezu, pin 3, signál, prostřední vodič, pin 2, 5V a vnější vodič, pin 1, zem.

Napájecí kabel musí být připojen k 5V zdroji, který je schopen dodat dostatečný proud pro chod motoru – až 500mA nebo více v závislosti na servu. Dobrou zprávou je, že signální vedení serva obecně potřebuje velmi malý proud, i když teoreticky je nutné jej přepínat mezi 0 a 5V pomocí PWM signálu.

Schema připojení serva k Picu

servo

Můžete předpokládat, že signální vedení musí být řízeno jako napěťová zátěž, a proto je vhodný způsob řízení serva:

  • Kladný vodič serva musí být připojen k externímu 5V zdroji napájení.

  • Rezistor R1 s odporem 10 kOhm může být u většiny serv mnohem větší – často funguje 47 kOhm. Rezistor 5,6 kOhm omezuje proud báze na něco málo pod 0,5 mA.

Všimněte si však, že pokud používáte jeden ovladač s tranzistorem, jako je ten výše uvedený, je vstup invertován.

Toto je správný způsob řízení serva, ale téměř ve všech případech můžete signál serva řídit přímo z 3.3V GPIO linky pomocí 1K rezistoru pro omezení proudu, pokud se servo pokazí. Některá serva budou fungovat i s motorem připojeným k 3.3V, ale s mnohem nižším točivým momentem.

Nyní už jen musíme nastavit PWM linku tak, aby produkovala 20ms pulzy s šířkou pulzu v rozmezí od 0,5 ms do 2,5 ms – tj. s pracovním cyklem 2,5 až 12,5 %.

Opět je jednodušší použít strukturu pro reprezentaci aktuálního stavu serva a vytvořit funkce pro změnu tohoto stavu:

Struktura Servo
typedef struct
{
    uint gpio;
    uint slice;
    uint chan;
    uint speed;
    uint resolution;
    bool on;
    bool invert;
} Servo;

Tato struktura obsahuje pole pro specifikaci inverze výstupu. Inicializace je snadná:

void ServoInit(Servo *s, uint gpio, bool invert)
{
    gpio_set_function(gpio, GPIO_FUNC_PWM);
    s->gpio  = gpio;
    s->slice = pwm_gpio_to_slice_num(gpio);
    s->chan  = pwm_gpio_to_channel(gpio);
    pwm_set_enabled(s->slice, false);
    s->on    = false;
    s->speed = 0;
    s->resolution = pwm_set_freq_duty(s->slice, s->chan, 50, 0);
    pwm_set_duty(s->slice, s->chan, 250);
    if (s->chan)
    {
        pwm_set_output_polarity(s->slice, false, invert);
    }
    else
    {
        pwm_set_output_polarity(s->slice, invert, false);
    }
    s->invert = invert;
}

Všimněte si, že jsme nastavili frekvenci na 50 Hz. Pokud chcete pracovat s nestandardním servem, můžete to změnit nebo nastavit. Také potřebujeme verzi funkce s vyšší přesností pro nastavení pracovního cyklu.

Funkce uvedená dříve pracuje s procenty, ale protože rozsah procent pro servo je pouze 2,5 % až 12,5 %, musíme zadat dvě desetinná místa, a proto musíme pracovat s procenty*100:

void pwm_set_dutyH(uint slice_num, uint chan, int d)
{
    pwm_set_chan_level(slice_num, chan, pwm_get_wrap(slice_num) * d / 10000);
}

Máme-li tohle hotovo, můžeme psát funkce pro zapnutí/vypnutí a polohování:

void ServoOn(Servo *s)
{
 pwm_set_enabled(s->slice, true);
 s->on = true;
}

void ServoOff(Servo *s)
{
 pwm_set_enabled(s->slice, false);
 s->on = false;
}

void ServoPosition(Servo *s,uintp)
{
     pwm_set_dutyH(s->slice, s->chan, p*10+250);
}

Funkce ServoPosition nastavuje polohu v procentech. To znamená, že ServoPosition(&s,50) nastaví servo do středu jeho rozsahu. To předpokládá, že servo má standardní rozsah polohování a většina z nich jej nemá. V praxi je pro dosažení co nejlepšího výkonu serva nutné kalibrovat každé servo a zjistit, jaký rozsah pohybu je podporován.

Kompletní program

Zde je kompletní program, včetně jednoduchého hlavního programu, který pohybuje servem mezi dvěma polohami, minimální a maximální. Opět je třeba do souboru CMakeLists.txt přidat knihovnu hardware_pwm:

Jednoduchý program pro řízení serva
#include "pico/stdlib.h"
#include "hardware/pwm.h"

typedef struct
{
    uint gpio;
    uint slice;
    uint chan;
    uint speed;
    uint resolution;
    bool on;
    bool invert;
} Servo;

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;
}

uint32_t pwm_get_wrap(uint slice_num)
{
    valid_params_if(PWM, slice_num >= 0 &&
                         slice_num < NUM_PWM_SLICES);
    return pwm_hw->slice[slice_num].top;
}

void ServoInit(Servo *s, uint gpio, bool invert)
{
    gpio_set_function(gpio, GPIO_FUNC_PWM);
    s->gpio = gpio;
    s->slice = pwm_gpio_to_slice_num(gpio);
    s->chan = pwm_gpio_to_channel(gpio);
    pwm_set_enabled(s->slice, false);
    s->on = false;
    s->speed = 0;
    s->resolution = pwm_set_freq_duty(s->slice,
                                   s->chan, 50, 0);
    pwm_set_dutyH(s->slice, s->chan, 250);
    if (s->chan)
    {
        pwm_set_output_polarity(s->slice, false, invert);
    }
    else
    {
        pwm_set_output_polarity(s->slice, invert, false);
    }
    s->invert = invert;
}

void pwm_set_dutyH(uint slice_num, uint chan, int d)
{
    pwm_set_chan_level(slice_num, chan,
                  pwm_get_wrap(slice_num) * d / 10000);
}

void ServoOn(Servo *s)
{
    pwm_set_enabled(s->slice, true);
    s->on = true;
}
void ServoOff(Servo *s)
{
    pwm_set_enabled(s->slice, false);
    s->on = false;
}

void ServoPosition(Servo *s, uint p)
{
    pwm_set_dutyH(s->slice, s->chan, p * 10 + 250);
}

int main()
{
    Servo s1;
    ServoInit(&s1, 20, false);
    ServoOn(&s1);
    while (true)
    {
        ServoPosition(&s1, 0);
        sleep_ms(500);
        ServoPosition(&s1, 100);
        sleep_ms(500);
    }
    return 0;
}

Pokud spustíte program s použitím dříve uvedeného obvodu, zjistíte, že servo nedělá vůbec nic, možná kromě vibrací. Důvodem je, že tranzistorový budič napětí je invertor. Když je linka PWM na vysoké úrovni, tranzistor je plně sepnutý a pulzní linka serva je efektivně uzemněna. Když je linka PWM na nízké úrovni, tranzistor je zcela vypnutý a pulzní linka serva je rezistorem přitažena k vysoké úrovni. Řešením je použití invertovaného výstupu z linky GPIO pomocí:

ServoInit(&s1, 20, true);

Za zmínku stojí, že serva jsou dobrými a levnými stejnosměrnými motory, kompletně s převodovkami. Stačí servo otevřít, odpájet motor od řídicích obvodů a připájet k motoru dva vodiče. Pokud chcete použít elektroniku pro chod vpřed/vzad, můžete odstranit koncové dorazy na převodovce, obvykle na velkém ozubeném kole, a potenciometr nahradit dvojicí rezistorů se stejnou hodnotou, například 2,2 kΩ.

Zdroje a odkazy

Harry Fairhead: Programming the Raspberry Pi Pico/W in C — papírová kniha