Przeszukaj forum
Pokazywanie wyników dla tagów 'Programownie'.
Znaleziono 5 wyników
-
Firma Arduino głównie jest znana ze swoich płytek deweloperskich o tej samej nazwie. W sprzedaży jest wiele rodzajów Arduino, ale czy wiedziałeś, że Arduino ma w swojej ofercie sterowniki PLC? Arduino Opta jest to sterownik micro PLC wyprodukowany we współpracy z firmą Finder, która specjalizuje się w projektowaniu i produkcji komponentów elektromechanicznych, takich jak przekaźniki. Chociaż projekt miał swoją premierę już dość dawno, to do tej pory nie był pokazywany szerzej na Forbocie. Mam nadzieję, że ten artykuł opisujący moje pierwsze uruchomienie tego sterownika będzie pomocny dla osób, które chcą rozpocząć przygodę z Arduino Opta. W tym artykule przedstawię Ci: Czym właściwie jest Arduino Opta? Twoje pierwsze kroki z tym sterownikiem. Jakie funkcje i możliwości oferuje? Projekt: implementacja sterowania temperaturą w terrarium. Arduino Opta PLC. Źródło zdjęcia. Czym właściwie jest Arduino Opta? Sterownik charakteryzuje się swoją małą skalą. Mikro PLC oznacza, że jest to urządzenie wyprodukowane w standardzie zwykłego sterownika, ale w mniejszej skali - np. mniej wejść i/lub wyjść, mniej funkcjonalności. Takie rozwiązania sprawdzą się w mniej skomplikowanych aplikacji. A jakie parametry cechują sterownik? Generalna specyfikacja PLC. Źródło zdjęcia. Sterownik jest certyfikowany, co oznacza, że można go stosować w przemyśle - ograniczeniem są parametry techniczne, takie jak pamięć, klasa ochrony IP, itp. Sterownik na pewno nie ma problemu z szybkością przez swój dwurdzeniowy procesor STM32. Parametry wejść analogowych. Źródło zdjęcia. Parametry wejść cyfrowych i wyjść przekaźnikowych. Źródło zdjęcia. Maksymalny prąd wyjściowy na pojedyncze wyjście sterownika wynosi 10 A. W sterowniku zastosowano wyjścia przekaźnikowe, które są o wiele wolniejsze od tranzystorowych. Przez to czas przełączania stanu wyjść jest rzędu milisekund, ale w zamian możliwe jest przełączanie stosunkowo wysokich prądów, przy jednoczesnej izolacji sygnałów. Po więcej informacji nt. danych technicznych odsyłam do dokumentacji sterownika. Pierwsze kroki z Arduino Opta: micro PLC Producent oferuje bezpłatny kurs, który wprowadzi Cię w podstawy sterowników logicznych, a także pokaże jak możesz stworzyć swoje pierwsze programy. Są to świetne materiały dla początkujących. Dowiecie się czym są sterowniki PLC, jakie są języki programowania lub jak odczytywać wartości analogowe z czujnika temperatury. Dodatkowo kurs wprowadzi Cię w techniki łączenia ze sobą kilku sterowników za pomocą standardu Modbus TCP/IP. Dla zainteresowanych polecam mój bliźniaczy artykuł, w którym omówiłem dokładniej czym są sterowniki PLC. Pomoże Ci przy pierwszych uruchomieniach programów na Arduino Opta. A teraz dość gadania i przejdźmy do praktyki! W poniższym artykule testuję Arduino PLC Starter Kit, który możecie zakupić na Botlandzie. Na zestaw składają się: sterownik Arduino Opta WiFi. moduł DIN Simul8 (przełączniki). moduł DIN Celcius (płytka grzewcza wraz z czujnikiem temperatury). Co oferuje Arduino Opta? Sterownik z dedykowanym środowiskiem programistycznym Arduino PLC IDE to naprawdę potężne narzędzie. Nie sposób w jednym artykule zmieścić wszystkich dostępnych możliwości, dlatego poniżej znajdziesz skrót tych najważniejszych, a potem przejdziemy do realnego wykorzystania Arduino Opta. Do testów oscyloskopu i trybów debugowania wykorzystałem środowisko Arduino PLC IDE. WiFi i Bluetooth zostały przetestowane w Arduino IDE. Wszystkie adresy ukryłem - lepiej dmuchać na zimne 🙂. a) Proste programowanie i diody Sterownik zaprogramujemy za pomocą USB-C w bardzo szybki i przyjemny sposób. Do dyspozycji na sterowniku mamy kilka diod, których zachowanie możemy zaprogramować w taki sposób, jaki chcemy. b) Cyfrowy oscyloskop W Arduino PLC IDE możemy śledzić konkretne wartości włączając oscyloskop i dodając do niego interesującą nas zmienną. Wygenerowany wykres możemy analizować i pobrać. Dodanie do oscyloskopu zmiennej sens_temp_cels, która symuluje temperaturę. Widok oscyloskopu. c) WiFi Wykorzystuję Arduino Opta WiFi, więc głupio byłoby nie przetestować jego tytułowej funkcjonalności. Na początku połączyłem się z moim domowym WiFi za pomocą przykładu Scan Network Advanced. Wykrycie dostępnych sieci WiFi i wypisanie ich na Serial Monitor. Udane połączenie z moją siecią WiFi. Do połączenia wykorzystałem ten przykład. Czas na rozmowę! Wykorzystuję przykład WiFiAdvancedChatServer, dzięki któremu mogę stworzyć chat server i wysyłać wiadomości do sterownika. Za pomocą laptopa połączę się bezprzewodowo z serwerem i wyślę wiadomość. Sterownik będzie połączony z moim komputerem stacjonarnym za pomocą przewodu, na którym wyświetlę odebraną wiadomość. Przykład musiałem lekko zmodyfikować, aby wyświetlał znaki, a nie pełne wiadomości. Wtedy lepiej działało 😉. Połączenie do WiFi i utworzenie chat server. Strona Arduino Opta. Połączenie do serwera za pomocą komendy telnet <IP> i wpisanie wiadomości. Strona laptopa. Odebrana wiadomość i rozłączenie klienta. Strona Arduino OPTA. d) Ethernet i Modbus Niestety nie byłem w stanie przetestować tych funkcjonalności, dlatego odsyłam do materiałów od Finder 🙂. e) Bluetooth Low Energy Oprócz WiFi jest także możliwość połączenia się ze sterownikiem za pomocą Bluetooth! Skan pobliskich urządzeń z włączonym Bluetooth. Opta wykrył mój telefon. Wykorzystałem przykład Arduino BLE Scan. f) Live debug mode Środowisko Arduino PLC IDE oferuje podglądanie stanu zmiennych na żywo. Było to bardzo przydatne, gdy tworzyłem algorytm do sterowania terrarium, który przedstawię w dalszej części tego artykułu. Podgląd zmiennych na żywo. Aktywowane zmienne są podświetlone. Wyświetlany jest także odliczony czas timera obok jego wyjścia ET. g) Debugowanie Wykonywany kod można debugować, zaznaczając odpowiednie punkty stopu. Wynik debugowania. W prawym dolnym rogu możemy zauważyć status sterownika HALTED, co oznacza, że przez debugowanie został zatrzymany. Twój pierwszy projekt wykorzystując Opta! W imię zasady Learning-by-doing i w celu przedstawienia możliwości sterownika wymyślmy problem, na podstawie którego krok po kroku przeprowadzę Cię przez podstawy programowania tej jednostki. Zaprojektujmy program sterujący ogrzewaniem terrarium. Nie jestem ekspertem zoologii. Użycie algorytmu na żywych jednostkach na własną odpowiedzialność! System ma działać zgodnie z następującymi zasadami: Standardowe ogrzewanie: Terrarium jest ogrzewane co minutę, aby utrzymać temperaturę 27°C, gdy drzwi są zamknięte. Zamknięcie zrealizujemy za pomocą symulacji krańcówki - wykorzystamy przełącznik. Zakładamy, że jeżeli drzwi są otwarte, to zmienna drzwi = 1. Jeżeli są zamknięte, to drzwi = 0. Reakcja na otwarcie drzwiczek: W stanie otworzonego terrarium niemożliwe jest załączenie grzałki. Jeśli drzwiczki zostaną otwarte i zamknięte, system natychmiast podnosi temperaturę do 30°C. Tryb manualny: Użytkownik może włączyć ogrzewanie ręcznie za pomocą przełącznika. Grzałka działa w tym trybie, dopóki przełącznik nie zostanie zwolniony. Informacja dla programisty: Po podłączeniu sterownika do komputera i włączeniu terminala powinny co 5 sekund pojawiać się informacje o: Temperaturze. Stanie drzwi (otwarte/zamknięte). Informacje mogą pojawić się także po wciśnięciu przycisku na PLC (USER). Zaprojektowany algorytm powinien uwzględniać bezpieczeństwo i zapewniać płynne przełączanie między trybami pracy. Włączenie grzałki powinno być zasygnalizowane włączeniem LED 1 na PLC. Połączenie układu Zanim zabierzemy się do programowania musimy wykonać odpowiednie połączenia między sterownikiem a modułami. Starter Kit oferuje przewody, za pomocą których możemy wszystko połączyć. Poniżej znajduje się schemat, w jaki sposób należy połączyć układ: Schemat połączeniowy dla naszego ćwiczenia. Powyższy układ połączono następująco: +24 V połączono razem między modułami. Osobne połączenia wykonano dla GND. Wyjście przełączników w DIN SIMUL8: nr 1 → I1. nr 8 → I2. Do wyjścia przekaźnikowego nr 1 podłączono na wejście +24 V. Wyjście z niego podłączono do INPUT HEAT 1. Gdy wyjście będzie aktywne, to rozpocznie się grzanie płytki. OUTPUT VOLTAGE w Din Celcius połączono do I7. Tym wejściem będziemy odczytywali temperaturę. Kolory przewodów: Czerwony - +24 V. Czarny - GND. Niebieski - sygnały logiczne/informacyjne. Znaczenie wejść i wyjść, i zmienne odpowiadające za nie: I1 - grzanie manualne (grzanie_manual). I2 - otworzenie drzwiczek (drzwi). I7 - temperatura terrarium (temperatura_terrarium). Przycisk USER na PLC - wysłanie informacji o stanie układu (info). Wyjście nr 1 - załączenie grzania terrarium (grzalka). LED STATUS 1 - informacja o załączeniu grzania (led_grzanie). Jeżeli uruchamiasz sterownik po raz pierwszy, to skorzystaj z lekcji Getting Started na stronie Arduino. Lekcja nauczy Cię jak skonfigurować sterownik i upewnić się, że wszystko działa. Oprogramowanie 1. Deklaracja zmiennych Zacznijmy od zadeklarowania wejść i wyjść. Robi się to w tabeli, co gwarantuje przejrzystość i czytelność. Konfiguracja wejść programowalnych. I7 ustawiamy jako wartość analogową o rozdzielczości 12 bitów. Konfiguracja wyjść programowalnych. Konfiguracja LED. Konfiguracja przycisku USER umieszczonego na PLC. Arduino OPTA umożliwia dodanie kilku programów, które będą równolegle wykonywane. Przyda nam się to, bo nasz projekt będzie się składał z: Pętli głównej odpowiedzialnej za: Pracę manualną. Pracę automatyczną. Przełączanie między pracą automatyczną a manualną. Wykrycie otworzenia terrarium. Załączanie grzałki. Skryptu, który będzie przeliczał wartość wyjściową czujnika temperatury na temperaturę wyrażoną w stopniach Celsjusza. Skryptu wyświetlającego informację o stanie układu. 2. Tryb manualny Sprawa jest prosta - załączając I1 włączamy grzałkę. Dodajmy nowy program: Dodawanie nowego programu. Po wybraniu New program wyskoczy okno: Wybieramy język LD, czyli najpopularniejszy język programowania PLC. Program nazwiemy main. Istnieją 4 rodzaje programów: Opisy typów programów. Źródło zdjęcia. Wybierzemy Fast, bo będzie to nasz główny program, w którym będą odbywały się najważniejsze rzeczy. Wyskoczy nam okno programu: Poświęćmy chwilę na wyjaśnieniu jak działa język LD. Język drabinkowy korzysta ze styków i cewek. Styki symbolizują wejścia, a cewki wyjścia. Rodzaje styków i cewek: -| |- - styk normalnie otwarty. Aktywuje się, gdy przypisana zmienna wynosi 1. -| / |- - styk normalnie zamknięty. Aktywuje się, gdy przypisana zmienna wynosi 0. -| S |- - styk set. Po aktywacji zmienna pozostaje aktywna do momentu resetu. -| R |- - resetuje zasetowany styk. -| P |- - styk wykrywający zbocze narastające zmiennej. -| N |- - styk wykrywający zbocze opadające zmiennej. -( )- - cewka normalnie otwarta. Aktywacja poprzez 1 na wejściu. -( / )- cewka normalnie zamknięta. Aktywacja poprzez 0 na wejściu. Po dwukrotnym naciśnięciu na styk pojawi się okno, w którym możemy skonfigurować go, np. przypisać do niego zmienną: Konfiguracja styku. Nie zapominajmy o diodzie, która ma nas informować o grzaniu. Cewkę dodamy poprzez wciśnięcie ikony coil: Ikona Coil. Bądź poprzez wciśnięcie prawego przycisku myszki i wybranie odpowiedniej opcji: Opcja dodania Coil. A oto nasz tryb manualny: Dobrym nawykiem jest stosowanie markerów. Są to zmienne w pamięci, w których przechowuje się wynik operacji logicznej. Wynik załączenia grzania przypiszmy do markera, a następnie za pomocą markera ustawmy odpowiednie cewki. Utwórzmy zmienną lokalną: Dodanie zmiennej lokalnej. Zmienna lokalna. I przenieśmy przypisanie wyjść do następnej linijki za pomocą markera. Dodanie kolejnej linijki kodu. Tryb manualny W taki sposób zbudowaliśmy nasz pierwszy funkcjonalny program! Aby go przetestować, należy przesłać go do sterownika. Opcja wysłania programu do sterownika. Po przesłaniu programu możemy włączyć podgląd (watch) i zobaczyć jak się zachowują nasze zmienne: Watch. Należy uniemożliwić grzanie, gdy drzwiczki są otwarte: Styk NC blokuje przepływ sygnału do cewek. Widzimy, że przy otwartych drzwiach grzałka nie działa. 3. Pobranie informacji o temperaturze Aby pobrać informację o temperaturze wewnątrz terrarium, posłużymy się poradnikiem. W skrócie: Poniżej przedstawiam program w języku ST do przeliczania wartości z czujnika na temperaturę: Dodajemy nowy program i nowe zmienne globalne. 4. Zamknięcie drzwiczek Teraz zajmijmy się grzaniem do 30 stopni, gdy drzwiczki zostaną zamknięte. Dodajmy następujące instrukcje w main: Jeżeli zamkną się drzwi (zbocze opadające na zmiennej drzwi), to ustawi się flaga drzwi_N_flaga (potrzebna do dalszej części kodu). Jeżeli osiągniemy 30 stopni, to resetujemy flagę. Dodatkowo tworzymy nowy tryb grzania (tryb_drzwi_N). Napiszmy skrypt w języku ST, który będzie obsługiwał zmienną tryb_drzwi_N: Jeżeli flaga zamknięcia drzwi jest aktywna, to włączamy tryb grzania. Jeżeli osiągnięto temperaturę 30 stopni, to włączamy odpowiednią flagę, a w main wyłączamy drzwi_N_flaga. 5. Standardowe ogrzewanie Stwórzmy tryb standardowego grzania. W tym celu stwórzmy zmienną odliczanie, dzięki której będziemy sygnalizować, czy mamy odliczać minutę do grzania. Dodatkowo utworzymy program w trybie Init, który wykona się tylko raz przy włączeniu sterownika. Wystartujemy w nim odliczanie. Zawartość programu Init. Zmienna jest typu bool. W zastosowaniach przemysłowych praca maszyny musi się odbyć po jawnym sygnale operatora, więc nasze rozwiązanie nie jest zgodne ze sztuką. Jednak na cele edukacyjne i hobbystyczne to nam ułatwia sprawę. Do mierzenia czasu służą timery. Aby go dodać klikamy opcję New Block: Dodanie bloku. W Object browser wyszukujemy timer TON. TON działa w następujący sposób: Opis timera TON. Źródło zdjęcia: Arduino PLC IDE. Obsługa trybu standardowego. Powyższy kod działaja w następujący sposób: Jeżeli odliczanie jest załączone i nie osiągnięto temperatury 27 stopni, to odliczamy 60 sekund. Po odliczeniu czasu wyłączamy odliczanie (reset) i załączamy tryb standardowy. Jeżeli osiągniemy 27 stopni, to załączamy znowu odliczanie i ściągamy flagę trybu standardowego. Dodajemy program, który obsłuży flagi i wykryje przekroczenie 27 stopni. Zauważ, że jest on bardzo podobny do trybu drzwi. Dodajemy włączenie grzałki pod wpływem trybu standardowego. Dodatkowo widoczne jest zabezpieczenie, w którym przy otworzonych drzwiach nie ma możliwości grzania - styk NC drzwi. Jeżeli dotrwałeś do tego momentu, to chciałbym Ci serdecznie pogratulować. Właśnie stworzyliśmy system ogrzewania do terrarium! Działanie programu. Przedstawiłem tryb manualny i reakcję na zamknięcie drzwi. Czerwona dioda na DIN CELSIUS sygnalizuje grzanie. 6. Informacja dla programisty Stwórzmy ostatnią część projektu, jakim jest wyświetlanie co pewien czas, bądź na żądanie, pewnych parametrów związanych z algorytmem. Wykorzystamy bardzo ciekawą funkcjonalność narzędzia Arduino PLC IDE - połączenie Sketcha z Arduino IDE i algorytmu sterowania z Arduino PLC IDE. Shared variables to zmienne, które będą wymieniane między algorytmem sterowania, a Sketchem. W Shared Variables w Outputs wpisujemy zmienne, które chcielibyśmy śledzić: Tworzymy nowy skrypt o nazwie do_zmiennych_OUT, w którym będziemy przypisywali wartości do zmiennych w Sketchu: Następnie tworzymy skrypt, który co wciśnięcie przycisku lub co 5 sekund wyświetli informacje o statusie algorytmu: Program do przesyłu informacji. Funkcja, która umożliwia wysłanie informacji do odbiornika. Przetestujmy wyświetlanie informacji w Serial monitor w Arduino IDE: Nasz program jest skończony! Podsumowanie To była bardzo długa i intensywna bitwa… Ale udało się! Poznaliśmy razem dużo możliwości sterownika. WiFi, Bluetooth, równoległość wykonywania operacji, sterowanie temperaturą, wyświetlanie informacji to tylko niektóre z możliwości tego PLC. Trzeba przyznać, że to potężna jednostka, która ma ogrom potencjalnych zastosowań. To naprawdę wszechstronne urządzenie, które może ułatwić wiele zadań i sprawdzić się w różnych projektach. Jest łatwe w obsłudze i daje dużo możliwości, co czyni je świetnym wyborem do nowoczesnych rozwiązań. ________ Informacja: zestaw z Arduino Opta na potrzeby niniejszego artykułu dostarczyła firma Botland - oficjalny dystrybutor Arduino.
- 3 odpowiedzi
-
- 4
-
- Elektronika
- Programownie
-
(i 1 więcej)
Tagi:
-
Matlab Co to Matlab? Podstawy, zastosowanie, pierwsze kroki
Marcel_S opublikował temat w Artykuły użytkowników
Wstęp Dotychczasowe moje artykuły dotyczyły przeważnie prostych tematów. Sortowanie danych, systemy liczbowe, czy sterowniki PLC. Teraz zaprezentuję Wam bardziej zaawansowane narzędzie. Napotkacie go na studiach, ale także jest szansa, że wykorzystacie go w swoich projektach. W tym artykule przedstawię Ci: Czym jest Matlab? Gdzie stosuje się Matlaba? Czy warto się go uczyć? Jak zastosować podstawowe techniki do pisania twoich pierwszych programów! Na swojej drodze projektowej na pewno spotkasz się z tym programem, dlatego warto wiedzieć, czym on jest. Źródło zdjęcia. Co to MATLAB? Logo Matlaba. Źródło zdjęcia. Matlab to interaktywne środowisko wykorzystywane przez inżynierów i naukowców na całym świecie. Służy on do zaawansowanych obliczeń i symulacji. Nazwa Matlab wywodzi się od słów Matrix Laboratory i pierwotnie został stworzony do obliczeń na macierzach. Macierze to bardzo ważny temat, bo dzięki nim możesz np. rozwiązać skomplikowany układ elektroniczny lub wykorzystać je do reprezentacji danych, przykładowo odczyty czujnika w czasie. Zastosowania Matlaba Matlaba możesz wykorzystać tam, gdzie masz styczność z: skomplikowanymi operacjami na macierzach i obliczeniami matematycznymi, przetwarzaniem sygnałów - obróbką danych i ich analizą, modelowaniem - przeprowadzaniem symulacji, np. w dziedzinie robotyki, teorią sterowania - implementacją algorytmów sterowania, regulacją, sztuczną inteligencją - machine learningiem i sztucznymi sieciami neuronowymi. Dzięki temu środowisku można wykonywać obliczenia arytmetyczne i na macierzach, pisać skrypty i rozmaite algorytmy lub wykreślać wykresy funkcji 2D i 3D. Należy jednak wspomnieć, że to nie jest arkusz kalkulacyjny jak Excel! Jest to narzędzie, które umożliwia znacznie więcej, szczególnie dla inżyniera. Praktycznym wykorzystaniem programu jest sterowanie urządzeniami. Matlab oferuje biblioteki, za pomocą których można przykładowo regulować temperaturę przedmiotu odpowiednio wysterowując grzałkę i pobierając dane o temperaturze. Takie dane możemy następnie przetworzyć i wykorzystać algorytmy przetwarzania sygnałów. Matlaba można wykorzystać przy symulacji robotyki. Możliwe jest stworzenie modelu manipulatora przemysłowego, na który działają różne obciążenia. Modelowanie i symulacje to ważny etap w projektowaniu skomplikowanych rozwiązań. Źródło zdjęcia. Podsumowując: Matlab to potężne narzędzie, które warto znać, bo ma wiele zastosowań. Jest bardzo wygodne, gdy potrzebne jest zaimplementowanie bardzo skomplikowanego algorytmu. Czy warto się uczyć Matlaba? Jak najbardziej! Warto spróbować swoich sił z tym środowiskiem. Dzięki niemu można rozwiązać wiele skomplikowanych problemów we względnie krótkim czasie przy niewielkim wysiłku. Matlab jest użyteczny w wielu dziedzinach, dlatego nawet podstawowa wiedza o nim będzie bardzo przydatna. Środowisko Matlab to nie tylko program do teoretycznych obliczeń, ale także do praktycznych zastosowań. Źródło zdjęcia. Niestety jest pewien haczyk… Matlab nie jest całkowicie darmowy. Istnieją różne wersje, z których można korzystać. Jeżeli jesteś studentem kierunku technicznego, to twoja uczelnia powinna oferować Ci darmowy dostęp do wersji akademickiej. W innym wypadku istnieje 30-dniowa wersja Trial. Możesz też korzystać z Matlaba Online w wersji Basic, który oferuje darmowe korzystanie z programu przez 20 godzin miesięcznie. Podstawy Matlaba Poniżej przedstawiam podstawowe instrukcje, które pozwolą Ci pisać pierwsze programy. Korzystam z Matlaba w wersji R2024a. Po uruchomieniu zobaczymy taki widok: MATLAB R2024a. Jest to okno główne Matlaba. Na razie zajmiemy się tylko jednym jego elementem. 1. Command window Jest to największe, białe okno, w którym widnieją znaki “>>”. Okno umożliwia wpisywanie pojedynczych komend i wykonywanie ich na bieżąco. Na tym etapie możemy zacząć wykonywać proste czynności, na przykład: przypisanie: a = 5 dodawanie, odejmowanie, mnożenie, dzielenie: +, -, *, / operatory porównania: >, <, >=, <=, ~=, komentarze: % Wynik pojawi się po wpisaniu komendy. Wstawienie średnika na końcu spowoduje niepokazanie wyniku. Przykład wykorzystania command window. Jeżeli w command window utworzymy zmienną, to zostanie ona zapamiętana w środowisku i będziemy mogli ją wykorzystać później. Zmienne są zapisywane w workspace. Aby go wyświetlić należy wpisać w command window polecenie workspace. 2. Podstawowe operacje na macierzach a) Definicja Aby zdefiniować macierz należy określić jej strukturę. Gdy definiujemy macierz zapisujemy jej elementy w nawiasie kwadratowym wpisując po kolei jej elementy. Kolejne wiersze oddzielamy średnikiem. Między elementami może być przecinek, lecz nie musi. Ważna uwaga: indeks pierwszego elementu wynosi 1! Zaznaczam to, bo w innych językach może wynosić 0. Aby utworzyć wektor należy stworzyć macierz o jednym wierszu, czyli nie rozdzielać liczb średnikiem. Przykład definicji macierzy: Definicja macierzy w command window. b) Odwołanie się do elementów Podobnie jak w innych językach programowania, aby odwołać się do konkretnego elementu należy podać jego indeksy. W przypadku Matlaba indeksy podaje się w nawiasach okrągłych: Wpisanie A(3, 2) zwróci element w trzecim wierszu, drugiej kolumnie, czyli 32. Odwołanie się do pojedynczego elementu. Możemy odwołać się do całego wiersza. Polecenie A(2, : ) zwraca wszystkie elementy z drugiego wiersza. Jeżeli chcemy odwołać się do całej drugiej kolumny, to napiszemy A(:, 2). Odwołanie się do całego wiersza lub kolumny. W przypadku, gdy potrzebujemy kilku wybranych wierszy/kolumn, to możemy przekazać wektor z potrzebnymi indeksami. Wektor, podobnie jak macierz, zapisuje się w nawiasach kwadratowych. Wybranie elementów z 1. i 3. wiersza i z 1. i 3. kolumny. Chcąc wybrać następujące po sobie wiersze/kolumny należy wpisać w odpowiednie miejsce następującą formułę ze swoimi danymi: indeks_startu:krok:indeks_stopu. Utworzyłem nową macierz. Wybrałem z niej elementy od 2. do 4. wiersza. Liczby są z kolumn od 1 do 5, ale z krokiem co 2. Ostatecznie zostanie wybrany element, z co drugiej kolumny. Krok można pominąć. Wtedy wyniesie 1. Stosując polecenie end jako w miejscu indeksu stopu Matlab wybierze wszystkie elementy od indeksu startu do ostatniego możliwego elementu. c) Macierze specjalne Matlab oferuje 3 podstawowe funkcje, dzięki którym można stworzyć specjalną macierz o charakterystycznych elementach: zeros() - macierz wypełniona zerami. ones() - macierz wypełniona jedynkami. rand() - macierz o losowych elementach. We wszystkich przypadkach pierwszy argument do funkcji to ilość wierszy, a drugi to liczba kolumn. Wykorzystanie funkcji zeros(), ones() i rand(). Może zdarzyć się sytuacja, w której będziesz potrzebował/a macierz o jednakowych elementach, ale różnych od 1. W tym celu: pomnóż macierz ones() przez tę liczbę… …lub dodaj do niej o 1 mniejszą liczbę. Dwie techniki definicji macierzy o jednakowych elementach. Macierz rand() zwraca losowe elementy od 0 do 1. Jeżeli chcesz przesunąć zakres, to zastosuj następującą technikę: losowa_macierz = rand(liczba_wierszy, liczba_kolumn) * rozpiętość_przedziału + indeks_rozpoczęcia. Przykładowo: index_rozpoczęcia = 5 - dolna granica przedziału, rozpiętość_przedziału = 7 - ile elementów będzie w dobranym zakresie, Ostatecznie największa możliwa liczba wyniesie 5+7=12, a więc przedział będzie wynosił liczby z zakresu <5, 12>. Macierz o losowych elementach z zakresu <5, 12>. d) Operacje na macierzach Oczywiście macierze możemy dodawać, mnożyć i modyfikować poprzez stałe. Zapamiętaj, że w Matlabie występują dwa rodzaje mnożenia macierzy. Zwykłe mnożenie zapisujemy jako gwiazdka ( * ). Element-wise multiplication zapisuje się jako kropka i gwiazdka ( .* ). Definicje nowych macierzy. Przykładowe operacje na macierzach. 3. Przydatne funkcje Przed rozpoczęciem tego punktu wygodniej będzie przenieść się do skryptu. W tym celu należy utworzyć nowy skrypt w lewym górnym rogu: Tworzenie nowego skryptu. Ukaże się okno, w którym można pisać skrypt. Znajduje się nad command window. a) Wykres funkcji 2D. Funkcja plot() Przed wyświetleniem funkcji, należy najpierw wygenerować dane osi x i y. Najprostszym sposobem jest wygenerowanie wektorów, które będą reprezentowały te dane. Można posłużyć się funkcją linspace(). Generuję wartości osi x. Posłużę się funkcją linspace(), która tworzy wektor o podanej rozpiętości i ilości elementów. Argumenty funkcji to odpowiednio: pierwszy element, ostatni element, ilość elementów w wektorze. Jako dane dla osi y wybiorę funkcję sinus. Wykorzystam funkcję sin(). Utworzę okno posługując się funkcją figure(n), gdzie n to numer okna. To nasze pierwsze okno, więc n wyniesie 1. Czas na wyświetlenie wykresu - korzystam z funkcji plot(x, y). Pierwszym argumentem jest wektor x a drugim y. Prosty skrypt wykorzystujący funkcję plot(). Po kliknięciu przycisku run powinien wyświetlić się poniższy wykres: Funkcja sinus. Można zauważyć, że wykres jest kanciasty. To przez małą liczbę elementów w wektorze. Zmieńmy liczbę elementów ze 100 do 1000. Wykres sinusa przy większej ilości danych. Wykres wygląda lepiej i dokładniej odwzorowuje funkcję. Oprócz funkcji sinus możesz zwizualizować inne funkcje trygonometryczne lub swoje własne dane. b) Funkcja subplot() Służy do wyświetlania kilku wykresów w jednym oknie. Moglibyśmy wyświetlać je w kilku oknach tworząc nowe funkcją figure(), ale metoda, którą teraz przedstawię, często przydaje się, gdy porównujemy ze sobą kilka danych przedstawionych na wykresach. Wykorzystuję wcześniej zrobiony sinus i dodatkowo tworzę cosinusa. Tworzę okno dla wykresów funkcji funkcją figure(). Wykorzystuję funkcję subplot(w, k, p). Argumenty funkcji kolejno: w – liczba wierszy w oknie, k – liczba kolumn w oknie, p – pozycja funkcji, którą chcemy wyświetlić. Gdy wyświetlamy pierwszy wykres, to piszemy 1, przy drugim 2 itp… Zakładając, że mamy dwie funkcje do wykreślenia, to będziemy potrzebowali dwóch wierszy (na dwie funkcje) i jedną kolumnę. Wykreślam kolejne wykresy funkcją plot(x, y). Skrypt wykorzystujący funkcję subplot(). Wynik: Wynik skryptu. c) Help i dokumentacja Matlaba Za nami już kilka przydatnych funkcji. Nie trzeba ich się uczyć na pamięć, bo Matlab posiada bogatą dokumentację. Wystarczy w command window wpisać help, a następnie nazwę funkcji, którą chcemy użyć. Wykorzystanie komendy help. W helpie jest opisana funkcja, jej działanie, argumenty, które przyjmuje, co zwraca, przykłady i wiele innych przydatnych informacji. Oprócz tego zachęcam Cię do przejrzenia ogólnej dokumentacji. Znajdziesz ją w prawym górnym rogu, klikając znak zapytania. Prawy górny róg Matlaba. Tutaj znajdziesz kilka cennych elementów, takich jak dokumentacja środowiska. d) Instrukcja warunkowa if Bardzo przydatnym elementem Matlaba jest możliwość wykorzystania instrukcji warunkowej. Jej składnia prezentuje się następująco: if warunek Działanie elseif warunek Operacja else Operacja end Utwórzmy prosty skrypt, który zaprezentuje działanie ifa. Sprawdźmy, czy podając 3 długości można z nich utworzyć trójkąt: Definiujemy 3 liczby, które będą reprezentowały długości 3 boków trójkąta: a, b, c. Trójkąt jest możliwy do stworzenia, jeżeli suma dowolnych dwóch boków jest większa od trzeciego boku. Innymi słowy, poniższe warunki muszą zostać spełnione: a + b > c, a + c > b, b + c > a. Jeżeli warunek będzie spełniony, to wyświetlimy napis ‘Można utworzyć trójkąt’. W przeciwnym wypadku wyświetlimy ‘NIE można utworzyć trójkąta’. Wykorzystanie instrukcji warunkowej if. Do wyświetlenia napisu można też użyć funkcji disp(). Taki program powinien wyświetlać wynik w command window. e) Funkcje Przenieśmy ten program do osobnej funkcji, którą będziemy mogli wywoływać wielokrotnie bez powtarzania nadmiernej ilości kodu. W tym celu wykonajmy następujące kroki: W tym samym folderze, w którym jest twój główny skrypt, utwórzmy nowy skrypt. Pamiętaj, że nazwa funkcji musi mieć taką samą nazwę, co nazwa skryptu. Utwórzmy funkcję zgodnie z poniższą składnią: function zmienna_wyjściowa = nazwa_pliku(zmienne_wejściowe) W naszym przypadku: Zmienną wyjściową nazwijmy odp (odpowiedź). Nazwę pliku nazwijmy czy_trójkąt. Zmienne wejściowe to będą liczby a, b, c. Przepiszmy całą funkcję z głównego skryptu zamieniając funkcje wyświetlającą napis na przypisanie odpowiedzi do zmiennej odp. Funkcja czy_trojkat. Wywołanie funkcji. f) Pętla for Ostatnim przydatnym elementem, który chcę Ci pokazać są pętle. Służą one powtarzania pewnej czynności tyle razy ile zadeklarowaliśmy. Jej składnia wygląda następująco: for iterator = pierwszy_element:krok:ostatni_element Operacje end Napiszmy prosty przykład, w którym kilka razy wykonamy kilka operacji na zmiennej. Wykorzystano pętlę for do powtórzenia operacji. Podsumowanie MATLAB to wszechstronne i potężne narzędzie, które oferuje szerokie możliwości w zakresie rozwiązywania skomplikowanych problemów inżynieryjnych, matematycznych i naukowych. Chociaż MATLAB nie jest darmowy, jego zalety w projektowaniu, modelowaniu i analizie danych czynią go wartym nauki i inwestycji, zwłaszcza dla osób związanych z dziedzinami technicznymi. Nawet podstawowa znajomość tego środowiska może otworzyć nowe możliwości i ułatwić pracę przy wielu projektach. Jeżeli chcesz zobaczyć jak Matlab jest wykorzystywany do praktycznych celów polecam artykuł o programowaniu Arduino z użyciem tego narzędzia. Spróbuj napisać kilka skryptów/algorytmów samodzielnie. Poznawaj więcej funkcji i możliwości i spróbuj wykorzystać potencjał środowiska.- 3 odpowiedzi
-
- 6
-
- Elektronika
- Programownie
- (i 1 więcej)
-
Intermediate Resource Integration System (IRIS2) IRIS to projekt, który leżał u mnie od dłuższego czasu - co jakiś czas projektuję urządzenia podłączane do komputera, ale zawsze ciężko dorobić do nich oprogramowanie, bo co urządzenie to zwykle inaczej się one komunikują (inne pakiety danych etc.) Rolą IRIS jest "uprościć" (może lepszym określeniem było ułatwić analizę kodu przez AI) ten proces maksymalnie jak tylko się da, a że moją domeną jest C# to padło też na wybór tego języka programowania. Przykładowe użycie IRIS /// <summary> /// Represents an example of a device that changes or reads the value of an LED /// using RUSTIC protocol messages. /// </summary> public sealed class ExampleArduinoLEDChangingDevice(SerialPortDeviceAddress deviceAddress, SerialInterfaceSettings settings) : SerialDeviceBase(deviceAddress, settings) { /// <summary> /// Get LED value /// </summary> public async Task<bool> GetLEDValue(CancellationToken cancellationToken = default) => await GetLEDValue<GetValueTransaction>(cancellationToken); private async Task<bool> GetLEDValue<TTransactionType>(CancellationToken cancellationToken = default) where TTransactionType : IDataExchangeTransaction<GetValueTransaction, GetValueRequestData, GetValueResponseData> { // Create request data GetValueRequestData requestData = new("LED"); // Exchange data GetValueResponseData result = await TTransactionType .ExchangeAsync<ExampleArduinoLEDChangingDevice, SerialPortInterface>(this, requestData, cancellationToken); // Return result of the operation return result.value.ToString() == "1"; } /// <summary> /// Set LED value /// </summary> public async Task<bool> SetLEDValue(bool value, CancellationToken cancellationToken = default) => await SetLEDValue<SetValueTransaction>(value, cancellationToken); private async Task<bool> SetLEDValue<TTransactionType>(bool value, CancellationToken cancellationToken = default) where TTransactionType : IDataExchangeTransaction<SetValueTransaction, SetValueRequestData, SetValueResponseData> { // Create request data SetValueRequestData requestData = new("LED", value ? "1" : "0"); // Exchange data SetValueResponseData result = await TTransactionType .ExchangeAsync<ExampleArduinoLEDChangingDevice, SerialPortInterface>(this, requestData, cancellationToken); // Return result of the operation return !result.IsError; } } Przykład korzysta z wbudowanego protokołu, który nazwałem RUSTIC. Protokół ten opiera się na prostych przypisaniach wartości przesyłanych jako linijki poprzez UART np. // Przypisanie zmiennej > ZMIENNA=32 < ZMIENNA=OK // Pobranie wartości zmiennej > ZMIENNA=? < ZMIENNA=32 Oprócz protokołu RUSTIC standardowo jest też zaimplementowany protokół LINE, który jest prostym protokołem wymiany wiadomości (np. do zaimplementowania w formie konsoli do debugowania). /// <summary> /// Read message from device /// </summary> public async Task<string> ReadMessage() => await ReadMessage<LineReadTransaction>(); private async Task<string> ReadMessage<TTransaction>() where TTransaction : IReadTransaction<TTransaction, LineTransactionData>, new() { // Read message LineTransactionData response = await TTransaction.ReadAsync<ExampleArduinoEchoDevice, SerialPortInterface>(this); // Return response return response.ToString(); } Oraz przykład użycia samego urządzenia w docelowej aplikacji: public static class ExampleApp { public static async void RunApp(string comPort) { // Create new Arduino echo device ExampleArduinoLEDChangingDevice device = new(new SerialPortDeviceAddress(comPort), new SerialInterfaceSettings(115200)); device.Connect(); // Exchange data example bool ledValue = await device.GetLEDValue(); Console.WriteLine("LED value: " + ledValue); // Wait for 500 ms to see the result await Task.Delay(500); // Set LED value bool setOk = await device.SetLEDValue(true); Console.WriteLine("Set LED value was success: " + setOk); // Read LED value ledValue = await device.GetLEDValue(); Console.WriteLine("LED value after set: " + ledValue); // Wait for 500 ms to see the result await Task.Delay(500); // Set LED value setOk = await device.SetLEDValue(false); Console.WriteLine("Set LED value was success: " + setOk); // Read LED value ledValue = await device.GetLEDValue(); Console.WriteLine("LED value after set: " + ledValue); // Disconnect device device.Disconnect(); } } Ale jak to działa? Podstawowym elementem IRIS jest klasa DeviceBase, która reprezentuje urządzenie (zazwyczaj sprzęt podłączony do komputera, ale nie jest to twarde wymaganie, równie dobrze urządzeniem może być API REST, ale to nadużywanie elastyczności projektu). Urządzenie to punkt dostępu dla zewnętrznych aplikacji, które będą komunikować się ze sprzętem (nie powinny one używać funkcji niskiego poziomu, aczkolwiek są one dostępne do bardziej radykalnych zastosowań). Urządzenie wykonuje Transakcje - wymianę danych pomiędzy komputerem, a sprzętem do niego podłączonym. Rolą transakcji jest wymiana pakietów danych, najprościej można sprowadzić transakcję do komendy, aczkolwiek jest to porównanie dość rozbieżne z możliwościami, gdyż jedna transakcja może również reprezentować serię komend wykonywanych w urządzeniu. Transakcje zawierają opis "jak" interfejs komunikacyjny powinien wykonać transmisję danych (odczyt/zapis) do urządzenia. Przykładowa trasakcja odczytania tekstu z urządzenia: public struct LineReadTransaction : IReadTransaction<LineReadTransaction, LineTransactionData>, ILineTransaction { public async Task<LineTransactionData> _ReadAsync<TDevice, TCommunicationInterface>( TDevice device, CancellationToken cancellationToken = default) where TDevice : DeviceBase<TCommunicationInterface> where TCommunicationInterface : ICommunicationInterface { // Get the communication interface and receive data ICommunicationInterface communicationInterface = device.GetCommunicationInterface(); return await communicationInterface .ReceiveDataAsync<LineReadTransaction, LineTransactionData>(this, cancellationToken); } } Transakcja ta implementuje interfejs IReadTransaction, który informuje iż jest to transakcja odczytu. Pierwszym parametrem generycznym (osoby zaznajomione z C++ mogą używać określenia template') jest typ transakcji (tzw. TSelf), a drugim jest typ zwracanej struktury danych. Transakcja wykorzystuje wbudowaną implementację odbioru danych z urządzenia poprzez jego interfejs komunikacyjny (zazwyczaj port szeregowy). Dodatkowo transakcja implementuje interfejs ILineTransaction, który zawiera kilka istotnych informacji: public interface ILineTransaction : ITransactionReadUntilByte, IWithEncoder<LineDataEncoder, byte[]> { byte ITransactionReadUntilByte.ExpectedByte => 0x0A; } Informuje on, iż pakiet danych kończy się zawsze bajtem 0x0A oraz, że do konwersji danych binarnych do struktur wykorzystywany jest konkretny Enkoder/Dekoder. By nie zagłębiać się za bardzo w szczegóły - niektóre interfejsy typu port szeregowy (w tym ten podłączony przez USB) używają danych binarnych do transmisji. Takie interfejsy nie wykorzystują standardowej implementacji interfejsu komunikacyjnego, tylko implementację rozszerzoną przeznaczoną dla interfejsów operujących danymi surowymi. Ta implementacja posiada wbudowane metody do wykrywania i dostosowywania się do wymagań danej transakcji - potrafią wykryć jaki enkoder jest w danej transakcji zaimplementowany i automatycznie użyć go do konwersji danych. Dla chętnych implementacja jest przedstawiona poniżej: async Task<TResponseDataType> ICommunicationInterface.ReceiveDataAsync<TTransactionType, TResponseDataType>( TTransactionType transaction, CancellationToken cancellationToken) { // Check if transaction supports data reader, if not throw exception if (transaction is not IWithDataReader withDataReader) throw new NotSupportedException("Transaction type is not supported"); // Read data using raw data reader byte[] data = await withDataReader .ReadDataAsync<IRawDataCommunicationInterface, TTransactionType, IRawDataReader, byte[]>(this, transaction, cancellationToken); // Decode data return await DecodeData<TResponseDataType, TTransactionType>(transaction, data); } private Task<TResponseDataType> DecodeData<TResponseDataType, TTransactionType>( TTransactionType transaction, byte[] rawData) where TResponseDataType : struct { // Check if user expects raw data and convert it if needed if (typeof(TResponseDataType) == typeof(byte[])) return Task.FromResult((TResponseDataType) Convert.ChangeType(rawData, typeof(TResponseDataType))); // Check if transaction supports encoder, if not throw exception if (transaction is not IWithEncoder withEncoder) throw new NotSupportedException("Transaction does not support encoder. Cannot decode data."); // Decode data using encoder withEncoder.Decode(rawData, out TResponseDataType responseData); return Task.FromResult(responseData); } Czytaki IRIS posiada też kilka bardziej zaawansowanych możliwości. Jedną widać w poprzedniej implementacji - czytaki. Czytaki służą do automatycznego odczytywania danych (zazwyczaj) binarnych z interfejsu i są niskopoziomową implementacją wcześniej wspomiananego systemu, który określa bajt na którym interfejs komunikacyjny ma zakończyć odczyt danych transakcji LINE. Wbudowane czytaki to odczytujący konkretną ilość bajtów z interfejsu oraz odczytujący do momentu napotkania konkretnego bajtu. Implementacja nowych czytaków jest bardzo prosta: public readonly struct UntilByteRawDataReader : IRawDataReader { public Task<byte[]> PerformRead<TTransactionType>(IRawDataCommunicationInterface communicationInterface, TTransactionType transaction, CancellationToken cancellationToken = default) where TTransactionType : ICommunicationTransaction<TTransactionType> { // Check if transaction is a read until byte transaction if (transaction is ITransactionReadUntilByte untilByte) return communicationInterface.ReadRawDataUntil(untilByte.ExpectedByte, cancellationToken); // If transaction is not supported, throw exception throw new NotSupportedException("Transaction type is not supported."); } } Ten czytak jest jednym z wbudowanych i działa wyłącznie z interfejsami binarnymi (w innym przypadku program wyrzuci wyjątek, że interfejs komunikacyjny nie jest prawidłowego typu). Pozwala on na automatyczny odczyt transakcji zgodnie z predefiniowaną wartością oczekiwanego bajtu. No i teraz wreszcie intefejsy komunikacyjne Interfejsy komunikacyjne to niskopoziomowe obiekty, które służą do komunikacji pomiędzy funkcjami systemowymi (np. port szeregowy), a funkcjami wysokopoziomowymi (np. wykonaj daną transakcję). Są to najbardziej złożone obiekty w IRIS co przekłada się na ich czasochłonną implementację. Poniżej można zobaczyć przykładowy kod interfejsu dla portu szeregowego: public sealed class SerialPortInterface : SerialPort, IRawDataCommunicationInterface { /// <summary> /// Used when reading data stream by single character to prevent unnecessary allocations /// </summary> private readonly byte[] _singleCharReadBuffer = new byte[1]; public SerialPortInterface(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) { PortName = portName; BaudRate = baudRate; DataBits = dataBits; Parity = parity; StopBits = stopBits; Handshake = Handshake.None; DtrEnable = false; RtsEnable = false; NewLine = Environment.NewLine; ReceivedBytesThreshold = 1024; } /// <summary> /// Connect to device - open port and start reading data /// </summary> /// <exception cref="CommunicationException">If port cannot be opened</exception> public void Connect() { try { // Open the port Open(); if (!IsOpen) throw new CommunicationException("Cannot connect to device - port open failed."); } catch (UnauthorizedAccessException) { throw new CommunicationException( "Cannot access device. Access has been denied. Is any software accessing this port already?"); } } public void Disconnect() => Close(); #region IRawDataCommunicationInterface /// <summary> /// Transmit data to device over serial port /// </summary> void IRawDataCommunicationInterface.TransmitRawData(byte[] data) { if (!IsOpen) throw new CommunicationException("Port is not open!"); // Write data to device Write(data, 0, data.Length); } /// <summary> /// Read data from device over serial port /// </summary> /// <param name="length">Amount of data to read</param> /// <param name="cancellationToken">Used to cancel read operation</param> /// <returns></returns> /// <exception cref="CommunicationException">If port is not open</exception> async Task<byte[]> IRawDataCommunicationInterface.ReadRawData(int length, CancellationToken cancellationToken) { if (!IsOpen) throw new CommunicationException("Port is not open!"); // Create buffer for data // TODO: Get rid of this allocation byte[] data = new byte[length]; int bytesRead = 0; // Read data until all data is read while (bytesRead < length) { bytesRead += await BaseStream.ReadAsync(data, bytesRead, length - bytesRead, cancellationToken); } // Return data return data; } /// <summary> /// Reads data until specified byte is found /// </summary> /// <param name="receivedByte">Byte to find</param> /// <param name="cancellationToken">Used to cancel read operation</param> /// <returns>Array of data, if byte is not found, empty array is returned</returns> /// <exception cref="CommunicationException">If port is not open</exception> async Task<byte[]> IRawDataCommunicationInterface.ReadRawDataUntil(byte receivedByte, CancellationToken cancellationToken) { // Check if device is open if (!IsOpen) throw new CommunicationException("Port is not open!"); // Read data until byte is found // TODO: Get rid of this allocation List<byte> data = new List<byte>(); // Read data until byte is found while (true) { int bytesRead = await BaseStream.ReadAsync(_singleCharReadBuffer, 0, 1, cancellationToken); // Check if data is read if (bytesRead == 0) continue; // If data is read, add it to list data.Add(_singleCharReadBuffer[0]); // Check if byte is found if (_singleCharReadBuffer[0] == receivedByte) break; } // Return data return data.ToArray(); } #endregion Obserwatory Obserwatory to obiekty, których rolą jest wykrywanie zmian w podłączonych urządzeniach. Robią to skanując wszystkie dostępne urządzenia co pewien czas (standardowo 500ms) oraz porównując dane do poprzedniego skanu. Gdy dane się różnią aktualizują pamięć podręczną (usuwają/dodają urządzenia) jednocześnie wysyłając zdarzenia o dodaniu/usunięciu urządzenia. Przykładowo chcąc obserwować wszystkie systemowe porty szeregowe tworzymy obserwator: public sealed class SerialPortDeviceWatcher : DeviceWatcherBase<SerialPortDeviceWatcher, SerialPortDeviceAddress> { /// <summary> /// Scan for all available devices /// </summary> protected override Task<(List<SerialPortDeviceAddress>, List<SerialPortDeviceAddress>)> ScanForDevicesAsync(CancellationToken cancellationToken) { // Get all available serial ports string[] ports = SerialPort.GetPortNames(); // Create lists for hardware and software devices List<SerialPortDeviceAddress> hardwareDevices = new(); List<SerialPortDeviceAddress> softwareDevices = new(); // Loop through all ports for (int serialPortIndex = 0; serialPortIndex < ports.Length; serialPortIndex++) { string port = ports[serialPortIndex]; // Create device address SerialPortDeviceAddress deviceAddress = new(port); // Add device to list hardwareDevices.Add(deviceAddress); softwareDevices.Add(deviceAddress); } // Return devices return Task.FromResult((hardwareDevices, softwareDevices)); } } A następnie możemy go użyć w programie: public static class ExampleCOMRecognitionApp { private static SerialPortDeviceWatcher _deviceWatcher = default!; public static async void RunApp() { // Create new COM recognition watcher _deviceWatcher = new SerialPortDeviceWatcher(); // Attach event handler _deviceWatcher.OnDeviceAdded += OnDeviceAdded; _deviceWatcher.OnDeviceRemoved += OnDeviceRemoved; // Start watching for COM devices _deviceWatcher.Start(); } public static async void KillApp() { // Stop watching for COM devices _deviceWatcher.Stop(); } private static void OnDeviceRemoved(SerialPortDeviceAddress hardwareDevice, SerialPortDeviceAddress softwareDevice) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Device disconnected: {softwareDevice.Address}"); Console.ResetColor(); } private static void OnDeviceAdded(SerialPortDeviceAddress hardwareDevice, SerialPortDeviceAddress softwareDevice) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"Device connected: {softwareDevice.Address}"); Console.ResetColor(); } } Obserwatory są w pełni asynchroniczne (działają w tle). Powyższy przykład wykrywa zmiany w urządzeniach portu szeregowego oraz wyświetla stosowny monit na konsoli. Podsumowanie To by było chyba na tyle... Projekt można znaleźć na GitHubie oraz przejrzeć Przykłady, znajomość języka Szekspira się przydaje. Plany na przyszłość? Obsługa BLE Obsługa REST poprzez WiFi dla ESP32 (tutaj się zastanawiam) Obsługa MQTT? Zrobienie obserwatora USB dla Linuxa (obecny działa wyłącznie na Windowsie) ... pewnie coś jeszcze 😉
-
Dzień dobry, od wczoraj testuje wyświetlacz TFT. Wszystko idzie dobrze, ale nie wiem jak mam wyświetlić zmienną int na takim wyświetlaczu. Bardzo proszę o odpowiedź. Z góry dziękuję! 😄
- 17 odpowiedzi
-
- elektronika
- int
-
(i 3 więcej)
Tagi:
-
Wstęp Czy wiesz, że FORBOTJESTCOOL(36) = 2676043599332257617141(10)? Ja też nie wiedziałem. Dowiedziałem się, gdy włączyłem kalkulator systemów liczbowych i odkryłem system trzydziesto szóstkowy. Do zapisu liczb wykorzystuje on cyfry od 0 do 9 i litery od A do Z. Rzeczywiście, istnieją systemy liczbowe, w których jest tak dużo dostępnych znaków, że można z nich zapisywać słowa, a nawet całe zdania. W tym przykładzie pokazałem Ci, że istnieją większe systemy liczbowe, niż te powszechnie znane, np. dwójkowy, czwórkowy, ósemkowy, czy dziesiętny. Przedstawię Ci system szesnastkowy, za pomocą którego można reprezentować kolory lub adresy urządzeń podłączonych do twojego Arduino 🙂 System szesnastkowy stosuje się do adresowania urządzeń, które komunikują się za pomocą standardu I2C. Źródło zdjęcia. Jeżeli nie znasz podstaw systemów liczbowych lub nie wiesz jak zamienić liczbę dziesiętną na binarną i na odwrót, to zapraszam Cię do mojego poprzedniego artykułu artykułu o systemie binarnym. To kluczowy temat, a wiedza z niego będzie przydatna w dalszych częściach tego artykułu! Konwersja liczb szesnastkowych i dziesiętnych System szesnastkowy, nazywany również heksadecymalnym (w skrócie hex), jak sama nazwa wskazuje, zawiera w sobie 16 znaków, którym przypisane są odpowiednie wartości systemu dziesiętnego. W systemie szesnastkowym korzystamy z cyfr 0..9 i liter A...F: A(16) = 10(10) B(16) = 11(10) C(16) = 12(10) D(16) = 13(10) E(16) = 14(10) F(16) = 15(10) a) Zamiana przy użyciu operacji modulo (dec → hex) Jest to standardowa, algorytmiczna operacja, którą można wykonać z każdym innym systemem liczbowym. W naszym przypadku liczbę dzieli się całkowicie przez 16(10) i zapisuje się resztę z dzielenia. Powtarza się to do momentu, aż w wyniku dzielenia znajdzie się 0. Wynik odczytywany jest od końca z reszt z dzielenia. Spróbujmy zamienić liczbę 234(10) na liczbę hex: Wynik dzielenia całkowitego kolejnych liczb zapisujemy w lewej kolumnie a resztę z dzielenia po prawej. Wynik, czyli liczbę szesnastkową, odczytuje się z reszt z dzielenia - od dołu do góry. Poniżej jeszcze dwa przykłady. b) Konwersja za pomocą wag (hex → dec) W tej metodzie korzystamy z definicji systemu liczbowego - każda cyfra na konkretnej pozycji definiuje wagę tej pozycji. Przeliczmy liczbę 1CA9(16): Podstawą liczby heksadecymalnej jest 16(10). Cyfra na ostatniej pozycji ma wagę 160(10), przedostatnia 161(10), kolejna 162(10) itp. Mnożymy wagi przez cyfry na ich pozycjach. Suma wszystkich iloczynów to wynik. c) Konwersja za pomocą systemu binarnego (hex → bin → dec) Jest to sztuczka, która może Ci pomóc zamienić w szybki sposób względnie małą liczbę hex na liczbę dziesiętną. Rozsuń liczbę hex na pojedyncze cyfry. Każdą cyfrę zamień na liczbę binarną. Złóż powstałe liczby binarne. Zamień liczbę binarną na decymalną. Metodę rozsuwania stosuje się głównie do zamiany hex → bin. Weźmy na tapetę liczbę D8(16): Rozdzielamy D8(16) na poszczególne cyfry - D(16) i 8(16). D(16) w hex to 13(10) w dec. 8(16) w hex to 8(10) w dec. Wiedząc to jesteśmy w stanie zapisać te liczby w systemie binarnym. Składamy obliczone części. Złożoną część przeliczamy na system dziesiętny. d) Konwersja za pomocą systemu binarnego (dec→ bin → hex) Tego typu zamiana liczb odbywa się analogicznie do poprzedniego przykładu, ale w odwrotnej kolejności: Zamień liczbę dec na bin. Rozsuń liczbę bin co 4 bity (4 cyfry). Zamień otrzymane liczby bin na hex. Złóż otrzymane liczby hex. Jak widzisz konwersja między systemem hex a dziesiętnym nie jest trudna. Użyliśmy tylko i wyłącznie wiedzy z poprzedniego artykułu. Polecam przećwiczyć te umiejętności wymyślając jakąś małą liczbę heksadecymalną (np. składającą się z dwóch cyfr) i zamienić ją na liczbę binarną/dziesiętną. Możesz też wymyślić liczbę dziesiętną i dokonać konwersji na heksadecymalną. Zastosowania liczb hex Do czego mogą się przydać liczby szesnastkowe? Ich najważniejszą właściwością jest zwięzły zapis dużych liczb: FFFF(16) = 65535(10) = 1111111111111111(2) Jak widać powyżej, liczbę 16 bitową można zapisać za pomocą 4 cyfr (liter) w systemie heksadecymalnym. a) Reprezentacja kolorów Jednym ze sztandarowych przykładów zastosowania systemu szesnastkowego jest zapis koloru. Kod składa się ze znaku # i trzech dwucyfrowych liczb szesnastkowych, które odpowiednio oznaczają poziomy kolorów: czerwonego, zielonego i niebieskiego (RGB). Im wyższa liczba, tym intensywniejszy jest poszczególny składnik koloru. Na przykład: ● #FF0000 - czerwony, rgb(255, 0, 0), ● #00FF00 - zielony, rgb(0, 255, 0), ● #0000FF - niebieski, rgb(0, 0, 255), ● #000000 - czarny, brak jakiegokolwiek składnika, rgb(0, 0, 0), ● #FFFFFF - biały, połączenie wszystkich składników, rgb(255, 255, 255), ● #A020F0 - fioletowy, rgb(160, 32, 240). b) Adresy pamięci W jaki sposób zapisać adres pod którym ma być przechowywana zmienna? Dzisiejsze komputery charakteryzują się ogromną pamięcią. Jest wiele możliwych adresów w pamięci pod które można zapisać jakąś informację. Wiąże się to z dużymi wartościami tych adresów. Liczby, które definiują dany adres są po prostu długie. W takim wypadku możemy skorzystać z liczby szesnastkowej, która skróci nam ten zapis. Tę technikę stosuje się często. Przykładem jest np. odczyt adresu zmiennej w języku C : Deklarujemy 3 zmienne po sobie. Odczytujemy po kolei ich adresy. Każdy odczytany adres będzie zapisany w systemie szesnastkowym… …a każdy adres będzie się różnił od poprzedniego o rozmiar zadeklarowanej zmiennej. Deklaracja zmiennej char (1 bajt): char a = 'X'; char b = 'Y'; char c = 'Z'; printf("Rozmiar pojedynczego znaku: %d\n", sizeof(char)); printf("Adres zmiennej a: %p\n", &a); printf("Adres zmiennej b: %p\n", &b); printf("Adres zmiennej c: %p\n", &c); Wyjście: Rozmiar pojedynczego znaku: 1 Adres zmiennej a: 0061FF1F Adres zmiennej b: 0061FF1E Adres zmiennej c : 0061FF1D Deklaracja zmiennej short (2 bajty) : short x = 1; short y = 2; short z = 3; printf("Rozmiar pojedynczego shorta: %d\n", sizeof(short)); printf("Adres zmiennej x: %p\n", &x); printf("Adres zmiennej y: %p\n", &y); printf("Adres zmiennej z: %p\n", &z); Wyjście: Rozmiar pojedynczego shorta: 2 Adres zmiennej x: 0061FF1A Adres zmiennej y: 0061FF18 Adres zmiennej z: 0061FF16 c) Adresy urządzeń w transmisji I2C Jednym z najpopularniejszych zastosowań systemu szesnastkowego jest zapis adresów urządzeń w komunikacji I2C (TWI). Skorzystajmy z karty katalogowej czujnika temperatury Adafruit 4089 z cyfrowym termometrem ADT7410, który może komunikować się właśnie przez ten protokół. Wycinek karty katalogowej Adafruit 4089. Źródło zdjęcia. Karta katalogowa urządzenia komunikującego się przez I2C powinna dostarczać klientowi informację o adresie danego urządzenia. Niektóre urządzenia dają możliwość konfiguracji adresu za pomocą pinów tego urządzenia. W przykładzie powyżej, jeżeli korzystamy z kilku tych samych czujników lub adres tego czujnika powtarza się z adresem innego, to za pomocą pinów A0 i A1 możemy zmienić jego adres. d) Adres MAC Jest to unikatowy i niepowtarzalny adres karty sieciowej nadany przez producenta. Stanowi on 48 bitową liczbę z czego pierwsze 24 bity oznaczają producenta karty sieciowej (ID producenta). Pozostałe 24 to ID urządzenia. Ze względu na rozmiary liczb, które są używane w adresach MAC, pisze się je w systemie szesnastkowym, co znacznie ułatwia odczyt takiego adresu. Przykładowe sposoby zapisu adresu MAC: 00:0c:29:cc:55:5e - MAC w Linux. Separatorem między parami znaków jest dwukropek. 00-0c-29-cc-55-5e - MAC w Windows. Separatorem między parami znaków jest myślnik. 000c.29cc.555e - MAC w urządzeniu sieciowym Cisco. Kropki między hextetami Adres MAC w Linux. Źródło zdjęcia. e) Komendy w postaci liczb hex Wysyłanie komend do mikrokontrolera może odbywać się właśnie w tym systemie. Dlaczego? Powód jest ten sam co wcześniej - możemy zawrzeć wiele komend w zwartym zapisie. Np. 0x00 - uśpij urządzenie, 0x01 - wybudź urządzenie, 0x02 - sparuj urządzenie, 0x03 - skonfiguruj urządzenie, 0x1F - włącz LED, itp. Jest to moim zdaniem dobry pomysł do organizacji i implementacji komunikacji z uC. Podsumowanie System szesnastkowy to bardzo przydatny i elastyczny system, który pozwala na zapisanie dużych liczb w krótkim zapisie. Jest on wykorzystywany praktycznie wszędzie, gdzie korzysta się z wielobitowych liczb. Nasuwa się jednak pytanie: Dlaczego nie korzystamy z większych systemów liczbowych? Jest kilka powodów: Złożoność zapisu - w systemie hex stosuje się 16 cyfr. Wyższe systemy liczbowe wprowadzają kolejne znaki oznaczające kolejne cyfry. W pewnym momencie może być po prostu za dużo tych znaków do interpretacji, co może komplikować program. Zgodność z potęgą liczby dwa - 16(10) = 24(10). System będący potęgą dwójki ułatwia obliczenia matematyczne i jest zgodny z logiką komputerową i cyfrową.
- 2 odpowiedzi
-
- 6
-
- Początkujący
- Elektronika
- (i 2 więcej)