正点原子讲解SPI学习,驱动编程NOR FLASH实战

配置SPI传输速度时,需要先失能SPI,__HAL_SPI_DISABLE,然后操作SPI_CR1中的波特率设置位,再使能SPI,

NM25Q128驱动步骤

myspi.c


#include "./BSP/MYSPI/myspi.h"SPI_HandleTypeDef g_spi1_handler;  /* SPI句柄 */void spi1_init(void)
{g_spi1_handler.Instance					= SPI1_SPI;         //SPI1基地址g_spi1_handler.Init.Mode				= SPI_MODE_MASTER;  //设置SPI模式g_spi1_handler.Init.Direction			= SPI_DIRECTION_2LINES; //spi读写g_spi1_handler.Init.DataSize            = SPI_DATASIZE_8BIT;   //g_spi1_handler.Init.CLKPolarity			= SPI_POLARITY_HIGH;   //时钟相位CPOL为1g_spi1_handler.Init.CLKPhase            = SPI_PHASE_2EDGE;    //时钟极性CPLA为1g_spi1_handler.Init.NSS					= SPI_NSS_SOFT;   //NSS管理是软件g_spi1_handler.Init.BaudRatePrescaler   = SPI_BAUDRATEPRESCALER_256;  //波特率分频最大g_spi1_handler.Init.FirstBit			= SPI_FIRSTBIT_MSB;     //发送高位在前g_spi1_handler.Init.TIMode              = SPI_TIMODE_DISABLE;  //帧格式失能g_spi1_handler.Init.CRCCalculation		= SPI_CRCCALCULATION_DISABLE;  //CRC校验失能g_spi1_handler.Init.CRCPolynomial       = 7;    //设置CRC校验多项式(0~65535),默认值7HAL_SPI_Init(&g_spi1_handler);
}//SPI2底层驱动,时钟使能,引脚配置
//此函数会被HAL_SPI_Init()调用
void HAL_SPI_Msp_Init(SPI_HandleTypeDef *hspi)
{GPIO_InitTypeDef gpio_init_struct;if(hspi->Instance == SPI1_SPI) //基地址正确{SPI1_SCK_GPIO_CLK_ENABLE();  /* SPI1_SCK脚时钟使能 */SPI1_MISO_GPIO_CLK_ENABLE(); /* SPI1_MISO脚时钟使能 */SPI1_MOSI_GPIO_CLK_ENABLE(); /* SPI1_MOSI脚时钟使能 *//*SCK引脚模式设置(复用输出)*/gpio_init_struct.Pin = SPI1_SCK_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_AF_PP;  //复用推挽输出gpio_init_struct.Pull = GPIO_PULLUP;   gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(SPI1_SCK_GPIO_PORT, &gpio_init_struct);/*MISO引脚模式设置(复用输出)*/gpio_init_struct.Pin = SPI1_MISO_GPIO_PIN;HAL_GPIO_Init(SPI1_MISO_GPIO_PORT, &gpio_init_struct);/*MOSI引脚模式配置(复用输出)*/gpio_init_struct.Pin = SPI1_MOSI_GPIO_PIN;HAL_GPIO_Init(SPI1_MISO_GPIO_PORT, &gpio_init_struct);}
}//spi发送接收函数
uint8_t spi1_read_write_byte(uint8_t data)
{uint8_t rec_data = 0;//发送data,接收到rec_data,HAL_SPI_TransmitReceive(&g_spi1_handler, &data, &rec_data, 1, 1000);return rec_data;
}//SPI1速度设置函数
void spi1_set_speed(uint8_t speed)
{assert_param(IS_SPI_BAUDRATE_PRESCALER(speed));  /* 判断有效性 */__HAL_SPI_DISABLE(&g_spi1_handler);  /*关闭SPI*/g_spi1_handler.Instance->CR1 &= 0xFFC7;  /* 位3-5清零,用来设置1波特率 */g_spi1_handler.Instance->CR1 |= speed << 3;   /* 设置SPI速度 */__HAL_SPI_ENABLE(&g_spi1_handler);  /*打开SPI*/
}

myspi.h


#ifndef __MYSPI_H
#define __MYSPI_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/
/* SPI2 引脚 定义 */#define SPI1_SCK_GPIO_PORT              GPIOB
#define SPI1_SCK_GPIO_PIN               GPIO_PIN_10
#define SPI1_SCK_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */#define SPI1_MISO_GPIO_PORT             GPIOB
#define SPI1_MISO_GPIO_PIN              GPIO_PIN_11
#define SPI1_MISO_GPIO_CLK_ENABLE()     do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */#define SPI1_MOSI_GPIO_PORT             GPIOB
#define SPI1_MOSI_GPIO_PIN              GPIO_PIN_12
#define SPI1_MOSI_GPIO_CLK_ENABLE()     do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 *//* SPI2相关定义 */
#define SPI1_SPI                        SPI1
#define SPI1_SPI_CLK_ENABLE()           do{ __HAL_RCC_SPI1_CLK_ENABLE(); }while(0)    /* SPI2时钟使能 *//******************************************************************************************//* SPI总线速度设置 */
#define SPI_SPEED_2         0
#define SPI_SPEED_4         1
#define SPI_SPEED_8         2
#define SPI_SPEED_16        3
#define SPI_SPEED_32        4
#define SPI_SPEED_64        5
#define SPI_SPEED_128       6
#define SPI_SPEED_256       7void spi1_init(void);
void spi1_set_speed(uint8_t speed);
uint8_t spi1_read_write_byte(uint8_t txdata);     
void spi1_set_speed(uint8_t speed);#endif

