:::: MENU ::::

Простая библиотека для памяти (Serial NOR Flash) типа M25, часть 2

Микросхемы памяти M25xxx представлены на рынке различными производителями. Они имеют одинаковый интерфейс и алгоритм работы. Далее будет представлена простенькая реализация библиотеки для работы с данной памятью.

Ссылка на DataSheet — M25P64.

Разделим программу управления памятью на две части. Одна будет универсальная (переносимая) для разных микроконтроллеров, а вторая драйвер, который нужно модифицировать для каждого нового микроконтроллера. В драйвере будут реализованы такие действия как поднять или опустить (высокий, низкий уровень) ту или иную ножку микросхемы памяти и общение по SPI.

Я использую микроконтроллер STM32L:

  • S = CS = PB0;
  • W = write protect = PB1;
  • H = hold = PA4;
  • SPI1 (PA5, PA6, PA7)

Код драйвера содержит инициализацию периферии и функции для ее управления, для того чтобы не менять универсальную часть программы — не меняйте название функций драйвера, а просто измените их реализацию для своего микроконтроллера. Кроме того вам нужно реализовать функцию задержки wait_us — см. код и см. datasheet (timeout).

// инициализация всех портов и spi
void m25p64_driver_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;

    // S = CS = PB0; W = write protect = PB1; H = hold = PA4
    // *******************************************************
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // *******************************************************

    // SPI 
    // *******************************************************
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; 
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; 
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 
    SPI_InitStructure.SPI_Mode =  SPI_Mode_Master; 
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; 
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; 
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; 
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; 
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; 
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; 
    SPI_Init(SPI1, &SPI_InitStructure);
    SPI_Cmd(SPI1, ENABLE);
    // *******************************************************
}

// управление портами
//tSHSL = 100ns
void m25p64_S_H(void)
{
    GPIO_SetBits(GPIOB, GPIO_Pin_0);
    wait_us(1);
}
void m25p64_S_L(void)
{
    GPIO_ResetBits(GPIOB, GPIO_Pin_0);
}
void m25p64_H_H(void)
{
    GPIO_SetBits(GPIOA, GPIO_Pin_4);
}
void m25p64_H_L(void)
{
    GPIO_ResetBits(GPIOA, GPIO_Pin_4);
}

//tWHSL = 20ns
void m25p64_W_H(void)
{
    GPIO_SetBits(GPIOB, GPIO_Pin_1);
    wait_us(1);
}

//tSHWL = 100ns
void m25p64_W_L(void)
{
    wait_us(1);
    GPIO_ResetBits(GPIOB, GPIO_Pin_1);
}

// передача и прием по spi
void spi_write_then_read(uint8_t *code, uint8_t n_tx, 
                         uint8_t *val, uint8_t n_rx)
{
    uint8_t temp;

    for(temp = 0; temp < (n_tx + n_rx); temp++)
    {
        while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
        SPI_I2S_SendData (SPI1, code[temp]);
        while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);

        if(temp >= n_tx)        
            val[(temp - n_tx)] = SPI_I2S_ReceiveData(SPI1);
        else
            SPI_I2S_ReceiveData(SPI1);
    }
}

void spi_read(uint8_t *val, uint8_t n_rx)
{
    uint8_t temp;

    for(temp = 0; temp < n_rx; temp++)
    {
        while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
        val[temp] = SPI_I2S_ReceiveData(SPI1);
    }
}

Универсальная часть программы содержит сами функции управления микросхемой, описанные в datasheet. При вызове инициализации init_m25p, также вызывается функция драйвера — инициализация периферии, снимается аппаратная защита памяти, отключается hold, и выбирается микросхема для общения по SPICS.

// _H - High - нет паузы
// _W - High - аппаратная защита памяти выкл.
// _S - Low - микросхема выбрана (SPI)
void init_m25p(void)
{
    m25p64_driver_init();

    m25p64_H_H();
    m25p64_W_H();
    m25p64_S_L();
}

При стирании памяти или ее записи, надо ожидать пока операции закончатся — для этого можно читать регистр status (бит WIP) и ждать пока он сбросится:

// проверка завершения операции
uint8_t wait_till_ready(void)
{
    uint8_t sr;

    sr = read_sr();
    sr = sr & 1;
    return(sr);
}

// пишем регистр статуса
void write_sr(uint8_t val)
{
    uint8_t data[2];

    data[0] = OPCODE_WRSR;
    data[1] = val;

    write_enable();

    m25p64_S_L();
    spi_write_then_read(&data[0], 2, NULL, 0);
    m25p64_S_H();
    while(wait_till_ready() == 1);
}

// стираем чип
void erasce_chip(void)
{
    uint8_t code = OPCODE_CHIP_ERASE;

    write_enable();

    m25p64_S_L();
    spi_write_then_read(&code, 1, NULL, 0);
    m25p64_S_H();
    while(wait_till_ready() == 1);
}

// стираем сектор
void erase_sector(uint32_t offset)
{
    uint8_t code[4];
    code[0] = OPCODE_SE;
    code[1] = offset >> 16;
    code[2] = offset >> 8;
    code[3] = offset;

    write_enable();

    m25p64_S_L();
    spi_write_then_read(&code[0], 4, NULL, 0);
    m25p64_S_H();
    while(wait_till_ready() == 1);
}

// пишем во flash
void write_flash(uint32_t adr, uint16_t len, uint8_t *buf)
{
    uint8_t code[16];

    code[0] = OPCODE_PP;
    code[1] = adr >> 16;
    code[2] = adr >> 8;
    code[3] = adr;
    code[4] = buf[0];
    code[5] = buf[1];
    code[6] = buf[2];

    write_enable();

    m25p64_S_L();
    spi_write_then_read(&code[0], 4, NULL, 0);
    spi_write_then_read(&buf[0], len, NULL, 0);
    m25p64_S_H();
    while(wait_till_ready() == 1);
}

Все остальные функции также сделаны в соответствии с datasheet:

// читаем flash
void read_flash(uint32_t adr, uint16_t len, uint8_t *buf)
{
    uint8_t code[4];

    code[0] = OPCODE_READ;
    code[1] = adr >> 16;
    code[2] = adr >> 8;
    code[3] = adr;

    m25p64_S_L();
    spi_write_then_read(&code[0], 4, buf, len);
    m25p64_S_H();
}

// читаем ID
// размер  buf - 3 байта
void read_ID(uint8_t *buf)
{
    uint8_t code = OPCODE_RDID;

    m25p64_S_L();
    spi_write_then_read(&code, 1, &buf[0], 3);
    m25p64_S_H();
}

// читаем регистр статуса
uint8_t read_sr(void)
{
    uint8_t code = OPCODE_RDSR;
    uint8_t val;

    m25p64_S_L();
    spi_write_then_read(&code, 1, &val, 1);
    m25p64_S_H();

    return val;
}

Пример работы:

uint8_t id[3];
uint8_t sign;
uint8_t flash_data[10];
uint8_t wr_flash_data[10] = {0, 1, 2, 3, 4,
                             5, 6, 7, 8, 9};

init_m25p();

sign = read_sign();
if(sign == 0x16)
    read_ID(id);

st = read_sr();

erase_sector(0);
read_flash(300, 10, flash_data);
write_flash(300, 10, wr_flash_data);
read_flash(300, 10, flash_data);

Проект — m25p64 (keil v5).