Imax9
НОВОСТИ   ПУБЛИКАЦИИ   FILES   ИНФОРМАЦИЯ

STM32F407 Урок 06. Работаем с внешней памятью FLASH и PSRAM по шине SPI.

Добрый день, уважаемые читатели. В этом уроке я хочу вам рассказать как подружить МК STM32F407 с микросхемами энергонезависимой памятью W25Q16 и псевдостатической памятью ESP-PSRAM64H. Как видно из даташита :

w25q16 psram

Оба экземпляра работают по SPI интерфейсу, W25Q16 распаяна на плате разработчика STM32F407VET6, а PSRAM для удобства распаяна на монтажке и подключена к SPI параллельно W25Q16. Вот как получилось у меня :

SOP8 board

Как всегда, в STM32CubeMX выбираем наш MCU STM32F407VE, включим осциляторы и не забудем включить отладку :

В Clock Configuration частота 132MHz тактирование от HSE.

Ножки PB0 и PE2 назначим как выходы выбора кристаллов соответственно W25Q16 и PSRAM64H, если есть необходимость в дополнителной индикации, включите еще и выходы PA6, PA7 для светодиодов :

gpio

I2C1 сконфигурируем под наш дисплей SSD1306 : скорость на Fast Mode, не забываем про DMA в TX режиме и NVIC. Кто забыл - ссылка на урок , основное :

i2cdma i2cnviv

На плате W25Q16 подключена к SPI1 в альтернативной конфигурации :

SPIpin

SPI1 --------- STM32pin

С первого раза найти куда подключать, получится не у всех, поэтому расскажу поподробнее. На гребенку, контакты контроллера PB3 - PB5 не выведены, но они есть на разъеме NRF. Фото, чтобы было понятнее и распиновка разъема :

board


NRF -------- STM ------- PSRAM64H



Включаем SPI1 Full-Duplex Master, Hardware NSS - Disable, остальные настройки следующие :

SPIConfig SPIDMA

NVIC все разрешим.

Увеличим Heap и Stack в 2 раза и создадим проект для Atollic. Скопируйте файлы обновленной библиотеки IC1306 в соответствующие папки Src и Inc своего проекта.

Сначала подключим библиотеку IC1306 для работы с экраном :

/* USER CODE BEGIN Includes */
#include <ic1306.h>
/* USER CODE END Includes */

До бесконечного цикла, произведем инициализацию, очистку и вывод сообщения на дисплей :

/* USER CODE BEGIN 2 */
display_init (50,0x22,0);
display_clear(0,7);
display_SmallPrint(0,0,"Hello RAM&Flash");
/* USER CODE END 2 */

В бесконечном цикле вставим задерку, чтобы не лопатил впустую :

/* USER CODE BEGIN 3 */
HAL_Delay(100);
}
/* USER CODE END 3 */

Полюбуемся на приветствие. Теперь определим макросы для удобства :

/* USER CODE BEGIN PD */
#define W25_CSL HAL_GPIO_WritePin(F_CS_GPIO_Port, F_CS_Pin, GPIO_PIN_RESET);
#define W25_CSH HAL_GPIO_WritePin(F_CS_GPIO_Port, F_CS_Pin, GPIO_PIN_SET);
#define RAM_CSH HAL_GPIO_WritePin(RAM_CS_GPIO_Port, RAM_CS_Pin, GPIO_PIN_SET);
#define RAM_CSL HAL_GPIO_WritePin(RAM_CS_GPIO_Port, RAM_CS_Pin, GPIO_PIN_RESET);
#define READ 0x03
#define WRITE 0x02
#define WRITE_EN 0x06
#define WRITE_DS 0x04
#define ERASE_SEC 0x20
#define SR1_READ 0x05
#define ID_READ 0x90
#define JEDEC_READ 0x9f
/* USER CODE END PD */

Сначала разберемся с W25Q16, напишем функции чтения ID и JEDEC, по ним индентифицируется микросхема flash.

