问题—— SPI_FlASH无法写入

在检查许多遍SPI、SPI_Flash的代码后,并未发现与官方示例、书上或是网上有什么不同,可却仍无法解决写入、擦除的问题

①那么就可以先查看一下自己手里这块板子的原理图,是不是SPI选错了,比如我的这块是stm32F407VET6,外扩flash芯片为M25P16(实际上我拿手机的放大镜反复拍摄才发现是ZD25Q16,被骗了),书上写的是SPI2,PB12 、PB13、PB14、 PB15,又被骗了

这时要相信原理图,要接的线是PC9、PC10、PC11、PC12,很容易查到这是SPI3(正常情况下应该会直接标明SPIx的,只是我这原理图比较含蓄)。

②接线也接对了,但是反应很奇怪,比如有时能写入,有时写不进去,总之失灵时不灵,写入读出要么0xFF,要么0x00(可能是读、写函数放得太近了。这多半是硬伤,因为有些flash芯片(或者整个板子)质量太差,这些flash芯片要么只能一页一页写,要么写入极慢。我的则两者兼具,flash芯片写一个字节跟写入一页字节居然差不多,而且还极慢,写一个字节和写一页经过50*256Byte循环写入测试,平均一字节写入或一页写入都在2.3ms左右 (ᇂ_ᇂ|||)

        如果擦除极快,没有如资料上说的15秒之类的,那么这可能是国产芯片,擦除就是这么快。倘若不确定是否真的擦除并且读状态寄存器也不确定的话,可以设置一个全局变量用于累加,看看擦除函数调用的WaitForEnd函数中,读寄存器到底执行了几次循环。也可以用打印输出来调试

这时推荐你换一块板子,如果坚持使用的话,也有解决办法

具体测试代码很简单,使用的是IAR命令行打印数据,方便检验写入的数据是否正常

至于打印数据可以不用串口打印,可以使用IAR、Keil等自带的Terminal IO,亦或直接使用ST Link Unity里的SWO(更快,且不会像前面那样缺字错字,如何配置可以参考嵌入式环境搭建中的目录五-ITM与SWO)

    (说明:以下代码均为裸机上HAL+C++开发)

其中向SPI写入一字节的程序改为下面这样

因为写入过慢,不加入这两个 SPI_FLASH_WaitForWriteEnd()就无法成功写入。如果你的板子写入速度正常就不必加入,否则写入速度就会被拖慢

void SPI_FLASH_WriteByte(uint32_t WriteAddr, uint8_t byte)
{
  SPI_FLASH_WaitForWriteEnd(); // 测试了很多遍无意中发现的
  SPI_FLASH_WriteEnable();     /* 发送FLASH写使能命令 */

  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(PageProgram);
  SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
  SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
  SPI_FLASH_SendByte(WriteAddr & 0xFF);
  SPI_FLASH_SendByte(byte);
  SPI_FLASH_CS_HIGH();
  SPI_FLASH_WaitForWriteEnd(); // 94.8us
}

FLash初始化(解锁的代码也改了一下,加入的是0x50是易失性写入寄存器指令

void SPI_FLASH_Init()
{
  /*初始化可以不进行,默认为0x00*/
  SPI_FLASH_WriteEnable(); // 使能读写
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(0x50); /*这是反复改变BP位的关键*/
  SPI_FLASH_CS_HIGH();

  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(WriteStatusReg);
  SPI_FLASH_SendByte(0x00);
  SPI_FLASH_CS_HIGH();
  SPI_FLASH_WaitForWriteEnd();
}

        然后测试过程中在擦除(我的这块需要暂停调试以手动延时)后发现终于能正常写入,但把ADC中断15625Hz直接降到了400Hz左右(如果使用SPL写的程序就会直接死在ADC中断无法进入while循环)

        取巧的办法就是一页一页写入,ADC中断把数据写入到数组里,while循环用轮询的方式把数据写入到flash里。以独立测试情况下的数据为支撑,理论上ADC写满一个数组用时64us*256=16.384ms,要远远大于2.3ms(实测过程中肯定会有所变化),实测也确实未出现滋滋电流声等由于写入过慢而导致的发声问题

#include "system.h"
System *sys; // 定义一个系统。本想起名为stm32f407,但太长了不好写
uint8_t recordbuffer1[256];
uint8_t recordbuffer2[256];
uint8_t *pbuffer = recordbuffer1;
uint8_t *pbufferwrite = recordbuffer2;
bool bufferflag = 0; // 0表示未完成,1代表完成
// 使用同1个数组512字节分两个区的方法会出问题
int main(void)
{
  HAL_Init();
  sys = new System;     // 系统初始化
  sys->function_init(); // 实现用户功能

  while (1)
  {
    if (bufferflag != 0) // 只要不为0就开始写入
    {
      bufferflag = 0; // 置零
      SPI_FLASH_PageWrite(pbufferwrite, sys->recordaddr - 256, 256);
    }
    else if (sys->key->isvalid())
      sys->keybond(); // 按键绑定
    else
      ;
  }
}

/*键盘中断回调函数*/
extern "C"
{

  /*ADC中断回调函数*/
  void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
  {
    if (sys->recordaddr == 0x200000)
      sys->k1close(); // 满了停止
    else
    {
      //tools为自定义Tool类实例化后的对象
      if (tools.cyclecount(sys->offset, 256))//在[0,256)间循环
      {
        uint8_t *temp = pbuffer;
        pbuffer = pbufferwrite;
        pbufferwrite = temp; // 交换指针
        bufferflag = 1;
        sys->recordaddr += 256;
      }
      *(pbuffer + sys->offset) = HAL_ADC_GetValue(&hadc1) >> 4; // 写入数组
    }
  }

  /*TIM中断回调函数*/
  void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  {
    __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
    if (htim->Instance == TIM6)
    {
      if (tools.cyclecount(sys->csec, 10))
      {
        sys->sec++;
        tools.dispsec(sys->sec);
      }
    }
    else if (htim->Instance == TIM7)
    {
      if (sys->playaddr >= sys->recordaddr)
      {
        sys->k2close();             // 满了停止
        sys->key->resetcode(keyk2); // 按键2显示为关闭状态
      }
      else
      {
        HAL_DAC_SetValue(&hdac, DAC1_CHANNEL_1, DAC_ALIGN_12B_R, SPI_FLASH_ReadByte(sys->playaddr) << 4);
        sys->playaddr++;
      }
    }
    else
      ;
  }

  // 键盘中断回调
  void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  {
    if (GPIO_Pin == GPIO_PIN_0)
    {
      sys->key->code = KEY_RAM & 0xF; // 获取键值
      sys->key->sign = 1;             // 置键有效
    }
  }
}

sysytem.cpp如下

#include "system.h"
// 按键0:擦除 按键1:录音 按键2:放音 按键3:慢放 按键4:快进
/*系统功能模块初始化*/
void System::function_init(void)
{
  /*用户变量初始化*/
  recordaddr = 0;
  playaddr = 0;
  sec = 0;
  csec = 0; // 注:在这里不是百分秒,而是十分秒
  recordcsec = 0;
  recordsec = 0;
  offset = 0; // 缓存偏移初始化
  //startaddr = 0;
  /*用户类的实例化*/

  /*用户功能初始化*/
  MX_SPI3_Init();
  HAL_SPI_MspInit(&hspi3);
  MX_ADC1_Init();
  MX_DAC_Init();
  HAL_ADC_MspInit(&hadc1); // 方便可移植
  HAL_DAC_MspInit(&hdac);

  MX_TIM2_Init();
  MX_TIM6_Init(); // 10Hz
  MX_TIM7_Init();

  HAL_TIM_Base_MspInit(&htim2);
  HAL_TIM_Base_MspInit(&htim6);
  HAL_TIM_Base_MspInit(&htim7);

  HAL_TIM_Base_Start(&htim2);
  HAL_DAC_Start(&hdac, DAC1_CHANNEL_1);
  SPI_FLASH_Init();
  __HAL_TIM_CLEAR_IT(&htim6, TIM_IT_UPDATE); // 清除定时器初始化过程中的更新中断标志,避免定时器一启动就中断
  __HAL_TIM_CLEAR_IT(&htim7, TIM_IT_UPDATE); // 清除定时器初始化过程中的更新中断标志,避免定时器一启动就中断
}

// 按键绑定
void System::keybond()
{
  key->sign = 0;               // 重置键效
  key->reverseflag(key->code); // 键标取反
  switch (key->code)
  {
  case 0x0: // 按键K0

    if (key->flag & keyk0) /*本键启*/
    {
        /*在本键开启(第一个参数1)的情况下,把keyk1、keyk2、keyk3键的功能全部关闭(最后一个参数0)*/
      if (key->operateotherkey(1, keyk1 | keyk2 | keyk3 | keyk4, 0))
      {
        k1close();
        k2close();
        k3close();
        k4close();
        HAL_TIM_Base_Stop_IT(&htim6); // 关闭计时器
      }
      /*再打开本键*/
      k0open();
    }
    else /*本键闭*/
    {
      k0close();
    }
    break;

  case 0x1: // 按键K1
    if (key->flag & keyk1)
    {
      /*先启闭其他键,如果需要的话*/
      if (key->operateotherkey(1, keyk2 | keyk3 | keyk4, 0))
      {
        k2close();
        k3close();
        k4close();
      }

      k1open();
    }
    else
    {
      k1close();
    }
    break;

  case 0x2: // 按键K2
    if (key->flag & keyk2)
    {
      if (key->operateotherkey(1, keyk1, 0))
      {
        k1close(); // 只需关闭录音
        playaddr = 0;
        sec = 0, csec = 0;
      }
      k2open();
    }
    else
    {
      k2close();
    }
    break;

  case 0x3: // 按键K3
    if (key->flag & keyk3)
    {
      if (!key->iskeyopen(keyk1 | keyk0)) // 如果录音开启,那么就不执行慢放
      {
        if (key->operateotherkey(1, keyk4, 0))
        {
          LCD_ShowChineseStringBig(307, 180, 76, 2, YELLOW); // 关闭快进
        }
        k3open();
      }
    }
    else
    {
      k3close();
    }

    break;

  case 0x4: // 按键K4
    if (key->flag & keyk4)
    {
      if (!key->iskeyopen(keyk1 | keyk0)) // 如果录音开启,那么就不执行慢放
      {
        if (key->operateotherkey(1, keyk3, 0))
        {
          LCD_ShowChineseStringBig(307, 220, 78, 2, YELLOW); // 关闭慢放
        }
        k4open();
      }
    }
    else
    {
      k4close();
    }
    break;

  case 0x5: // 按键K5
    break;
  case 0x6: // 按键K6
    break;
  case 0x7: // 按键K7
    break;
  case 0x8: // 按键K8
    break;
  case 0x9: // 按键K9
    break;
  case 0xA: // 按键KA
    break;
  case 0xB: // 按键KB
    break;
  case 0xC: // 按键KC
    break;
  case 0xD: // 按键KD
    break;
  case 0xE: // 按键KE
    break;
  case 0xF: // 按键KF
    break;
  default: // 异常状态
    break;
  }
}

/*系统初始化*/
System::System()
{
  /*基本全局初始化*/
  FSMC_init();          // 灵活静态存储初始化——必不可少
  GPIO_Configuration(); // GPIO初始化
  SystemClock_Config(); // 系统时钟初始化
  LCD_Init9488();       // 液晶初始化
  KEY_EXTI_init();      //  全局中断初始化

  /*基本初始化*/
  TFTLED = 0x01; // 背光寄存器初始化
  key = new Key;

  /*用户基本初始化*/
  UI_init();            // 显示Logo
  tools.delay_ms(2500); // 延时一坤秒左右
  LCD_Clear1(0x0000);   // 清屏
  userUI();             // 显示用户界面
}

System::~System()
{
  delete key;
  key = nullptr;
  // delete timer6;
  // timer6=nullptr;
  // delete dac1;
  // dac1=nullptr;
}

// 全局中断配置
// 全局中断配置
void System::KEY_EXTI_init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  __HAL_RCC_GPIOB_CLK_ENABLE();

  GPIO_InitStructure.Mode = MODE_INPUT;
  GPIO_InitStructure.Pull = GPIO_NOPULL;
  GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
  GPIO_InitStructure.Pin = GPIO_PIN_0;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);

  HAL_NVIC_SetPriority(EXTI0_IRQn, 0x01, 0x02);
  HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}

