Stm32 dma ошибка

I use STM32F407VTG6 controller and try to receive data from SPI using DMA. Then I want to process data on DMA Complete Transfer Interrupt. But when Complete Transfer Interrupt is occurred I see that TEIF (transfer error interrupt flag) is set. After this DMA can’t be started. This is part of my code:

static void DmaInit()
{
 DMA_InitTypeDef dma;

 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);

 // DMA for Rx
 dma.DMA_Channel = DMA_Channel_3;
 dma.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
 dma.DMA_Memory0BaseAddr = 0; // will be set later
 dma.DMA_DIR = DMA_DIR_PeripheralToMemory;
 dma.DMA_BufferSize = 1; // will be set later
 dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
 dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
 dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
 dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
 dma.DMA_Mode = DMA_Mode_Normal;
 dma.DMA_Priority = DMA_Priority_High;
 dma.DMA_FIFOMode = DMA_FIFOMode_Disable;
 dma.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
 dma.DMA_MemoryBurst = DMA_MemoryBurst_Single;
 dma.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
 DMA_DeInit(DMA2_Stream2);
 DMA_Init(DMA2_Stream2, &dma);

 // Enable DMA Interrupt on complete transfer
 NVIC_EnableIRQ(DMA2_Stream2_IRQn);
 DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);
}

// It run on external interrupt
static void DmaStart(uint32_t bufferSize, uint32_t* rxBuffer)
{ 
 // Start DMA for reading
 DMA2_Stream2->NDTR = bufferSize;
 DMA2_Stream2->M0AR = (uint32_t)rxBuffer;
 DMA_Cmd(DMA2_Stream2, ENABLE);
}

static void SpiInit()
{
  SPI_InitTypeDef spi;

  // Enable clock for SPI
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

  // SPI settings
  SPI_StructInit(&spi);
  spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  spi.SPI_Mode = SPI_Mode_Master;
  spi.SPI_DataSize = SPI_DataSize_8b;
  spi.SPI_CPOL = SPI_CPOL_Low;
  spi.SPI_CPHA = SPI_CPHA_2Edge;
  spi.SPI_NSS = SPI_NSS_Soft;
  spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
  spi.SPI_FirstBit = SPI_FirstBit_MSB;
  SPI_Init(SPI1, &spi);
  SPI_Cmd(SPI1, ENABLE);

  SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
}

asked May 10, 2014 at 1:30

Goshik's user avatar

1

I just finished my SDIO+DMA this days, some notes maybe useful for you:

  1. Clear Flags before enabling the stream

    DMA_ClearFlag(DMA2_Stream2, DMA_FLAG_FEIF2|DMA_FLAG_DMEIF2|DMA_FLAG_TEIF2|DMA_FLAG_HTIF2|DMA_FLAG_TCIF2);
    
  2. Clear EN bit in the DMA_SxCR Register, Wait Until the EN bit is read as 0 before DMA_Init()

    DMA_Cmd(DMA2_Stream2, DISABLE);
    while (DMA2_Stream2->CR & DMA_SxCR_EN);
    
  3. When use DMA_FIFOMode_Disable (Direct Mode), data width is determined by DMA_PeripheralDataSize (PSIZE), DMA_MemoryDataSize (MSIZE) is ignored

  4. Memory address must be aligned to your selected data width (HalfWord)

Reference:

  1. STM32F407XX Reference Manual — Chapter 10 DMA Controller
  2. STM32F4XX Standard Peripheral Library

answered May 12, 2014 at 0:41

Xiaobin's user avatar

1

I use STM32F407VTG6 controller and try to receive data from SPI using DMA. Then I want to process data on DMA Complete Transfer Interrupt. But when Complete Transfer Interrupt is occurred I see that TEIF (transfer error interrupt flag) is set. After this DMA can’t be started. This is part of my code:

static void DmaInit()
{
 DMA_InitTypeDef dma;

 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);

 // DMA for Rx
 dma.DMA_Channel = DMA_Channel_3;
 dma.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
 dma.DMA_Memory0BaseAddr = 0; // will be set later
 dma.DMA_DIR = DMA_DIR_PeripheralToMemory;
 dma.DMA_BufferSize = 1; // will be set later
 dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
 dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
 dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
 dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
 dma.DMA_Mode = DMA_Mode_Normal;
 dma.DMA_Priority = DMA_Priority_High;
 dma.DMA_FIFOMode = DMA_FIFOMode_Disable;
 dma.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
 dma.DMA_MemoryBurst = DMA_MemoryBurst_Single;
 dma.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
 DMA_DeInit(DMA2_Stream2);
 DMA_Init(DMA2_Stream2, &dma);

 // Enable DMA Interrupt on complete transfer
 NVIC_EnableIRQ(DMA2_Stream2_IRQn);
 DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);
}

