В этой короткой части мы рассмотрим режим 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; //инкремент периферии
То все будет работать точно так же.
На этом пока все, продолжение следует! 😉