środa, 7 września 2016

[15] STM32F4 - Programowanie pamięci Flash AT45DB161

Ten post chciałbym poświęcić na opis sposobu programowania pamięci AT45DB161.

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:

  1. #define AT45DB_FLASH_SPI                SPI1
  2. #define AT45DB_FLASH_SPI_PORT           GPIOA
  3. #define AT45DB_FLASH_SPI_MISO_PIN       GPIO_Pin_6
  4. #define AT45DB_FLASH_SPI_MOSI_PIN       GPIO_Pin_7
  5. #define AT45DB_FLASH_SPI_CLK_PIN        GPIO_Pin_5
  6. #define AT45DB_FLASH_CONTROL_PORT       GPIOB
  7. #define AT45DB_FLASH_CONTROL_CS_PIN     GPIO_Pin_2

  1. #define AT45DB_FLASH_CS_HI  HAL_GPIO_WritePin(AT45DB_FLASH_CONTROL_PORT, AT45DB_FLASH_CONTROL_CS_PIN , 1 )
  2. #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:

  1. #define AT45DB161D_PAGE_READ                    0xD2    //Odczyt jednej strony (528 lub 512 bajtów)
  2. #define AT45DB161D_CONTINUOUS_READ_LOW_FREQ     0x03    //Ciągły odczyt
  3. #define AT45DB161D_CONTINUOUS_READ_HIGH_FREQ    0x0B    //Ciągły odczyt z wysoką częstotliwością
  4. #define AT45DB161D_BUFFER_1_READ_LOW_FREQ       0xD1    //Odczyt bufora 1 z niską częstotliwością
  5. #define AT45DB161D_BUFFER_2_READ_LOW_FREQ       0xD3    //Odczyt bufora 2 z niską częstotliwością
  6. #define AT45DB161D_BUFFER_1_READ                0xD4    //Odczyt pierwszego bufora
  7. #define AT45DB161D_BUFFER_2_READ                0xD6    //Odczyt drugiego bufora

