IAP
基于stm32f407的IAP升级(野火stm32f407霸天虎v2)
FLASH 内存划分
STM32F407flash构成
扇区 0 和扇区 1 保存bootloader程序,扇区2保存版本信息临时文件等,扇区3,4保存APP1
扇区号 | 数据 |
---|---|
扇区0-扇区2(共48K) | 存放bootloader程序用于转跳 |
扇区3-扇区4(共80K) | 存放APP程序 |
ELSE | 待定 |
W25Q128 内存划分 :
w25q128 进行数据储存每页256字节,每次最大储存256字节
地址 | 数据 |
---|---|
000000-000080 | 文件名 |
000081-0000FF | 文件大小 |
000100-XXXXXX | DATA |
转跳程序
作用:
转跳函数用于接收书数据并转存完成后进行APP跳转
函数介绍
检查栈顶地址
数值根据不同容量的SRAM设定 STM32F407ZGT6为192kb
0x20000000 0010 0000 0000 0000 0000 0000 0000 0000
0x2FFC0000 0010 1111 1111 1100 0000 0000 0000 0000
192kb 0000 0000 0000 0011 0000 0000 0000 0000
192kb栈顶位置 0010 0000 0000 00xx xxxx xxxx xxxx xxxx
所以如果栈顶在192kb之内 数据必定为 0010 0000 0000 00xx xxxx xxxx xxxx xxxx,
与上 0x2FFC0000 0010 1111 1111 1100 0000 0000 0000 0000
的结果必定为0x20000000 0010 0000 0000 0000 0000 0000 0000 0000
jump2app
转跳至appxaddr+4地址的app
appxaddr为app的起始地址
appxaddr+4为中断向量表位置
初始化函数
在转跳前,需要将各个外设 中断初始化 防止转跳的APP出现故障
typedef void (*iapfun)(void); // 定义函数指针类型,用于跳转到应用
typedef volatile uint32_t vu32;
#define appxaddr 0x0800C000 // 转跳地址
void iap_load_app(uint32_t addr)
{
//if(((*(vu32*)appxaddr)&0x2FFC0000)==0x20000000) //棿查栈顶是否合合法
// {
// iapfun jump2app=(iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序弿始地(复位地址)
// MSR_MSP(*(vu32*)appxaddr);
// jump2app(); //跳转到APP.
// }
int i = 0;
if(((*(vu32*)appxaddr)&0x2FFC0000)==0x20000000) //检查栈顶地址是否合法.
{
/* 首地址是MSP,地址+4是复位中断服务程序地址 */
iapfun jump2app=(iapfun)*(vu32*)(appxaddr+4);
/* 关闭全局中断 */
__set_PRIMASK(1);
/* 关闭滴答定时器,复位到默认值 */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/* 设置所有时钟到默认状态 */
// RCC_DeInit();
HAL_RCC_DeInit();
/* 关闭所有中断,清除所有中断挂起标志 */
for (i = 0; i < 8; i++)
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
/* 使能全局中断 */
__set_PRIMASK(0);
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 启用 GPIO 时钟(假设所有 GPIO 端口)
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
// 配置 GPIOA
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; // 设置为模拟输入模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 清除上拉/下拉电阻
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 速度设置无关紧要
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置 GPIOB
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 配置 GPIOC
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
// 配置 GPIOD
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
// 配置 GPIOE
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
// 配置 GPIOF
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
// 配置 GPIOG
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
// 配置 GPIOH
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
/* 在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针 */
__set_CONTROL(0);
MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app(); //跳转到APP.
/* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
while (1)
{
}
}
}
文件传输协议
Ymodem(野火串口助手传输Bin文件)
外设
RCC
TIM2
SPI1
DAM
USART1 接收APP数据
USART2 打印信息调试
GPIO 控制灯珠
主函数
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "spi.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "W25QXX.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef void (*iapfun)(void); // 定义函数指针类型,用于跳转到应用
typedef volatile uint32_t vu32;
void iap_load_app(uint32_t addr); // 转跳函数声明
void Flash_Erase(uint32_t File_size); // 擦除函数声明
uint16_t crc16_ccitt(uint8_t *data, uint16_t length); // CRC校验函数
#define appxaddr 0x0800C000 // 转跳地址
#define MAX_SIZE 1029 // 单包朿大数
uint8_t Bootloader_buf[MAX_SIZE]; // 接收缓存匿
uint8_t W25Q12_to_Flash_buf[1024]; // 转存缓存匿
uint8_t B_flag = 0; // bootloader犿 0为状态机初始,三秒后定时器修改1,转跳APP状濿 2 正在接收
uint8_t U1_DMA_flag = 0; // 串口DMA完成接收标志使
uint8_t Ymodem_C = 0x43;
uint8_t Ymodem_ACK = 0x06;
uint8_t Ymodem_NAK = 0x15;
uint8_t EOT_state = 0;
uint16_t TIM2_Tick = 0;
uint8_t PN; // 包号;
uint8_t XPN; // 包号反码;
uint16_t Ymodem_CRC; // 储存校验的crc
uint32_t File_size; // 文件大小
uint16_t File_num; // 分包数量
uint32_t File_END_length = 0; // 后一包数据的大小
uint8_t Flie_COUNT = 0; // 已经接收数量
uint32_t W25Q128_W_addr = 0x000100; // 写W25Q128起始地址
uint32_t W25Q128_R_addr = 0x000100; // 读W25Q128起始地址
uint32_t FLASH_W_addr = 0x0800C000; // 写flash地址
uint8_t File_name_length = 0; //文件名长庿
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_TIM2_Init();
MX_SPI1_Init();
W25QXX_Init(); // SPI驱动
HAL_UART_Receive_DMA(&huart1, Bootloader_buf,133); //打开DMA接收133字节
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_SR_UIF);
HAL_TIM_Base_Start_IT(&htim2); //启TIM2中断 3s后触发中斿
while (1)
{
if(B_flag == 2){ // usart1接收到数捿,启动串口下载数据程
if(U1_DMA_flag == 1){ // DMA接收完成,弿始数据帧处理函数
U1_DMA_flag = 0;
printf("getrn");
PN = Bootloader_buf[1];
XPN = Bootloader_buf[2];
if(PN == (XPN ^ 0xFF))
{
printf("PN XPN YESrn"); // 包号校验成功
}else{
printf("PN != XPN ERRORrn"); // 包号校验失败
break;
}
switch(Bootloader_buf[0]){
case 0x01: // SHO 128字节数据
Ymodem_CRC = crc16_ccitt(&Bootloader_buf[3],128);
if(Bootloader_buf[131] == (uint8_t)(Ymodem_CRC>>8)
&& Bootloader_buf[132] == (uint8_t)(Ymodem_CRC& 0x00FF))
{
printf("CRC128 YESrn"); // CRC校验
}else{
printf("CRC128 ERRORrn");
}
if(PN == 0x00 && Bootloader_buf[3] != 0x00) // 如果是起始帧
{
uint8_t File_get0 = 0; // 第一0x00位置
uint8_t File_get1 = 0; // 第二0x00位置
for(int i= 3;i<=132;i++) // 遍历数组
{
if(Bootloader_buf[i] == 0x00)
{
if(File_get0 == 0) // 遍历到第一个0x00
{
File_get0 = i;
File_name_length = i-3;
HAL_UART_Transmit(&huart2,&Bootloader_buf[3],
File_name_length,0xffff); // 文件吿
printf("rn");
W25QXX_Write(&Bootloader_buf[3],0x000000,File_name_length);
printf("NAME WRITErn");
}else if(File_get1 == 0) // 遍历到第二个0x00
{
File_get1 = i;
char size_str[20];
W25QXX_Write(&Bootloader_buf[File_get0+1],
0x0000F0,File_get1-File_get0-1);// 冿 W25Q128文件大小
memcpy(size_str, &Bootloader_buf[File_get0+1],
File_get1-File_get0-1); //文件大小
size_str[File_get1-File_get0] = '�';
File_size = strtoul(size_str,NULL,10); // 传输文件大小
File_num = File_size / 1024;
if((File_size % 1024)>0)
{
File_num++;
File_END_length = File_size % 1024;
}
printf("data size: %d rndata num : %drnFile_END_length: %drn",File_size,File_num,File_END_length);
}else{
break; // 两个0x00已经找到,跳出for循环
}
}
}
printf("PN = 00 YES START------>rn"); // 接收成功
HAL_UART_Transmit(&huart1,&Ymodem_ACK,1,0xffff); // 发鿁确认收到指仿
HAL_Delay(10);
HAL_UART_Transmit(&huart1,&Ymodem_C,1,0xffff); // 请求下一帧数捿
}else if(PN == 0x00 && Bootloader_buf[3] == 0x00){ // 结束帧.数据 00 填 充,将w25q128数据转存至芯 片内置flash
HAL_UART_Transmit(&huart1,&Ymodem_ACK,1,0xffff); // 回应结束
printf("PN = 00 YES END------>rn");
Flash_Erase(File_size); // 擦除对应flash扇区
HAL_FLASH_Unlock();
for(int i= 1;i<=File_num;i++) // 轮询,每次读取1k数据并写入flash
{
if(i !=File_num)
{
W25QXX_Read(W25Q12_to_Flash_buf,W25Q128_R_addr,1024);//读取app1数据
W25Q128_R_addr+=1024;
for(int z = 0;z<1024;z++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE,
FLASH_W_addr,W25Q12_to_Flash_buf[z]);
FLASH_W_addr++;
}
printf("W25Q128 to Flash id:%d OKrn",i);
}else
{
W25QXX_Read(W25Q12_to_Flash_buf,W25Q128_R_addr,File_END_length);//读取 app1数据
W25Q128_R_addr+=File_END_length;
for(int z = 0;z<File_END_length;z++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE,
FLASH_W_addr,W25Q12_to_Flash_buf[z]);
FLASH_W_addr++;
}
printf("W25Q128 to Flash END id:%d OKrn",i);
}
}
HAL_FLASH_Lock();
iap_load_app(appxaddr); // 传输完成,转跳APP
}else{ // 朿后一帧数捿
printf("THE LAST 128 ------>rn");
W25QXX_Write(&Bootloader_buf[3],W25Q128_W_addr,File_END_length); // 将1024个数据写 入W25Q128
W25Q128_W_addr += File_END_length; // 将写入地坿后移势
HAL_UART_Transmit(&huart1,&Ymodem_ACK,1,0xffff); // 回应结束
}
break;
case 0x02: // STX 1024字节数据
Flie_COUNT++; // 计数
Ymodem_CRC = crc16_ccitt(&Bootloader_buf[3],1024);
if(Bootloader_buf[1027] == (uint8_t)(Ymodem_CRC>>8)
&& Bootloader_buf[1028] == (uint8_t)(Ymodem_CRC& 0x00FF))
{
printf("CRC1024 YESrn"); // CRC校验
uint8_t nametmp[File_name_length];
W25QXX_Read(nametmp,0x000000,File_name_length);
printf("END FILE NAME:%srn",nametmp);
}else{
printf("CRC:%x Bootloader_buf[1027]:%x Bootloader_buf[1028]:%xrn",
Ymodem_CRC,Bootloader_buf[1027],Bootloader_buf[1028]);
printf("CRC1024 ERRORrn");
}
// 计数
if(Flie_COUNT == File_num) // 计数值等于濻包数后丿匿
{
printf("Flie_COUNT:%drn",Flie_COUNT);
printf("THE LAST 1024 ------>rn");
W25QXX_Write(&Bootloader_buf[3],W25Q128_W_addr,File_END_length)// 将最后数据写入 W25Q128
W25Q128_W_addr += File_END_length; // 写入地址后移朿后包的长庿
HAL_UART_Transmit(&huart1,&Ymodem_ACK,1,0xffff); // 回应结束
}else{ // 正常数据
printf("Flie_COUNT:%drn",Flie_COUNT);
W25QXX_Write(&Bootloader_buf[3],W25Q128_W_addr,1024); // 尿1024个数据写入W25Q128
W25Q128_W_addr += 1024; // 写入地址后移1024
HAL_UART_Transmit(&huart1,&Ymodem_ACK,1,0xffff); // 回应确定,弿始接收下丿匿
}
break;
case 0x04: // EOT 结束数据
Flie_COUNT++; // 计数
if(EOT_state == 0) // 第一次接收到EOT
{
EOT_state = 1;
printf("EOT 1rn");
HAL_UART_Transmit(&huart1,&Ymodem_NAK,1,0xffff); // 回应重新传鿿
}else if(EOT_state == 1){
printf("EOT 2rn");
HAL_UART_Transmit(&huart1,&Ymodem_ACK,1,0xffff); // 确定结束
HAL_UART_Transmit(&huart1,&Ymodem_C,1,0xffff); // 请求传鿁空匿
}
break;
}
if((Flie_COUNT+1) < File_num || ( (Flie_COUNT+1) == File_num && File_END_length > 128))
{
printf("DMA OPEN 1029rn");
HAL_UART_Receive_DMA(&huart1, Bootloader_buf,1029);
}else if( ((Flie_COUNT+1) == File_num && File_END_length <= 128 )
|| (Flie_COUNT+1) == (File_num + 3)){
HAL_UART_Receive_DMA(&huart1, Bootloader_buf,133);
printf("DMA OPEN 133rn");
}else{
printf("DMA OPEN 1rn");
HAL_UART_Receive_DMA(&huart1, Bootloader_buf,1);
}
}
}
if(B_flag == 1)
{
iap_load_app(appxaddr);
}
}
__asm void MSR_MSP(uint32_t addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1)
{
B_flag =2; //usart1收到数据时转换状
U1_DMA_flag = 1; //DMA接收完成标志使
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) // TIM2定时,1s触发
{
if(htim->Instance == htim2.Instance)
{
TIM2_Tick ++;
if(TIM2_Tick == 9){ // 第三秒时向usart1发ymodem确认接收指令
HAL_UART_Transmit(&huart1,&Ymodem_C,1,0xffff);
}
if(TIM2_Tick >= 10){ // 4秒后判断是否有数据接政
if(B_flag == 0){
B_flag = 1;
}else{
HAL_TIM_Base_Stop_IT(&htim2);
}
}
}
}
/*
* @Brief 转跳APP1
* @param param1: APP1起始地址
* @param param2:
* @Note
* @Retval
*/
void iap_load_app(uint32_t addr)
{
//if(((*(vu32*)appxaddr)&0x2FFC0000)==0x20000000) //棿查栈顶是否合合法
// {
// iapfun jump2app=(iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序弿始地(复位地址)
// MSR_MSP(*(vu32*)appxaddr);
// jump2app(); //跳转到APP.
// }
int i = 0;
if(((*(vu32*)appxaddr)&0x2FFC0000)==0x20000000) //检查栈顶地址是否合法.
{
/* 首地址是MSP,地址+4是复位中断服务程序地址 */
iapfun jump2app=(iapfun)*(vu32*)(appxaddr+4);
/* 关闭全局中断 */
__set_PRIMASK(1);
/* 关闭滴答定时器,复位到默认值 */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/* 设置所有时钟到默认状态 */
// RCC_DeInit();
HAL_RCC_DeInit();
/* 关闭所有中断,清除所有中断挂起标志 */
for (i = 0; i < 8; i++)
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
/* 使能全局中断 */
__set_PRIMASK(0);
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 启用 GPIO 时钟(假设所有 GPIO 端口)
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
// 配置 GPIOA
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; // 设置为模拟输入模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 清除上拉/下拉电阻
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 速度设置无关紧要
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置 GPIOB
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 配置 GPIOC
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
// 配置 GPIOD
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
// 配置 GPIOE
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
// 配置 GPIOF
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
// 配置 GPIOG
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
// 配置 GPIOH
GPIO_InitStruct.Pin = GPIO_PIN_All; // 配置所有引脚
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
/* 在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针 */
__set_CONTROL(0);
MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app(); //跳转到APP.
/* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
while (1)
{
}
}
}
/*
* @Brief 重定向printf底层
* @param param1:
* @param param2:
* @Note
* @Retval
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
return (ch);
}
/*
* @Brief CRC_16校验
* @param param1: uint8_t *data 数组起始地址
* @param param2: uint16_t length 计算的数组长庿
* @Note
* @Retval 16位crc
*/
uint16_t crc16_ccitt(uint8_t *data, uint16_t length) {
uint16_t crc = 0x0000; // 初始数据
uint16_t polynomial = 0x1021;
for (uint16_t i = 0; i < length; i++) {
crc ^= (uint16_t)(data[i] << 8);
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ polynomial;
} else {
crc = crc << 1;
}
}
}
return crc;
}
/*
* @Brief 根据bin文件大小,选择擦除的扇匿,朿大擦陿80K(扇区3 扇区4)
* @param param1:
* @param param2:
* @Note
* @Retval
*/
void Flash_Erase(uint32_t File_size)
{
HAL_FLASH_Unlock();
HAL_StatusTypeDef status;
FLASH_EraseInitTypeDef erase_init; // 扇区擦除结构使
uint32_t sector_error;
erase_init.TypeErase = FLASH_TYPEERASE_SECTORS; //只擦除扇匿
erase_init.Banks = FLASH_BANK_1; // f407只有FLASH_BANK_1
erase_init.Sector = FLASH_SECTOR_3; // 擦除扇区叿
erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 擦除电压
if(File_size <= (1024*16)) //如果文件大小小于16k,只擦陿0x0800C000起始皿16K扇区
{
erase_init.NbSectors = 1; //擦除扇区数量
status = HAL_FLASHEx_Erase(&erase_init, §or_error);
printf("Flash_Erase :FLASH_SECTOR_3 rn");
}else{ //擦除0x0800C000起始皿16K扇区和后续的0x08100000起始皿64k扇区
erase_init.NbSectors = 2; //擦除扇区数量
status = HAL_FLASHEx_Erase(&erase_init, §or_error);
printf("Flash_Erase :FLASH_SECTOR_3 FLASH_SECTOR_4rn");
}
HAL_FLASH_Lock();
if (status != HAL_OK)
{
// 处理错误
printf("Flash_Erase ERRORrn");
}
}
W25Qxx通信部分
头文件
#ifndef __W25QXX_H
#define __W25QXX_H
/*
W25QXX ID信息
读取命令(0x90)
W25Q80的芯片ID为:0XEF13
W25Q16 的芯片ID为:0XEF14
W25Q32 的芯片ID为:0XEF15
W25Q64 的芯片ID为:0XEF16
W25Q128的芯片ID为:0XEF17
W25Q256的芯片ID为:0XEF18
W25Q512的芯片ID为:0XEF19
*/
#include <main.h>
//可以根据个人需求配置对应的硬件SPIx
#define W25QXX_SPI_Handle (&hspi1) //使用SPI2
//W25X系列/Q系列芯片列表
//W25Q80 ID 0XEF13
//W25Q16 ID 0XEF14
//W25Q32 ID 0XEF15
//W25Q64 ID 0XEF16
//W25Q128 ID 0XEF17
#define W25Q80 0XEF13
#define W25Q16 0XEF14
#define W25Q32 0XEF15
#define W25Q64 0XEF16
#define W25Q128 0XEF17
#define W25QXX_CS_L() HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_RESET)
#define W25QXX_CS_H() HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_SET)
extern uint16_t W25QXX_TYPE;
extern uint32_t W25QXX_SIZE;
extern uint8_t W25QXX_UID[8];
//
//指令表
#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
int W25QXX_Init(void);
void W25QXX_ReadUniqueID(uint8_t UID[8]);
uint16_t W25QXX_ReadID(void); //读取FLASH ID
uint8_t W25QXX_ReadSR(void); //读取状态寄存器
void W25QXX_Write_SR(uint8_t sr); //写状态寄存器
void W25QXX_Write_Enable(void); //写使能
void W25QXX_Write_Disable(void); //写保护
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead); //读取flash
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//写入flash
void W25QXX_Erase_Chip(void); //整片擦除
void W25QXX_Erase_Sector(uint32_t Dst_Addr); //扇区擦除
void W25QXX_Wait_Busy(void); //等待空闲
void W25QXX_PowerDown(void); //进入掉电模式
void W25QXX_WAKEUP(void); //唤醒
uint32_t W25QXX_ReadCapacity(void);
#endif
主函数
/**
* @file w25Qxx.c
*
* @brief Create by AnKun on 2020/6/18
*
*/
#include "W25QXX.h"
#include "spi.h"
uint16_t W25QXX_TYPE = 0;
uint32_t W25QXX_SIZE = 0;
uint8_t W25QXX_UID[8];
static void delay_us(uint32_t us)
{
uint32_t delay = (HAL_RCC_GetHCLKFreq() / 4000000 * us);
while(delay--) {
;
}
}
//SPI读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
static uint8_t W25QXX_SPI_ReadWriteByte(uint8_t TxData)
{
uint8_t RxData = 0X00;
// if(HAL_SPI_TransmitReceive(W25QXX_SPI_Handle, &TxData, &RxData, 1, 10) != HAL_OK)//普通方式
if(HAL_SPI_TransmitReceive_DMA(W25QXX_SPI_Handle, &TxData, &RxData, 1) != HAL_OK) { //DMA接收和发送
RxData = 0XFF;
}
return RxData;
}
//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q128
//容量为16M字节,共有128个Block,4096个Sector
//初始化SPI FLASH的IO口
int W25QXX_Init(void)
{
MX_SPI1_Init();//SPI2初始化
W25QXX_CS_L(); /* 拉低选中 */
W25QXX_SPI_ReadWriteByte(0XFF);
W25QXX_CS_H(); /* 拉高取消 */
W25QXX_TYPE = W25QXX_ReadID(); // 读取FLASH ID.
W25QXX_SIZE = W25QXX_ReadCapacity(); // 读取容量
W25QXX_ReadUniqueID(W25QXX_UID); // 读取唯一ID
if((W25QXX_TYPE & 0XEF00) != 0XEF00) {
return -1;
}
return 0;
}
//读取W25QXX的状态寄存器
//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
uint8_t W25QXX_ReadSR(void)
{
uint8_t byte = 0;
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_ReadStatusReg); //发送读取状态寄存器命令
byte = W25QXX_SPI_ReadWriteByte(0Xff); //读取一个字节
W25QXX_CS_H(); //取消片选
return byte;
}
//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(uint8_t sr)
{
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_WriteStatusReg); //发送写取状态寄存器命令
W25QXX_SPI_ReadWriteByte(sr); //写入一个字节
W25QXX_CS_H(); //取消片选
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_WriteEnable); //发送写使能
W25QXX_CS_H(); //取消片选
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_WriteDisable); //发送写禁止指令
W25QXX_CS_H(); //取消片选
}
//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
uint16_t W25QXX_ReadID(void)
{
uint16_t Temp = 0;
W25QXX_CS_L();
W25QXX_SPI_ReadWriteByte(0x90); //发送读取ID命令
W25QXX_SPI_ReadWriteByte(0x00);
W25QXX_SPI_ReadWriteByte(0x00);
W25QXX_SPI_ReadWriteByte(0x00);
Temp |= W25QXX_SPI_ReadWriteByte(0xFF) << 8;
Temp |= W25QXX_SPI_ReadWriteByte(0xFF);
W25QXX_CS_H();
return Temp;
}
uint32_t W25QXX_ReadCapacity(void)
{
int i = 0;
uint8_t arr[4] = {0, 0, 0, 0};
W25QXX_CS_L();
W25QXX_SPI_ReadWriteByte(0x5A);
W25QXX_SPI_ReadWriteByte(0x00);
W25QXX_SPI_ReadWriteByte(0x00);
W25QXX_SPI_ReadWriteByte(0x84);
W25QXX_SPI_ReadWriteByte(0x00);
for(i = 0; i < sizeof(arr); i++) {
arr[i] = W25QXX_SPI_ReadWriteByte(0xFF);
}
W25QXX_CS_H();
return ((((*(uint32_t *)arr)) + 1) >> 3);
}
void W25QXX_ReadUniqueID(uint8_t UID[8])
{
int i = 0;
W25QXX_CS_L();
W25QXX_SPI_ReadWriteByte(0x4B);
W25QXX_SPI_ReadWriteByte(0x00);
W25QXX_SPI_ReadWriteByte(0x00);
W25QXX_SPI_ReadWriteByte(0x00);
W25QXX_SPI_ReadWriteByte(0x00);
for(i = 0; i < 8; i++) {
UID[i] = W25QXX_SPI_ReadWriteByte(0xFF);
}
W25QXX_CS_H();
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
uint16_t i;
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_ReadData); //发送读取命令
W25QXX_SPI_ReadWriteByte((uint8_t)((ReadAddr) >> 16)); //发送24bit地址
W25QXX_SPI_ReadWriteByte((uint8_t)((ReadAddr) >> 8));
W25QXX_SPI_ReadWriteByte((uint8_t)ReadAddr);
for(i = 0; i < NumByteToRead; i++) {
pBuffer[i] = W25QXX_SPI_ReadWriteByte(0XFF); //循环读数
}
W25QXX_CS_H();
}
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint16_t i;
W25QXX_Write_Enable(); //SET WEL
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_PageProgram); //发送写页命令
W25QXX_SPI_ReadWriteByte((uint8_t)((WriteAddr) >> 16)); //发送24bit地址
W25QXX_SPI_ReadWriteByte((uint8_t)((WriteAddr) >> 8));
W25QXX_SPI_ReadWriteByte((uint8_t)WriteAddr);
for(i = 0; i < NumByteToWrite; i++)
W25QXX_SPI_ReadWriteByte(pBuffer[i]); //循环写数
W25QXX_CS_H(); //取消片选
W25QXX_Wait_Busy(); //等待写入结束
}
//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint16_t pageremain;
pageremain = 256 - WriteAddr % 256; //单页剩余的字节数
if(NumByteToWrite <= pageremain)
pageremain = NumByteToWrite; //不大于256个字节
while(1) {
W25QXX_Write_Page(pBuffer, WriteAddr, pageremain);
if(NumByteToWrite == pageremain)
break; //写入结束了
else { //NumByteToWrite>pageremain
pBuffer += pageremain;
WriteAddr += pageremain;
NumByteToWrite -= pageremain; //减去已经写入了的字节数
if(NumByteToWrite > 256)
pageremain = 256; //一次可以写入256个字节
else
pageremain = NumByteToWrite; //不够256个字节了
}
};
}
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
uint8_t W25QXX_BUFFER[4096];
void W25QXX_Write(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t *W25QXX_BUF;
W25QXX_BUF = W25QXX_BUFFER;
secpos = WriteAddr / 4096; //扇区地址
secoff = WriteAddr % 4096; //在扇区内的偏移
secremain = 4096 - secoff; //扇区剩余空间大小
if(NumByteToWrite <= secremain)
secremain = NumByteToWrite; //不大于4096个字节
while(1) {
W25QXX_Read(W25QXX_BUF, secpos * 4096, 4096); //读出整个扇区的内容
for(i = 0; i < secremain; i++) { //校验数据
if(W25QXX_BUF[secoff + i] != 0XFF)
break; //需要擦除
}
if(i < secremain) { //需要擦除
W25QXX_Erase_Sector(secpos); //擦除这个扇区
for(i = 0; i < secremain; i++) { //复制
W25QXX_BUF[i + secoff] = pBuffer[i];
}
W25QXX_Write_NoCheck(W25QXX_BUF, secpos * 4096, 4096); //写入整个扇区
} else
W25QXX_Write_NoCheck(pBuffer, WriteAddr, secremain); //写已经擦除了的,直接写入扇区剩余区间.
if(NumByteToWrite == secremain)
break; //写入结束了
else { //写入未结束
secpos++; //扇区地址增1
secoff = 0; //偏移位置为0
pBuffer += secremain; //指针偏移
WriteAddr += secremain; //写地址偏移
NumByteToWrite -= secremain; //字节数递减
if(NumByteToWrite > 4096)
secremain = 4096; //下一个扇区还是写不完w
else
secremain = NumByteToWrite; //下一个扇区可以写完了
}
};
}
//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{
W25QXX_Write_Enable(); //SET WEL
W25QXX_Wait_Busy();
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_ChipErase); //发送片擦除命令
W25QXX_CS_H(); //取消片选
W25QXX_Wait_Busy(); //等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个山区的最少时间:150ms
void W25QXX_Erase_Sector(uint32_t Dst_Addr)
{
//监视falsh擦除情况,测试用
Dst_Addr *= 4096;
W25QXX_Write_Enable(); //SET WEL
W25QXX_Wait_Busy();
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_SectorErase); //发送扇区擦除指令
W25QXX_SPI_ReadWriteByte((uint8_t)((Dst_Addr) >> 16)); //发送24bit地址
W25QXX_SPI_ReadWriteByte((uint8_t)((Dst_Addr) >> 8));
W25QXX_SPI_ReadWriteByte((uint8_t)Dst_Addr);
W25QXX_CS_H(); //取消片选
W25QXX_Wait_Busy(); //等待擦除完成
}
//等待空闲
void W25QXX_Wait_Busy(void)
{
while((W25QXX_ReadSR() & 0x01) == 0x01); // 等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_PowerDown); //发送掉电命令
W25QXX_CS_H(); //取消片选
delay_us(3); //等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{
W25QXX_CS_L(); //使能器件
W25QXX_SPI_ReadWriteByte(W25X_ReleasePowerDown); // send W25X_PowerDown command 0xAB
W25QXX_CS_H(); //取消片选
delay_us(3); //等待TRES1
}
移植性
未优化
手动移植主要修改各个文件储存区地址以及flah擦除区块设置