Pamięci FRAM w zastosowaniach praktycznych. Pamięć szeregowa FM24C64

Pamięci FRAM w zastosowaniach praktycznych. Pamięć szeregowa FM24C64

Pamięci są dzielone na dwie kategorie. Pierwszą z nich są pamięci nieulotne. Od wielu lat są one używane w aplikacjach w celu zapamiętania pewnych stałych, niezmiennych informacji. Może to być program realizowany przez mikroprocesor czy też dla przykładu wzorce znaków wyświetlanych na ekranie wyświetlacza LCD. Podstawową cechą tego rodzaju pamięci jest stałość przechowywanych informacji również wtedy, gdy brak jest napięcia zasilania. Zazwyczaj zapis pamięci stałej (często zwany jej programowaniem), nawet mimo użycia technologii EEPROM czy też FLASH EEPROM, jest dość trudny i zajmuje dużo czasu, jeśli odnieść to do czasu odczytu tej pamięci. Druga grupa to pamięci ulotne, tzw. RAM. Są one łatwe do zapisu i pracują bardzo szybko, więc służą do przechowywania danych, które często ulegają zmianie. Inaczej niż w pamięciach nieulotnych, dane przechowywane w RAM giną po odłączeniu napięcia zasilającego i jeśli konieczne jest z jakiś względów zachowanie niezmiennego stanu RAM, to jest wymagane stosowania pomocniczych źródeł zasilania. Jest to swego rodzaju wyzwanie dla konstruktora układu. Wad wyżej opisanych układów nie ma nowy rodzaj pamięci, której technologia wytwarzania przed kilkunastu laty opuściła laboratoria naukowe. Jest to pamięć FRAM. Skrót nazwy tłumaczy się jako Ferroelectric Random Access Memory. Oznacza on technologię wytwarzania (uwaga!) nieulotnej pamięci RAM, czyli pamięci łączącej w sobie szybkość pracy RAM i trwałość ROM.

Pamięci wytwarzane w technologii FRAM były dostępne już przed kilkunastoma latami (praktycznie od 1993 roku), ale zarówno cena jak i dostępny asortyment nie zachęcały do ich stosowania. W ostatnim czasie technologia ta przeżywa prawdziwą eksplozję rozwoju. Firmy oferują liczne układy peryferyjne wyposażone w pamięci FRAM, a cena tych układów gwałtownie spada. Moim zdaniem w przyszłości technologia ta może zastąpić popularną technologię FLASH EEPROM chyba, że zostanie opracowany jakiś zupełnie nowy rodzaj pamięci.

Czym są pamięci FRAM?

FRAM jest rodzajem pamięci RAM, w której wykorzystano efekt ferroelektryczny do zapamiętywania bitów słowa danych. Efekt ten, być może znany niektórym studentom politechnik z ćwiczeń w laboratorium fizyki, występuje w niektórych materiałach krystalicznych i jest zdolnością materiału do przechowywania polaryzacji elektrycznej również w przypadku nieobecności pola elektrycznego, które tę polaryzację wywołało. Komórka pamięci tworzona jest poprzez nałożenie materiału ferroelektrycznego w postaci krystalicznej pomiędzy dwa doprowadzenia płaskich elektrod w taki sposób, aby został utworzony kondensator z materiałem ferroelektrycznym jako dielektrykiem. Konstrukcja tego kondensatora zbliżona jest do typowej konstrukcji komórki pamięci DRAM z tym, że zamiast przechowywać informację w postaci naładowanego kondensatora (tak, jak w typowo robi to komórka pamięci DRAM), bit przechowywany jest w postaci polaryzacji ładunków w obrębie struktury krystalicznej. W ten sposób – poprzez zmianę polaryzacji ładunków przy pomocy pola elektrycznego wewnątrz kondensatora, można tworzyć i zapamiętać dwa stabilne stany. Stany te umownie mogą odpowiadać wartościom logicznym bitów, to jest „zeru” i „jedynce”.
Prosta zasada działania, niemalże identyczna z tą stosowaną w pamięciach RAM, umożliwia konstrukcję prostych obwodów zapisu i odczytu komórek pamięci. Jak wspomniano wcześniej, materiał ferroelektryczny zachowuje polaryzację ładunków pomimo zaniku pola elektrycznego i w związku tym, nie tak jak w pamięciach RAM, dane mogą być przechowywane w sposób nieulotny. Zbudowana z jego wykorzystaniem komórka pamięci nie wymaga również okresowego odświeżania.
Pamięć FRAM jest odporna na działania zewnętrznego pola magnetycznego. Jej zasada działania, nie ma nic wspólnego z ferromagnetyzmem. Podobny jest jedynie opis zjawiska fizycznego: w przypadku materiałów ferroelektrycznych polaryzacji ulegają ładunki, natomiast w przypadku materiałów ferromagnetycznych – domeny magnetyczne.

Zasada działania pamięci FRAM

Na rysunku 1 przedstawiony jest poglądowy model kryształu ferroelektrycznego o strukturze perowskitu. Kryształ posiada ruchomy atom w środku swojej struktury (oznaczony kolorem żółtym). Przyłożenie pola elektrycznego powoduje, że „mobilny” atom  przesuwa się w kierunku działania sił pola. Odwrócenie polaryzacji pola powoduje przemieszczanie się atomu w kierunku przeciwnym. Pozycje atomy na „górze” i „dole” struktury krystalicznej są stabilne. Atom pozostaje w jednej z nich w przypadku braku pola elektrycznego. Jako komórka pamięci cyfrowej, taka struktura jest wręcz idealna: posiada dwa stany stabilne, potrzebuje bardzo małej mocy do zmiany stanu i zachowuje ten stan nawet mimo działania różnych czynników zewnętrznych.