/* USER CODE BEGIN 4 */
uint16_t Read_ID(void)
{uint8_t temp[4]={0,};
W25_CSL;
temp[0]=ID_READ;
HAL_SPI_Transmit(&hspi1,&temp,4,1);
while (HAL_SPI_GetState(&hspi1)!=HAL_SPI_STATE_READY) {__NOP();}
HAL_SPI_Receive(&hspi1,&temp,2,1);
W25_CSH;
return ((temp[0]<<8)|temp[1]);
}
uint32_t Read_JEDEC(void)
{uint8_t temp[4]={0,};
W25_CSL;
temp[0]=JEDEC_READ;
HAL_SPI_Transmit(&hspi1,&temp,1,1);
while (HAL_SPI_GetState(&hspi1)!=HAL_SPI_STATE_READY) {__NOP();}
HAL_SPI_Receive(&hspi1,&temp,3,1);
W25_CSH;
return ((temp[0]<<16)|(temp[1]<<8)|temp[2]);
}
/* USER CODE END 4 */

Не забудем их определить до использования :

/* USER CODE BEGIN PFP */
uint16_t Read_ID(void);
uint32_t Read_JEDEC(void);
/* USER CODE END PFP */

В главной программе до бесконечного цикла считаем и выведем на дисплей :

display_SmallPrint(0,0,"Hello RAM&Flash");
uint8_t str[32];
sprintf(str,"ID: %x", Read_ID());
display_SmallPrint(0,1,str);
sprintf(str,"JEDEC: %x", Read_JEDEC());
display_SmallPrint(0,2,str);
/* USER CODE END 2 */

Результат на экране будет типа такого, как видите, совпадает с datasheet на данный тип памяти :

Теперь напишем функцию чтения регистра состояния SR1 и чтения страницы 256 байт :

uint8_t Read_SR1(void)
{uint8_t temp[1];
W25_CSL;
temp[0]=SR1_READ;
HAL_SPI_Transmit(&hspi1,&temp,1,1);
while (HAL_SPI_GetState(&hspi1)!=HAL_SPI_STATE_READY) {__NOP();}
HAL_SPI_Receive(&hspi1,&temp,1,1);
W25_CSH;
return temp[0];
}
void Read_Page(uint32_t addr,uint8_t *rData)
{uint8_t temp[4]={0,};
while(Read_SR1()&1) {__NOP();}
W25_CSL;
temp[0]=READ;
temp[1]=(addr>>16)&0xff;
temp[2]=(addr>>8)&0xff;
temp[3]=addr&0xff;
HAL_SPI_Transmit(&hspi1,&temp,4,1);
while (HAL_SPI_GetState(&hspi1)!=HAL_SPI_STATE_READY) {__NOP();}
HAL_SPI_Receive(&hspi1,rData,256,1);
W25_CSH;
}
/* USER CODE END 4 */

Не забудем определить заранее функцию Read_SR1, возвращающую результат :

uint32_t Read_JEDEC(void);
uint8_t Read_SR1(void);
/* USER CODE END PFP */

Прочитаем что записано в начале :

sprintf(str,"JEDEC: %x", Read_JEDEC());
display_SmallPrint(0,2,str);
uint8_t buffer[256];
Read_Page(0,&buffer[0]);
sprintf(str,"%02x %02x %02x %02x %02x %02x %02x %02x %02x",
buffer[0],buffer[1],buffer[2],buffer[3],buffer[4],buffer[5],buffer[6],buffer[7],buffer[8]);
display_SmallPrint(0,3,str);
/* USER CODE END 2 */

readff

Если микросхема чистая, то любуемся на FF, иначе на мусор.



Перед тем как записывать, необходимо сначала подать команду разрешение записи, после записи, дождавшись окончания процесса, подать команду запрета, программируют страницами :