norflash.c  跟着原子教学写的


#include "./BSP/MYSPI/norflash.h"
#include "./BSP/MYSPI/myspi.h"void norflash_init(void)
{NORFLASH_CS_GPIO_CLK_ENABLE();   /*NOR flash时钟使能*/GPIO_InitTypeDef gpio_init_struct;gpio_init_struct.Pin = NORFLASH_CS_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;gpio_init_struct.Pull = GPIO_PULLUP;gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(NORFLASH_CS_GPIO_PORT, &gpio_init_struct);spi1_init();  //spi1初始化,SCK,MOSI,MISO引脚配置时钟使能spi1_read_write_byte(0xFF);  //发送0xFF,清除DR的作用NORFLASH_CS(1);   //拉高片选
}//读nm25q128状态寄存器1
//读nm25q128状态寄存器BUSY位,SR1的第一位是BUSY,WEL,写使能锁存是第二位
//busy位=0是空闲状态,=1是忙状态
//WEL位=1,是可以写,=0是禁止写
uint8_t norflash_rd_sr1(void)
{uint8_t rec_data = 0;NORFLASH_CS(0);rec_data = spi1_read_write_byte(0x05);   /*读状态寄存器1*/NORFLASH_CS(1); //拉高片选 return rec_data;
}/*读flash指定地址一字节数据*/
uint8_t norflash_read_data(uint32_t addr)
{uint8_t rec_data = 0;//拉低片选NORFLASH_CS(0);/*发送读命令*/spi1_read_write_byte(0x03);  //NM25Q128发送读命令指令/*发送地址,有24位,一次只发一个字节,需要移位操作*/spi1_read_write_byte(addr >> 16);spi1_read_write_byte(addr >> 8);spi1_read_write_byte(addr);/*接收数据*/rec_data = spi1_read_write_byte(0xFF); //发送0xFF过去,交换数据NORFLASH_CS(1);  //拉高片选return rec_data;  //返回读取的一字节数据
}//擦除扇区
void norflash_erase_sector(uint32_t addr)
{//1.写使能,执行页写,扇区擦除,块擦除,片擦除都需要写使能NORFLASH_CS(0);spi1_read_write_byte(0x06);  //主机向NM25Q128写使能指令NORFLASH_CS(1); //拉高片选//2.等待空闲while(norflash_rd_sr1() & 0x01);  //检测写使能是否完成//3.发送擦除扇区指令NORFLASH_CS(0);spi1_read_write_byte(0x20);/*4.发送地址,有24位,一次只发一个字节,需要移位操作*/spi1_read_write_byte(addr >> 16);spi1_read_write_byte(addr >> 8);spi1_read_write_byte(addr); //会自动将该地址的扇区擦除/*5.等待空闲*/while(norflash_rd_sr1() & 0x01);  //检测擦除完成
}//norflash页写
void norflash_write_page(uint8_t data, uint32_t addr)
{/*1.擦除扇区*/norflash_erase_sector(addr);/*2. 写使能*/NORFLASH_CS(0);spi1_read_write_byte(0x06);  //主机向NM25Q128写使能指令NORFLASH_CS(1); //拉高片选/*3.发送页写指令*/NORFLASH_CS(0);spi1_read_write_byte(0x02);/*4.发送地址,有24位,一次只发一个字节,需要移位操作*/spi1_read_write_byte(addr >> 16);spi1_read_write_byte(addr >> 8);spi1_read_write_byte(addr); /*5.要写入的数据*/spi1_read_write_byte(data);NORFLASH_CS(1); //拉高片选/*6.等待写入完成,等待空闲*/while(norflash_rd_sr1() & 0x01);  //等待空闲
}

norflash.h   跟着自己开发板改的引脚,跟着原子教学写的