Rysunek 1. Budowa kryształu ferro-elektrycznego - perowskitu.

Odczyt. Mimo iż podstawowym elementem komórki pamięci jest kondensator, to jednak bity nie są zapamiętywane jako ładunek liniowy. Odczyt takiej komórki pamięci wymaga detekcji położenia atomu wewnątrz struktury krystalicznej. Niestety nie może ono być rozpoznane bezpośrednio i musi tu być używany pewien „zabieg”.
Do kondensatora przykładane jest napięcie. Na skutek tego pomiędzy jego okładzinami pojawia się pole elektryczne. Ruchomy atom przesuwa się zgodnie z kierunkiem działania pola, lub pozostaje w spoczynku, jeśli jego położenie jest z nim zgodne. W środku struktury kryształu występuje stan równowagi, który utrzymuje pozostałe atomy w ściśle określonych położeniach sieci krystalicznej a wypadowe pole elektryczne jest równe 0. Jeśli ruchomy atom przemieszcza się, to powoduje zaburzenie, co skutkuje impulsem energetycznym. Impuls ten dodaje się do ładunku zgromadzonego przez kondensator (pamiętajmy, że opisywane są struktury o rozmiarach kilkunastu atomów!). Na skutek tego jedne kondensatory naładowane będą do wartości ładunku wymuszonej przez pole elektryczne a inne będą posiadać ładunek będący kombinacją oddziaływania pola elektrycznego i ruchu atomu. Mimo, iż odczyt pamięci wymaga przemieszczenia atomu, to jednak należy pamiętać o tym, że atom przebywa bardzo krótką drogę. Jego położenie zmienia się w czasie 1 nanosekundy (10-9 sekundy) a cała operacja odczytu zajmuje około 70 nanosekund.
Łatwo na podstawie powyższego opisu wywnioskować, jak będzie wyglądać obwód określający stan bitu: będzie to rodzaj komparatora porównującego ładunek odebrany z komórki pamięci z pewnym poziomem odniesienia. Dobrze, a co ze zmianą polaryzacji ładunku? Przecież pisałem, że podczas odczytu atom przemieszcza się wewnątrz struktury. W związku z przyjętą metodą odczytu, każdemu cyklowi dostępu do pamięci towarzyszy rodzaj operacji odświeżania. A co w związku z tym z czasem dostępu do pamięci? Czy nie jest przez to odświeżanie wydłużany? Niestety tak. Wpływ odświeżania na czas odczytu pamięci FRAM jest znaczny, ponieważ zajmuje ono aż 50 nanosekund.
Zapis. Operacja zapisu jest bardzo podobna do opisywanego wcześniej odczytu. Inaczej niż inne rodzaje pamięci stałych programowane elektrycznie, nie wymaga przyłożenia wysokiego napięcia czy też długiego czasu zapisu. Wewnętrzny obwód przykłada napięcie do okładzin kondensatora ferroelektrycznego. Powoduje to odpowiednie przemieszczenie ruchomego atomu i polaryzację kryształów. Jeśli jest to potrzebne, nowe dane po prostu zmieniają stan polaryzacji kryształu ferroelektrycznego. Tak, jak w przypadku odczytu, zmiana stanu kryształu zajmuje ok. 1 nanosekundy podczas, gdy cała operacja zapisu około 70 nanosekund (7 x 10-8s!). Nieporównywalnie krótki czas, jeśli odnieść go np. do pamięci FLASH.

Rysunki zaczerpnięte z materiałów firmy Ramtron. Od lewej: zasada działania odczytu i zapisu pamięci FRAM, budowa struktury układu pamięci FRAM, rozwój technologii wytwarzania pamięci FRAM.

 

Główne zalety stosowania pamięci FRAM:
Cykl zapisu i cykl odczytu zajmują tyle samo czasu.
Można przeprowadzić ogromną ilość operacji zapisu i odczytu, ponieważ pamięć praktycznie nie zużywa się (gwarantowane jest 109 cykli zapisu!).
Do zasilania wymagana jest bardzo mała moc. Odczyt i zapis pamięci wymaga jej dokładnie tak samo mało.
W związku z brakiem dodatkowych napięć zasilających i konieczności stosowania dodatkowego źródła zasilania pamięci RAM w celu podtrzymania zawartości pamięci, aplikacja ulega znacznemu uproszczeniu.

Aplikacje