Zapis danych do pamięci oraz ich kasowanie:

  1. #define AT45DB161D_BUFFER_1_WRITE                   0x84    //Zapis do bufora pierwszego
  2. #define AT45DB161D_BUFFER_2_WRITE                   0x87    //Zapis do bufora drugiego
  3. #define AT45DB161D_BUFFER_1_TO_PAGE_WITH_ERASE      0x83    //Buffer 1 do strony w pamięci z kasowaniem
  4. #define AT45DB161D_BUFFER_2_TO_PAGE_WITH_ERASE      0x86    //Buffer 2 do strony w pamięci z kasowaniem
  5. #define AT45DB161D_BUFFER_1_TO_PAGE_WITHOUT_ERASE   0x88    //Buffor 1 do strony w pamięci bez kasowania
  6. #define AT45DB161D_BUFFER_2_TO_PAGE_WITHOUT_ERASE   0x89    //Buffor 2 do strony w pamięci bez kasowania
  7. #define AT45DB161D_PAGE_ERASE                       0x81    //Kasowanie strony
  8. #define AT45DB161D_BLOCK_ERASE                      0x50    //Kasowanie bloku
  9. #define AT45DB161D_SECTOR_ERASE                     0x7C    //Kasowanie sektora
  10. //Sekwencja 4 bajtów potrzebnych do kasowania całego chipu
  11. #define AT45DB161D_CHIP_ERASE_0                     0xC7           
  12. #define AT45DB161D_CHIP_ERASE_1                     0x94       
  13. #define AT45DB161D_CHIP_ERASE_2                     0x80       
  14. #define AT45DB161D_CHIP_ERASE_3                     0x9A
  15.    
  16. #define AT45DB161D_PAGE_THROUGH_BUFFER_1            0x82    //Programowanie strony za pomocą bufora 1
  17. #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:

  1. #define AT45DB161D_STATUS_REGISTER_READ                 0xD7    //Odczytanie statusu układu
  2. #define AT45DB161D_READ_MANUFACTURER_AND_DEVICE_ID      0x9F    //Odczytanie informacji o układzie producent oraz id urzadzenia
  3. #define AT45DB161D_DEEP_POWER_DOWN                      0xB9    //Wprowadzenie w tryb niższego poboru prądu
  4. #define AT45DB161D_RESUME_FROM_DEEP_POWER_DOWN          0xAB    //Wznowienie pracy układu
  5. #define AT45DB161D_ULTRA_DEEP_POWER_DOWN                0x79    //Oszczedzanie energii
  6. #define AT45DB161D_TRANSFER_PAGE_TO_BUFFER_1            0x53    //Transfer strony z pamięci do buffora 1
  7. #define AT45DB161D_TRANSFER_PAGE_TO_BUFFER_2            0x55    //Transfer strony z pamięci do buffora 2
  8. #define AT45DB161D_COMPARE_PAGE_TO_BUFFER_1             0x60    //Porownanie danych z pamięci do buffora 1
  9. #define AT45DB161D_COMPARE_PAGE_TO_BUFFER_2             0x61    //Porownanie danych z pamieci do buffora 2
  10. #define AT45DB161D_AUTO_PAGE_REWRITE_THROUGH_BUFFER_1   0x58    //Auto Page Rewrite through Buffer 1
  11. #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.

  1. //Wyczyszczenie rejestrów z informacjami o zabezpieczeniach rejestrow
  2. #define AT45DB161D_ERASE_SECTOR_PROTECTION_REGISTER_0 0x3D
  3. #define AT45DB161D_ERASE_SECTOR_PROTECTION_REGISTER_0 0x2A
  4. #define AT45DB161D_ERASE_SECTOR_PROTECTION_REGISTER_0 0x7F
  5. #define AT45DB161D_ERASE_SECTOR_PROTECTION_REGISTER_0 0xCF
  6. //Ustawienie rejestrow
  7. #define AT45DB161D_PROGRAM_SECTOR_PROTECTION_REGISTER_0 0x3D
  8. #define AT45DB161D_PROGRAM_SECTOR_PROTECTION_REGISTER_1 0x2A
  9. #define AT45DB161D_PROGRAM_SECTOR_PROTECTION_REGISTER_2 0x7F
  10. #define AT45DB161D_PROGRAM_SECTOR_PROTECTION_REGISTER_3 0xFC
  11. //Zabezpieczenie przed dostępem do poszczegolnych rejestrow
  12. #define AT45DB161D_SECTOR_LOCKDOWN_0 0X3D
  13. #define AT45DB161D_SECTOR_LOCKDOWN_1 0x2A
  14. #define AT45DB161D_SECTOR_LOCKDOWN_2 0x7F
  15. #define AT45DB161D_SECTOR_LOCKDOWN_3 0x30

Za włączenie bądź wyłączenie ochrony poszczególnych sektorów:

  1. //Wlaczenie ochrony dla poszczegolnych sektorow
  2. #define AT45DB161D_ENABLE_SECTOR_PROTECTION_0 0x3D
  3. #define AT45DB161D_ENABLE_SECTOR_PROTECTION_1 0x2A
  4. #define AT45DB161D_ENABLE_SECTOR_PROTECTION_2 0x7F
  5. #define AT45DB161D_ENABLE_SECTOR_PROTECTION_3 0xA9
  6. //Wylaczenie ochrony poszczegolnych sektorow
  7. #define AT45DB161D_DISABLE_SECTOR_PROTECTION_0 0x3D
  8. #define AT45DB161D_DISABLE_SECTOR_PROTECTION_1 0x2A
  9. #define AT45DB161D_DISABLE_SECTOR_PROTECTION_2 0x7F
  10. #define AT45DB161D_DISABLE_SECTOR_PROTECTION_3 0x9A

