Poniższy artykuł przedstawia – krok po kroku – wykonanie niewielkiego, kieszonkowego oscyloskopu cyfrowego. Nie jest on tak dobry jak zwykły warsztatowy oscyloskop, ale z drugiej strony można zabrać go niemalże wszędzie. Poza tym – nie wszystkie projekty DIY muszą mieć sens.
„Przeglądając Internet natknąłem się na proste oscyloskopy do samodzielnego montażu, zwykle wykonane w oparciu o kompatybilną z Arduino płytkę, która prawie zawsze wykorzystuje program napisany w Arduino IDE. Problem polega na tym, że Arduino nie jest zbyt szybkie, a jego ADC też nie jest za dobre. Dlatego niektórzy dodają do płytki Arduino osobny ADC (przetwornik analogowo-cyfrowy – przyp. red.), co zwiększa złożoność sprzętu i całkowitą cenę” - pisze autor projektu.
„Pomyślałem, że można to zrobić łatwiej i taniej, ale nadal uzyskać rozsądną wydajność. Jestem fanem serii mikrokontrolerów STM32 firmy ST Microelectronics, więc wybrałem jeden z nich. Lubię też robić projekty z możliwie najmniejszym i często najtańszym mikrokontrolerem, ale wciąż z akceptowalną wydajnością” - opisuje swój projekt dalej. Przyjrzyjmy się zatem, krok po kroku, jak projekt ten został zrealizowany.
Krok 1: Mikrokontroler: STM32F030F4
Najmniejszy mikrokontroler STM, jaki jest dostępny, to STM32F030F4, zwykle sprzedawany w serwisie eBay w postaci płytki rozwojowej. Wewnątrz tego mikrokontrolera znajduje się przyzwoity ADC, potrafi wykonać konwersję w czasie 1 µs. Wyniki konwersji można odczytać z stosownego rejestru, ale co jest bardzo ważne w tym przypadku, są one również przesyłane przez DMA do dowolnego miejsca w pamięci, dzięki czemu proces jest bardzo szybki i nie wymaga angażowania procesora. W tym mikrokontrolerze znajdują się również zwykłe timery, ma SPI, I²C, RTC, USART i dwa Watchdogi. Oczywiście nie można używać wszystkich urządzeń peryferyjnych w tym samym czasie, ponieważ ta wersja STMa ma tylko 20 pinów, a niektóre z nich są używane do takich rzeczy jak zasilanie, reset, zewnętrzny oscylator, programowanie i wybór sposobu bootowania.
Aby zaprogramować mikrokontrolery z rodziny STM, potrzebny jest odpowiedni programator – na przykład STLink-V2, dostępny zarównow wielu hurtowniach elektronicznych, jak i na portalach aukcyjnych. Płytka z STM32F030F4 jest sprzedawana za mniej niż 3 euro, a STLink-V2 dostępny jest za podobną cenę. Jeśli posiadasz oficjalną płytkę rozwojową firmy ST Microelectronics, taką jak Nucleo-board, to jest na niej STLink-V2, który można z niej zdjąć. Autor tak właśnie zrobił i umieścił ten programator w niewielkiej, osobnej obudowie.
Krok 3: Lista części
* Obudowa (12 cm x 8 cm x 3 cm)
* Płytka uniwersalna (8 cm x 12 cm)
* Mikrokontroler STM32F030F4 na przejściówce TSSOP20 - DIP
* ST7735s - 1,8-calowy wyświetlacz TFT
* Bateria litowo jonowa
* HT7333 - stabilizator napięcia 3,3V
* Wzmacniacz operacyjny MCP6021
* Kwarc 8 MHz
* Enkoder obrotowy i pokrętło
* Przycisk zasilania
* Złącza bananowe
* Płytka ładowarki dla ogniw litowo-jonowych
Krok 4: Przygotowanie
Jak zwykle wszystkie projekty zaczynam od prototypu. Niestety płytka rozwojowa STM32F030F4 jest zbyt szeroka, aby można ją było zastosować na jednej płytce prototypowej. Jest też zbyt wąska, aby można go było używać na dwóch połączonych ze sobą płytkach prototypowych, jak pokazano na zdjęciu po prawej stronie. Rozwiązaniem jest zdjęcie jednego z rzędów goldpinów po jednej stronie.
Docelowo, z uwagi na to, że moduł ten jest zbyt duży, aby zmieścić go w niewielkiej plastikowej obudowie, autor wykorzystał sam układ na płytce uniwersalnej (patrz: zdjęcie po lewej), do którego dołożył wszystkie potrzebne elementy (kondensatory odsprzęgające, rezonator i stabilizator LDO). Sam układ w obudowie TSSOP20 jest stanowczo zbyt mały na konstrukcję w pająku, w związku z czym wykorzystana jest widoczna na zdjęciach płytka uniwersalna.
Autor nie planuje projektować płytki drukowanej dla urządzenia, ponieważ, jak sam zauważa, dla jednego układu nie ma to sensu.
Krok 3: Specyfikacja układu
Ekran TFT ma rozdzielczość 160 x 128 pikseli, co oznacza, że oś X (podstawa czasu) potrzebuje 160 próbek ADC. ADC potrzebuje minimum 1 mikrosekundę na próbkę. Użyteczna granica oglądania sygnałów na tak małym ekranie to około dziesięć okresów, a to przekłada się na 16 pikseli. W ten sposób łatwo oszacować możemy, że jeden przebieg zajmie 16 mikrosekund, co daje przebieg o częstotliwości 62500 Hz. To niewiele, gdy porówna się do prawdziwego oscyloskopu cyfrowego z pasmem 100 MHz, ale wystarczy do projektów audio, oświetlenia LED i eksperymentów z niskimi częstotliwościami.
Ostatecznie autorowi nie udało uzyskać się próbkowania sygnału na ADC z prędkością 1 000 000 próbek na sekundę, więc zdecydował się na 800 kHz, jako maksimum. Dlatego podstawa czasu wzrasta do 40 µs na działkę. Przy 5 podziałach na ekranie oznacza to 200 µs na cały ekran. Przebieg o częstotliwości 5 kHz wypełnia wtedy cały ekran, granica użyteczności wynosi około 50 kHz. Jeśli chodzi o drugi koniec zakresu rozdzielczości czasowej, to autor arbitralnie przyjął 200 ms na działkę, co przekłada się na 1 sekundę na cały ekran, wtedy sygnał 1 Hz wykorzystuje całą szerokość ekranu.
ADC przekształca napięcie na swoim wejściu w odniesieniu do napięcia 3,3 V (napięcia zasilania). Można wybrać rozdzielczości 12, 10, 8 i 6 bitów, a im niższa ona będzie, tym szybsza jest konwersja. Ponieważ ekran TFT ma oś Y (rozdzielczość pionową) równą 128 pikseli (7 bitów), nie jest konieczne, aby ADC miał większą rozdzielczość niż 7 bitów, ale rozdzielczość 7 bitów nie jest dostępna w układzie, więc jest ustawiona na 8 bitów. Najmniej znaczący bit zostanie po prostu odrzucony przez przesunięcie w prawo o 1 bit zmierzonej wartości.
Tłumienie sygnału jest wymagane, aby móc zmierzyć przebieg o amplitudzie większej niż 3,3 V. Jeśli istnieje potrzeba mierzyć mniejsze sygnały, to potrzebny jest wzmacniacz. Oznacza to albo konieczność posiadania zasilania symetrycznego, albo przesunięcie napięcia wejściowego do połowy napięcia zasilania. Ponieważ autor chciał używać tylko jednej baterii w systemie i maksymalnie uprościć urządzenie, wybrano metodę z offsetem. Oznacza to, że masa wejścia nie jest w ogóle połączony z masą urządzenia, a jest podłączona do napięcia offsetu, które jest wirtualną masą. Oznacza to również, że trzeba używać tego oscyloskopu zasilanego z baterii.
Oscyloskop ma tylko jedną czułość: 1 V na działkę. Do układu można by dodać np. tłumik z przekaźnikami lub cyfrowy potencjometr, a także regulowany wzmacniacz, jednakże to dosyć złożone i wymagałoby np. zwiększenia napięcia zasilania, ponieważ przekaźniki wymagają co najmniej 5 V do pracy. Cyfrowe potencjometry z kolei generują szum w sygnale. Oczywiście można do niego dodać wzmacniacz lub tłumik, wystarczy dołączyć go przed wejściem. Oscyloskop nie powinien powodować dużego obciążenia mierzonego obwodu. Większość oscyloskopów posiada rezystancję wejściową 1 megaohma, podobnie jest z tym. Można wykorzystywać go z sondą oscyloskopową 10x, co dodatkowo zwiększy zarówno zakres napięć wejściowych, jak i impedancję wejściową (dziesięciokrotnie), ale może być również wymagać dodania układu kompensacyjnego z trymerem. Autor nie testował swojego urządzenia z tego rodzaju sondą.
Analogowa szerokość pasma oscyloskopu musi być jak najwyższa - co do zasady, co najmniej 10 razy większa od częstotliwości, którą układ ma mierzyć. Wynika to z faktu, że np. fala prostokątna o częstotliwości zaledwie 10 kHz ma wiele harmonicznych i aby zobaczyć dziewiątą harmoniczną, trzeba mieć pasmo o szerokości co najmniej 90 kHz. Mówiąc inaczej, jeśli spróbujesz zobaczyć falę prostokątną o częstotliwości 10 MHz na oscyloskopie 10 MHz, zobaczysz falę sinusoidalną o częstotliwości 10 MHz. Tutaj wystarczyłoby pasmo 500 kHz, ale im więcej, tym lepiej. Autor użył wzmacniacza operacyjnego z pasmem równym 10 MHz - MCP6021, z tego prostego powodu, że miał go na stanie. Jeśli w swoim projekcie chcesz wykorzystać inny, to podstawowym wymaganiem jest możliwość pracy z napięciem 3,3 V.
Przy przesunięciu od 0 V do 3 V dopuszczalne napięcie na wejściu wynosi od -6 V do 6 V. Ale zawsze z maksymalną górną wartością napięcia 6 V! Czyli od -6V do 0V przez -3V do +3V aż do 0 V do 6 V. Wystarczająco dobre dla większości projektów z Arduino, a jeśli to konieczne, podwojenie rezystora wejściowego z 1 Mohm do 2 Mohm podwoi zakres napięcia wejściowego.
Poziom wyzwalania (trigger) można ustawić w dowolnym miejscu na ekranie, co oznacza, że można go ustawić w zakresie od prawie -6V do prawie 6V. Ale tak naprawdę nie ma to wiele wspólnego z napięciem, a jedynie z pozycją przebiegu na ekranie. Wyzwalanie działa tylko na narastających zboczach sygnału. Jeśli chcesz, możesz dodać w oprogramowaniu opcję wyzwalania również na opadających zboczach, ale sam autor uznał, że nie jest to konieczne.
Ostatnią specyfikacją urządzenia, jest określenia sposobu ładowania i rozmiaru ogniwa litowo-jonowego w systemie. Akumulator ładowany jest za pomocą małej płytki ze złączem mini-USB. Na początku autor chciał użyć baterii litowo-jonowej w rozmiarze 18650, ale była ona za duża, dlatego wybrano małe ogniwo podobne do baterii do telefonu. W swoim projekcie możesz użyć dowolnej baterii litowo-jonowej o nominalnym napięciu równym 3,7 V.
Specyfikacje:
* Rezystancja wejściowa 1 Mohm
* Czułość 1V/div.
* Podstawa czasu 200 ms/div – 40 µs/div.
* Napięcie wejściowe od -6 V – 6 V.
* Poziom wyzwalania dowolny poziom na ekranie
* Zasilanie z baterii
Krok 5: Sprzęt i schemat
Możemy teraz spojrzeć na schemat układu (po lewej). Jeśli wykorzystana jest płytka rozwojowa z STM32F030F4, to niewiele jest do zrobienia poza podłączeniem wyświetlacza, enkodera i wzmacniacza operacyjnego. Nawet stabilizator znajduje się na płytce. Autor nie korzystał jednak z płytki rozwojowej, dlatego też musiał wszystkie potrzebne elementy, takie jak oscylator i kondensatory filtrujące zasilanie, umieścić w układzie samodzielnie.
Krok 6: Zasada działania
Biblioteka TFT
Dla większości standardowych wyświetlaczy są gotowe do użycia biblioteki, jednakże autor nie mógł znaleźć takowej dla wybranego wyświetlacza, dołączonego do mikrokontrolera STM32. Biblioteka, którą przeportował na STM32, została stworzony dla Arduino. Po przeportowaniu biblioteki na układ STM32 okazało się, że wyświetlacz nie jest zbyt szybki. Na szczęście używa SPI i można podnieść prędkość tego interfejsu, co okazało się konieczne, aby utrzymać rozsądną częstotliwość odświeżania wyświetlacza. Mimo tego, konieczne okazało się maksymalne ograniczenie ilości przesyłanych danych. Przy ustawieniu najkrótszej podstawy czasu ekran odświeżany jest z częstotliwością około 15 do 20 Hz. Przy najdłuższej podstawie czasu (200 ms/div) częstotliwość odświeżania wynosi tylko 0,5 Hz! Przyjrzymy się temu ostatniemu czasowi bliżej w dalszej części artykułu.
Odczytywanie położenia enkoderów obrotowych okazało się najbardziej problematyczne, ale wynikało to z ich słabej jakości. Wykorzystane elementy wymagały filtrowania wejść, a i tak nie można ich przekręcać zbyt szybko, gdyż wtedy nie są dostatecznie responsywne.
Wykorzystanie DMA do zbierania danych z ADC jest jedną z najbardziej kluczowych operacji. Czas trwania 8-bitowej konwersji ADC równy jest około jednej mikrosekundzie. Przetwornik analogowo-cyfrowy jest uruchamiany przez zegar (TIM3), który działa w sposób ciągły i wysyła impulsy do ADC zgodnie z aktualnym ustawieniem podstawy czasu. Najniższa częstotliwość to 160 Hz. Zatem ADC wykonuje 160 konwersji na sekundę, wypełniając ekran o rozdzielczości 160 pikseli w ciągu 1 sekundy. Najwyższa częstotliwość to 800 kHz, więc ekran jest wypełniony 160 x (1/800 000) = 200 µs. Gdyby tylko wyświetlacz i oprogramowanie działały dostatecznie szybko, można by uzyskać częstotliwość odświeżania równą nawet 5 kHz (co nie było trudne w realizacji w przypadku oscyloskopów z kineskopami).
Dane z ADC są przesyłane do tablicy: adc_buffer[], znajdującej w pamięci. Odbywa się to za pomocą DMA, co oznacza, że procesor mikrokontrolera nie jest do tego w ogóle potrzebny - może kontynuować wszystko to, co w danym momencie robi. To sprawia, że przechwytywanie danych jest bardzo proste i szybkie. Gdy DMA jest gotowy z zaprogramowaną liczbą wartości, które ma przetransportować, ustawia flagę TC (transmisja zakończona) i wyzwala przerwanie. Samo przerwanie nie robi wiele, po prostu czyści flagę TC i ustawia zmienną o nazwie „token” jako sygnał do głównej procedury, informujący, że dane są gotowe do wyświetlenia. ADC kontynuuje konwersję, a DMA przenosi wyniki konwersji do zmiennej adc_buffer[]. Więc, bez względu na to, co jeszcze dzieje się wewnątrz mikrokontrolera, do adc_buffer[] dociera niekończący się strumień wartości z przetwornika analogowo-cyfrowego.
Wyświetlacz ma szerokość 160 pikseli, więc do wyświetlenia pełnego przebiegu potrzeba tylko 160 wartości. W rzeczywistości adc_buffer[] zawiera 320 próbek. Tak więc DMA przechowuje w nim 320 wartości, zanim wyzwoli przerwanie TC. Dzieje się tak, ponieważ wyzwalanie odbywa się programowo. A ponieważ jest bardzo mało prawdopodobne, że pierwsza wartość w adc_buffer[] to miejsce, w którym powinien zostać uruchomiony trigger. Konieczne jest znalezienie miejsca, w którym znajduje się ten punkt. Odczytywanych jest więc 320 wartości i w pierwszych 160 z nich wyszukiwany jest rzeczywisty punkt wyzwolenia. Realizacja tego jest dosyć prosta – punkt wyzwolenia jest wyszukiwany w adc_buffer[] poprzez sprawdzenie, czy każda kolejna wartość jest równa wartości wyzwalacza en, a następna wartość jest tuż nad nią. Działa to całkiem dobrze, ale potrzebny jest bufor o rozmiarze większym niż rzeczywisty rozmiar wyświetlacza. Eksperymentalnie ustalono, że 320 próbek jest wartością optymalną.
To również jest powód, dla którego częstotliwość odświeżania w ustawieniach niższej podstawy czasu jest wolniejsza niż można by się spodziewać. Jak wspomniano wcześniej, przy używaniu ustawienia 200 ms/div, jeden ekran pełen danych zajmuje 1 sekundę, ale ponieważ wykonywana jest podwójna liczba konwersji, zajmuje to 2 sekundy. Na szybszych ustawieniach podstawy czasu ten problem nie jest tak bardzo zauważalny.
Krok 7: Kod programu
Kod programu składa się z wielu plików. Nie będziemy prezentowali poniżej listingów poszczególnych z nich – zajęłoby to stanowczo za dużo miejsca. Omówione zostaną tylko najważniejsze ich elementy. Poszczególne pliki można pobrać na stronie z projektem (patrz: link na końcu artykułu).
Main.c
Ponieważ konwersje nigdy się nie kończą, wartości w adc_buffer[] będą stale nadpisywane, przy najszybszym ustawieniu podstawy czasu będzie to miało miejsce dla wszystkich wartości co 400 mikrosekund. Aby zapobiec wyświetlaniu błędnych danych, pierwszą rzeczą, którą wykonuje główna pętla programu, jest skopiowanie adc_buffer[] do display_buffer[]. Nieco później kopiuje również te same dane do erase_buffer[]. Dzieje się tak, ponieważ wymazywanie całego ekranu trwa bardzo długo, dlatego też zamiast zmazywać cały ekran, program nadpisuje piksele poprzedniej klatki, wyświetlając jednocześnie kolejny piksel nowej klatki (tzn. ten z przebiegiem).
Reszta programu to generalnie kosmetyka. Wyświetlana jest siatka poziomych i pionowych linii, aktualne wartości czułości i podstawy czasu. Linia zerowego napięcia jest delikatnie jaśniejsza niż wszystkie inne i porusza się z przesunięciem. W miejscu obecnego poziomu wyzwalania wyświetlana jest mała linia. To, co w danym momencie jest aktywne na enkoderze obrotowym - podstawa czasu, przesunięcie lub poziom wyzwalania, jest pokazane na żółto.
SystemClock_Config Aby uzyskać największą prędkość z mikrokontrolera, autor zwiększał zegar znacznie powyżej oficjalnego maksimum mikrokontrolera (48 MHz), do 64 MHz, a nawet do 80 MHz. Wszystko działało bez problemów nawet przy 80 MHz, ale nie chciałem utrzymywać tej częstotliwości tak wysoko, więc została ona zredukowana do 64 MHz. Jest to nadal daleko poza specyfikacjami, więc mikrokontroler może działać nieprawidłowo. To powiedziawszy, wątpię, że spowoduje to kłopoty, ponieważ według specyfikacji producenta, będzie on działać w każdych warunkach, od 2,4 V do 3,6 V zasilania i od -40°C do 105°C. Oscyloskop stoi na biurku w 25°C i przy stabilnym zasilaniu 3,3V.
MX_ADC_Init W mikrokontrolerze pozostał jeszcze jeden pin, zadbałem o to, aby był to PA1, ponieważ ten pin może być użyty jako drugie wejście ADC. Możliwe byłoby zrobienie w ten sposób oscyloskopu dwukanałowego, ale autor tego nie próbował (jeszcze). W tej chwili PA1 jest skonfigurowane jako wyjście, którego używa do debugowania. Zegar ADC to zegar systemowy podzielony przez 4, to jest 16 MHz i to również jest powyżej specyfikacji producenta. Nie powinno być wyższe niż 14 MHz, a mimo wszystko działa. Każda konwersja rozpoczyna się na zboczu narastającym sygnału TRGO z TIM3 (patrz poniżej). ADC nie generuje żadnych przerwań, o te dba DMA po 320 próbkach.
MX_SPI1_Init Interfejs ten pracuje z częstotliwością 16 MHz z wyświetlaczem TFT ST7735.
MX_TIM3_Init Ten timer jest odpowiedzialny za rozpoczęcie konwersji ADC. Działa z częstotliwością 64 MHz i przy każdym UPDATE (przepełnieniu) wysyła impuls do ADC przez wyjście TRGO. Poprzez zmianę maksymalnej wartości timera w rejestrze zwanym AutoReloadRegister (ARR) zmienia się częstotliwość impulsów TRGO (podstawa czasowa). Ta zmiana jest wykonywana w procedurze przerwań TIM6 (patrz poniżej).
MX_DMA_Init Po prostu włącza swoje przerwania w NVIC.
MX_TIM14_Init Ten timer generuje falę prostokątną na PB1 o częstotliwości nieco większej niż 100 kHz i zmiennej szerokości impulsu. Filtr dolnoprzepustowy wykonany z rezystora 1k i kondensatora 10uF przekształca to w napięcie przesunięcia (offset) dla wzmacniacza operacyjnego.
MX_TIM16_Init Realizuje filtrację sygnału z enkodera obrotowego i zmniejsza wartość limitu czasu. Ten timer jest normalnie wyłączony i uruchamiany tylko wtedy, gdy na enkoderze obrotowym zostanie wykryty ruch. Wykrywanie ruchu odbywa się poprzez przerwania EXTI w GPIO podłączonym do enkodera obrotowego. Podczas pracy ten licznik czasu wytwarza 1000 przerwań na sekundę. Kiedy limit czasu osiągnie zero, ta procedura przerwania wyłącza się i ponownie włącza przerwania EXTI.
MX_GPIO_Init Ustawia linie GPIO jako wejście i wyjście, w zależności od potrzeby i dba o włączenie EXTI (zewnętrznego wejścia przerwania) na niektórych wejściach mikrokontrolera.
STM32F0XX_IT.C
DMA1_Channel1_IRQHandler czyści swoją flagę TC, a następnie ustawia token na 1. To wskazówka dla programu w main.c, że są do pobrania nowe dane, które muszą zostać wyświetlone.
TIM16_IRQHandler to funkcja obsługująca przerwanie timera TIM16 - określa, czy przycisk enkodera obrotowego jest wciśnięty i czy enkoder obrotowy jest obracany zgodnie z ruchem wskazówek zegara, czy przeciwnie do wskazówek zegara. Następnie ustawia ustawienia podstawy czasu dla TIM3, ustawienia przesunięcia w TIM14 i poziom wyzwalania. Ustawia również kolor, w jakim opcje powinny być wyświetlane. Ostatnią rzeczą, jaką ta funkcja wykonuje, jest wyłączenie się po krótkim czasie (timeout) i ponowne włączenie przerwań dla wejść zewnętrznych - EXTI (patrz poniżej).
EXTI2_3_IRQHandler to przerwanie dla przycisku obrotowego (enkodera). Włącza i wyłacza TIM16 użyty w filtrze tego wejścia. Ustawia limit czasu na 20 ms.
EXTI4_15_IRQHandler to z kolei przerwanie dla połączenia enkodera obrotowego A i B. Włącza TIM16 do filtrowania i wyłącza się. Ustawia limit czasu na 20 ms
Źródło: https://www.instructables.com/id/Mini-Oscilloscope/?utm_source=elektroda
„Przeglądając Internet natknąłem się na proste oscyloskopy do samodzielnego montażu, zwykle wykonane w oparciu o kompatybilną z Arduino płytkę, która prawie zawsze wykorzystuje program napisany w Arduino IDE. Problem polega na tym, że Arduino nie jest zbyt szybkie, a jego ADC też nie jest za dobre. Dlatego niektórzy dodają do płytki Arduino osobny ADC (przetwornik analogowo-cyfrowy – przyp. red.), co zwiększa złożoność sprzętu i całkowitą cenę” - pisze autor projektu.
„Pomyślałem, że można to zrobić łatwiej i taniej, ale nadal uzyskać rozsądną wydajność. Jestem fanem serii mikrokontrolerów STM32 firmy ST Microelectronics, więc wybrałem jeden z nich. Lubię też robić projekty z możliwie najmniejszym i często najtańszym mikrokontrolerem, ale wciąż z akceptowalną wydajnością” - opisuje swój projekt dalej. Przyjrzyjmy się zatem, krok po kroku, jak projekt ten został zrealizowany.
Krok 1: Mikrokontroler: STM32F030F4
Najmniejszy mikrokontroler STM, jaki jest dostępny, to STM32F030F4, zwykle sprzedawany w serwisie eBay w postaci płytki rozwojowej. Wewnątrz tego mikrokontrolera znajduje się przyzwoity ADC, potrafi wykonać konwersję w czasie 1 µs. Wyniki konwersji można odczytać z stosownego rejestru, ale co jest bardzo ważne w tym przypadku, są one również przesyłane przez DMA do dowolnego miejsca w pamięci, dzięki czemu proces jest bardzo szybki i nie wymaga angażowania procesora. W tym mikrokontrolerze znajdują się również zwykłe timery, ma SPI, I²C, RTC, USART i dwa Watchdogi. Oczywiście nie można używać wszystkich urządzeń peryferyjnych w tym samym czasie, ponieważ ta wersja STMa ma tylko 20 pinów, a niektóre z nich są używane do takich rzeczy jak zasilanie, reset, zewnętrzny oscylator, programowanie i wybór sposobu bootowania.
Aby zaprogramować mikrokontrolery z rodziny STM, potrzebny jest odpowiedni programator – na przykład STLink-V2, dostępny zarównow wielu hurtowniach elektronicznych, jak i na portalach aukcyjnych. Płytka z STM32F030F4 jest sprzedawana za mniej niż 3 euro, a STLink-V2 dostępny jest za podobną cenę. Jeśli posiadasz oficjalną płytkę rozwojową firmy ST Microelectronics, taką jak Nucleo-board, to jest na niej STLink-V2, który można z niej zdjąć. Autor tak właśnie zrobił i umieścił ten programator w niewielkiej, osobnej obudowie.
Krok 3: Lista części
* Obudowa (12 cm x 8 cm x 3 cm)
* Płytka uniwersalna (8 cm x 12 cm)
* Mikrokontroler STM32F030F4 na przejściówce TSSOP20 - DIP
* ST7735s - 1,8-calowy wyświetlacz TFT
* Bateria litowo jonowa
* HT7333 - stabilizator napięcia 3,3V
* Wzmacniacz operacyjny MCP6021
* Kwarc 8 MHz
* Enkoder obrotowy i pokrętło
* Przycisk zasilania
* Złącza bananowe
* Płytka ładowarki dla ogniw litowo-jonowych
Krok 4: Przygotowanie
Jak zwykle wszystkie projekty zaczynam od prototypu. Niestety płytka rozwojowa STM32F030F4 jest zbyt szeroka, aby można ją było zastosować na jednej płytce prototypowej. Jest też zbyt wąska, aby można go było używać na dwóch połączonych ze sobą płytkach prototypowych, jak pokazano na zdjęciu po prawej stronie. Rozwiązaniem jest zdjęcie jednego z rzędów goldpinów po jednej stronie.
Docelowo, z uwagi na to, że moduł ten jest zbyt duży, aby zmieścić go w niewielkiej plastikowej obudowie, autor wykorzystał sam układ na płytce uniwersalnej (patrz: zdjęcie po lewej), do którego dołożył wszystkie potrzebne elementy (kondensatory odsprzęgające, rezonator i stabilizator LDO). Sam układ w obudowie TSSOP20 jest stanowczo zbyt mały na konstrukcję w pająku, w związku z czym wykorzystana jest widoczna na zdjęciach płytka uniwersalna.
Autor nie planuje projektować płytki drukowanej dla urządzenia, ponieważ, jak sam zauważa, dla jednego układu nie ma to sensu.
Krok 3: Specyfikacja układu
Ekran TFT ma rozdzielczość 160 x 128 pikseli, co oznacza, że oś X (podstawa czasu) potrzebuje 160 próbek ADC. ADC potrzebuje minimum 1 mikrosekundę na próbkę. Użyteczna granica oglądania sygnałów na tak małym ekranie to około dziesięć okresów, a to przekłada się na 16 pikseli. W ten sposób łatwo oszacować możemy, że jeden przebieg zajmie 16 mikrosekund, co daje przebieg o częstotliwości 62500 Hz. To niewiele, gdy porówna się do prawdziwego oscyloskopu cyfrowego z pasmem 100 MHz, ale wystarczy do projektów audio, oświetlenia LED i eksperymentów z niskimi częstotliwościami.
Ostatecznie autorowi nie udało uzyskać się próbkowania sygnału na ADC z prędkością 1 000 000 próbek na sekundę, więc zdecydował się na 800 kHz, jako maksimum. Dlatego podstawa czasu wzrasta do 40 µs na działkę. Przy 5 podziałach na ekranie oznacza to 200 µs na cały ekran. Przebieg o częstotliwości 5 kHz wypełnia wtedy cały ekran, granica użyteczności wynosi około 50 kHz. Jeśli chodzi o drugi koniec zakresu rozdzielczości czasowej, to autor arbitralnie przyjął 200 ms na działkę, co przekłada się na 1 sekundę na cały ekran, wtedy sygnał 1 Hz wykorzystuje całą szerokość ekranu.
ADC przekształca napięcie na swoim wejściu w odniesieniu do napięcia 3,3 V (napięcia zasilania). Można wybrać rozdzielczości 12, 10, 8 i 6 bitów, a im niższa ona będzie, tym szybsza jest konwersja. Ponieważ ekran TFT ma oś Y (rozdzielczość pionową) równą 128 pikseli (7 bitów), nie jest konieczne, aby ADC miał większą rozdzielczość niż 7 bitów, ale rozdzielczość 7 bitów nie jest dostępna w układzie, więc jest ustawiona na 8 bitów. Najmniej znaczący bit zostanie po prostu odrzucony przez przesunięcie w prawo o 1 bit zmierzonej wartości.
Tłumienie sygnału jest wymagane, aby móc zmierzyć przebieg o amplitudzie większej niż 3,3 V. Jeśli istnieje potrzeba mierzyć mniejsze sygnały, to potrzebny jest wzmacniacz. Oznacza to albo konieczność posiadania zasilania symetrycznego, albo przesunięcie napięcia wejściowego do połowy napięcia zasilania. Ponieważ autor chciał używać tylko jednej baterii w systemie i maksymalnie uprościć urządzenie, wybrano metodę z offsetem. Oznacza to, że masa wejścia nie jest w ogóle połączony z masą urządzenia, a jest podłączona do napięcia offsetu, które jest wirtualną masą. Oznacza to również, że trzeba używać tego oscyloskopu zasilanego z baterii.
Oscyloskop ma tylko jedną czułość: 1 V na działkę. Do układu można by dodać np. tłumik z przekaźnikami lub cyfrowy potencjometr, a także regulowany wzmacniacz, jednakże to dosyć złożone i wymagałoby np. zwiększenia napięcia zasilania, ponieważ przekaźniki wymagają co najmniej 5 V do pracy. Cyfrowe potencjometry z kolei generują szum w sygnale. Oczywiście można do niego dodać wzmacniacz lub tłumik, wystarczy dołączyć go przed wejściem. Oscyloskop nie powinien powodować dużego obciążenia mierzonego obwodu. Większość oscyloskopów posiada rezystancję wejściową 1 megaohma, podobnie jest z tym. Można wykorzystywać go z sondą oscyloskopową 10x, co dodatkowo zwiększy zarówno zakres napięć wejściowych, jak i impedancję wejściową (dziesięciokrotnie), ale może być również wymagać dodania układu kompensacyjnego z trymerem. Autor nie testował swojego urządzenia z tego rodzaju sondą.
Analogowa szerokość pasma oscyloskopu musi być jak najwyższa - co do zasady, co najmniej 10 razy większa od częstotliwości, którą układ ma mierzyć. Wynika to z faktu, że np. fala prostokątna o częstotliwości zaledwie 10 kHz ma wiele harmonicznych i aby zobaczyć dziewiątą harmoniczną, trzeba mieć pasmo o szerokości co najmniej 90 kHz. Mówiąc inaczej, jeśli spróbujesz zobaczyć falę prostokątną o częstotliwości 10 MHz na oscyloskopie 10 MHz, zobaczysz falę sinusoidalną o częstotliwości 10 MHz. Tutaj wystarczyłoby pasmo 500 kHz, ale im więcej, tym lepiej. Autor użył wzmacniacza operacyjnego z pasmem równym 10 MHz - MCP6021, z tego prostego powodu, że miał go na stanie. Jeśli w swoim projekcie chcesz wykorzystać inny, to podstawowym wymaganiem jest możliwość pracy z napięciem 3,3 V.
Przy przesunięciu od 0 V do 3 V dopuszczalne napięcie na wejściu wynosi od -6 V do 6 V. Ale zawsze z maksymalną górną wartością napięcia 6 V! Czyli od -6V do 0V przez -3V do +3V aż do 0 V do 6 V. Wystarczająco dobre dla większości projektów z Arduino, a jeśli to konieczne, podwojenie rezystora wejściowego z 1 Mohm do 2 Mohm podwoi zakres napięcia wejściowego.
Poziom wyzwalania (trigger) można ustawić w dowolnym miejscu na ekranie, co oznacza, że można go ustawić w zakresie od prawie -6V do prawie 6V. Ale tak naprawdę nie ma to wiele wspólnego z napięciem, a jedynie z pozycją przebiegu na ekranie. Wyzwalanie działa tylko na narastających zboczach sygnału. Jeśli chcesz, możesz dodać w oprogramowaniu opcję wyzwalania również na opadających zboczach, ale sam autor uznał, że nie jest to konieczne.
Ostatnią specyfikacją urządzenia, jest określenia sposobu ładowania i rozmiaru ogniwa litowo-jonowego w systemie. Akumulator ładowany jest za pomocą małej płytki ze złączem mini-USB. Na początku autor chciał użyć baterii litowo-jonowej w rozmiarze 18650, ale była ona za duża, dlatego wybrano małe ogniwo podobne do baterii do telefonu. W swoim projekcie możesz użyć dowolnej baterii litowo-jonowej o nominalnym napięciu równym 3,7 V.
Specyfikacje:
* Rezystancja wejściowa 1 Mohm
* Czułość 1V/div.
* Podstawa czasu 200 ms/div – 40 µs/div.
* Napięcie wejściowe od -6 V – 6 V.
* Poziom wyzwalania dowolny poziom na ekranie
* Zasilanie z baterii
Krok 5: Sprzęt i schemat
Możemy teraz spojrzeć na schemat układu (po lewej). Jeśli wykorzystana jest płytka rozwojowa z STM32F030F4, to niewiele jest do zrobienia poza podłączeniem wyświetlacza, enkodera i wzmacniacza operacyjnego. Nawet stabilizator znajduje się na płytce. Autor nie korzystał jednak z płytki rozwojowej, dlatego też musiał wszystkie potrzebne elementy, takie jak oscylator i kondensatory filtrujące zasilanie, umieścić w układzie samodzielnie.
Krok 6: Zasada działania
Biblioteka TFT
Dla większości standardowych wyświetlaczy są gotowe do użycia biblioteki, jednakże autor nie mógł znaleźć takowej dla wybranego wyświetlacza, dołączonego do mikrokontrolera STM32. Biblioteka, którą przeportował na STM32, została stworzony dla Arduino. Po przeportowaniu biblioteki na układ STM32 okazało się, że wyświetlacz nie jest zbyt szybki. Na szczęście używa SPI i można podnieść prędkość tego interfejsu, co okazało się konieczne, aby utrzymać rozsądną częstotliwość odświeżania wyświetlacza. Mimo tego, konieczne okazało się maksymalne ograniczenie ilości przesyłanych danych. Przy ustawieniu najkrótszej podstawy czasu ekran odświeżany jest z częstotliwością około 15 do 20 Hz. Przy najdłuższej podstawie czasu (200 ms/div) częstotliwość odświeżania wynosi tylko 0,5 Hz! Przyjrzymy się temu ostatniemu czasowi bliżej w dalszej części artykułu.
Odczytywanie położenia enkoderów obrotowych okazało się najbardziej problematyczne, ale wynikało to z ich słabej jakości. Wykorzystane elementy wymagały filtrowania wejść, a i tak nie można ich przekręcać zbyt szybko, gdyż wtedy nie są dostatecznie responsywne.
Wykorzystanie DMA do zbierania danych z ADC jest jedną z najbardziej kluczowych operacji. Czas trwania 8-bitowej konwersji ADC równy jest około jednej mikrosekundzie. Przetwornik analogowo-cyfrowy jest uruchamiany przez zegar (TIM3), który działa w sposób ciągły i wysyła impulsy do ADC zgodnie z aktualnym ustawieniem podstawy czasu. Najniższa częstotliwość to 160 Hz. Zatem ADC wykonuje 160 konwersji na sekundę, wypełniając ekran o rozdzielczości 160 pikseli w ciągu 1 sekundy. Najwyższa częstotliwość to 800 kHz, więc ekran jest wypełniony 160 x (1/800 000) = 200 µs. Gdyby tylko wyświetlacz i oprogramowanie działały dostatecznie szybko, można by uzyskać częstotliwość odświeżania równą nawet 5 kHz (co nie było trudne w realizacji w przypadku oscyloskopów z kineskopami).
Dane z ADC są przesyłane do tablicy: adc_buffer[], znajdującej w pamięci. Odbywa się to za pomocą DMA, co oznacza, że procesor mikrokontrolera nie jest do tego w ogóle potrzebny - może kontynuować wszystko to, co w danym momencie robi. To sprawia, że przechwytywanie danych jest bardzo proste i szybkie. Gdy DMA jest gotowy z zaprogramowaną liczbą wartości, które ma przetransportować, ustawia flagę TC (transmisja zakończona) i wyzwala przerwanie. Samo przerwanie nie robi wiele, po prostu czyści flagę TC i ustawia zmienną o nazwie „token” jako sygnał do głównej procedury, informujący, że dane są gotowe do wyświetlenia. ADC kontynuuje konwersję, a DMA przenosi wyniki konwersji do zmiennej adc_buffer[]. Więc, bez względu na to, co jeszcze dzieje się wewnątrz mikrokontrolera, do adc_buffer[] dociera niekończący się strumień wartości z przetwornika analogowo-cyfrowego.
Wyświetlacz ma szerokość 160 pikseli, więc do wyświetlenia pełnego przebiegu potrzeba tylko 160 wartości. W rzeczywistości adc_buffer[] zawiera 320 próbek. Tak więc DMA przechowuje w nim 320 wartości, zanim wyzwoli przerwanie TC. Dzieje się tak, ponieważ wyzwalanie odbywa się programowo. A ponieważ jest bardzo mało prawdopodobne, że pierwsza wartość w adc_buffer[] to miejsce, w którym powinien zostać uruchomiony trigger. Konieczne jest znalezienie miejsca, w którym znajduje się ten punkt. Odczytywanych jest więc 320 wartości i w pierwszych 160 z nich wyszukiwany jest rzeczywisty punkt wyzwolenia. Realizacja tego jest dosyć prosta – punkt wyzwolenia jest wyszukiwany w adc_buffer[] poprzez sprawdzenie, czy każda kolejna wartość jest równa wartości wyzwalacza en, a następna wartość jest tuż nad nią. Działa to całkiem dobrze, ale potrzebny jest bufor o rozmiarze większym niż rzeczywisty rozmiar wyświetlacza. Eksperymentalnie ustalono, że 320 próbek jest wartością optymalną.
To również jest powód, dla którego częstotliwość odświeżania w ustawieniach niższej podstawy czasu jest wolniejsza niż można by się spodziewać. Jak wspomniano wcześniej, przy używaniu ustawienia 200 ms/div, jeden ekran pełen danych zajmuje 1 sekundę, ale ponieważ wykonywana jest podwójna liczba konwersji, zajmuje to 2 sekundy. Na szybszych ustawieniach podstawy czasu ten problem nie jest tak bardzo zauważalny.
Krok 7: Kod programu
Kod programu składa się z wielu plików. Nie będziemy prezentowali poniżej listingów poszczególnych z nich – zajęłoby to stanowczo za dużo miejsca. Omówione zostaną tylko najważniejsze ich elementy. Poszczególne pliki można pobrać na stronie z projektem (patrz: link na końcu artykułu).
Main.c
Ponieważ konwersje nigdy się nie kończą, wartości w adc_buffer[] będą stale nadpisywane, przy najszybszym ustawieniu podstawy czasu będzie to miało miejsce dla wszystkich wartości co 400 mikrosekund. Aby zapobiec wyświetlaniu błędnych danych, pierwszą rzeczą, którą wykonuje główna pętla programu, jest skopiowanie adc_buffer[] do display_buffer[]. Nieco później kopiuje również te same dane do erase_buffer[]. Dzieje się tak, ponieważ wymazywanie całego ekranu trwa bardzo długo, dlatego też zamiast zmazywać cały ekran, program nadpisuje piksele poprzedniej klatki, wyświetlając jednocześnie kolejny piksel nowej klatki (tzn. ten z przebiegiem).
Reszta programu to generalnie kosmetyka. Wyświetlana jest siatka poziomych i pionowych linii, aktualne wartości czułości i podstawy czasu. Linia zerowego napięcia jest delikatnie jaśniejsza niż wszystkie inne i porusza się z przesunięciem. W miejscu obecnego poziomu wyzwalania wyświetlana jest mała linia. To, co w danym momencie jest aktywne na enkoderze obrotowym - podstawa czasu, przesunięcie lub poziom wyzwalania, jest pokazane na żółto.
SystemClock_Config Aby uzyskać największą prędkość z mikrokontrolera, autor zwiększał zegar znacznie powyżej oficjalnego maksimum mikrokontrolera (48 MHz), do 64 MHz, a nawet do 80 MHz. Wszystko działało bez problemów nawet przy 80 MHz, ale nie chciałem utrzymywać tej częstotliwości tak wysoko, więc została ona zredukowana do 64 MHz. Jest to nadal daleko poza specyfikacjami, więc mikrokontroler może działać nieprawidłowo. To powiedziawszy, wątpię, że spowoduje to kłopoty, ponieważ według specyfikacji producenta, będzie on działać w każdych warunkach, od 2,4 V do 3,6 V zasilania i od -40°C do 105°C. Oscyloskop stoi na biurku w 25°C i przy stabilnym zasilaniu 3,3V.
MX_ADC_Init W mikrokontrolerze pozostał jeszcze jeden pin, zadbałem o to, aby był to PA1, ponieważ ten pin może być użyty jako drugie wejście ADC. Możliwe byłoby zrobienie w ten sposób oscyloskopu dwukanałowego, ale autor tego nie próbował (jeszcze). W tej chwili PA1 jest skonfigurowane jako wyjście, którego używa do debugowania. Zegar ADC to zegar systemowy podzielony przez 4, to jest 16 MHz i to również jest powyżej specyfikacji producenta. Nie powinno być wyższe niż 14 MHz, a mimo wszystko działa. Każda konwersja rozpoczyna się na zboczu narastającym sygnału TRGO z TIM3 (patrz poniżej). ADC nie generuje żadnych przerwań, o te dba DMA po 320 próbkach.
MX_SPI1_Init Interfejs ten pracuje z częstotliwością 16 MHz z wyświetlaczem TFT ST7735.
MX_TIM3_Init Ten timer jest odpowiedzialny za rozpoczęcie konwersji ADC. Działa z częstotliwością 64 MHz i przy każdym UPDATE (przepełnieniu) wysyła impuls do ADC przez wyjście TRGO. Poprzez zmianę maksymalnej wartości timera w rejestrze zwanym AutoReloadRegister (ARR) zmienia się częstotliwość impulsów TRGO (podstawa czasowa). Ta zmiana jest wykonywana w procedurze przerwań TIM6 (patrz poniżej).
MX_DMA_Init Po prostu włącza swoje przerwania w NVIC.
MX_TIM14_Init Ten timer generuje falę prostokątną na PB1 o częstotliwości nieco większej niż 100 kHz i zmiennej szerokości impulsu. Filtr dolnoprzepustowy wykonany z rezystora 1k i kondensatora 10uF przekształca to w napięcie przesunięcia (offset) dla wzmacniacza operacyjnego.
MX_TIM16_Init Realizuje filtrację sygnału z enkodera obrotowego i zmniejsza wartość limitu czasu. Ten timer jest normalnie wyłączony i uruchamiany tylko wtedy, gdy na enkoderze obrotowym zostanie wykryty ruch. Wykrywanie ruchu odbywa się poprzez przerwania EXTI w GPIO podłączonym do enkodera obrotowego. Podczas pracy ten licznik czasu wytwarza 1000 przerwań na sekundę. Kiedy limit czasu osiągnie zero, ta procedura przerwania wyłącza się i ponownie włącza przerwania EXTI.
MX_GPIO_Init Ustawia linie GPIO jako wejście i wyjście, w zależności od potrzeby i dba o włączenie EXTI (zewnętrznego wejścia przerwania) na niektórych wejściach mikrokontrolera.
STM32F0XX_IT.C
DMA1_Channel1_IRQHandler czyści swoją flagę TC, a następnie ustawia token na 1. To wskazówka dla programu w main.c, że są do pobrania nowe dane, które muszą zostać wyświetlone.
TIM16_IRQHandler to funkcja obsługująca przerwanie timera TIM16 - określa, czy przycisk enkodera obrotowego jest wciśnięty i czy enkoder obrotowy jest obracany zgodnie z ruchem wskazówek zegara, czy przeciwnie do wskazówek zegara. Następnie ustawia ustawienia podstawy czasu dla TIM3, ustawienia przesunięcia w TIM14 i poziom wyzwalania. Ustawia również kolor, w jakim opcje powinny być wyświetlane. Ostatnią rzeczą, jaką ta funkcja wykonuje, jest wyłączenie się po krótkim czasie (timeout) i ponowne włączenie przerwań dla wejść zewnętrznych - EXTI (patrz poniżej).
EXTI2_3_IRQHandler to przerwanie dla przycisku obrotowego (enkodera). Włącza i wyłacza TIM16 użyty w filtrze tego wejścia. Ustawia limit czasu na 20 ms.
EXTI4_15_IRQHandler to z kolei przerwanie dla połączenia enkodera obrotowego A i B. Włącza TIM16 do filtrowania i wyłącza się. Ustawia limit czasu na 20 ms
Źródło: https://www.instructables.com/id/Mini-Oscilloscope/?utm_source=elektroda
Cool? Ranking DIY