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:
- SPI_HandleTypeDef hspi1;
- void MX_SPI1_Init(void)
- {
- hspi1.Instance = SPI1;
- hspi1.Init.Mode = SPI_MODE_MASTER;
- hspi1.Init.Direction = SPI_DIRECTION_2LINES;
- hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
- hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
- hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
- hspi1.Init.NSS = SPI_NSS_SOFT;
- hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
- hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
- hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
- hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
- hspi1.Init.CRCPolynomial = 7;
- hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
- hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
- hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
- hspi1.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
- hspi1.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
- hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
- hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
- hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
- hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
- hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;
- if (HAL_SPI_Init(&hspi1) != HAL_OK)
- {
- Error_Handler();
- }
- }
Inicjalizacja pinu CS:
- //Config CS Pin
- /*Configure GPIO pin : PC12 */
- GPIO_InitStruct.Pin = GPIO_PIN_12;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- 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()).
- void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
- {
- GPIO_InitTypeDef GPIO_InitStruct = {0};
- if(spiHandle->Instance==SPI1)
- {
- /* USER CODE BEGIN SPI1_MspInit 0 */
- /* USER CODE END SPI1_MspInit 0 */
- /* SPI1 clock enable */
- __HAL_RCC_SPI1_CLK_ENABLE();
- __HAL_RCC_GPIOA_CLK_ENABLE();
- __HAL_RCC_GPIOD_CLK_ENABLE();
- /**SPI1 GPIO Configuration
- PA5 ------> SPI1_SCK
- PA6 ------> SPI1_MISO
- PD7 ------> SPI1_MOSI
- */
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_InitStruct.Pin = GPIO_PIN_7;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
- HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
- /* USER CODE BEGIN SPI1_MspInit 1 */
- /* USER CODE END SPI1_MspInit 1 */
- }
- }
W przykładzie wykorzystuje interfejs SPI1, CS sterowany jest osobno:
Dane dla kości przechowuje w następującej strukturze:
- typedef struct AT45DB_Size_TypedefStruct{
- uint8_t FlashNumber;
- uint8_t FlashSize;
- uint16_t PageSize;
- uint16_t Pages;
- uint8_t Shift;
- uint32_t CS_GPIO_Pin;
- }AT45DB_Size_typedef;
- 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.
- void AT45DB_InitStructureData(AT45DB_Size_typedef *ptr, uint8_t flashNumber)
- {
- if(flashNumber == 1)
- {
- ptr->FlashNumber = 1;
- ptr->CS_GPIO_Pin = AT45DB_CS_PIN_1;
- }
- else if(flashNumber == 2)
- {
- ptr->FlashNumber = 2;
- ptr->CS_GPIO_Pin = AT45DB_CS_PIN_2;
- }
- }
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.
- uint8_t AT45DB_Init(AT45DB_Size_typedef *ptr) {
- uint8_t revision = 0;
- uint8_t subcode = 0;
- uint8_t status = 0;
- at45db_resume(ptr->CS_GPIO_Pin);
- at45db_waitbusy(ptr->CS->GPIO_Pin);
- uint32_t manufactureID = AT45DB_ReadManufactureID(ptr->CS_GPIO_Pin);
- revision = (manufactureID & 0xFF000000) >> 24;
- subcode = ((manufactureID & 0x00FF0000) >> 16) & 0x1F;
- status = at45db_readstatus(ptr->CS_GPIO_Pin) & 0x01;
- if(revision != 0x1F) {
- return 1;
- }
- return at45db_get_size(subcode, status, (AT45DB_Size_typedef **)&ptr);
- }
- static uint8_t at45db_get_size(uint8_t subcode, uint8_t status, AT45DB_Size_typedef **ptr)
- {
- if(subcode == AT45DB021){
- (*ptr)->FlashSize = 2;
- (*ptr)->Pages = 1024;
- if(status) {
- (*ptr)->Shift = 0;
- (*ptr)->PageSize = 256;
- }
- else{
- (*ptr)->Shift = 9;
- (*ptr)->PageSize = 264;
- }
- }
- else if (subcode == AT45DB041) {
- (*ptr)->FlashSize = 4;
- (*ptr)->Pages = 2048;
- if(status) {
- (*ptr)->Shift = 0;
- (*ptr)->PageSize = 256;
- }
- else {
- (*ptr)->Shift = 9;
- (*ptr)->PageSize = 264;
- }
- }
- else if (subcode == AT45DB081) {
- (*ptr)->FlashSize = 8;
- (*ptr)->Pages = 4096;
- if(status) {
- (*ptr)->Shift = 0;
- (*ptr)->PageSize = 256;
- }
- else {
- (*ptr)->Shift = 9;
- (*ptr)->PageSize = 264;
- }
- }
- else if (subcode == AT45DB161) {
- (*ptr)->FlashSize = 16;
- (*ptr)->Pages = 4096;
- if(status) {
- (*ptr)->Shift = 0;
- (*ptr)->PageSize = 512;
- }
- else {
- (*ptr)->Shift = 10;
- (*ptr)->PageSize = 528;
- }
- }
- else if (subcode == AT45DB321) {
- (*ptr)->FlashSize = 32;
- (*ptr)->Pages = 8192;
- if(status)
- {
- (*ptr)->Shift = 0;
- (*ptr)->PageSize = 512;
- }
- else {
- (*ptr)->Shift = 10;
- (*ptr)->PageSize = 528;
- }
- }
- else if (subcode == AT45DB641) {
- (*ptr)->FlashSize = 64;
- (*ptr)->Pages = 8192;
- if(status)
- {
- (*ptr)->Shift = 0;
- (*ptr)->PageSize = 1024;
- }
- else {
- (*ptr)->Shift = 11;
- (*ptr)->PageSize = 1056;
- }
- }
- else{
- return 1;
- }
- return 0;
- }
Teraz kilka funkcji kasowania:
- void AT45DB_EraseChip(uint32_t CSPin)
- {
- at45db_resume(CSPin);
- at45db_waitbusy(CSPin);
- AT45DB_FLASH_CS_LO_SEND_(CSPin);
- at45db_spi_send(AT45DB_CHIPERASE1);
- at45db_spi_send(AT45DB_CHIPERASE2);
- at45db_spi_send(AT45DB_CHIPERASE3);
- at45db_spi_send(AT45DB_CHIPERASE4);
- AT45DB_FLASH_CS_HI_DEF_(CSPin);
- at45db_waitbusy(CSPin);
- }
- void AT45DB_EraseSector(uint8_t sector, uint32_t CSPin)
- {
- AT45DB_FLASH_CS_LO_SEND_(CSPin);
- at45db_spi_send(AT45DB_SECTERASE);
- if((sector == 0x0a) || (sector == 0x0b))
- {
- at45db_spi_send(0x00);
- at45db_spi_send(((sector & 0x01) << 4));
- at45db_spi_send(0x00);
- }
- else
- {
- at45db_spi_send(sector << 1);
- at45db_spi_send(0x00);
- at45db_spi_send(0x00);
- }
- AT45DB_FLASH_CS_HI_DEF_(CSPin);
- at45db_waitbusy(CSPin);
- }
- void AT45DB_ErasePage(AT45DB_Size_typedef *ptr, uint16_t page)
- {
- page = page << ptr->Shift;
- at45db_resume(ptr->CS_GPIO_Pin);
- at45db_waitbusy(ptr->CS_GPIO_Pin);
- AT45DB_FLASH_CS_LO_SEND_(ptr->CS_GPIO_Pin);
- at45db_spi_send(AT45DB_PGERASE);
- at45db_spi_send((page >> 16) & 0xff);
- at45db_spi_send((page >> 8) & 0xff);
- at45db_spi_send(page & 0xff);
- AT45DB_FLASH_CS_HI_DEF_(ptr->CS_GPIO_Pin);
- at45db_waitbusy(ptr->CS_GPIO_Pin);
- }
Teraz funkcja zapisu strony:
- uint8_t AT45DB_WritePage(AT45DB_Size_typedef *ptr, uint8_t *dataToSave, uint16_t dataToSaveLength, uint16_t pageNumber, uint8_t bufferNumber)
- {
- pageNumber = pageNumber << ptr->Shift;
- at45db_resume(ptr->CS_GPIO_Pin);
- at45db_waitbusy(ptr->CS_GPIO_Pin);
- AT45DB_FLASH_CS_LO_SEND_(ptr->CS_GPIO_Pin);
- if(bufferNumber == 1) { at45db_spi_send(AT45DB_MNTHRUBF1); }
- else if(bufferNumber == 2) { at45db_spi_send(AT45DB_MNTHRUBF2); }
- at45db_spi_send((pageNumber >> 16) & 0xff);
- at45db_spi_send((pageNumber >> 8) & 0xff);
- at45db_spi_send(pageNumber & 0xff);
- HAL_StatusTypeDef opStatus = HAL_SPI_Transmit(&hspi1, dataToSave, dataToSaveLength, 100);
- AT45DB_FLASH_CS_HI_DEF_(ptr->CS_GPIO_Pin);
- at45db_waitbusy(ptr->CS_GPIO_Pin);
- return opStatus;
- }
I funkcje odczytujące dane:
- uint8_t AT45DB_ReadPage(AT45DB_Size_typedef *ptr, uint8_t* dataToRead, uint16_t dataToReadLength, uint16_t pageNumber)
- {
- pageNumber = pageNumber << ptr->Shift;
- if(dataToReadLength > ptr->PageSize) {
- dataToReadLength = ptr->PageSize;
- }
- at45db_resume(ptr->CS_GPIO_Pin);
- at45db_waitbusy(ptr->CS_GPIO_Pin);
- AT45DB_FLASH_CS_LO_SEND_(ptr->CS_GPIO_Pin);
- at45db_spi_send(AT45DB_RDARRAYHF);
- at45db_spi_send((pageNumber >> 16) & 0xff);
- at45db_spi_send((pageNumber >> 8) & 0xff);
- at45db_spi_send(pageNumber & 0xff);
- at45db_spi_send(0);
- HAL_StatusTypeDef opStatus = HAL_SPI_Receive(&hspi1, dataToRead, dataToReadLength, 100);
- AT45DB_FLASH_CS_HI_DEF_(ptr->CS_GPIO_Pin);
- return opStatus;
- }
Przykładowy zapis oraz odczyt całej strony można wywołać następujący sposób:
- uint8_t buffer_to_write[528] = {0x00};
- uint8_t buffer_to_read[528] = {0x00};
- for(uint16_t i = 0; i<528; i+=4)
- {
- buffer_to_write[i + 0] = 0xA5;
- buffer_to_write[i + 1] = 0x49;
- buffer_to_write[i + 2] = 0x93;
- buffer_to_write[i + 3] = 0xB4;
- }
- AT45DB_WritePage(&AT45DB_Size_t[0], &buffer_to_write[0], 528, 0, 1);
- AT45DB_ReadPage(&AT45DB_Size_t[0], &buffer_to_read[0], 528, 0);