Oczywiście, w praktyce, wszystkie opisywane wyżej zjawiska nie będą obchodzić potencjalnego użytkownika układu. Producenci wyposażając układ peryferyjny w interfejs SPI zwalniają konstruktora od pamiętania o fizycznych cechach pamięci. Nieco inaczej jest w przypadku pamięci równoległych – poruszę ten temat w dalszej części artykułu.
Kiedy po raz pierwszy przeczytałem o upowszechnieniu się technologii pamięci FRAM, przed oczyma od razu miałem szerokie spektrum aplikacji. Po pierwsze, ze względu na bardzo dużą szybkość oraz ogromną liczbę cykli zapisu, bardzo zasadne jest użycie tego rodzaju pamięci jako pamięci masowej w komputerach PC, telefonach komórkowych, aparatach fotograficznych i innych urządzeniach elektronicznych. Moim zdaniem pamięci tego rodzaju mogą z powodzeniem zastąpić pamięć FLASH w mikrokontrolerach tworząc wspólny obszar pamięci dla danych oraz programu. Rzec by można, że im mniejszy będzie kod programu użytkownika, tym więcej pamięci RAM będzie miało do dyspozycji CPU mikrokontrolera. To nie wszystko: programy będą mogły dynamicznie modyfikować swój stan podczas pracy mikroprocesora. Moim zdaniem zupełnie zmieni to podejście do programowania oraz sposób tworzenia aplikacji. To jest przyszłość. Niestety istnieją jeszcze pewne ograniczenie technologiczne i póki co pamięci FRAM dzielą się na dwie podstawowe kategorie.
Pierwsza to typowe aplikacje do przechowywania danych. W nich pamięci FRAM służą do zapamiętywania danych przechowywanych poza systemem. Drugi rodzaj aplikacji jest związany z konstrukcją i opracowywaniem urządzeń związanych z mikrokontrolerami i mikroprocesorami. Dzięki bardzo dużej szybkości zapisu, pamięci te mogą być wielokrotnie błyskawicznie zapisywane, znacznie skracając czas potrzebny na testowanie aplikacji podczas jej uruchamiania. Innym aspektem jest programowanie w czasie produkcji – znaczne skrócenie czasu zapisu programu obniża koszty wytwarzania urządzenia. Nie jest również konieczne wyposażanie linii produkcyjnej w specjalne układy programatorów.

Produkty firmy RAMTRON

Firmą wiodącą na rynku produktów FRAM i praktycznie założoną tylko w celu ich rozwoju oraz rozpowszechniania jest Ramtron International Corporation z siedzibą w Colorado Springs (USA). Firma założona została w 1984 roku i po blisko 10 latach prac badawczych, w 1993 roku wprowadziła na rynek pierwszy produkt komercyjny. Była to pamięć o pojemności 4 kb (kilo - bit). Współcześnie firma jest liderem w dziedzinie technologii wytwarzania pamięci FRAM, właścicielem wielu patentów z tej dziedziny i oferuje szereg różnych układów peryferyjnych. Między innymi układy zawierające np. zegar czasu rzeczywistego oraz 256 kb pamięci FRAM. Krótkie zestawienie jej produktów zamieszczono w tabelach 1 i 2.

Czas przejść do pokazania praktycznych zastosowań FRAM. Ten rodzaj pamięci mimo dostępności układów oraz niewątpliwych ich zalet, nie jest jeszcze w naszym kraju zbyt popularny. Wiele z układów FRAM umożliwia zastąpienie wprost pamięci EEPROM, inne wymagają specjalnych aplikacji a jeszcze inne są całkowicie unikatowe. Proponuję aby na początek zająć się typową pamięcią szeregową z interfejsem 2-Wire (kompatybilny z I2C) o pojemności 64 kbit.

Pamięć szeregowa FM24C64 (64 kbit)

Wykonana w technologii FRAM pamięć FM24C64 wyposażona jest w szybki interfejs 2-Wire. Jest ona funkcjonalnym odpowiednikiem popularnej pamięci EEPROM produkowanej przez wiele firm pod oznaczeniem 24C64 (np. przez firmę Atmel Corp. AT24C64). Jako, że ten rodzaj pamięci jest dobrze znany elektronikom, tu w skrócie zostaną wymienione cechy pamięci po to, aby zająć się praktyczną realizacją interfejsu programowego umożliwiającego zapis i odczyt danych:
- niski pobór prądu: 150 μA przy zasilaniu 5V, 10 μA w trybie standby.
- organizacja: 8192 x 8 bit.
- możliwość wykonania aż 1012 cykli zapisu / odczytu!
- gwarancja na utrzymanie zawartości przez 10 lat.
- brak czasu oczekiwania na zapis/odczyt bajtu (czas dostępu znacznie krótszy od okresu zegara transmisji interfejsu).
- częstotliwość sygnału zegarowego interfejsu do 1MHz.
- kompatybilna z pamięciami EEPROM typu 24C64.
- interfejs zbliżony funkcjonalnie do I2C (tryby: standardowy 100 kHz i szybki 400 kHz).
Jak wspomniano przy okazji krótkiej charakterystyki pamięci, jest ona kompatybilna z popularną pamięcią EEPROM 24C64. Podobnie jest z programem obsługi: nie wymaga on żadnych specjalnych zabiegów. Najważniejsza jest różnica funkcjonalna: pamięć FRAM w porównaniu z EEPROM wyróżnia się ogromną wręcz szybkością zapisu. W związku z tym, że interfejs I2C jest znany z szeregu aplikacji, opis pamięci ograniczę do krótkiej charakterystyki funkcjonalnej.
Podobnie jak I2C, kompatybilny z nim interfejs 2-Wire wymaga rezystorów zasilających (pull-up) o wartości minimalnej około 1,8kΩ. Wartość rezystorów zależy od pojemności połączeń. Przy bardzo krótkich połączeniach wystarczające mogą być rezystory wbudowane w strukturę np. mikrokontrolera, ale nie polecam rozwiązania tego typu.  Linie interfejsu są dwukierunkowe, transmisja zawsze nadzorowana jest przez układ zarządzający (master), podczas gdy pamięć jest zawsze układem nadzorowanym (slave). Podobnie jak w I2C linie noszą nazwę SDA (danych) i SCL (zegarowa). Od I2C przejęto również sygnalizację stanów (potwierdzenie odbioru danych czy komendy - ACK) oraz polecenia START, STOP.
Po otrzymaniu polecenia START interfejs pamięci oczekuje na 7-bitowy adres oraz bit kierunku transmisji (zapis / odczyt). W 8-bitowym słowie adresu, bity 7 do 4 identyfikują rodzaj układu i są predefiniowane przez producenta. Bity 3 do 1 mogą być ustawiane przez użytkownika poprzez zwieranie odpowiednich wyprowadzeń do potencjałów „1” lub „0”. Najmłodszy bit 0 określa kierunek transmisji (0 – zapis, 1 – odczyt).
Na listingu 1 umieściłem funkcje obsługi zapisu i odczytu danych. Program w asemblerze 8051 napisany został na podstawie noty aplikacyjnej firmy Atmel przeznaczonej dla pamięci AT24C64 (dla EEPROM). Jak wspomniałem wcześniej, oprócz bardzo dużej szybkości działania, pamięć FRAM nie różni się niczym w obsłudze od swojego odpowiednika EEPROM. Przykładowy program zapisuje dane do pamięci FRAM, odczytuje i weryfikuje odczytaną zawartość. W przypadku niezgodności linia ERROROUT (w przykładzie jest to P1.4) przyjmuje stan wysoki. Program źródłowy zawiera dużo komentarzy i nie jest zbyt trudny do analizy. Wymagana jest jedynie elementarna znajomość asemblera mikrokontrolera 8051.

