STM32 SPI LL DMA 发送

问题描述 投票:0回答:2

我一直在尝试在 STM32G030C8 上使用 DMA 和 STM32 LL 驱动程序让 SPI 主传输工作。

我确实让 SPI 在没有 DMA 的情况下与 LL 驱动程序一起工作,所以我相信至少我的接线是正确的。

我做了什么:

  1. 通过设置

    SPI1_TX
    请求 DMA1 通道 1,将 SPI 设置为在cubeMX 中使用 DMA

  2. 在代码中设置传输:

main.c

#include "main.h"
#include "dma.h"
#include "gpio.h"
#include "spi.h"

uint8_t test_data[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

void SystemClock_Config(void);

int main(void) {
       
        HAL_Init();

        SystemClock_Config();

        MX_GPIO_Init();
        MX_SPI1_Init();
        MX_DMA_Init();
        while (1) {
                LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1, (uint32_t)(&test_data[0]),
                                       (uint32_t)LL_SPI_DMA_GetRegAddr(SPI1),
                                       LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1));
                LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 8);
                LL_SPI_EnableDMAReq_TX(SPI1);
                LL_SPI_Enable(SPI1);

                LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);

                HAL_Delay(1000);
                HAL_GPIO_TogglePin(STATUS_LED_GPIO_Port, STATUS_LED_Pin);
        }
}

spi.c:

#include "spi.h"

void MX_SPI1_Init(void)
{

  LL_SPI_InitTypeDef SPI_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  GPIO_InitStruct.Pin = LL_GPIO_PIN_1;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_0;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = LL_GPIO_PIN_2;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_0;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_SPI1_TX);

  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);

  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_NORMAL);

  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_BYTE);

  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_BYTE);

  NVIC_SetPriority(SPI1_IRQn, 0);
  NVIC_EnableIRQ(SPI1_IRQn);

  SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
  SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
  SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT;
  SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;
  SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;
  SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
  SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV4;
  SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;
  SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
  SPI_InitStruct.CRCPoly = 7;
  LL_SPI_Init(SPI1, &SPI_InitStruct);
  LL_SPI_SetStandard(SPI1, LL_SPI_PROTOCOL_MOTOROLA);
  LL_SPI_DisableNSSPulseMgt(SPI1);
}

dma.c:

#include "dma.h"

void MX_DMA_Init(void)
{
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

  NVIC_SetPriority(DMA1_Channel1_IRQn, 0);
  NVIC_EnableIRQ(DMA1_Channel1_IRQn);

}

从参考手册中我找到了以下 DMA 配置步骤:

通道配置流程 配置 DMA 通道 x 需要以下序列:

  1. DMA_CPARx
    寄存器中设置外设寄存器地址。 外设事件发生后,数据从该地址移至内存或从内存移至该地址, 或在内存到内存模式下启用通道后。
  2. DMA_CMARx
    寄存器中设置内存地址。 数据在外设事件之后或在 通道在内存到内存模式下启用。
  3. DMA_CNDTRx
    寄存器中配置要传输的数据总数。 每次数据传输后,该值都会递减。
  4. DMA_CCRx
    寄存器中配置下列参数: – 通道优先级 – 数据传输方向 – 循环模式 – 外设和内存递增模式 – 外设和内存数据大小 – 一半和/或全部传输和/或传输错误时中断使能
  5. 通过设置
    DMA_CCRx
    寄存器中的 EN 位来激活通道。 通道一旦启用,就可以为来自连接的外设的任何 DMA 请求提供服务 到此通道,或者可以启动内存到内存块传输。

据我了解,步骤 1,2,3 和 5 是在 main.c 中完成的,步骤 4 已经在 spi.c 中完成

还有关于SPI和DMA的参考手册:

SPIx_CR2
寄存器中的 TXE 或 RXNE 使能位为 放。必须向 Tx 和 Rx 缓冲区发出单独的请求。

-在传输中,每次 TXE 设置为 1 时都会发出 DMA 请求。然后 DMA 写入

SPIx_DR
寄存器

使用 DMA 启动通信时,防止 DMA 通道管理升高 发生错误事件时,必须按顺序执行以下步骤:

  1. 如果 DMA Rx 为,则在 SPI_CR2 寄存器中的 RXDMAEN 位中启用 DMA Rx 缓冲区 使用过。
  2. 如果使用了 DMA 寄存器中的 Tx 和 Rx 的 DMA 流。
  3. 如果使用 DMA Tx,请在 SPI_CR2 寄存器的 TXDMAEN 位中启用 DMA Tx 缓冲区。
  4. 通过设置 SPE 位来启用 SPI。

据我所知,我已经完成了所有步骤,但我用连接到 SPI1 线的示波器看不到任何东西。

我一定错过了一些东西(或者某些东西按错误的顺序完成),但我无法弄清楚出了什么问题。

在其他一些问题中,问题是 DMA 通道错误且不支持 SPI,但在这个 MCU 中,如果我理解正确的话,DMAMUX 会处理该问题,并且任何信号都应该在任何 DMA 通道中可用? (在spi.c中配置)

编辑:

从 SPI 和 DMA 读取标志:

LL_SPI_IsActiveFlag_BSY(SPI1)                   returns 0
LL_SPI_IsEnabledDMAReq_TX(SPI1)                 returns 1
LL_SPI_IsEnabled(SPI1)                          returns 1
LL_DMA_IsEnabledChannel(DMA1, LL_DMA_CHANNEL_1) returns 1
LL_DMA_IsActiveFlag_TE1(DMA1)                   returns 0
LL_SPI_IsActiveFlag_TXE(SPI1)                   returns 1

所以,一切似乎都已启用,没有错误,但没有数据传输!

如有任何帮助,我们将不胜感激! 谢谢!

c stm32 spi dma low-level-api
2个回答
4
投票

经过一段时间的调试,我发现STM32cubeMX代码生成器存在一个错误。 (这似乎也已经报告(https://community.st.com/s/question/0D53W00001HJ3EhSAL/wrong-initialization-sequence-with-cubemx-in-cubeide-when-using-i2s-with-dma? t=1641156995520&搜索查询)

选择 SPI 和 DMA 时,生成器首先初始化 SPI,然后初始化 DMA

MX_SPIx_Init();
MX_DMA_Init();

问题在于 SPI 初始化尝试设置 DMA 寄存器,但 DMA 时钟尚未启用,因此未保存值。

这也是我让它与 USART 一起工作的原因,USART 初始化是在 DMA 初始化之后进行的。

所以简单的解决方案是将 DMA 初始化移到其他外设之前。 但由于每次您在cubeMX中进行更改时都会自动生成此代码,因此更改将会丢失。

为了解决这个问题,我使用cubeMX在高级设置下的项目管理器选项卡中禁用自动初始化调用:

enter image description here

然后在下面的用户代码段中手动调用这些初始化函数,现在看起来像这样:

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_DMA_Init();
    /* USER CODE BEGIN 2 */
    MX_SPI1_Init();
    MX_USART1_UART_Init();
    /* USER CODE END 2 */

0
投票

对于其他搜索在其他部件上运行的特定代码但类似 SPI IP 的人:STM32H5 等高端设备,他们需要:

  LL_SPI_StartMasterTransfer(SPI2);

在 DMA 启用通道语句之后。

© www.soinside.com 2019 - 2024. All rights reserved.