Работа с флеш памятью 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 - идентификатор микросхемы.

Приятного дня ;)

Просмотров:   6228

Комментарии

вт, 09/19/2017 - 14:40
Денис

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

чт, 01/18/2018 - 11:13

Денис, у меня W25Q128F не работала пока я на ногу HOLD/RESET ей единицу не подал, может и Вам поможет.

Добавить комментарий

Plain text

  • HTML-теги не обрабатываются и показываются как обычный текст
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Строки и параграфы переносятся автоматически.
CAPTCHA
Введи эти символы. Ато роботы одолели!