STM32 SPI LL DMA 传输
STM32 SPI LL DMA Transmit
我一直在尝试在 STM32G030C8 上使用 DMA 和 STM32 LL 驱动程序让 SPI 主传输工作。
我确实让 SPI 在没有 DMA 的情况下与 LL 驱动程序一起工作,所以我相信至少我的接线是正确的。
我做了什么:
通过设置 SPI1_TX
请求到 DMA1 通道 1
来设置 SPI 在 cubeMX 中使用 DMA
在代码中设置传输:
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:
- 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.
- 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.
- Configure the total number of data to transfer in the
DMA_CNDTRx
register.
After each data transfer, this value is decremented.
- 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
- 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:
- Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is
used.
- Enable DMA streams for Tx and Rx in DMA registers, if the streams are used.
- Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used.
- 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 */
我一直在尝试在 STM32G030C8 上使用 DMA 和 STM32 LL 驱动程序让 SPI 主传输工作。
我确实让 SPI 在没有 DMA 的情况下与 LL 驱动程序一起工作,所以我相信至少我的接线是正确的。
我做了什么:
通过设置
来设置 SPI 在 cubeMX 中使用 DMASPI1_TX
请求到 DMA1 通道 1在代码中设置传输:
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:
- 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.- 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.- Configure the total number of data to transfer in the
DMA_CNDTRx
register. After each data transfer, this value is decremented.- 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- 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:
- Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used.
- Enable DMA streams for Tx and Rx in DMA registers, if the streams are used.
- Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used.
- 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 */