Opis układu
W tym poście przedstawię program pozwalający na obsługę pamięci AT45DB161E oraz D. Kod powinien być podobny do innych kości z rodziny AT45DB.
Komunikacja z mikrokontrolerem odbywa się poprzez interfejs SPI. Maksymalna częstotliwość taktowania wynosi 85MHz.
Rys. 1. Opis wyprowadzeń
Podłączenie
Podłączenie do układu jest właściwie standardowe. Należy wybrać odpowiedni interfejs, ja wykorzytuje SPI1 z zestawem pinów zdefiniowanych poniżej w definicjach. Podłączenie jest dosyć standardowe. Pin WP można podłączyć do masy poprzez rezystor 10kOhm. Pin Reset należy podłączyć do 3.3V. Dodatkowo nie należy zapominać o filtrowaniu zasilania 100nF. Linie SCK, SO, SI oraz CD można połączyć z mikrokontrolerem poprzez rezystory 220 Ohm.
Programowanie
Do obsługi włączenie SPI wykorzystuje biblioteki HAL-a, natomiast nie powinno być żadnego problemu z przeniesieniem programu na zwykłe. Wystarczy tylko zmienić sposób włączenie interfejsu komunikacyjnego oraz ustawienia pinów. Reszta elementów pozostanie bez zmian.
Definicje potrzebnych rejestrów oraz wyprowadzeń:
SPI, wyprowadzenia oraz ustawianie pinów:
- #define AT45DB_FLASH_SPI SPI1
- #define AT45DB_FLASH_SPI_PORT GPIOA
- #define AT45DB_FLASH_SPI_MISO_PIN GPIO_Pin_6
- #define AT45DB_FLASH_SPI_MOSI_PIN GPIO_Pin_7
- #define AT45DB_FLASH_SPI_CLK_PIN GPIO_Pin_5
- #define AT45DB_FLASH_CONTROL_PORT GPIOB
- #define AT45DB_FLASH_CONTROL_CS_PIN GPIO_Pin_2
- #define AT45DB_FLASH_CS_HI HAL_GPIO_WritePin(AT45DB_FLASH_CONTROL_PORT, AT45DB_FLASH_CONTROL_CS_PIN , 1 )
- #define AT45DB_FLASH_CS_LO HAL_GPIO_WritePin( AT45DB_FLASH_CONTROL_PORT, AT45DB_FLASH_CONTROL_CS_PIN , 0 )
Oraz dla pinu WP jak by ktoś miał ochotę nim też sterować, u mnie jest on na stałe podłączony do masy.
Kolejnym elementem są definicje nazw wszystkich wymaganych rejestrów:
- #define AT45DB161D_PAGE_READ 0xD2 //Odczyt jednej strony (528 lub 512 bajtów)
- #define AT45DB161D_CONTINUOUS_READ_LOW_FREQ 0x03 //Ciągły odczyt
- #define AT45DB161D_CONTINUOUS_READ_HIGH_FREQ 0x0B //Ciągły odczyt z wysoką częstotliwością
- #define AT45DB161D_BUFFER_1_READ_LOW_FREQ 0xD1 //Odczyt bufora 1 z niską częstotliwością
- #define AT45DB161D_BUFFER_2_READ_LOW_FREQ 0xD3 //Odczyt bufora 2 z niską częstotliwością
- #define AT45DB161D_BUFFER_1_READ 0xD4 //Odczyt pierwszego bufora
- #define AT45DB161D_BUFFER_2_READ 0xD6 //Odczyt drugiego bufora
Zapis danych do pamięci oraz ich kasowanie:
- #define AT45DB161D_BUFFER_1_WRITE 0x84 //Zapis do bufora pierwszego
- #define AT45DB161D_BUFFER_2_WRITE 0x87 //Zapis do bufora drugiego
- #define AT45DB161D_BUFFER_1_TO_PAGE_WITH_ERASE 0x83 //Buffer 1 do strony w pamięci z kasowaniem
- #define AT45DB161D_BUFFER_2_TO_PAGE_WITH_ERASE 0x86 //Buffer 2 do strony w pamięci z kasowaniem
- #define AT45DB161D_BUFFER_1_TO_PAGE_WITHOUT_ERASE 0x88 //Buffor 1 do strony w pamięci bez kasowania
- #define AT45DB161D_BUFFER_2_TO_PAGE_WITHOUT_ERASE 0x89 //Buffor 2 do strony w pamięci bez kasowania
- #define AT45DB161D_PAGE_ERASE 0x81 //Kasowanie strony
- #define AT45DB161D_BLOCK_ERASE 0x50 //Kasowanie bloku
- #define AT45DB161D_SECTOR_ERASE 0x7C //Kasowanie sektora
- //Sekwencja 4 bajtów potrzebnych do kasowania całego chipu
- #define AT45DB161D_CHIP_ERASE_0 0xC7
- #define AT45DB161D_CHIP_ERASE_1 0x94
- #define AT45DB161D_CHIP_ERASE_2 0x80
- #define AT45DB161D_CHIP_ERASE_3 0x9A
- #define AT45DB161D_PAGE_THROUGH_BUFFER_1 0x82 //Programowanie strony za pomocą bufora 1
- #define AT45DB161D_PAGE_THROUGH_BUFFER_2 0x85 //Programowanie strony za pomocą bufora 2
Występują także komendy odpowiedzialne za różnego rodzaju dodatkowe informacje:
- #define AT45DB161D_STATUS_REGISTER_READ 0xD7 //Odczytanie statusu układu
- #define AT45DB161D_READ_MANUFACTURER_AND_DEVICE_ID 0x9F //Odczytanie informacji o układzie producent oraz id urzadzenia
- #define AT45DB161D_DEEP_POWER_DOWN 0xB9 //Wprowadzenie w tryb niższego poboru prądu
- #define AT45DB161D_RESUME_FROM_DEEP_POWER_DOWN 0xAB //Wznowienie pracy układu
- #define AT45DB161D_ULTRA_DEEP_POWER_DOWN 0x79 //Oszczedzanie energii
- #define AT45DB161D_TRANSFER_PAGE_TO_BUFFER_1 0x53 //Transfer strony z pamięci do buffora 1
- #define AT45DB161D_TRANSFER_PAGE_TO_BUFFER_2 0x55 //Transfer strony z pamięci do buffora 2
- #define AT45DB161D_COMPARE_PAGE_TO_BUFFER_1 0x60 //Porownanie danych z pamięci do buffora 1
- #define AT45DB161D_COMPARE_PAGE_TO_BUFFER_2 0x61 //Porownanie danych z pamieci do buffora 2
- #define AT45DB161D_AUTO_PAGE_REWRITE_THROUGH_BUFFER_1 0x58 //Auto Page Rewrite through Buffer 1
- #define AT45DB161D_AUTO_PAGE_REWRITE_THROUGH_BUFFER_2 0x59 //Auto Page Rewrite through Buffer 2
Dostępny jest też cały zestaw rejestrów odpowiedzialny za ustawianie zabezpieczeń oraz opcji dostępu do poszczególnych części pamięci.
- //Wyczyszczenie rejestrów z informacjami o zabezpieczeniach rejestrow
- #define AT45DB161D_ERASE_SECTOR_PROTECTION_REGISTER_0 0x3D
- #define AT45DB161D_ERASE_SECTOR_PROTECTION_REGISTER_0 0x2A
- #define AT45DB161D_ERASE_SECTOR_PROTECTION_REGISTER_0 0x7F
- #define AT45DB161D_ERASE_SECTOR_PROTECTION_REGISTER_0 0xCF
- //Ustawienie rejestrow
- #define AT45DB161D_PROGRAM_SECTOR_PROTECTION_REGISTER_0 0x3D
- #define AT45DB161D_PROGRAM_SECTOR_PROTECTION_REGISTER_1 0x2A
- #define AT45DB161D_PROGRAM_SECTOR_PROTECTION_REGISTER_2 0x7F
- #define AT45DB161D_PROGRAM_SECTOR_PROTECTION_REGISTER_3 0xFC
- //Zabezpieczenie przed dostępem do poszczegolnych rejestrow
- #define AT45DB161D_SECTOR_LOCKDOWN_0 0X3D
- #define AT45DB161D_SECTOR_LOCKDOWN_1 0x2A
- #define AT45DB161D_SECTOR_LOCKDOWN_2 0x7F
- #define AT45DB161D_SECTOR_LOCKDOWN_3 0x30
Za włączenie bądź wyłączenie ochrony poszczególnych sektorów:
- //Wlaczenie ochrony dla poszczegolnych sektorow
- #define AT45DB161D_ENABLE_SECTOR_PROTECTION_0 0x3D
- #define AT45DB161D_ENABLE_SECTOR_PROTECTION_1 0x2A
- #define AT45DB161D_ENABLE_SECTOR_PROTECTION_2 0x7F
- #define AT45DB161D_ENABLE_SECTOR_PROTECTION_3 0xA9
- //Wylaczenie ochrony poszczegolnych sektorow
- #define AT45DB161D_DISABLE_SECTOR_PROTECTION_0 0x3D
- #define AT45DB161D_DISABLE_SECTOR_PROTECTION_1 0x2A
- #define AT45DB161D_DISABLE_SECTOR_PROTECTION_2 0x7F
- #define AT45DB161D_DISABLE_SECTOR_PROTECTION_3 0x9A
Włączenie SPI oraz pinów SCK, MISO, MOSI, CS:
- void AT45DB_INIT_GPIO_SPI()
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- SPI_HandleTypeDef SPIHandle;
- SPIHandle.Instance = SPI1;
- //Wlaczenie zegara dla GPIOA & GPIOB
- __GPIOA_CLK_ENABLE();
- __GPIOB_CLK_ENABLE();
- //Wlaczenie zegara dla SPI1
- __HAL_RCC_SPI1_CLK_ENABLE();
- //SPI1
- //PA5 ------> SPI1_SCK
- //PA6 ------> SPI1_MISO
- //PA7 ------> SPI1_MOSI
- GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
- GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- //CS
- GPIO_InitStruct.Pin = AT45DB_FLASH_CONTROL_CS_PIN ;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
- HAL_GPIO_Init(AT45DB_FLASH_CONTROL_PORT, &GPIO_InitStruct);
- SPIHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
- SPIHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;
- SPIHandle.Init.Mode = SPI_MODE_MASTER;
- SPIHandle.Init.Direction = SPI_DIRECTION_2LINES;
- SPIHandle.Init.DataSize = SPI_DATASIZE_8BIT;
- SPIHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
- SPIHandle.Init.CRCPolynomial = 7;
- SPIHandle.Init.TIMode = SPI_TIMODE_DISABLE;
- SPIHandle.Init.CLKPolarity = SPI_POLARITY_LOW;
- SPIHandle.Init.CLKPhase = SPI_PHASE_1EDGE;
- SPIHandle.Init.NSS = SPI_NSS_SOFT;
- //Wylaczenie SPI
- __HAL_SPI_DISABLE(&SPIHandle);
- //Wprowadzenie ustawien
- HAL_SPI_Init(&SPIHandle);
- //Wlaczenie SPI
- __HAL_SPI_ENABLE(&SPIHandle);
- }
Przebieg poszczególnych sygnałów z dokumentacji producenta (Adesco):
Rys. Odczytanie danych producenta oraz układu
Jak widać na rysunku w pierwszej kolejności należy zmienić stan linii CS z wysokiego na niski. następnie wysyłany jest kod 0x9F, który informuje układ, że chcemy odczytać z niego dane dotyczące producent, identyfikatora itp. Uzyskuje się to poprzez wysyłanie do niego danych tzw. dummy bits, przeważnie 0x00 lub 0xFF. Po ich wysłaniu w odpowiedzi otrzymuje się potrzebne informacje.
Po zakończeniu transmisji zmieniany jest stan na linii CS z niskiego na wysoki.
Odczytanie ID producenta oraz układu wygląda następująco:
- uint32_t AT45DB_READ_MANUF_DEVIC_ID()
- {
- uint32_t id_from_device = 0;
- //CS najpierw na HI, potem na LOW
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI,AT45DB161D_READ_MANUFACTURER_AND_DEVICE_ID);
- id_from_device |= SPI1_SEND_DATA( AT45DB_FLASH_SPI , 0x00) << 24; //Revision
- id_from_device |= SPI1_SEND_DATA( AT45DB_FLASH_SPI , 0x00) << 16; //Subcode
- id_from_device |= SPI1_SEND_DATA( AT45DB_FLASH_SPI , 0x00) << 8; //Family
- id_from_device |= SPI1_SEND_DATA( AT45DB_FLASH_SPI , 0x00); //Manufacturer
- AT45DB_FLASH_CS_HI;
- return id_from_device;
- }
Odczytanie statusu układu:
- uint8_t AT45DB_READ_STATUS_REGISTER()
- {
- uint8_t status;
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_STATUS_REGISTER_READ);
- status = SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
- AT45DB_FLASH_CS_HI;
- return status;
- }
Status register jest to dwu bajtowy rejestr przechowujący informacje o stanie. Gotowość układu tzn. czy jest zajęty czy w stanie gotowości jest określany przez bit 7. Jeśli jest on ustawiony na 1 to układ jest gotowy na przeprowadzanie operacji, natomiast jeśli jest on ustawiony na 0, wtedy pamięć przeprowadza jakieś operacje.
Bit 6 natomiast służy do porównywania danych z buforem (compare 0x40). Jest on rezultatem operacji Memory Page to Buffer. Jeśli dane są sobie równe to ustawiony będzie w to miejsce 0. W przypadku gdy zwrócona zostanie wartość 1, wtedy przynajmniej 1 byte danych jest błędny.
Bity 5,4,3 oraz 2 ustawiają gęstość pamięci flash.
Pierwszy bit określa czy ochrona sektorów (sector protection) jest wyłączona (0) lub włączona (1).
Bit 0 pozwala na określenie jakiej wielkości są strony, do których wgrywane są dane. Ustawienie 1 pozwala na uzyskanie stron o wielkości 512 bitów, natomiast domyśla wartość 0 wynosi 528 bitów.
Reset układu:
- void AT45DB_CHIP_ERASE()
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_CHIP_ERASE_0);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_CHIP_ERASE_1);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_CHIP_ERASE_2);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_CHIP_ERASE_3);
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- while(!(AT45DB_READ_STATUS_REGISTER() & READY_BUSY)){ __NOP; }
- AT45DB_FLASH_CS_HI;
- }
W pierwszej kolejności ustawiony jest pin CS na wysoki, następnie na niski. Po czym zostaje wysłana następująca sekwencja danych 0xC7, 0x94, 0x80 oraz 0x9A, Po przesłaniu danych zmieniany jest pin CS na wysoki po czym znowu na niski. Po tym następuje sprawdzanie rejestru statusu, aż do momentu zakończenia kasowania. Ta komenda pozwala na wyczyszczenie całej pamięci czyli 16Mbit. Pominięte zostają rejestry chronione oraz niedostępne.
Następna funkcja pozwala na wykasowanie pojedynczego sektora z danymi. Opisywana przeze mnie pamieć posiada 16 sektorów numerowanych od 0 do 15. Można kasować sektory pojedynczo. Schemat ich rozmieszczenia znajduje się w dokumentacji (strona 5).
- void AT45DB_SECTORE_ERASE(uint8_t sector)
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_SECTOR_ERASE);
- if((sector == 0x0a) || (sector == 0x0b))
- {
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,((sector & 0x01) << 4));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
- }
- else
- {
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,sector << 1);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
- }
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- while(!(AT45DB_READ_STATUS_REGISTER() & READY_BUSY)){ __NOP; }
- AT45DB_FLASH_CS_HI;
- }
Wyczyszczenie bloku danych:
- void AT45DB_BLOCK_ERASE(uint16_t block)
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BLOCK_ERASE);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(block >> 3));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(block << 5));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- while(!(AT45DB_READ_STATUS_REGISTER() & 0x80)){ __NOP(); }
- }
Kasowanie jednej strony z danymi:
- void AT45DB_PAGE_ERASE(uint16_t page)
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- TM_SPI_Send(AT45DB_FLASH_SPI ,AT45DB161D_PAGE_ERASE);
- TM_SPI_Send(AT45DB_FLASH_SPI ,(uint8_t)(page >> 6));
- TM_SPI_Send(AT45DB_FLASH_SPI ,(uint8_t)(page << 2));
- TM_SPI_Send(AT45DB_FLASH_SPI ,0x00);
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- while(!(AT45DB_READ_STATUS_REGISTER() & 0x80)){ __NOP(); }
- }
Aby wykonać kasowanie należy wysłać adres czyli 0x81 po czym należy wysłać trzy bajty adresu z trzema tzw. dummy bits. 12 bitów adresu od A20 do A9, co informuje która strona ma być wymazana w pamięci. Po nich trzeba przesłać 9 bitów dummy.
Zapisanie danych do buffora:
- void AT45DB_BUFFER_WRITE(uint8_t bufferNum, uint32_t offset)
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- if(bufferNum == 1)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_1_WRITE); }
- else
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_WRITE); }
- //14 bitów dummy
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
- //Reszta dummy + offset
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(offset >> 8));
- //bit 7 do 0 z ofsetu
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(offset & 0xff));
- }
Odczyt danych z buffora
- void AT45DB_READ_BUFFER(uint8_t bufferNum, uint16_t offset, uint8_t low)
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- if(bufferNum == 1 && low == 1)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_1_READ_LOW_FREQ); }
- else if(bufferNum == 1 && low == 0)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_1_READ); }
- else if{bufferNum == 2 && low == 1)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_READ_LOW_FREQ); }
- else if{bufferNum == 2 && low == 0)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_READ); }
- TM_SPI_Send(AT45DB_FLASH_SPI ,0x00);
- TM_SPI_Send(AT45DB_FLASH_SPI ,(uint8_t)(offset >> 8));
- TM_SPI_Send(AT45DB_FLASH_SPI ,(uint8_t)(offset & 0xff));
- }
Zapis bufora do jednej strony w pamięci:
- void AT45DB_BUFFER_TO_PAGE(uint8_t bufferNum, uint16_t page, uint8_t erase)
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- if(erase == 1 && bufferNum == 1)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI , AT45DB161D_BUFFER_1_TO_PAGE_WITH_ERASE); }
- else if(erase == 1 && bufferNum != 1)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_TO_PAGE_WITH_ERASE); }
- else if(erase != 1 && bufferNum == 1)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI , AT45DB161D_BUFFER_1_TO_PAGE_WITHOUT_ERASE); }
- else if(erase != 1 && bufferNum != 1)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_TO_PAGE_WITHOUT_ERASE); }
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page >> 6));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page << 2));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
- AT45DB_FLASH_CS_HI;
- while(!(AT45DB_READ_STATUS_REGISTER() & 0x80)){ __NOP(); }
- }
Zapis strony z danymi do bufora:
- void AT45DB_PAGE_TO_BUFFER(uint16_t page, uint8_t bufferNum)
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- if(bufferNum == 1)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI , AT45DB161D_TRANSFER_PAGE_TO_BUFFER_1); }
- else
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_TRANSFER_PAGE_TO_BUFFER_2); }
- //2 bity dummy, 12 bitow addressowych od PA11 do PA0, 10 bitow dummy
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page >> 6));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page << 2));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- while(!(AT45DB_READ_STATUS_REGISTER() & 0x80)){ __NOP(); }
- }
Odczyt danych ze strony w pamięci:
- uint32_t AT45DB_READ_MAIN_MEMORY_PAGE(uint16_t page, uint32_t offset, uint8_t *tablica)
- {
- uint8_t *data_page = tablica;
- int licznik;
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI, AT45DB161D_PAGE_READ);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI, (uint8_t)(page >> 6));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI, (page << 2) | (offset >> 8));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI, (uint8_t)(offset & 0xff));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
- for(licznik=0; licznik<528; licznik++)
- { data_page[licznik] |= TM_SPI_Send(AT45DB_FLASH_SPI, 0x00); }
- AT45DB_FLASH_CS_HI;
- }
Porównanie strony do bufora:
- uint8_t AT45DB_COMPARE_PAGE_TO_BUFFER(uint16_t page, uint8_t bufferNum)
- {
- uint8_t stat;
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- if(bufferNum == 1)
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI , AT45DB161D_COMPARE_PAGE_TO_BUFFER_1); }
- else
- { SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_COMPARE_PAGE_TO_BUFFER_2); }
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page >> 6));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page << 2));
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
- AT45DB_FLASH_CS_HI;
- while(!((stat = AT45DB_READ_STATUS_REGISTER()) & 0x80)){ __NOP; }
- return ((status & 0x40) ? 0 : 1);
- }
Systemowy reset urządzenia:
- void AT45DB_SOFTWARE_RESET()
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB_SOFTWARE_RESET_ADDR_1);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB_SOFTWARE_RESET_ADDR_2);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB_SOFTWARE_RESET_ADDR_3);
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB_SOFTWARE_RESET_ADDR_4);
- AT45DB_FLASH_CS_HI;
- //Opoznienie min. 35us
- Delayms(1);
- }
Włączenie trybu oszczędzania energii. Podczas niego pobór prądu wynosi 3uA. Z tego trybu możliwe jest wybudzenie tylko poprzez wysłanie komendy pozwalającej na przywrócenie układu do normalnej pracy. Wejście w ten tryb możliwe jest tylko podczas normalnej pracy. W trybie zawieszanie układu nie ma możliwości jej wykonywania na żadnym buforze.
- void AT45DB_DEEP_POWER_DOWN()
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_DEEP_POWER_DOWN);
- //Po zmianie stanu na pinie CS nastepuje wejscie w stan obnizonego
- //poboru mocy
- AT45DB_FLASH_CS_HI;
- //Opoznienie 200ms
- Delayms(200);
- }
Wyjście z trybu oszczędzania energii:
- void AT45DB_RESUME_FROM_DEEP_POWER_DOWN()
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_RESUME_FROM_DEEP_POWER_DOWN);
- AT45DB_FLASH_CS_HI;
- //Odczekanie 200 ms
- Delayms(200);
- }
Drugi tryb oszczędzania energii jaki można wywołać jest Ultra Deep Power Down. W nim pobór prądu wynosi 400nA.
- void AT45DB_ULTRA_DEEP_POWER_DOWN()
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_ULTRA_DEEP_POWER_DOWN);
- AT45DB_FLASH_CS_HI;
- //Odczekanie 200 ms
- Delayms(200);
- }
Wyjście z tego trybu jest wykonywane poprzez zmianę stanu na pinie CS na niski na określoną ilość czasu, opisywaną w dokumentacji jako tcslu. Powinien on trwać przynajmniej 20 ns. Wobec tego zostanie wywołany w funkcji na około 1us. Po tym czasie tryb Standby zostanie wywołany po czasie wynoszącym maksymalnie 180us
- void AT45DB_ULTRA_DEEP_POWER_DOWN()
- {
- AT45DB_FLASH_CS_HI;
- AT45DB_FLASH_CS_LO;
- Delayus(1);
- AT45DB_FLASH_CS_HI;
- Delayus(300);
- }
W celu zaprogramowania większej ilości danych buforem można wykorzystać następującą funkcję:
- void AT45DB_WRITE_FLASH(uint32_t addres, uint8_t *data, uint16_t lenght)
- {
- int licznik = 0;
- //Page_Size domyślnie wynosi 528bajtow
- AT45DB_BUFFER_WRITE(WRITE_BUFFER, addres % PAGE_SIZE);
- for (licznik = 0; licznik < (int)lenght; licznik++)
- {
- SPI1_SEND_DATA(AT45DB_FLASH_SPI, *data++);
- }
- AT45DB_BUFFER_TO_PAGE(WRITE_BUFFER, addres / PAGE_SIZE, 1);
- }
Jako adres podaje się lokalizacje do jakiej ma zostać zapisana, data podaje się buffor danych, który ma być wysłany do pamięci, ostatni parametr natomiast podaje wielkość wprowadzanego buffora, czyli liczbę elementów.
Poniżej funkcja odczytująca dane z pamięci:
- void AT45DB_READ_FLASH(uint32_t addres, uint8_t *data, int lenght)
- {
- int licznik = 0;
- uint8_t *d=data;
- AT45DB_PAGE_TO_BUFFER(addres / PAGE_SIZE, READ_BUFFER);
- AT45DB_READ_BUFFER(READ_BUFFER,addres % PAGE_SIZE, 1);
- for (licznik = 0x00; licznik < lenght; licznik++)
- {
- *d++ = SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0xFF);
- }
- }
W celu obsługi układu należy najpierw sprawdzić czy udało się uzyskać komunikację poprzez odczyt ID oraz statusu. Po tych operacjach można wykonać czyszczenie pamięci. Następnie można przejść do programowania poprzez podanie danych do bufora, który następnie będzie programował całość lub część.
Dokumentacja:
[1] Datasheet