当你在STM32H7平台上使用RT-Thread操作系统驱动ST7735屏幕时,SPI DMA模式能显著提升性能,但这条优化之路布满陷阱。本文将分享我在实际项目中遇到的典型问题及其解决方案,涵盖RAM分区、Cache一致性和RT-Thread驱动框架适配等核心挑战。
STM32H7系列的存储架构比传统MCU复杂得多。默认情况下,RT-Thread Studio分配的RAM区域从0x20000000开始,但这块内存不能被某些DMA控制器访问。以STM32H743VIT6为例,其1MB RAM实际上分散在多个区域:
| 内存区域 | 起始地址 | 大小 | 可被DMA1访问 | 可被DMA2访问 |
|---|---|---|---|---|
| DTCM | 0x20000000 | 128KB | 否 | 否 |
| AXI SRAM | 0x24000000 | 512KB | 是 | 是 |
| SRAM1 | 0x30000000 | 128KB | 是 | 是 |
| SRAM2 | 0x30020000 | 128KB | 是 | 是 |
| SRAM3 | 0x30040000 | 32KB | 是 | 是 |
| SRAM4 | 0x38000000 | 64KB | 是 | 否 |
关键修改步骤:
ld复制MEMORY
{
RAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
}
SECTIONS
{
.spi4.txbuf :
{
. = ALIGN(4);
*(.spi4.txbuf)
. = ALIGN(4);
} >RAM4
}
c复制uint8_t ram4_spi4_buf[4096] __attribute__((section(".spi4.txbuf")));
bash复制arm-none-eabi-nm -n your_elf_file.elf | grep ram4_spi4_buf
应显示地址在0x38000000~0x3800FFFF范围内。
STM32H7的Cache机制在提升性能的同时,也带来了数据一致性问题。当CPU修改了Cache中的数据但未写回RAM,或者DMA直接修改了RAM但Cache中仍是旧数据时,就会导致显示异常。
典型症状:
解决方案:
c复制void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct = {0};
HAL_MPU_Disable();
// 配置RAM4区域(0x38000000)为非缓存
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x38000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
c复制void hw_board_init(char *clock_src, int32_t clock_src_freq, int32_t clock_target_freq)
{
MPU_Config();
#ifdef SCB_EnableICache
SCB_EnableICache();
#endif
#ifdef SCB_EnableDCache
SCB_EnableDCache();
#endif
// 其他初始化代码...
}
注意:MPU配置必须在Cache启用之前完成,否则可能导致不可预知的行为。
RT-Thread对STM32H7的SPI DMA支持尚不完善,需要进行以下框架级修改:
关键修改点:
drv_dma.h中的DMA结构体定义:c复制// 针对H7系列的特殊定义
#if defined(SOC_SERIES_STM32H7)
struct dma_config {
DMA_Stream_TypeDef *Instance;
uint32_t request;
uint32_t direction;
// 其他成员...
};
#endif
dma_config.h中的SPI4配置:c复制#define SPI4_TX_DMA_CONFIG \
{ \
.Instance = DMA1_Stream0, \
.request = DMA_REQUEST_SPI4_TX, \
.direction = DMA_MEMORY_TO_PERIPH, \
// 其他参数... \
}
c复制// 在drv_spi.c中添加SPI中断使能
static int stm32_spi_init(struct stm32_spi *spi)
{
// 原有初始化代码...
// 添加SPI中断使能
HAL_NVIC_SetPriority(spi->irq, 1, 0);
HAL_NVIC_EnableIRQ(spi->irq);
return 0;
}
c复制// 在drv_spi.c中修改传输完成处理
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
struct stm32_spi *spi = container_of(hspi, struct stm32_spi, handle);
rt_spi_release_bus(&spi->spi_bus.parent);
spi->state = RT_SPI_READY;
}
结合上述基础工作,我们最终实现的SPI DMA驱动包含以下关键组件:
1. DMA缓冲区管理
c复制#define RAM4_SPI4_BUF_SIZE 4096
__attribute__((section(".spi4.txbuf")))
uint8_t spi4_tx_buf[RAM4_SPI4_BUF_SIZE];
volatile enum {
SPI_DMA_IDLE,
SPI_DMA_BUSY,
SPI_DMA_ERROR
} spi4_dma_state = SPI_DMA_IDLE;
2. DMA传输函数实现
c复制int32_t mspi_send_data(uint8_t *data, uint32_t len)
{
if(len > RAM4_SPI4_BUF_SIZE) {
rt_kprintf("Error: Data exceeds buffer size\n");
return -1;
}
memcpy(spi4_tx_buf, data, len);
spi4_dma_state = SPI_DMA_BUSY;
LCD_CS_LOW();
if(HAL_SPI_Transmit_DMA(&hspi4, spi4_tx_buf, len) != HAL_OK) {
spi4_dma_state = SPI_DMA_ERROR;
LCD_CS_HIGH();
return -1;
}
while(spi4_dma_state == SPI_DMA_BUSY) {
rt_thread_mdelay(1);
}
LCD_CS_HIGH();
return (spi4_dma_state == SPI_DMA_IDLE) ? 0 : -1;
}
3. 中断回调处理
c复制void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI4) {
spi4_dma_state = SPI_DMA_IDLE;
}
}
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI4) {
spi4_dma_state = SPI_DMA_ERROR;
}
}
性能对比测试结果:
| 传输模式 | 传输1KB数据时间(ms) | CPU占用率 |
|---|---|---|
| 普通SPI轮询 | 12.5 | 100% |
| SPI中断 | 8.2 | 60% |
| SPI DMA(本文方案) | 1.8 | <5% |
在实际项目中,这套方案成功将ST7735屏幕的刷新率从15FPS提升到了65FPS,同时CPU负载从90%降低到10%以下。最令人头疼的显示撕裂和错位问题也随着Cache配置的完善而彻底解决。