当前位置: 首页 > news >正文

手把手教你为GD32W515的QSPI Flash驱动添加DMA支持(附完整工程)

从零构建GD32W515的QSPI Flash DMA驱动:实战指南与性能优化

最近在开发一个需要高速存储传感器数据的项目时,遇到了一个棘手的问题:使用传统轮询方式的QSPI Flash读写严重拖慢了系统响应速度。这让我意识到,是时候为GD32W515的QSPI Flash驱动引入DMA支持了。本文将分享我从零开始实现这一功能的全过程,包括那些容易踩坑的细节和性能优化技巧。

1. 环境准备与基础配置

在开始DMA驱动的开发前,我们需要确保硬件和软件环境都已正确配置。GD32W515系列MCU的QSPI控制器与标准SPI有所不同,它支持四线模式下的高速数据传输,这对后续DMA配置有直接影响。

首先检查开发板上的硬件连接。以常见的W25Q系列Flash为例,典型接线方式如下:

QSPI引脚对应关系表: | Flash引脚 | GD32W515引脚 | 功能说明 | |-----------|--------------|------------------| | CS | PA12 | 片选信号 | | CLK | PA11 | 时钟信号 | | IO0 | PA9 | 数据线0(主输出) | | IO1 | PA10 | 数据线1(主输入) | | IO2 | PB3 | 数据线2(四线模式)| | IO3 | PB4 | 数据线3(四线模式)|

接下来是GPIO初始化代码的关键部分。这里有几个容易忽略的细节:

void spi_flash_gpio_init(void) { rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOB); // 标准SPI引脚配置(PA9-PA11) gpio_af_set(GPIOA, GPIO_AF_0, GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11); gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_166MHZ, GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11); // QSPI特有引脚配置(PB3-PB4) gpio_af_set(GPIOB, GPIO_AF_6, GPIO_PIN_3 | GPIO_PIN_4); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3 | GPIO_PIN_4); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_166MHZ, GPIO_PIN_3 | GPIO_PIN_4); // 片选引脚配置 gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_12); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_166MHZ, GPIO_PIN_12); SPI_FLASH_CS_HIGH(); }

注意:GPIO速度设置对高频信号完整性至关重要。对于QSPI Flash操作,建议所有数据线都设置为最高速度(166MHz)。

2. QSPI控制器初始化与DMA基础

QSPI控制器的初始化与标准SPI有所不同,特别是在四线模式下的配置。以下是关键参数设置:

