Hlavní navigace

Linux pro řízení: minimalistické řešení řízení stejnosměrného mo­toru

3. 10. 2016
Doba čtení: 13 minut

Sdílet

Zpětnovazební řízení stejnosměrného motorku připojeného k Raspberry Pi s minimem hardwarových komponent slouží jako jednoduchá ukázka využití upraveného jádra Linux pro řízení.

Článek navazuje na předchozí úvodní článek, ve kterém bylo popsané, jakým způsobem byly využity konstrukce spinlocků pro dosažení možnosti téměř okamžitého přeplánování úlohy na jinou s vyšší prioritou i během provádění operací/systémových volání uvnitř jádra. Dlouhodobé sledování dob odezvy systému i při maximální zátěži ukazuje, že pro výkonnější systémy na bázi architektury x86 nepřekročí maximální doby odezvy okolo 100 μs, pro nevýkonné systémy jako je Raspberry Pi 1 maximální doby odezvy nepřekročí 350 až 400 μs.

Řízení stejnosměrného motoru

Řízení malého stejnosměrného motoru je základní úloha z oboru řídicích systémů, která se hodí jak pro výuku, tak pro otestování vlastností procesorového systému a může být zajímavá i pro nadšence a zájmové kroužky. Na základě výsledků předchozího otestování upraveného jádra Linux na platformě Raspberry Pi lze předpokládat, že by měly být rychlosti odezvy pro úspěšné předvedení experimentu dostatečné. Tento závěr vychází z porovnání maximální latence 350 μs s typickými hodnotami mechanické a elektrické časové konstanty daného systému.

Mechanická časová konstanta se pro malé elektrické motory pohybuje v rozmezí od 1 do 20 ms. Přitom i moderní motory s lehkou kotvou mají časovou konstantu výjimečně kratší než 3 ms. Elektrická časová konstanta bývá kratší (řádově pětkrát). V celkové dynamice je tedy dominantní delší, mechanická, časová konstanta. Při požadavku na regulátor s dobrými vlastnostmi – především doba ustálení – pro soustavu na mezi stability (motor je integrátor při uvažování regulace na polohu) je pak dostatečná vzorkovací frekvence rovná alespoň dvojnásobku vlastní frekvence systému. Rozumným kompromisem vzhledem k časovým konstantám a schopnostem platformy zaručit časování je pak hlavní/vzorkovací frekvence regulátoru 1 kHz.

Pro snímání aktuální polohy je pak nejčastěji používané inkrementální rotační čidlo polohy (IRC). Levnější motory jsou vybaveny čidly s desítkami pulzů na otáčku. Kvalitní čidla pak mívají 500 a více okének na otáčku, což představuje 2000 rozlišitelných poloh. Pokud uvažujeme za maximum 4000 otáček motoru za minutu, tak se jedná pro čidlo s 500 okénky o frekvenci změn kombinace signálu přibližně 150 kHz (4000/60*500*4). Raspberry Pi nedisponuje periferií potřebnou k hardwarovému zpracování výstupu IRC čidla (na rozdíl od jiných platforem, například AM335×). Z porovnání s maximální latencí tedy vyplývá, že úloha softwarového zpracování událostí generovaných při plných otáčkách motoru s kvalitnějším čidlem je již za mezí schopností platformy. Tato skutečnost je spíš výhodou, protože napomáhá otestovat platformu na hranici jejích schopností a zároveň lze tento limit pro účely výuky dobře demonstrovat. Pro zaručené zpracování všech událostí by bylo nutné doplnit Raspberry Pi o další hardware realizovaný například v programovatelném obvodu – FPGA – jak bylo předvedeno  například na loňské přednášce na konferenci LinuxDays.

Hardwarové uspořádání

