środa, 15 grudnia 2021

STM32H7 - AT45DB

Tym razem chciałbym opisać sposób obsługi pamięci Flash AT45DB. W moim przypadku będzie to pamięć AT45DB161.

[Źródło: https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-nucleo-boards/nucleo-h753zi.html#overview]

Opis kości:


AT45DB161 jest to 16 megabitowa pamięć flash.

Diagram architektury pamięci:

Jak można zaobserwować powyżej pamięć składa 4096 stron, gdzie każda z nich składa się z 528 bajtów. Dane można zapisać i odczytać stronami.


Jak można zaobserwować powyżej zapis danych do układu może być wykonywany przez bufor 1 lub bufor 2. Dzęki wykorzystaniu dwóch buforów można dane odczytywać podczas programowania.

Hardware:


W projekcie wykorzystuje dwie kości pamięci flash podłączone do jednego interfejsu SPI z osobnymi pinami CS do wyboru układu.


Opis wyprowadzeń:

  • SI - Serial Input - MOSI
  • SCK - Serial Clock - Zegar SPI
  • /RESET - Chip Reset
  • /CS - Chip Select
  • /WP - Hardware Page Write protect Pin
  • VCC - zasilanie 3V3
  • GND - GND
  • SO - Serial Output - MISO
Piny sygnałowe posiadają tolerancję 5V. Przez co mogą być używane np z Arduino.

Pinem WP można sterować przez podawanie stanu niskiego w celu blokady zapisu lub stanu wysokiego do umożliwienia edycji danych. 

Piny do STM32 są podłączone w następujący sposób:

  • CS_1 - PC12
  • MISO - PA6
  • MOSI - PD7
  • SCK - PA5

Dodatkowo do zasilania podłączony jest kondensator filtrujący 100nF. Dodatkowo można podłączyć też elektrolit w postaci kondensatora np. 10uF. W przypadku zapisu oraz kasowania mogą z układu pojawiać się dosyć duże piki prądowe, które mogą powodować niepoprawne działanie całego układu podczas tych operacji.

Program:


Inicjalizacja SPI:

  1. SPI_HandleTypeDef hspi1;
  2.  
  3. void MX_SPI1_Init(void)
  4. {
  5.   hspi1.Instance = SPI1;
  6.   hspi1.Init.Mode = SPI_MODE_MASTER;
  7.   hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  8.   hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  9.   hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  10.   hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  11.   hspi1.Init.NSS = SPI_NSS_SOFT;
  12.   hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
  13.   hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  14.   hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  15.   hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  16.   hspi1.Init.CRCPolynomial = 7;
  17.   hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
  18.   hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
  19.   hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
  20.   hspi1.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
  21.   hspi1.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
  22.   hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
  23.   hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
  24.   hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
  25.   hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
  26.   hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;
  27.   if (HAL_SPI_Init(&hspi1) != HAL_OK)
  28.   {
  29.     Error_Handler();
  30.   }
  31.  
  32. }

Inicjalizacja pinu CS:

  1. //Config CS Pin
  2. /*Configure GPIO pin : PC12 */
  3. GPIO_InitStruct.Pin = GPIO_PIN_12;
  4. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  5. GPIO_InitStruct.Pull = GPIO_NOPULL;
  6. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  7. HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

Inicjalizacja pinów SPI odbywa się w wygenerowanej funkcji HAL_SPI_MspInit(), jest ona wywoływana z funkcji uruchamiającej SPI w bibliotece HAL (HAL_SPI_Init()).

  1. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
  2. {
  3.   GPIO_InitTypeDef GPIO_InitStruct = {0};
  4.   if(spiHandle->Instance==SPI1)
  5.   {
  6.   /* USER CODE BEGIN SPI1_MspInit 0 */
  7.   /* USER CODE END SPI1_MspInit 0 */
  8.     /* SPI1 clock enable */
  9.     __HAL_RCC_SPI1_CLK_ENABLE();
  10.  
  11.     __HAL_RCC_GPIOA_CLK_ENABLE();
  12.     __HAL_RCC_GPIOD_CLK_ENABLE();
  13.     /**SPI1 GPIO Configuration
  14.     PA5     ------> SPI1_SCK
  15.     PA6     ------> SPI1_MISO
  16.     PD7     ------> SPI1_MOSI
  17.     */
  18.     GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6;
  19.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  20.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  21.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  22.     GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
  23.     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  24.  
  25.     GPIO_InitStruct.Pin = GPIO_PIN_7;
  26.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  27.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  28.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  29.     GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
  30.     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
  31.   /* USER CODE BEGIN SPI1_MspInit 1 */
  32.   /* USER CODE END SPI1_MspInit 1 */
  33.   }
  34. }

W przykładzie wykorzystuje interfejs SPI1, CS sterowany jest osobno:

Dane dla kości przechowuje w następującej strukturze:

  1. typedef struct AT45DB_Size_TypedefStruct{
  2.     uint8_t FlashNumber;
  3.     uint8_t FlashSize;
  4.     uint16_t PageSize;
  5.     uint16_t Pages;
  6.     uint8_t  Shift;
  7.     uint32_t CS_GPIO_Pin;
  8. }AT45DB_Size_typedef;
  9.  
  10. extern AT45DB_Size_typedef AT45DB_Size_t[2];

Na samym początku dokonuje podstawowej inicjalizacji danych w strukturze przez podanie do niej numeru kości oraz pinu sterującego CS. Oba piny obsługuje z jednego portu, dlatego jedynym parametrem jest pin.

  1. void AT45DB_InitStructureData(AT45DB_Size_typedef *ptr, uint8_t flashNumber)
  2. {
  3.     if(flashNumber == 1)
  4.     {
  5.         ptr->FlashNumber = 1;
  6.         ptr->CS_GPIO_Pin = AT45DB_CS_PIN_1;
  7.     }
  8.     else if(flashNumber == 2)
  9.     {
  10.         ptr->FlashNumber = 2;
  11.         ptr->CS_GPIO_Pin = AT45DB_CS_PIN_2;
  12.     }
  13. }

Kolejnym krokiem jest odczytanie parametrów dla każdej z kości i wprowadzenie ustawień do struktury. Funkcja sprawdza, jaka kość jest podłączona na liniach w zależności od odczytanych parametrów. 

  1. uint8_t AT45DB_Init(AT45DB_Size_typedef *ptr) {
  2.     uint8_t revision = 0;
  3.     uint8_t subcode = 0;
  4.     uint8_t status = 0;

  5. at45db_resume(ptr->CS_GPIO_Pin);
  6. at45db_waitbusy(ptr->CS->GPIO_Pin);
  7.  
  8.     uint32_t manufactureID = AT45DB_ReadManufactureID(ptr->CS_GPIO_Pin);
  9.  
  10.     revision = (manufactureID & 0xFF000000) >> 24;
  11.     subcode = ((manufactureID & 0x00FF0000) >> 16) & 0x1F;
  12.     status = at45db_readstatus(ptr->CS_GPIO_Pin) & 0x01;
  13.  
  14.     if(revision != 0x1F) {
  15.         return 1;
  16.     }
  17.  
  18.     return at45db_get_size(subcode, status, (AT45DB_Size_typedef **)&ptr);
  19. }
  20.  
  21. static uint8_t at45db_get_size(uint8_t subcode, uint8_t status, AT45DB_Size_typedef **ptr)
  22. {
  23.     if(subcode == AT45DB021){
  24.         (*ptr)->FlashSize = 2;
  25.         (*ptr)->Pages = 1024;
  26.         if(status) {
  27.             (*ptr)->Shift = 0;
  28.             (*ptr)->PageSize = 256;
  29.         }
  30.         else{
  31.             (*ptr)->Shift = 9;
  32.             (*ptr)->PageSize = 264;
  33.         }
  34.     }
  35.     else if (subcode == AT45DB041) {
  36.         (*ptr)->FlashSize = 4;
  37.         (*ptr)->Pages = 2048;
  38.         if(status) {
  39.             (*ptr)->Shift = 0;
  40.             (*ptr)->PageSize = 256;
  41.         }
  42.         else {
  43.             (*ptr)->Shift = 9;
  44.             (*ptr)->PageSize = 264;
  45.         }
  46.     }
  47.     else if (subcode == AT45DB081) {
  48.         (*ptr)->FlashSize = 8;
  49.         (*ptr)->Pages = 4096;
  50.         if(status) {
  51.             (*ptr)->Shift = 0;
  52.             (*ptr)->PageSize = 256;
  53.         }
  54.         else {
  55.             (*ptr)->Shift = 9;
  56.             (*ptr)->PageSize = 264;
  57.         }
  58.     }
  59.     else if (subcode == AT45DB161) {
  60.         (*ptr)->FlashSize = 16;
  61.         (*ptr)->Pages = 4096;
  62.         if(status) {
  63.             (*ptr)->Shift = 0;
  64.             (*ptr)->PageSize = 512;
  65.         }
  66.         else {
  67.             (*ptr)->Shift = 10;
  68.             (*ptr)->PageSize = 528;
  69.         }
  70.     }
  71.     else if (subcode == AT45DB321) {
  72.         (*ptr)->FlashSize = 32;
  73.         (*ptr)->Pages = 8192;
  74.         if(status)
  75.         {
  76.             (*ptr)->Shift = 0;
  77.             (*ptr)->PageSize = 512;
  78.         }
  79.         else {
  80.             (*ptr)->Shift = 10;
  81.             (*ptr)->PageSize = 528;
  82.         }
  83.     }
  84.     else if (subcode == AT45DB641) {
  85.         (*ptr)->FlashSize = 64;
  86.         (*ptr)->Pages = 8192;
  87.         if(status)
  88.         {
  89.             (*ptr)->Shift = 0;
  90.             (*ptr)->PageSize = 1024;
  91.         }
  92.         else {
  93.             (*ptr)->Shift = 11;
  94.             (*ptr)->PageSize = 1056;
  95.         }
  96.     }
  97.     else{
  98.         return 1;
  99.     }
  100.  
  101.     return 0;
  102. }

Teraz kilka funkcji kasowania:

  1. void AT45DB_EraseChip(uint32_t CSPin)
  2. {
  3.     at45db_resume(CSPin);
  4.     at45db_waitbusy(CSPin);
  5.  
  6.     AT45DB_FLASH_CS_LO_SEND_(CSPin);
  7.  
  8.     at45db_spi_send(AT45DB_CHIPERASE1);
  9.     at45db_spi_send(AT45DB_CHIPERASE2);
  10.     at45db_spi_send(AT45DB_CHIPERASE3);
  11.     at45db_spi_send(AT45DB_CHIPERASE4);
  12.  
  13.     AT45DB_FLASH_CS_HI_DEF_(CSPin);
  14.  
  15.     at45db_waitbusy(CSPin);
  16. }
  17.  
  18. void AT45DB_EraseSector(uint8_t sector, uint32_t CSPin)
  19. {
  20.     AT45DB_FLASH_CS_LO_SEND_(CSPin);
  21.  
  22.     at45db_spi_send(AT45DB_SECTERASE);
  23.  
  24.     if((sector == 0x0a) || (sector == 0x0b))
  25.     {
  26.         at45db_spi_send(0x00);
  27.         at45db_spi_send(((sector & 0x01) << 4));
  28.         at45db_spi_send(0x00);
  29.     }
  30.     else
  31.     {
  32.         at45db_spi_send(sector << 1);
  33.         at45db_spi_send(0x00);
  34.         at45db_spi_send(0x00);
  35.     }
  36.  
  37.     AT45DB_FLASH_CS_HI_DEF_(CSPin);
  38.     at45db_waitbusy(CSPin);
  39. }
  40.  
  41. void AT45DB_ErasePage(AT45DB_Size_typedef *ptr, uint16_t page)
  42. {
  43.     page = page << ptr->Shift;
  44.  
  45.     at45db_resume(ptr->CS_GPIO_Pin);
  46.     at45db_waitbusy(ptr->CS_GPIO_Pin);
  47.  
  48.     AT45DB_FLASH_CS_LO_SEND_(ptr->CS_GPIO_Pin);
  49.  
  50.     at45db_spi_send(AT45DB_PGERASE);
  51.     at45db_spi_send((page >> 16) & 0xff);
  52.     at45db_spi_send((page >> 8) & 0xff);
  53.     at45db_spi_send(page & 0xff);
  54.  
  55.     AT45DB_FLASH_CS_HI_DEF_(ptr->CS_GPIO_Pin);
  56.  
  57.     at45db_waitbusy(ptr->CS_GPIO_Pin);
  58. }

Teraz funkcja zapisu strony:

  1. uint8_t AT45DB_WritePage(AT45DB_Size_typedef *ptr, uint8_t *dataToSave, uint16_t dataToSaveLength, uint16_t pageNumber, uint8_t bufferNumber)
  2. {
  3.     pageNumber = pageNumber << ptr->Shift;
  4.     at45db_resume(ptr->CS_GPIO_Pin);
  5.     at45db_waitbusy(ptr->CS_GPIO_Pin);
  6.  
  7.     AT45DB_FLASH_CS_LO_SEND_(ptr->CS_GPIO_Pin);
  8.  
  9.     if(bufferNumber == 1)       { at45db_spi_send(AT45DB_MNTHRUBF1); }
  10.     else if(bufferNumber == 2)  { at45db_spi_send(AT45DB_MNTHRUBF2); }
  11.  
  12.     at45db_spi_send((pageNumber >> 16) & 0xff);
  13.     at45db_spi_send((pageNumber >> 8) & 0xff);
  14.     at45db_spi_send(pageNumber & 0xff);
  15.  
  16.     HAL_StatusTypeDef opStatus = HAL_SPI_Transmit(&hspi1, dataToSave, dataToSaveLength, 100);
  17.  
  18.     AT45DB_FLASH_CS_HI_DEF_(ptr->CS_GPIO_Pin);
  19.  
  20.     at45db_waitbusy(ptr->CS_GPIO_Pin);
  21.  
  22.     return opStatus;
  23. }

I funkcje odczytujące dane:

  1. uint8_t AT45DB_ReadPage(AT45DB_Size_typedef *ptr, uint8_t* dataToRead, uint16_t dataToReadLength, uint16_t pageNumber)
  2. {
  3.     pageNumber = pageNumber << ptr->Shift;
  4.     if(dataToReadLength > ptr->PageSize) {
  5.         dataToReadLength = ptr->PageSize;
  6.     }
  7.  
  8.     at45db_resume(ptr->CS_GPIO_Pin);
  9.     at45db_waitbusy(ptr->CS_GPIO_Pin);
  10.  
  11.     AT45DB_FLASH_CS_LO_SEND_(ptr->CS_GPIO_Pin);
  12.  
  13.     at45db_spi_send(AT45DB_RDARRAYHF);
  14.     at45db_spi_send((pageNumber >> 16) & 0xff);
  15.     at45db_spi_send((pageNumber >> 8) & 0xff);
  16.     at45db_spi_send(pageNumber & 0xff);
  17.     at45db_spi_send(0);
  18.  
  19.     HAL_StatusTypeDef opStatus = HAL_SPI_Receive(&hspi1, dataToRead, dataToReadLength, 100);
  20.  
  21.     AT45DB_FLASH_CS_HI_DEF_(ptr->CS_GPIO_Pin);
  22.  
  23.     return opStatus;
  24. }

Przykładowy zapis oraz odczyt całej strony można wywołać następujący sposób:

  1. uint8_t buffer_to_write[528] = {0x00};
  2. uint8_t buffer_to_read[528] = {0x00};
  3.  
  4. for(uint16_t i = 0; i<528; i+=4)
  5. {
  6.     buffer_to_write[i + 0] = 0xA5;
  7.     buffer_to_write[i + 1] = 0x49;
  8.     buffer_to_write[i + 2] = 0x93;
  9.     buffer_to_write[i + 3] = 0xB4;
  10. }
  11.  
  12. AT45DB_WritePage(&AT45DB_Size_t[0], &buffer_to_write[0], 528, 0, 1);
  13. AT45DB_ReadPage(&AT45DB_Size_t[0], &buffer_to_read[0], 528, 0);

Bibliotekę można pobrać z dysku Google pod tym linkiem.