Włączenie SPI oraz pinów SCK, MISO, MOSI, CS:

  1. void AT45DB_INIT_GPIO_SPI()
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct;
  4.     SPI_HandleTypeDef SPIHandle;
  5.    
  6.     SPIHandle.Instance = SPI1;
  7.    
  8.     //Wlaczenie zegara dla GPIOA & GPIOB
  9.     __GPIOA_CLK_ENABLE();
  10.     __GPIOB_CLK_ENABLE();
  11.    
  12.     //Wlaczenie zegara dla SPI1
  13.     __HAL_RCC_SPI1_CLK_ENABLE();
  14.    
  15.     //SPI1
  16.     //PA5     ------> SPI1_SCK
  17.     //PA6     ------> SPI1_MISO
  18.     //PA7     ------> SPI1_MOSI
  19.    
  20.     GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
  21.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  22.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  23.     GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
  24.     GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
  25.     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  26.    
  27.     //CS
  28.     GPIO_InitStruct.Pin = AT45DB_FLASH_CONTROL_CS_PIN ;
  29.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  30.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  31.     GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
  32.     HAL_GPIO_Init(AT45DB_FLASH_CONTROL_PORT, &GPIO_InitStruct);
  33.    
  34.     SPIHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  35.     SPIHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;
  36.     SPIHandle.Init.Mode = SPI_MODE_MASTER; 
  37.     SPIHandle.Init.Direction = SPI_DIRECTION_2LINES;
  38.     SPIHandle.Init.DataSize = SPI_DATASIZE_8BIT;
  39.    
  40.     SPIHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  41.     SPIHandle.Init.CRCPolynomial = 7;
  42.     SPIHandle.Init.TIMode = SPI_TIMODE_DISABLE;
  43.     SPIHandle.Init.CLKPolarity = SPI_POLARITY_LOW;         
  44.     SPIHandle.Init.CLKPhase = SPI_PHASE_1EDGE;     
  45.     SPIHandle.Init.NSS = SPI_NSS_SOFT;
  46.    
  47.     //Wylaczenie SPI
  48.     __HAL_SPI_DISABLE(&SPIHandle);
  49.    
  50.     //Wprowadzenie ustawien
  51.     HAL_SPI_Init(&SPIHandle);
  52.    
  53.     //Wlaczenie SPI
  54.     __HAL_SPI_ENABLE(&SPIHandle);
  55. }

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:

  1. uint32_t AT45DB_READ_MANUF_DEVIC_ID()
  2. {
  3.     uint32_t id_from_device = 0;
  4.    
  5.     //CS najpierw na HI, potem na LOW
  6.     AT45DB_FLASH_CS_HI;
  7.     AT45DB_FLASH_CS_LO;
  8.    
  9.     SPI1_SEND_DATA(AT45DB_FLASH_SPI,AT45DB161D_READ_MANUFACTURER_AND_DEVICE_ID);
  10.    
  11.     id_from_device |= SPI1_SEND_DATA( AT45DB_FLASH_SPI , 0x00) << 24;    //Revision
  12.     id_from_device |= SPI1_SEND_DATA( AT45DB_FLASH_SPI , 0x00) << 16;    //Subcode
  13.     id_from_device |= SPI1_SEND_DATA( AT45DB_FLASH_SPI , 0x00) << 8;     //Family
  14.     id_from_device |= SPI1_SEND_DATA( AT45DB_FLASH_SPI , 0x00);          //Manufacturer
  15.    
  16.     AT45DB_FLASH_CS_HI;
  17.    
  18.     return id_from_device;
  19. }