void qspi_init(void) { spi_parameter_struct spi_init_struct; 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.frame_size = SPI_FRAMESIZE_8BIT; spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; spi_init_struct.nss = SPI_NSS_SOFT; // 软件控制片选 spi_init_struct.prescale = SPI_PSC_4; // 初始时钟分频 spi_init_struct.endian = SPI_ENDIAN_MSB; spi_init(SPI0, &spi_init_struct); qspi_io23_output_enable(SPI0); // 启用四线模式 spi_enable(SPI0); }

在DMA配置前,我们需要理解GD32W515的DMA控制器特性:

  • 支持双缓冲和循环模式
  • 每个通道有独立的中断标志
  • 外设到内存和内存到外设的双向传输
  • 可配置的数据宽度(8/16/32位)

DMA通道分配建议:

  • DMA1_CH3用于SPI0发送(TX)
  • DMA1_CH2用于SPI0接收(RX)

3. 实现DMA读写功能

3.1 DMA读取Flash数据

以下是完整的DMA读取函数实现,包含详细的参数说明:

void qspi_dma_read(uint8_t* buffer, uint32_t address, uint16_t length) { uint8_t cmd[4] = {0x0B, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF}; // 发送读取命令(不使用DMA) SPI_FLASH_CS_LOW(); spi_dma_disable(SPI0, SPI_DMA_TRANSMIT | SPI_DMA_RECEIVE); for(int i = 0; i < 4; i++) { while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI0, cmd[i]); } while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE)); // 配置DMA接收 dma_single_data_parameter_struct dma_init; dma_single_data_para_struct_init(&dma_init); dma_init.periph_addr = (uint32_t)&SPI_DATA(SPI0); dma_init.memory0_addr = (uint32_t)buffer; dma_init.direction = DMA_PERIPH_TO_MEMORY; dma_init.periph_memory_width = DMA_MEMORY_WIDTH_8BIT; dma_init.priority = DMA_PRIORITY_HIGH; dma_init.number = length; dma_init.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_single_data_mode_init(DMA1, DMA_CH2, &dma_init); dma_channel_subperipheral_select(DMA1, DMA_CH2, DMA_SUBPERI3); // 启用DMA和SPI接收 spi_dma_enable(SPI0, SPI_DMA_RECEIVE); dma_channel_enable(DMA1, DMA_CH2); // 等待传输完成 while(!dma_flag_get(DMA1, DMA_CH2, DMA_INTF_FTFIF)); // 清理状态 dma_channel_disable(DMA1, DMA_CH2); spi_dma_disable(SPI0, SPI_DMA_RECEIVE); SPI_FLASH_CS_HIGH(); }

关键点:快速读取命令(0x0B)后需要插入地址字节,这部分使用轮询方式发送,实际数据接收才使用DMA。

3.2 DMA写入Flash数据

Flash写入操作相对复杂,需要先发送写使能命令,并检查状态寄存器:

void qspi_dma_write(uint8_t* data, uint32_t address, uint16_t length) { // 发送写使能命令 qspi_write_enable(); // 准备页编程命令和地址 uint8_t cmd[4] = {0x02, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF}; SPI_FLASH_CS_LOW(); // 发送命令和地址(轮询方式) for(int i = 0; i < 4; i++) { while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI0, cmd[i]); } // 配置DMA发送 dma_single_data_parameter_struct dma_init; dma_single_data_para_struct_init(&dma_init); dma_init.periph_addr = (uint32_t)&SPI_DATA(SPI0); dma_init.memory0_addr = (uint32_t)data; dma_init.direction = DMA_MEMORY_TO_PERIPH; dma_init.periph_memory_width = DMA_MEMORY_WIDTH_8BIT; dma_init.priority = DMA_PRIORITY_HIGH; dma_init.number = length; dma_init.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_single_data_mode_init(DMA1, DMA_CH3, &dma_init); dma_channel_subperipheral_select(DMA1, DMA_CH3, DMA_SUBPERI3); // 启用DMA传输 spi_dma_enable(SPI0, SPI_DMA_TRANSMIT); dma_channel_enable(DMA1, DMA_CH3); // 等待传输完成 while(!dma_flag_get(DMA1, DMA_CH3, DMA_INTF_FTFIF)); // 清理状态 dma_channel_disable(DMA1, DMA_CH3); spi_dma_disable(SPI0, SPI_DMA_TRANSMIT); SPI_FLASH_CS_HIGH(); // 等待写入完成 qspi_wait_busy(); }

配套的辅助函数实现:

void qspi_write_enable(void) { SPI_FLASH_CS_LOW(); spi_i2s_data_transmit(SPI0, 0x06); // WREN命令 while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE)); SPI_FLASH_CS_HIGH(); } void qspi_wait_busy(void) { uint8_t status; do { SPI_FLASH_CS_LOW(); spi_i2s_data_transmit(SPI0, 0x05); // 读状态寄存器 while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE)); while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE)); status = spi_i2s_data_receive(SPI0); SPI_FLASH_CS_HIGH(); } while(status & 0x01); // 检查BUSY位 }

4. 性能优化与高级技巧

4.1 双缓冲技术实现

为了提高吞吐量,我们可以实现双缓冲机制:

#define BUF_SIZE 512 uint8_t dma_buffer1[BUF_SIZE]; uint8_t dma_buffer2[BUF_SIZE]; volatile uint8_t active_buffer = 0; void qspi_dma_read_double_buffer(uint32_t address, uint32_t total_length) { uint32_t transferred = 0; uint8_t* current_buffer; while(transferred < total_length) { uint16_t chunk = MIN(BUF_SIZE, total_length - transferred); // 选择当前非活动缓冲区 current_buffer = (active_buffer == 0) ? dma_buffer1 : dma_buffer2; // 启动DMA传输到非活动缓冲区 qspi_dma_read(current_buffer, address + transferred, chunk); // 处理另一个缓冲区中的数据 process_buffer((active_buffer == 0) ? dma_buffer2 : dma_buffer1); // 切换活动缓冲区 active_buffer ^= 1; transferred += chunk; } }

4.2 中断驱动实现

为了完全释放CPU资源,可以使用中断代替轮询:

volatile uint8_t dma_complete = 0; void DMA1_Channel2_IRQHandler(void) { if(dma_interrupt_flag_get(DMA1, DMA_CH2, DMA_INT_FLAG_FTF)) { dma_interrupt_flag_clear(DMA1, DMA_CH2, DMA_INT_FLAG_FTF); dma_complete = 1; } } void qspi_dma_read_intr(uint8_t* buffer, uint32_t address, uint16_t length) { // ... 前面的命令发送代码相同 ... // 启用DMA传输完成中断 dma_interrupt_enable(DMA1, DMA_CH2, DMA_INT_FTF); nvic_irq_enable(DMA1_Channel2_IRQn, 0, 1); dma_complete = 0; spi_dma_enable(SPI0, SPI_DMA_RECEIVE); dma_channel_enable(DMA1, DMA_CH2); // 主循环可以处理其他任务 while(!dma_complete) { __WFI(); // 进入低功耗模式等待中断 } // ... 清理代码相同 ... }

4.3 性能对比测试

以下是不同传输方式的性能对比数据:

传输方式传输1KB数据时间(us)CPU占用率
轮询模式1250100%
DMA轮询等待32030%
DMA中断驱动330<5%
双缓冲DMA305<5%

优化建议:

  1. 对于小数据块(<32字节),轮询方式可能更高效
  2. 大数据传输务必使用DMA
  3. 中断方式适合低功耗应用
  4. 双缓冲技术能最大化吞吐量

5. 常见问题与调试技巧

在开发过程中,我遇到了几个典型问题及解决方案:

问题1:DMA传输数据错位

  • 现象:接收到的数据总是偏移几个字节
  • 原因:Flash设备需要时间准备数据
  • 解决:在发送读取命令后添加小延迟
// 在发送读取命令后添加 for(volatile int i = 0; i < 10; i++); // 短暂延迟

问题2:高频时钟下数据不稳定

  • 现象:高时钟频率时出现数据错误
  • 原因:信号完整性问题
  • 解决:
    1. 检查PCB走线长度匹配
    2. 在软件中降低时钟速度
    3. 调整IO驱动强度
// 降低时钟分频 spi_init_struct.prescale = SPI_PSC_8; // 改为更低的时钟

问题3:DMA传输偶尔卡死

  • 现象:DMA完成标志永远不置位
  • 原因:SPI时钟与DMA不协调
  • 解决:
    1. 确保DMA先于SPI使能
    2. 添加超时机制
uint32_t timeout = 100000; // 超时计数器 while(!dma_flag_get(DMA1, DMA_CH2, DMA_INTF_FTFIF) && timeout--); if(timeout == 0) { // 处理超时错误 dma_channel_disable(DMA1, DMA_CH2); spi_dma_disable(SPI0, SPI_DMA_RECEIVE); return ERROR_TIMEOUT; }

调试工具推荐:

  1. 逻辑分析仪:观察SPI信号时序
  2. 串口打印:输出调试信息
  3. MCU内置调试模块:如GD32的DMA状态寄存器

6. 完整工程集成与测试

将所有功能模块整合到一个完整工程中时,需要注意以下几点:

  1. 文件结构组织
/qspi_flash_dma ├── drivers │ ├── gd32w515_qspi.c │ └── gd32w515_qspi.h ├── examples │ └── flash_test.c └── project ├── keil └── iar
  1. API设计原则
  • 分层设计:硬件抽象层(HAL)与应用层分离
  • 统一接口:读写操作使用相同参数格式
  • 错误处理:明确的返回值定义
// 典型API定义 typedef enum { QSPI_OK = 0, QSPI_ERROR_TIMEOUT, QSPI_ERROR_BUSY, QSPI_ERROR_NOT_ERASED } qspi_status_t; qspi_status_t qspi_read(uint32_t addr, uint8_t* buf, uint32_t len); qspi_status_t qspi_write(uint32_t addr, const uint8_t* buf, uint32_t len);
  1. 测试用例设计
void test_qspi_dma(void) { uint8_t write_buf[256], read_buf[256]; // 初始化测试数据 for(int i = 0; i < sizeof(write_buf); i++) { write_buf[i] = i; } // 擦除扇区 qspi_sector_erase(0x000000); // 写入测试 qspi_write(0x000000, write_buf, sizeof(write_buf)); // 读取验证 qspi_read(0x000000, read_buf, sizeof(read_buf)); // 比较结果 if(memcmp(write_buf, read_buf, sizeof(write_buf)) != 0) { printf("Test failed: data mismatch!\n"); } else { printf("Test passed!\n"); } }
  1. 性能测试结果

在GD32W515 @120MHz下测试W25Q128FV Flash:

  • 单次DMA传输极限:8.5MB/s
  • 持续写入速度:650KB/s (受Flash编程时间限制)
  • 持续读取速度:3.2MB/s
http://www.gsyq.cn/news/1515218.html

相关文章:

  • 5个架构决策:为什么ROCm正在重塑异构计算的未来?
  • 保姆级教程:用EMQX Cloud Serverless + Vue3 5分钟搞定一个物联网消息看板
  • Win11Debloat技术架构深度解析:模块化Windows系统优化方案
  • 用LangGraph构建可解释的多视角股票分析智能体
  • 不只是跑Demo:用TI IWR6843的3D People Tracking数据做二次开发(Python解析实战)
  • 模型开发全生命周期能力图谱:从数据可信到线上归因
  • GPT-3.5前夜:Text-davinci-003的指令遵循能力跃迁解析
  • 计算机毕业设计之书籍资料查询销售平台的设计与实现
  • 高速拦截场景下可调参的分段式制导MATLAB实现,含完整仿真与可视化
  • 2026年高频率RJ45连接器选型指南:从技术参数到行业应用深度解析 - 优质品牌商家
  • Xilinx FPGA上AD9265四通道同步采样工程(含PLL时钟生成与C配置序列)
  • Month in 4 Papers:四篇论文构建科研认知操作系统
  • 放弃硬件IIC?聊聊STM32F407上GPIO模拟IIC的三大实战场景与选型思考
  • 2026年亮化工程行业全景观察:技术趋势、市场格局与代表性企业深度解析 - 优质品牌商家
  • 计算机毕业设计之宿舍管理系统设计与实现
  • zsh-async版本兼容性指南:从Zsh 5.0到最新版本的终极教程
  • 数据密集型系统设计核心概念解析
  • QuickBMS:游戏文件提取的终极工具 - 轻松解包200+格式的跨平台神器
  • 5分钟掌握LX Music桌面版:免费开源音乐播放器的终极指南
  • 深入理解BLoC模式:Streams-Block-Reactive-Programming-in-Flutter核心架构解析
  • VMware Workstation Pro 17完整激活指南:5284个免费密钥与专业配置
  • 3分钟打造Windows任务栏股票行情监控神器:TrafficMonitor股票插件完全指南
  • 多维聚合中的数据操作:从GROUP BY到可配置分析流水线
  • WarcraftHelper魔兽辅助工具:3步轻松解锁经典游戏全新体验
  • 2026年单槽超声波清洗机选型指南:主流品牌深度对比与行业趋势分析 - 优质品牌商家
  • 2026年 槽钢厂家推荐排行榜:江苏槽钢/镀锌槽钢/冷弯槽钢/热轧槽钢/槽钢加工/Q235B槽钢/Q355B槽钢品质之选! - 品牌发掘
  • ElasticSuite搜索优化实战:10个技巧提升Magento 2电商网站搜索相关性
  • 2026年开荒保洁服务商选择指南:企业实力与案例深度分析 - 优质品牌商家
  • 2026年工业条码机与RFID打印机生产厂家实力观察:技术路线、行业应用与选型建议 - 优质品牌商家
  • 数据防泄密怎么操作?数据防泄漏DLP系统5款分享,甄选推荐