#ifndef __NORFLASH_H
#define __NORFLASH_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/
/* NORFLASH 片选 引脚 定义 */#define NORFLASH_CS_GPIO_PORT           GPIOB
#define NORFLASH_CS_GPIO_PIN            GPIO_PIN_14
#define NORFLASH_CS_GPIO_CLK_ENABLE()   do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 *//******************************************************************************************//* NORFLASH 片选信号 */
#define NORFLASH_CS(x)      do{ x ? \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_SET) : \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_RESET); \}while(0)/* FLASH芯片列表 */
#define W25Q80      0XEF13          /* W25Q80   芯片ID */
#define W25Q16      0XEF14          /* W25Q16   芯片ID */
#define W25Q32      0XEF15          /* W25Q32   芯片ID */
#define W25Q64      0XEF16          /* W25Q64   芯片ID */
#define W25Q128     0XEF17          /* W25Q128  芯片ID */
#define W25Q256     0XEF18          /* W25Q256  芯片ID */
#define BY25Q64     0X6816          /* BY25Q64  芯片ID */
#define BY25Q128    0X6817          /* BY25Q128 芯片ID */
#define NM25Q64     0X5216          /* NM25Q64  芯片ID */
#define NM25Q128    0X5217          /* NM25Q128 芯片ID *//* 指令表 */
#define FLASH_WriteEnable           0x06 
#define FLASH_WriteDisable          0x04 
#define FLASH_ReadStatusReg1        0x05 
#define FLASH_ReadStatusReg2        0x35 
#define FLASH_ReadStatusReg3        0x15 
#define FLASH_WriteStatusReg1       0x01 
#define FLASH_WriteStatusReg2       0x31 
#define FLASH_WriteStatusReg3       0x11 
#define FLASH_ReadData              0x03 
#define FLASH_FastReadData          0x0B 
#define FLASH_FastReadDual          0x3B 
#define FLASH_FastReadQuad          0xEB  
#define FLASH_PageProgram           0x02 
#define FLASH_PageProgramQuad       0x32 
#define FLASH_BlockErase            0xD8 
#define FLASH_SectorErase           0x20 
#define FLASH_ChipErase             0xC7 
#define FLASH_PowerDown             0xB9 
#define FLASH_ReleasePowerDown      0xAB 
#define FLASH_DeviceID              0xAB 
#define FLASH_ManufactDeviceID      0x90 
#define FLASH_JedecDeviceID         0x9F 
#define FLASH_Enable4ByteAddr       0xB7
#define FLASH_Exit4ByteAddr         0xE9
#define FLASH_SetReadParam          0xC0 
#define FLASH_EnterQPIMode          0x38
#define FLASH_ExitQPIMode           0xFFextern uint16_t norflash_TYPE;      /* 定义FLASH芯片型号 *//* 普通函数 */void norflash_init(void);                   /* 初始化25QXX */
void norflash_erase_sector(uint32_t addr);   /* 扇区擦除 */
uint8_t norflash_rd_sr1(void);     /*读状态寄存器1*/
uint8_t norflash_read_data(uint32_t addr);  /*读flash指定地址一字节数据*/
void norflash_write_page(uint8_t data, uint32_t addr);  //写页#endif

另外,有几个norflash写的函数,当写入字节超过剩余页字节容量

下面是正点例程处理写入超过当前页剩余字节数,超过扇区剩余字节数,扇区内有字节已经被写1,只能整块擦除,的norflash.c代码,我已经加了注释,非常巧妙