// It run on external interrupt
static void DmaStart(uint32_t bufferSize, uint32_t* rxBuffer)
{ 
 // Start DMA for reading
 DMA2_Stream2->NDTR = bufferSize;
 DMA2_Stream2->M0AR = (uint32_t)rxBuffer;
 DMA_Cmd(DMA2_Stream2, ENABLE);
}

static void SpiInit()
{
  SPI_InitTypeDef spi;

  // Enable clock for SPI
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

  // SPI settings
  SPI_StructInit(&spi);
  spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  spi.SPI_Mode = SPI_Mode_Master;
  spi.SPI_DataSize = SPI_DataSize_8b;
  spi.SPI_CPOL = SPI_CPOL_Low;
  spi.SPI_CPHA = SPI_CPHA_2Edge;
  spi.SPI_NSS = SPI_NSS_Soft;
  spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
  spi.SPI_FirstBit = SPI_FirstBit_MSB;
  SPI_Init(SPI1, &spi);
  SPI_Cmd(SPI1, ENABLE);

  SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
}

asked May 10, 2014 at 1:30

Goshik's user avatar

1

I just finished my SDIO+DMA this days, some notes maybe useful for you:

  1. Clear Flags before enabling the stream

    DMA_ClearFlag(DMA2_Stream2, DMA_FLAG_FEIF2|DMA_FLAG_DMEIF2|DMA_FLAG_TEIF2|DMA_FLAG_HTIF2|DMA_FLAG_TCIF2);
    
  2. Clear EN bit in the DMA_SxCR Register, Wait Until the EN bit is read as 0 before DMA_Init()

    DMA_Cmd(DMA2_Stream2, DISABLE);
    while (DMA2_Stream2->CR & DMA_SxCR_EN);
    
  3. When use DMA_FIFOMode_Disable (Direct Mode), data width is determined by DMA_PeripheralDataSize (PSIZE), DMA_MemoryDataSize (MSIZE) is ignored

  4. Memory address must be aligned to your selected data width (HalfWord)

Reference:

  1. STM32F407XX Reference Manual — Chapter 10 DMA Controller
  2. STM32F4XX Standard Peripheral Library

answered May 12, 2014 at 0:41

Xiaobin's user avatar

1

Direct memory access (DMA), или прямой доступ к памяти (ПДП) используется для быстрой передачи данных между памятью и периферийным устройством, памятью и памятью, или между двумя периферийными устройствами без участия процессора. В микроконтроллере STM32F103c8 доступен один контроллер DMA1 с 7-ю каналами. DMA2 присутствует только в микроконтроллерах high-density и XL-density. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: https://dimoon.ru/category/obuchalka/stm32f1.

Содержание

  • Функциональное описание DMA
  • Приоритеты каналов DMA
  • Каналы DMA
  • Выравнивание данных разной разрядности
  • Особенности обращения к периферии AHB/APB
  • Кольцевой режим DMA (Circular mode)
  • Режим «Из памяти в память» (Memory-to-memory mode)
  • Прерывания DMA
  • Ошибки при передаче данных по DMA
  • Каналы DMA и периферия

Функциональное описание DMA

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

При возникновении определенного события, периферийное устройство отправляет сигнал запроса в контроллер DMA. После этого запускается процесс обмена данными, который состоит из 3-х шагов:

  1. Загрузка данных из регистра периферийного устройства (если направление передачи из периферии в память) или загрузка данных из памяти (если направление передачи из памяти в периферию);
  2. Сохранение загруженных данных в память (если из периферии в память) или в периферийное устройство (если из памяти в периферию);
  3. Уменьшение значения регистра DMA_CNDTRx на единицу. Как только этот регистр станет равен нулю, то передача данных завершится.

Приоритеты каналов DMA

