NRF52832驱动W25Qxx并实现低功耗(FreeRTOS)

    在使用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()确认读写完成,如果是在非操作系统中,不使用改函数。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值