void WriteEnable(void)
{uint8_t temp[1];
W25_CSL;
temp[0]=WRITE_EN;
HAL_SPI_Transmit(&hspi1,&temp,1,1);
W25_CSH;
}
void WriteDisable(void)
{uint8_t temp[1];
W25_CSL;
temp[0]=WRITE_DS;
HAL_SPI_Transmit(&hspi1,&temp,1,1);
W25_CSH;
}
void Write_Page(uint32_t addr,uint8_t *wData)
{uint8_t temp[4]={0,};
while(Read_SR1()&1) {__NOP();}
W25_CSL;
temp[0]=WRITE;
temp[1]=(addr>>16)&0xff;
temp[2]=(addr>>8)&0xff;
temp[3]=addr&0xff;
HAL_SPI_Transmit(&hspi1,&temp,4,1);
HAL_SPI_Transmit(&hspi1,wData,256,1);
while (HAL_SPI_GetState(&hspi1)!=HAL_SPI_STATE_READY) {__NOP();}
W25_CSH;
}
/* USER CODE END 4 */

В основную программу, до бесконечного цикла, добавим запись и чтение того что записали :

display_SmallPrint(0,3,str);
WriteEnable();
for(uint16_t i=0;i<256;i++)
{buffer[i]=0xff;}
buffer[0]=0x55;
buffer[1]=0xff;
buffer[2]=0x00;
buffer[3]=0xAA;
Write_Page(0,&buffer[0]);
sprintf(str,"SR1: %02x", Read_SR1());
display_SmallPrint(0,6,str);
WriteDisable();
Read_Page(0,&buffer[0]);
sprintf(str,"%02x %02x %02x %02x %02x %02x %02x %02x %02x",
buffer[0],buffer[1],buffer[2],buffer[3],buffer[4],buffer[5],buffer[6],buffer[7],buffer[8]);
display_SmallPrint(0,4,str);
/* USER CODE END 2 */

writeflash

Записать 1 после 0, невозможно, необходимо сначала стереть поэтому при записи заполняли буфер значениями 0xff. Стирать можно страницами 256 байт, секторами 4Kb, группами по 32Kb и 64Kb, всю микросхему целиком.




Вот функция стирания страницами, остальные аналогично, datasheet вам в руки :

void EraseSec(uint32_t addr)
{uint8_t temp[4]={0,};
while(Read_SR1()&1) {__NOP();}
W25_CSL;
temp[0]=ERASE_SEC;
temp[1]=(addr>>16)&0xff;
temp[2]=(addr>>8)&0xff;
temp[3]=addr&0xff;
HAL_SPI_Transmit(&hspi1,&temp,4,1);
W25_CSH;
HAL_Delay(1);
}
/* USER CODE END 4 */

Теперь, вставив код ниже вместо функции записи, убедитесь, что из ячеек памяти читаются значения 0xff, и можно заново записывать любые значения :

if(buffer[0]!=0xff) {EraseSec(0);}

С flash памятью разобрались, поработаем с PSRAM, подробную об этом типе памяти информацию Вы можете найти сами, кратко расскажу что знаю. Обычная статическая память имеет небольшой объем около 8x8Kbit, динамическая же требует дополнительной схемы регенирации. В PSRAM схема регенирации уже встроена в саму микросхему, для пользователя это обычная статическая память большого объема 8x8Mbit. Напишем функцию чтения ID :

void Read_IDRAM(uint8_t *temp)
{
RAM_CSL;
*temp=JEDEC_READ;
HAL_SPI_Transmit(&hspi1,temp,4,1);
while (HAL_SPI_GetState(&hspi1)!=HAL_SPI_STATE_READY) {__NOP();}
HAL_SPI_Receive(&hspi1,temp,8,1);
RAM_CSH;
}
/* USER CODE END 4 */

Закомментирум все что связано с работой flash и после приветствия выведем ID PSRAM :