Výstupy IRC čidla jsou po převedení na 3.3 voltové napěťové úrovně přivedené na piny obecné vstupně výstupní brány GPIO. Tyto piny lze nakonfigurovat pro vyvolání přerušení na vzestupné nebo sestupné hraně signálu. Aby byla zajištěna možnost samostatného zpracování vzestupné a sestupné hrany pro oba signály, je každý signál přiveden na dvojici pinů. Viz obrázek zapojení vstupně výstupních pinů (svg, pdf). Akční veličinou při řízení motoru je velikost přivedeného napětí. To je realizováno výkonovým stupněm, který bývá až na ojedinělé výjimky realizovaný pulzně-šířkovou modulací (PWM), protože lineární řízení vede ke značným ztrátám na spínacích tranzistorech. Raspberry Pi však nabízí jen jeden PWM výstup dostupný na rozšiřujícím konektoru (viz popis RPi hardware na eLinux.org). Přitom k polohovému řízení stejnosměrného motoru je potřebné řídit jak velikost napětí tak jeho polaritu. Řešením je využít jeden GPIO výstup pro řízení polarity/směru. Použitý obvod 74HCT02 pak podle tohoto signálu přivádí PWM signál na jednu nebo druhou polovinu můstku výkonového členu. Zároveň převádí 3.3 voltový výstup na úrovně kompatibilní s pětivoltovou logikou. Vlastní výkonový stupeň lze pak realizovat například obvodem L6203 (výkonový můstek v uspořádání do H).

Zpracování vstupu inkrementálního čidla

Při každé změně některého ze vstupních signálů z IRC senzoru je potřeba rozhodnout, jestli se jedná o přírůstek polohy o jeden inkrement v kladném nebo záporném smyslu otáčení. Podle toho je poté potřeba přičíst nebo odečíst jedničku od proměnné reprezentující aktuální polohu motoru. Pokud nemá zpracování vést k plýtvání procesorového času, nemůže být řešeno aktivním čekáním a je ho potřeba řešit přes aktivaci příslušné rutiny na základě vyvolaného přerušení. Alternativou je synchronní vzorkování, ale protože předpokládáme krátké intervaly mezi změnami (desítky μs), byla by perioda vzorkování/nastavená na časovači kratší než reakční doba systému a ten by byl trvale přetížený. Protože napojení vlastního kódu na zdroje přerušení je pro aplikaci v uživatelském prostoru problematické, bylo zpracování řešeno formou jednoduchého ovladače zařízení, který je realizovaný v jaderném modulu rpi_gpio_irc_module . Zdrojový kód modulu je součástí ukázkového projekturpi-rt-control publikovaného na serveru GitHub. V návrhu jsou použitá čtyři samostatná přerušení, každé pro jeden typ události ({vzestupná,sestupná} hrana × {kanál A, B}). Směr je určený pouze z pořadí událostí. Určitou zajímavostí je, že na plně-preemptivní variantě jádra Linux je i zpracování hardwarových událostí převedené na aktivaci k tomu určených vláken/úloh, ve kterých se teprve spouští kód ovladače. Přitom úlohy jsou na úrovni hardwarového přerušení vkládané do fronty úloh čekajících na přidělení procesorového času v pořadí, ve kterém byly registrované a jsou později i v tomto pořadí aktivované. Tato vlastnost do určité míry eliminuje vliv občasných výskytů prodlev v systému delších než je čas mezi jednotlivými událostmi. Zpoždění však nesmí překročit čas příchodu více než tří událostí. Pak doje ke ztrátě polohy. Efekt je znázorněný na obrázku zpracování událostí z inkrementálního senzoru (svg, pdf) převzatém z prezentace k článku popisujícímu použití Matlabu/Simulinku pro RT řízení z Linuxu.

Vlastní ovladač lze do jádra zavést příkazem

modprobe rpi_gpio_irc_module

Při zavedení ovladače jsou při registraci rutin zpracování přerušení vytvořené i čtyři úlohy (jaderná vlákna) a je jim přiřazena základní priorita pro zpracování přerušení – 50. Protože zpracování těchto událostí je úloha nejvíce citlivá na rychlost odezvy, je vhodné prioritu zvýšit nad priority zpracování událostí z ostatních periferií (USB, ETHERNET, SD-karta atd.). Toho lze z příkazové řádky dosáhnout například následovně:

IRC_PIDS=$(ps Hxa -o command,pid | \
  sed -n -e 's/^\[irq\/[0-9]*-irc[0-9]_ir\][ \t]*\([0-9]*\)$/\1/p')

