https://obrazki.elektroda.pl/5502422400_1590145798_thumb.jpg Witajcie moi drodzy Chciałbym zaprezentować tutaj drugą wersję mojego Arduino VGA Shield (nakładki na Arduino UNO generującej jednokolorowy obraz VGA i czarno-biały PAL/NTSC) - tym razem wykonaną za pomocą elementów SMD, i z dodatkowymi układami na pokładzie (expander portów MCP23016 oraz dwie kości EEPROM AT24CM02). Opis pierwszej wersji tego shielda znajdziecie tutaj: https://www.elektroda.pl/rtvforum/viewtopic.php?p=18496229#18496229 Co to za Arduino Shield? W dużym skrócie mój shield jest nakładką na Arduino UNO która pozwala bardzo łatwo generować jednokolorowy obraz na monitor VGA oraz obsługiwać klawiaturę PS/2. Opcjonalnie też dostępne jest wyjście sygnału PAL/NTSC dla telewizorów. Shield korzysta z biblioteki 'ASCII Video Terminal' dla PIC32MX250F128B autorstwa geoffg. Arduino UNO komunikuje się z tym shieldem poprzez UART na wybranych przez nas za pomocą lutowanej zworki pinach. Dodatkowo na pokładzie (nowość w tej wersji!) znajduje się: - expander portów MCP23016, dzięki czemu zyskujemy aż 16 dodatkowych IO! - dwie kości EEPROM AT24CM02, dzięki którym zyskujemy aż 512KB pamięci nieulotnej do własnych projektów! Obie nowości znajdują się na magistrali I2C od Arduino. Poniżej szczegółowo opiszę cały projekt, umieszczę proste przykłady użycia nowości na płytce a na koniec dam bardziej zaawansowane demka korzystające z poszczególnych funkcjonalności shielda. Projekt Arduino VGA Shield SMD Niniejszy projekt jest zasadniczo aktualizacją jego poprzedniej wersji która była wykonana w THT (montaż przewlekany): https://obrazki.elektroda.pl/2294289300_1588796118_thumb.jpg W obecnej wersji większość elementów jest w obudowach SMD, dodatkowo dzięki zaoszczędzonemu miejscu udało mi się na płytkę dodać: - złącze USB od PIC32 (do aktualizacji softu poprzez bootloader; może to dać też w przyszłości możliwość łatwego użycia tego shielda bez Arduino, ale na razie brakuje na PCB w tym celu regulatora 3.3V) - dwie kości pamięci EEPROM AT24CM02 na magistralę I2C od Arduino (standardowo piny 4 i 5) - 16-bitowy expander portów MCP23016 na magistralę I2C od Arduino (jak wyżej; piny 4 i 5) - rezystory pull-up na piny 4 i 5 (jak wyżej; magistrala I2C Arduino) W ten sposób obecna wersja wyszła tak: https://obrazki.elektroda.pl/8546300900_1588796382_thumb.jpg Całość open hardware i do pobrania poniżej. Schemat płytki w PDF: 1030456 Schemat płytki w PNG: 1030457 Pełny projekt płytki w Eagle (.sch + .brd): 1030455 Pliki Gerber które wyeksportowałem z Eagle (te same, których użyłem przy zleceniu wykonania gołego PCB płytkarni): 1030453 Tym razem obyło się bez żadnych błędów na płytce. Płytki standardowo zamówiłem w Chinach (10 sztuk - ktoś chętny na kilka?) a jak przybyły to wziąłem się do lutowania. Lutowanie Arduino VGA Shield w wersji SMD Opiszę tutaj szczegółowo jak zlutowałem tę płytkę w warunkach domowych. Na początek przygotowałem wszystkie elementy: https://obrazki.elektroda.pl/8220448100_1588804224_thumb.jpg https://obrazki.elektroda.pl/6139364300_1588804228_thumb.jpg I tym razem nawet nie używałem złącza VGA (DSUB25) z starej płyty głównej od komputera, tylko miałem zakupione przez internet. Do lutowania użyłem mojej wiernej najtańszej lutownicy kolbowej i na tę okazję zmieniłem jej grot na grot ścięty (choć pewnie lepszy byłby grot ścięty typu minifala, ten z wyżłobionym 'zbiorniczkiem' na cynę): https://obrazki.elektroda.pl/4748971600_1588804324_thumb.jpg Zacząłem lutowanie od najmniejszych elementów: https://obrazki.elektroda.pl/3753769700_1588804437_thumb.jpg Dałem też wtedy cynową zworkę na sygnał "B" (Blue) koło złącza VGA, czyli wybrałem niebieski kolor czcionki. Szybko przyszła pora na złącza: https://obrazki.elektroda.pl/8356330500_1588804501_thumb.jpg https://obrazki.elektroda.pl/6638436500_1588804722_thumb.jpg Złącze DSUB25 czyli VGA oraz MDC6 (aka 'mini din') czyli PS/2: https://obrazki.elektroda.pl/6815685300_1588804785_thumb.jpg Na zdjęciu można zobaczyć, że wciąż nie przylutowałem EEPROMów - to dlatego, że dopiero czekałem na paczkę z nimi. Jak przyszły to uzupełniłem ubytek: https://obrazki.elektroda.pl/3204394600_1588804882_thumb.jpg https://obrazki.elektroda.pl/1382751400_1588804914_thumb.jpg Potem jeszcze uzupełniłem ubytek złącza RCA (od sygnału PALNTSC); nie miałem takiego na składzie więc wylutowałem z jakiegoś fragmentu urządzenia elektronicznego, to chyba był odtwarzacz kaset VHS: https://obrazki.elektroda.pl/7021071000_1588805047_thumb.jpg https://obrazki.elektroda.pl/9088830900_1588805043_thumb.jpg https://obrazki.elektroda.pl/5706716800_1588805051_thumb.jpg Złącze RCA: https://obrazki.elektroda.pl/4076050200_1588805209_thumb.jpg I oto końcowy efekt (już po wyczyszczeniu z nadmiaru topnika): https://obrazki.elektroda.pl/1416338600_1588846009_thumb.jpg https://obrazki.elektroda.pl/6408921300_1588846191_thumb.jpg Warto jeszcze pamiętać, że przed użyciem należy zrobić ze spoiwa lutowniczego następujące zworki: - zworka pinu RX shielda z Arduino - zworka pinu TX shielda z Arduino - zworka pinu RESET z Shielda (łączy RESET od PIC32 z RESET od Arduino) - zworka wyboru koloru VGA z Shielda - dwie zworki na magistrali I2C dla EEPROMów Przed użyciem shielda trzeba oczywiście też wgrać mu firmware na PIC32MX250F128B za pomocą PICKIT3 (lub innego programatora) poprzez złącze ICSP dostępne na płytce. Test generowanego sygnału VGA z różnymi monitorami i telewizorem W temacie dotyczącym poprzedniej wersji mojego shielda (tutaj: https://www.elektroda.pl/rtvforum/viewtopic.php?p=18496229#18496229 ) pojawiła się uwaga, że nie działa on z częścią monitorów. Postanowiłem to zweryfikować. Użyłem do tego: - telewizora LG42LE4500 - monitora DELL 1703FP - monitora ASUS VH196S - monitora HP L1706 - monitora SyncMaster 740N Zapraszam do obejrzenia zdjęć z testów. Test 1 - monitor DELL 1703FP: https://obrazki.elektroda.pl/9890789800_1588852185_thumb.jpg https://obrazki.elektroda.pl/5803001300_1588852224_thumb.jpg Tabliczka znamionowa użytego monitora: https://obrazki.elektroda.pl/2073302500_1588783980_thumb.jpg Test 2 - telewizor LG42LE4500: https://obrazki.elektroda.pl/5747425500_1588852640_thumb.jpg https://obrazki.elektroda.pl/7081934500_1588852638_thumb.jpg Tabliczka znamionowa użytego telewizora: https://obrazki.elektroda.pl/7031910900_1588783997_thumb.jpg Test 3 - monitor ASUS VH196S: https://obrazki.elektroda.pl/2275459400_1588854942_thumb.jpg https://obrazki.elektroda.pl/3360872300_1588854987_thumb.jpg Tabliczka znamionowa użytego monitora: https://obrazki.elektroda.pl/5879373100_1588854839_thumb.jpg Test 4 - HP L1706. https://obrazki.elektroda.pl/7060437300_1588855274_thumb.jpg https://obrazki.elektroda.pl/2953685300_1588855274_thumb.jpg Tabliczka znamionowa użytego monitora: https://obrazki.elektroda.pl/6106436200_1588855202_thumb.jpg Test 5 - SyncMaster 740N. Ten monitor naprawiałem tutaj: https://www.elektroda.pl/rtvforum/topic3688191.html , wymieniłem w nim kondensatory elektrolityczne. Dzięki temu może dalej działać i brać udział w testach mojego shielda: https://obrazki.elektroda.pl/4246636900_1588855412_thumb.jpg https://obrazki.elektroda.pl/2066575900_1588855446_thumb.jpg Tabliczka znamionowa użytego monitora: https://obrazki.elektroda.pl/1258439900_1588855496_thumb.jpg Podsumowując, shield VGA przetestowałem z czterema monitorami i jednym telewizorem i w każdym przypadku obraz był poprawnie wyświetlany. Sygnał widziany jest przez wszystkie urządzenia jako 640x480, mimo iż w dokumentacje użytej biblioteki jest podana nieco inna rozdzielczość. Po prostu shield w sprytny sposób sobie z tym radzi zostawiając puste marginesy wokół obrazu (co zresztą widać na większości zdjęć z jego działania). Nie znalazłem ani jednego urządzenia przez którego sygnał VGA z mojego shielda nie był wspierany, a jego timingi (31.5kHz i 60Hz) są zgodne z tym, co jest w kodzie: https://obrazki.elektroda.pl/8567581200_1590314726_thumb.jpg Test generowanego sygnału PAL Generowanie czarno-białego obrazu PAL (oraz NTSC - też jest wspierany) może się przydać gdy chcemy obsłużyć jakiś starszy telewizor, pewnie jeszcze CRT, który nie posiada złącza VGA. Może to być podyktowane chociażby sentymentem - niektórzy lubią korzystać ze starszego sprzętu nawet dla samej satysfakcji korzystania z niego. Z tego powodu przetestowałem też jak shield radzi sobie z generowaniem sygnału PAL. Sygnał PAL ze shielda podłącza się za pomocą przewodu RCA: https://obrazki.elektroda.pl/9451123200_1590143186_thumb.jpg Wystarczy pojedynczy sygnał video, audio nie jest oferowane przez ten shield. Test PAL 1 - telewizor LG42LE4500: Podłączenie: https://obrazki.elektroda.pl/2258585600_1590146184_thumb.jpg https://obrazki.elektroda.pl/9368920000_1590146199_thumb.jpg https://obrazki.elektroda.pl/3942760900_1590146205_thumb.jpg Obraz na ekranie: https://obrazki.elektroda.pl/9427440500_1590146322_thumb.jpg Obraz generowany przez shield jest tutaj rzecz jasna czarno-biały; zworka od koloru (wybór R/G/B) jest tylko dla VGA. Test PAL 2 - stary telewizor CRT TX-21AT1P: Ten pierwszy test PAL z nowym, płaskim telewizorem LG42LE4500 poniekąd mija się z celem, bo użyty tam telewizor ma złącze VGA (i to nie jedno!) więc nic nie stoi na przeszkodzie, by go podłączyć normalnie. Z tego powodu też przetestowałem tryb PAL z telewizorem który jest o wiele bardziej z tym trybem związanym - ze starym, ciężkim telewizorem kineskopowym, dokładniej 'Panasonic Colour TV' model TX-21AT1P: https://obrazki.elektroda.pl/8911258000_1590161461_thumb.jpg Tabliczka znamionowa tego telewizora: https://obrazki.elektroda.pl/8935412200_1590161531_thumb.jpg https://obrazki.elektroda.pl/7483420000_1590161541_thumb.jpg Sygnał PAL podłączyłem z przodu telewizora do złącz RCA: https://obrazki.elektroda.pl/9148315700_1590161702_thumb.jpg Warto tu wspomnieć, że nawet jeśli telewizor nie ma złącz RCA to sygnał PAL można też podać poprzez SCART - w przewodzie SCART jest jeden pin odpowiedzialny za wejście 'composite video'. Sygnał PAL oczywiście tutaj nie jest modulowany i odbieramy go w trybie AV: https://obrazki.elektroda.pl/6474478000_1590161796_thumb.jpg (Aczkolwiek jakby się postarać i użyć np. modulatora ze starego odtwarzacza VHS to można by prosto odbierać sygnał ze shielda poprzez złącze antenowe; choć wtedy jest ryzyko, że będziemy 'siać' sygnał po okolicy; do tej pory pamiętam jak kilkanaście lub więcej lat temu odbierałem przez przypadek na telewizorze obraz z konsoli do gier sąsiada). Na telewizorze uruchomiłem demko, które opisane jest nieco niżej w tekście. Rezultat: https://obrazki.elektroda.pl/3756502800_1590162046_thumb.jpg Uwaga - co jeśli monitor pokazuje 'nieobsługiwany format VGA'? Chciałbym tu tylko jeszcze przypomnieć, że shield w celu obsługi zarówno standardu PAL jak i VGA musi w sprytny sposób między nimi przełączać. Nie generuje dwóch sygnałów naraz. Shield przy starcie (po sygnale RESET lub podaniu zasilania) sam określa, czy monitor VGA jest podłączony i jeśli nie, to przechodzi w tryb PAL. Określa to poprzez badanie stanu na pinie RA4. Więc jeśli podłączymy monitor VGA w trakcie do shielda to powinniśmy wcisnąć przycisk RESET na Arduino lub wyłączyć i włączyć zasilanie - w przeciwnym razie shield będzie już 'nastawiony na PAL' i monitor VGA nic nie wyświetli. RESET z Arduino jest połączony z RESET (aka MCLR) z PIC32 z płytki shielda, ale należy pamiętać by na płytce shielda dać tam zworkę by oba układy resetowały się razem. (O tym, że mam wspomnieć o tym problemie przypomniał mi Bartek, który odkupił ode mnie jedno PCB poprzedniej wersji shielda - za przetestowanie układu i zwrócenie uwagi tu dziękuję) Test magistrali I2C - skan dostępnych urządzeń Na samym początku na mojej płytce uruchomiłem prosty program całkowicie niezwiązany z VGA - skaner I2C. Program ten pochodzi z oficjalnej strony Arduino: https://playground.arduino.cc/Main/I2cScanner/ i po prostu wykrywa wszystkie urządzenia podłączone na magistralę I2C, czyli w przypadku użytego przeze mnie Arduino UNO na jego piny 4 i 5. Program drukuje adresy I2C znalezionych urządzeń na port szeregowy/terminal Arduino. #include <Wire.h> //include Wire.h library void setup() { Wire.begin(); // Wire communication begin Serial.begin(9600); // The baudrate of Serial monitor is set in 9600 while (!Serial); // Waiting for Serial Monitor Serial.println("\nI2C Scanner"); } void loop() { byte error, address; //variable for error and I2C address int nDevices; Serial.println("Scanning..."); nDevices = 0; for (address = 1; address < 127; address++ ) { // The i2c_scanner uses the return value of // the Write.endTransmisstion to see if // a device did acknowledge to the address. Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address < 16) Serial.print("0"); Serial.print(address, HEX); Serial.println(" !"); nDevices++; } else if (error == 4) { Serial.print("Unknown error at address 0x"); if (address < 16) Serial.print("0"); Serial.println(address, HEX); } } if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); delay(5000); // wait 5 seconds for the next I2C scan } Program skompilowałem i wgrałem na Arduino z założonym moim shieldem. Rezultat sketchu: https://obrazki.elektroda.pl/8259841700_1588789861_thumb.jpg Widzimy, że skan I2C znalazł aż 9 urządzeń! Ale czy na pewno? Na powyższym zrzucie ekranu: - adres 0x20 to port expander MCP23016 - adresy 0x50, 0x51, 0x52, 0x53 to pierwszy AT24CM02 (przypominam: w adresie urządzenia AT24CM02 dwa bity odpowiadają za jego 'wewnętrzny adres' pamięci; dwa bity dają nam 4 możliwe adresy) - adresy 0x54, 0x55, 0x56, 0x57 to drugi AT24CM02 Czyli wszystko jest okej - dwie pamięci EEPROM i jeden expander portów. Poniżej zaprezentuję już jak można ich użyć. Lutowanie przejściówki do expandera portów (dla płytki stykowej) Na płytce znajduje się 16-pinowe złącze 2.54mm (2 rzędy po 8 pinów) od MCP23016: https://obrazki.elektroda.pl/3189098400_1588846402_thumb.jpg Oferuje ono dodatkowe 16 pinów IO którymi możemy sterować przez I2C. Polecam używać go w połączeniu z takim przewodem (najlepiej też zakupić odpowiednie gniazdo pod ten przewód by nie dało się go wsadzić odwrotnie, ale ja takiego pod ręką nie miałem): https://obrazki.elektroda.pl/1013751400_1588799286_thumb.jpg https://obrazki.elektroda.pl/6126385500_1588799285_thumb.jpg Do tego sugeruję zlutować sobie przejściówkę z tego przewodu do płytki stykowej/prototypowej. Płytka stykowa ma rozstaw pól 2.54mm pasujący do goldpinów, za wyjątkiem środkowego przedziałka płytki który ma akurat 5.08mm, czyli tyle ile wąski rozstaw pinów z obudowy DIP. Aż prosi się to wykorzystać. Opiszę tutaj jak - na początek trzeba przygotować goldpiny 1x8 (dwie sztuki) i 2x8 (jedna sztuka) oraz płytkę wierconą: https://obrazki.elektroda.pl/8689529400_1588799587_thumb.jpg https://obrazki.elektroda.pl/3694746900_1588799587_thumb.jpg Następnie warto dodać topnik na płytkę, by się lepiej lutowało: https://obrazki.elektroda.pl/4560970200_1588799647_thumb.jpg Dalej umieścić na naszej płytce pierwszy rząd goldpinów (on będzie wchodzić w płytkę stykową), ale odwrotnie niż zazwyczaj: https://obrazki.elektroda.pl/3611174500_1588799703_thumb.jpg Teraz trzeba ją przylutować - najwygodniej bardzo cienką końcówką. Przy lutowaniu pilnować, by goldpiny były prostopadle do płytki: https://obrazki.elektroda.pl/8970239000_1588799870_thumb.jpg Pierwszy lut gotowy: https://obrazki.elektroda.pl/5596099700_1588799906_thumb.jpg Następnie trzeba dokończyć wszystkie luty i można od drugiej strony płytki dać złącze 2x8 2.54mm goldpin i zacząć łączyć odpowiednie piny: https://obrazki.elektroda.pl/1829572700_1588799984_thumb.jpg Dalej polecam delikatnie przylutować drugi pojedynczy rząd goldpinów który wejdzie w płytkę stykową: https://obrazki.elektroda.pl/6292228500_1588800085_thumb.jpg https://obrazki.elektroda.pl/3709848100_1588800087_thumb.jpg Następnie wystarczy zmostkować odpowiednie piny za pomocą lutownicy. Jeszcze tylko trzeba odciąć zbędny fragment PCB: https://obrazki.elektroda.pl/8851617300_1588800172_thumb.jpg Przejściówka gotowa: https://obrazki.elektroda.pl/5095248300_1588800212_thumb.jpg Idealnie pasuje do wybranego przeze mnie przewodu (choć ponownie tu Wam polecam użyć nie zwykłych goldpinów 2x8, lecz złącza pod ten konkretny kabelek; wtedy unikniecie ryzyka podłączenia go odwrotnie): https://obrazki.elektroda.pl/1732464200_1588800500_thumb.jpg Przejściówka expandera portów gotowa. Tak wygląda razem z Shieldem i Arduino: https://obrazki.elektroda.pl/3922228300_1588800544_thumb.jpg Następne cztery akapity przedstawią testy MCP23016 w oparciu o te połączenie. Test expandera portu - MCP23016 - użyta biblioteka Na początku do obsługi expandera portów MCP23016 sterowanego poprzez I2C próbowałem użyć tej biblioteki od Adafruit (mimo iż jest ona dla MCP23017 - liczyłem na podobieństwo układów): https://obrazki.elektroda.pl/9380545400_1588790023_thumb.jpg Ale nie zadziałała, więc ostatecznie użyłem biblioteki CyMCP23016 autorstwa Chris Brunner aka cyrusbuilt, stąd: https://github.com/cyrusbuilt/CyMCP23016 Kopia zapasowa repozytorium (na czas pisania tematu): 1030477 Bibliotekę dodałem do Arduino poprzez Sketch->Include Library->Add .ZIP Library. Test expandera portu - MCP23016 - układ testowy Do przygotowania układu testowego MCP23016 użyłem płytki stykowej i wcześniej opisanej przejściówki i kabelka, do tego kilka rezystorów: https://obrazki.elektroda.pl/7024523100_1588802931_thumb.jpg https://obrazki.elektroda.pl/2680579400_1588802952_thumb.jpg Do tego oczywiście trzeba osobno podpiąć masę (i zasilanie, jeśli go potrzebujemy) https://obrazki.elektroda.pl/4391025300_1588803055_thumb.jpg Test expandera portu - MCP23016 - blink LED Pierwszym przykładem użycia MCP23016 jaki przygotowałem jest proste miganie jedną diodą poprzez ten expander portów, poprzez I2C. Wybrałem pin GP0.0, w tej bibliotece oznaczany MCP23016_PIN_GPIO0_0. Kod prawie w całości pochodzi z przykładu biblioteki z Githuba: /** * Basic input/output test for MCP23016 expander. */ #include <Arduino.h> #include "CyMCP23016.h" CyMCP23016 mcp; void setup() { Serial.begin(9600); Serial.println(("start")); // Init MCP23016 at the default address. This assumes we are running on // an ATmel AVR-based arduino, like the Arduino Uno. mcp.begin(); Serial.println(("begin")); // Set Pin 0 on Port 0 as an output. mcp.pinMode(MCP23016_PIN_GPIO0_0, OUTPUT); Serial.println(("pm")); } void loop() { delay(1000); // Set the pin HIGH and read back the state. mcp.digitalWrite(MCP23016_PIN_GPIO0_0, HIGH); uint8_t val = mcp.digitalRead(MCP23016_PIN_GPIO0_0); Serial.print(F("Pin 0.0 is ")); Serial.println(val == HIGH ? "HIGH" : "LOW"); delay(1000); // Set the pin LOW and read back the state. mcp.digitalWrite(MCP23016_PIN_GPIO0_0, LOW); val = mcp.digitalRead(MCP23016_PIN_GPIO0_0); Serial.print(F("Pin 0.0 is ")); Serial.println(val == HIGH ? "HIGH" : "LOW"); } Kod tylko przełącza stan na pierwszym pinie expandera i dodatkowo wysyła jego stan na UART. Rezultat w Serial Monitor: https://obrazki.elektroda.pl/7086169000_1588791010_thumb.jpg Rezultat na filmie: https://filmy.elektroda.pl/92_1588794181.mp4 W taki sam sposób możemy sterować wszystkimi 16 pinami od MCP23016. Test expandera portu - MCP23016 - prosty licznik binarny Drugim, bardziej zaawansowanym przykładem użycia MCP23016 jaki tu zaprezentuję jest prosty licznik binarny zrealizowany na 8 diodach LED (czyli 8-bitowy). Do tego użyłem całego portu GP0: https://obrazki.elektroda.pl/6748085500_1588798418_thumb.jpg Odpowiednio zmodyfikowałem też kod przykładu. Do ustawienia odpowiednich diod LED użyłem pętli for oraz operatorów bitowych, całość sketchu jest do wglądu poniżej: /** * Basic input/output test for MCP23016 expander. */ #include <Arduino.h> #include "CyMCP23016.h" CyMCP23016 mcp; int counter = 0; void setup() { Serial.begin(9600); Serial.println(("start")); mcp.begin(); Serial.println(("begin")); // Set Pin 0 on Port 0 as an output. for(int i = 0; i < 8; i++) { mcp.pinMode(i, OUTPUT); mcp.digitalWrite(i, LOW); } delay(1000); Serial.println(("pm")); } void loop() { delay(250); for(int i = 0; i < 8; i++) { mcp.digitalWrite(i, counter & (1<<(i))); } counter++; } Powyższy kod powinien być zrozumiały nawet dla początkujących z Arduino, jedyną z pozoru trudną rzeczą w nim jest 'wyłuskanie' bitu za pomocą counter & (1<<(i)). Ta linijka po prostu sprawdza, czy i-ty bit zmiennej counter jest zapalony. Rezultat na filmie: https://filmy.elektroda.pl/15_1588793680.mp4 Wszystko działa poprawnie. Można by jeszcze sprawdzić czy wszystko jest okej z drugim portem od MCP23016, ale uznałem, że to już jest zbędne. Test pamięci EEPROM - AT24CM02 - użyta biblioteka Do obsługi pamięci EEPROM a dokładniej dwóch kości AT24CM02 użyłem biblioteki autorstwa Rushikesh Patel aka luffykesh z Githuba, tej: https://github.com/luffykesh/AT24Cx Kopia zapasowa/snapshot repo na czas pisania artykułu: 1030460 Bibliotekę dodałem do Arduino poprzez Sketch->Include Library->Add .ZIP Library. Test pamięci EEPROM - AT24CM02 - test zapisu i odczytu Na początek sprawdziłem czy w ogóle komunikacja z AT24CM02 działa poprawnie. W tym celu użyłem przykładowego kodu z wybranej biblioteki, jedynie tylko zmodyfikowałem go by korzystał z konkretnie AT24CM02 (z pierwszego na PCB, o adresie 0x50): /* * * Read and write demo of the AT24CX library * Written by Christian Paul, 2014-11-24 * * */ // include libraries #include <Wire.h> #include <AT24CX.h> // EEPROM object AT24CM02 mem; // setup void setup() { // serial init Serial.begin(9600); Serial.println("AT24CX read/write demo"); Serial.println("----------------------"); } // main loop void loop() { // read and write byte Serial.println("Write 42 to address 12"); mem.write(12, 42); Serial.println("Read byte from address 12 ..."); byte b = mem.read(12); Serial.print("... read: "); Serial.println(b, DEC); Serial.println(); // read and write integer Serial.println("Write 65000 to address 15"); mem.writeInt(15, 65000); Serial.println("Read integer from address 15 ..."); unsigned int i = mem.readInt(15); Serial.print("... read: "); Serial.println(i, DEC); Serial.println(); // read and write long Serial.println("Write 3293732729 to address 20"); mem.writeLong(20, 3293732729UL); Serial.println("Read long from address 20 ..."); unsigned long l = mem.readLong(20); Serial.print("... read: "); Serial.println(l, DEC); Serial.println(); // read and write long Serial.println("Write 1111111111 to address 31"); mem.writeLong(31, 1111111111); Serial.println("Read long from address 31 ..."); unsigned long l2 = mem.readLong(31); Serial.print("... read: "); Serial.println(l2, DEC); Serial.println(); // read and write float Serial.println("Write 3.14 to address 40"); mem.writeFloat(40, 3.14); Serial.println("Read float from address 40 ..."); float f = mem.readFloat(40); Serial.print("... read: "); Serial.println(f, DEC); Serial.println(); // read and write double Serial.println("Write 3.14159265359 to address 50"); mem.writeDouble(50, 3.14159265359); Serial.println("Read double from address 50 ..."); double d = mem.readDouble(50); Serial.print("... read: "); Serial.println(d, DEC); Serial.println(); // read and write char Serial.print("Write chars: '"); char msg = "This is a message"; Serial.print(msg); Serial.println("' to address 200"); mem.writeChars(200, msg, sizeof(msg)); Serial.println("Read chars from address 200 ..."); char msg2; mem.readChars(200, msg2, sizeof(msg2)); Serial.print("... read: '"); Serial.print(msg2); Serial.println("'"); Serial.println(); // write array of bytes Serial.println("Write array of 80 bytes at address 1000"); byte xy = {0,0,0,1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,7,7,8,8,8,9,9,9, // 10 x 3 = 30 10,11,12,13,14,15,16,17,18,19, // 10 120,121,122,123,124,125,126,127,128,129, // 10 130,131,132,133,134,135,136,137,138,139, // 10 200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219}; // 20 mem.write(1000, (byte*)xy, sizeof(xy)); // read bytes with multiple steps Serial.println("Read 80 single bytes starting at address 1000"); for (int i=0; i<sizeof(xy); i++) { byte sb = mem.read(1000+i); Serial.print(" = "); Serial.println(sb); } Serial.println(); // read bytes with one step Serial.println("Read 80 bytes with one operation at address 1000"); byte z; memset(&z, 32, sizeof(z)); mem.read(1000, z, sizeof(z)); for (int i=0; i<sizeof(z); i++) { Serial.print(" = "); Serial.println(z); } // stop while (1==1) {delay(1000);} } https://obrazki.elektroda.pl/7951808100_1588847183_thumb.jpg Następnie zmodyfikowałem ten kod tak, by przetestować użycie dwóch EEPROM jednocześnie. W celu weryfikacji czy na pewno kod korzysta z dwóch różnych kości postanowiłem zapisać na ten sam adres różne wartości (dla pierwszej kości: 42, dla drugiej: 15) i potem je odczytać i sprawdzić czy są zachowywane. /* * * Read and write demo of the AT24CX library * Written by Christian Paul, 2014-11-24 * * */ // include libraries #include <Wire.h> #include <AT24CX.h> // EEPROM object AT24CM02 mem0(0); AT24CM02 mem1(1); // setup void setup() { // serial init Serial.begin(9600); Serial.println("AT24CX read/write demo"); Serial.println("----------------------"); } // main loop void loop() { // read and write byte Serial.println(" Write 42 to address 12"); mem0.write(12, 42); Serial.println(" Write 15 to address 12"); mem1.write(12, 15); Serial.println(" Read byte from address 12 ..."); byte b0 = mem0.read(12); Serial.print(" ... read: "); Serial.println(b0, DEC); Serial.println(" Read byte from address 12 ..."); byte b1 = mem1.read(12); Serial.print(" ... read: "); Serial.println(b1, DEC); // stop while (1==1) {delay(1000);} } Rezultat sketchu: https://obrazki.elektroda.pl/9200007400_1588847521_thumb.jpg Jak widać obie pamięci EEPROM działają poprawnie. W ten sposób zyskujemy całe dodatkowe 512KB (dwie kości po 256KB) pamięci! Małe demko - drukowanie kodów klawiszy na ekranie Teraz przedstawię proste demko już bezpośrednio związane z głównymi atutami mojej nakładki, czyli generowaniem obrazu VGA i obsługą klawiatury PS/2. Poniższe demko odbiera klawisze wciśnięte na klawiaturze i wyświetla ich kody na ekranie - za wyjątkiem strzałek, które są kodowane jako kilka kolejnych wartości i trzeba je w specjalny sposób wykrywać. Każda wciśnięcie jednej ze strzałek daje na UART następujące kody: * Up Arrow - 27 91 65 * Down Arrow - 27 91 66 * Right Arrow 27 91 67 * Left Arrow 27 91 68 Strzałki w poniższym demku są wykrywane poprawne i informacje o ich wciśnięciu są wyświetlane jako tekst. UWAGA: Poniższe demko poprawnie obsługuje Caps Lock i wpisywanie znaków z Shift, ale tego nie ma w kodzie na Arduino, gdyż dzieje się to w samym shieldzie i na klawiaturze. Pełny kod demka: /* * Up Arrow - 27 91 65 * Down Arrow - 27 91 66 * Right Arrow 27 91 67 * Left Arrow 27 91 68 * */ #if 0 #define USE_ARDUINO_RXTX #endif #ifdef USE_ARDUINO_RXTX #define vgaSerial Serial #else #include <SoftwareSerial.h> SoftwareSerial mySerial(2, 3); // RX, TX #define vgaSerial mySerial #endif void SCR_Reset() { vgaSerial.print((char)27); vgaSerial.print("c"); } void SCR_PrintLn(const char *s) { vgaSerial.println(s); } void SCR_Print(const char *s) { vgaSerial.print(s); } void SCR_ClearScreen() { vgaSerial.print((char)27); vgaSerial.print("[2J"); } void SCR_SetPos(int x, int y) { vgaSerial.print((char)27); vgaSerial.print("["); vgaSerial.print(x); vgaSerial.print(";"); vgaSerial.print(y); vgaSerial.print("H"); } void setup() { // Open serial communications and wait for port to open: Serial.begin(57600); while (!Serial) { ; // wait for serial port to connect. Needed for Native USB only } Serial.println("started vga"); #ifndef USE_ARDUINO_RXTX //mySerial.begin(1200); // mySerial.begin(57600); // mySerial.begin(38400); mySerial.begin(19200); //mySerial.begin(115200); mySerial.listen(); #endif delay(10); SCR_Reset(); SCR_ClearScreen(); // put your setup code here, to run once: SCR_PrintLn("Arduino VGA Shield Key Code Tester"); //SCR_SetPos(5,5); //SCR_Print("A"); } char buffer ; //whatever void OnPress_ArrowDown() { sprintf (buffer, "Pressed DOWN ARROW"); SCR_PrintLn(buffer); } void OnPress_ArrowRight() { sprintf (buffer, "Pressed RIGHT ARROW"); SCR_PrintLn(buffer); } void OnPress_ArrowLeft() { sprintf (buffer, "Pressed LEFT ARROW"); SCR_PrintLn(buffer); } void OnPress_ArrowUp() { sprintf (buffer, "Pressed UP ARROW"); SCR_PrintLn(buffer); } void OnPress_Key(char inByte) { sprintf (buffer, "Pressed code %i (visual %c)", (int)inByte, inByte); SCR_PrintLn(buffer); } void loop() { // put your main code here, to run repeatedly: while (vgaSerial.available() > 0) { char inByte = vgaSerial.read(); if(inByte == 27) { while (vgaSerial.available() == 0) { } vgaSerial.read(); while (vgaSerial.available() == 0) { } char inByte2 = vgaSerial.read(); if(inByte2 == 65) { OnPress_ArrowUp(); } if(inByte2 == 66) { OnPress_ArrowDown(); } if(inByte2 == 68) { OnPress_ArrowLeft(); } if(inByte2 == 67) { OnPress_ArrowRight(); } } else { OnPress_Key(inByte); } } } Rezultat działania: https://obrazki.elektroda.pl/9093480600_1589293381_thumb.jpg Shift i Caps-Lock są poprawnie obsługiwane, a same klawisze są bezbłędnie odbierane (za wyjątkiem pewnej sytuacji - ale o tym w następnym akapicie). Dlaczego lepiej korzystać z baud 19200 a nie np. z 1200? Tutaj chciałbym zwrócić uwagę na to jak duże znaczenie ma świadomy wybór odpowiedniego baud komunikacji UART ze shieldem. Nie można wybrać zbyt dużego baud, bo ogranicza nas zdolność SoftwareSerial z Arduino. Przy około 38400 mogą się zacząć problemy. To raczej wiadomo, podobne informacje powtarzają się na forum Arduino: https://forum.arduino.cc/index.php?topic=528912.0 Ale nie można też wybrać zbyt małego baud, bo wtedy będziemy gubić znaki lub co gorsza otrzymywać błędne dane. Łatwo to pokazać uruchamiając demo 'test klawiszy' i wciskając (trzymając cały czas) klawisz strzałki, który wysyłany jest przez UART jako trzy znaki. Zdjęcie demka 'odbiór klawiszy' przy baud 1200: https://obrazki.elektroda.pl/9787153500_1589293837_thumb.jpg Zdjęcie demka 'odbiór klawiszy' przy baud 19200: https://obrazki.elektroda.pl/2876768200_1589293876_thumb.jpg Widzicie różnicę? Wniosek: nieodpowiednio dobrany baud może naprawdę namieszać, również gdy wybierzemy jego zbyt małą wartość. Na zdjęciu widać, że raz nawet wysłała się wartość '109' która odpowiada klawiszowi 'm', co z pewnością mogłoby mieć niepożądany skutek jeśli byśmy w naszym programie korzystali zarówno ze strzałek jak i z tego klawisza. Zaawansowane edytor tekstu Teraz zaprezentuję nieco bardziej zaawansowane demko oparte o mojego shielda, a mianowicie prosty edytor tekstu. Edytor tekstu będzie oferować: - obsługa klawisza Shift - obsługa klawisza Caps Lock - obsługa klawisza Delete (usuwanie znaku po kursorze) - obsługa klawisza Backspace (usuwanie znaku przed kursorem) - obsługa klawisza Enter (dodawanie nowej linii) Część tej funkcjonalności jest po stronie Arduino, a część na shieldzie. Rzeczy takie jak poruszanie kursorem, obsługa bufora tekstu, dodawanie znaków, usuwanie ich itp. są po stronie Arduino. Zdjęcia z działania dema-edytora tekstu na monitorze VGA i klawiaturze PS2: https://obrazki.elektroda.pl/5248090800_1589747497_thumb.jpg https://obrazki.elektroda.pl/5745421400_1589747497_thumb.jpg https://obrazki.elektroda.pl/5153392700_1589747496_thumb.jpg https://obrazki.elektroda.pl/5951234900_1589747502_thumb.jpg https://obrazki.elektroda.pl/5538928900_1589747504_thumb.jpg Edytor tekstu można by znacznie ulepszyć, ograniczyć bardziej zbędne odświeżanie pełnego ekranu lub po prostu przenieść w większej części na PICa. Skecz Arduino do pobrania: 1030452 Test edytora tekstu na starym telewizorze CRT TX-21AT1P W ramach uzupełnienia poprzedniego akapitu przedstawiam tu filmik z działania edytora tekstu w trybie PAL na telewizorze kineskopowym: https://filmy.elektroda.pl/13_1590143145.mp4 Zbędne 'pełne odświeżenie' ekranu w trakcie dodawania nowej linii do tekstu można by usunąć w kodzie programu, ale nie to było celem tego demka. Zaawansowane demko - system plików na EEPROM Przedstawione tutaj demko opracowałem w celu dokładniejszego przetestowania obu pamięci EEPROM znajdujących się na płytce (te pamięci pozwalają nam zapisać dane które nie utracą się nawet po całkowitym odłączeniu zasilania od Arduino). Demko to stanowi podstawową wersję systemu plików, który wspiera podstawowe operacje na plikach i katalogach (tworzenie ich, usuwanie, dodawanie danych, przeglądanie, czyszczenie), pozwala też zagnieżdżać katalogi/pliki w innych katalogach tworząc strukturę drzewa. System oczywiście też jest na różne sposoby ograniczony - brakuje dynamicznej alokacji pamięci dla danych pliku, choć jest ona dostępna dla samych katalogów i plików (używane są ponownie zwolnione sloty). Demko obsługuje się poprzez Serial Monitor od Arduino który pełni dla niego funkcję linii komend i wspiera następujące polecenia: - dirls - wyświetla zawartość bieżącego katalogu - mkdir - tworzy katalog o danej nazwie - rmdir - usuwa katalog z zawartością - resetfs - resetuje EEPROMy do pustego systemu plików - resetfile - resetuje plik do stanu pustego (tj. ustawia jego długość na zero) - rmfile - usuwa plik - cd - przechodzi do danego folderu - mkfile - tworzy pusty plik o danej nazwie - append - dodaje do danego pliku dany ciąg znaków - show - pokazuje dany plik w konsoli Poniżej umieszczam zrzuty ekranu z testów powyższego systemu plików; myślę, że ich zawartość nie wymaga komentarza. Testy komend mkdir, ls, mkfile, append, ls: https://obrazki.elektroda.pl/9208051700_1589744617_thumb.jpg Testy komend: cd, mkdir, mkfile: https://obrazki.elektroda.pl/5551405800_1589744899_thumb.jpg Testy komend: rmdir, rmfile, resetfs: https://obrazki.elektroda.pl/7737634500_1589744985_thumb.jpg Testy komend: show, mkfile, append: https://obrazki.elektroda.pl/9026338300_1589745050_thumb.jpg Demko do pobrania: 1030449 Kompilacja wsadu z kodu źródłowego Jeśli tylko chcecie złożyć i używać tego shielda z Arduino to nie musicie samodzielnie kompilować kodu na PICa, potrzebny dla niego .hex załączam tutaj: 1030451 Ale jeśli ktoś chce zmodyfikować samo działanie shielda, to kod można łatwo skompilować w środowisku MPLAB X IDE v5.20: https://obrazki.elektroda.pl/1438991700_1590314916_thumb.jpg Przy użyciu kompilatora XC32 (v2.20): https://obrazki.elektroda.pl/6079210300_1590315087_thumb.jpg Dodatkowo do kompilacji wymagana jest biblioteka PIC32 Peripheral Library (słynne plib.h wraz z innymi nagłówkami), którą ściąga się osobno ze strony Microchipa: https://www.microchip.com/SWLibraryWeb/product.aspx?product=PIC32%20Peripheral%20Library Gdy już ją zainstalujemy, to wszystko poprawnie się kompiluje: https://obrazki.elektroda.pl/2965134100_1590410028_thumb.jpg Kompilować wsad można w dwóch trybach: - tryb dla bootloadera (do wgrania wsadu przez USB za pomocą PIC32UBL.exe) - wtedy w projekcie załączamy 32MX250F128B.ld - tryb bez bootloadera (do wgrania wsadu przez ICSP) - wtedy w projekcie wykluczamy plik 32MX250F128B.ld Więcej informacji na ten temat znajdziecie w oryginalnej paczce z kodem źródłowym: 1030459 Dalszy rozwój projektu - dalsza minimalizacja? Obecna wersja pozostawia jeszcze pole do popisu i możliwość ulepszenia, m.in. dlatego że np. użyty PIC32 jest w wersji PIC32MX250F128B-50I/SO, czyli obudowa SOIC: https://obrazki.elektroda.pl/7523413500_1588796498_thumb.jpg A ten sam mikrokontroler jest jeszcze dostępny w obudowach SSOP (PIC32MX250F128B-50I/SS): https://obrazki.elektroda.pl/9127079900_1588796563_thumb.jpg ... oraz w QFN (PIC32MX250F128B-50I/ML): https://obrazki.elektroda.pl/2453157800_1588796599_thumb.jpg Wszystkie obudowy naszego PICa (kolejno: QFN, SSOP, SOIC i SPDIP) umieściłem na obrazku poniżej: https://obrazki.elektroda.pl/2428852500_1588857711_thumb.jpg Dalszy rozwój projektu - co można by lepiej zrobić? Projekt (i też jego wykonanie - luty) nie jest idealny i dużo można by jeszcze ulepszyć. - można by dać slot na kartę microSD na płytce (podłączony do SPI od Arduino) - można by (jak wspomniałem wyżej) użyć mniejszych obudów elementów i zyskać w ten sposób miejsce na coś więcej na płytce - można by zastąpić MCP23016 (wymagający zewnętrznego rezystora i kondensatora na pinie CLK) poprzez nieco lepszy MCP23017 (który nie wymaga tych dwóch elementów) - można by też dać tam mocniejszego PICa i/lub iść w stronę kolorowego VGA - umieszczone tu przykłady możny by ulepszyć i zoptymalizować, lecz nie to było ich celem (to tylko demonstracja możliwości shielda, nie miała być wydajna) Możliwe zatem, że za jakiś czas zrobię trzecią wersję shielda. Tabela załączników Dla wygody czytelników umieszczam tutaj raz jeszcze odnośniki do załączników które pojawiły się w temacie: NazwaOpisZałącznik Źródła Eagle projektuPliki .sch i .brd które można edytować w Eagle. Użyjcie tego, jeśli chcecie zrobić własną, zmodyfikowaną wersję tego shielda. 1030455 Pliki Gerber projektuTe pliki wysyłamy płytkarni jeśli chcemy by nam wyprodukowali PCB pod ten projekt. Nie polecam ich edytować. 1030453 Arduino skecz - demo edytora tekstuPełny kod edytora tekstu zrealizowanego na tym shieldzie dla Arduino w formacie .ino. Wymaga podłączonej klawiatury i monitora/telewizora do działania. 1030452 Arduino skecz - demo klawiszyPełny kod testera klawiszy PS/2 z obsługą strzałek zrealizowanego na tym shieldzie dla Arduino w formacie .ino. Wymaga podłączonej klawiatury i monitora/telewizora do działania. 1030450 Arduino skecz - demo system plików na EEPROMPełny kod prostego systemu plików na dwóch kościach EEPROM na magistrali I2C Arduino wraz z linią komend na UART. Ten projekt nie korzysta wcale z VGA i nie korzysta z klawiatury PS/2. Kod w formacie .ino 1030449 Skompilowane firmware dla PICa z ShieldaNiezbędny wsad do jednorazowego wgrania na PIC32MX250F128B znajdującego się na pokładzie shielda. Można go wgrać np. za pomocą PICKIT3. 1030451 Kod źródłowy shieldaKod C firmware shielda na PIC32MX250F128B dla MPLAB. Projekt autorstwa geoffg. 1030459 Datasheet AT24CM02Dla dociekliwych - nota katalogowa użytej kości pamięci 1030458 Datasheet MCP23016Dla dociekliwych - nota katalogowa użytego expandera portów 1030454 Biblioteka AT24CXBiblioteka AT24CX z Githuba - kopia zapasowa, wersja ta którą przetestowałem i wykorzystałem do tego projektu. Tę paczkę .ZIP można łatwo dodać do Arduino poprzez 'Add .ZIP Library...'. 1030460 Biblioteka CyMCP23016Biblioteka CyMCP23016 z Githuba - kopia zapasowa, wersja ta którą przetestowałem i wykorzystałem do tego projektu. Tę paczkę .ZIP można łatwo dodać do Arduino poprzez 'Add .ZIP Library...'. 1030477 Podsumowanie Jestem bardzo zadowolony z drugiej wersji mojego shielda. Przejście z montażu przewlekanego na powierzchniowy pozwoliło mi zmieścić na nim nieco więcej niż w pierwszej wersji. Dzięki oparciu o Arduino UNO całość jest wygodna i prosta w użyciu nawet dla początkujących. Umieszczone dodatkowo na płytce układy (MCP23016 i AT24CM02) też posiadają gotowe biblioteki dla Arduino dostępne w sieci, co sprawia, że obsługa mojego shielda sprowadza się do składania programów niemalże jak z klocków, a użycie klawiatury PS/2 oraz możliwość wyświetlania jednokolorowego obrazu VGA oraz czarno-białego obrazu PAL/NTSC nadaje całości nieco 'oldschoolowy' klimat.
/**
AT24CX.h
Library for using the EEPROM AT24C32-512/M01/M02
Original work Copyright (c) 2014 Christian Paul
Modified work Copyright (c) 2019 Rushikesh Patel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the " Software " ), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef AT24CX_h
#define AT24CX_h
// includes
#include & lt; Arduino.h & gt;
// byte
typedef uint8_t byte;
// AT24Cx I2C adress
// 80
// 0x50
#define AT24CX_ID B1010000
enum eeprom_type_t{
AT24CX_t,
AT24C32_t,
AT24C64_t,
AT24C128_t,
AT24C256_t,
AT24C512_t,
AT24CM01_t,
AT24CM02_t,
};
// general class definition
class AT24CX {
public:
AT24CX(byte index=0, byte pageSize=32);
void write(unsigned int address, byte data);
void write(unsigned int address, byte *data, int n);
void writeInt(unsigned int address, unsigned int data);
void writeLong(unsigned int address, unsigned long data);
void writeFloat(unsigned int address, float data);
void writeDouble(unsigned int address, double data);
void writeChars(unsigned int address, char *data, int length);
byte read(unsigned int address);
void read(unsigned int address, byte *data, int n);
unsigned int readInt(unsigned int address);
unsigned long readLong(unsigned int address);
float readFloat(unsigned int address);
double readDouble(unsigned int address);
void readChars(unsigned int address, char *data, int n);
protected:
void init(byte index, byte pageSize);
int _id;
byte _b[8];
uint16_t _pageSize;
eeprom_type_t eepromType;
private:
void read(unsigned int address, byte *data, int offset, int n);
void write(unsigned int address, byte *data, int offset, int n);
};
// AT24C32 class definiton
class AT24C32 : public AT24CX {
public:
AT24C32(byte index=0);
};
// AT24C64 class definiton
class AT24C64 : public AT24CX {
public:
AT24C64(byte index=0);
};
// AT24C128 class definiton
class AT24C128 : public AT24CX {
public:
AT24C128(byte index=0);
};
// AT24C256 class definiton
class AT24C256 : public AT24CX {
public:
AT24C256(byte index=0);
};
// AT24C512 class definiton
class AT24C512 : public AT24CX {
public:
AT24C512(byte index=0);
};
//AT24CM02 class definition
class AT24CM02 : public AT24CX {
public:
AT24CM02(byte index=0);
void write(unsigned int address, byte data){
_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3); //insert 17th and 16th bit of data word address in i2c address
AT24CX::write(address,data);
}
void write(unsigned int address, byte *data, int n){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);AT24CX::write(address,data,n);}
void writeInt(unsigned int address, unsigned int data){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);AT24CX::writeInt(address,data);}
void writeLong(unsigned int address, unsigned long data){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);AT24CX::writeLong(address,data);}
void writeFloat(unsigned int address, float data){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);AT24CX::writeFloat(address,data);}
void writeDouble(unsigned int address, double data){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);AT24CX::writeDouble(address,data);}
void writeChars(unsigned int address, char *data, int length){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);AT24CX::writeChars(address,data,length);}
byte read(unsigned int address){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);return AT24CX::read(address);}
void read(unsigned int address, byte *data, int n){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);AT24CX::read(address,data,n);}
unsigned int readInt(unsigned int address){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);return AT24CX::readInt(address);}
unsigned long readLong(unsigned int address){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);return AT24CX::readLong(address);}
float readFloat(unsigned int address){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);return AT24CX::readFloat(address);}
double readDouble(unsigned int address){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);return AT24CX::readDouble(address);}
void readChars(unsigned int address, char *data, int n){_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);AT24CX::readChars(address,data,n);}
};
//AT24CM01 class definition
class AT24CM01 : public AT24CX {
public:
AT24CM01(byte index=0);
void write(unsigned int address, byte data){
_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1); //insert 16th bit of data word address in i2c address
AT24CX::write(address,data);
}
void write(unsigned int address, byte *data, int n){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);AT24CX::write(address,data,n);}
void writeInt(unsigned int address, unsigned int data){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);AT24CX::writeInt(address,data);}
void writeLong(unsigned int address, unsigned long data){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);AT24CX::writeLong(address,data);}
void writeFloat(unsigned int address, float data){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);AT24CX::writeFloat(address,data);}
void writeDouble(unsigned int address, double data){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);AT24CX::writeDouble(address,data);}
void writeChars(unsigned int address, char *data, int length){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);AT24CX::writeChars(address,data,length);}
byte read(unsigned int address){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);return AT24CX::read(address);}
void read(unsigned int address, byte *data, int n){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);AT24CX::read(address,data,n);}
unsigned int readInt(unsigned int address){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);return AT24CX::readInt(address);}
unsigned long readLong(unsigned int address){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);return AT24CX::readLong(address);}
float readFloat(unsigned int address){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);return AT24CX::readFloat(address);}
double readDouble(unsigned int address){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);return AT24CX::readDouble(address);}
void readChars(unsigned int address, char *data, int n){_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);AT24CX::readChars(address,data,n);}
};
#endif
/**
AT24CX.cpp
Library for using the EEPROM AT24C32-512/M01/M02
Original work Copyright (c) 2014 Christian Paul
Modified work Copyright (c) 2019 Rushikesh Patel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the " Software " ), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include " AT24CX.h "
#include & lt; Wire.h & gt;
/**
* Constructor with AT24Cx EEPROM at given index and size of page
*/
AT24CX::AT24CX(byte index, byte pageSize) {
init(index, pageSize);
eepromType=AT24CX_t;
}
/**
* Constructor with AT24Cx EEPROM at given index(default 0)
*/
AT24C32::AT24C32(byte index) {
init(index, 32);
eepromType=AT24C32_t;
}
/**
* Constructor with AT24C64 EEPROM at given index(default 0)
*/
AT24C64::AT24C64(byte index) {
init(index, 32);
eepromType=AT24C64_t;
}
/**
* Constructor with AT24C128 EEPROM at given index(default 0)
*/
AT24C128::AT24C128(byte index) {
init(index, 64);
eepromType=AT24C128_t;
}
/**
* Constructor with AT24C128 EEPROM at given index(default 0)
*/
AT24C256::AT24C256(byte index) {
init(index, 64);
eepromType=AT24C256_t;
}
/**
* Constructor with AT24C512 EEPROM at given index(default 0)
*/
AT24C512::AT24C512(byte index) {
init(index, 128);
eepromType=AT24C512_t;
}
/*
* Constructor with AT24CM02 EEPROM at given index(default 0)
*/
AT24CM02::AT24CM02(byte index){
_id = AT24CX_ID | (index & lt; & lt; 2 & 0x4);
_pageSize = 256;
eepromType=AT24CM02_t;
Wire.begin();
}
/*
* Constructor with AT24CM01 EEPROM at given index(default 0)
*/
AT24CM01::AT24CM01(byte index){
_id = AT24CX_ID | (index & lt; & lt; 1 & 0x6);
_pageSize = 256;
eepromType=AT24CM01_t;
Wire.begin();
}
/**
* Init
*/
void AT24CX::init(byte index, byte pageSize) {
_id = AT24CX_ID | (index & 0x7);
_pageSize = pageSize;
Wire.begin();
}
/**
* Write byte
*/
void AT24CX::write(unsigned int address, byte data) {
Wire.beginTransmission(_id);
if(Wire.endTransmission()==0) {
Wire.beginTransmission(_id);
Wire.write(address & gt; & gt; 8);
Wire.write(address & 0xFF);
Wire.write(data);
Wire.endTransmission();
delay(10);
}
}
/**
* Write integer
*/
void AT24CX::writeInt(unsigned int address, unsigned int data) {
write(address, (byte*) & data, 2);
}
/**
* Write long
*/
void AT24CX::writeLong(unsigned int address, unsigned long data) {
write(address, (byte*) & data, 4);
}
/**
* Write float
*/
void AT24CX::writeFloat(unsigned int address, float data) {
write(address, (byte*) & data, 4);
}
/**
* Write double
*/
void AT24CX::writeDouble(unsigned int address, double data) {
write(address, (byte*) & data, 8);
}
/**
* Write chars
*/
void AT24CX::writeChars(unsigned int address, char *data, int length) {
write(address, (byte*)data, length);
}
/**
* Read integer
*/
unsigned int AT24CX::readInt(unsigned int address) {
memset(_b,0,sizeof(unsigned int));//for platforms that have 32bit integers
read(address, _b, 2);
return *(unsigned int*) & _b[0];
}
/**
* Read long
*/
unsigned long AT24CX::readLong(unsigned int address) {
read(address, _b, 4);
return *(unsigned long*) & _b[0];
}
/**
* Read float
*/
float AT24CX::readFloat(unsigned int address) {
read(address, _b, 4);
return *(float*) & _b[0];
}
/**
* Read double
*/
double AT24CX::readDouble(unsigned int address) {
read(address, _b, 8);
return *(double*) & _b[0];
}
/**
* Read chars
*/
void AT24CX::readChars(unsigned int address, char *data, int n) {
read(address, (byte*)data, n);
}
/**
* Write sequence of n bytes
*/
void AT24CX::write(unsigned int address, byte *data, int n) {
// status quo
int c = n; // bytes left to write
int offD = 0; // current offset in data pointer
int offP; // current offset in page
int nc = 0; // next n bytes to write
// write all bytes in multiple steps
while (c & gt; 0) {
// calc offset in page
offP = address % _pageSize;
// maximal 30 bytes to write
nc = min(min(c, 30), _pageSize - offP);
write(address, data, offD, nc);
c-=nc;
offD+=nc;
address+=nc;
if(address%65536==0)
{
//re-calculate i2c address in case of AT24CM01 and AT24CM02 when crossing 65536 bytes border
if(eepromType==AT24CM02_t)
_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);
else if(eepromType==AT24CM01_t)
_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);
}
}
}
/**
* Write sequence of n bytes from offset
*/
void AT24CX::write(unsigned int address, byte *data, int offset, int n) {
Wire.beginTransmission(_id);
if (Wire.endTransmission()==0) {
Wire.beginTransmission(_id);
Wire.write(address & gt; & gt; 8);
Wire.write(address & 0xFF);
byte *adr = data+offset;
Wire.write(adr, n);
Wire.endTransmission();
delay(10);
}
}
/**
* Read byte
*/
byte AT24CX::read(unsigned int address) {
byte b = 0;
int r = 0;
Wire.beginTransmission(_id);
if (Wire.endTransmission()==0) {
Wire.beginTransmission(_id);
Wire.write(address & gt; & gt; 8);
Wire.write(address & 0xFF);
if (Wire.endTransmission()==0) {
Wire.requestFrom(_id, 1);
while (Wire.available() & gt; 0 & & r & lt; 1) {
b = (byte)Wire.read();
r++;
}
}
}
return b;
}
/**
* Read sequence of n bytes
*/
void AT24CX::read(unsigned int address, byte *data, int n) {
int c = n;
int offD = 0;
int offP; // current offset in page
int nc = c;
// read until are n bytes read
while (c & gt; 0) {
// calc offset in page
offP = address % _pageSize;
// read maximal 32 bytes
nc = min(min(c,32), _pageSize - offP);
read(address, data, offD, nc);
address+=nc;
offD+=nc;
c-=nc;
if(address%65536==0)
{
//re-calculate i2c id in case of CM01 and CM02, when page border encountered
if(eepromType==AT24CM02_t)
_id=(_id & 0xFC)|(address & gt; & gt; 16 & 0x3);
else if(eepromType==AT24CM01_t)
_id=(_id & 0xFE)|(address & gt; & gt; 16 & 0x1);
}
}
}
/**
* Read sequence of n bytes to offset
*/
void AT24CX::read(unsigned int address, byte *data, int offset, int n) {
Wire.beginTransmission(_id);
if (Wire.endTransmission()==0) {
Wire.beginTransmission(_id);
Wire.write(address & gt; & gt; 8);
Wire.write(address & 0xFF);
if (Wire.endTransmission()==0) {
int r = 0;
Wire.requestFrom(_id, n);
while (Wire.available() & gt; 0 & & r & lt; n) {
data[offset+r] = (byte)Wire.read();
r++;
}
}
}
}