/*系统时钟配置*/
void System::SystemClock_Config(void)
{
  /*系统时钟168MHz*/
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 12;
  RCC_OscInitStruct.PLL.PLLN = 336;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
}

/************************************************************/
/*                      按键功能设计                         */
/************************************************************/

/*开启键0*/
void System::k0open()
{
  sec = 0, csec = 0; // 计时器清零
  recordaddr = 0;    // 录音地址
  recordcsec = 0, recordsec = 0;
  playaddr = 0;
  tools.dispsec(0);
  HAL_TIM_Base_Start_IT(&htim6);                        // 开启定时器
  LCD_ShowChineseStringBig(161, 220, 70, 2, LIGHTBLUE); // 显示擦除画面
  SPI_FLASH_ChipErase();
  LCD_ShowChineseStringBig(161, 220, 70, 2, YELLOW); // 关闭擦除画面
  HAL_TIM_Base_Stop_IT(&htim6);
}

/*关闭键0*/
void System::k0close()
{
  k0open(); // 擦除不能取消,所以没有重复开关的功能
}

void System::k1open()
{
  if (recordaddr == 0) /*判断有没有擦除的必要*/
  {
    uint8_t i = 0;
    uint8_t arr[10];

    SPI_FLASH_BufferRead(arr, i, 10);
    for (; i < 10; i++) /*只要有数据就清空*/
      if (arr[i] != Dummy_Byte)
      {
        k0open();
        break;
      }
  }
  LCD_ShowChineseStringBig(161, 180, 72, 2, LIGHTBLUE); // 显示录音
  sec = recordsec, csec = recordcsec;
  tools.dispsec(sec);
  HAL_TIM_Base_Start_IT(&htim6);
  HAL_ADC_Start_IT(&hadc1); // 开启ADC
}