DMA1 в микроконтроллерах STM32F103c8 имеет 7 каналов, причем в конкретный момент времени передача данных может осуществляться только по одному из них. Однако, если активно несколько каналов, то при одновременном возникновении запросов DMA передача будет запущена для того канала, приоритет которого выше. Приведу небольшой пример. Пусть у нас 3-й канал DMA1 настроен на передачу массива данных в SPI1, а 1-й канал на прием от ADC1. Установим приоритет канала 3 больше, чем у канала 1. В этом случае, если запросы от SPI1 и ADC1 возникнут одновременно, то сначала будет обработан запрос от SPI1 (3-й канал), а уже потом от ADC1 (1-й канал). То есть одновременно включать несколько каналов DMA можно, но одновременно вести передачу может только один из них.

Приоритеты можно настраивать программно, всего 4-е градации:

  • Very high priority (Очень высокий приоритет)
  • High priority (Высокий приоритет)
  • Medium priority (Средний приоритет)
  • Low priority (Низкий приоритет)

При одинаковом уровне приоритета (например, 1-й и 3-й канал настроили на Very high priority) канал с меньшим номером будет иметь приоритет над каналом с большим номером (канал 1 будет иметь бОльший приоритет)

Каналы DMA

Каждый канал DMA имеет следующие регистры:

  • DMA_CCRx — регистр конфигурации канала DMA. В нем содержатся биты конфигурации передачи данных, биты разрешения прерывания от канала и бит включения канала.
  • DMA_CNDTRx — сколько кадров данных подлежит передаче (0..65535). Я намеренно употребил слово «кадр», а не «байт». Дело в том, то есть возможность настроить количество байт данных, которые передаются за одну транзакцию (настраивается в регистре DMA_CCRx), об этом поговорим далее.
  • DMA_CMARx — адрес памяти. Если направление передачи из периферии в память, то сюда будем записывать данные, если обратно, то читать.
  • DMA_CPARx — адрес периферийного устройства. Если направление передачи из периферии в память, то отсюда будем читать данные, если обратно, то записывать. В режиме Memory-to-memory сюда так же записывается адрес памяти.

Так, вроде все понятно: имеем 4-е регистра, с помощью которых можно настроить пересылку данных туда-сюда.

Теперь самое время поговорить о такой вещи как инкремент адреса памяти и периферии. Все примеры буду приводить для SPI. Инкремент адреса периферии чаще всего не имеет смысла, а вот инкремент памяти очень полезен. Пусть в регистр DMA_CMARx занесен адрес нулевой ячейки массива, который мы ходим отправить в SPI (вспоминаем указатели Си). После каждой отправки данных в SPI внутренний указатель памяти канала DMA будет увеличиваться на 1 элемент массива. Тут стоит отметить один важный момент: инкремент производится внутреннего указателя, который недоступен программно для чтения или записи, регистр DMA_CMARx не меняет своего значения в процессе передачи данных. 

На примере SPI работать это будет вот так. В регистр DMA_CMARx занесли адрес нулевого элемента массива, который хотим отправить, в DMA_CPARx адрес регистра данных DR модуля SPI. В DMA_CNDTRx записали количество байт для передачи. Включили инкремент адреса памяти, в модуле SPI разрешили запрос к DMA на передачу данных и запустили процесс, установкой бита EN в регистре DMA_CCRx. В начальном состоянии передатчик SPI пуст, флаг пустого передатчика устанавливается в единицу, это провоцирует зарос DMA. DMA получает запрос от SPI, после этого читает байт данных из массива и записывает его в регистр DR интерфейса SPI, увеличивает внутренний указатель памяти на один элемент массива (1 байт) и уменьшает значение регистра DMA_CNDTRx на единицу. После того, как SPI выплюнет байт данных, процесс повторится. Все это будет продолжаться до тех пор, пока значение DMA_CNDTRx не станет равно нулю. После этого канал DMA завершит передачу и больше не будет реагировать на запросы от SPI.

Но это для случая, если нам надо передавать данные в периферию по одному байту. А что делать, если у нас разрядность массива 2 байта, и периферия хочет на вход 2 байта тоже?

Для таких случаев в регистре DMA_CCRx есть конфигурационные биты разрядности периферийного регистра (PSIZE) и разрядности данных в памяти (MSIZE). Они могут принимать следующие значения:

  • 00: 8-bits
  • 01: 16-bits
  • 10: 32-bits
  • 11: Reserved (эта комбинация не используется)

