在使用52832的时候,有时候需要存储大量的数据,就需要外置存储芯片,这里我使用的存储是W25Qxx系列,并实现低功耗。
为了方便实现低功耗和代码移植的方便,这里我是用的是模拟SPI驱动W25Qxx.废话少说,上代码:
w25qxx.h
#ifndef __W25QXX_H__
#define __W25QXX_H__
#include "boards.h"
#include "app_error.h"
#define W25QXX_ADD_BASE (0) //W25QXX的起始地址
#define W25QXX_ADD_END (128*1024*1024-1) //W25Q128的结束地址,如果是其它器件,需要修改此项大小
#define SECTOR_SIZE 4096 //扇区大小
#define PAGE_SIZE 256 //页大小
/**
* 函数名:W25Qxx_PowerDown
* 功能:进入掉电模式
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_PowerDown(void);
/**
* 函数名:W25Qxx_WakeUP
* 功能:唤醒
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_WakeUP(void);
/**
* 函数名:W25Qxx_Erase_Sector
* 功能:擦除一个扇区
* 输入参数:
* sectorAddr:扇区地址
* 输出参数:None
* 返回值:None
* 其他:擦除一个扇区至少需要150ms
**/
void W25Qxx_Erase_Sector(uint32_t sectorAddr);
/**
* 函数名:W25Qxx_Write_Page
* 功能:在一个页内写入数据
* 输入参数:
* @pBuffer:数据缓存
* @writeAddr:开始写入的地址
* @writeLen:写入的数据长度(小于页的剩余字节数)
* 输出参数:None
* 返回值:NRF错误类型
* 其他:None
**/
uint32_t W25Qxx_Write_Page(uint8_t *pBuffer, uint32_t writeAddr, uint32_t writeLen);
/**
* 函数名:W25Qxx_Write_NoCheck
* 功能:从指定地址开始写入指定长度的数据
* 输入参数:
* @pBuffer:数据缓存
* @writeAddr:开始写入的地址
* @writeLen:写入的数据长度(开始写入的地址+数据长度小于总地址数)
* 输出参数:None
* 返回值:NRF错误类型
* 其他:写之前必须保证所写地址范围的数据以擦除(数据都为0xFF).
**/
uint32_t W25Qxx_Write_NoCheck(uint8_t *pBuffer, uint32_t writeAddr, uint32_t writeLen);
/**
* 函数名:W25Qxx_Init
* 功能:初始化W25QXX
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_Init(void);
/**
* 函数名:W25Qxx_unInit
* 功能:复位W25QXX
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_unInit(void);
/**
* 函数名:W25Qxx_ReadID
* 功能:读取芯片ID
* 输入参数:None
* 输出参数:None
* 返回值:芯片ID
* 其他:None
**/
uint16_t W25Qxx_ReadID(void);
/**
* 函数名:W25Qxx_Read
* 功能:从指定地址开始读取指定长度的数据
* 输入参数:
* @readAddr:开始读取的地址
* @readLen:读取的数据长度
* 输出参数:
* @pBuffer:读取的数据缓存
* 返回值:NRF错误类型
* 其他:None
**/
uint32_t W25Qxx_Read(uint8_t *pBuffer, uint32_t readAddr, uint32_t readLen);
/**
* 函数名:W25Qxx_Write
* 功能:从指定地址开始写入指定长度的数据
* 输入参数:
* @pBuffer:数据缓存
* @writeAddr:开始写入的地址
* @writeLen:写入的数据长度(开始写入的地址+数据长度小于总地址数)
* 输出参数:None
* 返回值:NRF错误类型
* 其他:该函数自带擦除功能,写入地址可以随意
**/
uint32_t W25Qxx_Write(uint8_t *pBuffer, uint32_t writeAddr, uint32_t writeLen);
/**
* 函数名:W25Qxx_WaitRW
* 功能:等待W25Qxx读写完成
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_WaitRW(void);
#endif
w25qxx.c
#include "w25qxx.h"
#include <string.h>
#ifndef BOOTLOADER
#include "uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#else
#include "nrf_log.h"
#endif
#include "nrf_delay.h"
//指令表
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg 0x05
#define W25X_WriteStatusReg 0x01
#define W25X_ReadData 0x03
#define W25X_FastReadData 0x0B
#define W25X_FastReadDual 0x3B
#define W25X_PageProgram 0x02
#define W25X_BlockErase 0xD8
#define W25X_SectorErase 0x20
#define W25X_ChipErase 0xC7
#define W25X_PowerDown 0xB9
#define W25X_ReleasePowerDown 0xAB
#define W25X_DeviceID 0xAB
#define W25X_ManufactDeviceID 0x90
#define W25X_JedecDeviceID 0x9F
//模拟引脚使用
/*
SPIM2_SCK_PIN、SPIM2_MOSI_PIN等是引脚号
*/
#define W25QXX_SCK(x) nrf_gpio_pin_write(SPIM2_SCK_PIN, x)
#define W25QXX_MOSI(x) nrf_gpio_pin_write(SPIM2_MOSI_PIN, x)
#define W25QXX_MISO nrf_gpio_pin_read(SPIM2_MISO_PIN)
#define SPIM2_SS_EN nrf_gpio_pin_write(SPIM2_SS_PIN, 0)
#define SPIM2_SS_DIS nrf_gpio_pin_write(SPIM2_SS_PIN, 1)
static SemaphoreHandle_t MutexSemaphore; //flash写入的互斥信号量
/**
* 函数名:SPI2_ReadWriteByte
* 功能:SPI读写
* 输入参数:
* @TxData:发送的数据
* 输出参数:None
* 返回值:接收的数据
* 其他:None
**/
static uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{
uint32_t i = 0;
uint8_t RxData = 0;
taskENTER_CRITICAL();
W25QXX_SCK(0);
for(i = 8; i > 0; i--)
{
W25QXX_SCK(0);
if(TxData & (1 << (i - 1))){
W25QXX_MOSI(1);
}
else{
W25QXX_MOSI(0);
}
nrf_delay_us(1);
W25QXX_SCK(1);
RxData <<= 1;
RxData |= W25QXX_MISO;
nrf_delay_us(1);
}
W25QXX_SCK(0);
taskEXIT_CRITICAL();
return RxData;
}
/**
* 函数名:W25Qxx_ReadSR
* 功能:读取状态寄存器
* 输入参数:None
* 输出参数:None
* 返回值:状态寄存器值
* 其他:None
**/
static uint8_t W25Qxx_ReadSR(void)
{
uint8_t data = 0;
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_ReadStatusReg);
data = SPI2_ReadWriteByte(0xFF);
SPIM2_SS_DIS;
return data;
}
#if 0
/**
* 函数名:W25Qxx_WriteSR
* 功能:写状态寄存器
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
static void W25Qxx_WriteSR(uint8_t sr)
{
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_WriteStatusReg);
SPI2_ReadWriteByte(sr);
SPIM2_SS_DIS;
}
#endif
/**
* 函数名:W25Qxx_WriteEnable
* 功能:写使能
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
static void W25Qxx_WriteEnable(void)
{
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_WriteEnable);
SPIM2_SS_DIS;
}
#if 0
/**
* 函数名:W25Qxx_WriteDisenable
* 功能:写禁止
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
static void W25Qxx_WriteDisenable(void)
{
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_WriteDisable);
SPIM2_SS_DIS;
}
#endif
/**
* 函数名:W25Qxx_Busy
* 功能:等待总线空闲
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
static void W25Qxx_Wait_Busy(void)
{
使用操作系统
while((W25Qxx_ReadSR() & 0x01) == 0x01)
{
vTaskDelay(5); //5MS检测一次
}
// while((W25Qxx_ReadSR() & 0x01) == 0x01); //不使用操作系统
}
/**
* 函数名:W25Qxx_PowerDown
* 功能:进入掉电模式
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_PowerDown(void)
{
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_PowerDown);
SPIM2_SS_DIS;
}
/**
* 函数名:W25Qxx_WakeUP
* 功能:唤醒
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_WakeUP(void)
{
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_ReleasePowerDown);
SPIM2_SS_DIS;
}
/**
* 函数名:W25Qxx_Init
* 功能:初始化W25QXX
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_Init(void)
{
static uint8_t flag = 0;
if( flag == 0 )
{
flag = 1;
MutexSemaphore = xSemaphoreCreateMutex();
if( MutexSemaphore == NULL ){
APP_ERROR_CHECK(NRF_ERROR_NO_MEM);
}
}
nrf_gpio_cfg_output(SPIM2_SS_PIN);
nrf_gpio_cfg_output(SPIM2_MOSI_PIN);
nrf_gpio_cfg_output(SPIM2_SCK_PIN);
nrf_gpio_cfg_input(SPIM2_MISO_PIN, NRF_GPIO_PIN_PULLDOWN);
W25Qxx_PowerDown();
}
/**
* 函数名:W25Qxx_unInit
* 功能:复位W25QXX
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_unInit(void)
{
nrf_gpio_cfg_default(SPIM2_SS_PIN);
nrf_gpio_cfg_default(SPIM2_MOSI_PIN);
nrf_gpio_cfg_default(SPIM2_SCK_PIN);
nrf_gpio_cfg_default(SPIM2_MISO_PIN);
}
/**
* 函数名:W25Qxx_ReadID
* 功能:读取芯片ID
* 输入参数:None
* 输出参数:None
* 返回值:芯片ID
* 其他:None
**/
uint16_t W25Qxx_ReadID(void)
{
uint16_t id = 0;
W25Qxx_WakeUP();
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_ManufactDeviceID);
SPI2_ReadWriteByte(0x00);
SPI2_ReadWriteByte(0x00);
SPI2_ReadWriteByte(0x00);
id |= SPI2_ReadWriteByte(0xFF) << 8;
id |= SPI2_ReadWriteByte(0xFF);
SPIM2_SS_DIS;
W25Qxx_PowerDown();
return id;
}
#if 0
/**
* 函数名:W25Qxx_Erase_Chip
* 功能:全片擦除
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:等待时间很长
**/
static void W25Qxx_Erase_Chip(void)
{
W25Qxx_Wait_Busy();
W25Qxx_WriteEnable();
W25Qxx_Wait_Busy();
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_ChipErase);
SPIM2_SS_DIS;
W25Qxx_Wait_Busy();
}
#endif
/**
* 函数名:W25Qxx_Erase_Sector
* 功能:擦除一个扇区
* 输入参数:
* sectorAddr:扇区地址
* 输出参数:None
* 返回值:None
* 其他:擦除一个扇区至少需要150ms
**/
void W25Qxx_Erase_Sector(uint32_t sectorAddr)
{
sectorAddr *= SECTOR_SIZE;
W25Qxx_WakeUP();
W25Qxx_Wait_Busy();
W25Qxx_WriteEnable(); //SET WEL
W25Qxx_Wait_Busy();
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_SectorErase);
SPI2_ReadWriteByte((uint8_t)((sectorAddr)>>16));
SPI2_ReadWriteByte((uint8_t)((sectorAddr)>>8));
SPI2_ReadWriteByte((uint8_t)sectorAddr);
SPIM2_SS_DIS;
W25Qxx_Wait_Busy();
W25Qxx_PowerDown();
}
/**
* 函数名:W25Qxx_Read
* 功能:从指定地址开始读取指定长度的数据
* 输入参数:
* @readAddr:开始读取的地址
* @readLen:读取的数据长度
* 输出参数:
* @pBuffer:读取的数据缓存
* 返回值:NRF错误类型
* 其他:None
**/
uint32_t W25Qxx_Read(uint8_t *pBuffer, uint32_t readAddr, uint32_t readLen)
{
uint32_t err_code = NRF_SUCCESS;
xSemaphoreTake(MutexSemaphore,portMAX_DELAY);
W25Qxx_WakeUP();
W25Qxx_Wait_Busy();
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_ReadData);
SPI2_ReadWriteByte((uint8_t)((readAddr)>>16));
SPI2_ReadWriteByte((uint8_t)((readAddr)>>8));
SPI2_ReadWriteByte((uint8_t)readAddr);
for(uint32_t i = 0; i < readLen; i++)
{
pBuffer[i] = SPI2_ReadWriteByte(0xFF);
}
SPIM2_SS_DIS;
W25Qxx_PowerDown();
xSemaphoreGive(MutexSemaphore);
return err_code;
}
/**
* 函数名:W25Qxx_Write_Page
* 功能:在一个页内写入数据
* 输入参数:
* @pBuffer:数据缓存
* @writeAddr:开始写入的地址
* @writeLen:写入的数据长度(小于页的剩余字节数)
* 输出参数:None
* 返回值:NRF错误类型
* 其他:None
**/
uint32_t W25Qxx_Write_Page(uint8_t *pBuffer, uint32_t writeAddr, uint32_t writeLen)
{
uint32_t err_code = NRF_SUCCESS;
W25Qxx_WakeUP();
W25Qxx_Wait_Busy();
W25Qxx_WriteEnable();
SPIM2_SS_EN;
SPI2_ReadWriteByte(W25X_PageProgram);
SPI2_ReadWriteByte((uint8_t)((writeAddr)>>16));
SPI2_ReadWriteByte((uint8_t)((writeAddr)>>8));
SPI2_ReadWriteByte((uint8_t)writeAddr);
for(uint32_t i = 0; i < writeLen; i++)
{
SPI2_ReadWriteByte(pBuffer[i]);
}
SPIM2_SS_DIS;
W25Qxx_Wait_Busy();
W25Qxx_PowerDown();
return err_code;
}
/**
* 函数名:W25Qxx_Write_NoCheck
* 功能:从指定地址开始写入指定长度的数据
* 输入参数:
* @pBuffer:数据缓存
* @writeAddr:开始写入的地址
* @writeLen:写入的数据长度(开始写入的地址+数据长度小于总地址数)
* 输出参数:None
* 返回值:NRF错误类型
* 其他:写之前必须保证所写地址范围的数据以擦除(数据都为0xFF).
**/
uint32_t W25Qxx_Write_NoCheck(uint8_t *pBuffer, uint32_t writeAddr, uint32_t writeLen)
{
uint32_t err_code;
uint32_t pageremain = 0; //写入的字节长度
pageremain = PAGE_SIZE - writeAddr % PAGE_SIZE; //开始写入的起始页还能写入的字节数
if( writeLen <= pageremain ) //如果写入的起始页就能将数据写完
pageremain = writeLen;
while(1)
{
err_code = W25Qxx_Write_Page(pBuffer, writeAddr, pageremain);
if( err_code != NRF_SUCCESS )
break;
if( writeLen == pageremain ) //写入结束
break;
else
{
pBuffer += pageremain; //调整数据缓存
writeAddr += pageremain; //调整写入地址
writeLen -= pageremain; //剩余需要写入的字节数
if( writeLen > PAGE_SIZE ) //如果剩余的字节数大于页
pageremain = PAGE_SIZE;
else
pageremain = writeLen;
}
}
return err_code;
}
/**
* 函数名:W25Qxx_Write
* 功能:从指定地址开始写入指定长度的数据
* 输入参数:
* @pBuffer:数据缓存
* @writeAddr:开始写入的地址
* @writeLen:写入的数据长度(开始写入的地址+数据长度小于总地址数)
* 输出参数:None
* 返回值:NRF错误类型
* 其他:该函数自带擦除功能,写入地址可以随意
**/
static uint8_t W25QXX_BUF[SECTOR_SIZE] = {0};
uint32_t W25Qxx_Write(uint8_t *pBuffer, uint32_t writeAddr, uint32_t writeLen)
{
uint32_t err_code;
uint32_t secpos;
uint16_t secoff;
uint16_t secremain; //单次写入数据量
uint16_t i;
xSemaphoreTake(MutexSemaphore,portMAX_DELAY);
secpos = writeAddr / SECTOR_SIZE; //写入数据的第一个扇区地址
secoff = writeAddr % SECTOR_SIZE; //写入数据的第一个扇区内偏移地址
secremain = SECTOR_SIZE - secoff; //写入数据的第一个扇区内剩余空间
if(writeLen <= secremain) //如果一个扇区能写完
secremain=writeLen;
for(;;)
{
xSemaphoreGive(MutexSemaphore);
memset(W25QXX_BUF, 0, SECTOR_SIZE);
err_code = W25Qxx_Read(W25QXX_BUF, (secpos * SECTOR_SIZE), SECTOR_SIZE); //将扇区的数据都读取出来
xSemaphoreTake(MutexSemaphore,portMAX_DELAY);
if( err_code != NRF_SUCCESS)
break;
for(i = 0; i < secremain; i++)
{
if( W25QXX_BUF[secoff + i] != 0xFF ) //校验扇区是否需要擦除
break;
}
if( i < secremain ) //此次写入的扇区需要擦除
{
W25Qxx_Erase_Sector(secpos); //擦除这个扇区
memcpy(&W25QXX_BUF[secoff], pBuffer, secremain ); //将要写入的数据和原来扇区的数据组合
err_code = W25Qxx_Write_NoCheck(W25QXX_BUF, secpos * SECTOR_SIZE, SECTOR_SIZE); //向扇区写数据
if( err_code != NRF_SUCCESS)
break;
}
else //不需要擦除
{
err_code = W25Qxx_Write_NoCheck(pBuffer, writeAddr, secremain); //向扇区写数据
if( err_code != NRF_SUCCESS)
break;
}
if( writeLen == secremain ) //写入完成
break;
else
{
secpos++; //写下一个扇区
secoff = 0; //扇区便宜地址为0
pBuffer += secremain; //数据指针偏移
writeAddr += secremain; //写地址偏移
writeLen -= secremain; //剩余字节数递减
if( writeLen > SECTOR_SIZE ) //剩余字节数大于一个扇区
secremain = SECTOR_SIZE;
else
secremain = writeLen;
}
}
xSemaphoreGive(MutexSemaphore);
return err_code;
}
/**
* 函数名:W25Qxx_WaitRW
* 功能:等待W25Qxx读写完成
* 输入参数:None
* 输出参数:None
* 返回值:None
* 其他:None
**/
void W25Qxx_WaitRW(void)
{
if(pdTRUE == xSemaphoreTake(MutexSemaphore, 3000/portTICK_PERIOD_MS)) //阻塞等待读写完成
xSemaphoreGive(MutexSemaphore);
}
在使用w25qxx的时候先调用W25Qxx_Init(),然后调用W25Qxx_Read()对w25qxx进行读操作,W25Qxx_Write()进行写操作。在操作系统中,调用W25Qxx_WaitRW()确认读写完成,如果是在非操作系统中,不使用改函数。