Jacek Bogusz
j.bogusz@easy-soft,net.pl

Listing 1. Funkcje zapisu i odczytu pamięci 24C64

;------------------------------------------
;Funkcje obsługi pamięci 24C64
;------------------------------------------
;Dla mikrokontrolera z rodziny Intel 8051
;pracującego z zegarem 12MHz

$INCLUDE (REG_51.PDF)              ;dołączenie definicji rejestrów uK (kompilator RA-51 Raisonance)
NAME        OBSLUGA_AT24C64
FIXEDADDR   EQU   0A0H             ;stała część adresu pamięci 24C64
USRADDR     EQU   0                ;ustawiana przez użytkownika część adresu (wartości 0..7)
MEMSIZE     EQU   2000H            ;liczba bajtów dla 24C64
PMEMSIZE    EQU   32               ;liczba bajtów na stronę pamięci dla 24C64
FILL        EQU   0AAH             ;wartość używana w funkcji FILL do wypełnienia pamięci

;Definicje funkcji rejestrów
index       EQU   R0               ;wskaźnik bufora w pamięci RAM mikrokontrolera
count       EQU   R1               ;licznik bajtów
zdata       EQU   R2               ;rejestr bajtu danych
addr_lo     EQU   R3               ;młodszy bajt adresu bajtu w pamięci 24C64
addr_hi     EQU   R4               ;starszy bajt - / / -    - / / -

;Definicje linii interfejsu
SCL         BIT   P1.2             ;linia zegara (SCL)
SDA         BIT   P1.3             ;linia danych (SDA)
ERROROUTBIT P1.4                   ;sygnalizacja błędu weryfikacji

;zmienne w pamięci RAM
DSEG AT 20H
buffer:     DS    PMEMSIZE         ;bufor do zapisu / odczytu danych
DSEG AT 60H                        ;początek stosu
stack:      DS    20H              ;rozmiar stosu
;tablica wektorów przerwań

CSEG
CODE AT 0000H                      ;wektor przerwania po RESET
            ajmp  on_reset
CODE AT 0003H                      ;zewnętrzne przerwanie INT0
            reti
CODE AT 000BH                      ;przerwanie TF0
            reti
CODE AT 0013H                      ;zewnętrzne przerwanie INT1
            reti
CODE AT 001BH                      ;przerwanie TF1
            reti
CODE AT 0023H                      ;przerwanie od UART
            reti

USING       0                      ;używany będzie bank rejestrów numer 0
CODE AT 0080H                      ;początek programu głównego

on_reset:
     mov    SP,#(stack-1)          ;inicjalizacja wskaźnika stosu
     setb   SDA                    ;inicjalizacji linii interfejsu
     setb   SCL                    ;SDA = SCL = 1
     clr    ERROROUT               ;ustawienie stanu niskiego wyjścia sygn.błędu
;< przykłady użycia funkcji >
     call   byte_fill              ;wypełnienie pamięci wartością FILL (0AAH)
     jc     fault                  ;jeśli ustawiona flaga C, to błąd
     call   verify_byte_fill ;weryfikacja zawartości
     jc     fault
     call   page_fill              ;wypełnienie strony pamięci wartością FILL
     jc     fault                  ;jeśli ustawiona flaga C, to błąd
     call   verify_page_fill ;weryfikacja zawartości
     jc     fault
     jmp    $
fault:
     setb   ERROROUT               ;sygnalizacja błędu
     jmp    $                      ;pętla nieskończona