То есть, если мы поставим MSIZE=16 бит (2 байта), то за раз мы будем отправлять уже 2 байта, и указатель на адрес памяти будет увеличиваться на 2. А вот регистр DMA_CNDTRx все так же будет уменьшаться на единицу, так как он содержит не количество байт для передачи, а количество самих передач (транзакций). Получается, что MSIZE нужен для того, чтобы сказать DMA, на сколько байт увеличивать внутренний указатель на адрес памяти. Все верно, но MSIZE  используется и еще для одной вещи.

Выравнивание данных разной разрядности

Очень часто бывают ситуации, когда разрядность приемника данных не совпадает с разрядностью источника. Например, в модуле SPI разрядность регистра данных DR равна 16 бит (2 байта, или полуслово). Однако, SPI у нас может быть настроен на передачу 8-и бит за раз и мы имеем массив данных для передачи, с разрядностью 8 бит. DMA позволяет настроить независимо разрядность передатчика и приемника данных. Как было сказано выше, с помощью битов MSIZE мы задаем разрядность данных в памяти. Но есть еще биты PSIZE, которыми надо указать разрядность регистра периферийного устройства (8, 16 или 32 бита). Если PSIZE не равен MSIZE, то DMA будет производить автоматическое выравнивание данных по следующим правилам.

Пусть разрядность источника данных 8 бит, а приемника 16. Тогда при пересылке DMA добавит 8 незначащих нулей к данным из источника и запишет их в приемник: из источника прочитали, например, 0x13, а в приемник записали 0x0013. В случае, если разрядность источника больше разрядности приемника, то DMA обрежет лишние старшие биты у данных из источника, и в приемник запишет только младшие биты: если разрядность источника 32 бита, а приемника 8 бит, то DMA прочтет из источника значение, например, 0xABCDEF12, а в приемник попадет 0x12. В принципе, все как при присвоении значений переменным в Си.

В Reference manual на микроконтроллеры STM32F1xxx в разделе про DMA есть вот такая таблица:

Programmable data width and endian behavior

Рис. 1. Правила выравнивания данных DMA

Таблица из Reference manual-а может показаться довольно замысловатой (на самом деле это так и есть 😉 ).Давайте разберемся в ней на одном из случае. Например, источник данных у нас 32 бита, а приемник 16 бит:

Рис. 2. Преобразование 32-х битных значений к 16-и битным

Пусть мы пересылаем данные из одного массива в памяти в другой посредством DMA. Причем эти массивы разной разрядности. Массив-источник имеет 32 разряда и содержит следующие данные:

  • элемент 0: B3B2B1B0, смещение 0x00
  • элемент 1: B7B6B5B4, смещение 0x04
  • элемент 2: BBBAB9B8, смещение 0x08
  • элемент 3: BFBEBDBC, смещение 0x0C

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

В качестве массива это будет выглядеть вот так:

uint32_t Sourse[4] = 
{
  0xB3B2B1B0,
  0xB7B6B5B4,
  0xBBBAB9B8,
  0xBFBEBDBC
};

Ну и приемник данных:

uint16_t Destination[4];

А DMA будет выполнять вот такую операцию:

for(int i=0; i<4; i++)
  Destination[i] = Sourse[i];

Думаю, что для людей, знакомых с Си, будет понятен результат такой операции:

uint16_t Destination[4] = 
{
  0xB1B0,
  0xB5B4,
  0xB9B8,
  0xBDBC
};

В принципе, все то же самое справедливо и для пересылки данных из памяти в регистр периферии, только в том случае не используется инкремент адреса периферии.

Особенности обращения к периферии AHB/APB

Тут есть одна очень важная и не очевидная особенность архитектуры микроконтроллеров STM32. CPU в STM32 является 32-х разрядным, и для записи в память 8, 16 или 32-х бит существуют разные команды и разные запросы на запись. Для ОЗУ ни каких проблем не существует: мы можем выполнять 8, 16 и 32-х разрядные запросы к памяти. А вот к периферии AHB/APB можно обращаться только 32-х битными запросами. А если нам надо выполнить запись в регистр, который имеет разрядность меньше, чем 32 бита? Объясню на примере все того же SPI. Регистр данных DR у него имеет разрядность 16 бит, и старшие 16 бит 32-х разрядной шины просто не используются:

SPI register map and reset values

Рис. 3. Карта регистров SPI

