STM32L4 SPI Передача полного прерывания с использованием DMA срабатывает только один раз

Я пытаюсь отправить массив из 10 байтов между двумя нуклео-платами (NUCLEO-L432KCU), используя SPI и DMA. Моя цель - разработать код для ведомой платы с использованием API низкого уровня. Основная плата используется просто для тестирования и, когда все заработает, ее заменят настоящей системой.

Прежде чем продолжить, вот еще несколько подробностей о системе: Отправитель настроен как главный. Код мастера разработан с использованием HAL API. Выбор микросхемы на главной плате реализован с помощью GPIO. Приемник настроен как подчиненный с включенной опцией Получать только подчиненное устройство и Аппаратный вход NSS. Код инициализации создается автоматически G с помощью инструмента CubeMX.

В моей текущей реализации я могу получать данные на ведомой плате, но только один раз: на практике кажется, что прерывание срабатывает только один раз, и мне трудно понять, что мне не хватает!

Я считаю, что ошибка как-то связана с очисткой некоторых флагов прерывания. Я просмотрел справочное руководство, но не вижу, что делаю не так.

Ниже приведен мой код для отправителя и получателя.


Код отправителя

Примечание. Что касается отправителя, я сообщаю только об основной функции, поскольку весь остальной код создается автоматически. Кроме того, я проверил с помощью логического анализатора, что код работает. Пожалуйста, дайте мне знать, если вам понадобится дополнительная информация.

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_SPI1_Init();
  MX_SPI3_Init();
  MX_USART2_UART_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */

  uint8_t test[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A};
  HAL_GPIO_WritePin(SPI1_CS_GPIO_Port,SPI1_CS_Pin,RESET);
  HAL_SPI_Transmit(&hspi1,test,sizeof(test),1000);
  HAL_GPIO_WritePin(SPI1_CS_GPIO_Port,SPI1_CS_Pin,SET);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

Код для получателя Примечание. Настройка DMA и SPI в основном выполняется автоматически с помощью инструмента CubeMX. Другие инициализации для моего проекта предоставляются в основной функции.

uint8_t aRxBuffer[10];
uint8_t received_buffer[100];
uint16_t cnt = 0;

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_SPI1_Init();
  MX_SPI3_Init();
  MX_USART2_UART_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */

  // Custom configuration of DMA (after calling function MX_SPI3_INIT()
  // Configure address of the buffer for receiving data
  LL_DMA_ConfigAddresses(DMA2, LL_DMA_CHANNEL_1, LL_SPI_DMA_GetRegAddr(SPI3), (uint32_t)aRxBuffer,LL_DMA_GetDataTransferDirection(DMA2, LL_DMA_CHANNEL_1));
  // Configure data length
  LL_DMA_SetDataLength(DMA2, LL_DMA_CHANNEL_1,10);
  // Enable DMA Transfer complete interrupt
  LL_DMA_EnableIT_TC(DMA2, LL_DMA_CHANNEL_1);
  // LL_DMA_EnableIT_TE(DMA2, LL_DMA_CHANNEL_1);

  // We Want the SPI3 to receive 8-bit data
  // Therefore we trigger the RXNE interrupt when the FIFO level is greater than or equal to 1/4 (8bit)
  // See pag. 1221 of the TRM
  LL_SPI_SetRxFIFOThreshold(SPI3,LL_SPI_RX_FIFO_TH_QUARTER);
  LL_SPI_EnableDMAReq_RX(SPI3);

  // Enable SPI_3
  LL_SPI_Enable(SPI3);
  // Enable DMA_2,CHANNEL_1
  LL_DMA_EnableChannel(DMA2, LL_DMA_CHANNEL_1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

Ниже приведен обработчик IRQ (закомментированный код представляет различные попытки заставить его работать!):

void DMA2_Channel1_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Channel1_IRQn 0 */

    // Transfer-complete interrupt management
    if(LL_DMA_IsActiveFlag_TC1(DMA2))
    {
        //LL_DMA_ClearFlag_TC1(DMA2);
        LL_DMA_ClearFlag_GI1(DMA2);
        /* Call function Transmission complete Callback */
        DMA1_TransmitComplete_Callback();
    }
    else if(LL_DMA_IsActiveFlag_TE1(DMA2))
    {
        /* Call Error function */
        int _error = 0;
    }


      // Enable SPI_3
      //LL_SPI_Disable(SPI3);
      // Enable DMA_2,CHANNEL_1
      //LL_DMA_DisableChannel(DMA2, LL_DMA_CHANNEL_1);

      //LL_DMA_EnableIT_TC(DMA2, LL_DMA_CHANNEL_1);
      // LL_DMA_EnableIT_TE(DMA2, LL_DMA_CHANNEL_1);

      // We Want the SPI3 to receive 8-bit data
      // Therefore we trigger the RXNE interrupt when the FIFO level is greater than or equal to 1/4 (8bit)
      // See pag. 1221 of the TRM
      //LL_SPI_SetRxFIFOThreshold(SPI3,LL_SPI_RX_FIFO_TH_QUARTER);
      //LL_SPI_EnableDMAReq_RX(SPI3);

      // Enable SPI_3
      //LL_SPI_Enable(SPI3);
      // Enable DMA_2,CHANNEL_1
      LL_DMA_EnableChannel(DMA2, LL_DMA_CHANNEL_1);



    //  LL_DMA_EnableIT_TE(DMA2, LL_DMA_CHANNEL_1);

  /* USER CODE END DMA2_Channel1_IRQn 0 */

  /* USER CODE BEGIN DMA2_Channel1_IRQn 1 */

  /* USER CODE END DMA2_Channel1_IRQn 1 */
}