;wypełnienie pamięci wartością zadeklarowaną jako FILL
;tryb stronnicowania nie jest wykorzystywany, zapisywany
;jest bajt po bajcie; ustawienie flagi CY wskazuje na przekroczenie
;limitu czasu operacji
;modyfikuje wartości A, B, DPTR, ADDR_HI (R2), ADDR_LO (R3), bit C
byte_fill:
     mov    zdata,#FILL
     mov    dptr,#0         ;adres początkowy dla operacji zapisu
bf_x1:
     mov    addr_lo,DPL     ;ustawienie adresu bajtu w pamięci 24C64
     mov    addr_hi,DPH
     mov    B,#120          ;licznik powtórzeń
bf_x2:
     mov    A,#USRADDR      ;do akumulatora zapisywana jest zmienna część adresu
     call   write_byte      ;próba zapisu
     jnc    bf_x3           ;skok, jeśli zapis pomyślny
     djnz   B,bf_x2         ;błąd, ponowna próba zapisu
     setb   C               ;zapis nie powiódł się, sygnalizacja błędu
     jmp    bf_x4           ;i wyjście
bf_x3:
     inc    dptr            ;następna pozycja w pamięci 24C64
     mov    A,DPL           ;sprawdzenie młodszego bajtu (limitu) dla adresów
     cjne   A,#(LOW MEMSIZE),bf_x1   ;powtórnie, jeśli to nie ostatni bajt
     mov    A,DPH           ;sprawdzenie starszego bajtu adresu
     cjne   A,#(HIGH MEMSIZE),bf_x1  ;powtórnie, jeśli to nie ostatni bajt
     clr    C               ;zerowanie flagi sygnalizacji błędu
bf_x4:
     ret                    ;koniec, pomyślna realizacja funkcji

;weryfikacja bajtów zapisanych w pamięci; odczytuje i sprawdza jeden
;bajt w danym momencie; używana jest funkcja Random Read do sprawdzenia
;zawartości pod pierwszym adresem oraz inicjalizacji wewnętrznego licznika
;adresów; następnie wywoływana jest Current Address Read i sprawdzana krok
;po kroku zawartość pamięci
;modyfikuje wartości A, B, DPTR, ADDR_HI (R2), ADDR_LO (R3), bit C
verify_byte_fill:
     mov    DPTR,#0         ;inicjalizacja rejestru adresów
     mov    addr_lo,DPL     ;zapamiętanie adresów
     mov    addr_hi,DPH
     mov    B,#120          ;liczba powtórzeń
vbf_x1:
     mov    A,#USRADDR      ;załadowanie zmiennej części adresu
     call   read_random     ;próba odczytu
     jnc    vbf_x2          ;skok, jeśli pomyślna
     djnz   B,vbf_x1        ;błąd, powtórna próba odczytu
     jmp    vbf_x6          ;przekroczono limit, wyjście z ustawioną flagą CY
vbf_x2:
     cjne   A,#FILL,vbf_x6  ;skok, jeśli błąd porównania
     jmp    vbf_x5          ;następny adres w pamięci
vbf_x3:
     mov    A, #USRADDR
     call   read_current    ;wywołanie funkcji odczytu bieżącego bajtu
     jc     vbf_x6          ;skok, jeśli operacja nie powiodła się
     cjne   A,#FILL,vbf_x6  ;skok, jeśli błąd porównania
vbf_x5:
     inc    dptr            ;następny adres w pamięci
     mov    A,DPL           ;sprawdzenie młodszego bajtu (limitu) dla adresów
     cjne   A,#(LOW MEMSIZE),vbf_x3  ;powtórnie, jeśli to nie ostatni bajt
     mov    A,DPH           ;sprawdzenie starszego bajtu dla adresów
     cjne   A,#(HIGH MEMSIZE),vbf_x3 ;powtórnie, jeśli to nie ostatni bajt
     clr    C               ;zerowanie flagi sygnalizacji błędu
     ret                    ;wyjście
vbf_x6:
     setb   C               ;ustawienie flagi sygnalizacji błędu
     ret

;wypełnienie strony w pamięci wartością FILL
;modyfikuje wartości A, B, DPTR, ADDR_HI (R2), ADDR_LO (R3), bit C
page_fill:
     mov    B,#PMEMSIZE     ;do B liczba bajtów na stronę
     mov    index,#buffer   ;"index" jako wskaźnik do bufora w RAM mikrokontrolera
pf_x1:
     mov    @index,#FILL    ;wypełnienie bufora w RAM wartością FILL
     inc    index
     djnz   B,pf_x1
                            ;zapis bufora do pamięci 24C64 - jedna strona
     mov    dptr,#0         ;inicjalizacji wskaźnika adresów
pf_x2:
     mov    addr_lo,DPL     ;nastawy rejestru adresów
     mov    addr_hi,DPH
     mov    count,#PMEMSIZE ;liczba bajtów na stronę
     mov    B,#120          ;rejestr B jako licznik powtórzeń
pf_x3:
     mov    A,#USRADDR      ;załadowanie programowanej części adresu
     call   write_block     ;próba zapisu
     jnc    pf_x4           ;skok, jeśli próba pomyślna
     djnz   B,pf_x3         ;ponowna próba zapisu
     setb   C               ;ustawienie flagi sygnalizacji błędu
     jmp    pf_x6           ;i wyjście