Если в DMA мы настроим разрядность периферии PSIZE = 16 бит, и разрядность памяти MSIZE = 16 бит, то DMA продублирует младшие 16 бит в старшие и произведет 32-х битный запрос к периферии:

0xABCD -> 0xABCDABCD

Т.е. из 0xABCD DMA сделает 0xABCDABCD и это значение отправится в периферию. И так как старшие 16 бит регистра DR не используются (зарезервированы), то старшие 16 бит просто проигнорируются. Так же можно настроить PSIZE = 32 бита, и тогда в регистр DR будет занесено значение 0x0000ABCD. А вот если PSIZE установить 8 бит, то DMA сделает следующее преобразование:

0xAB -> 0xABABABAB

Таким образом, в DR будет занесено 0xABAB а не 0x00AB, как можно подумать, если не знать этих особенностей. Вот как раз из-за того, что к периферии можно обращаться только 32-битными запросами, все регистры в периферии выравнены по границе 32 бита (см. рис. 3).

Кольцевой режим DMA (Circular mode)

Думаю, все знакомы с кольцевым буфером. Его очень удобно использовать при непрерывном приеме/передаче данных. В DMA микроконтроллеров STM32 такой режим работы реализован аппаратно, и включается он битом CIRC в регистре управления DMA_CCRx. Если этот режим активирован, то после передачи всех данных по DMA (после того, как DMA_CNDTRx станет равно нулю), регистр DMA_CNDTRx заново перезагружается исходным значением и передача продолжается.

Режим «Из памяти в память» (Memory-to-memory mode)

В «обычном» режиме канал DMA ждет запроса на передачу данных от какого-либо периферийного модуля (SPI, ADC, таймер, и т.д.) Однако, канал DMA может работать и без запроса от периферии, т.е. передача начнется сразу после установки бита EN в регистре DMA_CCRx. Этот режим может использоваться для копирования одной области памяти в другую. Для этого необходимо в регистры DMA_CPARx и DMA_CMARx занести адреса массивов, над которыми необходимо выполнить операцию копирования, и установить бит MEM2MEM в регистре DMA_CCRx. Получается, что и регистру адреса периферии, и регистру адреса памяти присваиваются адреса массивов в памяти. При пересылке MEM2MEM можно использовать любой свободный канал DMA. А как выбирается направление передачи? Точно так же, как и при обмене данными с периферией: битом DIR регистра DMA_CCRx. Пример передачи из памяти в память будет в одной из следующих статей, там, где мы перейдем к практике. Стоит отметить, что нельзя использовать режим MEM2MEM одновременно с Circular mode.

Прерывания DMA

Каждый канал DMA имеет 3 прерывания:

  • Half-transferDMA передал половину данных, удобно при потоковой передаче данных совместно с кольцевым режимом: пока передаем одну половину массива, заполняем другую.
  • Transfer complete — прерывание о завершении передачи данных.
  • Transfer error — прерывание об ошибке передачи.

Ошибки при передаче данных по DMA

Ошибка DMA может возникнуть при чтении/записи в зарезервированное адресное пространство микроконтроллера STM32. При возникновении ошибки соответствующий канал DMA отключается (сбрасывается бит EN) и возникает прерывание Transfer error (если разрешено).

Каналы DMA и периферия

DMA1 в микроконтроллерах STM32F103C8 имеет 7 каналов передачи данных, причем на каждом канале висит своя периферия. Приведу таблицу из Reference manual, чтоб было понятнее:

Summary of DMA1 requests for each channel

Рис. 4. Каналы DMA и соответствующие им запросы от периферийных устройств

Например, 1-й канал может обслуживать запросы от ADC1, TIM2_CH3 и TIM4_CH1, а 2-й канал от SPI1_RX, USART3_TX, TIM1_CH1, TIM2_UP и TIM3_CH3. Стоит отметить, что сами запросы должны быть разрешены в регистрах периферийных устройств, при этом, если разрешить DMA-запрос от 2-х источников, то обмен данными будет запускаться от 2-х разных запросов. С ходу не смогу привести пример, где это может быть полезно, и скорее всего такая конфигурация не имеет смысла.

На этом пока все, в следующей статье будет описание регистров DMA, а уже потом перейдем к практике. Продолжение следует!!! 😉

Продолжение.

Code here is configuring DMA2 for transferring 160 samples each from two analog channels from ADC1 to memory.