Odczytanie statusu układu:

  1. uint8_t AT45DB_READ_STATUS_REGISTER()
  2. {
  3.     uint8_t status;
  4.     AT45DB_FLASH_CS_HI;
  5.     AT45DB_FLASH_CS_LO;
  6.        
  7.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_STATUS_REGISTER_READ);
  8.     status = SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
  9.    
  10.     AT45DB_FLASH_CS_HI;
  11.    
  12.     return status;
  13. }

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:

  1. void AT45DB_CHIP_ERASE()
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_CHIP_ERASE_0);
  6.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_CHIP_ERASE_1);
  7.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_CHIP_ERASE_2);
  8.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_CHIP_ERASE_3);
  9.    
  10.     AT45DB_FLASH_CS_HI;
  11.     AT45DB_FLASH_CS_LO;
  12.     while(!(AT45DB_READ_STATUS_REGISTER() & READY_BUSY)){ __NOP; }
  13.        
  14.     AT45DB_FLASH_CS_HI;
  15. }

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).

  1. void AT45DB_SECTORE_ERASE(uint8_t sector)
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_SECTOR_ERASE);
  6.     if((sector == 0x0a) || (sector == 0x0b))
  7.     {
  8.         SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
  9.         SPI1_SEND_DATA(AT45DB_FLASH_SPI ,((sector & 0x01) << 4));
  10.         SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
  11.     }
  12.     else
  13.     {
  14.         SPI1_SEND_DATA(AT45DB_FLASH_SPI ,sector << 1);
  15.         SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
  16.         SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
  17.     }
  18.                
  19.     AT45DB_FLASH_CS_HI;
  20.     AT45DB_FLASH_CS_LO;
  21.     while(!(AT45DB_READ_STATUS_REGISTER() & READY_BUSY)){ __NOP; }
  22.     AT45DB_FLASH_CS_HI;
  23. }

Wyczyszczenie bloku danych:

  1. void AT45DB_BLOCK_ERASE(uint16_t block)
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BLOCK_ERASE);
  6.    
  7.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(block >> 3));
  8.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(block << 5));
  9.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
  10.        
  11.     AT45DB_FLASH_CS_HI;
  12.     AT45DB_FLASH_CS_LO;
  13.     while(!(AT45DB_READ_STATUS_REGISTER() & 0x80)){ __NOP(); }
  14. }

Kasowanie jednej strony z danymi:

  1. void AT45DB_PAGE_ERASE(uint16_t page)
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.     TM_SPI_Send(AT45DB_FLASH_SPI ,AT45DB161D_PAGE_ERASE);
  6.    
  7.     TM_SPI_Send(AT45DB_FLASH_SPI ,(uint8_t)(page >> 6));
  8.     TM_SPI_Send(AT45DB_FLASH_SPI ,(uint8_t)(page << 2));
  9.     TM_SPI_Send(AT45DB_FLASH_SPI ,0x00);
  10.        
  11.     AT45DB_FLASH_CS_HI;
  12.     AT45DB_FLASH_CS_LO;
  13.     while(!(AT45DB_READ_STATUS_REGISTER() & 0x80)){ __NOP(); }
  14. }

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:

  1. void AT45DB_BUFFER_WRITE(uint8_t bufferNum, uint32_t offset)
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.     if(bufferNum == 1)
  6.     {   SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_1_WRITE); }
  7.     else
  8.     {   SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_WRITE); }
  9.    
  10.     //14 bitów dummy
  11.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
  12.     //Reszta dummy + offset
  13.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(offset >> 8));
  14.     //bit 7 do 0 z ofsetu
  15.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(offset & 0xff));
  16. }

Odczyt danych z buffora

  1. void AT45DB_READ_BUFFER(uint8_t bufferNum, uint16_t offset, uint8_t low)
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.     if(bufferNum == 1 && low == 1)
  6.     {   SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_1_READ_LOW_FREQ); }
  7.     else if(bufferNum == 1 && low == 0)
  8.     {   SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_1_READ); }
  9.     else if{bufferNum == 2 && low == 1)
  10.     {   SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_READ_LOW_FREQ); }
  11.     else if{bufferNum == 2 && low == 0)
  12.     {   SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_READ); }
  13.    
  14.     TM_SPI_Send(AT45DB_FLASH_SPI ,0x00);
  15.     TM_SPI_Send(AT45DB_FLASH_SPI ,(uint8_t)(offset >> 8));
  16.     TM_SPI_Send(AT45DB_FLASH_SPI ,(uint8_t)(offset & 0xff));
  17. }

