#include "exflash_spi4_gd25qxx.h" #include "gd32h7xx_dma.h" #include /* 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 前清 cache;DMA 写 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: PK0,MOSI: PJ10,MISO: PJ11 * - SPI Mode0,8bit,MSB 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~0x01FFFFFF(32MB) */ 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 ID:Manufacturer[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 IRQ:SPI4 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 IRQ:SPI4 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; } }