/******************************************************************************************************* @file        norflash.c* @author      正点原子团队(ALIENTEK)* @version     V1.0* @date        2021-10-23* @brief       NOR FLASH(25QXX) 驱动代码* @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司***************************************************************************************************** @attention** 实验平台:正点原子 探索者 F407开发板* 在线视频:www.yuanzige.com* 技术论坛:www.openedv.com* 公司网址:www.alientek.com* 购买地址:openedv.taobao.com** 修改说明* V1.0 20211023* 第一次发布******************************************************************************************************/#include "./BSP/SPI/spi.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/NORFLASH/norflash.h"uint16_t g_norflash_type = W25Q128;     /* 默认是NM25Q128 *//*** @brief       初始化SPI NOR FLASH* @param       无* @retval      无*/
void norflash_init(void)
{uint8_t temp;NORFLASH_CS_GPIO_CLK_ENABLE();      /* NORFLASH CS脚 时钟使能 */GPIO_InitTypeDef gpio_init_struct;gpio_init_struct.Pin = NORFLASH_CS_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;gpio_init_struct.Pull = GPIO_PULLUP;gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(NORFLASH_CS_GPIO_PORT, &gpio_init_struct); /* CS引脚模式设置(复用输出) */NORFLASH_CS(1);                         /* 取消片选 */spi1_init();                            /* 初始化SPI1 */spi1_set_speed(SPI_SPEED_4);            /* SPI1 切换到高速状态 21Mhz */g_norflash_type = norflash_read_id();   /* 读取FLASH ID. */if (g_norflash_type == W25Q256)         /* SPI FLASH为W25Q256, 必须使能4字节地址模式 */{temp = norflash_read_sr(3);         /* 读取状态寄存器3,判断地址模式 */if ((temp & 0X01) == 0)             /* 如果不是4字节地址模式,则进入4字节地址模式 */{norflash_write_enable();        /* 写使能 */temp |= 1 << 1;                 /* ADP=1, 上电4位地址模式 */norflash_write_sr(3, temp);     /* 写SR3 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_Enable4ByteAddr);    /* 使能4字节地址指令 */NORFLASH_CS(1);}}//printf("ID:%x\r\n", g_norflash_type);
}/*** @brief       等待空闲* @param       无* @retval      无*/
static void norflash_wait_busy(void)
{while ((norflash_read_sr(1) & 0x01) == 0x01);   /* 等待BUSY位清空 */
}/*** @brief       25QXX写使能*   @note      将S1寄存器的WEL置位* @param       无* @retval      无*/
void norflash_write_enable(void)
{NORFLASH_CS(0);spi1_read_write_byte(FLASH_WriteEnable);   /* 发送写使能 */NORFLASH_CS(1);
}/*** @brief       25QXX发送地址*   @note      根据芯片型号的不同, 发送24ibt / 32bit地址* @param       address : 要发送的地址* @retval      无*/
static void norflash_send_address(uint32_t address)
{if (g_norflash_type == W25Q256)                     /* 只有W25Q256支持4字节地址模式 */{spi1_read_write_byte((uint8_t)((address)>>24)); /* 发送 bit31 ~ bit24 地址 */} spi1_read_write_byte((uint8_t)((address)>>16));     /* 发送 bit23 ~ bit16 地址 */spi1_read_write_byte((uint8_t)((address)>>8));      /* 发送 bit15 ~ bit8  地址 */spi1_read_write_byte((uint8_t)address);             /* 发送 bit7  ~ bit0  地址 */
}/*** @brief       读取25QXX的状态寄存器,25QXX一共有3个状态寄存器*   @note      状态寄存器1:*              BIT7  6   5   4   3   2   1   0*              SPR   RV  TB BP2 BP1 BP0 WEL BUSY*              SPR:默认0,状态寄存器保护位,配合WP使用*              TB,BP2,BP1,BP0:FLASH区域写保护设置*              WEL:写使能锁定*              BUSY:忙标记位(1,忙;0,空闲)*              默认:0x00**              状态寄存器2:*              BIT7  6   5   4   3   2   1   0*              SUS   CMP LB3 LB2 LB1 (R) QE  SRP1**              状态寄存器3:*              BIT7      6    5    4   3   2   1   0*              HOLD/RST  DRV1 DRV0 (R) (R) WPS ADP ADS** @param       regno: 状态寄存器号,范:1~3* @retval      状态寄存器值*/
uint8_t norflash_read_sr(uint8_t regno)
{uint8_t byte = 0, command = 0;switch (regno){case 1:command = FLASH_ReadStatusReg1;  /* 读状态寄存器1指令 */break;case 2:command = FLASH_ReadStatusReg2;  /* 读状态寄存器2指令 */break;case 3:command = FLASH_ReadStatusReg3;  /* 读状态寄存器3指令 */break;default:command = FLASH_ReadStatusReg1;break;}NORFLASH_CS(0);spi1_read_write_byte(command);      /* 发送读寄存器命令 */byte = spi1_read_write_byte(0Xff);  /* 读取一个字节 */NORFLASH_CS(1);return byte;
}/*** @brief       写25QXX状态寄存器*   @note      寄存器说明见norflash_read_sr函数说明* @param       regno: 状态寄存器号,范:1~3* @param       sr   : 要写入状态寄存器的值* @retval      无*/
void norflash_write_sr(uint8_t regno, uint8_t sr)
{uint8_t command = 0;switch (regno){case 1:command = FLASH_WriteStatusReg1;  /* 写状态寄存器1指令 */break;case 2:command = FLASH_WriteStatusReg2;  /* 写状态寄存器2指令 */break;case 3:command = FLASH_WriteStatusReg3;  /* 写状态寄存器3指令 */break;default:command = FLASH_WriteStatusReg1;break;}NORFLASH_CS(0);spi1_read_write_byte(command);  /* 发送读寄存器命令 */spi1_read_write_byte(sr);       /* 写入一个字节 */NORFLASH_CS(1);
}/*** @brief       读取芯片ID* @param       无* @retval      FLASH芯片ID*   @note      芯片ID列表见: norflash.h, 芯片列表部分*/
uint16_t norflash_read_id(void)
{uint16_t deviceid;NORFLASH_CS(0);spi1_read_write_byte(FLASH_ManufactDeviceID);   /* 发送读 ID 命令 */spi1_read_write_byte(0);                        /* 写入一个字节 */spi1_read_write_byte(0);spi1_read_write_byte(0);deviceid = spi1_read_write_byte(0xFF) << 8;     /* 读取高8位字节 */deviceid |= spi1_read_write_byte(0xFF);         /* 读取低8位字节 */NORFLASH_CS(1);return deviceid;
}/*** @brief       读取SPI FLASH*   @note      在指定地址开始读取指定长度的数据* @param       pbuf    : 数据存储区* @param       addr    : 开始读取的地址(最大32bit)* @param       datalen : 要读取的字节数(最大65535)* @retval      无*/
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t i;NORFLASH_CS(0);spi1_read_write_byte(FLASH_ReadData);       /* 发送读取命令 */norflash_send_address(addr);                /* 发送地址 */for (i = 0; i < datalen; i++){pbuf[i] = spi1_read_write_byte(0XFF);   /* 循环读取 */}NORFLASH_CS(1);
}/*** @brief       SPI在一页(0~65535)内写入少于256个字节的数据*   @note      在指定地址开始写入最大256字节的数据* @param       pbuf    : 数据存储区* @param       addr    : 开始写入的地址(最大32bit)* @param       datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!* @retval      无*/
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t i;norflash_write_enable();                    /* 写使能 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_PageProgram);    /* 发送写页命令 */norflash_send_address(addr);                /* 发送地址 */for (i = 0; i < datalen; i++){spi1_read_write_byte(pbuf[i]);          /* 循环读取 */}NORFLASH_CS(1);norflash_wait_busy();       /* 等待写入结束 */
}/*** @brief       无检验写SPI FLASH*   @note      必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!*              具有自动换页功能*              在指定地址开始写入指定长度的数据,但是要确保地址不越界!** @param       pbuf    : 数据存储区* @param       addr    : 开始写入的地址(最大32bit)* @param       datalen : 要写入的字节数(最大65535)* @retval      无*/
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t pageremain;pageremain = 256 - addr % 256;  /* 单页剩余的字节数 */if (datalen <= pageremain)      /* 不大于256个字节 */{
//1,和2两个操作都是为了让写入256个字节和写入小于当前页剩余字节数的函数3为一个函数,所以1,2做了这样处理pageremain = datalen; //1}while (1){/* 当写入字节比页内剩余地址还少的时候, 一次性写完* 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理*///先写满首地址剩余字节,当写入字节小于剩余当前字节页字节时norflash_write_page(pbuf, addr, pageremain);   //3if (datalen == pageremain)      /* 写入结束了 */{break;}else                            /* datalen > pageremain */{pbuf += pageremain;         /* pbuf指针地址偏移,前面已经写了pageremain字节 */addr += pageremain;         /* 写地址偏移,前面已经写了pageremain字节 */datalen -= pageremain;      /* 写入总长度减去已经写入了的字节数 */if (datalen > 256)          /* 剩余数据还大于一页,可以一次写一页 */{pageremain = 256;       /* 一次可以写入256个字节 */}else                        /* 剩余数据小于一页,可以一次写完 */{//2pageremain = datalen;   /* 不够256个字节了 */  }}}
}/*** @brief       写SPI FLASH*   @note      在指定地址开始写入指定长度的数据 , 该函数带擦除操作!*              SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block*              擦除的最小单位为Sector.** @param       pbuf    : 数据存储区* @param       addr    : 开始写入的地址(最大32bit)* @param       datalen : 要写入的字节数(最大65535)* @retval      无*/
uint8_t g_norflash_buf[4096];   /* 扇区缓存 */void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint32_t secpos;uint16_t secoff;uint16_t secremain;uint16_t i;uint8_t *norflash_buf;norflash_buf = g_norflash_buf;secpos = addr / 4096;       /* 扇区地址 */secoff = addr % 4096;       /* 在扇区内的偏移 */secremain = 4096 - secoff;  /* 扇区剩余空间大小 *///printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */if (datalen <= secremain){secremain = datalen;    /* 不大于4096个字节 */}while (1){norflash_read(norflash_buf, secpos * 4096, 4096);   /* 读出整个扇区的内容 */for (i = 0; i < secremain; i++)     /* 校验数据 */{if (norflash_buf[secoff + i] != 0XFF){break;                      /* 需要擦除, 直接退出for循环 */}}//上面if执行break话,就是扇区剩余扇区不需要擦除,执行完后i==secremainif (i < secremain)                  /* 需要擦除 */{norflash_erase_sector(secpos);  /* 擦除这个扇区 */for (i = 0; i < secremain; i++) /* 复制 */{norflash_buf[i + secoff] = pbuf[i];}norflash_write_nocheck(norflash_buf, secpos * 4096, 4096);  /* 写入整个扇区 */}else    /* 写已经擦除了的,直接写入扇区剩余区间. */{norflash_write_nocheck(pbuf, addr, secremain);              /* 直接写扇区 */}if (datalen == secremain){break;  /* 写入结束了 */}else        /* 写入未结束 */{secpos++;               /* 扇区地址增1 */secoff = 0;             /* 偏移位置为0 */pbuf += secremain;      /* 指针偏移 */addr += secremain;      /* 写地址偏移 */datalen -= secremain;   /* 字节数递减 */if (datalen > 4096){secremain = 4096;   /* 下一个扇区还是写不完 */}else{secremain = datalen;/* 下一个扇区可以写完了 */}}}
}/*** @brief       擦除整个芯片*   @note      等待时间超长...* @param       无* @retval      无*/
void norflash_erase_chip(void)
{norflash_write_enable();    /* 写使能 */norflash_wait_busy();       /* 等待空闲 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_ChipErase);  /* 发送读寄存器命令 */ NORFLASH_CS(1);norflash_wait_busy();       /* 等待芯片擦除结束 */
}/*** @brief       擦除一个扇区*   @note      注意,这里是扇区地址,不是字节地址!!*              擦除一个扇区的最少时间:150ms* * @param       saddr : 扇区地址 根据实际容量设置* @retval      无*/
void norflash_erase_sector(uint32_t saddr)
{//printf("fe:%x\r\n", saddr);   /* 监视falsh擦除情况,测试用 */saddr *= 4096;norflash_write_enable();        /* 写使能 */norflash_wait_busy();           /* 等待空闲 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_SectorErase);    /* 发送写页命令 */norflash_send_address(saddr);   /* 发送地址 */NORFLASH_CS(1);norflash_wait_busy();           /* 等待扇区擦除完成 */
}

