在检查许多遍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