display_SmallPrint(0,0,"Hello RAM&Flash");
uint8_t str[32];
uint8_t id[8];
Read_IDRAM(id);
sprintf(str,"id:%02x%02x%02x%02x%02x%02x%02x%02x",id[0],id[1],id[2],id[3],id[4],id[5],id[6],id[7]);
display_SmallPrint(0,1,str);
/* USER CODE END 2 */

Любуемся на результат и рядом вырезка из datasheet :

ramid IdRamMfKgd

Напишем функцию чтения, читать можно разными размерами блока и с любого адреса, я выбрал буфер в 1KB :

void Read_RAM(uint32_t addr,uint8_t *rData)
{uint8_t temp[4]={0,};
RAM_CSL;
temp[0]=READ;
temp[1]=(addr>>16)&0xff;
temp[2]=(addr>>8)&0xff;
temp[3]=addr&0xff;
HAL_SPI_Transmit(&hspi1,&temp,4,1);
while (HAL_SPI_GetState(&hspi1)!=HAL_SPI_STATE_READY) {__NOP();}
HAL_SPI_Receive(&hspi1,rData,1024,1);
RAM_CSH;
}
/* USER CODE END 4 */

После приветствия, определим буфер и выведем на экран содержимое начального адреса :

display_SmallPrint(0,1,str);
uint8_t buffer[1024];
Read_RAM(0x00,&buffer[0]);
sprintf(str,"%02x %02x %02x %02x %02x %02x %02x %02x %02x",
buffer[0],buffer[1],buffer[2],buffer[3],buffer[4],buffer[5],buffer[6],buffer[7],buffer[8]);
display_SmallPrint(0,2,str);
/* USER CODE END 2 */

ramread

Полюбуемся немного мусором из памяти, теперь напишем функцию записи в SPRAM, буфер оставим такой-же, вы можете определить любой, в отличии от flash :


void Write_RAM(uint32_t addr,uint8_t *wData)
{uint8_t temp[4]={0,};
RAM_CSL;
temp[0]=WRITE;
temp[1]=(addr>>16)&0xff;
temp[2]=(addr>>8)&0xff;
temp[3]=addr&0xff;
HAL_SPI_Transmit(&hspi1,&temp,4,1);
HAL_SPI_Transmit(&hspi1,wData,1024,1);
while (HAL_SPI_GetState(&hspi1)!=HAL_SPI_STATE_READY) {__NOP();}
RAM_CSH;
}
/* USER CODE END 4 */

Записывать мы можем также с любого адреса, ниже код, который записывает в начало каждого мегабайта (начиная с 3 по 7) по 1KB данных, которые будут порядковым номером мегабайта, а считаем с адреса, последних записанных 4 байт :

display_SmallPrint(0,2,str);
for(uint8_t j=3;j<8;j++)
{for(uint16_t i=0;i<1024;i++) {buffer[i]=j;}
Write_RAM(0x100000*j,&buffer[0]); }
for(uint8_t j=3;j<8;j++)
{ Read_RAM(0x100000*j+0x3fc,&buffer[0]);
sprintf(str,"%02x %02x %02x %02x %02x %02x %02x %02x %02x",
buffer[0],buffer[1],buffer[2],buffer[3],buffer[4],buffer[5],buffer[6],buffer[7],buffer[8]);
display_SmallPrint(0,j,str);
}
/* USER CODE END 2 */

ramwrite


Смотрим на первые 4 байта - это то, что мы записали, а далее идет мусор. Следует напомнить, что при отключении питания наши данные пропадут и мы считаем мусор вместо записанных данных.




Эту микросхему я планирую применить в своем wi-fi радио на STM32F4xx, публикацию планирую на осень. На этом пока все, как всегда, если Вам есть что сказать :

Адрес для контактов : imax9@narod.ru

Если вам понравились мои работы и вы желаете поддержать сайт - сделайте дотацию.

При копировании статьи – обязательна ссылка на авторство и источник. Без разрешения автора копирование запрещено.

© Максим Ильин 2020г.

Яндекс.Метрика