void System::k1close()
{

  HAL_ADC_Stop_IT(&hadc1);      // 关闭ADC
  HAL_TIM_Base_Stop_IT(&htim6); // 关闭计时器
  recordcsec = csec;
  recordsec = sec;
  csec = 0; // 为了把放音清除
  sec = 0;
  playaddr = 0;
  LCD_ShowChineseStringBig(161, 180, 72, 2, YELLOW); // 显示录音
}

void System::k2open()
{
  if (recordaddr == playaddr)
    playaddr = 0, sec = 0, csec = 0; // 置零
  tools.dispsec(sec);
  LCD_ShowChineseStringBig(161, 140, 74, 2, LIGHTBLUE); // 蓝为开启
  HAL_TIM_Base_Start_IT(&htim6);                        // 打开计时器
  HAL_TIM_Base_Start_IT(&htim7);                        // 打开放音用的中断
}

void System::k2close()
{
  HAL_TIM_Base_Stop_IT(&htim6); // 关闭计时器
  HAL_TIM_Base_Stop_IT(&htim7);
  LCD_ShowChineseStringBig(161, 140, 74, 2, YELLOW); // 黄为关闭
}

void System::k3open()
{
  __HAL_TIM_SetAutoreload(&htim6, 12599); // 慢放2/3
  __HAL_TIM_SetAutoreload(&htim7, 125);
  LCD_ShowChineseStringBig(307, 220, 78, 2, LIGHTBLUE); // 慢放
}

