Главная » Статьи » Уроки по программированию stm32fxxx » Уроки по программированию stm32f4xx

Урок 8: DMA
Данный урок будет посвящён более необычной функции МК, чем все рассмотренные ранее. Из-за этого и сама тема урока несколько сложнее предыдущих.
Прежде всего несколько слов о том, что такое DMA.
DMA (Direct Memory Access - прямой доступ к памяти) позволяет передавать блоки данных минуя процессор. Т.е. раньше, если необходимо скопировать блок данных из одного буфера в другой, копирование производилось в цикле вида:

for (i=0;i<size;i++)
{
     A[i] = B[i];
}

Если Size - огромное число, то копирование отнимало много времени. Если же процессор параллельно должен был опрашивать кнопки, мигать светодиодом и т.д., данные процессы останавливались на время копирования. Это неудобно и не оптимально.
При использовании DMA вы просто говорите ему, что, куда и сколько копировать и говорите когда начинать. По окончании выполнения работы DMA оповестит об этом процессор. Который всё время работы DMA мог заниматься любыми другими делами.
Другой жизненный пример - передача данных через UART. Зачастую встаёт задача передачи больших объёмов данных, к примеру информации, снятой с датчика. Но нельзя тратить время на передачу, т.к. тогда процессор не будет снимать данные с датчика (Если это важно).
В этом случае хорошо использовать DMA для передачи уже снятой информации. можно даже попробовать настроить DMA, которое сначала будет снимать данные с датчика, записывая их в определённую область памяти, а по окончанию записи заданного числа данных, указать другому каналу DMA начать копировать данные из этого буфера с UART, пока первый канал параллельно заполняет другой буфер.
Теперь вы понимаете, насколько полезной является данная функция. А в микроконтроллере stm32f4 целых 2 DMA по 7 потоков в каждом. К каждому потоку может быть подключен один из 7-ми каналов.
К сожалению каждый канал прикреплён к своему периферийному устройству. Причём, как правило, один канал отвечает сразу за несколько периферийных устройств. Разумеется невозможно запрограммировать DMA на параллельную работу с двумя устройствами, принадлежащими одному каналу. Для решения этой проблемы каждое периферийное устройство дублировано в таблицах ДМА.
Какой канал за какое устройство отвечает можно увидеть на рисунках 1 и 2, представленных ниже.


рис. 1



рис. 2



DMA может передавать данные от периферии к памяти, от памяти к периферии и от памяти к памяти. ДМА не может передавать данные от периферии к периферии.
Разобравшись с тем, что такое DMA, перейдём непосредственно к настройке. Она производится по стандартной схеме:
1) Включаем тактирование DMA.
2) Настраиваем DMA.
3) Если надо включаем работу DMA у периферии.
3) Когда надо включаем DMA.
4) Ждём срабатывания прерывания от DMA.

Теперь рассмотрим всё это на примере передачи данных по UART. Для этого проделаем следующие шаги: 1) Включаем тактирование DMA.
2) Включаем тактирование UART
3) Настраиваем DMA.
4) Настраиваем UART.
5) Включаем прерывания от DMA.
6) Включаем прерывания от UART.
7) Включаем общие прерывания с DMA и UART. 8) Разрешаем работу UART через DMA.

На этом инициализация заканчивается. Сам алгоритм программы будет такой:
1) Принимаем байт с ПК.
2) Включаем DMA.
3) Ждём окончания работы DMA.
4) Сбрасываем флаг окончания работы DMA.

Начнём с настройки:
1) Тактирование включается следующим образом:

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

Функцию RCC_AHB1PeriphClockCmd можно найти в stm32f4xx_rcc.c, а определение RCC_AHB1Periph_DMA1 в файле
stm32f4xx_rcc.h. Там же можно найти как включать тактовую для других периферийных устройств.

2) Настройку UART я пока опущу.
3) Настройка DMA. Очень важная часть нашего урока. Именно при настройке вы указываете что, куда и как надо отправлять.

Настройка выглядит следующим образом:

DMA_InitTypeDef DMA_ini_USART;

DMA_ini_USART.DMA_Channel = DMA_Channel_4;
DMA_ini_USART.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->DR);
DMA_ini_USART.DMA_Memory0BaseAddr = (uint32_t)buffer;
DMA_ini_USART.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_ini_USART.DMA_BufferSize = sizeof(buffer);
DMA_ini_USART.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_ini_USART.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_ini_USART.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_ini_USART.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_ini_USART.DMA_Mode = DMA_Mode_Normal;
DMA_ini_USART.DMA_Priority = DMA_Priority_Medium;
DMA_ini_USART.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_ini_USART.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
DMA_ini_USART.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_ini_USART.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

DMA_Init(DMA1_Stream6, &DMA_ini_USART);

Разберём написанное подробнее:

Прежде всего мы должны сказать системе указатель на регистр (именно указатель, а не сам регистр) периферийного устройства с которым хотим что бы работало DMA. В нашем случае это регистр для передачи данных USART2 носящий название DR. Значок & перед названием регистра говорит, что мы передаём адрес регистра в памяти а не значение регистра.
DMA_ini_USART.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->DR);

Теперь надо указать DMA область память, которая будет взаимодействовать с нашим USART. Для этого создадим массив и заполним его предложением. Перед название буфера не ставится &, т.к. название и есть указатель на массив.

char buffer[]="I am DMA!";

DMA_ini_USART.DMA_Memory0BaseAddr = (uint32_t)buffer;

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

DMA_ini_USART.DMA_DIR = DMA_DIR_MemoryToPeripheral;

В StdPeriph описаны следующие варианты, которые можно использовать при инициализации DMA_DIR:

DMA_DIR_PeripheralToMemory - направление от памяти к периферии.
DMA_DIR_MemoryToMemory - направление от периферии к памяти.