Ниже приведена инициализация SPI и DMA (создается автоматически):

/* SPI1 init function */
void MX_SPI1_Init(void)
{
  LL_SPI_InitTypeDef SPI_InitStruct;

  LL_GPIO_InitTypeDef GPIO_InitStruct;
  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);

  /**SPI1 GPIO Configuration  
  PA1   ------> SPI1_SCK
  PA7   ------> SPI1_MOSI 
  */
  GPIO_InitStruct.Pin = SCLK1_to_SpW_Pin|MOSI1_to_SpW_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_5;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
  SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
  SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_4BIT;
  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_DIV8;
  SPI_InitStruct.BitOrder = LL_SPI_LSB_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_EnableNSSPulseMgt(SPI1);

}
/* SPI3 init function */
void MX_SPI3_Init(void)
{
  LL_SPI_InitTypeDef SPI_InitStruct;

  LL_GPIO_InitTypeDef GPIO_InitStruct;
  /* Peripheral clock enable */
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI3);

  /**SPI3 GPIO Configuration  
  PA4   ------> SPI3_NSS
  PB3 (JTDO-TRACESWO)   ------> SPI3_SCK
  PB5   ------> SPI3_MOSI 
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_4;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_6;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = SCLK_from_SpW_Pin|MOSI_from_SpW_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_6;
  LL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /* SPI3 DMA Init */

  /* SPI3_RX Init */
  LL_DMA_SetPeriphRequest(DMA2, LL_DMA_CHANNEL_1, LL_DMA_REQUEST_3);

  LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

  LL_DMA_SetChannelPriorityLevel(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MODE_NORMAL);

  LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_BYTE);

  LL_DMA_SetMemorySize(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_BYTE);

  /* SPI3 interrupt Init */
  NVIC_SetPriority(SPI3_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
  NVIC_EnableIRQ(SPI3_IRQn);

  SPI_InitStruct.TransferDirection = LL_SPI_SIMPLEX_RX;
  SPI_InitStruct.Mode = LL_SPI_MODE_SLAVE;
  SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_4BIT;
  SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;
  SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;
  SPI_InitStruct.NSS = LL_SPI_NSS_HARD_INPUT;
  SPI_InitStruct.BitOrder = LL_SPI_LSB_FIRST;
  SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
  SPI_InitStruct.CRCPoly = 7;
  LL_SPI_Init(SPI3, &SPI_InitStruct);

  LL_SPI_SetStandard(SPI3, LL_SPI_PROTOCOL_MOTOROLA);

  LL_SPI_DisableNSSPulseMgt(SPI3);

}

Спасибо.


person Alexis Nicole    schedule 04.06.2018    source источник


Ответы (1)


