STM32F407 Урок 06. Работаем с внешней памятью FLASH и PSRAM по шине SPI.
Добрый день, уважаемые читатели. В этом уроке я хочу вам рассказать как подружить МК STM32F407 с микросхемами энергонезависимой памятью W25Q16 и псевдостатической памятью ESP-PSRAM64H. Как видно из даташита :
Оба экземпляра работают по SPI интерфейсу, W25Q16 распаяна на плате разработчика STM32F407VET6, а PSRAM для удобства распаяна на монтажке и подключена к SPI параллельно W25Q16. Вот как получилось у меня :
Как всегда, в STM32CubeMX выбираем наш MCU STM32F407VE, включим осциляторы и не забудем включить отладку :
В Clock Configuration частота 132MHz тактирование от HSE.
Ножки PB0 и PE2 назначим как выходы выбора кристаллов соответственно W25Q16 и PSRAM64H, если есть необходимость в дополнителной индикации, включите еще и выходы PA6, PA7 для светодиодов :
I2C1 сконфигурируем под наш дисплей SSD1306 : скорость на Fast Mode, не забываем про DMA в TX режиме и NVIC. Кто забыл - ссылка на урок , основное :
На плате W25Q16 подключена к SPI1 в альтернативной конфигурации :
SPI1 --------- STM32pin
- SCK ------ PB3
- MISO ----- PB4
- MOSI ----- PB5
С первого раза найти куда подключать, получится не у всех, поэтому расскажу поподробнее. На гребенку, контакты контроллера PB3 - PB5 не выведены, но они есть на разъеме NRF. Фото, чтобы было понятнее и распиновка разъема :
NRF -------- STM ------- PSRAM64H
- 1 ------ GND ----- GND
- 2 ------ 3V3 ----- 3V3
- 5 ------ SCK ---- SCLK
- 6 ------ MOSI ---- SI
- 7 ------ MISO ---- SO
- na ----- PE2 ----- CE
Включаем SPI1 Full-Duplex Master, Hardware NSS - Disable, остальные настройки следующие :
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 */
Если микросхема чистая, то любуемся на 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 */
Записать 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 :
Напишем функцию чтения, читать можно разными размерами блока и с любого адреса, я выбрал буфер в 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 */
Полюбуемся немного мусором из памяти, теперь напишем функцию записи в 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 */
Смотрим на первые 4 байта - это то, что мы записали, а далее идет мусор. Следует напомнить, что при отключении питания наши данные пропадут и мы считаем мусор вместо записанных данных.
Эту микросхему я планирую применить в своем wi-fi радио на STM32F4xx. На этом пока все, как всегда, если Вам есть что сказать :
Адрес для контактов : imax9@narod.ru
Если вам понравились мои работы и вы желаете поддержать сайт - сделайте дотацию.
При копировании статьи – обязательна ссылка на авторство и источник. Без разрешения автора копирование запрещено.
© Максим Ильин 2020г.