Zapis bufora do jednej strony w pamięci:

  1. void AT45DB_BUFFER_TO_PAGE(uint8_t bufferNum, uint16_t page, uint8_t erase)
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.  
  6.     if(erase == 1 && bufferNum == 1)
  7.     {   SPI1_SEND_DATA(AT45DB_FLASH_SPI , AT45DB161D_BUFFER_1_TO_PAGE_WITH_ERASE);      }
  8.     else if(erase == 1 && bufferNum != 1)
  9.     {    SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_TO_PAGE_WITH_ERASE);      }
  10.     else if(erase != 1 && bufferNum == 1)
  11.     {    SPI1_SEND_DATA(AT45DB_FLASH_SPI , AT45DB161D_BUFFER_1_TO_PAGE_WITHOUT_ERASE);  }
  12.     else if(erase != 1 && bufferNum != 1)
  13.     {    SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_BUFFER_2_TO_PAGE_WITHOUT_ERASE);   }
  14.    
  15.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page >> 6));
  16.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page << 2));
  17.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
  18.    
  19.     AT45DB_FLASH_CS_HI;
  20.  
  21.     while(!(AT45DB_READ_STATUS_REGISTER() & 0x80)){  __NOP();   }
  22. }

Zapis strony z danymi do bufora:

  1. void AT45DB_PAGE_TO_BUFFER(uint16_t page, uint8_t bufferNum)
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.  
  6.     if(bufferNum == 1)
  7.     {    SPI1_SEND_DATA(AT45DB_FLASH_SPI , AT45DB161D_TRANSFER_PAGE_TO_BUFFER_1);   }
  8.     else
  9.     {    SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_TRANSFER_PAGE_TO_BUFFER_2);    }
  10.  
  11.     //2 bity dummy, 12 bitow addressowych od PA11 do PA0, 10 bitow dummy
  12.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page >> 6));
  13.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page << 2));
  14.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
  15.        
  16.     AT45DB_FLASH_CS_HI;
  17.     AT45DB_FLASH_CS_LO;
  18.    
  19.     while(!(AT45DB_READ_STATUS_REGISTER() & 0x80)){ __NOP(); }
  20. }

Odczyt danych ze strony w pamięci:

  1. uint32_t AT45DB_READ_MAIN_MEMORY_PAGE(uint16_t page, uint32_t offset, uint8_t *tablica)
  2. {
  3.     uint8_t *data_page = tablica;
  4.     int licznik;
  5.     AT45DB_FLASH_CS_HI;
  6.     AT45DB_FLASH_CS_LO;
  7.     SPI1_SEND_DATA(AT45DB_FLASH_SPI, AT45DB161D_PAGE_READ);
  8.    
  9.     SPI1_SEND_DATA(AT45DB_FLASH_SPI, (uint8_t)(page >> 6));
  10.     SPI1_SEND_DATA(AT45DB_FLASH_SPI, (page << 2) | (offset >> 8));
  11.     SPI1_SEND_DATA(AT45DB_FLASH_SPI, (uint8_t)(offset & 0xff));
  12.    
  13.     SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
  14.     SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
  15.     SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
  16.     SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0x00);
  17.        
  18.     for(licznik=0; licznik<528; licznik++)
  19.     { data_page[licznik] |= TM_SPI_Send(AT45DB_FLASH_SPI, 0x00);    }
  20.        
  21.     AT45DB_FLASH_CS_HI;
  22. }

Porównanie strony do bufora:

  1. uint8_t AT45DB_COMPARE_PAGE_TO_BUFFER(uint16_t page, uint8_t bufferNum)
  2. {
  3.     uint8_t stat;
  4.    
  5.     AT45DB_FLASH_CS_HI;
  6.     AT45DB_FLASH_CS_LO;
  7.  
  8.     if(bufferNum == 1)
  9.     {   SPI1_SEND_DATA(AT45DB_FLASH_SPI , AT45DB161D_COMPARE_PAGE_TO_BUFFER_1); }
  10.     else
  11.     {   SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_COMPARE_PAGE_TO_BUFFER_2); }
  12.    
  13.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page >> 6));
  14.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,(uint8_t)(page << 2));
  15.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,0x00);
  16.    
  17.     AT45DB_FLASH_CS_HI;
  18.  
  19.     while(!((stat = AT45DB_READ_STATUS_REGISTER()) & 0x80)){ __NOP; }
  20.          
  21.     return ((status & 0x40) ? 0 : 1);
  22. }