Далее указываем размер данных, которые мы хотим передать. В нашем случае размер всего буфера:

DMA_ini_USART.DMA_BufferSize = sizeof(buffer);

У DMA есть полезная функция увеличения на 1 значения указателя на каждой итерации. причём как для памяти, так и для периферии. Это связано с тем, что DMA можно использовать для копирования данных из одного массива в другой. В нашем случае мы включаем увеличение номера указателя для памяти и запрещаем увеличение для UART. мы ведь должны писать все данные в один и тот же регистр.

DMA_ini_USART.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_ini_USART.DMA_MemoryInc = DMA_MemoryInc_Enable;

Теперь надо указать размерность данных. В нашем случае мы передаём данные размером в 1 байт, что и отмечаем в настройках:

DMA_ini_USART.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_ini_USART.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

Ещё бывает DMA_PeripheralDataSize_HalfWord и DMA_PeripheralDataSize_Word.

Теперь выбираем режим передачи. В нашем случае обычный.

DMA_ini_USART.DMA_Mode = DMA_Mode_Normal;

Существует 2 режима:
DMA_Mode_Normal - однократное срабатывание DMA.
DMA_Mode_Circular - многократное срабатывание DMA. В этом случае DMA может выдавать прерывание, но продолжит повторять свою работу вне зависимости отреагируете ли вы на него до тех пор пока вы его не выключите.

Теперь определяем приоритет:

DMA_ini_USART.DMA_Priority = DMA_Priority_Low;

Отключаем FIFO и ставим пакетную передачу на single.
DMA_ini_USART.DMA_Priority = DMA_Priority_Medium;
DMA_ini_USART.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_ini_USART.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
DMA_ini_USART.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_ini_USART.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

Записываем всю структуру в функцию. Как можно видеть из таблицы на рис 1, нам надо инициализировать 4-й канал DMA1, т.к. именно он отвечает за выход UART.

DMA_Init(DMA1_Stream6, &DMA_ini_USART);

Разрешаем UART работать в DMA.

USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);

И устанавливаем прерывания от DMA по окончанию передачи:

NVIC_EnableIRQ(DMA1_Stream6_IRQn);

DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE);

Всё. Теперь осталось только описать функцию прерывания:

void DMA1_Stream6_IRQHandler(void)
{
if (DMA_GetITStatus(DMA1_Stream6, DMA_IT_TCIF6) == SET)
{
DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);


}
}

Где if (DMA_GetITStatus(DMA1_Stream6, DMA_IT_TCIF6) == SET) проверяет флаг прерывания.

DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6); - сбрасывает этот флаг.

И вот мы вплотную приблизились к включению ДМА.

DMA_Cmd(DMA1_Stream6, ENABLE);

После этой строки ДМА передаст данные и попадёт в прерывание.
На это всё. Остальное можно посмотреть на видео:



Категория: Уроки по программированию stm32f4xx | Добавил: Korvin (27.12.2013)
Просмотров: 13554 | Комментарии: 6 | Рейтинг: 4.0/1
Всего комментариев: 6
6 SIvan32  
0
Спасибо за статью.
Заметил досадную ошибку: рис. 1 и рис. 2 одинаковы.

4 sts-5  
0
Отличный видеоролик. впечатляет умение работать с библиотекой.

В интернете примеров Перифирия-Память и наоборот достаточно много, Память-Память ничего не нашёл.
Подскажите пожалуйста, как организовать настройку DMA в режиме Память-Память.
В программе присутствуют следующие строки.

int massiv_out[65];        // Массив для передачи данных
int massiv _in [65];         // Массив для приёма данных
#define  DMA_Memory_0   ((uint32_t)0x00000000)
#define  DMA_Memory_1   ((uint32_t)0x00080000)


DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)& massiv_in;
DMA_InitStructure.DMA_Memory1BaseAddr = (uint32_t)& massiv_out;


Компилятор на нижнюю строку выдаёт ошибку: DMA_InitStructure.DMA_Memory1BaseAddr = (uint32_t)& massiv_out;
Используется Nucleo- F411RE.

С уважением, Сергей.

5 Korvin  
0
Здравствуйте.
Организовать работу память-память гораздо проще.
Указываете в обоих случаях (и для памяти и для периферии) указатели на память, ставите копирование из памяти в память и инкрементацию обоих указателей.

DMA_ini_USART.DMA_Channel = DMA_Channel_4;
DMA_ini_USART.DMA_PeripheralBaseAddr = (uint32_t)_out;
DMA_ini_USART.DMA_Memory0BaseAddr = (uint32_t)_in;
DMA_ini_USART.DMA_DIR = DMA_DIR_MemoryToMemory;
DMA_ini_USART.DMA_BufferSize = sizeof(_in);
DMA_ini_USART.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_ini_USART.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_ini_USART.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_ini_USART.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_ini_USART.DMA_Mode = DMA_Mode_Normal;
DMA_ini_USART.DMA_Priority = DMA_Priority_Medium;
DMA_ini_USART.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_ini_USART.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
DMA_ini_USART.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_ini_USART.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

Например так.

2 Anton  
0
Не поминаю я про что вы пишите...
Вот алгоритм:
На этом инициализация заканчивается. Сам алгоритм программы будет такой:
1) Принимаем байт с ПК.
2) Включаем DMA.
3) Ждём окончания работы DMA.
4) Сбрасываем флаг окончания работы DMA.

А потом про передачу массива пошла речь. А как быть с приёмом то опишите пожалуйста.

3 Korvin  
0
Принимайте с помощью прерываний.

1 Anton  
0
Спасибо за неплохое описание DMA. Частично помогло понять, как настроить его для работы с SDIO... хорошее дело делаете)

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]