下面是norflash.h

/******************************************************************************************************* @file        norflash.h* @author      正点原子团队(ALIENTEK)* @version     V1.0* @date        2021-10-23* @brief       NOR FLASH(25QXX) 驱动代码* @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司***************************************************************************************************** @attention** 实验平台:正点原子 探索者 F407开发板* 在线视频:www.yuanzige.com* 技术论坛:www.openedv.com* 公司网址:www.alientek.com* 购买地址:openedv.taobao.com** 修改说明* V1.0 20211023* 第一次发布******************************************************************************************************/#ifndef __norflash_H
#define __norflash_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/
/* NORFLASH 片选 引脚 定义 */#define NORFLASH_CS_GPIO_PORT           GPIOB
#define NORFLASH_CS_GPIO_PIN            GPIO_PIN_14
#define NORFLASH_CS_GPIO_CLK_ENABLE()   do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 *//******************************************************************************************//* NORFLASH 片选信号 */
#define NORFLASH_CS(x)      do{ x ? \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_SET) : \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_RESET); \}while(0)/* FLASH芯片列表 */
#define W25Q80      0XEF13          /* W25Q80   芯片ID */
#define W25Q16      0XEF14          /* W25Q16   芯片ID */
#define W25Q32      0XEF15          /* W25Q32   芯片ID */
#define W25Q64      0XEF16          /* W25Q64   芯片ID */
#define W25Q128     0XEF17          /* W25Q128  芯片ID */
#define W25Q256     0XEF18          /* W25Q256  芯片ID */
#define BY25Q64     0X6816          /* BY25Q64  芯片ID */
#define BY25Q128    0X6817          /* BY25Q128 芯片ID */
#define NM25Q64     0X5216          /* NM25Q64  芯片ID */
#define NM25Q128    0X5217          /* NM25Q128 芯片ID *//* 指令表 */
#define FLASH_WriteEnable           0x06 
#define FLASH_WriteDisable          0x04 
#define FLASH_ReadStatusReg1        0x05 
#define FLASH_ReadStatusReg2        0x35 
#define FLASH_ReadStatusReg3        0x15 
#define FLASH_WriteStatusReg1       0x01 
#define FLASH_WriteStatusReg2       0x31 
#define FLASH_WriteStatusReg3       0x11 
#define FLASH_ReadData              0x03 
#define FLASH_FastReadData          0x0B 
#define FLASH_FastReadDual          0x3B 
#define FLASH_FastReadQuad          0xEB  
#define FLASH_PageProgram           0x02 
#define FLASH_PageProgramQuad       0x32 
#define FLASH_BlockErase            0xD8 
#define FLASH_SectorErase           0x20 
#define FLASH_ChipErase             0xC7 
#define FLASH_PowerDown             0xB9 
#define FLASH_ReleasePowerDown      0xAB 
#define FLASH_DeviceID              0xAB 
#define FLASH_ManufactDeviceID      0x90 
#define FLASH_JedecDeviceID         0x9F 
#define FLASH_Enable4ByteAddr       0xB7
#define FLASH_Exit4ByteAddr         0xE9
#define FLASH_SetReadParam          0xC0 
#define FLASH_EnterQPIMode          0x38
#define FLASH_ExitQPIMode           0xFFextern uint16_t norflash_TYPE;      /* 定义FLASH芯片型号 *//* 静态函数 */
static void norflash_wait_busy(void);               /* 等待空闲 */
static void norflash_send_address(uint32_t address);/* 发送地址 */
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen);    /* 写入page */
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写flash,不带擦除 *//* 普通函数 */
void norflash_init(void);                   /* 初始化25QXX */
uint16_t norflash_read_id(void);            /* 读取FLASH ID */
void norflash_write_enable(void);           /* 写使能 */
uint8_t norflash_read_sr(uint8_t regno);    /* 读取状态寄存器 */
void norflash_write_sr(uint8_t regno,uint8_t sr);   /* 写状态寄存器 */void norflash_erase_chip(void);             /* 整片擦除 */
void norflash_erase_sector(uint32_t saddr); /* 扇区擦除 */
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen);     /* 读取flash */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen);    /* 写入flash */#endif