Недавно я реализовал похожую систему и надеюсь, что смогу помочь. У меня есть несколько вопросов, комментариев, которые, возможно, могут решить вашу проблему, но это сложно сделать без присутствия.

  • Вы знаете, что это за SPI или DMA? Возникает ли прерывание SPI на ведомом устройстве? Это будет означать, что DMA неисправен, а не SPI. Важно точно знать, где система дает сбой.
  • LL_SPI_SetRxFIFOThreshold(SPI3,LL_SPI_RX_FIFO_TH_QUARTER); необходимо, но должно быть выполнено в init
  • Флаг TCIF должен быть сброшен (как и вы) во время IRQ.
  • Вы должны настроить SPI для запуска DMA (я не вижу этого в вашем коде), используя регистр SPI_CR2_RXDMAEN. Это вам также следует сделать во время init, если вы не знаете, когда вы получите данные.
  • По той же причине, я думаю, вам следует включить канал DMA во время init и оставить его включенным.

Я надеюсь, что один из этих комментариев поможет. Если нет, мы попробуем еще раз.

Изменить: Хорошая работа. Я рад, что вы его запустили, решив большинство проблем. С помощью предоставленной вами информации я понял, в чем основная проблема с буфером.

Вы устанавливаете DMA на получение 10 байтов с помощью:

LL_DMA_SetDataLength(DMA2, LL_DMA_CHANNEL_1,10);

Это устанавливает внутренний счетчик DMA на 10. Для каждого байта, который он получает, счетчик уменьшается на единицу, пока не достигнет нуля. Это то, что позволяет ему считать 10 байт. В нормальном режиме, если вы хотите получить еще 10 байт, вам нужно отправить эту команду еще раз. В круговом режиме это значение будет автоматически сброшено до 10, что означает, что он может получить еще 10 байт.

Следовательно, если вы ожидаете, что всегда будете получать 10 байтов, то режим cicular должен работать для вас нормально. Если нет, вам придется использовать нормальный режим и указать MCU, сколько байтов вы ожидаете (немного сложнее).

person Hein Wessels    schedule 04.06.2018
comment
Привет спасибо за ответ. Если я не ошибаюсь, LL_SPI_SetRxFIFOThreshold должно быть в порядке: согласно примерам проектов STM32L4 LL эта функция вызывается сразу после вызова функции LL_SPI_Init. SPI_CR2_RXDMAEN включен, вызывая функцию LL_SPI_EnableDMAReq_RX (SPI_TypeDef * SPIx), поэтому проблемы быть не должно. Я заметил следующее: если я поставлю точку останова на LL_DMA_ClearFlag_GI1 (DMA2); функции регистр состояния (SR) имеет RXNE = 0x01, TXE = 0x01, OVR = 0x01 и FRLVL = 0c03. Это означает, что произошла ошибка переполнения и фактически FIFO заполнен. - person Alexis Nicole; 04.06.2018
comment
Однако выполнение инструкции LL_DMA_ClearFlag_GI1 (DMA2); очищает OVR, RXNE и FRLVL. Это странно, поскольку я знаю, что отправляю ровно 10 байт, а на стороне получателя aRxBuffer имеет размерность 10, так что, на мой взгляд, все должно быть в порядке. Я также попытался увеличить aRxBuffer до 20 (просто чтобы посмотреть), но проблема все еще сохраняется. - person Alexis Nicole; 04.06.2018
comment
Обновление: я решил проблему с установленным флагом OVR, но основная проблема все еще остается. Я также заметил следующее: если я отправлю 10 байтов, скажем, ABCDEFGHIJ, когда LL_DMA_IsActiveFlag_TC1 истинно в подпрограмме IRQ, регистр данных будет содержать GH, а не IJ, как я ожидал. - person Alexis Nicole; 04.06.2018
comment
Думаю, я наконец понял. Если я устанавливаю буфер DMA с НОРМАЛЬНОГО на ЦИРКУЛЯРНЫЙ, он работает. Однако я думаю, почему это не работает, если я установил его в нормальном режиме: если я ожидаю получить ровно 10 байтов, через 10 байтов буфер должен сброситься. Это правильно? - person Alexis Nicole; 04.06.2018
comment
Хорошая работа! Я обновил свой ответ, чтобы объяснить режимы буфера и почему ваш код не получил более одной передачи. Надеюсь, это поможет :) - person Hein Wessels; 05.06.2018