pf_x4:
                            ;dodanie rozmiaru PMEMSIZE do wskaźnika adresów
     mov    A,DPL           ;dodawanie 2 bajtów (młodszy, później starszy)
     add    A,#PMEMSIZE
     mov    DPL,A
     jnc    pf_x5
     inc    DPH             ;rozkaz INC DPH zastępuje dodanie do DPH przeniesienia
pf_x5:
     cjne   A,#(LOW MEMSIZE),pf_x2  ;jak poprzednio-sprawdzenie adresu
     mov    A,DPH
     cjne   A,#(HIGH MEMSIZE),pf_x2
     clr    C
pf_x6:
     ret

;weryfikacja strony zapisanej w pamięci 24C64
;weryfikowana jest jedna strona w danym momencie
;modyfikuje wartości A, B, DPTR, ADDR_HI (R2), ADDR_LO (R3), bit C
verify_page_fill:
                            ;przepisanie strony z pamięci 24C64 do bufora
     mov    DPTR,#0         ;inicjalizacja licznika adresów
vpf_x1:
     mov    addr_lo,DPL
     mov    addr_hi,DPH
     mov    count,#PMEMSIZE ;liczba bajtów na stronę
     mov    B,#120          ;licznik powtórzeń
vpf_x2:
     mov    A,#USRADDR      ;zapamiętanie zmiennej części adresu
     call   read_block      ;próba odczytu
     jnc    vpf_x4          ;skok, jeśli pomyślna
     djnz   B,vpf_x2        ;ponowna próba odczytu
vpf_x3:
     setb   C               ;ustawienie bitu sygnalizacji błędu
     jmp    vpf_x7          ;wyjście
vpf_x4:
                            ;weryfikacja zawartości bufora
     mov    B,#PMEMSIZE     ;liczba bajtów na stronę
     mov    index,#buffer   ;zapamiętanie adresu bufora w RAM
vpf_x5:
     cjne   @index,#FILL,vpf_x3  ;skok, jeśli błąd porównania
     inc    index           ;następna pozycja w buforze
     djnz   B,vpf_x5
                            ;dodanie PMEMSIZE do wskaźnika adresu
     mov    A,DPL           ;operacja przeprowadzana jak wyżej
     add    A,#PMEMSIZE
     mov    DPL,A
     jnc    vpf_x6
     inc    DPH
vpf_x6:
     cjne   A,#(LOW MEMSIZE),vpf_x1
     mov    A,DPH
     cjne   A,#(HIGH MEMSIZE),vpf_x1
     clr    C
vpf_x7:
     ret

;zapis na stronie pamięci zawartości bufora BUFFER ;wywołanie:
;adres 1-go bajtu w ADDR_HI:ADDR_LO, dane do zapisu w BUFFER,
;liczba bajtów w COUNT, zmienna część adresu układu w A
;zwraca CY=1, jeśli magistrala jest zajęta lub pamięć nie odpowiada
;modyfikuje: A, COUNT, INDEX
write_block:
     call   start           ;wysłanie polecenie I2C START
     jc     wb_x8           ;przerwa, jeśli magistrala jest zajęta
     rl     A               ;zmienna część adresu przesuwana jest na poz. bitów 3:1
     orl    A,#FIXEDADDR    ;dodanie stałej części adresu
     clr    ACC.0           ;zerowanie bitu numer 0: zapis danych
     call   shout           ;wysłanie adresu pamięci
     jc     wb_x7           ;jeśli układ nie odpowiada, to błąd i wyjście
     mov    A,addr_hi       ;wysłanie starszego bajtu adresu słowa w pamięci
     call   shout
     jc     wb_x7           ;jeśli układ nie odpowiada, to błąd i wyjście
     mov    A,addr_lo       ;wysłanie młodszego bajtu adresu słowa w pamięci
     call   shout
     jc     wb_x7           ;jeśli układ nie odpowiada, to błąd i wyjście
     mov    index,#buffer   ;załadowanie do zmiennej INDEX adresu bufora w RAM
wb_x6:
     mov    A,@index        ;pobierz bajt
     call   shout           ;wyślij do pamięci 24C64
     jc     wb_x7           ;jeśli układ nie odpowiada, to błąd i wyjście
     inc    index           ;następna pozycja w buforze
     djnz   count,wb_x6     ;następny adres w 24C64
     clr    C               ;zerowanie flagi C - sygnalizacji błędu
wb_x7:
     call   stop            ;wysłanie polecenie I2C STOP
wb_x8:
     ret

