feat: 完善Mesh继电器OTA与地址协议

- 增加STM8 Bootloader工程及出厂固件恢复流程\n- 增加W25Q64外部Flash驱动和OTA分包接收、CRC16校验、断点续传状态管理\n- 增加固件版本查询、升级结果上报与主机确认机制\n- 将业务帧从55 AA帧头调整为发送方Mesh短地址+CMD+LEN+DATA+CRC16\n- 修正JDY-28M外层地址与业务发送方地址的职责划分,支持广播请求定向应答\n- 同步更新硬件引脚、存储分区、OTA流程和主机通信协议文档\n- 已使用SDCC重新编译,应用代码大小为5512字节,链接地址为0x8800
This commit is contained in:
十七
2026-11-03 11:29:55 +08:00
parent d275a3523b
commit 42a4650a78
30 changed files with 2318 additions and 108 deletions
+38
View File
@@ -0,0 +1,38 @@
PROJECT := stm8_bootloader
BUILD_DIR := build
SRC_DIR := src
INC_DIR := include
CC := sdcc
OBJCOPY := packihx
# Bootloader 固定放在 STM8S003F3P6 的 Flash 起始地址 0x8000。
CFLAGS := -mstm8 --std-c99 --opt-code-size -I$(INC_DIR)
LDFLAGS := -mstm8 --out-fmt-ihx --code-loc 0x8000 --data-loc 0x0000
SOURCES := $(shell find $(SRC_DIR) -name '*.c' -print)
OBJECTS := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.rel,$(SOURCES))
IHX := $(BUILD_DIR)/$(PROJECT).ihx
HEX := $(BUILD_DIR)/$(PROJECT).hex
.PHONY: all clean size
all: $(HEX)
$(HEX): $(IHX)
@mkdir -p $(dir $@)
$(OBJCOPY) $< > $@
$(IHX): $(OBJECTS)
@mkdir -p $(dir $@)
$(CC) $(LDFLAGS) -o $@ $(OBJECTS)
$(BUILD_DIR)/%.rel: $(SRC_DIR)/%.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
size: $(IHX)
@ls -lh $(IHX) $(HEX)
clean:
rm -rf $(BUILD_DIR)
+22
View File
@@ -0,0 +1,22 @@
# STM8S003F3P6TR Bootloader
这是独立于应用程序的 STM8S003F3P6TR Bootloader 工程。
当前地址规划:
```text
0x8000 ~ 0x83FF Bootloader,1KB
0x8400 ~ 0x9FFF Application,7KB
```
当前版本完成的基础功能:
- 复位后初始化最小硬件环境;
- 读取应用程序首字节,判断应用程序是否存在;
- 通过跳转入口启动应用程序;
- 看门狗保护;
- 仅为后续 W25Q64 升级标志读取、CRC16/MODBUS 校验和应用程序写入预留接口。
OTA 分包接收、Mesh 通信、W25Q64 下载写入属于主程序,不属于本工程。
注意:当前版本是 Bootloader 基础框架,尚未接入完整的 JDY-28M OTA 下载协议和 W25Q64 驱动。实物烧录前必须先确认应用程序已经按照 `0x8400` 重新链接。
+21
View File
@@ -0,0 +1,21 @@
/**
* @file bootloader.h
* @brief Bootloader 顶层流程接口。
*/
#ifndef STM8_BOOTLOADER_H
#define STM8_BOOTLOADER_H
#include <stdint.h>
/** 初始化 Bootloader 所需的最小硬件资源。 */
void Bootloader_Init(void);
/** 判断应用程序区域是否包含可执行程序。 */
uint8_t Bootloader_IsApplicationValid(void);
/** 跳转到应用程序入口地址,函数成功后不返回。 */
void Bootloader_JumpToApplication(void);
/** Bootloader 主任务,后续OTA接收和搬运流程在此扩展。 */
void Bootloader_Task(void);
/** 检查W25Q64中的升级请求并完成固件搬运。 */
uint8_t Bootloader_ProcessUpgrade(void);
#endif
@@ -0,0 +1,57 @@
/**
* @file bootloader_config.h
* @brief Bootloader 的固定地址和安全策略配置。
*/
#ifndef STM8_BOOTLOADER_CONFIG_H
#define STM8_BOOTLOADER_CONFIG_H
#include <stdint.h>
/** Bootloader 在 STM8 程序 Flash 中的起始地址。 */
#define BOOTLOADER_START_ADDRESS 0x8000u
/** Bootloader 预留空间,当前规划为 1KB。 */
#define BOOTLOADER_SIZE_BYTES 0x0800u
/** 应用程序固定入口地址,必须与应用工程链接地址一致。 */
#define APPLICATION_START_ADDRESS 0x8800u
/** STM8S003F3P6 程序 Flash 的最后一个地址。 */
#define APPLICATION_END_ADDRESS 0x9FFFu
/** 应用程序允许使用的最大空间。 */
#define APPLICATION_MAX_SIZE_BYTES \
(APPLICATION_END_ADDRESS - APPLICATION_START_ADDRESS + 1u)
/** W25Q64 中保存出厂固件镜像的固定地址,容量64KiB。 */
#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
/** 升级元数据魔数,用于避免误把普通数据当成升级请求。 */
#define OTA_METADATA_MAGIC 0x4F544131ul
/** 出厂恢复镜像元数据魔数。 */
#define FACTORY_METADATA_MAGIC 0x46414331ul
/* 元数据字段相对于各自元数据扇区起始地址的固定偏移。 */
#define METADATA_MAGIC_OFFSET 0x00u
#define METADATA_IMAGE_SIZE_OFFSET 0x04u
#define METADATA_VERSION_OFFSET 0x08u
#define METADATA_CRC16_OFFSET 0x0Au
#define METADATA_IMAGE_VALID_OFFSET 0x0Eu
#define METADATA_UPGRADE_REQUEST_OFFSET 0x0Fu
#define METADATA_UPGRADE_RESULT_OFFSET 0x10u
/** 镜像已经完整写入并通过校验时使用的有效标志。 */
#define METADATA_IMAGE_VALID_VALUE 0xA5u
/** 应用程序有效标记,避免把空白 Flash 当作可执行程序。 */
#define APPLICATION_FIRST_BYTE_INVALID 0x00u
#define APPLICATION_FIRST_BYTE_ERASED 0xFFu
/** Bootloader 看门狗刷新周期,必须小于硬件看门狗超时时间。 */
#define BOOTLOADER_WATCHDOG_REFRESH_LOOP 20000u
/** 上电恢复按键判定时间,单位为毫秒。 */
#define FACTORY_BUTTON_HOLD_TIME_MS 5000u
/** Bootloader忙等待1ms的循环次数,需结合实际系统时钟校准。 */
#define BOOTLOADER_DELAY_1MS_LOOPS 1200u
#endif
@@ -0,0 +1,72 @@
/**
* @file stm8_bootloader_registers.h
* @brief Bootloader 使用的 STM8S003F3P6 最小寄存器定义。
*/
#ifndef STM8_BOOTLOADER_REGISTERS_H
#define STM8_BOOTLOADER_REGISTERS_H
#include <stdint.h>
#define REG8(address) (*((volatile uint8_t *)(address)))
/* FLASH 外设寄存器地址,地址来自 STM8S003 数据手册。 */
#define FLASH_CR1_ADDR 0x505Au
#define FLASH_CR2_ADDR 0x505Bu
#define FLASH_NCR2_ADDR 0x505Cu
#define FLASH_IAPSR_ADDR 0x505Fu
#define FLASH_PUKR_ADDR 0x5062u
#define FLASH_CR1 REG8(FLASH_CR1_ADDR)
#define FLASH_CR2 REG8(FLASH_CR2_ADDR)
#define FLASH_NCR2 REG8(FLASH_NCR2_ADDR)
#define FLASH_IAPSR REG8(FLASH_IAPSR_ADDR)
#define FLASH_PUKR REG8(FLASH_PUKR_ADDR)
/* Flash 控制位。 */
#define FLASH_CR2_PRG 0x01u
#define FLASH_CR2_WPRG 0x10u
#define FLASH_IAPSR_HVOFF 0x08u
/* IWDG 寄存器。 */
#define IWDG_KR_ADDR 0x50E0u
#define IWDG_PR_ADDR 0x50E1u
#define IWDG_RLR_ADDR 0x50E2u
#define IWDG_KR REG8(IWDG_KR_ADDR)
#define IWDG_PR REG8(IWDG_PR_ADDR)
#define IWDG_RLR REG8(IWDG_RLR_ADDR)
/* SPI1 外设寄存器。 */
#define SPI1_CR1_ADDR 0x5200u
#define SPI1_CR2_ADDR 0x5201u
#define SPI1_ICR_ADDR 0x5202u
#define SPI1_SR_ADDR 0x5203u
#define SPI1_DR_ADDR 0x5204u
#define SPI1_CR1 REG8(SPI1_CR1_ADDR)
#define SPI1_CR2 REG8(SPI1_CR2_ADDR)
#define SPI1_SR REG8(SPI1_SR_ADDR)
#define SPI1_DR REG8(SPI1_DR_ADDR)
#define GPIOA_ODR REG8(0x5000u)
#define GPIOA_IDR REG8(0x5001u)
#define GPIOA_DDR REG8(0x5002u)
#define GPIOA_CR1 REG8(0x5003u)
#define GPIOC_ODR REG8(0x500Au)
#define GPIOC_DDR REG8(0x500Cu)
#define GPIOC_CR1 REG8(0x500Du)
#define SPI_CR1_SPE 0x40u
#define SPI_CR1_MSTR 0x04u
#define SPI_CR1_BR0 0x08u
#define SPI_CR1_BR1 0x10u
#define SPI_CR1_BR2 0x20u
#define SPI_CR2_SSI 0x01u
#define SPI_CR2_SSM 0x02u
#define SPI_SR_RXNE 0x01u
#define SPI_SR_TXE 0x02u
#define SPI_SR_BSY 0x80u
#define FLASH_IAPSR_EOP 0x04u
#define FLASH_CR2_PRG 0x01u
#define FLASH_CR2_ERASE 0x20u
#endif
+26
View File
@@ -0,0 +1,26 @@
/**
* @file w25q64.h
* @brief W25Q64JV 硬件SPI访问接口。
*/
#ifndef STM8_BOOTLOADER_W25Q64_H
#define STM8_BOOTLOADER_W25Q64_H
#include <stdint.h>
/** 初始化STM8硬件SPI和W25Q64片选。 */
void W25Q64_Init(void);
/** 读取W25Q64 JEDEC ID,便于启动时确认芯片存在。 */
uint32_t W25Q64_ReadJedecId(void);
/** 从W25Q64连续读取数据。 */
void W25Q64_Read(uint32_t address, uint8_t *buffer, uint16_t length);
/** 擦除包含指定地址的4KB扇区。 */
uint8_t W25Q64_EraseSector(uint32_t address);
/** 向W25Q64页内写入数据,长度不能跨越256字节页边界。 */
uint8_t W25Q64_PageProgram(uint32_t address, const uint8_t *data,
uint16_t length);
/** 将升级元数据中的request字节清零,表示升级已处理。 */
uint8_t W25Q64_ClearUpgradeRequest(void);
/** 写入Bootloader升级结果码,供主程序启动后上报。 */
uint8_t W25Q64_WriteUpgradeResult(uint8_t result);
#endif
+411
View File
@@ -0,0 +1,411 @@
/**
* @file bootloader.c
* @brief STM8S003F3P6 Bootloader 基础实现。
*
* Bootloader 不处理 JDY-28M Mesh 通信,也不负责接收OTA分包。OTA通信和
* W25Q64下载写入由 Application 完成;本程序只在复位后检查升级请求,
* 将已经校验完成的固件从W25Q64搬运到STM8片内应用区,然后跳转应用程序。
*/
#include "bootloader.h"
#include "bootloader_config.h"
#include "stm8_bootloader_registers.h"
#include "w25q64.h"
#include "bootloader_config.h"
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;
} BootloaderMetadata;
/** 出厂镜像元数据与普通升级元数据使用相同布局,便于复用校验流程。 */
typedef BootloaderMetadata FactoryMetadata;
#define OTA_RESULT_NONE 0x00u
#define OTA_RESULT_SUCCESS 0x01u
#define OTA_RESULT_CRC_ERROR 0x02u
#define OTA_RESULT_FLASH_WRITE_ERROR 0x03u
#define OTA_RESULT_VERIFY_ERROR 0x04u
#define OTA_RESULT_SIZE_ERROR 0x05u
#define OTA_RESULT_W25Q64_ERROR 0x06u
#define FACTORY_BUTTON_MASK 0x08u
/**
* @brief 初始化独立看门狗。
*
* Bootloader不能依赖应用程序的初始化结果,因此在自身入口中重新配置
* 看门狗。这里采用与应用程序一致的约5秒量级策略,具体超时时间仍需结合
* STM8时钟分频和实物测量确认。
*/
static void Bootloader_WatchdogInit(void)
{
IWDG_KR = 0xCCu;
IWDG_KR = 0x55u;
IWDG_PR = 0x06u;
IWDG_RLR = 0xFFu;
IWDG_KR = 0xAAu;
}
/**
* @brief 刷新独立看门狗。
*/
static void Bootloader_WatchdogRefresh(void)
{
IWDG_KR = 0xAAu;
}
/**
* @brief 读取程序 Flash 中的一个字节。
* @param address 需要读取的 16 位 Flash 地址。
* @return 该地址中的 Flash 数据。
*/
static uint8_t Bootloader_ReadFlashByte(uint16_t address)
{
return *((const uint8_t *)address);
}
/**
* @brief 初始化上电恢复按键PA3。
*
* 按键另一端接地,因此PA3采用内部上拉:高电平表示释放,低电平表示按下。
*/
static void Bootloader_ButtonInit(void)
{
GPIOA_DDR &= (uint8_t)~FACTORY_BUTTON_MASK;
GPIOA_CR1 |= FACTORY_BUTTON_MASK;
}
/**
* @brief 产生约1ms忙等待。
*
* 该延时只用于Bootloader上电按键判定,最终5秒精度需要在目标时钟下实测校准。
*/
static void Bootloader_Delay1ms(void)
{
volatile uint16_t loop;
for (loop = 0u; loop < BOOTLOADER_DELAY_1MS_LOOPS; loop++)
{
__asm__("nop");
}
}
/**
* @brief 判断上电时是否长按恢复按键。
* @return 1表示PA3持续按下达到5秒,0表示未按下或5秒内释放。
*/
static uint8_t Bootloader_IsFactoryButtonHeld(void)
{
uint16_t elapsed;
if ((GPIOA_IDR & FACTORY_BUTTON_MASK) != 0u)
{
return 0u;
}
for (elapsed = 0u; elapsed < FACTORY_BUTTON_HOLD_TIME_MS; elapsed++)
{
Bootloader_WatchdogRefresh();
if ((GPIOA_IDR & FACTORY_BUTTON_MASK) != 0u)
{
return 0u;
}
Bootloader_Delay1ms();
}
return 1u;
}
static uint16_t Bootloader_Crc16(const uint8_t *data, uint16_t length,
uint16_t crc)
{
uint8_t bit;
while (length-- != 0u)
{
crc ^= data[0];
data++;
for (bit = 0u; bit < 8u; bit++)
{
crc = (crc & 1u) ? (uint16_t)((crc >> 1) ^ 0xA001u) :
(uint16_t)(crc >> 1);
}
}
return crc;
}
static uint8_t Bootloader_ApplicationWriteByte(uint16_t address,
uint8_t value)
{
/* 程序Flash解锁序列必须在第一次擦写前完成。 */
static uint8_t unlocked;
if (unlocked == 0u)
{
FLASH_PUKR = 0x56u;
FLASH_PUKR = 0xAEu;
unlocked = 1u;
}
FLASH_CR2 = FLASH_CR2_PRG;
FLASH_NCR2 = (uint8_t)~FLASH_CR2_PRG;
*((volatile uint8_t *)address) = value;
while ((FLASH_IAPSR & FLASH_IAPSR_EOP) == 0u) {}
return (*((volatile uint8_t *)address) == value) ? 1u : 0u;
}
static uint8_t Bootloader_EraseApplication(void)
{
uint16_t address;
uint16_t page;
static uint8_t unlocked;
if (unlocked == 0u)
{
FLASH_PUKR = 0x56u;
FLASH_PUKR = 0xAEu;
unlocked = 1u;
}
/* STM8S003程序Flash按64字节块擦除;擦除前先把整片应用区设为0xFF。 */
for (page = 0u; page < APPLICATION_MAX_SIZE_BYTES; page += 64u)
{
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) {}
}
return 1u;
}
static uint8_t Bootloader_ValidateImage(uint32_t image_address,
uint32_t image_size,
uint16_t expected_crc16)
{
uint8_t buffer[16];
uint32_t source = image_address;
uint16_t crc = 0xFFFFu;
if (image_size == 0ul || image_size > APPLICATION_MAX_SIZE_BYTES)
{
return 0u;
}
while (image_size != 0ul)
{
uint16_t chunk = (image_size > 16ul) ? 16u : (uint16_t)image_size;
W25Q64_Read(source, buffer, chunk);
crc = Bootloader_Crc16(buffer, chunk, crc);
source += chunk;
image_size -= chunk;
}
return (crc == expected_crc16) ? 1u : 0u;
}
static uint8_t Bootloader_WriteImage(uint32_t image_address,
uint32_t image_size)
{
uint8_t buffer[16];
uint16_t index;
uint16_t destination = (uint16_t)APPLICATION_START_ADDRESS;
uint32_t source = image_address;
if (Bootloader_EraseApplication() == 0u)
{
return 0u;
}
while (image_size != 0ul)
{
uint16_t chunk = (image_size > 16ul) ? 16u : (uint16_t)image_size;
W25Q64_Read(source, buffer, chunk);
for (index = 0u; index < chunk; index++)
{
if (Bootloader_ApplicationWriteByte(destination, buffer[index]) == 0u)
{
return 0u;
}
destination++;
}
source += chunk;
image_size -= chunk;
}
return 1u;
}
static uint8_t Bootloader_CopyImage(uint32_t image_address,
uint32_t image_size,
uint16_t expected_crc16,
uint8_t write_result)
{
/* 先完整校验,再擦除应用区,避免坏固件导致原程序被提前破坏。 */
if (Bootloader_ValidateImage(image_address, image_size,
expected_crc16) == 0u)
{
if (write_result != 0u)
{
(void)W25Q64_WriteUpgradeResult(OTA_RESULT_CRC_ERROR);
}
return 0u;
}
if (Bootloader_WriteImage(image_address, image_size) == 0u)
{
if (write_result != 0u)
{
(void)W25Q64_WriteUpgradeResult(OTA_RESULT_FLASH_WRITE_ERROR);
}
return 0u;
}
if (write_result == 0u)
{
return 1u;
}
if (W25Q64_WriteUpgradeResult(OTA_RESULT_SUCCESS) == 0u)
{
return 0u;
}
return W25Q64_ClearUpgradeRequest();
}
/**
* @brief 初始化 Bootloader 所需的最小硬件资源。
*/
void Bootloader_Init(void)
{
Bootloader_WatchdogInit();
Bootloader_ButtonInit();
W25Q64_Init();
}
uint8_t Bootloader_ProcessUpgrade(void)
{
BootloaderMetadata metadata;
W25Q64_Read(OTA_METADATA_ADDRESS, (uint8_t *)&metadata,
(uint16_t)sizeof(metadata));
if (metadata.magic != OTA_METADATA_MAGIC ||
metadata.image_valid != METADATA_IMAGE_VALID_VALUE ||
metadata.request == 0u)
{
return 0u;
}
if (metadata.image_size == 0ul ||
metadata.image_size > APPLICATION_MAX_SIZE_BYTES)
{
(void)W25Q64_WriteUpgradeResult(OTA_RESULT_SIZE_ERROR);
return 0u;
}
return Bootloader_CopyImage(OTA_IMAGE_ADDRESS, metadata.image_size,
metadata.image_crc16, 1u);
}
/**
* @brief 从W25Q64固定出厂区域恢复应用程序。
* @return 1表示恢复成功,0表示出厂镜像不存在或校验失败。
*/
static uint8_t Bootloader_ProcessFactoryRecovery(void)
{
FactoryMetadata metadata;
W25Q64_Read(FACTORY_METADATA_ADDRESS, (uint8_t *)&metadata,
(uint16_t)sizeof(metadata));
if (metadata.magic != FACTORY_METADATA_MAGIC ||
metadata.image_valid != METADATA_IMAGE_VALID_VALUE ||
metadata.request == 0u ||
metadata.image_size == 0ul ||
metadata.image_size > APPLICATION_MAX_SIZE_BYTES)
{
return 0u;
}
return Bootloader_CopyImage(FACTORY_IMAGE_ADDRESS, metadata.image_size,
metadata.image_crc16, 0u);
}
/**
* @brief 判断应用程序区域是否存在有效入口。
*
* 这里只进行最小判断:应用区首字节不能是空白态 0xFF,也不能是明显无效
* 的0x00。正式版本还必须增加应用程序长度、版本和CRC16校验,只有整体校验
* 成功后才允许跳转。
*
* @return 1表示首字节具备基本有效性,0表示应用区为空或明显无效。
*/
uint8_t Bootloader_IsApplicationValid(void)
{
uint8_t first_byte = Bootloader_ReadFlashByte(
(uint16_t)APPLICATION_START_ADDRESS);
return (first_byte != APPLICATION_FIRST_BYTE_ERASED &&
first_byte != APPLICATION_FIRST_BYTE_INVALID) ? 1u : 0u;
}
/**
* @brief 跳转到应用程序入口地址。
*
* 应用程序必须按照 APPLICATION_START_ADDRESS 重新链接。跳转前不再执行
* 复杂外设关闭动作,避免增加Bootloader体积;应用程序进入后应重新初始化
* GPIO、UART、SPI、定时器、ADC和看门狗。
*/
void Bootloader_JumpToApplication(void)
{
void (*application_entry)(void);
application_entry = (void (*)(void))APPLICATION_START_ADDRESS;
application_entry();
}
/**
* @brief Bootloader 主任务。
*
* 当前基础版本暂不接收OTA数据,后续应在这里加入:
* 1. 读取W25Q64升级标志;
* 2. 校验完整固件CRC16/MODBUS;
* 3. 擦除应用程序区并按页写入;
* 4. 回读校验并清除升级标志;
* 5. 没有升级任务时启动现有应用程序。
*/
void Bootloader_Task(void)
{
uint8_t upgrade_processed = 0u;
uint8_t upgrade_requested = 0u;
uint8_t upgrade_success = 0u;
uint8_t factory_button_held;
factory_button_held = Bootloader_IsFactoryButtonHeld();
if (factory_button_held != 0u)
{
/* 上电长按优先级最高:不处理普通OTA请求,直接恢复出厂镜像。 */
(void)Bootloader_ProcessFactoryRecovery();
if (Bootloader_IsApplicationValid() != 0u)
{
Bootloader_JumpToApplication();
}
}
while (1)
{
Bootloader_WatchdogRefresh();
if (upgrade_processed == 0u)
{
upgrade_processed = 1u;
{
BootloaderMetadata metadata;
W25Q64_Read(OTA_METADATA_ADDRESS, (uint8_t *)&metadata,
(uint16_t)sizeof(metadata));
upgrade_requested = (metadata.magic == OTA_METADATA_MAGIC &&
metadata.image_valid == METADATA_IMAGE_VALID_VALUE &&
metadata.request != 0u) ? 1u : 0u;
}
upgrade_success = Bootloader_ProcessUpgrade();
if ((upgrade_requested != 0u && upgrade_success == 0u) ||
(upgrade_requested == 0u &&
Bootloader_IsApplicationValid() == 0u))
{
(void)Bootloader_ProcessFactoryRecovery();
}
if (Bootloader_IsApplicationValid() != 0u)
{
Bootloader_JumpToApplication();
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
/**
* @file main.c
* @brief Bootloader 独立工程入口。
*/
#include "bootloader.h"
/**
* @brief Bootloader 入口函数。
* @return 理论上不会返回。
*/
int main(void)
{
Bootloader_Init();
Bootloader_Task();
return 0;
}
+160
View File
@@ -0,0 +1,160 @@
/**
* @file w25q64.c
* @brief W25Q64JV 最小硬件SPI驱动。
*
* 引脚固定为:PC5=SCK、PC6=MOSI、PC7=MISO、PA3=CS#。
* 本驱动只使用读取路径,因为Bootloader只读取主程序已经下载并校验好的
* 固件;擦除和写入W25Q64属于Application职责。
*/
#include "w25q64.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
static void W25Q64_Select(void)
{
GPIOA_ODR &= (uint8_t)~FLASH_CS_MASK;
}
static void W25Q64_Deselect(void)
{
GPIOA_ODR |= FLASH_CS_MASK;
}
static uint8_t W25Q64_Transfer(uint8_t value)
{
while ((SPI1_SR & SPI_SR_TXE) == 0u) {}
SPI1_DR = value;
while ((SPI1_SR & SPI_SR_RXNE) == 0u) {}
return SPI1_DR;
}
static void W25Q64_WriteEnable(void)
{
W25Q64_Select();
W25Q64_Transfer(W25Q64_CMD_WRITE_ENABLE);
W25Q64_Deselect();
}
static uint8_t W25Q64_ReadStatus1(void)
{
uint8_t status;
W25Q64_Select();
W25Q64_Transfer(W25Q64_CMD_READ_STATUS1);
status = W25Q64_Transfer(0xFFu);
W25Q64_Deselect();
return status;
}
static uint8_t W25Q64_WaitReady(void)
{
uint32_t timeout = 200000ul;
while ((W25Q64_ReadStatus1() & W25Q64_STATUS_BUSY) != 0u)
{
if (timeout-- == 0ul)
{
return 0u;
}
}
return 1u;
}
void W25Q64_Init(void)
{
/* PC5、PC6配置为推挽输出;PC7配置为输入;PA3配置为CS#输出。 */
GPIOC_DDR |= 0x60u;
GPIOC_CR1 |= 0x60u;
GPIOA_DDR |= FLASH_CS_MASK;
GPIOA_CR1 |= FLASH_CS_MASK;
W25Q64_Deselect();
/* SPI主机、MSB first、模式0、低速启动,避免上电阶段时序过快。 */
SPI1_CR1 = (uint8_t)(SPI_CR1_MSTR | SPI_CR1_BR2 |
SPI_CR1_BR1 | SPI_CR1_BR0 | SPI_CR1_SPE);
SPI1_CR2 = (uint8_t)(SPI_CR2_SSM | SPI_CR2_SSI);
}
uint32_t W25Q64_ReadJedecId(void)
{
uint32_t id;
W25Q64_Select();
W25Q64_Transfer(W25Q64_CMD_JEDEC_ID);
id = (uint32_t)W25Q64_Transfer(0xFFu) << 16;
id |= (uint32_t)W25Q64_Transfer(0xFFu) << 8;
id |= (uint32_t)W25Q64_Transfer(0xFFu);
W25Q64_Deselect();
return id;
}
void W25Q64_Read(uint32_t address, uint8_t *buffer, uint16_t length)
{
uint16_t index;
W25Q64_Select();
W25Q64_Transfer(W25Q64_CMD_READ_DATA);
W25Q64_Transfer((uint8_t)(address >> 16));
W25Q64_Transfer((uint8_t)(address >> 8));
W25Q64_Transfer((uint8_t)address);
for (index = 0u; index < length; index++)
{
buffer[index] = W25Q64_Transfer(0xFFu);
}
W25Q64_Deselect();
}
uint8_t W25Q64_EraseSector(uint32_t address)
{
W25Q64_WriteEnable();
W25Q64_Select();
W25Q64_Transfer(W25Q64_CMD_SECTOR_ERASE);
W25Q64_Transfer((uint8_t)(address >> 16));
W25Q64_Transfer((uint8_t)(address >> 8));
W25Q64_Transfer((uint8_t)address);
W25Q64_Deselect();
return W25Q64_WaitReady();
}
uint8_t W25Q64_PageProgram(uint32_t address, const uint8_t *data,
uint16_t length)
{
uint16_t index;
if (length == 0u || length > 256u ||
(((uint16_t)address & 0x00FFu) + length) > 256u)
{
return 0u;
}
W25Q64_WriteEnable();
W25Q64_Select();
W25Q64_Transfer(W25Q64_CMD_PAGE_PROGRAM);
W25Q64_Transfer((uint8_t)(address >> 16));
W25Q64_Transfer((uint8_t)(address >> 8));
W25Q64_Transfer((uint8_t)address);
for (index = 0u; index < length; index++)
{
W25Q64_Transfer(data[index]);
}
W25Q64_Deselect();
return W25Q64_WaitReady();
}
uint8_t W25Q64_ClearUpgradeRequest(void)
{
uint8_t cleared = 0u;
return W25Q64_PageProgram(OTA_METADATA_ADDRESS +
METADATA_UPGRADE_REQUEST_OFFSET,
&cleared, 1u);
}
uint8_t W25Q64_WriteUpgradeResult(uint8_t result)
{
return W25Q64_PageProgram(OTA_METADATA_ADDRESS +
METADATA_UPGRADE_RESULT_OFFSET,
&result, 1u);
}