feat: 完善Mesh继电器OTA升级闭环
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
|
||||
/** 初始化 Bootloader 所需的最小硬件资源。 */
|
||||
void Bootloader_Init(void);
|
||||
/** 刷新独立看门狗,供W25Q64和Flash等待循环调用。 */
|
||||
void Bootloader_WatchdogRefresh(void);
|
||||
/** 判断应用程序区域是否包含可执行程序。 */
|
||||
uint8_t Bootloader_IsApplicationValid(void);
|
||||
/** 跳转到应用程序入口地址,函数成功后不返回。 */
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
#define APPLICATION_MAX_SIZE_BYTES \
|
||||
(APPLICATION_END_ADDRESS - APPLICATION_START_ADDRESS + 1u)
|
||||
|
||||
/** W25Q64 中保存出厂固件镜像的固定地址,容量64KiB。 */
|
||||
/** W25Q80BV中保存出厂固件镜像的固定地址,预留8KiB。 */
|
||||
#define FACTORY_IMAGE_ADDRESS 0x000000ul
|
||||
/** W25Q64 中保存出厂固件元数据的固定地址,独占4KiB扇区。 */
|
||||
#define FACTORY_METADATA_ADDRESS 0x010000ul
|
||||
/** W25Q64 中保存OTA升级固件的固定地址,容量64KiB。 */
|
||||
#define OTA_IMAGE_ADDRESS 0x020000ul
|
||||
/** W25Q64 中保存OTA元数据的固定地址,独占4KiB扇区。 */
|
||||
#define OTA_METADATA_ADDRESS 0x030000ul
|
||||
/** W25Q80BV中保存出厂固件元数据的固定地址,独占4KiB扇区。 */
|
||||
#define FACTORY_METADATA_ADDRESS 0x002000ul
|
||||
/** W25Q80BV中保存OTA升级固件的固定地址,预留8KiB。 */
|
||||
#define OTA_IMAGE_ADDRESS 0x003000ul
|
||||
/** W25Q80BV中保存OTA元数据的固定地址,独占4KiB扇区。 */
|
||||
#define OTA_METADATA_ADDRESS 0x005000ul
|
||||
/** 升级元数据魔数,用于避免误把普通数据当成升级请求。 */
|
||||
#define OTA_METADATA_MAGIC 0x4F544131ul
|
||||
/** 出厂恢复镜像元数据魔数。 */
|
||||
@@ -53,5 +53,7 @@
|
||||
#define FACTORY_BUTTON_HOLD_TIME_MS 5000u
|
||||
/** Bootloader忙等待1ms的循环次数,需结合实际系统时钟校准。 */
|
||||
#define BOOTLOADER_DELAY_1MS_LOOPS 1200u
|
||||
/** 内部Flash单次擦写等待的最大循环次数,超时即判定操作失败。 */
|
||||
#define BOOTLOADER_FLASH_WAIT_TIMEOUT 60000ul
|
||||
|
||||
#endif
|
||||
|
||||
@@ -54,6 +54,13 @@
|
||||
#define GPIOC_DDR REG8(0x500Cu)
|
||||
#define GPIOC_CR1 REG8(0x500Du)
|
||||
|
||||
/* GPIO Port D寄存器;PD4用于W25Q64片选CS#。 */
|
||||
#define GPIOD_ODR REG8(0x500Fu)
|
||||
#define GPIOD_IDR REG8(0x5010u)
|
||||
#define GPIOD_DDR REG8(0x5011u)
|
||||
#define GPIOD_CR1 REG8(0x5012u)
|
||||
#define GPIOD_CR2 REG8(0x5013u)
|
||||
|
||||
#define SPI_CR1_SPE 0x40u
|
||||
#define SPI_CR1_MSTR 0x04u
|
||||
#define SPI_CR1_BR0 0x08u
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* @file w25q64.h
|
||||
* @brief W25Q64JV 硬件SPI访问接口。
|
||||
* @brief W25Q80BV 硬件SPI访问接口。
|
||||
*/
|
||||
#ifndef STM8_BOOTLOADER_W25Q64_H
|
||||
#define STM8_BOOTLOADER_W25Q64_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** 初始化STM8硬件SPI和W25Q64片选。 */
|
||||
/** 初始化STM8硬件SPI和W25Q64片选;当前片选引脚为PD4。 */
|
||||
void W25Q64_Init(void);
|
||||
/** 读取W25Q64 JEDEC ID,便于启动时确认芯片存在。 */
|
||||
uint32_t W25Q64_ReadJedecId(void);
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t magic;
|
||||
uint32_t image_size;
|
||||
uint16_t version;
|
||||
uint16_t image_crc16;
|
||||
uint8_t image_valid;
|
||||
uint8_t request;
|
||||
uint8_t result;
|
||||
uint8_t reserved;
|
||||
uint32_t magic; /* 用于识别元数据类型的固定魔数。 */
|
||||
uint32_t image_size; /* 固件裸二进制长度,业务格式为高字节在前。 */
|
||||
uint16_t version; /* 固件版本号,业务格式为高字节在前。 */
|
||||
uint16_t image_crc16; /* 固件完整镜像CRC16,业务格式为高字节在前。 */
|
||||
uint8_t image_valid; /* 镜像已经完整写入并通过校验的标志。 */
|
||||
uint8_t request; /* 非零表示Bootloader需要处理该镜像。 */
|
||||
uint8_t result; /* 最近一次搬运结果码。 */
|
||||
uint8_t reserved; /* 预留字段,保持元数据固定长度。 */
|
||||
} BootloaderMetadata;
|
||||
|
||||
/** 出厂镜像元数据与普通升级元数据使用相同布局,便于复用校验流程。 */
|
||||
@@ -36,6 +36,43 @@ typedef BootloaderMetadata FactoryMetadata;
|
||||
#define OTA_RESULT_W25Q64_ERROR 0x06u
|
||||
#define FACTORY_BUTTON_MASK 0x08u
|
||||
|
||||
/**
|
||||
* @brief 从W25Q64读取并解析一份镜像元数据。
|
||||
*
|
||||
* 元数据中的固件长度、版本和CRC16属于跨程序边界的数据,统一采用
|
||||
* 高字节在前的存储格式,不能直接把Flash字节流强制转换成C结构体。
|
||||
* 魔数保留现有出厂布局的字节顺序,以兼容已经生成的镜像元数据;其余
|
||||
* 数值字段严格按照 bootloader_config.h 中定义的固定偏移显式解析。
|
||||
*
|
||||
* @param address 元数据所在的W25Q64起始地址。
|
||||
* @param metadata 输出的解析结果,不能为NULL。
|
||||
*/
|
||||
static void Bootloader_ReadMetadata(uint32_t address,
|
||||
BootloaderMetadata *metadata)
|
||||
{
|
||||
uint8_t raw[18];
|
||||
|
||||
W25Q64_Read(address, raw, (uint16_t)sizeof(raw));
|
||||
|
||||
/* 魔数沿用当前镜像生成格式:Flash中按低字节到高字节保存。 */
|
||||
metadata->magic = (uint32_t)raw[0] |
|
||||
((uint32_t)raw[1] << 8u) |
|
||||
((uint32_t)raw[2] << 16u) |
|
||||
((uint32_t)raw[3] << 24u);
|
||||
|
||||
/* 长度、版本和CRC16统一按协议规定的高字节在前解析。 */
|
||||
metadata->image_size = ((uint32_t)raw[4] << 24u) |
|
||||
((uint32_t)raw[5] << 16u) |
|
||||
((uint32_t)raw[6] << 8u) |
|
||||
(uint32_t)raw[7];
|
||||
metadata->version = (uint16_t)(((uint16_t)raw[8] << 8u) | raw[9]);
|
||||
metadata->image_crc16 = (uint16_t)(((uint16_t)raw[10] << 8u) | raw[11]);
|
||||
metadata->image_valid = raw[14];
|
||||
metadata->request = raw[15];
|
||||
metadata->result = raw[16];
|
||||
metadata->reserved = raw[17];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化独立看门狗。
|
||||
*
|
||||
@@ -55,7 +92,7 @@ static void Bootloader_WatchdogInit(void)
|
||||
/**
|
||||
* @brief 刷新独立看门狗。
|
||||
*/
|
||||
static void Bootloader_WatchdogRefresh(void)
|
||||
void Bootloader_WatchdogRefresh(void)
|
||||
{
|
||||
IWDG_KR = 0xAAu;
|
||||
}
|
||||
@@ -74,6 +111,7 @@ static uint8_t Bootloader_ReadFlashByte(uint16_t address)
|
||||
* @brief 初始化上电恢复按键PA3。
|
||||
*
|
||||
* 按键另一端接地,因此PA3采用内部上拉:高电平表示释放,低电平表示按下。
|
||||
* W25Q64片选使用PD4,与本按键互不复用。
|
||||
*/
|
||||
static void Bootloader_ButtonInit(void)
|
||||
{
|
||||
@@ -120,6 +158,17 @@ static uint8_t Bootloader_IsFactoryButtonHeld(void)
|
||||
return 1u;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按CRC-16/MODBUS规则处理一段固件数据。
|
||||
*
|
||||
* 该函数支持分段计算:调用者可以把上一次返回的CRC作为下一次调用的
|
||||
* 初始值,从而不需要把整个固件一次性装入RAM。
|
||||
*
|
||||
* @param data 当前数据片段。
|
||||
* @param length 当前数据片段长度。
|
||||
* @param crc 当前累计CRC;第一段应传入0xFFFF。
|
||||
* @return 加入当前数据片段后的累计CRC值。
|
||||
*/
|
||||
static uint16_t Bootloader_Crc16(const uint8_t *data, uint16_t length,
|
||||
uint16_t crc)
|
||||
{
|
||||
@@ -137,9 +186,20 @@ static uint16_t Bootloader_Crc16(const uint8_t *data, uint16_t length,
|
||||
return crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向STM8内部程序Flash写入一个字节并回读校验。
|
||||
*
|
||||
* 函数首次调用时解锁程序Flash;每次写入都等待EOP完成标志,并在等待
|
||||
* 期间刷新看门狗。超过超时阈值后返回失败,由上层决定是否恢复或复位。
|
||||
*
|
||||
* @param address 内部Flash写入地址。
|
||||
* @param value 要写入的字节。
|
||||
* @return 1表示写入并回读一致,0表示超时或回读不一致。
|
||||
*/
|
||||
static uint8_t Bootloader_ApplicationWriteByte(uint16_t address,
|
||||
uint8_t value)
|
||||
{
|
||||
uint32_t timeout = BOOTLOADER_FLASH_WAIT_TIMEOUT;
|
||||
/* 程序Flash解锁序列必须在第一次擦写前完成。 */
|
||||
static uint8_t unlocked;
|
||||
if (unlocked == 0u)
|
||||
@@ -151,10 +211,25 @@ static uint8_t Bootloader_ApplicationWriteByte(uint16_t address,
|
||||
FLASH_CR2 = FLASH_CR2_PRG;
|
||||
FLASH_NCR2 = (uint8_t)~FLASH_CR2_PRG;
|
||||
*((volatile uint8_t *)address) = value;
|
||||
while ((FLASH_IAPSR & FLASH_IAPSR_EOP) == 0u) {}
|
||||
while ((FLASH_IAPSR & FLASH_IAPSR_EOP) == 0u)
|
||||
{
|
||||
Bootloader_WatchdogRefresh();
|
||||
if (timeout-- == 0ul)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
return (*((volatile uint8_t *)address) == value) ? 1u : 0u;
|
||||
}
|
||||
|
||||
/**
|
||||
* 擦除整个应用程序区域。
|
||||
*
|
||||
* STM8S003F3P6程序Flash按64字节块擦除。每个块都单独等待完成并设置
|
||||
* 超时,任意一块失败都会立即终止,防止把不完整擦除状态当作成功升级。
|
||||
*
|
||||
* @return 1表示应用区全部擦除完成,0表示某个擦除块超时。
|
||||
*/
|
||||
static uint8_t Bootloader_EraseApplication(void)
|
||||
{
|
||||
uint16_t address;
|
||||
@@ -171,15 +246,34 @@ static uint8_t Bootloader_EraseApplication(void)
|
||||
/* STM8S003程序Flash按64字节块擦除;擦除前先把整片应用区设为0xFF。 */
|
||||
for (page = 0u; page < APPLICATION_MAX_SIZE_BYTES; page += 64u)
|
||||
{
|
||||
uint32_t timeout = BOOTLOADER_FLASH_WAIT_TIMEOUT;
|
||||
address = (uint16_t)(APPLICATION_START_ADDRESS + page);
|
||||
FLASH_CR2 = FLASH_CR2_ERASE;
|
||||
FLASH_NCR2 = (uint8_t)~FLASH_CR2_ERASE;
|
||||
*((volatile uint8_t *)address) = 0x00u;
|
||||
while ((FLASH_IAPSR & FLASH_IAPSR_EOP) == 0u) {}
|
||||
while ((FLASH_IAPSR & FLASH_IAPSR_EOP) == 0u)
|
||||
{
|
||||
Bootloader_WatchdogRefresh();
|
||||
if (timeout-- == 0ul)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1u;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分段读取并校验外部Flash中的完整固件镜像。
|
||||
*
|
||||
* 每次只读取16字节,避免占用超过STM8 RAM能力的连续缓冲区;校验通过后
|
||||
* 才允许擦除内部应用区。
|
||||
*
|
||||
* @param image_address 外部Flash镜像起始地址。
|
||||
* @param image_size 镜像长度,不能超过内部应用区容量。
|
||||
* @param expected_crc16 元数据中保存的期望CRC16。
|
||||
* @return 1表示镜像长度合法且CRC匹配,0表示校验失败。
|
||||
*/
|
||||
static uint8_t Bootloader_ValidateImage(uint32_t image_address,
|
||||
uint32_t image_size,
|
||||
uint16_t expected_crc16)
|
||||
@@ -204,6 +298,16 @@ static uint8_t Bootloader_ValidateImage(uint32_t image_address,
|
||||
return (crc == expected_crc16) ? 1u : 0u;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将外部Flash中的镜像搬运到STM8内部应用区。
|
||||
*
|
||||
* 搬运前先擦除应用区,随后按16字节读取外部Flash并逐字节写入内部
|
||||
* Flash。单字节写入函数负责等待、超时和回读校验。
|
||||
*
|
||||
* @param image_address 外部Flash镜像起始地址。
|
||||
* @param image_size 镜像长度。
|
||||
* @return 1表示全部写入成功,0表示擦除或写入失败。
|
||||
*/
|
||||
static uint8_t Bootloader_WriteImage(uint32_t image_address,
|
||||
uint32_t image_size)
|
||||
{
|
||||
@@ -234,6 +338,18 @@ static uint8_t Bootloader_WriteImage(uint32_t image_address,
|
||||
return 1u;
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成一次完整的镜像校验、搬运和结果记录流程。
|
||||
*
|
||||
* 先校验、后擦除是安全关键:如果外部镜像损坏,必须保留当前内部应用,
|
||||
* 不能因为升级请求而先把可运行程序擦掉。
|
||||
*
|
||||
* @param image_address 外部Flash镜像起始地址。
|
||||
* @param image_size 镜像长度。
|
||||
* @param expected_crc16 期望的完整镜像CRC16。
|
||||
* @param write_result 非零时把升级结果写回OTA元数据。
|
||||
* @return 1表示流程成功,0表示校验、搬运或结果记录失败。
|
||||
*/
|
||||
static uint8_t Bootloader_CopyImage(uint32_t image_address,
|
||||
uint32_t image_size,
|
||||
uint16_t expected_crc16,
|
||||
@@ -278,11 +394,18 @@ void Bootloader_Init(void)
|
||||
W25Q64_Init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查OTA元数据并处理一次普通升级请求。
|
||||
*
|
||||
* 只有魔数、镜像有效标志和升级请求标志同时正确时才会进入搬运流程;
|
||||
* 长度非法时直接记录尺寸错误,不擦除内部应用区。
|
||||
*
|
||||
* @return 1表示升级镜像已经成功搬运并清除请求,0表示没有请求或处理失败。
|
||||
*/
|
||||
uint8_t Bootloader_ProcessUpgrade(void)
|
||||
{
|
||||
BootloaderMetadata metadata;
|
||||
W25Q64_Read(OTA_METADATA_ADDRESS, (uint8_t *)&metadata,
|
||||
(uint16_t)sizeof(metadata));
|
||||
Bootloader_ReadMetadata(OTA_METADATA_ADDRESS, &metadata);
|
||||
if (metadata.magic != OTA_METADATA_MAGIC ||
|
||||
metadata.image_valid != METADATA_IMAGE_VALID_VALUE ||
|
||||
metadata.request == 0u)
|
||||
@@ -306,8 +429,7 @@ uint8_t Bootloader_ProcessUpgrade(void)
|
||||
static uint8_t Bootloader_ProcessFactoryRecovery(void)
|
||||
{
|
||||
FactoryMetadata metadata;
|
||||
W25Q64_Read(FACTORY_METADATA_ADDRESS, (uint8_t *)&metadata,
|
||||
(uint16_t)sizeof(metadata));
|
||||
Bootloader_ReadMetadata(FACTORY_METADATA_ADDRESS, &metadata);
|
||||
if (metadata.magic != FACTORY_METADATA_MAGIC ||
|
||||
metadata.image_valid != METADATA_IMAGE_VALID_VALUE ||
|
||||
metadata.request == 0u ||
|
||||
@@ -389,8 +511,7 @@ void Bootloader_Task(void)
|
||||
upgrade_processed = 1u;
|
||||
{
|
||||
BootloaderMetadata metadata;
|
||||
W25Q64_Read(OTA_METADATA_ADDRESS, (uint8_t *)&metadata,
|
||||
(uint16_t)sizeof(metadata));
|
||||
Bootloader_ReadMetadata(OTA_METADATA_ADDRESS, &metadata);
|
||||
upgrade_requested = (metadata.magic == OTA_METADATA_MAGIC &&
|
||||
metadata.image_valid == METADATA_IMAGE_VALID_VALUE &&
|
||||
metadata.request != 0u) ? 1u : 0u;
|
||||
@@ -406,6 +527,10 @@ void Bootloader_Task(void)
|
||||
{
|
||||
Bootloader_JumpToApplication();
|
||||
}
|
||||
/* 升级和出厂恢复都失败时,不再喂狗,交给看门狗复位重新尝试。 */
|
||||
while (1)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,44 @@
|
||||
/**
|
||||
* @file w25q64.c
|
||||
* @brief W25Q64JV 最小硬件SPI驱动。
|
||||
* @brief W25Q80BV 最小硬件SPI驱动。
|
||||
*
|
||||
* 引脚固定为:PC5=SCK、PC6=MOSI、PC7=MISO、PA3=CS#。
|
||||
* 引脚固定为:PC5=SCK、PC6=MOSI、PC7=MISO、PD4=CS#。
|
||||
* 本驱动只使用读取路径,因为Bootloader只读取主程序已经下载并校验好的
|
||||
* 固件;擦除和写入W25Q64属于Application职责。
|
||||
*/
|
||||
#include "w25q64.h"
|
||||
#include "bootloader.h"
|
||||
#include "stm8_bootloader_registers.h"
|
||||
#include "bootloader_config.h"
|
||||
|
||||
#define FLASH_CS_MASK 0x08u
|
||||
#define W25Q64_CMD_JEDEC_ID 0x9Fu
|
||||
#define W25Q64_CMD_READ_DATA 0x03u
|
||||
#define W25Q64_CMD_WRITE_ENABLE 0x06u
|
||||
#define W25Q64_CMD_READ_STATUS1 0x05u
|
||||
#define W25Q64_CMD_PAGE_PROGRAM 0x02u
|
||||
#define W25Q64_CMD_SECTOR_ERASE 0x20u
|
||||
#define W25Q64_STATUS_BUSY 0x01u
|
||||
#define FLASH_CS_MASK 0x10u /* PD4,低电平选中W25Q64。 */
|
||||
#define W25Q64_CMD_JEDEC_ID 0x9Fu /* 读取厂商、存储器类型和容量标识。 */
|
||||
#define W25Q64_CMD_READ_DATA 0x03u /* 普通连续读取命令。 */
|
||||
#define W25Q64_CMD_WRITE_ENABLE 0x06u /* 允许后续擦除或写入。 */
|
||||
#define W25Q64_CMD_READ_STATUS1 0x05u /* 读取状态寄存器1。 */
|
||||
#define W25Q64_CMD_PAGE_PROGRAM 0x02u /* 页编程命令。 */
|
||||
#define W25Q64_CMD_SECTOR_ERASE 0x20u /* 擦除4KB扇区。 */
|
||||
#define W25Q64_STATUS_BUSY 0x01u /* 状态寄存器BUSY位。 */
|
||||
|
||||
/** 将W25Q64片选拉低,开始一次SPI事务。 */
|
||||
static void W25Q64_Select(void)
|
||||
{
|
||||
GPIOA_ODR &= (uint8_t)~FLASH_CS_MASK;
|
||||
GPIOD_ODR &= (uint8_t)~FLASH_CS_MASK;
|
||||
}
|
||||
|
||||
/** 将W25Q64片选拉高,结束一次SPI事务并释放总线。 */
|
||||
static void W25Q64_Deselect(void)
|
||||
{
|
||||
GPIOA_ODR |= FLASH_CS_MASK;
|
||||
GPIOD_ODR |= FLASH_CS_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过SPI发送一个字节并同时读取返回字节。
|
||||
*
|
||||
* SPI是全双工接口,发送期间必须等待发送缓冲区可写,发送完成后还要
|
||||
* 等待接收缓冲区出现一个字节。对于只写命令,返回值会被忽略;对于读取
|
||||
* 命令,返回值就是W25Q64输出的数据。
|
||||
*/
|
||||
static uint8_t W25Q64_Transfer(uint8_t value)
|
||||
{
|
||||
while ((SPI1_SR & SPI_SR_TXE) == 0u) {}
|
||||
@@ -37,6 +47,7 @@ static uint8_t W25Q64_Transfer(uint8_t value)
|
||||
return SPI1_DR;
|
||||
}
|
||||
|
||||
/** 向W25Q64发送写使能命令,为擦除或页编程准备写入许可。 */
|
||||
static void W25Q64_WriteEnable(void)
|
||||
{
|
||||
W25Q64_Select();
|
||||
@@ -44,6 +55,7 @@ static void W25Q64_WriteEnable(void)
|
||||
W25Q64_Deselect();
|
||||
}
|
||||
|
||||
/** 读取状态寄存器1,用于判断W25Q64是否仍处于擦写忙状态。 */
|
||||
static uint8_t W25Q64_ReadStatus1(void)
|
||||
{
|
||||
uint8_t status;
|
||||
@@ -54,11 +66,19 @@ static uint8_t W25Q64_ReadStatus1(void)
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待W25Q64完成内部擦除或编程。
|
||||
*
|
||||
* 正常情况下持续刷新Bootloader看门狗;超过最大轮询次数则认为外部
|
||||
* Flash或SPI通信异常,返回失败,避免无限等待。
|
||||
*/
|
||||
static uint8_t W25Q64_WaitReady(void)
|
||||
{
|
||||
uint32_t timeout = 200000ul;
|
||||
while ((W25Q64_ReadStatus1() & W25Q64_STATUS_BUSY) != 0u)
|
||||
{
|
||||
/* W25Q64正常擦写可能持续较长时间,但异常时必须由超时退出。 */
|
||||
Bootloader_WatchdogRefresh();
|
||||
if (timeout-- == 0ul)
|
||||
{
|
||||
return 0u;
|
||||
@@ -67,13 +87,19 @@ static uint8_t W25Q64_WaitReady(void)
|
||||
return 1u;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Bootloader使用的硬件SPI和W25Q64片选GPIO。
|
||||
*
|
||||
* SPI采用主机模式、模式0、MSB first,并使用较低初始时钟,降低上电阶段
|
||||
* 外部Flash尚未稳定时出现通信错误的概率。
|
||||
*/
|
||||
void W25Q64_Init(void)
|
||||
{
|
||||
/* PC5、PC6配置为推挽输出;PC7配置为输入;PA3配置为CS#输出。 */
|
||||
/* PC5、PC6配置为推挽输出;PC7配置为输入;PD4配置为CS#输出。 */
|
||||
GPIOC_DDR |= 0x60u;
|
||||
GPIOC_CR1 |= 0x60u;
|
||||
GPIOA_DDR |= FLASH_CS_MASK;
|
||||
GPIOA_CR1 |= FLASH_CS_MASK;
|
||||
GPIOD_DDR |= FLASH_CS_MASK;
|
||||
GPIOD_CR1 |= FLASH_CS_MASK;
|
||||
W25Q64_Deselect();
|
||||
|
||||
/* SPI主机、MSB first、模式0、低速启动,避免上电阶段时序过快。 */
|
||||
@@ -82,6 +108,10 @@ void W25Q64_Init(void)
|
||||
SPI1_CR2 = (uint8_t)(SPI_CR2_SSM | SPI_CR2_SSI);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取W25Q64 JEDEC ID。
|
||||
* @return 由厂商ID、存储器类型和容量ID组成的24位标识;当前仅供诊断使用。
|
||||
*/
|
||||
uint32_t W25Q64_ReadJedecId(void)
|
||||
{
|
||||
uint32_t id;
|
||||
@@ -94,6 +124,12 @@ uint32_t W25Q64_ReadJedecId(void)
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从W25Q64连续读取数据。
|
||||
* @param address 24位Flash起始地址,SPI线上按高字节到低字节发送。
|
||||
* @param buffer 接收数据的RAM缓冲区。
|
||||
* @param length 需要读取的字节数。
|
||||
*/
|
||||
void W25Q64_Read(uint32_t address, uint8_t *buffer, uint16_t length)
|
||||
{
|
||||
uint16_t index;
|
||||
@@ -109,6 +145,11 @@ void W25Q64_Read(uint32_t address, uint8_t *buffer, uint16_t length)
|
||||
W25Q64_Deselect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 擦除包含指定地址的4KB扇区。
|
||||
* @param address 扇区内任意地址,W25Q64会按扇区边界执行擦除。
|
||||
* @return 1表示擦除完成,0表示等待超时。
|
||||
*/
|
||||
uint8_t W25Q64_EraseSector(uint32_t address)
|
||||
{
|
||||
W25Q64_WriteEnable();
|
||||
@@ -121,6 +162,17 @@ uint8_t W25Q64_EraseSector(uint32_t address)
|
||||
return W25Q64_WaitReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在单个256字节页内编程数据。
|
||||
*
|
||||
* 调用者必须保证数据不跨页;跨页写入会被拒绝,避免W25Q64在页边界
|
||||
* 回卷覆盖页首数据。
|
||||
*
|
||||
* @param address 页内起始地址。
|
||||
* @param data 待写入数据。
|
||||
* @param length 写入长度,范围为1至256字节。
|
||||
* @return 1表示写入完成,0表示参数非法或W25Q64等待超时。
|
||||
*/
|
||||
uint8_t W25Q64_PageProgram(uint32_t address, const uint8_t *data,
|
||||
uint16_t length)
|
||||
{
|
||||
@@ -144,6 +196,7 @@ uint8_t W25Q64_PageProgram(uint32_t address, const uint8_t *data,
|
||||
return W25Q64_WaitReady();
|
||||
}
|
||||
|
||||
/** 清零OTA元数据中的升级请求标志,防止同一镜像重复搬运。 */
|
||||
uint8_t W25Q64_ClearUpgradeRequest(void)
|
||||
{
|
||||
uint8_t cleared = 0u;
|
||||
@@ -152,6 +205,7 @@ uint8_t W25Q64_ClearUpgradeRequest(void)
|
||||
&cleared, 1u);
|
||||
}
|
||||
|
||||
/** 写入OTA升级结果码,供主程序上电后读取并向主机上报。 */
|
||||
uint8_t W25Q64_WriteUpgradeResult(uint8_t result)
|
||||
{
|
||||
return W25Q64_PageProgram(OTA_METADATA_ADDRESS +
|
||||
|
||||
Reference in New Issue
Block a user