STM32 SPI LL DMA 传输

STM32 SPI LL DMA Transmit

我一直在尝试在 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 配置步骤:

Channel configuration procedure The following sequence is needed to configure a DMA channel x:

  1. Set the peripheral register address in the DMA_CPARx register. The data is moved from/to this address to/from the memory after the peripheral event, or after the channel is enabled in memory-to-memory mode.
  2. Set the memory address in the DMA_CMARx register. The data is written to/read from the memory after the peripheral event or after the channel is enabled in memory-to-memory mode.
  3. Configure the total number of data to transfer in the DMA_CNDTRx register. After each data transfer, this value is decremented.
  4. Configure the parameters listed below in the DMA_CCRx register: – the channel priority – the data transfer direction – the circular mode – the peripheral and memory incremented mode – the peripheral and memory data size – the interrupt enable at half and/or full transfer and/or transfer error
  5. Activate the channel by setting the EN bit in the DMA_CCRx register. A channel, as soon as enabled, may serve any DMA request from the peripheral connected to this channel, or may start a memory-to-memory block transfer.

据我了解,第 1、2、3 和 5 步已在 main.c 中完成,而第 4 步已在 spi.c

中完成

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

A DMA access is requested when the TXE or RXNE enable bit in the SPIx_CR2 register is set. Separate requests must be issued to the Tx and Rx buffers.

-In transmission, a DMA request is issued each time TXE is set to 1. The DMA then writes to the SPIx_DR register

When starting communication using DMA, to prevent DMA channel management raising error events, these steps must be followed in order:

  1. Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used.
  2. Enable DMA streams for Tx and Rx in DMA registers, if the streams are used.
  3. Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used.
  4. Enable the SPI by setting the SPE bit.

据我所知,我已经完成了所有步骤,但是我的示波器连接到 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

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

感谢任何帮助! 谢谢!

经过一段时间的调试,我发现STM32cubeMX代码生成器有一个bug。 (这似乎也已经被报道了(https://community.st.com/s/question/0D53W00001HJ3EhSAL/wrong-initialization-sequence-with-cubemx-in-cubeide-when-using-i2s-with-dma?t=1641156995520&searchQuery

选择SPI和DMA时,生成器先初始化SPI,再初始化DMA with

MX_SPIx_Init();
MX_DMA_Init();

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

这也是我使用 USART 的原因,USART 初始化是在 DMA 初始化之后进行的。

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

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

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

    /* 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 */