void System::k3close()
{
  __HAL_TIM_SetAutoreload(&htim6, 8399); // 恢复
  __HAL_TIM_SetAutoreload(&htim7, 83);
  LCD_ShowChineseStringBig(307, 220, 78, 2, YELLOW); // 慢放
}

void System::k4open()
{
  __HAL_TIM_SetAutoreload(&htim6, 4799); // 快进 1.75,两倍速会卡住,因为HAL库太占资源
  __HAL_TIM_SetAutoreload(&htim7, 47);
  LCD_ShowChineseStringBig(307, 180, 76, 2, LIGHTBLUE); // 快进
}

void System::k4close()
{
  __HAL_TIM_SetAutoreload(&htim6, 8399); // 恢复
  __HAL_TIM_SetAutoreload(&htim7, 83);
  LCD_ShowChineseStringBig(307, 180, 76, 2, YELLOW); // 快进
}

system.h文件如下

#ifndef _SYSTEM_H
#define _SYSTEM_H
#include "stm32f4xx.h" //必须放在最上面,你也不想它突然报几百个错吧
#include "stm32f4xx_hal_conf.h"
#include "stm32f4xx_it.h"
//#include <iostream>
/*DATA*/
#include "logo.h"
#include "FONT.h"
#include "WAVEDAT.h"
/*USER*/
#include "AD-DA.h"
#include "flash.h"
#include "FSMC.h"
#include "LCD.h"
#include "spi.h"
#include "spi_flash.h"
#include "tools.h"
#include "timer.h"
#include "tools.h"
#include "UI.h"
#include "usart.h"
#include "key.h"
/*指针类*/
#define KEY_RAM (*((volatile unsigned short *)0x6006000C)) // 键盘接口地址
#define IO_CS (*((volatile unsigned short *)0x60020000))   // MCU-IO扩展模块中并行IO片选地址

class System
{

public:
    System(); // 系统初始化
    ~System();

    /*初始化*/

    void KEY_EXTI_init(void); // 键盘外部中断配置
    void function_init(void);
    void SystemClock_Config(void);

    /*功能模块设计*/
    void keybond(void); // 按键绑定

    /*键区*/
    void k0open(); // 擦除
    void k0close();
    void k1open(); // 录音
    void k1close();
    void k2open(); // 放音
    void k2close();
    void k3open(); // 快进
    void k3close();
    void k4open(); // 慢放
    void k4close();

public:
    /*基本类成员*/

    Key *key;

    /*用户类成员*/

public:
    /*句柄*/

public:
    /*标志类*/
    /*数值类*/
    uint8_t min, csec; // 分钟、秒、百分秒
    uint16_t sec;
    uint8_t recordcsec;
    uint16_t recordsec;

    /*计数类*/

    /*指针型*/
    uint32_t recordaddr; // 录音地址
    uint32_t playaddr;   // 放音地址
                         // uint32_t startaddr;
    uint16_t offset;

    /*debugger*/
};

#endif

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值