The ADC,DMA,NVIC etc. seem properly configured but for some reason I am getting transfer errors exactly as the ADC is turned on.

More precisely, the TEIF0 flag for DMA2 and OVR flag for ADC1 is raised exactly after ADC_Cmd in the second snippet is called.

When looking for causes for Transfer errors in RM0090 all it states is:

Transfer error:
the transfer error interrupt flag (TEIFx) is set when:

– A bus error occurs during a DMA read or a write access

– A write access is requested by software on a memory address register in Double
buffer mode whereas the stream is enabled and the current target memory is the
one impacted by the write into the memory address register (refer to
Section 10.3.9: Double buffer mode)

The definition isn’t very informative. A link to a similar issue can be found here: https://www.mikrocontroller.net/topic/389574

is it possible that the issue may be coming from the startup code? The code here was ported from a project that originally uses is compiled on Linux system with GNU makefile. But the same code was successfully compiled and run on the same chip with Keil MDK and all other functionality like LEDs, GPIO ports seems to be running well.

Debugging the code with Keil I also have access to the register values if they’re needed.

Timer2 for external triggering of ADC is configured like so:

static void tim2_config(int fs_divisor)
{
  TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;

  /* TIM2 Periph clock enable */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

  /* Time base configuration */

  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
   TIM_TimeBaseStructure.TIM_Period = fs_divisor - 1;
  TIM_TimeBaseStructure.TIM_Prescaler = 0;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

  /* TIM2 TRGO selection */

  TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);

  /* TIM2 enable counter */

  TIM_Cmd(TIM2, ENABLE);
}

The ADC and DMA are then configured like so:

void adc_configure(){
    ADC_InitTypeDef  ADC_init_structure;
    GPIO_InitTypeDef GPIO_initStructre;
    DMA_InitTypeDef  DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // Clock configuration

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOAEN,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);

    // Analog pin configuration ADC1->PA1, ADC2->PA2

    GPIO_initStructre.GPIO_Pin =  GPIO_Pin_1 | GPIO_Pin_2;
    GPIO_initStructre.GPIO_Mode = GPIO_Mode_AN;
    GPIO_initStructre.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA,&GPIO_initStructre);

    // ADC structure configuration

    ADC_DeInit();
    ADC_init_structure.ADC_DataAlign = ADC_DataAlign_Left;
    ADC_init_structure.ADC_Resolution = ADC_Resolution_12b;
    ADC_init_structure.ADC_ContinuousConvMode = DISABLE;
    ADC_init_structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
    ADC_init_structure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
    ADC_init_structure.ADC_NbrOfConversion = 2;
    ADC_init_structure.ADC_ScanConvMode = ENABLE;
    ADC_Init(ADCx,&ADC_init_structure);

    // Select the channel to be read from

    ADC_RegularChannelConfig(ADCx,ADC_Channel_1,1,ADC_SampleTime_144Cycles);
    ADC_RegularChannelConfig(ADCx,ADC_Channel_2,2,ADC_SampleTime_144Cycles);
    //ADC_VBATCmd(ENABLE);

    DMA_DeInit(DMA_STREAMx);
    DMA_InitStructure.DMA_Channel = DMA_CHANNELx;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADCx_DR_ADDRESS;
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)adc_buf;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
    DMA_InitStructure.DMA_BufferSize = ADC_BUF_SZ;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA_STREAMx, &DMA_InitStructure);

    /* Enable DMA request after last transfer (Single-ADC mode) */

    ADC_DMARequestAfterLastTransferCmd(ADCx, ENABLE);

    /* Enable ADC1 DMA */

    ADC_DMACmd(ADCx, ENABLE);

    /* DMA2_Stream0 enable */

    DMA_Cmd(DMA_STREAMx, ENABLE);

    /* Enable DMA Half & Complete interrupts */

    DMA_ITConfig(DMA2_Stream0,DMA_IT_TC | DMA_IT_HT, ENABLE);

    /* Enable the DMA Stream IRQ Channel */

    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // Enable and start ADC conversion

    ADC_Cmd(ADC1,ENABLE);
    ADC_SoftwareStartConv(ADC1);
}

DMA is not working on STM32H7 devices, or the transmitted/received data are corrupted. Polling and interrupt based methods for the same peripheral configuration are working.

The problem is related to two things: memory layout on STM32H7 and internal data cache (D-Cache) of the Cortex-M7 core. 