;odczyt jednego bajtu do jednej strony bajtów z pamięci 24C64
;wysyła polecenie Random Read a następnie  Sequential Read
;wywołanie: adres 1-go bajtu w ADDR_HI:ADDR_LO, dane zwracane w BUFFER,
;liczba bajtów w COUNT, zmienna część adresu układu w A
;zwraca CY=1, jeśli magistrala jest zajęta lub pamięć nie odpowiada
;modyfikuje: A, COUNT, INDEX
read_block:
                            ;tryb zapisu w celu ustawienia licznika adresów 24C64
    call    start           ;wysłanie polecenia I2C START
    jc      rb_x5           ;przerwa, jeśli magistrala jest zajęta
    rl      A               ;zmienna część adresu przesuwana jest na poz. bitów 3:1
    orl     A,#FIXEDADDR    ;dodanie stałej części adresu
    mov     index,A         ;zapamiętanie kopii adresu urządzenia
    clr     ACC.0           ;wybór operacji zapisu
    call    shout           ;wysłanie adresu pamięci
    jc      rb_x4           ;wyjście, jeśli brak potwierdzenia
    mov     A,addr_hi       ;wysłanie starszej części adresu słowa
    call    shout
    jc      rb_x4           ;wyjście, jeśli brak potwierdzenia
    mov     A,addr_lo       ;wysłanie młodszej części adresu słowa
    call    shout
    jc      rb_x4           ;wyjście, jeśli brak potwierdzenia
                            ;zmiana trybu na "odczyt" i odczyt bajtów
    call    start           ;powtórne wysłanie polecenia I2C START
    jc      rb_x4           ;wyjście, jeśli brak potwierdzenia
    mov     A,index         ;odtworzenie adresu pamięci
    setb    ACC.0           ;kasowanie bitu 0 adresu - odczyt
    call    shout           ;wysłanie adresu na magistralę I2C
    jc      rb_x4           ;wyjście, jeśli brak potwierdzenia
    mov     index,#buffer   ;do zmiennej INDEX wskazanie do bufora
rb_x1:
    call    shin            ;odbiór bajtu od pamięci
    mov     @index,A        ;zapamiętanie ich w RAM mikrokontrolera
    cjne    count,#1,rb_x2  ;skok, jeśli to nie ostatni bajt
    call    NAK             ;nie wysyłaj potwierdzenia, ostatni bajt
    jmp     rb_x3           ;wyjście
rb_x2:
    call    ACK             ;wyślij potwierdzenie
    inc     index           ;następna pozycja w buforze
    djnz    count,rb_x1     ;następny bajt w pamięci 24C64
rb_x3:
    clr     C               ;kasowanie flagi błędu
rb_x4:
    call    stop            ;wysłanie rozkazu I2C STOP
rb_x5:
    ret

;zapis bajtu do pamięci 24C64; wywołanie: adres 1-go bajtu w
;ADDR_HI:ADDR_LO, dane do zapisu w ZDATA, zmienna część adresu
;układu pamięci w A, zwraca CY=1, jeśli magistrala jest zajęta lub
;pamięć nie odpowiada, modyfikuje A
write_byte:
    call    start           ;wysłanie rozkazu I2C START
    jc      wb_y9           ;wyjście, jeśli brak potwierdzenia
    rl      A               ;zmienna część adresu przesuwana jest na poz. bitów 3:1
    orl     A,#FIXEDADDR    ;dodanie stałej części adresu
    clr     ACC.0           ;operacja zapisu (b0=0)
    call    shout           ;wysłanie adresu pamięci
    jc      wb_y8           ;wyjście, jeśli brak potwierdzenia
    mov     A,addr_hi       ;wysłanie starszego bajtu adresu słowa
    call    shout 
    jc      wb_y8           ;wyjście, jeśli brak potwierdzenia
    mov     A,addr_lo       ;wysłanie młodszego bajtu adresu słowa
    call    shout
    jc      wb_y8           ;wyjście, jeśli brak potwierdzenia
    mov     A,zdata         ;pobranie danych z ZDATA do ACC
    call    shout           ;wysłanie danych do pamięci 24C64
    jc      wb_y8           ;wyjście, jeśli brak potwierdzenia
    clr     C               ;kasowanie znacznika błędu
wb_y8:
    call    stop            ;wysłanie rozkazu I2C STOP
wb_y9:
    ret

;odczyt bajtu spod bieżącego adresu w pamięci 24C64
;wywoływana ze zmienną częścią adresu w A, zwraca bajt w A
;zwraca CY=1, jeśli magistrala jest zajęta lub pamięć nie
;odpowiada, modyfikuje A
read_current:
    call    start           ;wysłanie rozkazu I2C START
    jc      rc_x5           ;wyjście, jeśli brak potwierdzenia
    rl      A               ;zmienna część adresu przesuwana jest na poz. bitów 3:1
    orl     A,#FIXEDADDR    ;dodanie stałej części adresu
    setb    ACC.0           ;operacja odczytu (b0=1)
    call    shout           ;wysłanie adresu pamięci
    jc      rc_x4           ;wyjście, jeśli brak potwierdzenia
    call    shin            ;odbiór bajtu danych
    call    NAK             ;nie wysyłaj potwierdzenia, koniec operacji
    clr     C               ;kasowanie flagi sygnalizacji błędu
rc_x4:
    call    stop            ;wysłanie rozkazu I2C STOP
rc_x5:
    ret

;wywyłanie funkcji Read Random (odczyt swobodny)
;wywoływana ze zmienną częścią adresu w A, adresem bajtu w
;ADDR_HI:ADDR_LO; bajt zwracany w akumulatorze
;zwraca CY=1, jeśli magistrala jest zajęta lub pamięć nie
;odpowiada
read_random:
    push    B               ;zapamiętanie stanu rejestru B na stosie
    mov     B,A             ;zapamiętanie kopii zmiennej części adresu
                            ;tryb zapisu w celu ustawienia licznika adresów 24C64
    call    start           ;wysłanie rozkazu I2C START
    jc      rr_x7           ;wyjście, jeśli brak potwierdzenia
    rl      A               ;zmienna część adresu przesuwana jest na poz. bitów 3:1
    orl     A,#FIXEDADDR    ;dodanie stałej części adresu
    clr     ACC.0           ;operacja zapisu (b0=0)
    call    shout           ;wysłanie adresu pamięci
    jc      rr_x6           ;wyjście, jeśli brak potwierdzenia
    mov     A,addr_hi       ;wysłanie starszego bajtu adresu słowa
    call    shout
    jc      rr_x6           ;wyjście, jeśli brak potwierdzenia
    mov     a,addr_lo       ;wysłanie młodszego bajtu adresu słowa
    call    shout
    jc      rr_x6           ;wyjście, jeśli brak potwierdzenia
                            ;wywołanie funkcji Call Current Address Read
    mov     A,B             ;odtwórz zmienną część adresu
    call    read_current
    jmp     rr_x7           ;wyjście
