Initial commit: CCU621_M firmware project with BLE debug link support.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 17:28:36 +08:00
commit 9ceb218f80
1597 changed files with 724159 additions and 0 deletions
+550
View File
@@ -0,0 +1,550 @@
#include "exflash_spi4_gd25qxx.h"
#include "gd32h7xx_dma.h"
#include <stddef.h>
/* NOR Flash 标准指令 */
#define EXFLASH_CMD_WRITE (0x02u) /* Page Program */
#define EXFLASH_CMD_WREN (0x06u) /* Write Enable */
#define EXFLASH_CMD_READ (0x03u) /* Read Data */
#define EXFLASH_CMD_RDSR (0x05u) /* Read Status Register */
#define EXFLASH_CMD_RDID (0x9Fu) /* Read JEDEC ID */
#define EXFLASH_CMD_SE (0x20u) /* 4KB Sector Erase */
#define EXFLASH_CMD_BE (0xC7u) /* Bulk Erase */
#define EXFLASH_CMD_EN4B (0xB7u) /* Enter 4-byte address mode */
/* 状态寄存器位定义 */
#define EXFLASH_SR_WIP_MASK (0x01u) /* WIP=1 表示写/擦进行中 */
/* 读操作发送的占位字节 */
#define EXFLASH_DUMMY_BYTE (0xA5u)
#define EXFLASH_DMA_PERIPH (DMA0)
#define EXFLASH_DMA_RX_CH (DMA_CH1)
#define EXFLASH_DMA_TX_CH (DMA_CH2)
#define EXFLASH_DMA_WAIT_MAX_LOOP (0x00FFFFFFu)
/* Cortex-M7 D-Cache line size = 32 bytes */
#define EXFLASH_DCACHE_LINE_SIZE (32u)
/* DMA 中断置位标志:1=该通道传输完成 */
static volatile uint8_t s_spi4_dma_rx_done = 0u;
static volatile uint8_t s_spi4_dma_tx_done = 0u;
/* 读阶段 TX 使用的固定哑字节;DMA 配置为 memory_inc disable */
static uint8_t s_spi4_dma_dummy_tx = EXFLASH_DUMMY_BYTE;
/* 写阶段 RX 丢弃字节;DMA 配置为 memory_inc disable */
static uint8_t s_spi4_dma_dummy_rx = 0u;
static uint32_t cache_addr_down_align(uint32_t addr, uint32_t align)
{
return addr & ~(align - 1u);
}
static uint32_t cache_len_up_align(uint32_t addr, uint32_t len, uint32_t align)
{
uint32_t end = addr + len;
uint32_t end_aligned = (end + (align - 1u)) & ~(align - 1u);
return end_aligned - cache_addr_down_align(addr, align);
}
static void dcache_clean_by_addr(uint32_t addr, uint32_t len)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t a = cache_addr_down_align(addr, EXFLASH_DCACHE_LINE_SIZE);
uint32_t l = cache_len_up_align(addr, len, EXFLASH_DCACHE_LINE_SIZE);
SCB_CleanDCache_by_Addr((uint32_t *)a, (int32_t)l);
#else
(void)addr;
(void)len;
#endif
}
static void dcache_invalidate_by_addr(uint32_t addr, uint32_t len)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t a = cache_addr_down_align(addr, EXFLASH_DCACHE_LINE_SIZE);
uint32_t l = cache_len_up_align(addr, len, EXFLASH_DCACHE_LINE_SIZE);
SCB_InvalidateDCache_by_Addr((uint32_t *)a, (int32_t)l);
#else
(void)addr;
(void)len;
#endif
}
/**
* @brief 发送 32bit 地址(A31~A0)。
* @param addr 线性地址。
*/
static void spi_flash_send_addr(uint32_t addr)
{
spi_flash_send_byte((uint8_t)((addr >> 24) & 0xFFu));
spi_flash_send_byte((uint8_t)((addr >> 16) & 0xFFu));
spi_flash_send_byte((uint8_t)((addr >> 8) & 0xFFu));
spi_flash_send_byte((uint8_t)(addr & 0xFFu));
}
/**
* @brief 进入 4-byte 地址模式(支持 32MB 地址空间访问)。
*/
static void spi_flash_enter_4byte_addr_mode(void)
{
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_EN4B);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 等待 DMA 收发双通道完成。
* @return 1 成功,0 超时。
*/
static uint8_t spi4_dma_wait_done(void)
{
uint32_t timeout = EXFLASH_DMA_WAIT_MAX_LOOP;
while (timeout-- > 0u) {
if ((s_spi4_dma_rx_done == 1u) && (s_spi4_dma_tx_done == 1u)) {
return 1u;
}
}
return 0u;
}
/**
* @brief 启动 SPI4 DMA 全双工传输(同时配置 RX/TX)。
* @param tx_buf TX 源地址。
* @param tx_inc TX 地址是否自增。
* @param rx_buf RX 目标地址。
* @param rx_inc RX 地址是否自增。
* @param length 传输字节数。
* @return 1 启动并完成成功,0 超时/失败。
*/
static uint8_t spi4_dma_transfer(uint8_t *tx_buf,
uint32_t tx_inc,
uint8_t *rx_buf,
uint32_t rx_inc,
uint16_t length)
{
dma_single_data_parameter_struct dma_init_struct;
if ((tx_buf == NULL) || (rx_buf == NULL) || (length == 0u)) {
return 0u;
}
s_spi4_dma_rx_done = 0u;
s_spi4_dma_tx_done = 0u;
dma_deinit(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_deinit(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
dma_single_data_para_struct_init(&dma_init_struct);
/* DMA 读 TX 前清 cacheDMA 写 RX 后需要 invalidate cache */
if (tx_inc == DMA_MEMORY_INCREASE_ENABLE) {
dcache_clean_by_addr((uint32_t)tx_buf, (uint32_t)length);
}
if (rx_inc == DMA_MEMORY_INCREASE_ENABLE) {
dcache_invalidate_by_addr((uint32_t)rx_buf, (uint32_t)length);
}
/* RX: SPI4_RDATA -> memory */
dma_init_struct.request = DMA_REQUEST_SPI4_RX;
dma_init_struct.periph_addr = (uint32_t)&SPI_RDATA(SPI4);
dma_init_struct.periph_inc = DMA_MEMORY_INCREASE_DISABLE;
dma_init_struct.memory0_addr = (uint32_t)rx_buf;
dma_init_struct.memory_inc = rx_inc;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init_struct.number = length;
dma_single_data_mode_init(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, &dma_init_struct);
dma_interrupt_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FTF);
/* TX: memory -> SPI4_TDATA */
dma_init_struct.request = DMA_REQUEST_SPI4_TX;
dma_init_struct.periph_addr = (uint32_t)&SPI_TDATA(SPI4);
dma_init_struct.periph_inc = DMA_MEMORY_INCREASE_DISABLE;
dma_init_struct.memory0_addr = (uint32_t)tx_buf;
dma_init_struct.memory_inc = tx_inc;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init_struct.number = length;
dma_single_data_mode_init(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, &dma_init_struct);
dma_interrupt_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FTF);
spi_dma_enable(SPI4, SPI_DMA_RECEIVE);
spi_dma_enable(SPI4, SPI_DMA_TRANSMIT);
/* 显式启动一次主机传输,保证时钟输出 */
spi_master_transfer_start(SPI4, SPI_TRANS_START);
dma_channel_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_channel_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
if (spi4_dma_wait_done() == 0u) {
spi_dma_disable(SPI4, SPI_DMA_RECEIVE);
spi_dma_disable(SPI4, SPI_DMA_TRANSMIT);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
return 0u;
}
spi_dma_disable(SPI4, SPI_DMA_RECEIVE);
spi_dma_disable(SPI4, SPI_DMA_TRANSMIT);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
/* DMA 写完 RX 后,CPU 读取前再次 invalidate,避免 cache 仍是旧数据 */
if (rx_inc == DMA_MEMORY_INCREASE_ENABLE) {
dcache_invalidate_by_addr((uint32_t)rx_buf, (uint32_t)length);
}
return 1u;
}
/**
* @brief 初始化 SPI4 与外部 Flash 片选 GPIO。
* @details
* - CS: PJ9(软件控制)
* - SCK: PK0MOSI: PJ10MISO: PJ11
* - SPI Mode08bitMSB first
*/
void spi_flash_init(void)
{
spi_parameter_struct spi_init_struct;
/* 1) 时钟使能 */
rcu_periph_clock_enable(RCU_GPIOJ);
rcu_periph_clock_enable(RCU_GPIOK);
rcu_periph_clock_enable(RCU_SPI4);
rcu_periph_clock_enable(RCU_DMA0);
rcu_periph_clock_enable(RCU_DMAMUX);
rcu_spi_clock_config(IDX_SPI4, RCU_SPISRC_APB2);
/* DMA 中断配置放在 DMA 时钟就绪后,避免早期访问 DMA 寄存器引发异常 */
nvic_irq_enable(DMA0_Channel1_IRQn, 4U, 0U);
nvic_irq_enable(DMA0_Channel2_IRQn, 4U, 0U);
/* 2) 片选脚初始化:默认拉高(未选中) */
gpio_mode_set(GPIOJ, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_9);
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_9);
SPI_FLASH_CS_HIGH();
/* 3) SPI4 复用脚初始化 */
gpio_af_set(GPIOK, GPIO_AF_5, GPIO_PIN_0); /* SCK */
gpio_af_set(GPIOJ, GPIO_AF_5, GPIO_PIN_10 | GPIO_PIN_11); /* MOSI/MISO */
gpio_mode_set(GPIOK, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_output_options_set(GPIOK, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_0);
gpio_mode_set(GPIOJ, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_11);
gpio_mode_set(GPIOJ, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10);
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_10);
/* 4) SPI4 参数初始化 */
spi_i2s_deinit(SPI4);
spi_struct_para_init(&spi_init_struct);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; /* 全双工 */
spi_init_struct.device_mode = SPI_MASTER; /* 主机 */
spi_init_struct.data_size = SPI_DATASIZE_8BIT; /* 8bit 帧 */
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; /* Mode 0 */
spi_init_struct.nss = SPI_NSS_SOFT; /* 软件 NSS */
spi_init_struct.prescale = SPI_PSC_32; /* 分频,保证稳定 */
spi_init_struct.endian = SPI_ENDIAN_MSB; /* 高位先发 */
spi_init(SPI4, &spi_init_struct);
/* 5) 使能 SPI 外设 */
spi_byte_access_enable(SPI4);
spi_nss_output_enable(SPI4);
spi_enable(SPI4);
/* 清理可能残留的 DMA 中断完成标志,防止首次传输误触发 */
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FLAG_FTF);
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FLAG_FTF);
/* 启用 4-byte 地址模式,支持访问 0x00000000~0x01FFFFFF32MB */
spi_flash_enter_4byte_addr_mode();
}
/**
* @brief 擦除指定地址所在扇区(4KB)。
* @param sector_addr 32bit 扇区地址(建议 4KB 对齐)。
*/
void spi_flash_sector_erase(uint32_t sector_addr)
{
spi_flash_write_enable();
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_SE);
spi_flash_send_addr(sector_addr);
SPI_FLASH_CS_HIGH();
spi_flash_wait_for_write_end();
}
/**
* @brief 全片擦除。
* @warning 全片擦除耗时很长,仅在必要时调用。
*/
void spi_flash_bulk_erase(void)
{
spi_flash_write_enable();
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_BE);
SPI_FLASH_CS_HIGH();
spi_flash_wait_for_write_end();
}
/**
* @brief 页内编程(不跨页)。
* @param pbuffer 写入源缓冲区。
* @param write_addr 写起始地址。
* @param num_byte_to_write 页内写入字节数(建议 <=256 且不跨页)。
*/
void spi_flash_page_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
if ((pbuffer == NULL) || (num_byte_to_write == 0u)) {
return;
}
spi_flash_write_enable();
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_WRITE);
spi_flash_send_addr(write_addr);
(void)spi4_dma_transfer(pbuffer,
DMA_MEMORY_INCREASE_ENABLE,
&s_spi4_dma_dummy_rx,
DMA_MEMORY_INCREASE_DISABLE,
num_byte_to_write);
SPI_FLASH_CS_HIGH();
spi_flash_wait_for_write_end();
}
/**
* @brief 连续写(自动按页拆分)。
* @param pbuffer 写入源缓冲区。
* @param write_addr 写起始地址。
* @param num_byte_to_write 写入总字节数。
* @details
* 原始实现将分页计数变量定义为 uint8_t,在数据量 >255 页时会溢出;
* 此处统一使用 uint32_t,避免分页计算错误。
*/
void spi_flash_buffer_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
uint32_t addr_offset;
uint32_t first_page_space;
uint32_t remain;
uint32_t page_bytes;
if ((pbuffer == NULL) || (num_byte_to_write == 0u)) {
return;
}
addr_offset = write_addr % SPI_FLASH_PAGE_SIZE;
first_page_space = SPI_FLASH_PAGE_SIZE - addr_offset;
remain = num_byte_to_write;
/* 若首地址非页对齐,先补齐当前页 */
if ((addr_offset != 0u) && (remain != 0u)) {
page_bytes = (remain < first_page_space) ? remain : first_page_space;
spi_flash_page_write(pbuffer, write_addr, (uint16_t)page_bytes);
write_addr += page_bytes;
pbuffer += page_bytes;
remain -= page_bytes;
}
/* 整页写 */
while (remain >= SPI_FLASH_PAGE_SIZE) {
spi_flash_page_write(pbuffer, write_addr, SPI_FLASH_PAGE_SIZE);
write_addr += SPI_FLASH_PAGE_SIZE;
pbuffer += SPI_FLASH_PAGE_SIZE;
remain -= SPI_FLASH_PAGE_SIZE;
}
/* 尾包写 */
if (remain > 0u) {
spi_flash_page_write(pbuffer, write_addr, (uint16_t)remain);
}
}
/**
* @brief 连续读取数据。
* @param pbuffer 读出目标缓冲区。
* @param read_addr 读取起始地址。
* @param num_byte_to_read 读取字节数。
*/
void spi_flash_buffer_read(uint8_t *pbuffer, uint32_t read_addr, uint16_t num_byte_to_read)
{
if ((pbuffer == NULL) || (num_byte_to_read == 0u)) {
return;
}
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_READ);
spi_flash_send_addr(read_addr);
(void)spi4_dma_transfer(&s_spi4_dma_dummy_tx,
DMA_MEMORY_INCREASE_DISABLE,
pbuffer,
DMA_MEMORY_INCREASE_ENABLE,
num_byte_to_read);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 读取 JEDEC ID(三字节)。
* @return 24bit IDManufacturer[23:16] | MemoryType[15:8] | Capacity[7:0]。
*/
uint32_t spi_flash_read_id(void)
{
uint32_t id0;
uint32_t id1;
uint32_t id2;
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_RDID);
id0 = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
id1 = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
id2 = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
SPI_FLASH_CS_HIGH();
return (id0 << 16) | (id1 << 8) | id2;
}
/**
* @brief 发起连续读序列(保持片选有效)。
* @param read_addr 读取起始地址。
*/
void spi_flash_start_read_sequence(uint32_t read_addr)
{
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_READ);
spi_flash_send_addr(read_addr);
}
/**
* @brief 在连续读序列中读取 1 字节。
* @return 读到的数据字节。
*/
uint8_t spi_flash_read_byte(void)
{
return spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
}
/**
* @brief SPI4 全双工收发一个字节。
* @param byte 发送字节。
* @return 同步接收字节。
*/
uint8_t spi_flash_send_byte(uint8_t byte)
{
uint32_t timeout = 0x00FFFFFFu;
/* GD32H7 SPI 主机在部分场景需要显式启动传输,否则时钟不输出,RP 永远不置位 */
spi_master_transfer_start(SPI4, SPI_TRANS_START);
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TP)) && (timeout-- > 0u)) {
/* 等待发送缓冲区可写 */
}
if (timeout == 0u) {
return 0xFFu;
}
spi_i2s_data_transmit(SPI4, byte);
timeout = 0x00FFFFFFu;
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RP)) && (timeout-- > 0u)) {
/* 等待接收缓冲区有数据 */
}
if (timeout == 0u) {
return 0xFFu;
}
return (uint8_t)spi_i2s_data_receive(SPI4);
}
/**
* @brief SPI4 全双工收发半字。
* @param half_word 发送半字。
* @return 同步接收半字。
*/
uint16_t spi_flash_send_halfword(uint16_t half_word)
{
uint32_t timeout = 0x00FFFFFFu;
spi_master_transfer_start(SPI4, SPI_TRANS_START);
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TP)) && (timeout-- > 0u)) {
/* 等待发送缓冲区可写 */
}
if (timeout == 0u) {
return 0xFFFFu;
}
spi_i2s_data_transmit(SPI4, half_word);
timeout = 0x00FFFFFFu;
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RP)) && (timeout-- > 0u)) {
/* 等待接收缓冲区有数据 */
}
if (timeout == 0u) {
return 0xFFFFu;
}
return (uint16_t)spi_i2s_data_receive(SPI4);
}
/**
* @brief 发送 Write Enable 指令(WREN)。
*/
void spi_flash_write_enable(void)
{
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_WREN);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 等待写/擦操作结束(轮询 SR.WIP)。
*/
void spi_flash_wait_for_write_end(void)
{
uint8_t flash_status;
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_RDSR);
do {
flash_status = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
} while ((flash_status & EXFLASH_SR_WIP_MASK) != 0u);
SPI_FLASH_CS_HIGH();
}
/**
* @brief DMA0 Channel1 IRQSPI4 RX 完成中断。
*/
void DMA0_Channel1_IRQHandler(void)
{
if (SET == dma_interrupt_flag_get(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FLAG_FTF)) {
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FLAG_FTF);
s_spi4_dma_rx_done = 1u;
}
}
/**
* @brief DMA0 Channel2 IRQSPI4 TX 完成中断。
*/
void DMA0_Channel2_IRQHandler(void)
{
if (SET == dma_interrupt_flag_get(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FLAG_FTF)) {
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FLAG_FTF);
s_spi4_dma_tx_done = 1u;
}
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef EXFLASH_SPI4_GD25QXX_H
#define EXFLASH_SPI4_GD25QXX_H
#include "gd32h7xx.h"
/* GD25QXX 页大小:每页 256 字节 */
#define SPI_FLASH_PAGE_SIZE (0x100u)
/* 片选脚由 PJ9 软件控制 */
#define SPI_FLASH_CS_LOW() gpio_bit_reset(GPIOJ, GPIO_PIN_9)
#define SPI_FLASH_CS_HIGH() gpio_bit_set(GPIOJ, GPIO_PIN_9)
/* 初始化 SPI4 与片选 GPIO */
void spi_flash_init(void);
/* 擦除 4KB 扇区 */
void spi_flash_sector_erase(uint32_t sector_addr);
/* 全片擦除(耗时较长) */
void spi_flash_bulk_erase(void);
/* 页内写(不处理跨页) */
void spi_flash_page_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write);
/* 连续写(自动处理跨页) */
void spi_flash_buffer_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write);
/* 连续读 */
void spi_flash_buffer_read(uint8_t *pbuffer, uint32_t read_addr, uint16_t num_byte_to_read);
/* 读取 JEDEC ID */
uint32_t spi_flash_read_id(void);
/* 发起连续读命令序列(随后可重复 spi_flash_read_byte */
void spi_flash_start_read_sequence(uint32_t read_addr);
/* 连续读阶段读取 1 字节 */
uint8_t spi_flash_read_byte(void);
/* SPI 收发 1 字节 */
uint8_t spi_flash_send_byte(uint8_t byte);
/* SPI 收发 1 半字 */
uint16_t spi_flash_send_halfword(uint16_t half_word);
/* 置写使能(WREN */
void spi_flash_write_enable(void);
/* 轮询 WIP 位,等待写/擦完成 */
void spi_flash_wait_for_write_end(void);
#endif
@@ -0,0 +1,75 @@
# externalflash 模块说明(SPI4 外部 Flash
> 本文档描述 `CCU621_M/BSP/externalflash` 模块的职责、接口、初始化位置以及 DMA + D-Cache 场景下的使用注意事项。
[返回主说明](./../../README.md)
## 1. 模块组成
- **底层驱动(SPI4 + GD25QXX**
- `exflash_spi4_gd25qxx.c`
- `exflash_spi4_gd25qxx.h`
- **统一对外接口(业务层推荐使用)**
- `flash_external_data.c`
- `flash_external_data.h`
## 2. 初始化入口
- 系统硬件初始化入口:`BSP/sys_drv_init.c`
- `v_sys_hardware_init()` 中调用 `v_flash_dataflash_init()`,完成 SPI4/GPIO/DMA 初始化。
> 约束:业务层/测试层不要重复 init,避免初始化时序与资源重复配置。
## 3. 推荐对外接口(统一接口)
业务层请优先使用以下接口(位于 `flash_external_data.c/.h`):
- `U8_T v_flash_dataflash_init(void)`
- `U8_T s32_flash_dataflash_write(U32_T addr, U8_T *buf, U32_T len)`
- `U8_T s32_flash_dataflash_read(U32_T addr, U8_T *buf, U32_T len)`
- `U8_T s32_flash_dataflash_erase_sector(U32_T addr)`
- 兼容旧命名:`unsigned int SPI_flsh_ReadID(void)`
### 3.1 地址规划(摘自 `flash_external_data.h`
- 全空间(4-byte 地址模式):`0x00000000 ~ 0x01FFFFFF`32MB
- OTA 区:`0x00000000 ~ 0x001FFFFF`
- 参数区:`0x210000 ~ ...`
- 业务记录区:`0x220000 ~ ...`
- FatFs 映射区:`0xA00000 ~ 0x01FFFFFF`
> 说明:以上为“线性地址”规划,调用统一接口时直接传该地址即可。
## 4. DMA + D-Cache 使用注意事项(非常重要)
当前 SPI4 外部 Flash 读写采用 **DMA** 方式提升性能;在 Cortex-M7 **开启 D-Cache** 的情况下,DMA 与 CPU 会出现数据一致性问题:
- DMA 写入内存后,CPU 可能仍读取到旧 cache 数据;
- DMA 从内存读取 TX 数据时,可能读不到 CPU 刚写入但未 clean 的 cache 内容。
### 4.1 当前工程的处理策略
- 底层 SPI4 DMA 传输在驱动内部做了 D-Cache clean/invalidate(按 32B cache line 对齐)。
- 统一接口 `s32_flash_dataflash_read/write` 额外做了一层保护:
- 如果业务传入的 `buf` **不是 32B 对齐**,内部会自动使用 **对齐 bounce buffer** 完成 DMA 传输,再 `memcpy` 到业务缓冲。
### 4.2 业务层建议
- 业务层可直接使用 `s32_flash_dataflash_read/write`,无需自行对齐或自行做 cache 维护。
- 若业务存在高频读写,可考虑在模块内复用一块“32B 对齐的工作缓冲”,减少 malloc/free 次数(后续可优化)。
## 5. 测试入口
- `app/app_init/app_test.c`
- 周期执行 flash 擦写读回校验
- 输出擦除/读写耗时与速度统计
- 新增 `u8_app_test_flash_32m_stride_rw()`:以 1MB 步长扫描 32MB 地址空间做擦写读回校验
## 6. 常见问题排查
- **读回校验失败**
- 首先确认是否走统一接口(避免绕过 bounce buffer
- 其次检查 D-Cache 是否开启、MPU 区域属性是否覆盖了 DMA buffer
- **卡死在 SPI_FLAG_RP 等待**
- 通常表示 SPI 主机传输未启动或时钟未输出,需要检查 `spi_master_transfer_start` 是否调用、片选是否正确、外设时钟是否使能
+149
View File
@@ -0,0 +1,149 @@
#include "flash_external_data.h"
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "app_init/app_init.h"
#include "exflash_spi4_gd25qxx.h"
#include "FreeRTOS.h"
#include "portable.h"
#define FLASH_IO_ALIGN (32u)
#define FLASH_IO_GUARD_SIZE (32u)
static uint8_t *align_up_u8(uint8_t *p, uint32_t align)
{
uintptr_t v = (uintptr_t)p;
v = (v + (align - 1U)) & ~(uintptr_t)(align - 1U);
return (uint8_t *)v;
}
static uint8_t need_bounce_buf(const void *p)
{
return (((uintptr_t)p) & (FLASH_IO_ALIGN - 1U)) ? 1U : 0U;
}
/**
* @brief 外部 Flash 初始化(统一接口)。
* @return 0 成功。
*/
U8_T v_flash_dataflash_init(void)
{
spi_flash_init();
printf("[EXFLASH] JEDEC ID: 0x%06X\r\n", (unsigned int)spi_flash_read_id());
return 0U;
}
/**
* @brief 外部 Flash 写接口(统一接口)。
* @param addr 24bit 线性地址。
* @param buf 写入数据缓冲区。
* @param len 写入长度(字节)。
* @return 0 成功,1 参数错误,2 长度超驱动接口上限。
*/
U8_T s32_flash_dataflash_write(U32_T addr, U8_T *buf, U32_T len)
{
uint8_t *raw;
uint8_t *aligned;
uint32_t alloc_size;
if ((buf == NULL) || (len == 0U)) {
return 1U;
}
/* 底层驱动 write 接口长度参数为 uint16_t。 */
if (len > 0xFFFFu) {
return 2U;
}
/* 若用户缓冲未按 32B 对齐,使用 bounce buffer,避免 DMA/DCache 维护影响堆头或触发一致性问题 */
if (need_bounce_buf(buf) == 0U) {
spi_flash_buffer_write(buf, addr, (U16_T)len);
return 0U;
}
alloc_size = len + FLASH_IO_ALIGN + FLASH_IO_GUARD_SIZE;
raw = (uint8_t *)pvPortMalloc(alloc_size);
if (raw == NULL) {
return 3U;
}
aligned = align_up_u8(raw, FLASH_IO_ALIGN);
memcpy(aligned, buf, len);
spi_flash_buffer_write(aligned, addr, (U16_T)len);
vPortFree(raw);
return 0U;
}
/**
* @brief 外部 Flash 读接口(统一接口)。
* @param addr 24bit 线性地址。
* @param buf 读出数据缓冲区。
* @param len 读取长度(字节)。
* @return 0 成功,1 参数错误,2 长度超驱动接口上限。
*/
U8_T s32_flash_dataflash_read(U32_T addr, U8_T *buf, U32_T len)
{
uint8_t *raw;
uint8_t *aligned;
uint32_t alloc_size;
if ((buf == NULL) || (len == 0U)) {
return 1U;
}
if (len > 0xFFFFu) {
return 2U;
}
if (need_bounce_buf(buf) == 0U) {
spi_flash_buffer_read(buf, addr, (U16_T)len);
return 0U;
}
alloc_size = len + FLASH_IO_ALIGN + FLASH_IO_GUARD_SIZE;
raw = (uint8_t *)pvPortMalloc(alloc_size);
if (raw == NULL) {
return 3U;
}
aligned = align_up_u8(raw, FLASH_IO_ALIGN);
spi_flash_buffer_read(aligned, addr, (U16_T)len);
memcpy(buf, aligned, len);
vPortFree(raw);
return 0U;
}
/**
* @brief 擦除指定地址所在扇区。
* @param addr 扇区起始地址(建议 4KB 对齐)。
* @return 0 成功。
*/
U8_T s32_flash_dataflash_erase_sector(U32_T addr)
{
spi_flash_sector_erase(addr);
return 0U;
}
/**
* @brief 读取外部 Flash JEDEC ID(兼容旧命名)。
*/
unsigned int SPI_flsh_ReadID(void)
{
return (unsigned int)spi_flash_read_id();
}
/**
* @brief 擦除 FatFs 映射区全部扇区。
*/
void v_flash_dataflash_fatfs_esaes_sectot(void)
{
U32_T i;
U32_T sector_addr;
for (i = 0U; i < FATFS_TOTAL_SECTORS; i++) {
sector_addr = FATFS_PHYSICAL_START_ADDR + (i * SPI_SECTOR_SIZE);
printf("SPI flash erase sector: 0x%08X\r\n", (unsigned int)sector_addr);
spi_flash_sector_erase(sector_addr);
mSleep(1U);
}
}
+100
View File
@@ -0,0 +1,100 @@
#ifndef FLASH_EXTERNAL_DATA_H
#define FLASH_EXTERNAL_DATA_H
#include "publicdata/type.h"
#include "meter_calculate/meter_calculate_impl.h"
/* ============================================================================
* 外部 Flash 容量与地址说明
*
* 当前上电识别 JEDEC ID: 0xC84019
* - Manufacturer: 0xC8GigaDevice
* - Capacity code: 0x19(典型为 256Mbit = 32MB
*
* 当前底层驱动已启用 4-byte 地址模式(4 字节地址):
* - 可直接访问线性地址范围:0x00000000 ~ 0x01FFFFFF32MB
* - 总容量:0x02000000 bytes
* ============================================================================ */
#define EXFLASH_ADDR_MIN (0x00000000u)
#define EXFLASH_ADDR_MAX (0x01FFFFFFu)
#define EXFLASH_TOTAL_SIZE (0x02000000u)
/* SPI NOR Flash 物理扇区大小(字节) */
#define SPI_SECTOR_SIZE (4096u)
#ifndef RECORD_ZERO_SAVE
#define RECORD_ZERO_SAVE (0x7FFFu)
#endif
#ifndef RECORD_HEADER
#define RECORD_HEADER (0x55u)
#endif
/* OTA 区(升级保留区) */
#define UPGRADE_START_ADDR (0x00000000u)
#define UPGRADE_END_ADDR (0x001FFFFFu)
#define UPGRADE_TOTAL_SIZE (UPGRADE_END_ADDR - UPGRADE_START_ADDR + 1u)
/* 配置区 */
#define DATAFLASH_VAR_CFG_DATA1_ADDR (0x210000u)
#define DATAFLASH_VAR_CFG_DATA2_ADDR (0x211000u)
#define DATAFLASH_VAR_CFG_DATA3_ADDR (0x212000u)
#define DATAFLASH_FIX_CFG_DATA1_ADDR (0x213000u)
#define DATAFLASH_FIX_CFG_DATA2_ADDR (0x214000u)
#define DATAFLASH_FIX_CFG_DATA3_ADDR (0x215000u)
#define DATAFLASH_OCPP_CFG_DATA_ADDR (0x216000u)
/* 故障、卡号、历史记录区 */
#define DATAFLASH_FAULT_ADDR (0x220000u)
#define DATAFLASH_FAULT_SECTOR_CNT (2u)
#define DATAFLASH_FAULT_END_ADDR (DATAFLASH_FAULT_ADDR + (DATAFLASH_FAULT_SECTOR_CNT * SPI_SECTOR_SIZE) - 1u)
#define DATAFLASH_OCPP_MV_OFFLINE_ADDR (0x2C0000u)
#define DATAFLASH_OCPP_MV_OFFLINE_HALF_SIZE (0x8000u)
#define DATAFLASH_OCPP_MV_OFFLINE_SECTOR_CNT (16u)
#define DATAFLASH_OCPP_MV_OFFLINE_TOTAL_SIZE (DATAFLASH_OCPP_MV_OFFLINE_HALF_SIZE * 2u)
#define DATAFLASH_CARD_ADDR (0x222000u)
#define DATAFLASH_CARD_SECTOR_CNT (7u)
#define DATAFLASH_CARD_END_ADDR (DATAFLASH_CARD_ADDR + (DATAFLASH_CARD_SECTOR_CNT * SPI_SECTOR_SIZE) - 1u)
#define DATAFLASH_HIS_RECORD_ADDR (0x230000u)
#define DATAFLASH_HIS_RECORD_END_ADDR (0x2BFFFFu)
#define DATAFLASH_HIS_RECORD_SIZE (DATAFLASH_HIS_RECORD_END_ADDR - DATAFLASH_HIS_RECORD_ADDR + 1u)
#define DATAFLASH_HIS_RECORD_SECTOR_CNT (DATAFLASH_HIS_RECORD_SIZE / SPI_SECTOR_SIZE)
/* Charge-order storage layout (compatible with migrated flash_file_mgr module) */
#ifndef CHG_ORDER_RECORD_SIZE
#define CHG_ORDER_RECORD_SIZE (sizeof(S_LOG_DATA))
#endif
#ifndef CHG_ORDER_FULL_RECORD_SIZE
#define CHG_ORDER_FULL_RECORD_SIZE (1u + CHG_ORDER_RECORD_SIZE + 1u)
#endif
#ifndef CHG_ORDER_PER_SECTOR_CNT
#define CHG_ORDER_PER_SECTOR_CNT (SPI_SECTOR_SIZE / CHG_ORDER_FULL_RECORD_SIZE)
#endif
#ifndef CHG_ORDER_MAX_CNT
#define CHG_ORDER_MAX_CNT (200u)
#endif
#ifndef TEMP_CHG_RECORD_SIZE
#define TEMP_CHG_RECORD_SIZE (sizeof(S_LOG_DATA))
#endif
/* FatFs 映射区 */
#define FATFS_PHYSICAL_START_ADDR (0xA00000u)
#define FATFS_PHYSICAL_STOP_ADDR (EXFLASH_ADDR_MAX)
#define FATFS_PARTITION_SIZE (FATFS_PHYSICAL_STOP_ADDR - FATFS_PHYSICAL_START_ADDR + 1u)
#define FATFS_TOTAL_SECTORS (FATFS_PARTITION_SIZE / SPI_SECTOR_SIZE)
/* 统一对外接口:0=成功,非0=失败 */
U8_T v_flash_dataflash_init(void);
U8_T s32_flash_dataflash_write(U32_T addr, U8_T *buf, U32_T len);
U8_T s32_flash_dataflash_read(U32_T addr, U8_T *buf, U32_T len);
U8_T s32_flash_dataflash_erase_sector(U32_T addr);
/* 兼容旧接口命名 */
unsigned int SPI_flsh_ReadID(void);
void v_flash_dataflash_fatfs_esaes_sectot(void);
#endif
+189
View File
@@ -0,0 +1,189 @@
/**
* @file flash_upgrade.c
* @brief 外置 Flash 固件升级区读写(与 CCU601E_D / CCU621_IAP 布局一致)
*/
#include "flash_upgrade.h"
#include "app_init/app_init.h"
#include "wdt_task/wdt_task.h"
#include "exflash_spi4_gd25qxx.h"
#include <stdio.h>
#include <string.h>
extern U8_T s32_flash_dataflash_write(U32_T addr, U8_T *buf, U32_T len);
extern U8_T s32_flash_dataflash_read(U32_T addr, U8_T *buf, U32_T len);
extern U8_T s32_flash_dataflash_erase_sector(U32_T addr);
/* 0: 按包擦扇区;1: upgrade_erase_program_area 已整体擦除 */
static int g_upgrade_program_pre_erased = 0;
static uint8_t s_upgrade_header_buf[sizeof(upgrade_header_t)]
__attribute__((aligned(32)));
__attribute__((weak)) void upgrade_erase_yield(void)
{
}
int upgrade_init(void)
{
upgrade_header_t header;
if (upgrade_read_header(&header) == 0) {
if (header.upgrade_flag == UPGRADE_FLAG_NEED_UP) {
return 1;
}
if (header.upgrade_flag == UPGRADE_FLAG_DONE) {
return 2;
}
}
g_upgrade_program_pre_erased = 0;
return 0;
}
int upgrade_write_header(const upgrade_header_t *header)
{
if (header == NULL) {
return -1;
}
spi_flash_wait_for_write_end();
v_wdt_dog_toggle();
s32_flash_dataflash_erase_sector(HEADER_SECTOR_ADDR);
v_wdt_dog_toggle();
memcpy(s_upgrade_header_buf, header, sizeof(upgrade_header_t));
if (s32_flash_dataflash_write(HEADER_SECTOR_ADDR, s_upgrade_header_buf,
sizeof(upgrade_header_t)) != 0U) {
printf("[FW_UPGRADE] header write failed\r\n");
return -1;
}
spi_flash_wait_for_write_end();
v_wdt_dog_toggle();
return 0;
}
int upgrade_read_header(upgrade_header_t *header)
{
if (header == NULL) {
return -1;
}
if (s32_flash_dataflash_read(HEADER_SECTOR_ADDR, (U8_T *)header, sizeof(upgrade_header_t)) != 0U) {
return -1;
}
return 0;
}
int upgrade_success_update_header(upgrade_header_t *header)
{
return upgrade_write_header(header);
}
int upgrade_write_packet(uint32_t packet_index, const uint8_t *data, uint32_t data_len)
{
uint32_t packet_addr;
uint32_t sector_start;
if ((data == NULL) || (data_len == 0U) || (data_len > PACKET_SIZE)) {
return -1;
}
packet_addr = PROGRAM_START_ADDR + (packet_index * PACKET_SIZE);
if (packet_addr + data_len > UPGRADE_END_ADDR) {
return -1;
}
if (!g_upgrade_program_pre_erased) {
sector_start = (packet_addr / SECTOR_SIZE) * SECTOR_SIZE;
if ((packet_index * PACKET_SIZE) % SECTOR_SIZE == 0U) {
s32_flash_dataflash_erase_sector(sector_start);
v_wdt_dog_toggle();
mSleep(1);
}
}
if (s32_flash_dataflash_write(packet_addr, (U8_T *)data, data_len) != 0U) {
return -1;
}
return 0;
}
int upgrade_read_packet(uint32_t packet_index, uint8_t *data, uint32_t *data_len)
{
uint32_t packet_addr;
uint32_t max_read_size;
if ((data == NULL) || (data_len == NULL)) {
return -1;
}
packet_addr = PROGRAM_START_ADDR + (packet_index * PACKET_SIZE);
if (packet_addr > UPGRADE_END_ADDR) {
return -1;
}
if (s32_flash_dataflash_read(packet_addr, data, PACKET_SIZE) != 0U) {
return -1;
}
max_read_size = UPGRADE_END_ADDR - packet_addr + 1U;
if (max_read_size < PACKET_SIZE) {
*data_len = max_read_size;
} else {
*data_len = PACKET_SIZE;
}
return 0;
}
int upgrade_erase_program_area(void)
{
return upgrade_erase_program_area_for_size(PROGRAM_MAX_SIZE);
}
int upgrade_erase_program_area_for_size(uint32_t firmware_bytes)
{
uint32_t sector_addr;
uint32_t last_byte;
uint32_t last_sector;
if (firmware_bytes == 0U) {
return -1;
}
if (firmware_bytes > PROGRAM_MAX_SIZE) {
return -1;
}
last_byte = PROGRAM_START_ADDR + firmware_bytes - 1U;
if (last_byte > UPGRADE_END_ADDR) {
return -1;
}
last_sector = (last_byte / SECTOR_SIZE) * SECTOR_SIZE;
printf("[FW_UPGRADE] erase start size=%lu sectors=0x%08lX..0x%08lX\r\n",
(unsigned long)firmware_bytes,
(unsigned long)PROGRAM_START_ADDR,
(unsigned long)last_sector);
for (sector_addr = PROGRAM_START_ADDR; sector_addr <= last_sector; sector_addr += SECTOR_SIZE) {
s32_flash_dataflash_erase_sector(sector_addr);
v_wdt_dog_toggle();
mSleep(1);
upgrade_erase_yield();
if (((sector_addr - PROGRAM_START_ADDR) / SECTOR_SIZE) % 10U == 9U) {
printf("[FW_UPGRADE] erase progress at 0x%08lX\r\n",
(unsigned long)sector_addr);
}
}
printf("[FW_UPGRADE] erase done\r\n");
g_upgrade_program_pre_erased = 1;
return 0;
}
+59
View File
@@ -0,0 +1,59 @@
/**
* @file flash_upgrade.h
* @brief 固件升级兼容头文件(整合自 CCU601E_D BSP/spi_Flash/flash_upgrade.h
*
* CCU621_M 平台:升级相关常量、类型与接口与 CCU601E_D 保持一致。
*/
#ifndef __FLASH_UPGRADE_H__
#define __FLASH_UPGRADE_H__
#include <stdint.h>
#include "main.h"
#include "externalflash/flash_external_data.h"
/* 扇区大小 (4KB) */
#define SECTOR_SIZE 4096
/* 数据包大小 */
#define PACKET_SIZE (1024)
/* 头部信息扇区 (第一个扇区) */
#define HEADER_SECTOR_ADDR UPGRADE_START_ADDR
#define HEADER_SECTOR_SIZE SECTOR_SIZE
/* 程序数据起始地址 (第二个扇区开始) */
#define PROGRAM_START_ADDR (UPGRADE_START_ADDR + HEADER_SECTOR_SIZE)
#define PROGRAM_MAX_SIZE (UPGRADE_TOTAL_SIZE - HEADER_SECTOR_SIZE)
/* 升级标志定义 */
#define UPGRADE_FLAG_IDLE 0x00
#define UPGRADE_FLAG_NEED_UP 0x55AA
#define UPGRADE_FLAG_DONE 0xAA55
#define UPGRADE_FLAG_ERROR 0xEEFF
/* 升级头部信息结构 */
#pragma pack(push, 1)
typedef struct {
uint16_t upgrade_flag;
uint32_t file_size;
uint32_t packet_count;
uint32_t crc32;
uint32_t timestamp;
uint8_t version[16];
uint8_t reserved[32];
} upgrade_header_t;
#pragma pack(pop)
/* 函数声明 */
int upgrade_init(void);
int upgrade_read_header(upgrade_header_t *header);
int upgrade_write_packet(uint32_t packet_index, const uint8_t *data, uint32_t data_len);
int upgrade_read_packet(uint32_t packet_index, uint8_t *data, uint32_t *data_len);
int upgrade_success_update_header(upgrade_header_t *header);
int upgrade_erase_program_area(void);
int upgrade_erase_program_area_for_size(uint32_t firmware_bytes);
/** 擦除循环中让出 CPU,读 BLE/TCP 入站(默认 weak 空实现,tcp_server 覆盖) */
void upgrade_erase_yield(void);
#endif /* __FLASH_UPGRADE_H__ */