页写入无校验就是写入地址全是0xFF的情况和写入地址带校验有的不是0xFF带擦除的两个函数非常有逻辑性,单独列出来

,后面跟上擦除整个扇区和擦除整个芯片的操作,因为norflash最小擦除单位是扇区,nandflash最小擦除单位是块,1个扇区4096byte,一个页256Byte,一个块65536字节

扇区擦除函数不同与教学的是传入扇区地址,在函数乘以了4096恢复了直接地址

/*** @brief       无检验写SPI FLASH*   @note      必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!*              具有自动换页功能*              在指定地址开始写入指定长度的数据,但是要确保地址不越界!** @param       pbuf    : 数据存储区* @param       addr    : 开始写入的地址(最大32bit)* @param       datalen : 要写入的字节数(最大65535)* @retval      无*/
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t pageremain;pageremain = 256 - addr % 256;  /* 单页剩余的字节数 */if (datalen <= pageremain)      /* 不大于256个字节 */{
//1,和2两个操作都是为了让写入256个字节和写入小于当前页剩余字节数的函数3为一个函数,所以1,2做了这样处理pageremain = datalen; //1}while (1){/* 当写入字节比页内剩余地址还少的时候, 一次性写完* 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理*///先写满首地址剩余字节,当写入字节小于剩余当前字节页字节时norflash_write_page(pbuf, addr, pageremain);   //3if (datalen == pageremain)      /* 写入结束了 */{break;}else                            /* datalen > pageremain */{pbuf += pageremain;         /* pbuf指针地址偏移,前面已经写了pageremain字节 */addr += pageremain;         /* 写地址偏移,前面已经写了pageremain字节 */datalen -= pageremain;      /* 写入总长度减去已经写入了的字节数 */if (datalen > 256)          /* 剩余数据还大于一页,可以一次写一页 */{pageremain = 256;       /* 一次可以写入256个字节 */}else                        /* 剩余数据小于一页,可以一次写完 */{//2pageremain = datalen;   /* 不够256个字节了 */  }}}
}/*** @brief       写SPI FLASH*   @note      在指定地址开始写入指定长度的数据 , 该函数带擦除操作!*              SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block*              擦除的最小单位为Sector.** @param       pbuf    : 数据存储区* @param       addr    : 开始写入的地址(最大32bit)* @param       datalen : 要写入的字节数(最大65535)* @retval      无*/
uint8_t g_norflash_buf[4096];   /* 扇区缓存 */void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint32_t secpos;uint16_t secoff;uint16_t secremain;uint16_t i;uint8_t *norflash_buf;norflash_buf = g_norflash_buf;secpos = addr / 4096;       /* 扇区地址 */secoff = addr % 4096;       /* 在扇区内的偏移 */secremain = 4096 - secoff;  /* 扇区剩余空间大小 *///printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */if (datalen <= secremain){secremain = datalen;    /* 不大于4096个字节 */}while (1){norflash_read(norflash_buf, secpos * 4096, 4096);   /* 读出整个扇区的内容 */for (i = 0; i < secremain; i++)     /* 校验数据 */{if (norflash_buf[secoff + i] != 0XFF){break;                      /* 需要擦除, 直接退出for循环 */}}//上面if执行break话,就是扇区剩余扇区不需要擦除,执行完后i==secremainif (i < secremain)                  /* 需要擦除 */{norflash_erase_sector(secpos);  /* 擦除这个扇区 */for (i = 0; i < secremain; i++) /* 复制 */{norflash_buf[i + secoff] = pbuf[i];}norflash_write_nocheck(norflash_buf, secpos * 4096, 4096);  /* 写入整个扇区 */}else    /* 写已经擦除了的,直接写入扇区剩余区间. */{norflash_write_nocheck(pbuf, addr, secremain);              /* 直接写扇区 */}if (datalen == secremain){break;  /* 写入结束了 */}else        /* 写入未结束 */{secpos++;               /* 扇区地址增1 */secoff = 0;             /* 偏移位置为0 */pbuf += secremain;      /* 指针偏移 */addr += secremain;      /* 写地址偏移 */datalen -= secremain;   /* 字节数递减 */if (datalen > 4096){secremain = 4096;   /* 下一个扇区还是写不完 */}else{secremain = datalen;/* 下一个扇区可以写完了 */}}}
}/*** @brief       擦除整个芯片*   @note      等待时间超长...* @param       无* @retval      无*/
void norflash_erase_chip(void)
{norflash_write_enable();    /* 写使能 */norflash_wait_busy();       /* 等待空闲 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_ChipErase);  /* 发送读寄存器命令 */ NORFLASH_CS(1);norflash_wait_busy();       /* 等待芯片擦除结束 */
}/*** @brief       擦除一个扇区*   @note      注意,这里是扇区地址,不是字节地址!!*              擦除一个扇区的最少时间:150ms* * @param       saddr : 扇区地址 根据实际容量设置* @retval      无*/
void norflash_erase_sector(uint32_t saddr)
{//printf("fe:%x\r\n", saddr);   /* 监视falsh擦除情况,测试用 */saddr *= 4096;norflash_write_enable();        /* 写使能 */norflash_wait_busy();           /* 等待空闲 */NORFLASH_CS(0);spi1_read_write_byte(FLASH_SectorErase);    /* 发送写页命令 */norflash_send_address(saddr);   /* 发送地址 */NORFLASH_CS(1);norflash_wait_busy();           /* 等待扇区擦除完成 */
}

