Работа с флеш памятью Winbond W25Q128

Наконец руки дошли до пришедших мне уже месяц назад микросхем Flash памяти w25q128 от компании Winbond.
Микросхема довольно крутая на свои деньги. В ней куча регистров и команд.
Я рассмотрю только несколько из них.

Я не использовал внешние подтяжки и конденсаторы, а просто собрал схему на макетной плате.

В качестве контроллера я использовал stm32f4 discovery.
SPI был настроен вот так.

void spi2_init() {
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
 
    GPIO_InitTypeDef gpio;
    GPIO_StructInit(&gpio);
 
    gpio.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    gpio.GPIO_Mode = GPIO_Mode_AF;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    gpio.GPIO_OType = GPIO_OType_PP;
    gpio.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB,&gpio);
    
    gpio.GPIO_Pin = GPIO_Pin_12;
    gpio.GPIO_Mode = GPIO_Mode_OUT;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    gpio.GPIO_OType = GPIO_OType_PP;
    gpio.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB,&gpio);
 
 
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_SPI2);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource14,GPIO_AF_SPI2);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource15,GPIO_AF_SPI2);
 
    SPI_I2S_DeInit(SPI2);
    SPI_InitTypeDef spi2;
    SPI_StructInit(&spi2);
 
    spi2.SPI_Mode = SPI_Mode_Master;
    spi2.SPI_DataSize = SPI_DataSize_8b;
    spi2.SPI_NSS = SPI_NSS_Soft;
    spi2.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
    SPI_Init(SPI2,&spi2);
    SPI_Cmd(SPI2,ENABLE);
}

u8 w25q128_spi_send(u8 data)
{
    /* Fill output buffer with data */
    SPI2->DR = data;
    /* Wait for transmission to complete */
    while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE));
    /* Wait for received data to complete */
    while (!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE));
    /* Wait for SPI to be ready */
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY));
    /* Return data from buffer */
    return SPI2->DR;
}

Перед использованием микросхему нужно сбросить. Для этого есть отдельная ножка, но мы не будем завязываться на неё. Ибо она отсутствует в некоторых корпусах да и ноги в контроллере обычно в дефиците. Поэтому произведем программный сброс.

Сброс производится в два присеста. Сначала нужно разрешить сброс, а уже только после этого сбрасывать.

void w25q128_reset()
{
    CS_LOW;
    w25q128_spi_send(W25_ENABLE_RESET);
    CS_HIGH;

    CS_LOW;
    w25q128_spi_send(W25_RESET);
    CS_HIGH;
}

Команды можно определить при помощи define или же как я при помощи перечисления.

typedef enum {
    W25_WRITE_DISABLE = 0x04,
    W25_WRITE_ENABLE = 0x06,
    
    W25_READ_STATUS_1 = 0x05,
    W25_READ_STATUS_2 = 0x35,
    W25_READ_STATUS_3 = 0x15,
    
    W25_WRITE_STATUS_1 = 0x01,
    W25_WRITE_STATUS_2 = 0x31,
    W25_WRITE_STATUS_3 = 0x11,
    
    W25_CHIP_ERASE = 0xc7, //0x60
    
    W25_GET_DEV_ID = 0x90,
    W25_GET_JEDEC_ID = 0x9f,
    
    W25_ENABLE_RESET = 0x66,
    W25_RESET = 0x99,
    
    W25_PAGE_PROGRAMM = 0x02,
    W25_READ = 0x03,
} W25_Command_t;  

Перед программированием нужно стереть память. Это можно сделать при помощи команды стирания чипа.

void w25q128_chip_erase()
{
    CS_LOW;
    w25q128_spi_send(W25_CHIP_ERASE);
    CS_HIGH;
}

Или же команд стирания блоков, страниц.
Стирание происходит довольно долго. Поэтому нужно либо ждать, либо отдать управление операционной системе, если вы её используете.

w25q128_chip_erase();
//ждем готовности
do{
    r.all = w25q128_read_status_1();
}while(r.bit.busy);

Для этого нам нужно считать регистр статуса.
Всего их в w25q128 три. Нам нужен первый.

u8 w25q128_read_status_1()
{
    u8 resp;
    CS_LOW;
    w25q128_spi_send(W25_READ_STATUS_1);
    w25q128_read(&resp,1);
    CS_HIGH;
    
    return resp;
}