for P in $IRC_PIDS ; do
  schedtool -F -p 95 $P
done

Protože je zpracování v jádře realizované formou znakového ovladače zařízení, je možné k aktuální hodnotě polohy přistupovat operací čtení souboru/znakového zařízení. Pro odpovídající soubor v adresáři „ /dev “ bylo zvoleno jméno „ irc0 “. Vlastní poloha je pak reprezentovaná binárně v prvních čtyřech bytech tohoto souboru. Hodnotu lze pak pro testovací účely číst například řádkovým příkazem

hexdump -e '"%d" ' /dev/irc0

Čtení z vlastního programu (uživatelské aplikace) pak lze implementovat následovně:

/* Jméno souboru/zařízení reprezentujícího IRC vstup */
const char *irc_dev_name = "/dev/irc0";
int irc_dev_fd;

/* Inicializace přístupu pro následné čtení polohy */
int irc_dev_init(void)
{
    irc_dev_fd = open(irc_dev_name, O_RDONLY);
    if (irc_dev_fd == -1) {
        return -1;
    }
    return 0;
}

/* Čtení hodnoty reprezentující polohu ze znakového zařízení */
int irc_dev_read(uint32_t *irc_val)
{
    if (read(irc_dev_fd, irc_val, sizeof(uint32_t))
             != sizeof(uint32_t)) {
        return -1;
    }
    return 0;
}

Realizace akčního zásahu, napětí přivedeného na motor

Pro řízení polarity/směru výstupu je potřeba nastavovat hodnotu příslušného GPIO pinu. K tomu je možné opět navrhnout ovladač nebo vyžít přístup z uživatelské aplikace. Jádro Linux nabízí možnost exportu GPIO pinu ve formě souborů v adresářové struktuře „ /sys “, která informuje a zpřístupňuje stav systému a hardware. Přístup na použitý GPIO pin 22 lze realizovat z příkazového řádku následovně

# Požadavek na zpřístupnění GPIO pinu přes adresář /sys
echo 22 >/sys/class/gpio/export

# Nastavení výstupního směru pinu
echo out >/sys/class/gpio/gpio22/direction

# Čtení hodnoty pinu
cat /sys/class/gpio/gpio22/value

# Nastavení výstupu/logické hodnoty pinu
echo 1 >/sys/class/gpio/gpio22/value

Přístup přes tyto soubory však představuje určité zpoždění navíc, a protože i pro konfiguraci PWM periferie je výhodné využít rychlejšího přístupu přímo z uživatelské aplikace do hardwarových registrů, použijeme tuto metodu i pro přístup ke GPIO pinu. Zpřístupnění vybraných periferií z uživatelského prostoru je realizované ve funkcirpi_peripheral_registers_map(void) v příkladovém projektu. Základem je mapování části fyzického adresního prostoru do virtuálního adresního prostoru aplikace s využitím systémového volání mmap() .

mem_fd = open("/dev/mem", O_RDWR|O_SYNC)
gpio_map = mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE,
                MAP_SHARED, mem_fd, GPIO_BASE);

Toto řešení bez psaní ovladače je však přístupné pouze pro programy s administrátorskou úrovní oprávnění a je přímo vázané na strukturu hardwarových periferií/registrů daného obvodu. Funkce pro přístup ke GPIO pinům se nalézají v souborech „rpi_gpio.c “ a „rpi_gpio.h “. Nastavení PWM periferie a nápis výstupní hodnoty g pak v souborech „rpi_bidirpwm.c “ a „rpi_bidirpwm.h “.

Přesné časování procesu realizujícího zpětnou vazbu

Nyní již stačí realizovat vlastní řídicí smyčku regulátoru. Oproti běžné uživatelské aplikaci jsou kladeny na řídicí aplikaci požadavky na dodržení časování. Přitom Linux a další moderní systémy podle dalších paměťových požadavků běžně odkládají data aplikací do odkládacího oddílu a kód programu do fyzické paměti zavádí po stránkách na základě jejich aktuální potřeby. Přitom výpadek stránky a její nahrání může trvat velmi dlouho. Proto je potřeba vyžádat uzamčení všech stránek řídicí aplikace v paměti. To lze pro již aplikací alokované stránky i pro budoucí alokace provést voláním

