{{indexmenu_n>11}} ====== 11 - Úvod do Nucleo STM32F446RE ====== * Seznámení s vývojovými deskami [[http://www.st.com/en/microcontrollers/stm32f446re.html/|Nucleo STM32F446RE]]. * Knihovna [[https://os.mbed.com/docs/mbed-os/v6.15/introduction/index.html|mbed-os 6]] a její [[https://os.mbed.com/platforms/ST-Nucleo-F446RE/|podpora pro Nucleo STM32F446RE]]. * Seznámení s webovým programovacím prostředím [[https://studio.keil.arm.com/|Keil Studio]]. * Použití tlačítka a LED diody. Desky Nucleo STM32F446RE jsou studenstvu dostupné na cvičení. Z vypracování úkolů na embedded programování je možné na cvičení získat až 6 bodů. [[courses:b3b36prg:internal:tutorialinstruction:10|(Pro vyučující.)]] ===== Vývoj v prostředí Keil Studio ===== * Vytvořte si účet ve webovém vývojovém prostředí [[https://studio.keil.arm.com/]], pokud jej ještě nemáte. * Založte projekt pro ''NUCLEO-F446RE'' a pojmenujte jej ''prg-nucleo''. * Do souboru ''main.cpp'' vložte jednoduchý program pro periodické blikání LED. #include "mbed.h" DigitalOut myled (LED1); // "Handle" to the LED. int main() { while (1) { myled = !myled; // Flip the state of the LED. ThisThread::sleep_for(1s); // Efficient MCU sleep. } } * Program zkompilujte tlačítkem ''Build project''. (První kompilace může trvat dlouho.) * Po zkompilování se stáhne do počítače výsledný binární soubor. * Připojte Nucleo k počítači; Nucleo se nahlásí jako externí disk. * Nahrajte binární soubor na Nucleo zkopírováním souboru na "disk". Vývojové prostředí Keil používá programovací jazyk %%C++%%, jehož syntax vychází z C. Pro ukázkové příklady vystačíme se stávající znalostí progrmaování a intuitivním vytvářením objektů (volání konstruktorů) a volání metod objektů. Objekt si můžeme představit jako rozšířený typ ''struct'', který má, kromě datových položek, také metody. Například objekt ''mybutton'' může mít metodu ''fall()'', která určí ukazatel na "cizí" funkci, která bude zavolána při detekci změny úrovně napětí, což odpovídá stisknutí tlačítka. Tedy např., ''mybutton.fall(&on_button_fall)''. V rámci PRG se %%C++%% věnují [[courses:b3b36prg:lectures:start|přednášky 10 až 13]]. ===== LED svítí pokud je tlačítko stisknuté ===== * Vyzkoušejte jednoduchý program pro ovládání LED stiskem tlačítka. #include "mbed.h" DigitalOut myled(LED1); DigitalIn mybutton(BUTTON1); int main() { while (1) { if (mybutton == 0) { myled = 1; } else { myled = 0; } } } Slovy jazyka C, proměnná ''myled'' je datového typu ''DigitalOut''. Terminologií %%C++%% se jedná o instanci třídy ''DigitalOut''. Instance třídy je spjata se sadou funkcí, které s ní umí pracovat - tzv. metodami. Metoda může být tzv. konstruktor, který instanci inicializuje. (Ve smyslu jako když inicializujeme proměnnou typu ''int''.) Konstruktor může přijímat parametry, jako jakákoliv jiná funkce. V našem případě předáváme "název pinu", který chceme ovládat. (Představte si, že výsledná instance je jakýsi ovladač/driver/API, který umí pracovat s LED diodou.) To, že můžeme "do proměnné přiřadit", je pouze syntax zucker, který ve skutečnosti volá metodu ''write''. Více vizte např. [[https://en.cppreference.com/w/cpp/language/class]]. Metoda ''write'' implementuje samotný mechanismus, který způsobí, že se na pinu objeví vysoká úroveň napětí. Vizte např. B3B38LPE1, B4M38AVS, B0B35APO. ===== Stisknutí tlačítka změní stav LED ===== * Program rozšiřte tak, aby jeden stisk tlačítka střídavě LED rozsvítil nebo zhasnul. ++++ Příklad řešení | #include "mbed.h" DigitalOut myled(LED1); DigitalIn mybutton(BUTTON1); int main() { bool mybutton_last = 0; while (1) { bool mybutton_current = mybutton; if (mybutton_last == 0 && mybutton_current == 1) { myled = !myled; } mybutton_last = mybutton_current; } } * Prakticky se jedná o detekci náběžné hrany nefiltrovaného signálu. ++++ ===== Robustní detekce stisku tlačítka ===== * Nedokonalý kontakt tlačítka způsobí probliknutí. (Zkuste tlačítko stisknout velice zlehka.) * Při změně stavu tlačítka může vzniknout šum. * Jak eliminovat toto nechtěné chování? ++++ Možné řešení | #include "mbed.h" DigitalOut myled(LED1); DigitalIn mybutton(BUTTON1); int main() { bool mybutton_last = 0; while (1) { bool mybutton_current = mybutton; if (mybutton_last == 0 && mybutton_current == 1) { ThisThread::sleep_for(100ms); // Efficient MCU sleep. if (mybutton) { myled = !myled; } } mybutton_last = mybutton_current; } } * Kdykoliv je tlačítko stisknuto (změna stavu tlačítka z uvolěného na stisknuté), program kontroluje, jestli je stisknuté i po 100 ms. * Můžete si to představit, jako velice jednoduchou verzi filtrování signálu. * Ve skutečnosti bývá filtr (low-pass filtr) implementován už na úrovni hardware. * Lze vylepšit softwarovým low-pass filtrem (klouzavý průměr) pomocí circular bufferu. ++++ ===== Efektivní detekce stisku tlačítka ===== * Využít procesorové přerušení pro efektivní obsluhu tlačítka. * MCU "začne sledovat" daný pin. Při náběžné hraně vyvolá tzv. přerušení (interrupt), který pozastaví běžící program a začne místo toho vykonávat obslužnou rutinu. Pokud zrovna MCU spí tak se probudí. * Nucleo má jednojádrové MCU. Opravdový paralelismus není možný. * Přerušení by mělo mít co nejrychlejší obsluhu. ++++ Možné řešení | #include "mbed.h" DigitalOut myled(LED1); InterruptIn mybutton(BUTTON1); void on_button_fall(){ myled = !myled; } int main() { mybutton.fall(&on_button_fall); ThisThread::sleep_for(1s); } ++++ ===== Efektivní a robustní detekce stisku tlačítka ===== * Zkombinujme obě myšlenky dohromady. * MCU má v sobě zabudovaný časovač, který můžeme využít přes třídu ''[[https://os.mbed.com/handbook/Timeout | Timeout]]''. * Tento časovač vyvolá přerušení po uplynutí dané doby, MCU může mezitím v klidu spát. /* * V tuto chvíli velice jednoduše filtrujeme signál. */ ++++ Možné řešení | #include "mbed.h" DigitalOut myled(LED1); DigitalIn mybutton_read(BUTTON1); InterruptIn mybutton_irq(BUTTON1); Timeout timeout; void on_timeout(){ if(mybutton_read == 0){ myled = !myled; } } void on_button_fall(){ timeout.attach(&on_timeout, 1s); } int main() { mybutton_irq.fall(&on_button_fall); while(1){ ThisThread::sleep_for(1s); } } * V případě zašuměného nebo nedokonalého stisku se obsluha ''on_button_fall'' spustí vícekrát a efektivně pokaždé vyrestartuje časovač. * Časovač tedy prakticky běží až od poslední náběžné hrany, kdy už přechodový jev odezněl. ++++ ===== Opakovač ===== * Vytvořte program, který zaznamená jak uživatel mačkal tlačítko. * Vytvořte si pole, které vyplníte vzorky stavu tlačítka. * Každý vzorek bude reprezentovat stejný časový úsek. * Po naplnění celého pole vzorky přehrajte na LED diodě. * Hrajte si s velikostí časového okna, počtem vzorků. * Uvědomte si, kolik paměti máme k dispozici a kolik zabírá pole vzorků. * Jak uložit informaci o stisknutí efektivně? ++++ Možné řešení | #include "mbed.h" DigitalOut myled(LED1); DigitalIn mybutton(BUTTON1); // This will define the memory footprint and the total time window captured by the program. constexpr uint32_t BUFFER_SIZE = 500; constexpr Kernel::Clock::duration_u32 TIME_UNIT = 10ms; constexpr uint32_t RECORDING_SIGNAL_BLINK_COUNT = 0; // Here we will store what the user "wrote" using the button. bool buffer [BUFFER_SIZE]; // A generic blinking function to show the user mode changes. Note that this BLOCKS the program. void blink(uint32_t i, Kernel::Clock::duration_u32 t = 50ms){ for(; i > 0; i--){ myled = true; ThisThread::sleep_for(t); myled = false; ThisThread::sleep_for(t); } } void record(){ for(uint32_t i = 0; i < BUFFER_SIZE; i++){ // Take one sample from the middle of the sampling period. ThisThread::sleep_for(TIME_UNIT/2); buffer[i] = !mybutton; ThisThread::sleep_for(TIME_UNIT/2); // Tell the user that the sample was taken. Note, that this blocks! blink(RECORDING_SIGNAL_BLINK_COUNT); } } void replay(){ for(uint32_t i = 0; i < BUFFER_SIZE; i++){ myled = buffer[i]; ThisThread::sleep_for(TIME_UNIT); } myled=false; // Turn of the light after the show. } int main() { blink(6); record(); blink(6); replay(); blink(6); // The program ends. Use the black button to restart the MCU. } Možná zlepšení: * Robustní vzorkování: V periodě měřit průměrnou dobu stisknutí a podle toho při přehrávání nastavit PWM střídu LED. * Napsat kód asynchroně: Použít třídu Ticker, která realizuje záznam a přehrání pomcí přerušení. * Šetřit paměť pomocí pole bitů a bitwise operací. ++++ ===== Efektivní opakovač ===== * Využije paměť efektivně: Nechť je jeden vzorek stavu tlačítka kódovaný jediným bitem. ==== Uložení informace ==== * Vytvořte pole typu ''uint32_t''. * Každý jeden index pole (každý ''uint32_t'') může kódovat až 32 různých booleovských hodnot. * Podle požadovaného počtu vzorků vypočítejte délku pole. (Zaokrouhlit nahoru!) ++++ Možné řešení | constexpr uint32_t NUMBER_OF_SAMPLES = 500; constexpr uint32_t BITS_PER_INDEX = 8*sizeof(uint32_t); constexpr uint32_t NUMBER_OF_INDICES = NUMBER_OF_SAMPLES / BITS_PER_INDEX + (NUMBER_OF_SAMPLES % BITS_PER_INDEX ? 1 : 0); uint32_t memory [NUMBER_OF_INDICES]; ++++ ==== Bitwise operace ==== * Vytvořte funkci, která podle zadaného čísla vzorku vybere správný index v paměti a v něm vyčte / nastaví správný bit. * Čtení (maskování) bitu ''x'' * varianta 1 bit = (number >> x) & 1; * varianta 2 bit = (number) & (x << 1); * jaký je rozdíl mezi variantou 1 a variantou 2? Kdy použijeme kterou z nich? * Nastavení bitu ''x'' na ''1'' number |= 1 << x; * Nastavení bitu ''x'' na ''0'' number &= ~(1 << x); * Přepínání bitu number ^= 1 << x; * Nastavení bitu ''x'' podle hodnoty proměnné ''v'' number = (number & ~(1U << x)) | (v << x); ==== Obojí dohromady ==== ++++ Možná kostra řešení | #include "mbed.h" DigitalOut myled(LED1); DigitalIn mybutton(BUTTON1); constexpr Kernel::Clock::duration_u32 TIME_UNIT = 500ms; constexpr uint32_t NUMBER_OF_SAMPLES = 40; constexpr uint32_t BITS_PER_INDEX = 8*sizeof(uint32_t); constexpr uint32_t NUMBER_OF_INDICES = NUMBER_OF_SAMPLES / BITS_PER_INDEX + (NUMBER_OF_SAMPLES % BITS_PER_INDEX ? 1 : 0); uint32_t memory [NUMBER_OF_INDICES]; void set_bit_better(uint32_t sample, bool value){ uint32_t memory_index = sample / BITS_PER_INDEX; if(memory_index >= NUMBER_OF_INDICES){ // Velky Spatny. } uint8_t x = sample % BITS_PER_INDEX; memory[memory_index] = (memory[memory_index] & ~(1U << x)) | (value << x); } void set_bit(uint32_t sample, bool value){ uint32_t memory_index = sample / BITS_PER_INDEX; if(memory_index >= NUMBER_OF_INDICES){ // Velky Spatny. } uint8_t x = sample % BITS_PER_INDEX; if(value){ memory[memory_index] |= 1 << x; }else{ memory[memory_index] &= ~(1 << x); } } bool get_bit(uint32_t sample){ uint32_t memory_index = sample / BITS_PER_INDEX; if(memory_index >= NUMBER_OF_INDICES){ // Velky Spatny. } uint8_t x = sample % BITS_PER_INDEX; return (memory[memory_index] >> x) & 1; } void prepare_sos(){ set_bit(1, 1); set_bit(2, 0); set_bit(3, 1); set_bit(4, 0); set_bit(5, 1); set_bit(6, 0); set_bit(7, 0); set_bit(8, 0); set_bit_better(9, 1); set_bit_better(10, 1); set_bit_better(11, 1); set_bit_better(12, 0); set_bit_better(13, 1); set_bit_better(14, 1); set_bit_better(15, 1); set_bit_better(16, 0); set_bit_better(17, 1); set_bit_better(18, 1); set_bit_better(19, 1); set_bit_better(20, 0); set_bit_better(21, 0); set_bit_better(22, 0); set_bit(23, 1); set_bit(24, 0); set_bit(25, 1); set_bit(25, 0); set_bit(26, 1); } int main() { memory[0] = 88569621; // A little bit more succinct memory setup. //prepare_sos(); // Both lines of code result in equivalent data in the memory. while(1){ for(int i = 0; i < NUMBER_OF_SAMPLES; i++){ myled = get_bit(i); ThisThread::sleep_for(TIME_UNIT); } } } ++++