In summary these can be the possible issues:

  • Memory placed in DTCM RAM for D1/D2 peripherals. Unfortunately this memory is used as default in some projects including examples.
  • Memory not placed in D3 SRAM4 for D3 peripherals.
  • D-Cache enabled for DMA buffers, different content in cache and in SRAM memory.
  • Starting the DMA just after writing the data to TX buffer, without placing __DSB() instruction between.

For Ethernet related problems, please see separate FAQ: FAQ: Ethernet not working on STM32H7x3 

1. Explanation: memory layout

The STM32H7 device consists of three bus matrix domains (D1, D2 and D3) as seen on the picture below. The D1 and D2 are connected through bus bridges, both can also access data in D3 domain. However there is no connection from D3 domain to D1 or D2 domain.

The DMA1 and DMA2 controllers are located in D2 domain and can access almost all memories with exception of ITCM and DTCM RAM (located at 0x20000000). This DMA is used in most cases. 

BDMA controller is located in D3 domain and can access only SRAM4 and backup SRAM in the D3 domain.

MDMA controller is located in D1 domain and can access all memories, including ITCM/DTCM. This controller is mainly used for handling D1 peripherals and memory to memory transfers.

698.png

From performance point of view it is better to put DMA buffers inside D2 domain (SRAM1, SRAM2 and SRAM3), since the D2-to-D1 bridge can add additional delay.

2. Explanation: handling DMA buffers with D-Cache enabled

The Cortex-M7 contains two internal caches, I-Cache for loading instructions and D-Cache for data. The D-Cache can affect the functionality of DMA transfers, since it will hold the new data in the internal cache and don’t write them to the SRAM memory. However the DMA controller loads the data from SRAM memory and not D-Cache.

In case the DMA transfer is started just after writing the data to the tx_buffer in the code, it can happen that the tx_buffer data will be still in write-buffer inside the CPU, while the DMA is already started. Solution can be to set the tx_buffer as device type and force CPU to order the memory operations, or add __DSB() instruction before starting the DMA.

There are several ways how to keep manage DMA buffers with D-Cache:

  • Disable D-Cache globally. It is the most simple solution, but not effective one, since you can loose great part of performance. Can be useful for debugging, to analyze if the problem is related to D-Cache.
  • Disable D-Cache for part of the memory. This can be done by configuring the memory protection unit (MPU). The downside is that the MPU regions have certain alignment restrictions and you need to place the DMA buffers to specific parts of memory. Each toolchain (GCC, IAR, KEIL) needs to be configured in different way.
    • Note that MPU regions can overlap and the higher region number has priority. Together with subregion disable bits, this can be useful to soften the alignment and size restrictions.
    • Note that Device and Strongly ordered memory types not allow unaligned access to the memory.
  • Configure part of memory as write-through. Can be used only for TX DMA. Similar to the previous option.
  • Use cache maintenance operations. It is possible to write data stored in cache back to memory («clean» operation) for specific address range, and also discard data stored in cache («invalidate» operation).
    • The downside is that these operations work withe cache-line size which is 32 bytes, so you can’t clean or invalidate single byte from the cache. This can lead to errors when RX buffer «shares» the cache line with other data or TX buffer (please see the picture below).
    • Beware that with uninitialized D-Cache, the maintenance operations «clean» or «clean and invalidate» can lead to BusFault exception. This is caused by uninitialized ECC (error correction code) after power-on reset. If you have project with a lot of maintenance operations and want to disable D-Cache temporarily, you can use SCB_InvalidateDCache function, which will clean the cache and set correct ECC, without enabling it.

700.png

Below are the possible MPU configurations. Green are configurations suitable for DMA buffers, blue is suitable only for TX-only DMA buffer and red are forbidden. Other configurations are not suitable for DMA buffers and will require cache maintenance operations:

Laurids_PETERSEN_0-1690194613355.png

3. Solution example 1: Simple placement of all memory to D1 domain

D-Cache must be disabled globally for this solution to work.

GCC (Atollic TrueStudio/System Workbench for STM32/Eclipse)

Replace DTCMRAM with RAM_D1 for section placement in linkerscript (.ld file extension). E.g. like this:

.data : 
{
  ... /* Keep same */
} >RAM_D1 AT> FLASH

this should be done also for .bss and ._user_heap_stack sections.

