Skocz do zawartości

Tablica liderów


Popularna zawartość

Pokazuje zawartość z najwyższą reputacją od 06.07.2020 we wszystkich miejscach

  1. 12 punktów
    RoChN 4 to robot typu linefollower, kolejny z serii Robotów o Chwytliwych Nazwach. Jest to konstrukcja w dużej mierze eksperymentalna, stworzona w celu przetestowania nowych pomysłów i komponentów. Wyniki nie były priorytetem, ale robot radzi sobie całkiem nieźle. Prawdopodobnie należy do kilku najlepszych, startujących na zawodach, linefollowerów w Polsce. Rozwijamy go razem z Pojemnikiem od ponad roku, w czasie wolnym między "przygotowaniami" do matury i jesteśmy zadowoleni z osiągniętych efektów. Historia powstania: Po zajęciu kolejnego czwartego miejsca RoChNem 3 uznaliśmy, że osiągnął on już szczyt swoich możliwości i trzeba zbudować coś nowego, aby wreszcie wskoczyć na podium. Początkowa “lista życzeń” wyglądała następująco: STM32 enkodery IMU Chcieliśmy też lepiej zaprojektować sekcję zasilania, żeby uniknąć losowych resetów procesora które zdarzały się poprzednikowi. W ciągu kilku tygodni stworzyliśmy projekt mocno inspirowany Fuzzym. Już wysyłaliśmy zamówienie, gdy odkryliśmy w projekcie poważny błąd. Jego naprawienie wymagałoby zaprojektowania całej płytki od nowa, więc schemat poszedł do kosza. W tym samym czasie na forum pojawił się opis Cukiereczka, który okazał się kopalnią wiedzy o linefollowerach. Uznaliśmy wtedy, że trzeba się inspirować najlepszymi i od tego czasu pomysł na RoChNa 4 był już inny. Postanowiliśmy w jednym robocie zamontować możliwie dużo “wodotrysków” i sprawdzić, które z nich dają tak dużą przewagę podczas zawodów. Dla upamiętnienia niedokończonego poprzednika oficjalny numer umieszczony na płytce ukończonego robota to 4.1. Jako, że jest to konstrukcja eksperymentalna, postawiliśmy na modułowość. Sterowniki silników, enkodery i moduł bluetooth umieszczone zostały na dodatkowych płytkach przylutowanych w pionie do głównego PCB. Dzięki temu rozwiązaniu byliśmy w stanie wygodnie wymieniać i przeprojektowywać peryferia, co bardzo się przydało podczas usuwania błędów konstrukcyjnych. MECHANIKA Kolory: Jak wiadomo ładne roboty szybciej jeżdżą[potrzebny przypis]. Dlatego od początku planowaliśmy jak będzie wyglądał nasz linefollower. Postanowiliśmy wzorować się na bolidach Formuły 1, w końcu są one dosyć szybkie. Mieliśmy już zamówione pionowe płytki w zielonym kolorze, więc ilość bolidów zawęziła się do malowania Force India z 2011 roku. Zdjęcie Cukiereczka jako robota, na którym się wzorowaliśmy, przemalowaliśmy w Paint-cie, żeby zobaczyć, jak będzie wyglądać. Na (nie)szczęście zielone płytki okazały się grubości 2mm, co dyskwalifikowało je z wykorzystania w robocie, więc mając wolny wybór zdecydowaliśmy się na żółto-czarną kolorystykę Renault. Ostatecznie jesteśmy całkiem zadowoleni z tego jak prezentuje się nasz robot. Podwozie: Robot składa się z dwóch płytek PCB połączonych taśmą FFC (raster 0,5mm, 24 piny) oraz dwiema listewkami. Próbowaliśmy je zrobić z włókna węglowego, ale okazało się, że nie umiemy wywiercić w nim otworów domowymi sposobami. Zamówiliśmy też listewki plastikowe, ale były tak miękkie, że na zakręcie przednia płytka mogłaby uderzać o tylną. Gdybyśmy kupili je z twardszego materiału, może zaoszczędzilibyśmy kilka gram na masie robota, ale aktualnie jesteśmy zadowoleni z aluminiowych. Z tyłu nie mogło zabraknąć listewki ochraniającej robota przed obróceniem podczas ruszania. Całkowita masa wynosi 62 gramy (73 z akumulatorem). Taśma FFC poza obsługą 14 czujników koloru i 3 żył z zasilaniem, posiada 7 żył, na których zależnie od użytych czujników odległości można wyprowadzić 2xI2C, SPI, jeden pin ADC, lub po prostu użyć ich jako GPIO. Napęd: Dwa standardowe silniki Pololu 10:1 z obustronnym wałem i węglowymi szczotkami jak zawsze spisują się bardzo dobrze, choć słychać już powoli ich zużycie (to już drugi komplet). Powoli osiągamy koniec ich możliwości, więc kolejna konstrukcja będzie pewnie używać czegoś innego. Zastosowaliśmy przedłużone mocowania, aby szerokość płytki nie przekraczała 10 cm i zamontowaliśmy od dołu zaślepki osłaniające przekładnie przed kurzem, który czasem w nie wpadał. Koła i opony: Początkowo robot jeździł na felgach toczonych w aluminium. Teraz ma dwa razy lżejsze wydrukowane na drukarce 3D, ale za to lekko bijące (musieliśmy rozwiercić otwór na wał, co nie wyszło idealnie). Kiedyś próbowaliśmy odlewania oponek z silikonu - miały podobną przyczepność, ale zużywały się dużo szybciej od Mini-Z, więc używamy tych drugich. ELEKTRONIKA PCB: Zdecydowaliśmy się na czterowarstwową płytkę, bo już podczas projektowania poprzedniego linefollowera mieliśmy problemy z optymalnym doprowadzeniem zasilania. Na głównej płytce jedna warstwa to wyłącznie masa, jedna zajmuje się zasilaniem, a dwie zewnętrzne wszystkimi sygnałami. Przednia płytka z czujnikami również jest czterowarstwowa, tam sygnały schowaliśmy do środka, aby zmniejszyć szumy na sygnałach analogowych. Mniejsze, żółte płytki są dwuwarstwowe. Zdecydowaliśmy się na elementy pasywne w obudowach 0402 - najmniejszych, które można wygodnie ręcznie polutować. W poprzedniej konstrukcji elementy w 0805 zajmowały dużo miejsca dlatego wybraliśmy mniejszy standard. Mikrokontroler: Pierwsza, porzucona wersja RoChNa 4 wykorzystywała STM32F1. Ostatecznie po przeanalizowaniu opisu Cukiereczka zdecydowaliśmy się na serię F7. W robocie zastosowaliśmy STM32F745VET6 w obudowie LQFP-100. Stu-pinowa obudowa okazała się niezbędna przy tylu elementach zastosowanych w robocie - tylko 5 pinów pozostało wolnych. Układ taktowany jest rezonatorem kwarcowym 8MHz, którego sygnał umożliwia stabilną pracę procesora na 216 MHz. Do programowania korzystamy z interfejsu SWD i odłamanego programatora ST-LINK z płytki Nucleo. Nie planowaliśmy programowania przez USB, choć gdyby udało się je uruchomić, to prawdopodobnie byśmy spróbowali. Zapas mocy obliczeniowej okazał się bardzo przydatny przy testowaniu najdziwniejszych rozwiązań. Czujniki linii: Użyliśmy standardowych elementów KTIR0711S. Rozmieszczone są w dość wąski (8,5 cm szerokości) łuk. Ponieważ nasz STM miał 16 pinów z ADC, a chcieliśmy także mierzyć stan baterii i mieć możliwość zamontowania analogowego czujnika odległości do wykrywania przeszkód, zdecydowaliśmy się na 14 sztuk. Odpowiedni dystans od podłoża zapewniają cztery rezystory w obudowach ¼ watta. Sterowniki silników: Po rozmowach z autorem Cukiereczka doszliśmy do wniosku, że sterowniki TB6612 mogą nie wytrzymać nagłych szarpnięć silnikami, więc wybraliśmy mostek H VNH7013 (40A prądu stałego) oraz układ DRV8703 sterujący takim mostkiem. Zestaw mógł sterować tylko jednym silnikiem, więc musieliśmy zamontować takie dwa w robocie. Jednak intuicja podpowiedziała nam, że coś może nie zadziałać, więc zamontowaliśmy je na osobnych, żółtych, pionowych płytkach, abyśmy mogli ewentualnie wymienić je na TB6612. Podczas testów udało nam się raz zakręcić silnikiem i dwa razy spalić mostek, więc ostatecznie robot jeździ na 4 starych sterownikach. Daje to 4,8A ciągłego poboru prądu przez silnik (12,8 w piku (♠)). Nie stosujemy żadnych opóźnień w celu oszczędzania tych układów i nie zostały one jeszcze uszkodzone (a przeżyły już wiele dziwnych manewrów), więc rozwiązanie choć mało profesjonalne okazało się skuteczne. Enkodery: Wybraliśmy enkodery magnetyczne AS5134 z uwagi na dużą szybkość kolejnych odczytów położenia magnesu, możliwość komunikacji po SPI do 6MHz i łatwą dostępność. Początkowo błędnie zasilaliśmy je napięciem 3,3V (układ potrzebuje co najmniej 4,5V), więc musieliśmy przeprojektować ich płytki (tu znowu pomogła nam modułowość projektu). Na prawej płytce umieściliśmy jedyny w robocie stabilizator na 5V i przewodem doprowadziliśmy zasilanie do lewej. Napięcia sygnałów z enkodera do STM-a (5V->3,3V) są dzielone dwoma rezystorami, a w drugą stronę (3,3V->5V) wzmacniane układem MOSFET-a i dwóch rezystorów. Chcieliśmy zastosować 6-biegunowe magnesy firmy Pololu, jednak enkoder zadziałał dopiero gdy użyliśmy pierścieniowe magnesy neodymowe namagnesowane po średnicy. Ustawienie magnesu i czujnika Halla w idealnej osi znacznie zwiększyło dokładność odczytów. Pomiary wykonujemy z częstotliwością 2 kHz. Zmiana kąta między odczytami wynosi zwykle 20-80 stopni, więc niedokładność pomiarów ±2 stopnie nie wpływa za bardzo na stabilność algorytmu sterującego silnikami. Bluetooth: Tu bez żadnych szaleństw. HC-05 spełnia swoją funkcję zadowalająco. Rozglądaliśmy się za czymś innym, ale nie znaleźliśmy rozsądnej alternatywy. Brakowało nam miejsca na głównej płytce robota, dlatego został on umieszczony na osobnej pionowej. Planowaliśmy wpinanie go na złączach goldpin, aby była możliwość wyjmowania go na przejazdy finałowe (mniejsza masa itd). Mieliśmy problemy z tym złączem. Wstrząsy podczas jazdy powodowały krótkie utraty zasilania podczas jazdy, co skutkowało zerwaniem połączenia i uniemożliwiało zatrzymania robota. Przylutowanie dwóch przewodów rozwiązało problem. Zasilanie: Dalej stosujemy akumulatory li-po Dualsky 2S, 150-250 mAh, te same co w Rochnie 3. Montujemy je do robota na rzep i przytrzymujemy kawałkiem drutu, aby się nie kołysały na boki. Mamy 7 akumulatorów oraz jedną ładowarkę na dwa linefollowery, więc podczas zawodów ledwo nadążamy z ich ładowaniem. Robot pożera naprawdę dużo prądu, dlatego wiele układów usypiamy lub odłączamy od zasilania (usypiając stabilizatory) kiedy robot akurat nie jedzie (patrz tabelka, nie uwzględniono prądu pobieranego przez silniki). IMU: Czujnik przestrzeni miał docelowo pomagać w zapamiętywaniu trasy i niwelowaniu poślizgu podczas pokonywaniu zakrętów. Użyliśmy układu LSM6DSM i przylutowaliśmy go bezpośrednio do głównej płytki. Pomimo tego, że podczas testów statycznych układ działa bez zarzutu (1g pojawia się w odpowiednim kierunku), to podczas jazdy wyłapywane są wszystkie wstrząsy i drgania, co uniemożliwia wykorzystanie danych w jakimkolwiek celu nawet po przefiltrowaniu. Czyszczenie kółek podczas jazdy: Przetestowaliśmy system umożliwiający czyszczenie kółek w trakcie przejazdu w celu minimalizacji wpływu przyklejającego się brudu. Rolkę do zbierania kurzu przymocowaliśmy do stelarzu wydrukowanego na drukarce 3D. System działał, ale stawiał bardzo duży opór, co znacząco zmniejszało osiągi robota, utrudniając nawet ruszenie z miejsca. Niewykorzystane bajery: Karta SD - Docelowo miała być użyta jako magazyn danych na temat trasy oraz aktualnej konfiguracji robota. Niestety nie udało nam się jej uruchomić poza odczytaniem informacji o danej karcie. Ostatecznie jako magazyn danych użyta została część niewykorzystanej pamięci FLASH. USB - Miało służyć do szybkiego wymieniania dużych ilości informacji pomiędzy robotem, a komputerem. Z nieznanych nam przyczyn nie udało nam się skłonić go do działania. Dane z robota wysyłamy albo przez Bluetooth albo zczytujemy całą pamięć FLASH programatorem. EEPROM - Został dodany na osobnej płytce, aby łatwiej zapisywać dane konfiguracyjne. Okazał się jednak nie być niezbędny, i zapisujemy te dane na kilku ostatnich bajtach pamięci. SOFTWARE Program został napisany w C z użyciem bibliotek HAL. Nigdy nie robiliśmy żadnego większego projektu na STM32, więc trzeba było nauczyć się wielu rzeczy. Algorytm podążania za linią składa się z głównego regulatora, który na podstawie odczytów z czujników linii zadaje prędkość obrotową dwóm regulatorom PD, które sterują prędkością kół. Dużą część programu zajmuje kod obsługujący komunikację przez bluetooth. Praktycznie wszystkie istotne zmienne można zmieniać zdalnie, istnieje także możliwość podglądu odczytów z czujników linii i innych danych w czasie rzeczywistym. Robot zapamiętuje cześć swojej konfiguracji w pamięci FLASH. Na potrzeby kalibracji i sprawdzania różnych rzeczy powstało kilka mniejszych programów na komputer. Głównie generują wykresy na podstawie danych z robota. Zapamiętywanie trasy: Od początku planowaliśmy zapamiętywać trasę przejazdu. Po kilku tygodniach prób i błędów udało się zrobić program, który działał na krótkich trasach. Niestety na długich się gubił. Uznaliśmy, że bez doświadczenia, wiedzy teoretycznej i pewnie dokładniejszych czujników nie będziemy w stanie zrobić lepszego algorytmu. Poniżej screenshot z programu wizualizującego zapamiętaną trasę. Usprawnienia sterowania: Robot ma kilka różnych sposobów aktywacji dodatkowego hamowania na zakrętach aby z nich nie wypadać. Uwzględnia też podczas pokonywania trasy różnego rodzaju przypadki szczególne np. kąty ostre ("teoretycznie" niemożliwe) i skrzyżowania. Na każdych zawodach sprawdzamy, jaka kombinacja ustawień zadziała najlepiej, choć mamy kilka optymalnych ustawień. PODSUMOWANIE Robot w pełni spełnił swoje zadanie, czyli przetestowanie nowej rodziny procesorów, enkoderów, IMU i zapamiętywania trasy, a wyniki są lepsze niż się spodziewaliśmy. Niestety z powodu epidemii ilość zawodów, w których mógł wystartować została mocno ograniczona. Projekt dalej jest rozwijany i wdrażamy kolejne ciekawe rozwiązania, które opiszemy, jeżeli sprawdzą się na zawodach. Osiągnięcia: III miejsce Robocomp 2019 IV miejsce Sumo Challenge 2019 II miejsce Robotic Arena 2020 Filmy z zawodów:
  2. 11 punktów
    Zabierając się za rozbudowane projekty, jest szansa że zabraknie nam pamięci. Jeżeli potrzebujemy przechować np. dużą tablicę zmiennych, możemy ją załadować do zewnętrznej pamięci RAM. Co jednak, gdy potrzebujemy przechować duże dane, ale stałe? Najlepiej podpiąć zewnętrzną kość pamięci. Co prawda, nie będzie możliwości zapisu danych (a przynajmniej tak łatwo) ale nasz mikrokontroler uzyska nawet kilka megabitów (!) dodatkowej pamięci. Może to być program, tekst, a nawet obrazek. Programując mikrokontroler najprawdopodobniej wgrywasz program do pamięci FLASH. Do dyspozycji możesz mieć także małą, dodatkową pamięć EEPROM. Czym się one różnią? Pamięć FLASH to ewolucja pamięci EEPROM. Same pamięci PROM są dość stare, mają ok. 50 lat. Są one jednorazowe - można je zaprogramować tylko raz. Ich nowocześniejszą wersją jest pamięć UVEPROM - które łatwo rozpoznać po okienku, które pozwala światłu ultrafioletowemu kasować całą pamięć. Pamięć EEPROM można skasować i zaprogramować elektrycznie, lecz zwykle z pomocą 12V. Z racji tego że te pamięci są stare jak komputery, interfejs jest równoległy; do podłączenia mamy, zwykle ośmiobitową, szynę danych, oraz szynę adresów - jej długość będzie zależeć od pojemności kości. Rozwój elektroniki sprawił, że powstał kolejny rodzaj pamięci: FLASH. Do programowania nie wymagają wysokiego napięcia, a do kasowania światła UV. Aby je zaprogramować, muszą być najpierw wyczyszczone, co można robić co najmniej stronami (nie pozwalają czyścić pojedynczych bajtów). Pamięci te zaczęto masowo stosować jeszcze pod koniec XX wieku. Kości pamięci. Od lewej: PROM, UV EPROM, FLASH (w dwóch obudowach), EEPROM (po I2C) Do podstawowych rozwiązań powinny wystarczyć małe pamięci EEPROM, które podłączymy po I2C. Trochę większe pamięci można podłączyć przez SPI. Lecz do niektórych rozwiązań najlepiej wykorzystać właśnie te starsze kości. I właśnie do nich zrobiłem programator, który dedykuję kościom FLASH (ponieważ tych mam najwięcej... ) przeróbka na EPROM/EEPROM jest możliwa oczywiście, należy zaopatrzyć płytkę w wyższe napięcie i odpowiedni MOSFET. Następnie najlepiej zapoznać się z notą katalogową wybranej pamięci, i przerobić program. Wracając do pamięci FLASH - zajrzyjmy do noty katalogowej wybranej kości - tutaj 29F002, w której był przechowywany BIOS komputera PC. Wiemy zatem, że jest to kostka o pojemności dwóch megabitów, wyprodukowana w technologii CMOS (jest wrażliwa na ładunek elektrostatyczny). Posiada dość niski czas odczytu (120ns), pobiera mało prądu, i co najważniejsze - nie wymaga wysokiego napięcia do programowania/kasowania. Ilość pinów może przerażać, lecz nie ma się czego bać. VCC i GND to zasilanie, na które podajemy 5V. Pin CE (Chip Enable) uaktywnia kość. Jeżeli z szyn korzysta więcej urządzeń niż jedno, to nasza kość pamięci nie będzie im przeszkadzać. Kreska nad nim oznacza, że jest on aktywny w stanie niskim - czyli żeby włączyć czip, musimy podciągnąć ten sygnał do masy. W programatorze będzie on zwarty do masy, jako że to jedyny układ korzystający z szyn. Pin OE (Output Enable) pozwoli na odczyt danych. W chwili podpięcia tego sygnału do masy, na szynę danych ustawiany jest bajt z podanego adresu. Pin WE (Write Enable) pozwala na zapis. Bajt który w chwili zapisu będzie na szynie danych, zostanie zapisany do podanego adresu. Pozostałe piny (D0..D7) to szyna danych, a A0..A17 to szyna adresu. Liczby na tych szynach są zapisywane w systemie binarnym - co oznacza że możemy operować na 262 144 (2^18) liczbach, a każda liczba może mieć wartość z zakresu 0-255. Jeżeli wiemy jak podłączyć naszą kość, możemy to już zrobić: przygotowałem także schemat układu. Sercem programatora jest Atmega 8, z racji tego że 328 nie miałem żadnej wolnej (nie ma powodu żeby jej nie stosować). Wokół niej powinien się znajdować przycisk reset z rezystorem podciągającym, kondensator do filtrowania zasilania. Przejdźmy do połączenia pamięci z atmegą - dlaczego między nimi są 2 układy? Są to dwa rejestry przesuwne, 74HC595. Dzięki trzem pinom z mikrokontrolera możemy obsłużyć aż 16 (a nawet więcej, bo rejestry można łączyć) pinów. Jeżeli dana pamięć ma szerszą szynę adresów, nieużyte piny powinniśmy połączyć z masą. Co do szyny danych, tą podłączyłem bezpośrednio do Atmegi. Piny RX i TX są zarezerwowane dla konwertera USB-UART, w celu komunikacji z komputerem. Tak wygląda układ zmontowany przeze mnie: Niebieski, podłużny element to drabinka rezystorowa, zastępująca 8 rezystorów podciągających szynę danych do masy. Dałem na wszelki wypadek. Teraz należy odpowiednio zaprogramować mikrokontroler. Obecnie dane do wgrania są przechowywane w pamięci mikrokontrolera, co będzie działać dopóki chcemy wgrywać małe dane. Pracuję nad programem w VS, który będzie przekazywał dane z komputera do Atmegi - w razie czego zaktualizuję post. Gotowa paczka z kodem i generatorem są na dole do pobrania. Kod napisałem w VS Code, z wtyczką Platformio (kod powinien być w 100% kompatybilny z Arduino IDE). Zacznijmy od zdefiniowania pinów: #include <Arduino.h> #define SHIFT_DATA 2 #define SHIFT_CLK 3 #define SHIFT_LATCH 4 #define D0 5 #define D7 12 #define WRITE_EN 13 #define OUTPUT_EN A0 Pierwsze 3 piny są dla rejestru przesuwnego. Podaliśmy także Pierwszy i ostatni pin szyny danych - będziemy operować na pętlach for(), więc nie musimy podawać wszystkich pinów (ważne żeby były podłączone po kolei). Kolejne dwa piny to WE i OE (CE jest zwarty do masy). Przejdźmy do właściwego kodu. Zaczniemy od napisania funkcji niezbędnych do działania urządzenia: void setAddress(int address) { shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address >> 8); shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address); digitalWrite(SHIFT_LATCH, LOW); digitalWrite(SHIFT_LATCH, HIGH); digitalWrite(SHIFT_LATCH, LOW); } Funkcja ta ustawi wybrany adres na szynie adresów. Najpierw wysyłamy do rejestrów pierwsze 8 bitów zmiennej. potem 8 kolejnych, ponieważ funkcja shiftOut() może wysłać tylko ośmiobitowy bajt. Po wysłaniu bitów zatwierdzamy je, ustawiając na chwilę latch w stan wysoki. void setDataBusOut() { for(int i = D0; i <= D7; i++) pinMode(i, OUTPUT); } void setDataBusIn() { for(int i = D0; i <= D7; i++) pinMode(i, INPUT); } Z racji tego, że na szynie danych będziemy zapisywać i odczytywać dane, te funkcje umożliwią ustawić szynę jako wejście lub wyjście. void setByte(byte out) { for(int i = D0; i <= D7; i++) digitalWrite(i, bitRead(out, i - D0)); } byte readByte() { byte bajt = 0; for(int i = D0; i <= D7; i++) if(digitalRead(i)) bitSet(bajt, i - D0); return bajt; } Funkcja setByte() ustawi dany bajt na szynie danych. Z kolei readByte() zwróci bajt, który jest obecnie na szynie danych. Wykorzystałem tutaj funkcje bitSet i bitRead, które pozwalają na operacje na bitach (odsyłam do dokumentacji Arduino). Jeżeli chodzi o podstawowe funkcje - to tyle. Teraz przeanalizujmy poszczególne arkusze noty katalogowej: Wygląda skomplikowanie, ale nie ma się czego bać. W celu odczytania bajtu z pamięci, musimy ustawić docelowy adres. Pin CE musi być zwarty do masy (u nas jest), a pin WE do dodatniej szyny zasilania. Następnie krótko po podpięciu OE do masy, dostajemy dane na szynie danych. Zróbmy zatem funkcję, która zwróci odczytany bajt: byte readData(int adr) { byte bajt = 0; setDataBusIn(); digitalWrite(WRITE_EN, HIGH); digitalWrite(OUTPUT_EN, HIGH); setAddress(adr); digitalWrite(OUTPUT_EN, LOW); delayMicroseconds(1); bajt = readByte(); digitalWrite(OUTPUT_EN, HIGH); return bajt; } Najpierw ustawiamy szynę danych jako wejście. Upewniamy się, że WE i OE są w stanie wysokim. Wysyłamy docelowy adres, ustawiamy OE na stan niski, czekamy chwilę, odczytujemy bajt, i ustawiamy OE z powrotem na stan wysoki. Zwracamy odczytany bajt. Teraz czas na prawdziwą zabawę. Dlaczego nagle jakieś komendy mamy wysyłać? Otóż jest to zabezpieczenie przed przypadkowym skasowaniem/zapisaniem danych. Wysłanie komend polega na wysłaniu 3 odpowiednich danych w odpowiednie adresy. Kolejno AA do adresu 555, 55 do adresu 2AA, i A0 do 555. Literka 'H' oznacza że liczby te są zapisane w systemie heksadecymalnym - tutaj mocno polecam pobawić się kalkulatorem w trybie programisty: Tak więc operacja zapisu sprowadza się do zapisania 4 bajtów. Zapiszmy funkcję zapisującą jeden bajt: void writeByte(byte bajt, int adr) { digitalWrite(OUTPUT_EN, HIGH); digitalWrite(WRITE_EN, HIGH); setAddress(adr); setByte(bajt); digitalWrite(WRITE_EN, LOW); digitalWrite(WRITE_EN, HIGH); } Myślę że objaśniać nie trzeba. Teraz zapiszmy funkcję zapisującą jeden bajt w pamięci: void programData(byte bajt, int adr) { setDataBusOut(); writeByte(0xAA, 0x555); writeByte(0x55, 0x2AA); writeByte(0xA0, 0x555); writeByte(bajt, adr); delayMicroseconds(1); } Na końcu dodałem mały odstęp czasowy, ponieważ czip po zapisie przez chwilę jeszcze prowadzi wewnętrzne operacje. Do zrobienia została jeszcze jedna rzecz - nie można zapisywać zapisanej już pamięci. Dlatego patrzymy w notę jak wyczyścić kość: Całość się sprowadza do wysłania 6 bajtów. void chipErase() { setDataBusOut(); writeByte(0xAA, 0x555); writeByte(0x55, 0x2AA); writeByte(0x80, 0x555); writeByte(0xAA, 0x555); writeByte(0x55, 0x2AA); writeByte(0x10, 0x555); delayMicroseconds(1); } I jeżeli chodzi o kwestię techniczną - to tyle. Przejdźmy zatem do setup(): void setup() { pinMode(SHIFT_DATA, OUTPUT); pinMode(SHIFT_CLK, OUTPUT); pinMode(SHIFT_LATCH, OUTPUT); pinMode(WRITE_EN, OUTPUT); pinMode(OUTPUT_EN, OUTPUT); digitalWrite(OUTPUT_EN, HIGH); digitalWrite(WRITE_EN, HIGH); writeByte(0xf0, 0x0000); //reset Serial.begin(115200); delay(3000); Serial.println("programator FLASH v1.0 - gotowy."); } Piny sterujące ustawiamy jako wyjście (i od razu jako stan wysoki), oraz resetujemy czip. Do tego wystarczy jedna komenda jak widać. W loopie zaś sprawdzamy jedynie czy z serialu przyszły nowe dane: void loop() { if(Serial.available()) readSerial(Serial.read()); } Jeżeli tak, wykonujemy dużego switcha: void readSerial(byte bajt) { switch(bajt) { case 'E': Serial.println("Czyszcze pamiec FLASH..."); chipErase(); delay(200); Serial.println("Wyczyszczono!"); break; case 'W': Serial.println("Wgrywam przykladowy program..."); for(int i = 0; i < sizeof(data); i++) programData(data[i], i); Serial.println("Wgrano pomyslnie!"); break; case 'R': Serial.println("Odczytuje dane od adresu 0x0000..."); for(int y = 0; y <= 249; y+=5) { for(int x = 0; x <= 4; x++) { Serial.print(readData(x+y), HEX); Serial.print(" "); } for(int x = 0; x <= 4; x++) Serial.print(char(readData(x+y))); Serial.println(""); } break; case 'T': Serial.println("Resetuje pamiec..."); setDataBusOut(); writeByte(0xF0, 0x0000); setDataBusIn(); break; default: Serial.println("Programator FLASH v1.0 - by Leoneq ;3"); Serial.println("-------------------------------------------"); Serial.println("E - Wyczysc pamiec"); Serial.println("R - Odczytaj zawartosc pamieci"); Serial.println("W - Wgraj przykladowy program"); Serial.println("T - Zresetuj pamiec FLASH"); break; } } Oczywiście te funkcje są przykładowe, zachęcam do samodzielnej zabawy z programem. Zostaje teraz kwestia samych danych do zapisu. Jak napisałem, docelowo mają one być nadawane z komputera PC, a obecnie są one przechowywane w pamięci uC: //generated data const byte data[11] = {B00100001, B11111111, B00000001, B11111001, B00111110, B00000001, B11010011, B00000000, B11000011, B00000100, B00000000}; Specjalnie do tego zadania napisałem generator w C++. Tworzy on z plików .bin (kod maszynowy) tablicę zmiennych. #include <iostream> #include <fstream> #include <bitset> using namespace std; ifstream source; fstream output; //0x21 0xFF 0x01 0xF9 0x3E 0x01 0xD3 0x00 0xC3 0x04 0x00 int main() { cout << "FLASH code generator v1.0\nby Leoneq ;3\n" ; source.open("ROM.bin", ios::binary | ios::ate); if(source.good()) cout << "Successfully opened the file.\n"; else cout << "Couldn't open the file. Put 'ROM.bin' in the same directory where .exe is."; int size = source.tellg(); source.close(); source.open("ROM.bin", ios::binary); char buffer[size]; source.read (buffer, size); source.close(); output.open("array.txt", ios::out); output << "//generated data\nconst byte data[" << size << "] = {"; for(int i = 0; i < (size-1); i++) output << "B" << bitset<8> (buffer[i]) << ", "; output <<"B" << bitset<8> (buffer[size]) << "};"; cout << "The array is in 'array.txt'. Exit..."; return 0; } Program wczytuje plik ROM.bin, który powinien znajdować się w tym samym miejscu co .exe, a wygenerowaną tablicę zapisze w array.txt. Tą tablicę należy skopiować do kodu Atmegi, wgrać i... cieszyć się programatorem. Linki: dokumentacja kości FLASH nota katalogowa rejestrów przesuwnych datasheet mikrokontrolera opis funkcji shiftOut(); bitSet(); bitRead(); ------------------ W planach mam więcej poradników tego typu, dlatego prosiłbym o komentarze co jest dobrze napisane, a co źle. Dzięki ^^ box.zip
  3. 11 punktów
    Czasem gdy zaprojektujemy jakiś szczególnie udany projekt, chcielibyśmy wyprodukować więcej niż tę jedną czy dwie sztuki — żeby sprzedać, rozdać, użyć w warsztatach czy jakiekolwiek inne zastosowanie wymyślimy. No i pojawia się problem, bo co innego zmontować jeden prototyp, a co innego ślęczeć codziennie i składać ten sam projekt od nowa kilkadziesiąt razy. Oczywiście możemy sobie życie ułatwić stosując bardziej zaawansowane narzędzia, ale o ile jeszcze przerobienie opiekacza na piecyk do wypalania płytek ma sens, o tyle kupowanie maszyny pick-and-place dla jednego projektu ma sens mniejszy. Na szczęście istnieją fabryki, które z dziką rozkoszą dla nas taki projekt wyprodukują. Jak to wygląda w praktyce? Usługa, której szukamy nazywa się po angielsku "PCB assembly" i zazwyczaj firmy, które robią płytki drukowane także oferują ich montaż. Aby skorzystać z takiej usługi, musimy mieć trochę więcej dokumentacji do naszego projektu: pliki gerber z projektem płytki — panelizację chyba w tym wypadku lepiej zostawić fabryce, wtedy zrobią to tak, żeby im jak najlepiej pasowało do reszty procesu, plik pick-and-place zawierający pozycje wszystkich komponentów — zazwyczaj program generujący gerbery także generuje ten plik, lista komponentów — wraz z nazwami na płytce, wielkością i wartością, oraz najlepiej z linkami do przykładowych podzespołów w sklepach, parametry płytki — kolor, grubość, wykończenie, dodatkowe wymagania, liczba sztuk, jaką zamawiamy, jeśli nasz projekt wymaga programowania, to wysyłamy firmware do wgrania i instrukcje jak to zrobić, opcjonalnie, instrukcja testowania, uwagi na temat montażu i/lub pakowania. Kiedy już skompletujemy powyższe informacje, załączamy je wszystkie do e-maila, w którym prosimy fabrykę o wycenę. Zazwyczaj po jednym lub dwóch dniach dostaniemy informację na temat tego ile to będzie kosztować i ile potrwa. Możemy w tym momencie dopytać o szczegóły, zmienić pewne rzeczy, etc. — kiedy będziemy zadowoleni z oferty, płacimy i fabryka produkuje i wysyła nasze urządzenia. Kilka uwag praktycznych: Warto wysłać zapytania do kilku fabryk, żeby mieć rozeznanie na temat tego ile to powinno kosztować, ale też dlatego, że różne fabryki będą wyceniać stosując różne reguły i nasz specyficzny projekt może wychodzić drogo w jednej, ale dużo taniej w innej. Rozmawiamy z żywymi ludźmi, więc choć zazwyczaj nie mają oni za dużych marginesów żeby móc się z nami targować, przynajmniej mogą nam doradzić co zmienić żeby było taniej/łatwiej/bardziej niezawodnie. Kiedy już wysyłamy zapytanie, to warto zapytać od razu o różne ilości. Na przykład jeśli chcemy zrobić czegoś 40 sztuk, to można zapytać o 20, 40 i 100. Nieraz jest tak, że 100 sztuk będzie kosztować tyle samo co 40, bo będą robione na linii produkcyjnej zamiast ręcznie, a części będą zamawiana hurtowo. W liście komponentów warto podać które komponenty chcemy oryginalne, a które fabryka może wymienić na równoważne zamienniki — szczególnie jeśli chodzi o oporniki, kondensatory, etc. zazwyczaj mają w magazynach części tańsze i gotowe do użytku od razu. Nie musimy się obawiać, że fabryka ukradnie nasz projekt i zacznie go sprzedawać. Zazwyczaj nie interesuje ich zupełnie co to jest i do czego ma służyć, ani nie chce im się dochodzić jak się tego używa i pisać własnej dokumentacji. Klonowane są projekty, które już są popularne. Na koniec jeszcze kilka rad dotyczących samego projektu. Kiedy robimy coś dla siebie, to nie obchodzi nas za bardzo optymalizacja kosztów i łatwości montażu — w końcu zrobimy to tylko raz. Kiedy projektujemy dla fabryki, to każda oszczędność zostanie pomnożona przez liczbę zamawianych sztuk, więc gra może być warta świeczki. Zatem: Unikamy elementów przewlekanych i "łatwych do lutowanie" opakowań — na przykład zamiast TQFP lepiej użyć QFN, bo jest tańszy, mniejszy i łatwiej go masowo montować. Minimalizujemy liczbę komponentów — mamy 4 takie same oporniki? Czemu nie użyć komponentu, który ma je w jednym opakowaniu ("resistor array"). Czy naprawdę potrzebujemy do wszystkiego pin headery, czy niektóre z nich mogłyby być po prostu test padami? Ułatwiamy programowanie i testowanie. Nikt nie będzie dolutowywał drucików do nóżek naszego mikrokontrolera żeby go zaprogramować — powinniśmy wyprowadzić odpowiednie połączenia. Mniejsze płytki nie tylko są tańsze, ale też mniej ważą (także cieńsze płytki), więc mniej zapłacimy za wysyłkę. Czy na pewno wszystko musi robić fabryka? Jeśli sprzedajemy moduł, to nóżki do niego użytkownik sobie może sam dolutować (szczególnie, jeśli będzie chciał inne niż nasze). Zanim zamówimy czegoś 100 sztuk, warto zrobić jeden prototyp dokładnie taki, jak będziemy zamawiać, żeby upewnić się, że na pewno wszystko jest dobrze. Poprawianie 100 płytek nie jest przyjemne. Jeśli udokumentujemy procedurę testowania, to nam w fabryce mogą płytki przetestować. Zazwyczaj i tak będziemy musieli za te zepsute zapłacić, przynajmniej złapiemy problem wcześniej, zanim cała paczka dojdzie do nas. Jeśli nasz projekt wymaga dodatkowo obudowy czy innych elementów mechanicznych, warto o to także zapytać w fabryce — bardzo często mają zaprzyjaźnione serwisy, które będą w stanie to dla nas zrobić, mimo, że sami nic takiego oficjalnie nie oferują. To samo jeśli chodzi o opakowania (standardowo po prostu włożą każdą sztukę do woreczka antystatycznego i całość w karton).
  4. 10 punktów
    Cześć! To mój pierwszy post, a także pierwszy projekt jaki zrealizowałam przy pomocy Arduino. Zastanawiałam się czy w ogóle wrzucać swój banalny projekt ale może przyda się komuś kto tak jak ja dopiero zaczyna działać w temacie Arduino. Za pare dni wyjeżdżam, a że nie chciałam zostawiać głównie pomidorów na pastwę losu albo znajomych postanowiłam więc zbudować system podlewania. Czasu było mało ale chyba się udało. Po drodze miałam dwie koncepcje, jedna zakładała podlewanie o konkretnej godzinie, a druga podlewanie kiedy się ściemni. Ostatecznie zdecydowałam się na użycie RTC ale wrzucę oba warianty. Możliwe, że po przyjeździe będę kontynuować prace i dodawać kolejne elementy jak wyświetlacz z odczytem parametrów (temp, wilgotność) itd. Mam nadzieję, że o niczym nie zapomniałam i post jest poprawny . Elementy projektu: Arduino UNO Pompa do wody 12V Zasilacz 12V Koszyk na baterie + 6x AA Moduł przekaźnika 1-kanałowego 5V Zegar czasu rzeczywistego (RTC) DS3231 (koncepcja 1) Fotorezystor + opornik (koncepcja 2) Wężyki fi6 i fi4 Trójniki i czwórniki do wężyków Koncepcja z fotorezystorem: const int pumpPin = 8; // Zdefiniowanie pinów przekaźnika pompy i fotorezystora const int lightPin = A0; const long onTime = 30 * 1000; // Czas działania pompy 30s const int dayResistance = 150; // Wartość graniczna z fotorezystora dla dnia const int nightResistance = 80; // Wartość graniczna z fotorezystora dla nocy int lightReading = 0; boolean isDay = true; void setup() { Serial.begin(9600); pinMode(pumpPin,OUTPUT); // Wyjście na moduł przekaźnika digitalWrite(pumpPin, HIGH); } void loop() { int lightReading = analogRead(lightPin); // Odczytanie realnej wartości z fotorezystora Serial.println(lightReading); // Wyświetlenie wartości w konsoli if (isDay and lightReading < nightResistance) { digitalWrite(pumpPin, LOW); delay(onTime); digitalWrite(pumpPin, HIGH); isDay=false; } if (!isDay and lightReading > dayResistance) { isDay = true; } } Koncepcja z RTC: #include <DS3231.h> int pumpPin = 8; DS3231 rtc(SDA, SCL); Time t; const int OnHour = 11; const int OnMin = 00; const int OnSec = 0; const int OffHour = 11; const int OffMin = 00; const int OffSec = 45; void setup() { Serial.begin(9600); rtc.begin(); pinMode(pumpPin, OUTPUT); digitalWrite(pumpPin, HIGH); } void loop() { t = rtc.getTime(); // Odczytanie czasu Serial.print(t.hour); // Wyświetlenie w konsoli Serial.print(":"); Serial.print(t.min); Serial.print(":"); Serial.print(t.sec); Serial.println(" "); delay (1000); if(t.hour == OnHour && t.min == OnMin && t.sec == OnSec){ digitalWrite(pumpPin,LOW); Serial.println("POMPA WŁĄCZONA"); } else if(t.hour == OffHour && t.min == OffMin && t.sec == OffSec){ digitalWrite(pumpPin,HIGH); Serial.println("POMPA WYŁĄCZONA"); } }
  5. 10 punktów
    Czasem patrzymy na dostępne na rynku płytki z mikrokontrolerami i myślimy sobie "ja bym to zrobił lepiej". Zazwyczaj nie mamy pretekstu żeby rzeczywiście tak zrobić, ale czasem tak się życie ułoży, że mamy okazję spróbować. Tak właśnie miałem z płytką firmy Feather M0 Basic firmy Adafruit i wydaje mi się, że wychodząc z oryginalnego projektu udało mi się dokonać kilku ulepszeń (ale także przyszło mi pogodzić się z paroma kompromisami). A zaczęło się od tego, że często używam w moich projektach mikrokontrolera SAMD21 w jego najmniejszej obudowie QFN32 i nieraz przydałaby się możliwość przetestowania układu zanim zamówi się płytkę. Niestety, płytki które posiadam nie wyprowadzają wszystkich pinów, więc testowanie za ich pomocą nie zawsze jest możliwe. Postanowiłem zatem zrobić swoją płytkę, ale dać jej standardowy układ pinów — padło na standard Adafruit Feather. Najbardziej rzucającą się w oczy polskiemu użytkownikowi wadą płytki Adafruita jest niewątpliwie cena. Niestety, produkcja w USA, koszty wysyłki, a także konieczność pokrycia wypłat dla armii ludzi piszących dokumentację i rozwijających oprogramowanie powoduje, że nie jest ona tania. Zatem pierwszym celem, który sobie postawiłem, jest takie zaprojektowanie nowej płytki, żeby można było ją wykonać jak najtaniej. Oczywiście od razu pojawiła się konieczność kompromisów — postanowiłem usunąć część układu odpowiedzialną za obsługę i ładowanie baterii — nie ma zatem gniazdka do baterii, a nóżka do której byśmy ją podłączali jest niepodłączona. Usunąłem też wszystkie komponenty, które nie są absolutnie niezbędne — nie ma zatem kwarcu, zamiast tego używany jest wewnętrzny oscylator, wyleciał przycisk reset, zastąpiony zworką bezpośrednio na płytce (trzeba tylko dotknąć czymś metalowym), poleciały też diody świecące — choć potem jedna wróciła. Oryginalna płytka używa mikrokontrolera w opakowaniu QFN48, ale ja potrzebowałem QFN32 — tańsze, ale też ma mniej nóżek. Na szczęście odrobina gimnastyki pozwoliła mi brakujące nóżki zastąpić takimi, które były niepodłączone w oryginalnej płytce. Ba, dodałem nawet trzy dodatkowe nóżki dostępne dla użytkownika: dwie analogowe i jedna zwykła. Została mi jedna nóżka, którą postanowiłem podłączyć do diody świecącej, aby mając gołą płytkę można było sobie chociaż pomrugać. Oczywiście i tu nie obyło się bez kompromisu — aby obsłużyć SPI musiałem poświęcić nóżki normalnie używane do debugowania — zatem albo debugujemy, albo używamy SPI. W pierwszej wersji płytki zostało mi dużo wolnego miejsca, a że koszty wysyłki płytek zależą od ich wagi, postanowiłem zmniejszyć tę wagę dodając parę losowej wielkości dziur. Kiedy przyszło do wybierania koloru płytki, podobieństwo do sera skłoniło mnie do wybrania koloru żółtego. A właśnie, wprowadziłem jeszcze jedno ulepszenie: dziurki na piny są ułożone w zygzak w taki sposób, że da się w nie wetknąć nóżki goldpinów bez konieczności lutowania. Ostatnią częścią, którą postanowiłem usunąć jest gniazdko USB. Nie ma się co dziwić, bo tylko są z nim same kłopoty — ludzie narzekają, że jest w złym miejscu, że się urywa jak pociągną za kabel, do tego jest to jedyna część wymagająca dziur w płytce — i podrażająca tym montaż.Tylko jak bez gniazdka USB będziemy naszą płytkę zasilać, o wgrywaniu do niej programów i obserwowaniu ich wyników już nie wspominając? Okazuje się, że da się zrobić "gniazdko" USB z samej płytki, po prostu wycinając ją we właściwy kształt i umieszczając w odpowiednich miejscach styki. Płytka musi wówczas też mieć odpowiednią grubość, ale na szczęście nie ma dzisiaj wielkich problemów z zamówieniem takich płytek (i wychodzą nawet taniej niż standardowe, z powodu mniejszej wagi). Zatem zaprojektowałem nową wersję, z gniazdkiem USB-C (jak szaleć to szaleć). W tej wersji dziury są tylko w miedzi, zamiast przez całą płytkę, bo wykoncypowałem sobie, że taką cienka i płaską płytkę mogę używać jako wizytówki, a do tego potrzebowałem po drugiej stronie miejsce na zamieszczenie wizytówkowych informacji. Niestety, kiedy płytki wreszcie przybyły (dzięki zawirowaniom czasoprzestrzennym po 4 tygodniach, odwiedziwszy po drodze Niemcy i Wielką Brytanię) okazało się, że co prawda mechanicznie gniazdko działa doskonale, ale prąd płytka dostaje tylko jeśli podłączę ją kablem USB 2.0, podłączona do portu USB-C kablem USB-C nie jest wykrywana przez komputer i w związku z tym nie dostaje zasilania. Czas zatem na poczytanie co tam w standardzie piszą — okazuje się, że wzmianka o rezystorach ściągających na nóżkach CC, którą zignorowałem, bo wydawała się dotyczyć tylko trybu OTG, jest jednak istotna. Rozczarowało mnie to trochę, bo nie tylko musiałem zaprojektować nową płytkę, ale jeszcze nie oszczędzam na liczbie komponentów — jedno gniazdko USB zastąpić będę musiał dwoma rezystorami. No ale rezystory przynajmniej są tanie i montowane powierzchniowo, zatem wziąłem się do pracy. Zrezygnowałem ostatecznie z pomysłu wizytówki, puste miejsce wypełniłem obszarem prototypowym, w którym można sobie wlutować własne elementy, a dziury są teraz już tylko narysowane. Poza tym dodałem dwie nóżki CC do gniazdka USB i podłączyłem do dwóch rezystorów 5.1kΩ. Przez chwilę jeszcze rozważałem użycie tylko jednego rezystora, bo większość kabli i tak używa tylko jednego pinu CC, ale okazuje się, że to właśnie jest błąd, który popełnili projektanci Raspberry Pi 4, a który spowodował problemy z bardziej zaawansowanymi kablami. Postanowiłem błędu nie powtarzać. Ostateczna wersja projektu wygląda tak: Na razie używam płytki do własnych eksperymentów i sprawuje się dobrze, ale zastanawiam się nad zamówieniem większej liczby już zmontowanych i zaprogramowanych płytek — mogą się przydać do prowadzenia warsztatów z CircuitPythona gdy epidemia się skończy.
  6. 8 punktów
    Większość dostępnych kursów programowania mikrokontrolerów koncentruje się na dość prostych modułach peryferyjnych jak chociażby GPIO, PWM, czy też komunikacyjnych - przykładowo UART, I2C, SPI. Jednak mikrokontrolery STM32 mają do zaoferowania o wiele więcej, a jedną z na ogół pomijanych funkcji jest możliwość odtwarzania dźwięku. Przykładowo płytka STM32F411-discovery używana w kursie STM32F4 ma możliwość podłączenia słuchawek lub zewnętrznego wzmacniacza - jednak ta opcja nie była opisywana. Chciałbym wyjaśnić od razu, że niniejszy artykuł nie jest częścią wspomnianego kursu. Nie jest nawet kursem, to tylko zebranymi notatkami, które mam nadzieję przydadzą się innym czytelnikom forum, a dla mnie będą okazją utrwalenia poznanych modułów. Hardware Na początek wypada zapoznać się ze schematem używanej płytki, a w szczególności z fragmentem związanym z odtwarzaniem dźwięku: W prawym, górnym rogu widzimy złącze CN4, czyli gniazdo słuchawkowe. Jak widzimy jest ono dołączone z układem CS43L22. Jest to zintegrowany przetwornik analogowo-cyfrowy oraz wzmacniacz klasy D. Do konfiguracji i sterowania układem CS43L22 użyjemy znanego nam interfejsu I2C, na schemacie linie tego interfejsu znajdziemy w lewej, górnej części, podłączone do pinów PB9 (Audio_SDA) oraz PB6 (Audio_SCL). Niżej widzimy nowy interfejs, o myląco podobnej nazwie: I2S. Za jego pomocą będziemy przesyłali tylko dane do odtwarzania, czyli wartości próbek dźwięku. Pomimo podobnej nazwy do I2C jest to interfejs zbliżony do SPI, a z I2C nie mający właściwie nic wspólnego. Na schemacie widzimy jego podłączenie: PC7 (I2S3_MCK), PC10 (I2S3_SCK), PC12 (I2S3_SD) oraz PA4 (I2S3_WS). Aby odtwarzać dźwięk, będziemy musieli jeszcze wysterować pin PD4 (Audio_RST). Stan niski resetuje układ CS43L22, więc konieczne będzie ustawienie stanu wysokiego na tej linii. Na szczęście to zwykły pin GPIO, więc nic trudnego. Więcej informacji o CS43L22 znajdziemy w jego nocie katalogowej, którą oczywiście warto przeczytać. Wygenerowanie projektu Kurs STM32F4 wykorzystywał poprzednią wersję narzędzia CubeMX, pokażę więc krok po kroku jak wygenerować projekt używając STM32CubeIDE w aktualnie najnowszej wersji, czyli 1.3.0. Najpierw oczywiście uruchamiamy STM32CubeIDE, następnie z menu File wybieramy opcję New -> STM32 Project. Opisywana płytka jest wyposażona w mikrokontroler STM32F411VET6, musimy więc go odszukać i wybrać: W kolejnym kroku podajemy nazwę projektu i zatwierdzamy domyślne ustawienia: Na koniec upewniamy się czy opcja "Copy only the necessary library files" jest zaznaczona. Po kliknięciu przycisku "Finish" powinniśmy otrzymać gotowy szablon projektu. Teraz ustawimy źródło taktowania naszego mikrokontrolera. Więcej szczegółów można przeczytać w kursie F4, tutaj w skrócie - wybieramy moduł RCC i dla opcji "High Speed Clock (HSE)" wybieramy z listy "Crystal/Ceramic Resonator". Do konfiguracji zegarów wrócimy później. Interfejs I2S Wreszcie możemy przejść do konfiguracji interfejsu I2S, a dokładniej I2S3. Jeśli używamy widoku kategorii (Categories), to znajdziemy nasz interfejs w grupie "Multimedia". Po zaznaczeniu I2S3, w środkowej części okna pojawi się możliwość wyboru trybu pracy interfejsu, (ang. Mode). Wybieramy opcję "Half-Duplex Master" oraz zaznaczamy "Master Clock Output". Poniżej zobaczymy sporo ustawień, w zakładce "Parameter Settings" zmieniamy pole "Selected Audio Frequency" na 22 kHz: Typowa częstotliwość samplowania dla dźwięku jakość CD to 44.1 kHz, ja wybrałem 22 kHz, czyli połowę tej wartości - to nadal całkiem niezły wynik, a znacznie mniej danych oraz mniejsze obciążenie procesora. Domyślnie przypisane piny zobaczymy wybierając zakładkę "GPIO Settings". Niestety nie odpowiadają one płytce STM32F411-discovery, musimy więc je zmienić na PA4, PC7, PC10 oraz PC12. Warto również przestawić wartość "Maximum output speed" na "High" W pierwszej wersji programu nie będziemy wykorzystywać przerwań, ale możemy je włączyć, przydadzą się później. Wchodzimy w zakładkę "NVIC Settings" i włączamy przerwanie. Tutaj ciekawostka dla spostrzegawczych - konfigurujemy I2S3, a na ekranie widzimy SPI3... taki mały błąd, niestety nie jedyny. W każdym razie oba interfejsy są ze sobą mocno powiązane. Teraz jeszcze jedna opcja trochę na wyrost. W zakładce "DMA Settings" dodajemy obsługę DMA - klikamy na przycisku "Add", następnie wybieramy DMA Request "SPI3_TX", a w dolnej części okna ustawiamy: Mode: Circular Data Width: Half Word Konfiguracja zegarów Teraz, gdy interfejs I2S jest już skonfigurowany możemy wrócić do taktowania. Musimy ustawić częstotliwość kwarcu na 8 MHz (domyślnie to 25), a następnie wybrać taktowanie dla HCLK. Maksymalna wartość to 100 MHz, więc możemy ją użyć. Jeśli zostawimy ustawienia domyślne i wrócimy do I2S3 zobaczymy w polu "Error between Selected and Real" dość znaczny błąd (ponad 18%). Na szczęście nasz mikrokontroler ma oddzielną pętlę PLL dla generacji dźwięku - nazywa się PLLI2S i pozwala na wybranie częstotliwości dopasowanej do potrzeb audio. Przykładowa konfiguracja używa dzielnika M=5, mnożnika N=141, co daje częstotliwość PLLI2S równą 112.8 MHz: Jak teraz przełączymy się na I2S3, zobczymy że błąd wynosi 0.14 % Interfejs I2C Jak pamiętamy interfejs I2C będzie wykorzystany do konfiguracji układu CS43L22. Na płytce discovery będzie to dokładniej interfejs I2C1, znajdziemy go w kategorii "Connectivity". Aby go włączyć wystarczy wybrać interfejs, a następnie jako tryb (Mode) użyć "I2C". Parametry możemy zostawić domyślne: Niestety domyślne przypisanie pinów nie pasuje do naszej płytki, konieczne jest więc wybranie zakładki "GPIO Settings" i wybranie pinów PB6 oraz PB9. Interfejs I2C powinien być już gotowy do użycia, możemy przejść do ostatniego etapu, czyli linii resetującej CS43L22. Linia Audio_RST Jest to zwykły pin GPIO, dodajemy więc PD4 jako wyjście. Teraz nasz projekt jest już autentycznie gotowy, wystarczy go zapisać, a CubeIDE wygeneruje kod potrzebny do inicjalizacji używanych przez nas peryferiów. Pierwszy program Przygotowanie projektu za pomocą CubeMX zajęło sporo miejsca, więc żeby nie tworzyć zbyt długiego artykułu podam teraz mocna skróconą wersję kodu. W następnej części postaram się skupić nieco dokładniej na programowaniu i omówię co i jak działa. Żeby przetestować naszą konfigurację potrzebujemy dwóch rzeczy: ustawień dla CS43L22 oraz danych, czyli próbek (sampli) dźwięku. Ustawienia CS43L22 to ogromny temat, w przykładach dostarczanych przez ST wraz z CubeIDE znajdziemy rozbudowaną bibliotekę obsługującą ten układ. Ja prezentuję opcję minimum: #define AUDIO_I2C_ADDR 0x94 static void cs43l22_write(uint8_t reg, uint8_t value) { HAL_I2C_Mem_Write(&hi2c1, AUDIO_I2C_ADDR, reg, 1, &value, sizeof(value), HAL_MAX_DELAY); } static void cs43l22_init(void) { HAL_GPIO_WritePin(AUDIO_RST_GPIO_Port, AUDIO_RST_Pin, GPIO_PIN_SET); cs43l22_write(0x04, 0xaf); cs43l22_write(0x06, 0x07); cs43l22_write(0x02, 0x9e); } Adres układu (0x94) znajdziemy w nocie katalogowej oraz na schemacie, funkcja cs43l22_write zapisuje wartość do wewnętrznego rejestru układu CS43L22 - opis rejestrów znajdziemy oczywiście w dokumentacji. cs43l22_init to maksymalnie uproszczona inicjalizacja CS43L22 - zwalniamy linię resetującą czyli AUDIO_RST, a następnie do rejestrów 0x04, 0x06 i 0x02 wpisujemy magiczne wartości - w kolejnej części postaram się wyjaśnić o co w tym chodzi. Teraz wystarczy przygotować i wysłać dane samego dźwięku: #include <math.h> #define BUFFER_SIZE 2200 static int16_t audio_data[2 * BUFFER_SIZE]; void play_tone(void) { for (int i = 0; i < BUFFER_SIZE; i++) { int16_t value = (int16_t)(32000.0 * sin(2.0 * M_PI * i / 22.0)); audio_data[i * 2] = value; audio_data[i * 2 + 1] = value; } cs43l22_init(); while (1) { HAL_I2S_Transmit(&hi2s3, (uint16_t*)audio_data, 2 * BUFFER_SIZE, HAL_MAX_DELAY); } } Próbki dźwięku są 16-bitowe, kodowane jako wartości ze znakiem, dlatego bufor audio_data ma typ int16_t. Stała BUFFER_SIZE określa liczbę próbek w buforze, a ponieważ obsługiwany jest dźwięk stereo, sama tablica musi mieć 2 x większą wielkość. Funkcja play_tone, najpierw generuje próbki do odtwarzania - jest to sinusoida o częstotliwości 1kHz (częstotliwość samplowania to 22kHz, stąd dzielenie przez 22.0). Na koniec w pętli while dane są wysyłane do układu CS43L22 za pomocą funkcji HAL_I2S_Transmit. Teraz wystarczy skompilować program, wgrać i cieszyć się efektem: W kolejnej części postaram się napisać nieco więcej o konfiguracji CS43L22 za pomocą I2C, samym protokole I2S oraz odtworzyć coś ciekawszego niż ton 1kHz. Kod wykorzystany w niniejszym artykule jest dostępny pod adresem: https://github.com/pbugalski/stm32f411-discovery/tree/master/02_audio
  7. 6 punktów
    W poprzednich odcinkach poznaliśmy mnóstwo niskopoziomowych szczegółów związanych z odtwarzaniem dźwięku za pomocą STM32. Czas wreszcie zrobić coś ciekawszego i zdobytą wiedzę do wykorzystać w praktyce. Format zapisu dźwięku Niewątpliwym problemem podczas generowania dźwięku przy wykorzystaniu mikrokontrolera jest ilość danych. Wybraliśmy próbkowanie fs=22050 Hz, więc jedna sekunda "utworu" wymaga ponad 86KB danych (22050 Hz * 2 kanały * 16 bitów). Układ STM32F411 ma co prawda 128KB pamięci RAM, jednak nawet wykorzystując całą pamięć zmieścilibyśmy raptem półtorej sekundy muzyki. Moglibyśmy zapisać naszą muzykę w pamięci Flash, ale jej mamy 512KB - bardzo dużo jak na mikrokontroler, ale mało jak na dane audio. Nie pozostaje więc nic innego niż użyć jakiegoś sposobu kompresji. Obecnie chyba najbardziej popularnym formatem zapisu skompresowanej muzyki jest .mp3 i pewnie udałoby nam się odtworzyć plik w tym formacie. Jednak wielkość pliku mp3 może być nadal spora, a zapotrzebowanie na moc obliczeniową niemałe. Postanowiłem więc wykorzystać zamiast mp3 nieco zapomniany format MOD. Pliki z muzyką w tym formacie dostępne są w wielu miejscach, np. https://modarchive.org/ Co więcej niektóre są zaskakująco małe, na początek wybrałem plik https://modarchive.org/index.php?request=view_by_moduleid&query=116185, który zajmuje raptem trochę ponad 13 KB (plik można odtworzyć w przeglądarce). Dekodowanie pliku .mod Nie jestem niestety ekspertem od plików .mod, pozostało mi więc poszukać biblioteki, która będzie w stanie takie pliki przetwarzać. Znalazłem bardzo ciekawą bibliotekę: https://github.com/jfdelnero/HxCModPlayer. W pierwszej chwili może wyglądać skomplikowanie, ale wystarczy skopiować dwa pliki: hxcmod.h i hcmod.c Znajdziemy w nich bardzo łatwe w użyciu funkcje: int hxcmod_init( modcontext * modctx ); int hxcmod_setcfg( modcontext * modctx, int samplerate, int stereo_separation, int filter ); int hxcmod_load( modcontext * modctx, void * mod_data, int mod_data_size ); void hxcmod_fillbuffer( modcontext * modctx, msample * outbuffer, mssize nbsample, tracker_buffer_state * trkbuf ); Typ modcontext przechowuje stan naszego odtwarzacza i nie musimy właściwie nic o nim wiedzieć poza tym, że potrzebna będzie nam zmienna tego typu, czyli: static modcontext modctx; Bardzo prosty program z użyciem tej biblioteki mógłby wyglądać następująco: hxcmod_init(&modctx); hxcmod_setcfg(&modctx, 22000, 0, 0); hxcmod_load(&modctx, (void*)minimalistic_mod, minimalistic_mod_size); hxcmod_fillbuffer(&modctx, audio_data, BUFFER_SIZE, NULL); HAL_I2S_Transmit(&hi2s3, (uint16_t*)audio_data, BUFFER_SIZE * 2, HAL_MAX_DELAY); Funkcja hxcmod_init inicjalizuje pola w zmiennej modctx. Następnie hxcmod_setcfg odpowiada za ustawienie parametrów odtwarzanego dźwięku (najważniejsze jest ustawienie częstotliwości samplowania). Kolejna wywoływana funkcja to hxcmod_load, która wczytuje plik .mod - jako parametry dostaje tablicę z danymi, czyli plikiem .mod oraz jej wielkość. Za chwilę opiszę jak takie dane przygotować. Na koniec wywołujemy hxcmod_fillbuffer, która przygotowuje porcję danych do wysłania za pomocą HAL_I2S_Transmit. Do bufora audio_data zapisywana jest tylko część danych (a nie cały utwór), liczbę sampli podajemy jako trzeci parametr, w przykładowym kodzie to po prostu BUFFER_SIZE. Dzięki temu w zapotrzebowanie na pamięć RAM jest bardzo małe i nie musimy dekodować całości na raz. Konwersja pliku .mod Plik .mod musimy jakoś umieścić w pamięci dostępnej dla mikrokontrolera. Chyba najłatwiejszą opcją jest przekonwertowanie pliku na tablicę bajtów i dodanie do programu. Prosty skrypt w Pythonie wykona taką pracę za nas (źródło ze strony: https://stackoverflow.com/questions/8707183/script-tool-to-convert-file-to-c-c-source-code-array) import sys from functools import partial if len(sys.argv) < 2: sys.exit('Usage: %s file' % sys.argv[0]) print("char a[] = {") n = 0 with open(sys.argv[1], "rb") as in_file: for c in iter(partial(in_file.read, 1), b''): print("0x%02X," % ord(c), end='') n += 1 if n % 16 == 0: print("") print("};") Po zmianie nazwy tablicy na minimalistic_mod oraz dodaniu stałej minimalistic_mod_size możemy utworzyć plik nagłówkowy minimalic_mod.h: #pragma once #include <stdint.h> extern const uint8_t minimalistic_mod[]; extern const uint32_t minimalistic_mod_size; Samego pliku minimalic_mod.c nie załączam tutaj, ale jest dostępny razem ze źródłami opisywanych programów. Testowy program Przed definicją funkcji main() dodajemy: #include "hxcmod.h" #include "minimalic_mod.h" #define BUFFER_SIZE 4096 #define AUDIO_I2C_ADDR 0x94 static int16_t audio_data[2 * BUFFER_SIZE]; static modcontext modctx; Poprzednio bufor audio_data miał wielkość 2200 - była w tym ukryta pewna sztuczka, bo żeby ton 1kHz ładnie brzmiał, wielkość bufora musiała być wielokrotnością okresu funkcji (czyli 22). Teraz każdą zawartość bufora będziemy przygotowywać wywołując funkcję hxcmod_fillbuffer, więc możemy wybrać inną wartość, a 4096 to taka sympatyczna, okrągła liczba. Do samej funkcji main() dopisujemy: cs43l22_init(); hxcmod_init(&modctx); hxcmod_setcfg(&modctx, 22000, 0, 0); hxcmod_load(&modctx, (void*)minimalistic_mod, minimalistic_mod_size); while (1) { hxcmod_fillbuffer(&modctx, audio_data, BUFFER_SIZE, NULL); HAL_I2S_Transmit(&hi2s3, (uint16_t*)audio_data, BUFFER_SIZE * 2, HAL_MAX_DELAY); } Teraz możemy uruchomić nasz program. Efekt niestety nie do końca jest taki jak oczekiwaliśmy. Okazuje się, że dekodowanie kolejnej porcji próbek trwa mało czasu, ale jednak wystarczająco dużo żeby skutecznie popsuć efekt odtwarzania muzyki. Obsługa DMA Problem z naszym dotychczasowym programem polegał na tym, że robiliśmy jedną czynność na raz. Najpierw generowaliśmy dane do odtworzenia, później wysyłaliśmy przez i2s i tak w pętli. Niestety podczas przetwarzania danych, nie mogliśmy ich odtwarzać, a to powodowało słyszalne przerwy w dźwięku. Nie pozostaje nam więc nic innego niż robić obie te rzeczy jednocześnie. Mamy co prawda tylko jeden procesor, ale z pomocą przychodzi nam mechanizm DMA. W pierwszej chwili moglibyśmy po prostu zastąpić wywołanie HAL_I2S_Transmit funkcją HAL_I2S_Transmit_DMA. Niestety samo użycie DMA nie wystarczy, a efekt może być nawet gorszy niż bez DMA - generowanie nowych próbek nakłada się na odtwarzanie, rezultat jest ciekawy, ale zupełnie odmienny od zamierzonego. Zamiast tego spróbujmy podzielić nasz bufor audio_data na dwie części. Gdy pierwsza zostanie w całości odtworzona, DMA rozpocznie pobieranie danych z drugiej części. Będziemy mogli w tym momencie nadpisać pierwszy bufor bez obawy, że używane dane zostaną uszkodzone. Jak dowiemy się o zakończeniu odtwarzania? Okazuje się że po wysłaniu połowy danych z bufora, zgłaszane jest przerwanie - a po wysłaniu całości kolejne. Możemy więc napisać dwie proste funkcje: void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s) { hxcmod_fillbuffer(&modctx, audio_data, BUFFER_SIZE / 2, NULL); } void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s) { hxcmod_fillbuffer(&modctx, audio_data + BUFFER_SIZE, BUFFER_SIZE / 2, NULL); } Pierwsza jest wywoływana po wysłaniu połowy danych i wywołuje hxcmod_fillbuffer wypełniając pierwszą część bufora audio_data nowymi danymi. Druga zostanie wywołana, gdy cały bufor zostanie wysłany - ponieważ DMA jest używana w trybie cyklicznym (Circular - ustawiliśmy w 1 części), w tym momencie będą nadawane dane z pierwszej połowy bufora, a drugą możemy spokojnie uzupełnić. Kod w funkcji main() jest teraz prostszy niż poprzednio. Na początku wypełniamy bufor audio_data pierwszymi danymi i raz wywołujemy HAL_I2S_Transmit_DMA. Później wszystko będzie działało w przerwaniach, sama pętla główna jest pusta, ale możemy umieścić w niej o coś ciekawszego cs43l22_init(); hxcmod_init(&modctx); hxcmod_setcfg(&modctx, 22000, 0, 0); hxcmod_load(&modctx, (void*)minimalistic_mod, minimalistic_mod_size); hxcmod_fillbuffer(&modctx, audio_data, BUFFER_SIZE, NULL); HAL_I2S_Transmit_DMA(&hi2s3, (uint16_t*)audio_data, BUFFER_SIZE * 2); while (1) { } Cały program jest dostępny w repozytorium https://github.com/pbugalski/stm32f411-discovery/tree/master/03_modplayer, a jego działanie wygląda następująco: Priorytety przerwań Pisząc ten artykuł przyszło mi do głowy, że zapomniałem jeszcze o jednej, ważnej rzeczy. Nasze przerwanie od DMA może zajmować dość dużo czasu procesora - powinno więc mieć niższy priorytet niż np. SysTick. Priorytety przerwań możemy ustawić za pomocą CubeMX (wyższa wartość oznacza niższy priorytet): Zakończenie Na koniec chciałbym wspomnieć o jednej rzeczy, a mianowicie o wykorzystaniu procesora - pomiary wykonywałem na STM32L476 taktowanym z częstotliwością 80MHz i wówczas generowanie dźwięku za pomocą biblioteki hxcmod wykorzystywało ok. 10% czasu CPU. Można więc założyć że na F4 będzie nieco mniej. Dzięki temu pozostały czas możemy wykorzystać w inny, być może pożyteczny sposób - przykładowo dodając wizualizację do odtwarzanej muzyki. Jako przykład wykorzystałem program napisany do artykułu Program działa na płytce STM32F746-discovery, ponieważ na niej miałem zarówno wyjście audio, jak i wyświetlacz. Efekt w rozdzielczości 480 x 272 wygląda następująco: Mam nadzieję, że niniejszymi artykułami zachęcę innych czytelników forum do eksperymentów z generowaniem dźwięku na mikrokontrolerach STM32. Okazuje się że nie jest to takie trudne jak się wydaje, a możliwości są przeogromne.
  8. 6 punktów
    W pierwszej części udało się uruchomić prosty przykład odtwarzający pojedynczy ton. Teraz wypadałoby nieco wyjaśnić, jak działał poprzedni program, a następnie trochę go udoskonalić. Układ CS43L22 Za generowanie dźwięku na płytce STM32F411-Discovery odpowiada wspominany wcześniej układ CS43L22. Wszystkie informacje o jego działaniu znajdziemy w dokumentacji - ma ona raptem 66 stron, co jest całkiem niewielką liczbą jak na układy dźwiękowe (większość płytek serii Discovery używa o wiele bardziej rozbudowanego układu WM8994, do niego dokumentacja ma już 360 stron). Zachęcam więc do przeczytania dokumentacji, postaram się tutaj przygotować maksymalnie skrócony opis funkcji używanych w programie z poprzedniej części artykułu. Interfejs I2C Jak wspominałem wcześniej do konfiguracji CS43L22 używany jest znany nam z kursu F4 interfejs I2C. Adres układu znajdziemy w dokumentacji płytki discovery i wynosi on 0x94. Sama komunikacja przebiega dość typowo - chcąc zapisać ustawienia, wysyłamy najpierw adres układu, następnie numer rejestru, a na koniec wartość. Adres rejestru ma postać pojedynczego bajtu, podobnie przechowywana w nim wartość. W dokumentacji znajdziemy elegancki diagram komunikacji z układem: Odczyt jest nieco bardziej skomplikowany, ale w bibliotece HAL mamy gotowe funkcje realizujące dokładnie takie sposób zapisu i odczytu danych: HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) Parametry tych funkcji to: hi2c - adres struktury opisującej interfejs I2C utworzonej przez CubeIDE DevAddress - adres układu CS22L43, czyli 0x94 MemAddress - numer rejestru do którego zapisujemy, albo z którego odczytujemy MemAddSize - wielkość adresu, zawsze 1 (bajt) pData - dane do zapisania, albo adres bufora do odczytu Size - liczba zapisywanych lub odczytywanych bajtów (też 1 bajt) Timeout - to chyba wiadomo Jest jeszcze oczywiście zwracana wartość, którą powinniśmy testować i odpowiednio reagować jeśli pojawi się błąd. Dla poprawienia czytelności pomijam sprawdzanie błędów, ale zachęcam do ich uwzględniania we własnych programach. Ponieważ większość parametrów jest zawsze taka sama, napisałem funkcję cs43l22_write, która przyjmuje tylko zmieniające się wartości, czyli numer rejestru i zapisywaną wartość: static void cs43l22_write(uint8_t reg, uint8_t value) { HAL_I2C_Mem_Write(&hi2c1, AUDIO_I2C_ADDR, reg, 1, &value, sizeof(value), HAL_MAX_DELAY); } Moglibyśmy oczywiście zdefiniować podobną funkcję do odczytu: static uint8_t cs43l22_read(uint8_t reg) { uint8_t value = 0; HAL_I2C_Mem_Read(&hi2c1, AUDIO_I2C_ADDR, reg, 1, &value, sizeof(value), HAL_MAX_DELAY); return value; } Rejestry układu CS43L22 Skoro wiemy jak zapisywać i odczytywać dane z rejestrów CS43L22 to teraz trudniejsza część, czyli same rejestry. Ich listę znajdziemy w dokumentacji układu: W pierwszej chwili ich liczba może nieco odstraszać, ale jak wspomniałem wcześniej - to i tak dość prosty w obsłudze układ. I jak wcześniej widzieliśmy nie musimy wszystkich jego funkcji od razu używać. Na początek możemy spróbować odczytać wartość z rejestru o numerze 0x01, który ma nazwę ID i jak łatwo się domyślić zwraca identyfikator układu: Piszemy bardzo krótki program: uint8_t id = cs43l22_read(0x01); Możemy sprawdzić odczytaną wartość używając debuggera: U mnie identyfikator wynosi 0xe3, czyli jest to wersja B1 układu. Konfiguracja CS43L22 Opis wszystkich rejestrów znajdziemy w dokumentacji, ja postaram się teraz krótko opisać minimalną wersję, która była używana w poprzedniej części, czyli: cs43l22_write(0x04, 0xaf); cs43l22_write(0x06, 0x07); cs43l22_write(0x02, 0x9e); Rejestr 0x04 - Power Control 2 Zapis wartości 0xaf do tego rejestru włącza lewy oraz prawy kanał wyjścia słuchawkowego. Do bitów PDN_HPx[1:0] zapisywana jest wartość 10, czyli wybierana opcja "Headphone channel is always ON.", natomiast wyjście głośniczka (speaker) jest wyłączane: PDN_SPKx[1:0] ustawione na 11 odpowiada opcji "Speaker channel is always OFF". Rejestr 0x06 - Interface Control 1 Tutaj ustawiamy kilka rzeczy jednocześnie, najważniejsze to: Master/Slave - nasz układ będzie działał jako slave, to mikrokontroler generuje wszystkie sygnały sterujące. Zerujemy więc bit M/S. DAC Interface Format - okazuje się że CS43L22 obsługuje nie tylko interfejs I2S, ale też podobne do niego interfejsy. Każda opcja powinna działać, ale musimy wybrać to samo co w poprzedniej części konfigurując CubeMX. Do bitów DACIF[1:0] zapisujemy więc wartość 01. Audio Word Length - w trybie I2S ten parametr nie jest istotny, ale dla porządku wybierzemy 16-bitową wielkość danych, czyli do AWL[1:0] zapisujemy 11. Jak przeliczymy wybrane opcje na wartość heksadecymalną to otrzymamy używaną w programie, czyli 0x07. Rejestr 0x02 - Power Control 1 Ten rejestr jest bardzo prosty, chociaż nieco zaskakujący. Zapisanie do niego wartości 0x01 lub 0xaf usypia układ CS43L22, a 0xae budzi. Zaskakujące jest dlaczego nie użyto po prostu wartości 0 / 1, ale jak widać 0xae było z jakiegoś powodu wygodniejsze. Na koniec jeszcze dwa rejestry, których nie używaliśmy, ale warto o nich wspomnieć: Rejestry 0x20 i 0x21 - Master Volume Control Tutaj chyba wszystko jest oczywiste, zapisując do tych rejestrów możemy regulować głośność dźwięku. To może być dość przydatna funkcja w docelowym programie. Pisząc "prawdziwy" program powinniśmy oczywiście zdefiniować odpowiednie stałe i korzystać z nich zamiast z magicznych liczb. Ale skoro przykłady miały być krótkie, to są i mocno niedoskonałe - za co z góry przepraszam. Interfejs I2S Teraz wreszcie coś nowego, bo o I2S jeszcze nie pisaliśmy. Krótki opis znajdziemy na wikipedii: https://pl.wikipedia.org/wiki/I²S Jak widzimy sam I2S używa trzech linii sygnałowych, ale często dodawana jest jeszcze jedna - mamy więc razem cztery sygnały. Używam nazw ze schematu płytki: I2S3_MCK - zegar używany wewnętrznie do taktowania CS43L22 I2S3_SCK - zegar dla transmisji danych I2S3_SD - linia danych I2S3_WS - aktywny kanał (L/R) Przedrostek I2S3_ wskazuje na podłączenie CS43L22 do interfejsu I2S3 mikrokontrolera, pisząc ogólnie o I2S będę go pomijać, zostaną więc linie: MCK, SCK, SD, WS. Omówienie interfejsu I2S najłatwiej zacząć od końca, czyli linii WS. Stan niski oznacza transfer danych dla lewego kanału, a wysoki dla prawego. Prawda że proste. Przy okazji - jaka powinna być częstotliwość sygnału na tej linii? Łatwo odgadnąć, że powinna odpowiadać częstotliwości samplowania, czyli fs. To bardzo ważna wartość, bo wszystkie inne są obliczane na jej podstawie. W poprzedniej części wybraliśmy częstotliwość 22 kHz (pewnie oznacza ona 22050 Hz), teraz możemy podłączyć analizator logiczny i sprawdzić, czy faktycznie nasz układ działa jak tego oczekujemy: Kolejna jest linia SD, czyli linia danych. W odróżnieniu od SPI interfejs I2S działa raczej jednokierunkowo - do wyjścia słuchawkowego będziemy tylko wysyłać dane, a gdybyśmy mieli podłączony np. mikrofon, dane byłyby tylko odczytywane. Wysyłamy 16 bitów na każdą próbkę dźwięku, a wartość jest interpretowana jako liczba ze znakiem. Jak pamiętamy w programie używaliśmy dlatego typu int16_t. Żeby zobaczyć jak nasze dane wyglądają w działaniu, możemy analizatorem zmierzyć napięcie na wyjściu słuchawkowym. Jak widzimy poniżej, wysyłając 0 dostajemy na wyjściu około 0V, zapisanie -32000 daje 1V, a 32000 około -1V. Co więcej przebieg wygląda jak sinusoida, czyli tak jak chcieliśmy. Linia SCK jest używana jako zegar dla linii danych. Jak pamiętamy częstotliwość fs=22kHz, każda próbka ma 16 bitów, ale musimy przesłać dane dla lewego i prawego kanału - więc zegar danych będzie miał częstotliwość: fs * 32 = 704 kHz. Sprawdzamy, czy wszystko się zgadza: Na koniec linia MCK. Nie jest ona używana bezpośrednio do transmisji danych i nie jest częścią standardu I2S. Jednak układ CS43L22 wymaga sygnału taktującego - i taką właśnie funkcję pełni MCK. Częstotliwość powinna wynosić 256 * fs, czyli 5,632 MHz. Powrót do CubeMX To wszystko skonfigurowaliśmy w poprzedniej części artykułu używając CubeMX. Teraz możemy wrócić do końcowych ustawień i wyjaśnić co oznaczały: Tryb "Half-Duplex Master" pozwala tylko wysyłać dane, w końcu i tak nic ciekawego ze słuchawek nie odczytamy. Zaznaczenie "Master Clock Output" sprawia, że generowany jest sygnał MCK. "Selected Audio Frequency" to częstotliwość samplowania fs, a wybraliśmy połowę jakości CD, czyli 22050 Hz. Standard komunikacji to "I2S Philips", wybrana wartość musi pasować do tego co wpisaliśmy do rejestru 0x06 układu CS43L22. Jeszcze dwa słowa o błędzie i taktowaniu mikrokontrolera: Żródło taktowania modułu I2S wybieramy multiplekserem "I2S source Mux" (widoczny po prawej stronie na dole). Wybraliśmy zegar PLLI2SCLK, który pracuje z częstotliwością 112,8 MHz. Jeśli podzielimy tą wartość przez oczekiwaną dla MCK, czyli 5,66 dostajemy wynik bliski 20. To "bliski" oznacza właśnie błąd naszej częstotliwości - gdyby wyszło dokładnie 20 mielibyśmy idealnie fs=22050, a tak mamy 22031 Hz, czyli chyba całkiem nieźle. Podsumowanie W tej części planowałem napisać nieco więcej o rozbudowie programu z poprzedniej części, niestety nawet ogólne omówienie szczegółów technicznych wyszło nieco dłuższe niż planowałem. Powstanie więc kolejna część, a w niej będą jak mam nadzieję ciekawsze rzeczy i dużo mniej rejestrów.
  9. 4 punkty
    Cześć, właśnie w przedsprzedaży pojawiła się w chińskim sklepie Banggood.com ciekawa płytka FPGA oparta na układzie FPGA Cyclone IV w cenie 115 PLN. Oto link do tego zestawu FPGA: https://www.banggood.com/ALTERA-Cyclone-IV-EP4CE6-FPGA-Development-Board-Kit-Altera-EP4CE-NIOSII-FPGA-Board-and-USB-Downloader-Infrared-Controller-p-1622523.html?rmmds=search&cur_warehouse=CN Co najciekawsze płytka ma bardzo dużo fajnych peryferiów na pokładzie i zawiera kostkę 64Mbit SDRAM co pozwala zaimplementować soft-procesor NIOSII z dużą pamięcią. Poza tym dużo fajnych interfejsów jak : PS2, RS232, VGA, AS-interface. Uważam, że stosunek ceny do wyposażenia zestawu FPGA jest bardzo dobry. Problemem może być dobra dokumentacja, ale z reguły dla chińskich wyrobów w stosunkowo krótkim czasie pojawia się dokumentacja pozwalająca korzystać z tych wyrobów. Pozdrawiam
  10. 4 punkty
    @lubniewicz A teraz na spokojnie obszerniej. Kolejno : 1. Skopany filtr kalmana, konkretniej źle dobrane wagi współczynników. Na zdjęciu wyjście z filtru kalmana, przy szybkich zmianach wychylenia widać "szpilki" które uniemożliwiały poprawną regulację PID'em. 2. Po poddaniu się z Kalmanemn na szybko napisaliśmy filtr komplementarny który ma tylko jedną wagę "alfa" i szybko udało się go odpowiednio dostroić, czyli żadnych szpilek przy zboczach.:(zielony przebieg) 3. W trakcie strojenia filtru okazało się, że rozdzielczość żyroskopu 250 jest stanowczo za mała, za absolutne minimum uznaliśmy 500. 4. Kolejnym błędem był sam regulator PID a dokładniej człon D ten człon powinien przemnażać prędkość kątową przez współczynnik, a nie jak w pierwotnej mojej wersji zróżniczkowaną po czasie różnicę błędów uchybu . 5.Ostatnim błędem była mechaniczna strona, ciężkie ogniwo powinno znajdować się jak najniżej, a czujnik jak najbliżej osi kół. O źle dobranych parametrach PID nawet nie wspominam bo to aż wstyd. P.S. W załączniku filmik efektu finalnego, którego bym nie osiągnął gdyby nie kolega lubniewicz. P.S 2 Robot właśnie pięknie przebalansował całe pisanie tego posta i ani razu nawet nie uciekł balans.zip
  11. 4 punkty
    Cześć. Kilka pytań, których celem jest znalezienie potencjalnego problemu a niekoniecznie uzyskanie na nie odpowiedzi: Czy obecna konstrukcja jest przemyślana (średnica kół, położenie IMU, dystrybucja masy), czytałeś ten wpis - https://forbot.pl/blog/budowa-robota-balansujacego-praktyczne-porady-id9178? Czy częstotliwość (output data rate) z jaką wyrzuca dane jest taka sama lub większa od częstotliwości Twojej pętli sterowania? Czy dobierałeś parametry PID w jakiś konkretny sposób? Czy próbowałeś wysterować układ stosując inny filtr zamiast filtru Kalmana np. komplementarny Jak dobierałeś parametry filtru Kalmana? Może Twój Kalman wprowadza jakieś znaczące opóźnienie? Po obejrzeniu filmu, szukałbym problemu najpierw w samej konstrukcji (podlinkowany artykuł) a potem w parametrach PID - wygląda jak przesterowany regulator - zacznij z jakimś małym P i wyzerowanymi członami I i D i stopniowo zwiększaj P aż do uzyskania jakiegoś stabilnego zachowania.
  12. 4 punkty
    Żeby dało się wygodnie korzystać z takiej płytki, to wypadałoby wiedzieć co która nóżka może zrobić. Zatem usiadłem dzisiaj do Inkscape-a i zrobiłem ściągę: Niestety było z tym trochę pracy, bo z noty katalogowej nie wynika wprost co da się zrobić — po kilku próbach postanowiłem po prostu podejść do problemu brutalnie, i napisałem sobie prosty programik, który wypróbowuje wszystkie możliwości i wypisuje co się udało a co nie. Niestety, przy PWM-ie się już poddałem — liczba możliwych kombinacji jest duża, a do tego jeszcze kolejność ma znaczenie — ciężko byłoby to nawet przedstawić graficznie w jakiś zrozumiały sposób.
  13. 4 punkty
  14. 4 punkty
    Patrząc pod względem tego co ci da jak najszybciej jak największe możliwości, poszedłbym chyba na twoim miejscu w kurs Arduino, podchodząc do tego nie jak do nauki samego Arduino, które już trochę trąci myszką, ale bardziej jako wstęp do mikrokontrolerów i tego co można nimi robić. Tak, w środowisku Arduino można pisać w C i C++, sam "język Arduino" to jest C++ z pewnymi rzeczami dodanymi dla ułatwienia, ale można też utworzyć pliki .cpp i .c i też się skompilują (choć w losowej kolejności). Lutowanie jest łatwiejsze niż ci się wydaje, szczególnie jak masz kurs, który mówi jak dokładnie jak się do tego zabrać. Jest to też zdolność bardziej manualna, więc może być dobre na "odetchnięcie" od wkuwania. Osobiście uważam lutowanie za bardzo relaksujące zajęcie. Jeśli będziesz szedł w elektronikę, to prędzej czy później lutowania się będziesz musiał nauczyć. Technikę cyfrową zrobiłbym po Arduino jeśli zainteresuje cię bardziej jak to tam wszystko w środku działa i z czego procesory są zrobione. Raspberry Pi to trochę powtórka z Arduino (też podłączasz różne rzeczy i je oprogramowujesz) a trochę podstawy Linuksa i Pythona. Pewnie Linuksa się będziesz musiał uczyć prędzej czy później, nie trzeba tego koniecznie robić na Pi, można też na "dużym" komputerze, choć podłączanie fizycznych rzeczy z pewnością jest dużą atrakcją i sprawia, że jest ciekawiej. Micro:bit to bardzo specyficzna płytka stworzona na potrzeby brytyjskich szkół — jeśli zrobisz kurs Arduino, to dużo nowego się tu chyba nie nauczysz. To oczywiście wszystko moje osobiste odczucia. Kursy czytałem, ale ich nie "robiłem" — z zestawem i wykonywaniem ćwiczeń — bo trochę za stary jestem i kiedyś się tego wszystkiego sam nauczyłem, mniej więcej. Tak więc niekoniecznie potrafię ocenić jakość tych kursów i ich stopień trudności.
  15. 4 punkty
    Użytkownik Reddita (DIY_Maxwell) miał drukarkę 3D, Raspberry Pi i dużo wolnego czasu - a oto efekt Źródło » https://www.reddit.com/r/raspberry_pi/comments/hm4s83/a_stopmotion_animation_i_made_using_raspberry_pi/
  16. 3 punkty
    W dużym skrócie największym błędem, był filtr kalmana za bardzo opierał się na gyro i przy zmianie kąta pokazywało szpilki przed zboczami co skutecznie uniemożliwiało jakakolwiek regulację
  17. 3 punkty
    Jak by cię położyć na ziemi, to byś się ślizgał pod górkę. Jak nie masz pojęcia, to się nie wypowiadaj i nie wprowadzaj ludzi w błąd, proszę cię o to po raz kolejny.
  18. 3 punkty
    Możesz rozwinąć tę myśl? Skąd taki pomysł? Wyczytałeś to może w nocie katalogowej? Bo jeśli tak, to poproszę o numer strony, bo nic takiego nie widzę — kiedy używany jest wewnętrzny oscylator te nóżki to zwykłe porty GPIO i nie ma najmniejszego znaczenia co jest do nich podłączone. Znowu kolegę fantazja ponosi?
  19. 3 punkty
    Tym razem autoreklama Debiutujemy na YouTubie - na razie ruszamy z kursem elektroniki w pigułce
  20. 3 punkty
    Witam wszystkich!!! W końcu po długiej nieobecności na forum, zrealizuje to do czego się zobowiązałem w moim wpisie pod projektem Bootlader programmer for UNO R3 - Rev.2, a dokładniej mówiąc opisze jak za pomocą Arduino UNO i wyżej przedstawionego urządzenia wgrywać Bootloadery do konkretnych ATmeg. Na warsztat weźmiemy procesory ATmega 8/168/328, gdyż środowisko Arduino IDE wspiera te procesory umożliwiając nam wgranie wybranego bootloadera. Cały proces przedstawię z wykorzystaniem Arduino UNO R3 wyposażonym w ATmegę 328. Opiszę cały proces od początku czyli od instalacji środowiska Arduino IDE, które jest dostępne na stronie producenta Arduino. Na razie nie podłączamy naszego Arduino do komputera tylko instalujemy Arduino IDE. Kiedy instalacja przebiegnie do końca bez jakichkolwiek problemów, wtedy podłączamy nasze Arduino i czekamy. Bywa także, że Windows musi doinstalować sterowniki aby prawidłowo rozpoznać podłączone urządzenie. Instalacja odbywa się automatycznie z Windows Update. System znajdzie i zainstaluje potrzebne nam drivery po czym wyświetli się komunikat, że sterowniki zostały prawidłowo zainstalowane oraz wyświetli nam nazwę podłączonego do komputera urządzenia. Po zakończeniu instalacji odłączamy nasze arduino i przygotujemy je do podłączenia Bootloader programmera. Najpierw zaczniemy od podłączenia płytki programatora do naszego Arduino tak jak to zostało przedstawione na zdjęciu. Aby ułatwić cały proces podłączenia, złącze programatora opisane zostało identycznymi oznaczeniami jakimi są opisane złącza Arduino UNO, dzięki temu proces podłączenia jest prosty i bezproble- mowy. Uwaga: Nie podłączamy linii RX, TX. W przypadku wgrywania bootloadera linie te nie mogą być podłączone. Następnym krokiem jest podłączenie Arduino do komputera oraz uruchomienie środowiska Arduino IDE po czym przechodzimy do ustawień środowiska Arduino IDE. Na początek sprawdzamy port, do którego podłączone jest nasze arduino. Jeżeli instalacja środowiska Arduino IDE przebiegła bez komplikacji to w zakładce "Narzędzia/Port" powinniśmy ujrzeć taki widok. Mamy do wyboru porty COM, wśród których znajduje się port do którego jest podłączone Arduino UNO. U mnie jest to port COM3. Możemy to rozpoznać po nazwie naszego urządzenia znajdującej się w nawiasie, po czym zaznaczamy wybrany port i przechodzimy do kolejnego ustawienia jakim jest wybór płytki. W tym celu klikamy "Narzędzia" i wybieramy opcję "Płytka". Z dostępnej listy wybieramy tą, która odpowiada naszej płytce podłączonej do komputera. W moim przypadku jest to: "Arduino UNO". Jest to bardzo ważne ustawienie bo od niego zależy wgrywanie sketch-ów czyli programów do Atmegi która jest sercem naszego Arduino, a także wgrywania bootloaderów do wybranych ATmeg z wykorzystaniem bootloader programmer. Ale wszystko po kolei. Po ustawieniu portu i wybraniu płytki klikamy "Plik/Przykłady" i z menu rozwijanego wybieramy "11.ArduinoISP/ArduinoISP". Jest to sketch, który czyni nasze arduino programatorem, które umożliwi nam wgranie do konkretnej ATmegi wybranego bootloadera z użyciem bootloader programmer. Naciskamy przycisk "wgraj" (strzałka w kółku) i czekamy na wgranie się sketcha. Po jego wgraniu klikamy na "Narzędzia/Programator" i z menu rozwijanego wybieramy "Arduino as ISP". Po zaznaczeniu tej opcji jesteśmy gotowi do wgrywania bootloaderów do ATmeg. Przejdziemy teraz do najważniejszej czynności czyli procesu wgrywania bootloadera. Bierzemy czysty procesor ATmega i umieszczamy go w podstawce bootloader programmera tak jak na zdjęciu. Jak widać na obudowie procesora z brzegu mamy półokrągle wycięcie a pod nim wgłębienie w postaci kółka które musi się znaleźć od strony dźwigni podstawki bootloader programmera. Opuszczamy na dół dźwignię podstawki tak aby piny złącza podstawki zostały dociśnięte do pinów procesora. Wgrywanie bootloadera do procesora ATMEGA 328 Klikamy na "Narzędzia i wybieramy "Wypal bootloader" po czym następuje wgrywanie bootloadera do naszej nowej czystej ATmegi. Proces ten może trwać od 15 sekund do jednej minuty. Diody: L, RX, TX na arduino powinny migać w trakcie wgrywania, a w dolnej części okna Arduino IDE powinniśmy ujrzeć taki komunikat w trakcie wgrywania oraz po zakończeniu wgrywania - zdjęcia poniżej. Po wszystkim możemy wyjąć procesor z podstawki i zaprogramować kolejny, jeżeli istnieje taka potrzeba. Wgrywanie bootloadera do procesora ATMEGA8/168 Tutaj zanim zaczniemy wypalać bootloader do jednego w powyższych procesorów, musimy dokonać zmiany jednego ustawienia. Klikamy "Narzędzia" i wybieramy opcję "Płytka" po czym z dostępnej listy wybieramy "Arduino NG or older" Ponownie klikamy "Narzędzia" i na liście wyboru pojawia nam się pole "Procesor" najeżdżamy na to pole kursorem, gdzie z menu rozwijanego mamy do wybory wyżej wymienione procesory. Klikamy na dany procesor do którego będziemy wgrywać bootloader. Następnie znów klikamy "Narzędzia" i opcję "Wypal bootloader" Proces ten może trwać od 15 sekund do jednej minuty. Diody: L, RX, TX na arduino powinny migać w trakcie wgrywania, a w dolnej części okna Arduino IDE powinniśmy ujrzeć taki komunikat w trakcie wgrywania oraz po zakończeniu wgrywania - zdjęcia poniżej. Po wszystkim możemy wyjąć procesor z podstawki i zaprogramować kolejny, jeżeli istnieje taka potrzeba. W przypadku kiedy wszystkie czynności zostały wykonane poprawnie a bootloader nie chce się wgrać, powinniśmy sprawdzić wszystkie połączenia płytek i procesora. Gdy to nie pomoże, warto na chwilę odłączyć arduino od komputera oraz zamknąć po czym ponownie uruchomić środowisko IDE Arduino. Za jakiś czas przedstawię kilka sposobów wgrywania sketch-y do procesora, który znajduje się już w gotowym projekcie. Tak aby obyło się bez zbędnych czynności w postaci ciągłego przekładania ATmegi z Arduino do gotowego projektu.
  21. 3 punkty
    Witamy na forum Tutaj masz bardzo fajnie wszystko wyjaśnione, jeśli chodzi o kwestie programową jak i połączeniową: https://forbot.pl/blog/kurs-arduino-ii-diody-rgb-tradycyjne-oraz-ws2812-id15495
  22. 3 punkty
    Witam! Aktualizacja projektu: 1. Przyszły płytki pod enkodery, więc teraz pozycja układów w stosunku do osi silnika jest idealna 2. Zaimplementowałem wiele poprawek w oprogramowaniu układowym. 3. Pamięć EEPROM podłączona, więc teraz mam możliwość zapisu i odczytu danych kalibracyjnych dla danego układu silnik / enkoder 4. MPU6050 podłączony i mam już dostęp do danych z żyro i akcelerometru Plan na rozwój oprogramowania: 1. Dalsza optymalizacja oprogramowania (np użycie DMA do odczytu enkoderów oraz MPU). 2. Początkowo planuję użyć filtr komplementarny do ustalenia kąta wychylenia robota względem płaszczyzny pionowej. Później poeksperymentuję z filtrem Kalmana. 3. Zacznę od pojedynczego kontrolera PID który będzie miał za zadanie utrzymać zadany kąt wychylenia robota. Na wyjściu będzie długość wektora do sterowania FOC oraz kierunek obrotów (znak). 4. Gdy powyższy PID będzie działał prawidłowo, wtedy dodam kolejny PID który będzie utrzymywał zadaną prędkość robota, a na wyjściu będzie wymagany kąt wychylenia robota. Ta wartość będzie podawana jak referencyjna dla pierwszego kontrolera. 4. Kolejny PID do korygowania prędkości obrotowej lewego i prawego silnika. Będzie miał za zadanie utrzymanie robota na prostym kursie, lub skręcanie w zadanym kierunku Ponieważ czeka mnie kolejny wyjazd (obowiązki zawodowe) więc jestem zmuszony wstrzymać prototypownie do października. W między czasie w wolnej chwili dopracuję to co mogę nie mając dostępu do sprzętu, czyli schemat i projekt płytki drukowanej. Zacznę też projektować robota od strony mechanicznej (pewnie założę oddzielny wątek DIY gdy już będę miał co zaprezentować). Poniżej jedna z płytek enkodera: Obowiązkowe są tylko elementy IC1 i C1, reszta jest opcjonalna zależnie od tego jak chcemy wykorzystać sam układ i w jaki sposób podłączyć enkoder. Pozdrawiam i do następnego razu.
  23. 2 punkty
    Każdy fan Arduino powinien kojarzyć mikrokontroler ATmega328P, który znajduje się m.in. na pokładzie Arduino UNO. Zastanawialiście się jednak jak wygląda wnętrze takiego układu? Użytkownik Reddita podpisujący się jako Ryancor wykorzystał kwas fluorowodorowy, aby to sprawdzić. Po dwóch godzinach udało mu się usunąć warstwy ochronne i oczyścić krzemowy rdzeń układu, który następnie trafił prosto pod mikroskop. Źródło » https://www.reddit.com/r/arduino/comments/hrwdun/delayered_atmega328p_silicon_die_the_hydrofluoric/
  24. 2 punkty
    @Ruby Dzięki za zainteresowaniem moim projektem. Koszt tego projektu wynosi około 150 - 250 zł a dokładnie : 3x Niebieski przełącznik odpowiedzialny za włączanie grzałek - 5,85 zł 2x Przełączniki z dźwignią - 6,4 zł Termostat - 14,9 zł Amperomierz - 11,95 zł Przetwornica 3A i potencjometr z włącznikiem (nie wiem ile omów ten wziąłem dla przykładu, trzeba by zmierzyć potencjometr w przetwornicy) - 9,3 zł Złącze do zasilania grzałek oraz od czujnika temperatury - 6,5 zł Złącze zasilające (może być wypuszczony kabel) - 6,2 zł 1x Gniazdo bezpiecznikowe - 1,5 zł 2x Grzałki 50w - 20 zł 2x Grzałki 40w - 12 zł 1m Osłony silikonowe led - 5 zł Pompka powietrzna - 30 zł (ja kupiłem w sklepie internetowym z komisu za 10 zł) Wężyki złączki i akcesoria do pompki - 15-20 zł Pojemnik do trawienia szklany - 100 zł (można użyć zwykłego prostokątnego pojemnika z kuchni za 5 zł, lub dać to szklarzowi za kilka zł) I do tego jeszcze jakieś przewody i tyle Ja swoje płytki projektuje w EasyEDA dość szybko nauczyłem się go obsługiwać można się też posłużyć filmami. Najpierw Trzeba zaprojektować płytkę np. w EasyEDA później prze konwertować do pdf-a w odbiciu lustrzanym i wydrukować na papierze kredowym 100 kartek około 25 zł (są różne gramatury miałem największą i najmniejszą ale ja nie widzę różnicy). Musi być też wydrukowane na drukarce laserowej. U mnie w miejscowości w sklepie komputerowym wydrukowali mi jedną kartkę za 30 gr. zazwyczaj jedną płytkę drukuje na 3 kartkach. Od jakiegoś czasu projektuje płytki dwustronne, wtedy drukuje płytkę na 6 kartkach. Płytkę najlepiej wyczyścić papierem ściernym grubość 1000 i odtłuścić, potem położyć papier na płytce i prasować na połowie mocy około 3 min. Potem trzeba ostudzić płytkę w wodzie i ostrożnie ściągnąć papier i płytka gotowa do wytrawienia. Może to wszystko wydaje się skomplikowane ale jak się już nauczysz projektować to nie skomplikowaną płytkę zaprojektujesz po 40 min. Ja od 23.08.2019 do teraz wytrawiłem 40 płytek (dzisiaj trawię jeszcze dwie). Powodzenia w projektowaniu płytek i nauce lutowania
  25. 2 punkty
    Hej, stworzyłem pudełko do dezynfekcji z dwiema lampami UVC i arduino nano, które pełni rolę timer-a. Przydaje się do dezynfekcji maseczek, kluczy, telefonu, wszystkiego, czego dotykamy rękoma i chcemy zdezynfekować i zmieści się do pudełka :)
  26. 2 punkty
    Witam wszystkich! Od programowania trzymałem się z daleka, jednak nauka biostatystyki pchnęła mnie w tym kierunku. Ostatnio zboczyłem trochę z kursu i zainteresowałem się Raspberry. W ciągu tygodnia od tej decyzji udało mi się podłączyć Netflixa do starego telewizora i udostępnić nie-sieciową drukarkę na komputerach domowych po WiFi. Są już pierwsze efekty, a ja uczę się dalej
  27. 2 punkty
    @ethanak Dzięki za podpowiedź! Na pewno pogrzebie w tym temacie! @Gieneq O autoformacie też pierwsze słyszę, rzeczywiście ułatwia życie Tutaj już kod z zastosowanymi waszymi poradami #include <LiquidCrystal.h> LiquidCrystal lcd(2, 3, 4, 5, 6, 7); float czas = 0.0; int stanLicznika = 0; enum stanLicznika {oczekiwanie, pomiar, wynik}; unsigned long aktualnyCzas = 0; unsigned long zmianaCzasu = 0; unsigned long zmiana = 0; void setup() { // put your setup code here, to run once: pinMode(8, INPUT_PULLUP); pinMode(9, INPUT_PULLUP); pinMode(10, INPUT_PULLUP); lcd.begin(16, 2); } void loop() { aktualnyCzas = millis(); switch (stanLicznika) { case oczekiwanie: lcd.setCursor(0, 0); lcd.print("Wcisnij"); lcd.setCursor(0, 1); lcd.print("przycisk"); if (digitalRead(8) == LOW) { lcd.clear(); zmianaCzasu = aktualnyCzas; stanLicznika = pomiar; } break; case pomiar: lcd.setCursor(0, 0); lcd.print("Pomiar czasu"); zmiana = aktualnyCzas - zmianaCzasu; if (zmiana >= 10) { zmianaCzasu = aktualnyCzas; czas = czas + 0.01; } lcd.setCursor(0, 1); lcd.print(czas); if (digitalRead(9) == LOW) { lcd.clear(); stanLicznika = wynik; } break; case wynik: lcd.setCursor(0, 0); lcd.print("Koniec pomiaru"); lcd.setCursor(0, 1); lcd.print(czas); if (digitalRead(10) == LOW) { lcd.clear(); czas = 0; stanLicznika = oczekiwanie; } else if (digitalRead(8) == LOW) { lcd.clear(); stanLicznika = pomiar; } break; } }
  28. 2 punkty
    @Ruby może jesteś naładowany A tak na serio, to przy dużym wzmocnieniu możliwe jest że dotykając kabelek aktywujesz tranzystor. Ewentualnie możesz (jak możesz) zlutować układ nawet w powietrzu, albo na kartonie i zrobić z tego taką grę z prawdziwego zdarzenia. Fajnie, że eksperymentujesz i obserwujesz co się dzieje z tym co robisz - to się ceni. Powodzenia z dalszymi zadaniami
  29. 2 punkty
    Witam wszystkich Forumowiczów! Imiona mam 2 (w sumie 3, bo jeszcze to z bierzmowania ), spośród nich moje ulubione to Dawid i tym imieniem zazwyczaj posługuję się w internecie Mam 29 lat, na co dzień zajmuję się pracą biurową, oprócz tego rozwijam liczne zainteresowania. Pochodzę z Dzierżoniowa na Dolnym Śląsku, być może część z Was jeszcze pamięta Zakłady Radiowe DIORA (duża część mojej Rodziny tam pracowała, także jako konstruktorzy). Od lat hobbystycznie koduję w C++, w tym także proste gry. W ostatnim czasie - sezon wakacyjno-urlopowy - postanowiłem sięgnąć do "korzeni" i zająć się przypomnieniem oraz zdobyciem nowych wiadomości z zakresu elektroniki. Zamierzam przerobić kursy dotyczące podstaw elektroniki i techniki cyfrowej, a także zabrać się za Arduino, by w pewien sposób połączyć informatykę z elektroniką. Oczywiście nie obędzie się bez przerobienia kursu budowy robotów. Zaopatrzyłem się ostatnio w 6 zestawów do kursów FORBOT (polecam - bardzo fajnie dobrane i spakowane elementy) i zabieram się za naukę! Pozdrawiam Forumowiczów!
  30. 2 punkty
    Cześć @ethanak, ja w takiej sytuacji mówię nieodmienie: "Zamiast realizować wyprawy do krawędzi spróbujcie przekopać się przez ten naleśnik". Wtedy płaskoziemca zaczyna analizować ten pomysł, a że nie grzeszy inteligencją zajmuje mu to trochę czasu, a ja mam spokój Pozdrawiam
  31. 2 punkty
    @jendrekpk witam na forum i pochwalę Cię, że zabrałeś się za zadania dodatkowe. Jednak mam kilka konstruktywnych uwag Po pierwsze polecam skrót klawiszowy chyba Ctrl-T autoformat, bo położenie nawiasów nie ułatwia czytania bloków warunkowych. Nie jest to dobry pomysł dawać 2 takei same bloki kodu - 2x używasz Serial.available(), gdzie logiczny przepływ danych jest inny: odbierz dane > zinterpretuj > zareaguj. Powiązane z powyższym - zmienna stanu nie powinna być na zewnątrz odczytu Seriala bo może to spowodować zacięcie kodu. Lepsze podejście to: odczyt Seriala, jeżeli odebrał to: sprawdź czy napis to "Czerwona", tak tak to: zmień wartość zmiennej na przeciwny i zareaguj ustawiając pin jak nie to nie rób nic, Jak zmienić wartość na przeciwny to zależy od podejścia - najprościej w if-else, ale możesz też bardziej fikuśnym sposobem, ale to na później. Najpierw działający kod
  32. 2 punkty
    Prototyp wersji na micro-USB przyszedł dzisiaj i ogólnie rzecz biorąc działa: Jednak po przetestowaniu, postanowiłem wprowadzić do projektu kilka zmian: Po pierwsze, skróciłem gniazdko — okazuje się, że wewnątrz wtyczki nie ma miejsca na włożenie jej do końca. Po drugie, usunąłem przesunięcie styków względem siebie, mające na celu zapewnienie zasilania zanim nastąpi połączenie sygnałów — styki są już przesunięte w ten sposób we wtyczce, w gniazdku nie muszą, a powiększa to nieco powierzchnię styku. Dodatkowo, ponieważ wtyk z płytki nie wygląda jak standardowe USB, postanowiłem go podpisać żeby nie było wątpliwości. Pierwsze włożenie wtyczki spotkało się z pewnym oporem, kolejne już nie. Problemem okazuje się warstwa HASL cyny na stykach, chroniąca je przed korozją: Jak widać, zwiększa ona nieco całkowitą grubość płytki ze stykami, a że cyna jest miękka, to została zdarta. Żeby temu zapobiec, postanowiłem usunąć pad z drugiej strony wtyczki — oryginalnie chciałem, żeby łączył się z ekranem kabla, ale i tak nie podłączam tego nigdzie na płytce, a boję się, że przy pomyłkowym odwrotnym włożeniu wtyczki może spowodować zwarcie. No i zwiększa on nieco całkowitą grubość. Podejrzewam, że użycie warstwy złota zamiast cyny też by pomogło, ale płytki miały być tanie... Poza tym są drobne kosmetyczne zmiany. Ponieważ fabryka, której chciałem użyć, życzy sobie dodatkowej opłaty za użycie czarnego nadruku na żółtych płytkach (domyślnie używają białego), postanowiłem zmienić kolor płytki i ozdoby — teraz zamiast szwajcarskiego sera są szwajcarskie krzyżyki. Przy okazji, skoro już zamawiałem płytki, to zamówiłem też wersję o grubości 0.8mm płytek z USB-C. Działają dobrze i znacznie stabilniej siedzą we wtyczce — więc jakby ktoś robił tę wersję, to polecam właśnie takie grubsze płytki.
  33. 2 punkty
    @KHX Jasne, przepraszam za tą reakcję negatywną, już odklikałem Czasami jestem zbyt poważny
  34. 2 punkty
    Kiedyś zrobiłem coś takiego: https://majsterkowo.pl/ekspres-do-plytek-czyli-wytrawiarka/ Używam do dziś
  35. 2 punkty
    Przede wszystkim: indeksy w tablicy zaczynają się od zera a nie od jedynki, czyli zmienna1[5] jest już poza tablicą.
  36. 2 punkty
    Poczekam aż będzie za 9,99 i kupię . BUMP.
  37. 2 punkty
    Cześć, Błąd 403 protokołu HTTP mówi, żę dostęp do zasobu sieci jest zakazany - strzelam, że nie założyłeś bezpłatnego konta przed próbą pobrania sterownika. Pozdrawiam
  38. 2 punkty
    Wow, na pierwszy projekt to ja brałem diodę, ale super, że się udało . Kod pewnie się komuś przyda, nic tylko życzyć powodzenia dalej w arduino i może modyfikacji projektu :).
  39. 2 punkty
    A zapomniałem jeszcze: uwielbia firany. Bardzo mu smakują.
  40. 2 punkty
    Postaram się udostępnić schematy, jednak muszę ich poszukać na starym dysku. Jedyny do którego mam dostęp to schemat 1bitowego pełnego sumatora w technologii bodajże TTL. Co do cen to tranzystory bipolarne kosztują 10 gr za sztukę najtańsze a z kanałem N ok.30 gr. Ceny z TME
  41. 2 punkty
    Gdy klient zleca Ci budowę Transformersa, ale ma ograniczony budżet Źródło » https://www.reddit.com/r/shittyrobots/comments/hjmp2c/war_of_the_worlds/
  42. 2 punkty
    Zdarza się taki problem, ale fajnie że doszedłeś do niego i udało się rozwiązać powodzenia z dalszymi zadaniami
  43. 2 punkty
    Jeśli masz suchą skórę, to jej opór spokojnie może przekraczać zakres twojego miernika.
  44. 2 punkty
    @Ruby pod każdym odcinkiem kursu na blogu są komentarze - to najlepsze miejsce do dyskusji na ten temat. Komentarze to w praktyce odpowiednie tematy na forum, więc najlepiej tam pisać o wszystkim co jest związane z daną częścią kursu
  45. 2 punkty
    Osobiście już od dawna składam sobie bramki i elementy cyfrowe z elementów dyskretnych. Zaprojektowałem taki sumator w kilku technologiach, jednak czasu mi zabrakło na złożenie tak owego. O ile mi się wydaję sumator z artykułu jest w technologii RTL, dość prosta, jednak potrzeba dużą ilość elementów, według moich projektów technologia NMOS jest najlepsza, potrzeba najmniej elementów (3x mniej niż w TTL), jednak tranzystory z kanałem typu N są 3 razy droższe niż NPN, plusem jest mniejsza ilość elementów czyli łatwiejszy montaż i projekt płytki.
  46. 2 punkty
    Moja wersja stacji pogodowej z wyświetlaniem wyników na lcd: #include <OneWire.h> #include <DallasTemperature.h> //Biblioteka termometru cyfrowego OneWire oneWire(A5); //podłącznie termometru do A5 DallasTemperature sensors(&oneWire); //przekazanie informacji do biblioteki #include "DHT.h" DHT dht; //biblioteka czujnika wilgoci #include <LiquidCrystal.h> //biblioteka lcd LiquidCrystal lcd(8, 9, 10, 11, 12, 13); //inf. o wyswietlaczu void setup() { // put your setup code here, to run once: Serial.begin(9600); dht.setup(2); //czujnik wilgoci pin 2 sensors.begin(); //inicjalizacja termometru lcd.begin(16, 2); //inicjalizacja lcd lcd.clear(); } void loop() { // put your main code here, to run repeatedly: sensors.requestTemperatures(); //zapytanie o odczyt termometru float temperatura = sensors.getTempCByIndex(0); //zapisanie odczytu jako stałej int wilgotnosc = dht.getHumidity(); //zapisanie odczytu jako stałej int odczyt = analogRead(A0); int zachmurzenie = map(odczyt, 0, 1024, 1, 5); lcd.setCursor(0, 0); lcd.print("Temperatura"); lcd.setCursor(0, 1); lcd.print(temperatura); lcd.setCursor(4, 1); lcd.print((char)223); lcd.setCursor(5, 1); lcd.print("C"); //wyświetlenie temperatury powietrza delay(2000); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Wilgotnosc"); lcd.setCursor(0, 1); lcd.print(wilgotnosc); lcd.setCursor(3, 1); lcd.print("%RH"); //wyswietlenie wilgotnosci powietrza delay(2000); lcd.clear(); switch(zachmurzenie) { //wyswietlenie inf. o zachmurzeniu case 1: lcd.setCursor(0, 0); lcd.print("Zachmurzenie"); lcd.setCursor(0, 1); lcd.print("Calkowite"); break; case 2: lcd.setCursor(0, 0); lcd.print("Zachmurzenie"); lcd.setCursor(0, 1); lcd.print("Duze"); break; case 3: lcd.setCursor(0, 0); lcd.print("Zachmurzenie"); lcd.setCursor(0, 1); lcd.print("Umiarkowane"); break; case 4: lcd.setCursor(0, 0); lcd.print("Zachmurzenie"); lcd.setCursor(0, 1); lcd.print("Male"); break; lcd.setCursor(0, 0); lcd.print("Zachmurzenie"); lcd.setCursor(0, 1); lcd.print("Bezchmurnie"); break; } delay(2000); lcd.clear(); }
  47. 2 punkty
    W kontekście tego układu ciężko analizować napięcia, jeżeli opór R jest duży to styki mogą mieć wpływ ale napięcie powinno zmieniać się raczej proporcjonalnie. Układ z kursu ma inny cel - tranzystory MOS są tu traktowane trochę lżej, bo są trudniejsze i układ ten służy do tego, żeby raz podłączyć R do zasilania i raz do masy i zaobserwować np. pojemność bramki. Ale żeby sprawdzić jak działa, przejść przez punkty pracy to trzeba już regulowane napięcie na bramce. Układ do testów wyglądałby tak:
  48. 2 punkty
    Witam. Dzięki serdeczne za podpowiedzi. Ja dopiero zaczynam przygodę z elektroniką i ciężko jest na początku połapać się, a na blogu jest tyle wszystkiego, że nie wiadomo od czego zacząć. Twoje wskazówki są bardzo cenne. Pozdrawiam serdecznie. Andrzej.
  49. 2 punkty
    Witam serdecznie, nazywam się Adrian i pochodzę z pięknego Gdańska. Na karku 23 lata i sprowadza mnie tutaj chęć rozwijania się w różnych dziedzinach informatyki. Jako, że moja praca inżynierska będzie zawierała w sobie Rpi to zdecydowałem się na zakup pełnego zestawu edukacyjnego, który jak mam nadzieję pozwoli mi przejśc przez ciekawę strefę nieznanych mi jeszcze zagadnień. IoT oraz Smart House to według mnie przyszłość więc czas wziąć to w swoje ręcę! Pozdrawiam, Lazur
  50. 2 punkty
    @Tobitobiasz44 to była ostatnia część tego kursu, jak będzie coś nowego na pewno to ogłosimy
Tablica liderów jest ustawiona na Warszawa/GMT+02:00
×
×
  • Utwórz nowe...