Skocz do zawartości

Leoneq

Użytkownicy
  • Zawartość

    145
  • Rejestracja

  • Ostatnio

  • Wygrane dni

    10

Leoneq wygrał w ostatnim dniu 8 listopada

Leoneq ma najbardziej lubianą zawartość!

Reputacja

194 Mistrz

2 obserwujących

O Leoneq

  • Ranga
    5/10
  • Urodziny 23.02.2004

Informacje

Ostatnio na profilu byli

1005 wyświetleń profilu
  1. Słowem wstępu, trzeba przyznać że tytuł artykułu sam w sobie jest dość kontrowersyjny. Dlaczego autorowi przyszło na myśl chwalić się najgorszą kartą graficzną na świecie, i to jeszcze drugą? Już spieszę z odpowiedzią: jest to moduł większego projektu, który jest dla mnie - autora - swego rodzaju wyzwaniem, dla czytelników - ciekawostką. A sprawa rozchodzi się o komputer ośmiobitowy. Jak zatem mogłaby wyglądać najgorsza karta graficzna? GeForce GT 210? Nie, pewnie jakiś radeon. Może zintegrowana grafika? W dobie XXI wieku już mało kto pamięta karty jeszcze starsze - stosowano niegdyś karty na szynach ISA, jak Trident TVGA-9000i (którego akurat posiadam). Karty te generowały obraz w rozdzielczości 640x480px, a przynajmniej przy trybie tekstowym. Przy trybie tekstowym, czyli karta generowała tylko i wyłącznie znaki, czasem tylko jednego koloru - lecz miała wtedy "fizycznie" największą rozdzielczość. Tryb graficzny umożliwiał kontrolę obrazu co do piksela i zmieniać nawet jego kolor, lecz wymagało to większej ilości zużycia RAMu - stąd tryby graficzne często były niższej rozdzielczości. Nie są to jednak karty najgorsze. Zainspirowany filmem Bena Eatera o jego "najgorszej karcie graficznej", postanowiłem samodzielnie zrobić takową kartę. Przyda się ona do zbudowania komputera ośmiobitowego, który już wcześniej zacząłem realizować, a że jeszcze przyszły mi płytki do rozlutu i były wakacje, był to całkiem dobry pomysł. Jak zatem prezentuje się karta? Źle. Na pewno teraz nasuwa się mnóstwo pytań czytelnikowi - więc już spieszę z opisem całości. Proces tworzenia karty zacząłem od postawienia założeń - na pewno nie chcę używać układów mocniejszych od Z80 (dla przykładu atmega 328p jest 16x mocniejsza od Z80), ponieważ nie ma to sensu - przerost formy nad treścią. Nie potrzebuję także trybu graficznego, chcę tylko terminal - więc tryb tekstowy mi wystarczy. Obraz chcę wyświetlać na monitorze kineskopowym (docelowo), więc chcę generować sygnał VGA. Kolor by się przydał, lecz priorytetem jest samo wyświetlanie znaków. Tak więc wiedziałem co chcę zrobić, musiałem się zastanowić jak. Najsampierw - zobaczyłem jaki sygnał należy generować. Tak więc, sygnał VGA to głównie 5 sygnałów - cyfrowa linia synchronizacji poziomej i pionowej, oraz 3 analogowe dla kolorów: czerwony, zielony i niebieski. Sygnał ten był opracowywany dla monitorów CRT, który obraz był generowany przez działko elektronowe, i sygnały cyfrowe tym działkiem sterują. Działko to pluje linię (display), po czym potrzebuje jeszcze chwili czasu (front porch), po czym - można powiedzieć - "synchronizuje się" z sygnałem (sync pulse), i wraca na początek linii (back porch). Całkiem podobnie jest dla linii synchronizacji pionowej. Kolory, jak już wcześniej wspomniałem, są sygnałem analogowym. Oznacza to, że im większe napięcie damy na ten sygnał, tym kolor będzie mocniejszy, a przy użyciu 3 sygnałów R, G i B - można te kolory łączyć. Jeżeli obraz nie jest wyświetlany, linie te powinny być podciągnięte do masy układu. I to w skrócie byłoby tyle - jeszcze rzut oka na czasy poszczególnych etapów sygnałów: Jak widać, aby uzyskać obraz 800x600 pikseli trzeba całość taktować aż czterdziestoma megahercami. Bramki logiczne, a i też niektóre mikrokontrolery nawet nie obsługują takich dużych częstotliwości - więc zdecydowałem się obniżyć ten zegar do 20MHz, co pozwoli uzyskać obraz 400x300 pikseli. Czasy zostają te same - lecz liczba pikseli się zmieni, co będzie bardzo ważną informacją w dalszej części artykułu. Wracając do układu Bena Eatera: on te czasy generował przy użyciu trzech liczników 4bit, potem porównywał konkretną liczbę z tych liczników, aby następnie sterować przerzutnikiem R/S dla linii synchronizacji. Ja uznałem "ale czemu tak komplikować sobie życie", zastosowałem jeden licznik binarny 12bit, a liczby porównywałem tylko logicznymi "jedynkami". Tak zatem wyglądają pierwsze dwa arkusze schematów: Pierwszy arkusz pokazuje kwestię zasilania, oraz złącz (karta będzie karmiona pięcioma woltami z zasilacza). Na złączu 40pin zaś znajdziemy ośmiobitową szynę danych, szesnastobitową szynę adresów, sygnały do wybierania jednego z 8 urządzeń, oraz piny WR i RD, odpowiadajace za operację zapisu i odczytu na wybranym urządzeniu. Złącze to łatwo można zdobyć z starych nagrywarek na szynie IDE, tasiemkę też - którą akurat kupiłem i skróciłem. Następny arkusz pokazuje licznik poziomy. Sam zegar na początku zrealizowałem przez układ 74HC14, z kwarcem 20MHz. Działał tylko po przyłożeniu palca (lub parówki) do styków kwarcu. Zamiast tego, generator sygnału 20MHz napędza licznik, który zlicza kolejno liczby w postaci binarnej. Liczby te sprawdzają bramki NAND, które interesują konkretnie 4 liczby: 400, 420, 484 i 528. Liczby te wziąłem z powyższej tabelki - a konkretnie po pierwszych 400 pikselach ustawiamy przerzutnik RS, dla wew. sygnału do wyświetlania pikseli. Sygnał HBLANK jest aktywny wtedy, kiedy nie powinny być wyświetlane żadne piksele. Następnie przy 420 pikselach ustawiamy kolejny przerzutnik, tym razem dla sygnału synchronizacji linii. Po 64 pikselach licznik ten jest resetowany, a przy 528 pikselu resetowany jest przerzutnik do wyświetlania pikseli, oraz resetowany sam licznik - następuje nowa linia, co jest też sygnałem taktującym dla zegara pionowego (sygnał jest przepuszczany przez bramkę NOT, ponieważ linia resetu i zegara jest aktywna w stanie wysokim). Tutaj też dość ważna informacja: na liczbę binarną składają się zarówno zera, jak i jedynki. Dlaczego zatem porównuję same jedynki? Każdy inny sygnał (który powinien być zerem) musiałbym podpiąć do bramki przez NOT, co by skutecznie powieliło ilość użytych układów. Porównywanie samych jedynek umożliwiło ominięcie tego, lecz "wybrana" liczba będzie występowała kilka razy. Nie ma to znaczenia i tak, ponieważ przerzutnik i tak został już ustawiony/zresetowany, a licznik jest potem resetowany. Tutaj jednak popełniłem kilka błędów. Do samej noty licznika (a nie samego pinoutu) zajrzałem duuużo później, gdzie przeczytałem, że liczniki te obsługują do 12MHz i to przy zasilaniu 15V (!) - tłumaczyło to wiele anomalii, lecz co ciekawe - zastosowanie liczników tych samych, lecz w wersji SMD zniwelowało wszelkie te anomalie. A że nie miałem pod ręką przejściówek z SO16 na DIP16, zrobiłem te śmieszne pajączki które widać na zdjęciu. Ważne że działa. Jest też jeszcze jeden błąd, który i ja popełniłem, i pan Eater popełnił: w informatyce zawsze się liczy od zera. Myślę że po wytłumaczeniu poprzedniego licznika, tutaj nie będzie niespodzianek. Jednak z racji tego, że na sygnał taktujący dostajemy 37.(87)kHz (zgodnie z tabelką), szukamy także pikseli podanych w tabelce: 600, 601, 605, 628. Technicznie rzecz biorąc pozwalałoby to na wyświetlanie obrazu 400x600px, lecz! Taka rozdzielczość byłaby troszkę niestandardowa, a litery byłyby mocno spłaszczone - przez co nieczytelne. Tak zaprojektowany i złożony układ prezentował się następująco: Generował on tylko i wyłącznie kolorowy ekran, lecz to już było coś. Problem zaczął w kwestii RAMu i ROMu. Zaczynając od RAMu, uznałem, że najlepiej będzie dodać licznik ósemkowy, który w połączeniu z bramkami AND, będzie po kolei sprawdzał każdy bit występujący na szynie danych pamięci ROM. W praktyce efekty okazały się co najmniej marne: Powinien się tutaj wyświetlać jeden znak, wykrzyknik, a dostałem... to. Potem spróbowałem czegoś podobnego lecz zamiast licznika ósemkowego - demultiplekser 74LS138. Bezskutecznie. Postanowiłem więc "wbudować" ten element karty do samej pamięci, marnując ją przy tym ogromnie: ponieważ obraz jest pobierany bezpośrednio z ROMu, wykorzystując przy tym jeden bit. A jest to pamięć ośmiobitowa. Tak więc zastosowany układ M27C1001, który jest pamięcią UV EPROM o pojemności 1Mbit, okazał się pomieścić "tylko" 4 tablice znaków po 255 znaków. A wygląda to tak: Linie adresu A0-A7 to wybór znaku, A8-A11 wybór linii, A12-A14 wybór kolumny, A15 - discord light theme, A16 - CP438, o którym mowa za chwilę. Pamięć samą w sobie zaprogramowałem wcześniej zrobionym i przerobionym programatorem, i mogę jeszcze powiedzieć, że mimo wersji o czasie odczytu do 100ns, wszystko działa sprawnie (a powinien być niższy od 50ns). Nawiązując jeszcze do samego wykonania płytki: przy projektowaniu schematu, nie wiedziałem do końca czy on zadziała. Dlatego nie trawiłem żadnych płytek, lecz całość wykonałem na płytkach prototypowych. Zasilanie doprowadzam górą, kynarami; sygnały zaś doprowadzam drutem 0.12mm z silniczka, który już się prawie skończył... Wracając: akurat się tak złożyło, że znak będzie duży na 8x16 (2³ x 2⁴) pikseli, co przy ekranie 400x300 pozwoli na wyświetlanie 50x18 znaków, nawet nieźle jak na gołe bramki logiczne. Po podpięciu pamięci RAM obraz wyglądał tak: Są to losowe znaki, co wynika z tego że pamięć RAM przy włączeniu zawiera losowe dane. A, nie powiedziałem jeszcze o samej tablicy znaków. Mocno inspirując się klasyczną, dosową czcionką, tak wygląda cała tablica znaków: Wykorzystując dodatkowy bit adresu pamięci EPROM, pozwoliłem sobie na zapis nowej tablicy znaków: CP438, gdzie standardowe znaki alfabetu są zastąpione przez Standardowy Alfabet Galaktyczny. Następnie w Processingu zrobiłem prosty program konwertujący piksele obrazu w tablicę zmiennych typu byte, jak docelowo miały być zapisywane znaki, a następnie tablicę tę skopiowałem do programatora. Niestety, nie opracowałem jeszcze zapisu z komputera, lecz na pewno będę musiał prędzej czy później zrobić - na chwilę obecną, programy muszą być mniejsze od pamięci samej atmegi. Więc skoro pamięć RAM i ROM już działała, to czego do szczęścia jeszcze trzeba? Ano, potrzebujemy coś zapisać do tej karty! I tutaj zaczęły się prawdziwe schody. Problem polega w tym, że pamięć RAM jest ciągle odczytywana przez timery. Przerwanie ich będzie skutkowało też przerwaniem wyświetlania obrazu. No i niestety, ale tak jest, ponieważ nie mam pomysłu jak inaczej wymyślić zapis. Tak więc, do odcinania timerów od RAMu służą tutaj bufory trójstanowe - 74HC245 i 244 (takie miałem pod ręką). Ale, zwykłe linie adresów i danych też trzeba było odciąć - więc musiałem zastosować tutaj 8 układów po 16 nóżek, więc lutownicą się namachałem. Na moje szczęście, powyższy schemat zadziałał za pierwszym razem. Jednak przez rozbieg, pomiędzy pamięć ROM i RAM dałem także bufor - jeżeli wstawiłbym tam zatrzask, to w najszczęśliwszym wypadku (zapis krótszy niż 0.4us) nie byłoby nic widać. Jednak zatrzask skutecznie by pozwolil zredukować liczbę anomalii, ale mimo wszystko tragedii nie ma przy zastosowaniu "zwykłego" bufora. Jak widać, proces zapisu jest aktywowany tylko, kiedy i linia zapisu i linia wyboru urządzenia są w stanie niskim - jest to realizowane bramką OR. Tak więc bufory są, wszystko jest, ale... kartę trzeba było jakoś sprawdzić. Jako, że płytkę z samym Z80 będę lutował później, na razie zapis przeprowadza Arduino MEGA (jeszcze raz: ono służy TYLKO do testowania karty, nie do generowania obrazu!), i karta działa zadziwiająco dobrze: Obraz jest monochromatyczny; zastosowanie palety kolorów byłoby ciekawym wyzwaniem. Kolor wybiera się przełącznikiem DIP przy złączu VGA. Ciekawa rzecz też była kiedy na początku źle zapisałem pamięć, na odwrót... Niestety, występuje także kilka anomalii. Lewy brzeg ekranu jest okupowany przez zgliczowane piksele, przez które najlepiej cały obraz jest przesunąć o znak w prawo. Pamięć ROM także wypaliłem w niezbyt komfortowych dla niej warunkach, więc piksele nie są do końca dobrze wypalone. Ciekawą anomalią są też te paski z tyłu, które występują tak trochę sinusoidalnie? Są stałe, rezystory pulldown i pullup nic nie dają... ale jeżeli sygnał koloru prześle przez płytkę stykową, linie magicznie znikają! Kiedy dostanę porządny oscyloskop (obecnie mam przystawkę hanteka, która mierzy tylko w kHz), głębiej to zbadam. Wiem też, że karta jest mocno wrażliwa na zakłócenia, np. z kabla USB czy oscyloskopu. Cały kod i obliczenia (dokumentację) udostępnię dopiero kiedy je sobie uporządkuję, bo kod arduino jest bardzo nieoptymalny, a obliczenia rozsiane po notesie... jak to pozbieram, wyślę na GitHuba, a całość opiszę po angielsku. EDIT: link do githuba Tak czy siak; karta jest gotowa, i może pracować razem z Z80, lub jako osobny moduł dla takiego Arduino (jak wiemy z poprzedniej konstrukcji, atmega nie do końca sobie radzi z VGA...) Wniosek: zostawić stare układy TTL, kupić płytkę z FPGA i się uczyć z forbotowego kursu. discord light theme
  2. Kiedy zaczyna się zabawę z lampami, czy to do budowy radia, czy wzmacniacza audio, należy załatwić sobie dobre zasilanie. Zwykle to zasilanie jest wbudowane w urządzenie, więc co nowe urządzenie lampowe (czy to radio, czy wzmacniacz) musiałbym kupować nowy transformator. To czego potrzebowałem to zasilacz, jak najbardziej uniwersalny (układy lampowe buduję bardziej w celach edukacyjnych niż praktycznych), co pozwoli mi na wykorzystanie tylko jednego transformatora do wszystkich moich projektów lampowych. Chciałem, aby zasilacz był podobny do szkolnych zasilaczy laboratoryjnych, i był możliwie bezpieczny. Zacząłem sobie od rozrysowania schematu, a zarazem obliczeń: Jak widać, wiele do szczęścia nie potrzeba. Jest to jeden z najprostszych zasilaczy jaki można zrobić - i to wykorzystując elementy głównie z odzysku. Zaczynając od lewej: zasilanie do układu jest dostarczane przez popularne złącze IEC. Faza jest odcinana pierwszym wyłącznikiem, nie można było także zapomnieć o bezpieczniku. Transformator jaki wykorzystałem to TS50/5 polskiej firmy Zatra, który dostałem od znajomego nauczyciela. Posiada on 3 uzwojenia wtórne - 2x 270v, 6.3v i 4v (na schemacie dwa ostatnie powinny być rozłączone). Jeżeli ktoś nie ma takiego transformatora, może wyprostować bezpośrednio napięcie gniazdkowe, ale tylko przy zastosowaniu transformatora o przekładni 1:1. Będzie on stanowił izolację galwaniczną. Napięcie żarzenia wielu lamp to 6.3v. Niektóre lampy grzeje się wyższym napięciem, którego ten transformator niestety nie dostarcza, więc musiałem się zadowolić tymi ponad 6 woltami. Uzwojenie to od razu zasila małą żaróweczkę sygnalizującą pracę zasilacza. Dla jasności: lampy można żarzyć napięciem zarówno stałym, jak i przemiennym. Napięcie anodowe lamp (to wysokie, niebezpieczne) jest prostowane scalonym mostkiem Greatza (z zasilacza komputerowego). Wyprostowane napięcie jest wygładzane przez filtr RC, a następnie ograniczane bardzo prostym układem z wykorzystaniem tranzystora MOSFET. Ten także jest z odzysku, posiada niską rezystancję w stanie przewodzenia, wytrzymuje dużą moc - i co najważniejsze - posiada zabezpieczenie w postaci diod zenera między bramką a źródłem. Działają one jako zabezpieczenie przeciwprzepięciowe - w teorii, potencjometr tutaj służy jako dzielnik napięcia, a rezystor obniża napięcie (gdyż mosfet to element sterowany napięciem). Jeżeli nastąpi tam przebicie, tranzystorowi na szczęście się nic nie stanie. Ale dlaczego w ogóle ten tranzystor dałem? Nie wszystkie lampy mają jedno napięcie anodowe, więc napięcie to można ograniczać - w czym pomaga panelowy woltomierz wskazówkowy, z posobnikiem w szeregu. Trudno znaleźć cyfrowe woltomierze na tak wysokie napięcie. Na samym końcu układu - jest przełącznik, który pozwala na zasilenie docelowego urządzenia, lub poprowadzenie napięcia przez rezystory mocy. Mają one 2 zastosowania: pozwalają dobrać napięcie, kiedy zasilacz nie jest zewnętrznie obciążony, oraz rozładowują kondensator, co jest bardzo ważną rzeczą. I najważniejsza rzecz - cała obudowa jest porządnie uziemiona. Aby wyrównać potencjały, masę napięcia anodowego także uziemiłem. Obliczenia skomplikowane nie są - na początku potrzebowałem znać moc urządzenia, aby wiedzieć jaki bezpiecznik dobrać. Z racji tego (a przynajmniej tak myślę, może błędnie myślę ), że poza transformatorem nie ma elementów LC (po stronie napięcia przemiennego), cos fi jest bliski 1, a więc moc bierna jest niewielka - dlatego jej nie brałem pod uwagę (równania 1.1-2). Nie brałem pod uwagę także strat na samym transformatorze. Następnie obliczyłem rezystor jaki jest potrzebny do filtru RC, oraz prąd i napięcie jakie będą po wyprostowaniu. Miałem na uwadze, że napięcie w gniazdku wynosi 240v, a transformator był na 220v, i że napięcie to może być wyższe (równania 3.1-3.4). Na samym końcu, korzystając z praw Ohma i Kirchoffa, obliczyłem posobnik dla woltomierza. Oryginalnie wyskalowany był na 0-40v, więc zmieniłem to na 0-500v. Woltomierz miał rezystancję 48kΩ. Starą skalę zeskanowałem, w gimpie przerobiłem, i wydrukowałem na papierze samoprzylepnym. Po zebraniu wszystkich potrzebnych elementów, wziąłem się za obudowę. Trochę mi to zajęło, ponieważ prawie wszystko robiłem ręcznie. Bazą obudowy jest aluminiowy radiator z starego zasilacza impulsowego - do którego z boku są przykręcone blachy aluminiowe, a u góry profil. Wszystko ze złomu. Rączka została wykonana z stali nierdzewnej. Najwięcej czasu zajął front obudowy, do którego wykonałem rysunek, a następnie wyfrezowałem blachę zgodnie z tym rysunkiem. (Niestety zdjęć z tego procesu nie mam). Następnie zasilacz zacząłem lutować - oczywiście wszystkie przewody są zakończone termokurczkami, wszystko jest porządnie przykręcone, a obudowa działa jak świetny radiator. No i podsumowanie. Zasilacz działa, bez obciążenia napięcie anodowe wynosi maks. 428v, oraz 375v z obciążeniem. Maksymalny prąd wynosi 48mA, co można zwiększyć, podłączając równolegle drugą połówkę uzwojenia. No i - zgodnie z przewidywaniami, napięcie faktycznie jest wyższe. Napięcie żarzenia wynosi obecnie 6.8v, ale jest to dość nieznaczna różnica. Rozładowanie kondensatora trwa ok. 5 sekund, z włączonym obciążeniem. Myślę że bez obciążenia, nawet w ciągu doby nie rozładowałby się do zera. Przy budowie jednak popełniłem kilka błędów: górny łącznik, łączący front z górą obudowy, ma źle umiejscowione utwory, przez co górna blacha trochę wystaje. Żarówka (jak i kondensator) trzyma się na kablach, co nie jest dobrą praktyką. Mogłem także dać więcej zabezpieczeń, np. ograniczenie prądu, zabezpieczenie przeciw odwrotnej polaryzacji, czy przeciw zwarciowe. Rezystor filtru także jest mocno przedobrzony - moc którą pobierze wynosi mniej niż 1 wat, i bardziej on służy za element montażowy. Mimo wszystko, zasilacz działa, poza obciążeniem nic się nie grzeje. Projekt tym samym uznaję za udany.
  3. Sprawdź konsolę, czy może skrypt palety kolorów nie wywala. Jeżeli w ogóle nie działa, spróbuj uruchomić go na komputerze - będziemy już coś wiedzieli. Jak na komputerze się nie uruchomi, to albo kombinujesz dalej co masz źle, albo szukasz innego skryptu na paletę.
  4. Z tego co widzę, highByte(); i lowByte(); nie będzie działać, jeżeli szyna adresów będzie szersza od 16 linii (użyjemy zmiennej większej od int16_t, więc wyślemy tylko 8 bitów po lewej i 8 po prawej). Przesunięciem bitowym (>> i <<) można obsłużyć większe dane. Ale do małych pamięci jak najbardziej można użyć tamtych funkcji, chociaż przesunięcie bitowe mi się wydaje lepszym rozwiązaniem.
  5. 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
  6. Spróbuj te funkcje zdeklarować przed funkcją setup - przenieś je na górę, i powinno być ok.
  7. Silniki obecnie są zwykłe z przekładniami xd W artykule chyba nawet link jest do nich. Musiałbym chyba sam silnik wymienić, albo w przekładnię zajrzeć. Jak znajdę chwilę to jeszcze się pobawię z tymi napędami, najwyżej wymienię na te żółte 48:1.
  8. Linear advance to taka technika jakby, która koryguje przyspieszenie i zwolnienie karetki (na początku kreska filamentu z dyszy jest cieńsza, na końcu grubsza). Funkcja ta podaje na początku zatem więcej filamentu, na końcu odrobinę mniej. Każda drukarka ją może mieć, która ma marlina.
  9. Właśnie problem jest taki, że jeden silnik jest normalny, a drugi "załącza się" od pewnego napięcia - niżej się nie załączy. Próbowałem różne progi ustawiać, skutkowało to zbyt nagłymi ruchami robota.
  10. W planach mam zrobić "ultimate useless box" - z wyświetlaczem, drugim serwem na klapkę, a nawet napędem do uciekania. Ale to bardzo odległe plany.
×
×
  • Utwórz nowe...