SPI总结

时钟CPOL,CPHA

NSS就是片选的意思,代码中是软件片选,CS引脚给从设备低电平,即代表开始与从设备通信

SPI相关寄存器,h7有所不同,SPI_DR是由硬件操作

SPI相关HAL库驱动

NMq25q128简介,支持SPI工作模式0和工作模式3

简介

读,擦除,写

NORflash驱动步骤

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/56246.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Hugging Face中的BERT进行标题分类

使用Hugging Face中的BERT进行标题分类 前言相关介绍出处基本原理优点缺点 前提条件实验环境BERT进行标题分类准备数据集读取数据集划分数据集设置相关参数创建自己DataSet对象计算准确率定义预训练模型定义优化器训练模型保存模型测试模型 参考文献 前言 由于本人水平有限&…

动态规划-简单多状态dp问题——面试题17.16.按摩师

多状态问题的核心就是每个位置不止有一个状态&#xff0c;因此需要多个dp表表示不同状态对应位置的值&#xff0c;然后根据题目考虑特定情况写出状态转移方程即可 1.题目解析 题目来源&#xff1a;面试题17.16.按摩师——力扣 测试用例 2.算法原理 1.状态表示 这里与路径问…

【CSS in Depth 2 精译_047】7.2 CSS 响应式设计中的媒体查询原则(上):深入理解媒体查询的类型

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 【第七章 响应式设计】&#xff08;概述&#xff09; 7.1 移动端优先设计原则&#xff08;上篇&#xff09; 7.1.1 创建移动端菜单&#xff08;下篇&#xff09;7.1.2 给视口添加 meta 标签&#xf…

MATLAB - 机器人机械臂设计轨迹规划器

系列文章目录 前言 本示例介绍了一种设计抓取和轨迹规划器的方法,该规划器可用于垃圾箱拣选系统。 在机器人技术中,垃圾箱拣选包括使用机械手从垃圾箱中取出物品。智能垃圾箱拣选是这一过程的高级版本,具有更强的自主性。使用摄像系统感知部件,规划器生成与场景相适应的无碰…

NASA:ARCTAS 区域的二级 FIRSTLOOK 气溶胶产品子集。 它包含气溶胶光学深度和粒子类型,以及相关的大气数据

目录 简介 信息 代码 引用 网址推荐 知识星球 机器学习 MISR L2 FIRSTLOOK Aerosol Product subset for the ARCTAS region V001 简介 这是 ARCTAS 区域的二级 FIRSTLOOK 气溶胶产品子集。 它包含气溶胶光学深度和粒子类型&#xff0c;以及相关的大气数据&#xff0c;…