mlockall(MCL_FUTURE | MCL_CURRENT);

Dále je potřeba vláknům aplikace přiřadit plánovač s politikou vhodnou pro řídicí aplikace. To je politikou, která představuje vyšší prioritu než mají běžné aplikace a která se neřídí snahou o rovnoměrné přidělení času mezi všechny úlohy (procesy+vlákna). Toho lze dosáhnout použitím nástroje „schedtool “ a nebo přímo z programu použitím standardních funkcí definovaných standardem POSIX.

pthread_attr_t attr;
struct sched_param schparam;

/* Inicializace struktury parametrů standardními hodnotami  */
pthread_attr_init(&attr);

/* Požadavek na provedení nastavení politiky plánování při založení vlákna */
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

/* Volba RT politiky */
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);

/* Volba priority vlákna v rámci dané politiky */
schparam.sched_priority = sched_get_priority_min(SCHED_FIFO);

/* Oznámení o volbě seznamu parametrů */
pthread_attr_setschedparam(&attr, &schparam);

/* Vytvoření z zapuštění nového vlákna  */
pthread_create(thread, &attr, start_routine, arg);

/* Uvolnění prostředků použitých pro přípravu parametrů */
pthread_attr_destroy(&attr);

Správné přiřazení priorit jednotlivým úlohám lze zjistit buď řádkovým příkazem „ schedtool “ nebo i správným nastavením běžného výpisu běžících procesů

ps Hxa --sort rtprio -o pid,policy,rtprio,state,tname,time,command

Kritické je také správné použití systémem nabízených prostředků pro správné časování úlohy. Pokud budou mezi volání regulátoru vloženy pouze prodlevy, bude se k nim přičítat i doba vykonání regulátoru a latence přepínání úlohy a bude postupně docházet k zpožďování vzorků. Proto je vždy potřeba plánovat další událost vzhledem k času, kdy právě zpracovávaná událost měla nastat a ne k času, kdy je vykonávaná. Dále je chybnou volbou použít standardní zdroj času „CLOCK_REALTIME “, protože se jedná o čas, který má být a může být synchronizovaný se světovým časem. V případě většího posunu při korekci pak může dojít k výpadku mnoha vzorků nebo k jejich vykonání bez prodlevy v řadě (například při korekci chybného data administrátorem nebo přes NTP). Proto je potřeba používat zdroj „CLOCK_MONOTONIC “ u kterého je zaručené, že rovnoměrně od spuštění počítače narůstá. Příklad správného periodického časování pro aktivity vyvolávané častěji, než jednou za sekundu následuje

sample_period_nsec = 20*1000*1000; /* čas v nanosekundách */
clock_gettime(CLOCK_MONOTONIC, &sample_period_time);

do {
  /* Výpočet času příští iterace */
  sample_period_time.tv_nsec += sample_period_nsec;
  if (sample_period_time.tv_nsec > 1000*1000*1000) {
    sample_period_time.tv_nsec -= 1000*1000*1000;
    sample_period_time.tv_sec += 1;
  }

  clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
                  &sample_period_time, NULL);
  /* zde vložit periodicky časované operace  */
  ...
} while(1);

Aplikace by měla být ošetřena tak, aby při ukončení uživatelem nebo chybou, uvedla výstupy do stavu, který zabrání škodám. Klasické vynucené ukončení programu je realizované zasláním signálu. V programu pak jeho přijetí a zpracování může vypadat následovně

/* Zastavení motoru, akční zásah 0 voltů */
void stop_motor(void)
{
  rpi_bidirpwm_set(0);
}

/* Obsluha signálu informujícího o ukončení programu */
void sig_handler(int sig)
{
  stop_motor();
  exit(1);
}

...
  struct sigaction sigact;
  memset(&sigact, 0, sizeof(sigact));
  sigact.sa_handler = sig_handler;
  sigaction(SIGINT, &sigact, NULL);
  sigaction(SIGTERM, &sigact, NULL);

