You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32F446RET6 SPI1 DMA驱动Nokia5110 LCD失效求助

SPI1 DMA传输Nokia5110异常问题排查

问题现象

使用STM32F446RET6的SPI1 DMA向Nokia5110 LCD发送数据时无法正常工作,非DMA方式可正常运行。

关键代码片段

static void initSPI1(void)
{
    // PA5, PA6, PA7 AS AF7
    RCC->AHB1ENR |= 1<<0;
    GPIOA->MODER &= ~(1<<10 | 1<<12 | 1<<14);
    GPIOA->MODER |= 1<<11 | 1<<13 | 1<<15;
    GPIOA->AFR[0] |= 1<<20 | 1<<22 | 1<<24 | 1<<26 | 1<<28 | 1<<30;

    // SPI1 CONFIG
    RCC->APB2ENR |= 1<<12;
    SPI1->CR1 |= 1<<2 | 1<<3 | 1<<4 | 1<<5 | 1<<8 | 1<<9;
    SPI1->CR2 |= 1<<1; // ENABLE TX DMA
    SPI1->CR1 |= 1<<6; // ENABLE SPI1

    // DMA2 CONFIG
    RCC->AHB1ENR |= 1<<22;
    DMA2_Stream3->CR |= 1<<4 | 1<<6 | 1<<8 | 1<<10 | 1<<25 | 1<<26;
}

static void sendChar(char character)
{
    GPIOA->ODR &= ~(1<<0); // CS_PIN_LOW

    DMA2_Stream3->NDTR = 1;
    DMA2_Stream3->PAR = (uint32_t) &SPI1->DR;
    DMA2_Stream3->M0AR = (uint32_t) character;
    DMA2_Stream3->CR |= 1<<0;

    while(!(DMA2->LISR & 1<<27));
    DMA2_Stream3->CR &= ~(1<<0);
    DMA2->LIFCR |= 1<<27;

//  send char without dma
//  SPI1->DR = character;
//  while(!(SPI1->SR & 1<<1));
//  while(SPI1->SR & 1<<7);
//
//  (void) SPI1->DR;
//  (void) SPI1->SR;

    GPIOA->ODR |= 1<<0; // CS_PIN_HIGH
}

程序错误点排查

1. DMA内存地址传递错误

在sendChar函数中,DMA2_Stream3->M0AR = (uint32_t) character 是错误的。DMA需要读取的是变量的内存地址,而非变量的值。正确写法应为:

DMA2_Stream3->M0AR = (uint32_t)&character;

当前写法会将字符的ASCII值当作内存地址,导致DMA从非法地址读取数据,传输内容完全错误。

2. DMA通道选择错误

SPI1_TX对应的DMA通道为DMA2 Stream3 Channel 3(参考STM32F446手册),但代码中配置了1<<25 | 1<<26(CHSEL[2:0] = 110,对应通道6),通道不匹配会导致DMA无法响应SPI的传输请求。正确的通道配置应为:

DMA2_Stream3->CR |= (3 << 25); // CHSEL[2:0] = 011,选择通道3

3. DMA数据宽度不匹配

SPI1配置为8位数据传输,但代码中DMA的PSIZE(外设数据宽度)和MSIZE(内存数据宽度)被设置为16位(1<<8和1<<10),数据宽度不匹配会导致传输数据被截断或错位。正确配置应为8位宽度:

// 清除原有16位配置,设置为8位
DMA2_Stream3->CR &= ~((3 << 8) | (3 << 10));

4. DMA Stream初始化不完整

  • 未开启内存增量模式(MINC位):虽然单次传输1字节时不影响,但如果后续扩展多字节传输,必须开启该位,建议补充:
    DMA2_Stream3->CR |= 1<<11; // 开启内存增量模式
    
  • 建议先清除DMA Stream的CR寄存器再配置,避免默认值干扰:
    DMA2_Stream3->CR = 0; // 先清空配置寄存器
    // 再重新写入所需配置
    DMA2_Stream3->CR |= 1<<4 | 1<<6 | (3 << 25) | 1<<11;
    

修正后的关键代码示例

修正后的initSPI1函数

static void initSPI1(void)
{
    // PA5, PA6, PA7 AS AF7
    RCC->AHB1ENR |= 1<<0;
    GPIOA->MODER &= ~(1<<10 | 1<<12 | 1<<14);
    GPIOA->MODER |= 1<<11 | 1<<13 | 1<<15;
    GPIOA->AFR[0] |= 1<<20 | 1<<22 | 1<<24 | 1<<26 | 1<<28 | 1<<30;

    // SPI1 CONFIG
    RCC->APB2ENR |= 1<<12;
    SPI1->CR1 |= 1<<2 | 1<<3 | 1<<4 | 1<<5 | 1<<8 | 1<<9;
    SPI1->CR2 |= 1<<1; // ENABLE TX DMA
    SPI1->CR1 |= 1<<6; // ENABLE SPI1

    // DMA2 CONFIG
    RCC->AHB1ENR |= 1<<22;
    DMA2_Stream3->CR = 0; // 清空寄存器
    // DIR=1(内存到外设), MINC=1(内存增量), PINC=0(外设非增量)
    // PSIZE=00(8位), MSIZE=00(8位), CHSEL=011(通道3), CIRC=0(非循环)
    DMA2_Stream3->CR |= (1<<4) | (1<<11) | (3<<25);
}

修正后的sendChar函数

static void sendChar(char character)
{
    GPIOA->ODR &= ~(1<<0); // CS_PIN_LOW

    DMA2_Stream3->NDTR = 1;
    DMA2_Stream3->PAR = (uint32_t)&SPI1->DR;
    DMA2_Stream3->M0AR = (uint32_t)&character; // 修正为变量地址
    DMA2_Stream3->CR |= 1<<0; // 启动DMA传输

    while(!(DMA2->LISR & (1<<27))); // 等待传输完成
    DMA2_Stream3->CR &= ~(1<<0); // 停止DMA
    DMA2->LIFCR |= (1<<24) | (1<<25) | (1<<26) | (1<<27); // 清除所有Stream3标志

    GPIOA->ODR |= 1<<0; // CS_PIN_HIGH
}

内容的提问来源于stack exchange,提问作者May Myat Thuzar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:32:20