В этой короткой части мы рассмотрим режим MEM2MEM и научится с помощью DMA копировать одну область памяти в другую. Все примеры, как и всегда, для микроконтроллера stm32f103c8. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: https://dimoon.ru/category/obuchalka/stm32f1.
Режим MEM2MEM имеет три особенности в сравнении с обычным режимом работы DMA, когда мы отправляем какие-либо данные в периферию или принимаем что-либо из нее:
- Режим MEM2MEM не требует каких-либо запросов от периферийных модулей. После включения канала DMA процесс передачи начинается сразу.
- В регистр адреса памяти DMA_CMARx и регистр адреса периферии DMA_CPARx заносятся адреса областей памяти.
- Нельзя одновременно использовать режим MEM2MEM и кольцевой режим CIRC.
Итак, поехали! Для MEM2MEM передачи можно использовать любой свободный канал DMA, для примеры выберем 1-й. Создадим 2 массива data[] и buff[]:
uint8_t data[10]; uint8_t buff[sizeof(data)];
Через DMA будем data[] копировать в buff[]. Инициализируем массивы вот таким образом:
for(int i=0; i<sizeof(data); i++)
{
data[i] = i+1;
buff[i] = 0;
}
Не забываем включить тактирование DMA1 и на всякий случай отключить выбранный нами канал DMA:
RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Включаем тактирование DMA1 DMA1_Channel1->CCR &= ~DMA_CCR_EN; //Отключаем канал перед настройкой
Настройка регистров передачи:
DMA1_Channel1->CNDTR = sizeof(data); //сколько элементов копируем DMA1_Channel1->CMAR = (uint32_t)(data); //что копируем (адрес "памяти") DMA1_Channel1->CPAR = (uint32_t)(buff); //куда копируем (адрес "периферии")
Настройка канала DMA:
DMA1_Channel1->CCR =
DMA_CCR_MEM2MEM //из памяти в память
| DMA_CCR_MINC //инкремент памяти
| DMA_CCR_PINC //инкремент периферии
| DMA_CCR_DIR; //направление из "памяти" в "периферию"
Обращаю внимание на направление передачи данных. Так как в регистр адреса памяти мы занесли адрес массива, который собираемся копировать, то направление передачи у нас из «памяти» в «периферию». Ну и еще в сравнении с «обычным» режимом работы канала DMA, тут мы используем и инкремент адреса памяти, и инкремент адреса периферии.
Запустить процесс можно вот такой строчкой:
DMA1_Channel1->CCR |= DMA_CCR_EN; //запускаем процесс
Полный код функции:
uint8_t data[10];
uint8_t buff[sizeof(data)];
void main()
{
for(int i=0; i<sizeof(data); i++)
{
data[i] = i+1;
buff[i] = 0;
}
RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Включаем тактирование DMA1
DMA1_Channel1->CCR &= ~DMA_CCR_EN; //Отключаем канал перед настройкой
DMA1_Channel1->CNDTR = sizeof(data); //сколько элементов копируем
DMA1_Channel1->CMAR = (uint32_t)(data); //что копируем (адрес "памяти")
DMA1_Channel1->CPAR = (uint32_t)(buff); //куда копируем (адрес "периферии")
DMA1_Channel1->CCR =
DMA_CCR_MEM2MEM //из памяти в память
| DMA_CCR_MINC //инкремент памяти
| DMA_CCR_PINC //инкремент периферии
| DMA_CCR_DIR; //направление из "памяти" в "периферию"
DMA1_Channel1->CCR |= DMA_CCR_EN; //запускаем процесс
for(;;)
{
}
}
Если посмотреть в отладчике IAR-а значения массивов data[] и buff[], то перед запуском DMA-копирования они имели вот такие значения:

а после завершения процесса вот такие:

Все работает! 🙂
Стоит отметить, что если вместо
DMA1_Channel1->CMAR = (uint32_t)(data); //что копируем (адрес "памяти") DMA1_Channel1->CPAR = (uint32_t)(buff); //куда копируем (адрес "периферии")
сделать вот так:
DMA1_Channel1->CMAR = (uint32_t)(buff); //куда копируем (адрес "памяти") DMA1_Channel1->CPAR = (uint32_t)(data); //что копируем (адрес "периферии")
и в инициализации канала DMA убрать DMA_CCR_DIR:
DMA1_Channel1->CCR =
DMA_CCR_MEM2MEM //из памяти в память
| DMA_CCR_MINC //инкремент памяти
| DMA_CCR_PINC; //инкремент периферии
То все будет работать точно так же.
На этом пока все, продолжение следует! 😉