Я любитель использовать объединения. Поэтому написал тип описывающий этот регистр.

typedef struct {
    u8 busy : 1;
    u8 write_enable : 1;
    u8 block_protect : 3;
    u8 top_bot_ptotect : 1;
    u8 sector_protect : 1;
    u8 status_reg_protect0 : 1;
} STATUS_REG1_STRUCT_t;

typedef union {
    u8 all;
    STATUS_REG1_STRUCT_t bit;
} Status_reg_1_t;

После стирания мы готовы записать тестовый блок данных.

//наполняем буфер
for (i=0;i<255;i++)
{
    page[i] = i;
}
    while(1)
        {
     
        r.all  = w25q128_read_status_1();
        if (!r.bit.busy)
        {
            if (!programmed)
            {
                w25q128_page_programm(page, 0, 10);
                do{
                    r.all = w25q128_read_status_1();
                }while(r.bit.busy);
                
                memset(page, 0, 255);
                
                programmed = 1;
            } else {
                w25q128_read_page(page, 0, 255);
            }
        }
}

Тут мы читаем статусный регистр и анализируем бит готовности. Если еще не проводилось программирование памяти, записываем страницу размером 10 байт с 0 адреса. Ждем готовности, сбрасываем буфер в ноль и переходим к чтению участка с нулевого адреса размером 255 байт.

Функция записи в память выглядит вот так.

u8 w25q128_page_programm(const u8* data, u32 addr, u8 len)
{
    w25q128_write_enable();
    
    CS_LOW;
    w25q128_spi_send(W25_PAGE_PROGRAMM);
    w25q128_spi_send((addr >> 16) & 0xFF);
    w25q128_spi_send((addr >> 8) & 0xFF);
    w25q128_spi_send(addr & 0xFF);
    while(len--)
    {
        w25q128_spi_send(*data++);
    }
    CS_HIGH;
    return 0;
}

Мы просто шлем команду записи и 3 байта адреса, после чего шлем данные.
Чтение производится аналогично, только команда другая.

u8 w25q128_read_page(u8* data, u32 addr, u8 len)
{
    CS_LOW;
    w25q128_spi_send(W25_READ);
    w25q128_spi_send((addr >> 16) & 0xFF);
    w25q128_spi_send((addr >> 8) & 0xFF);
    w25q128_spi_send(addr & 0xFF);
    while(len--)
    {
        *data++ = w25q128_spi_send(0x00);
    }
    CS_HIGH;
    return 0;
}

В отдельных случаях необходимо также проверить с какой микросхемой мы имеем дело.
Это можно сделать при помощи команды запроса идентификационных данных.

u8 w25q128_ident()
{
    const u8 get_id[] = {W25_GET_DEV_ID, 0,0,0 };
    u8 buff[2], ret;
    
    CS_LOW;
    w25q128_write(get_id, 4);
    w25q128_read(buff,2);
    CS_HIGH;
    
    if (buff[0] != 0xef && buff[1] != 0x17)
    {
        ret = W25_IDENT_ERROR;
    } else {
        ret = W25_OK;
    }
    return ret;
}

В ответ нам вернется EFh — идентификатор производителя и 17h — идентификатор микросхемы.

Приятного дня 😉

 

Похожий код:

Фото аватара
Алексей Петров

Программист, разработчик с 5 летним опытом работы. Учусь на разработчика игр на Unity и разработчика VR&AR реальности (виртуальной реальности). Основные языки программирования: C#, C++.

Оцените автора
Бла, бла код
Добавить комментарий

  1. Денис

    Я пишу код для работы AVR с памятью W25q32fw, она такая же меньше объемом, сделал сначала по даташиту, потом нашел вашу статью, все так же, потом проверил на логическом анализаторе, все как нужно, дошел до того что снял логическим анализатором цикл записи памяти с программатора CH341, и полностью его повторил,(там на самом деле как по даташиту и так как у меня и было), даже частоты передачи одинаковы, по 8МГц. Так вот вопрос, что может быть не так ? Хотя бы пальцем в небо, потому что проверил все. Запись произвожу первых двух блоков по 256.

    Ответить
  2. Иван

    Где узнать про

    Ответить