关于摩托车一键启动无钥匙进入、智能科技创新

摩托车一键启动无钥匙进入功能 一、工作原理 摩托车的一键启动无钥匙进入功能采用了世界最先进的RFID无线射频技术和最先进的车辆身份编码识别系统&#xff0c;率先应用小型化、小功率射频天线的开发方案&#xff0c;并成功融合了遥控系统和无钥匙系统&#xff0c;沿用了传统…

在 MTT GPU 上使用 llama.cpp 推理

大语言模型因其出色的自然语言理解和生成能力而迅速被广泛使用&#xff0c;llama.cpp 大幅降低了进行大语言模型推理的门槛&#xff0c;MTT GPU 同样也是 llama.cpp 支持的运行平台&#xff0c;能够充分利用硬件的性能来助力用户的大语言模型应用。 本文主要介绍了如何在摩尔线…

出处不详 取数游戏

目录 取数游戏题目描述背景输入输出数据范围 题解解法优化 打赏 取数游戏 题目描述 背景 两人将 n n n个正整数围成一个圆环&#xff0c;规则如下&#xff1a; 第一名玩家随意选取数字&#xff1b;第二名玩家从与第一名玩家相邻的两个数字中选择一个&#xff1b;而后依次在…

用Arduino单片机制作一个简单的音乐播放器

Arduino单片机上有多个数字IO针脚&#xff0c;可以输出数字信号&#xff0c;用于驱动发声器件&#xff0c;从而让它发出想要的声音。蜂鸣器是一种常见的发声器件&#xff0c;通电后可以发出声音。因此&#xff0c;单片机可以通过数字输出控制蜂鸣器发出指定的声音。另外&#x…

【尚硅谷】FreeRTOS学笔记(更新中更新时间2024.10.12)

在网上看到的一段很形象的描述&#xff0c;放在这里给大家娱乐一下。 裸机开发&#xff1a;n个人拉屎&#xff0c;先进去一个拉完&#xff0c;下一个再来。看门狗&#xff1a;如果有人拉完屎还占着&#xff0c;茅坑刷视频&#xff0c;把他拖出去中断系统&#xff1a;n个人拉屎&…

Python | Leetcode Python题解之第477题汉明距离总和

题目&#xff1a; 题解&#xff1a; class Solution:def totalHammingDistance(self, nums: List[int]) -> int:n len(nums)ans 0for i in range(30):c sum(((val >> i) & 1) for val in nums)ans c * (n - c)return ans

数通--3

一、动态路由 内部 路由器之间要互联互通&#xff0c;必须遵循相同的协议 企业内部用 IGP&#xff0c;企业之间用BGP RIP&#xff08;已淘汰&#xff0c;不考&#xff09; 距离就是长短&#xff0c;矢量就是方向&#xff0c;即路由的出接口 一台路由器 A 配好RIP&#xff0c;…

C++面试速通宝典——25

473. HTTP如何减少重定向请求 重定向请求&#xff1a; ‌‌‌‌  服务器上的一个资源可能由于迁移、维护等原因从url1移至url2后&#xff0c;而客户端不知情&#xff0c;他还是继续请求url1&#xff0c;这时服务器不能粗暴地返回错误&#xff0c;而是通过302响应码和Locati…

鸿蒙--商品列表

这里主要利用的是 List 组件 相关概念 Scroll:可滚动的容器组件,当子组件的布局尺寸超过父组件的视口时,内容可以滚动。List:列表包

Appium Device Farm安装教程

环境要求&#xff1a;Appium version ≥ 2.4.X 安装appium npm install -g appium2.11.3 如果安装提示如下问题 npm error code EEXIST npm error syscall rename npm error path /Users/wan/.npm/_cacache/tmp/d5787519 npm error dest /Users/wan/.npm/_cacache/content-…

鸿蒙--WaterFlow 实现商城首页

目录结构 ├──entry/src/main/ets // 代码区 │ ├──common │ │ ├──constants │ │ │ └──CommonConstants.ets // 公共常量类 │ │ └──utils │ │ └──Logger.ets // 日志打印类 │ ├──entryability │ │ └──EntryAbility.ets // 程序入口…

【2024最新】基于springboot+vue的体质数据分析及可视化lw+ppt

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

MPA-SVM多变量回归预测|海洋捕食者优化算法-支持向量机|Matalb

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译&am…

数据结构-5.6.二叉树的先,中,后序遍历

一.遍历&#xff1a; 二.二叉树的遍历&#xff1a;利用了递归操作 1.简介&#xff1a; 二叉树的先序遍历&#xff0c;中序遍历&#xff0c;后序遍历都是以根结点遍历顺序为准的&#xff0c;如先序遍历就先遍历根结点 2.实例&#xff1a; 例一&#xff1a; 例二&#xff1a; …

Rust 与生成式 AI:从语言选择到开发工具的演进

在现代软件开发领域&#xff0c;Rust 语言正在逐步崭露头角&#xff0c;尤其是在高性能和可靠性要求较高的应用场景。与此同时&#xff0c;生成式 AI 的崛起正在重新塑造开发者的工作方式&#xff0c;从代码生成到智能调试&#xff0c;生成式 AI 的应用正成为提升开发效率和质量…