Микросхемы памяти 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, и выбирается микросхема для общения по SPI — CS.
// _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).