rr_x6:
    call    stop            ;wysłanie rozkazu I2C STOP
rr_x7:
    pop     B               ;odtworzenie stanu rejestru B
    ret

;wysłanie polecenie I2C START (SDA = "1" -> "0" przy SCL = "1")
;powrót z SCL = SDA = 0, zwraca CY = 1, jeśli magistrala jest zajęta
start:
    setb    SDA
    setb    SCL
                            ;sprawdzenie zajętości magistrali I2C
    jnb     SDA,st_x0       ;skok, jeśli SDA <> "1"
    jnb     SCL,st_x0       ;skok, jeśli SCL <> "1"
    nop                     ;czas na ustalenie się linii
    clr     SDA             ;zmiana stanu SDA (SDA -> "0" przy SCL = "1")
    nop                     ;opóźnienie
    nop
    nop
    clr     SCL             ;zmiana stanu SCL (SCL -> "0" przy SDA = "0")
    clr     C               ;kasowanie flagi sygnalizacji błędu
    jmp     st_x1
st_x0:
    setb    c               ;ustawienie flagi sygnalizacji błędu
st_x1:
    ret

;wysłanie rozkazu I2C STOP (SDA = "0" -> "1" przy SCL = "1")
;spodziewane jest SCL = "0", powrór z SDA = SCL = "1"
stop:
    clr     SDA
    nop                     ;czas na ustalenie SDA
    setb    SCL             ;SCL -> "1" przy SDA = "0"
    nop                     ;czas na ustalenie
    nop
    nop
    setb    SDA             ;zmiana SDA -> "1"
    ret

;wyprowadzenie bajtu przez SDA w takt SCL (najbardziej znaczący
;bit wyprowadzany jest jako pierwszy); wywoływana z bajtem do
;wysłania w A, zwraca CY = 1, jeśli magistrala jest zajęta
shout:
    push    B               ;zapamiętanie stanu rejestru B na stosie
    mov     B,#8            ;rejestr B jako licznik wysyłanych bitów
sho_x2:
    rlc     A               ;przesunięcie bajtu w lewo i przeniesienie najstarszego
                            ;bitu dla flagi C
    mov     SDA, c          ;wyprowadzenie bitu
    nop                     ;czas na ustalenie się wyjścia mikrokontrolera
    setb    SCL             ;zmiana SCL ___|```
    nop                     ;czas na ustalenie
    clr     SCL             ;zmiana SCL ```|___
    djnz    B,sho_x2        ;następny bit
    setb    SDA             ;ustawienie SDA = "1" dla odbioru ACK
    nop                     ;czas na ustalenie się pamięci
    setb    SCL             ;narastające zbocze SCL
    nop                     ;czas na ustalenie
    mov     C,SDA           ;odczyt bitu ACK
    clr     SCL             ;zmiana SCL "1" -> "0"
    pop     B               ;odtworzenie stanu rejestru B
    ret

;odczyt 8 bitów, kompletacja słowa danych (z postaci szeregowej na równoległą)
;jako pierwszy wprowadzany jest najbardziej znaczący bit, bajt zwracany jest w A
shin:
    setb    SDA             ;SDA jak wejście
    push    B               ;zapamiętanie stanu rejestru B na stosie
    mov     B,#8            ;rejestr B jako licznik wysyłanych bitów
shi_x3:
    nop                     ;czas na ustalenie się linii danych
    setb    SCL             ;narastające zbocze SCL
    nop
    mov     C,SDA           ;odczyt linii danych
    rlc     A               ;wprowadzenie bitu do akumulatora na najmłodszą pozycję
    clr     SCL             ;opadające zbocze SCL
    djnz    B,shi_x3        ;następny bit
    pop     B               ;odtworzenie stanu rejestru B
    ret

;wysłanie potwierdzenia (stan niski linii SDA)
ACK:
    clr     SDA             ;wysłanie ACK
    nop                     ;czas na ustalenie
    setb    SCL             ;narastające zbocze sygnału SCL
    nop                     ;czas na ustalenie
    clr     SCL             ;opadające zbocze SCL
    ret

;wysłanie "nie-potwierdzenia"
NAK:
    setb    SDA             ;ustawienie NAK
    nop                     ;czas na ustalenie
    setb    SCL             ;narastające zbocze sygnału SCL
    nop                     ;czas na ustalenie
    clr     SCL             ;opadające zbocze SCL
    ret
END

http://www.tomaszbogusz.blox.pl/

ZałącznikWielkość
Źródło programu z artykułu (RASM-51)17.12 KB

Dodaj nowy komentarz

Zawartość pola nie będzie udostępniana publicznie.