Po přípravě výše uvedených podpůrných funkcí je pak realizace vlastního PSD regulátoru (diskrétní varianta PID regulátoru: P – proporcionální, I – integračního a D – derivační složka) pro regulaci na polohu jednoduchá

/* Výpočet regulační odchylky */
err =  (pos_req - actual_pos);

/* Akumulátor/suma/"integrál" odchylky */
ctrl_i_sum += err * ctrl_i;

/* Výpočet akčního zásahu */
action = ctrl_p * err + /* proporcionální část */
         ctrl_i_sum +   /* "integrační" složka */
              /* diferenční/"derivační" složka */
         ctrl_d * (err - ctrl_err_last);

/* Zapamatování odchylky pro příští výpočet diference */
ctrl_err_last = err;

/*
 * Změna měřítka pro výpočet v pevné řádové čárce
 * a aplikace akčního zásahu
 */
rpi_bidirpwm_set(action >> 8);

Ukázka je zjednodušená, není řešeno ošetření zahlcení výstupu (anti-windup) a dále je potřeba přidat generátor požadované polohy. Pro řízení na rychlogst například formou přičítání požadovaného zlomku rychlosti k požadované poloze v každé periodě. Kompletní příklad je obsažený v projektu v adresáři „rpi_simple_dc_servo “. Informace k problematice řízení stejnosměrného motoru s využitím RT systémů se nachází na stránkách předmětů Programování systémů reálného času a již nevyučované starší verze Počítače pro řízení Katedry řídicí techniky ČVUT FEL.

V experimentu dosažené výsledky

Pro malé rychlosti bylo dosaženo stabilních výsledků a dobré kvality regulace, přitom systém nevykazoval známky přetížení. Pro malé motorky s menším počtem ohlasů na otáčku bylo možné testovat řízení do téměř plné rychlosti. Platforma Raspberry Pi je schopná zpracovávat události z IRC čidla přibližně do frekvence 28 kHz. Při použití čidla s vyšším rozlišením namontovaném na motoru Maxon s lehkou kotvou již při vyšších otáčkách dochází k přetížení systému. Přesto, že je systém po dobu přetížení zcela nedostupný a ani na běh regulační smyčky nepřijde řada, dojde při vnějším zpomalení/přibrzdění motoru nebo odpojení jeho výkonové svorky k návratu běhu systému do normálu bez ztráty komunikace nebo vyvolání výjimky nebo pádu systému. Řešení je vhodné pro výuku a demonstraci zpětnovazebního řízení. V žádném případě by však nemělo být na této platformě a v tomto provedení použito pro průmyslové nasazení.

CS24_early

Pro otestování dalších možností platformy Raspberry Pi pro výuku bylo dále odzkoušeno řešení s využitím programovatelného obvodu a rozšiřujícího hardware navrženého ve firmě PiKRON. Toto řešení již neklade omezení na frekvenci impulzů z čidla a i na platformě Raspberry Pi umožňuje implementaci vektorového řízení PMSM motorů. Ani toto řešení však není plánované pro využití v průmyslových aplikacích. Pro průmyslová řešení máme vyvinuto množství robustních řešení od malých zařízení až po kilowattové motory.

V případě zájmu bude v navazujícím článku ukázané, jak lze pro výše popsaný experiment i pro vážněji míněné nasazení kombinovat návrh formou modelu v prostředí Matlab/Simulink s precizním časováním nabízeným plně-preemptivní variantou jádra Linux a systém kombinovat jak s kartami pro sběr dat a řízení, tak s komunikací CAN/CANopen. Členy skupiny IIG katedry řídicí techniky připravený a spravovaný otevřený projekt pro propojení generátoru kódu z prostředí Simulink pro RT Linux se nachází na stránkách projektu Lintarget.

Odkazy

Byl pro vás článek přínosný?

Autor článku

Vystudoval obor kybernetika a robotika na ČVUT FEL, kde v současné době učí a pracuje na projektech využívajících Linux a další procesorové technologie.