In some linkerscripts, the initial stack is defined separately. So you either need to update it with the section, or define it inside the section like:

._user_heap_stack :
{
 . = ALIGN(8);
 PROVIDE ( end = . );
 PROVIDE ( _end = . );
 . = . + _Min_Heap_Size;
 . = . + _Min_Stack_Size;
 _estack = .; /* <<<< line added */
 . = ALIGN(8);
} >RAM_D1

and remove the original _estack definition.

IAR (in project settings):

701.png

For Keil:

702.png

4. Solution example 2: Placing buffers in separated memory part

D-Cache must be disabled via MPU for that particular memory region, where DMA buffer is placed. Please note that MPU region size must be in power of two. Also the regions start address must have same alignment as size. E.g. if the regions is 512 bytes, the start address must be aligned to 512 bytes (9 LSBs must be zero).

NOTE: IAR compiler and Keil compiler version <= 5 allow placing variables at absolute address in code using compiler specific extensions.

C code:

Define placement macro:

#if defined( __ICCARM__ )
  #define DMA_BUFFER \
      _Pragma("location=\".dma_buffer\"")
#else
  #define DMA_BUFFER \
      __attribute__((section(".dma_buffer")))
#endif

Specify DMA buffers in code:

DMA_BUFFER uint8_t rx_buffer[256];

GCC linkerscript (*.ld file)

Place section to D2 RAM (you can also specify your own memory regions in linkerscript file):

.dma_buffer : /* Space before ':' is critical */
{
  *(.dma_buffer)
} >RAM_D2

This is without default value initialization. Otherwise you need to place special symbols and add your own initialization code.

IAR linker file (*.icf file)

define region D2_SRAM2_region   = mem:[from 0x30020000 to 0x3003FFFF];
place in D2_SRAM2_region { section .dma_buffer};
initialize by copy { section .dma_buffer}; /* optional initialization of default values */

Keil scatter file (*.sct file)

LR_IROM1 0x08000000 0x00200000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00200000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM2 0x24000000 0x00080000  {  ; RW data
   .ANY (+RW +ZI)
  }
  ; Added new region
  DMA_BUFFER 0x30040000 0x200 {
  *(.dma_buffer)
  }
}

Generation of scatter file should be disabled in Keil:

703.png

5. Solution example 3: Use Cache maintenance functions

Transmitting data:

#define TX_LENGTH  (16)
uint8_t tx_buffer[TX_LENGTH];

/* Write data */
tx_buffer[0] = 0x0;
tx_buffer[1] = 0x1;

/* Clean D-cache */
/* Make sure the address is 32-byte aligned and add 32-bytes to length, in case it overlaps cacheline */
SCB_CleanDCache_by_Addr((uint32_t*)(((uint32_t)tx_buffer) & ~(uint32_t)0x1F), TX_LENGTH+32);

/* Start DMA transfer */
HAL_UART_Transmit_DMA(&huart1, tx_buffer, TX_LENGTH);

Receiving data:

#define RX_LENGTH  (16)
uint8_t rx_buffer[RX_LENGTH];

/* Invalidate D-cache before reception */
/* Make sure the address is 32-byte aligned and add 32-bytes to length, in case it overlaps cacheline */
SCB_InvalidateDCache_by_Addr((uint32_t*)(((uint32_t)rx_buffer) & ~(uint32_t)0x1F), RX_LENGTH+32);

/* Start DMA transfer */
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_LENGTH);

/* No access to rx_buffer should be made before DMA transfer is completed */

Please note that in case of reception there can be problem if rx_buffer is not aligned to the size of cache-line (32-bytes), because during the invalidate operation another data sharing the same cache-line(s)  with rx_buffer can be lost.

6. References

  • «AN4838: Managing memory protection unit (MPU) in STM32 MCUs»
  • «AN4839: Level 1 cache on STM32F7 Series and STM32H7 Series»:
  • «AN4296: Overview and tips for using STM32F303/328/334/358xx CCM RAM with IAR EWARM, Keil MDK-ARM and GNU-based toolchains»:
  • «AN4891: STM32H7x3 system architecture and performance software expansion for STM32Cube»:

Понравилась статья? Поделить с друзьями:
  • Stk500 ошибка ардуино
  • Steering ошибка субару что означает
  • Steam код ошибки 105
  • Steering wheel electronics ошибка
  • Still ошибка d5043