Systemowy reset urządzenia:

  1. void AT45DB_SOFTWARE_RESET()
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB_SOFTWARE_RESET_ADDR_1);
  6.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB_SOFTWARE_RESET_ADDR_2);
  7.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB_SOFTWARE_RESET_ADDR_3);
  8.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB_SOFTWARE_RESET_ADDR_4);
  9.    
  10.     AT45DB_FLASH_CS_HI;
  11.     //Opoznienie min. 35us
  12.     Delayms(1);
  13. }

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.

  1. void AT45DB_DEEP_POWER_DOWN()
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.    
  6.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_DEEP_POWER_DOWN);
  7.    
  8.     //Po zmianie stanu na pinie CS nastepuje wejscie w stan obnizonego
  9.     //poboru mocy
  10.     AT45DB_FLASH_CS_HI;
  11.    
  12.     //Opoznienie 200ms
  13.     Delayms(200);
  14. }

Wyjście z trybu oszczędzania energii:

  1. void AT45DB_RESUME_FROM_DEEP_POWER_DOWN()
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.    
  6.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_RESUME_FROM_DEEP_POWER_DOWN);
  7.    
  8.     AT45DB_FLASH_CS_HI;
  9.    
  10.     //Odczekanie 200 ms
  11.     Delayms(200);
  12. }

Drugi tryb oszczędzania energii jaki można wywołać jest Ultra Deep Power Down. W nim pobór prądu wynosi 400nA.

  1. void AT45DB_ULTRA_DEEP_POWER_DOWN()
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.    
  6.     SPI1_SEND_DATA(AT45DB_FLASH_SPI ,AT45DB161D_ULTRA_DEEP_POWER_DOWN);
  7.    
  8.     AT45DB_FLASH_CS_HI;
  9.    
  10.     //Odczekanie 200 ms
  11.     Delayms(200);
  12. }

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

  1. void AT45DB_ULTRA_DEEP_POWER_DOWN()
  2. {
  3.     AT45DB_FLASH_CS_HI;
  4.     AT45DB_FLASH_CS_LO;
  5.    
  6.     Delayus(1);
  7.    
  8.     AT45DB_FLASH_CS_HI;
  9.    
  10.     Delayus(300);
  11. }

W celu zaprogramowania większej ilości danych buforem można wykorzystać następującą funkcję:

  1. void AT45DB_WRITE_FLASH(uint32_t addres, uint8_t *data, uint16_t lenght)
  2. {
  3.     int licznik = 0;
  4.    
  5.     //Page_Size domyślnie wynosi 528bajtow
  6.     AT45DB_BUFFER_WRITE(WRITE_BUFFER, addres % PAGE_SIZE);
  7.    
  8.     for (licznik = 0; licznik < (int)lenght; licznik++)
  9.     {  
  10.          SPI1_SEND_DATA(AT45DB_FLASH_SPI, *data++);
  11.     }
  12.     AT45DB_BUFFER_TO_PAGE(WRITE_BUFFER, addres / PAGE_SIZE, 1);
  13. }

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:

  1. void AT45DB_READ_FLASH(uint32_t addres, uint8_t *data, int lenght)
  2. {
  3.     int licznik = 0;
  4.     uint8_t *d=data;
  5.    
  6.     AT45DB_PAGE_TO_BUFFER(addres / PAGE_SIZE, READ_BUFFER);
  7.    
  8.     AT45DB_READ_BUFFER(READ_BUFFER,addres % PAGE_SIZE, 1);
  9.    
  10.     for (licznik = 0x00; licznik < lenght; licznik++)
  11.     {
  12.                 *d++ = SPI1_SEND_DATA(AT45DB_FLASH_SPI, 0xFF);
  13.     }
  14. }

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: