На STM32F7 DMA запускается таймерами APB1

У меня есть дискотека STM32F7 с микроконтроллером STM32F723IEK. Попытка инициировать запрос DMA от таймера вызывает ошибку DMA, но только для таймеров из группы APB1 (от TIM2 до TIM7 и других), которые подключены к DMA1. То же самое с TIM1 и TIM8, которые подключены к DMA2, работает нормально. Ошибка проявляется в том, что флаг TEIFx устанавливается в соответствующем регистре DMA LISR или HISR, а DMA сразу отключается после первой транзакции. Регистр NDTR уменьшается на единицу.

Согласно данным таблицы, ошибка TEIF может быть вызвана "ошибкой шины". Я понимаю это, например, как попытку доступа к периферийному устройству, которое не доступно с шины DMA. Однако та же самая настройка работает хорошо, используя DMA2 и TIM1/TIM8, без изменения адреса DMA. Таким образом, проблема, похоже, связана с запросом DMA, а не самой транзакцией данных. Учитывая, что для DMA1 определено много каналов таймера, это, безусловно, должно работать.

Я пытался изменить настройки DMA, но это не имело никакого значения. Соответствующая часть тестовой программы приведена ниже. Полная версия https://github.com/ak-hard/stm32-dma-tim/blob/master/main.c лишь немного больше и не имеет зависимостей, кроме заголовков устройств CMSIS и STM32.

Интересно, может кто-то прокомментировать или воспроизвести эту проблему.

const struct
{
    TIM_TypeDef *tim;
    DMA_TypeDef *dma;
    DMA_Stream_TypeDef *stream;
    unsigned channel;
} CFG = {
// uncomment the needed combination below, only TIM1 and TIM8 work
//    TIM1, DMA2, DMA2_Stream5, 6
    TIM8, DMA2, DMA2_Stream1, 7
//    TIM2, DMA1, DMA1_Stream1, 3
//    TIM2, DMA1, DMA1_Stream7, 3
//    TIM3, DMA1, DMA1_Stream2, 5
//    TIM4, DMA1, DMA1_Stream6, 2
//    TIM5, DMA1, DMA1_Stream0, 6
//    TIM5, DMA1, DMA1_Stream6, 6
//    TIM6, DMA1, DMA1_Stream1, 7
//    TIM7, DMA1, DMA1_Stream2, 1
//    TIM7, DMA1, DMA1_Stream4, 1
};

enum
{
    DMA_SxCR_DIR_P2M = 0,
    DMA_SxCR_PSIZE_WORD = DMA_SxCR_PSIZE_1,
    DMA_SxCR_MSIZE_WORD = DMA_SxCR_MSIZE_1,
};

#define DMA_SxCR_CHSEL_NUM(ch) ((ch) << DMA_SxCR_CHSEL_Pos)

uint32_t buf;

void start(void)
{
    SysTick->LOAD = 0xffffffu;
    SysTick->VAL = 0;
    SysTick->CTRL = 5;

    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN | RCC_APB2ENR_TIM8EN;
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM4EN | RCC_APB1ENR_TIM5EN
            | RCC_APB1ENR_TIM6EN | RCC_APB1ENR_TIM7EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN | RCC_AHB1ENR_DMA2EN;

    LED_PORT->MODER   |= 1 << (2 * LED_PIN);
    LED_PORT->OSPEEDR |= 3 << (2 * LED_PIN); // fastest speed

    CFG.tim->CR1 |= TIM_CR1_ARPE;
    CFG.tim->ARR = 16;
    CFG.tim->PSC = 1000;
    CFG.tim->EGR = TIM_EGR_UG; // Generate Update Event to copy ARR to its shadow
    CFG.tim->DIER |= TIM_DIER_UDE;
    CFG.stream->CR |= DMA_SxCR_CHSEL_NUM(CFG.channel) | DMA_SxCR_DIR_P2M | DMA_SxCR_PSIZE_WORD | DMA_SxCR_MSIZE_WORD;
    CFG.stream->NDTR = 16;
    CFG.stream->PAR = (uint32_t) &GPIOA->IDR;
    CFG.stream->M0AR = (uint32_t) &buf;
    CFG.stream->CR |= DMA_SxCR_EN;
    CFG.tim->CR1 |= TIM_CR1_CEN;

    // wait until DMA state changes
    while (CFG.dma->LISR == 0 && CFG.dma->HISR == 0)
        delay_ms(1);

    // check for any TEIFx bits
    int error = (CFG.dma->LISR | CFG.dma->HISR) & 0x02080208;

    while (1)
    {
        LED_PORT->ODR ^= 1 << LED_PIN;
        delay_ms(error ? 100 : 500);
    }
}

1 ответ

Здесь привыкли к ответу, но по какой-то причине его удалили. Спасибо автору, хотя.

Глядя на матрицу шин, становится ясно, что периферийная шина DMA1 подключена только к APB1. На самом деле это вообще не часть матрицы. Это, вероятно, означает, что DMA1 может обрабатывать только передачи от / к периферии APB1. Поскольку GPIO является периферийным устройством AHB, он не доступен из DMA1. Это также должно относиться к другим APB2 (например, SPI1) и периферийным устройствам AHB (например, OTGFS). Обычно не имеет смысла обращаться к периферийным устройствам AHB или APB2 из DMA1, потому что их запросы не маршрутизируются в DMA1. Тем не менее, это может быть необходимо для запутанных случаев, таких как GPIO по таймеру.

Я лично думаю, что этот момент можно сделать более очевидным в документации.

Другие вопросы по тегам