Главная » Статьи » Уроки по программированию 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, представленных ниже. 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); После этой строки ДМА передаст данные и попадёт в прерывание. На это всё. Остальное можно посмотреть на видео: | |
Просмотров: 13767 | Комментарии: 6 | |
Всего комментариев: 6 | ||||||
| ||||||