Главная » Статьи » Уроки по программированию stm32fxxx » Уроки по программированию stm32f1xx |
Урок 7: 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, пока первый канал параллельно заполняет другой буфер. Теперь вы понимаете, насколько полезной является данная функция. А в микроконтроллере stm32f103 целых 2 DMA. Причём в первом DMA 7 каналов, а во втором - 5. К сожалению каждый канал прикреплён к своему периферийному устройству. Причём, как правило, один канал отвечает сразу за несколько периферийных устройств. Разумеется невозможно запрограммировать DMA на параллельную работу с двумя устройствами, принадлежащими одному каналу. Какой канал за какое устройство отвечает можно увидеть на рисунках 1 и 2, представленных ниже. Разобравшись с тем, что такое 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_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); Функцию RCC_AHBPeriphClockCmd можно найти в stm32f10x_rcc.c, а определение RCC_AHBPeriph_DMA1 в файле stm32f10x_rcc.h. Там же можно найти как включать тактовую для других периферийных устройств. При помощи CMSIS тактовая DMA включается так: if ((RCC->AHBENR & RCC_AHBENR_DMA1EN) != RCC_AHBENR_DMA1EN) RCC->AHBENR |= RCC_AHBENR_DMA1EN; Т.е. устанавливаем бит DMA1EN в регистре RCC_AHBENR. 2) Настройку UART я пока опущу. 3) Настройка DMA. Очень важная часть нашего урока. Именно при настройке вы указываете что, куда и как надо отправлять. Прежде всего сбросим предыдущие настройки DMA: DMA_DeInit(DMA1_Channel4); Теперь приступим непосредственно к настройке. Она выглядит следующим образом: DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)text; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = sizeof(text); DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_Low; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel4, &DMA_InitStructure); Разберём написанное подробнее: Прежде всего мы должны сказать системе указатель на регистр (именно указатель, а не сам регистр) периферийного устройства с которым хотим что бы работало DMA. В нашем случае это регистр для передачи данных UART1 носящий название DR. Значок & перед названием регистра говорит, что мы передаём адрес регистра в памяти а не значение регистра. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR); Теперь надо указать DMA область память, которая будет взаимодействовать с нашим UART. Для этого создадим массив и заполним его предложением. Перед название буфера не ставится &, т.к. название и есть указатель на массив. text[]="Hello World."; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)text; Когда мы определились с буферами с которыми будем работать, нужно описать направление взаимодействия. В нашем случае от памяти к периферии. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; В StdPeriph описаны следующие варианты, которые можно использовать при инициализации DMA_DIR: DMA_DIR_PeripheralDST - направление от памяти к периферии. DMA_DIR_PeripheralSRC - направление от периферии к памяти. Далее указываем размер данных, которые мы хотим передать. В нашем случае размер всего буфера: DMA_InitStructure.DMA_BufferSize = sizeof(text); У DMA есть полезная функция увеличения на 1 значения указателя на каждой итерации. причём как для памяти, так и для периферии. Это связано с тем, что DMA можно использовать для копирования данных из одного массива в другой. В нашем случае мы включаем увеличение номера указателя для памяти и запрещаем увеличение для UART. мы ведь должны писать все данные в один и тот же регистр. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; Теперь надо указать размерность данных. В нашем случае мы передаём данные размером в 1 байт, что и отмечаем в настройках: DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; Ещё бывает DMA_PeripheralDataSize_HalfWord и DMA_PeripheralDataSize_Word. Теперь выбираем режим передачи. В нашем случае обычный. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; Существует 2 режима: DMA_Mode_Normal - однократное срабатывание DMA. DMA_Mode_Circular - многократное срабатывание DMA. В этом случае DMA может выдавать прерывание, но продолжит повторять свою работу вне зависимости отреагируете ли вы на него до тех пор пока вы его не выключите. Теперь определяем приоритет: DMA_InitStructure.DMA_Priority = DMA_Priority_Low; И отключаем режим передачи от памяти к памяти для нашего случая. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; Записываем всю структуру в функцию. Как можно видеть из таблицы на рис 1, нам надо инициализировать 4-й канал DMA1, т.к. именно он отвечает за выход UART. DMA_Init(DMA1_Channel4, &DMA_InitStructure); Разрешаем UART работать в DMA. USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); И устанавливаем прерывания от DMA по окончанию передачи: DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE); Есть 3 типа прерывания по DMA: DMA_IT_TC - По окончанию передачи. DMA_IT_HT - По передаче половины данных. DMA_IT_TE - При передаче возникла ошибка. 7) Включаем общие прерывания в NVIC. NVIC_EnableIRQ(DMA1_Channel4_IRQn); Инициализация окончена. Та же инициализация на CMSISi выглядит так: DMA1_Channel4->CMAR = (uint32_t) &text[0]; //адрес буфера передатчика DMA1_Channel4->CPAR = (uint32_t) &USART1->DR; //адрес регистра данных передатчика DMA1_Channel4->CNDTR = sizeof(text); //для передатчика DMA1_Channel4->CCR = DMA_CCR4_DIR | DMA_CCR4_MINC; //чтение из памяти, инкремент указателя в памяти DMA1_Channel4->CCR |= DMA_CCR4_TEIE; //канал 4 USART1->CR3 |= USART_CR3_DMAT; //разрешить передачу USART1 через DMA Чтобы включить DMA набираем: DMA1_Channel4->CCR |= DMA_CCR1_EN; Если же вы хотите перезапустить DMA, то придётся несколько усложнить функцию: DMA1_Channel4->CCR &= (uint16_t)(~DMA_CCR1_EN);//Выключаем DMA. DMA1_Channel4->CNDTR = sizeof(text);//Устанавливаем размер передаваемого буфера заново. DMA1_Channel4->CCR |= DMA_CCR1_EN;//Включаем DMA После этих строк DMA, будет работать и в конце выдаст прерывание вида: void DMA1_Channel4_IRQHandler(void) { DMA1->IFCR |= DMA_ISR_TCIF4;//очистить флаг окончания обмена. } Флаг надо сбросить, иначе прерывания будут генерироваться бесконечно. Собственно с работой UART через DMA всё. Вот пример работы с памятью: DMA_DeInit(DMA1_Channel4); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)text2; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)text; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = sizeof(text); DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_Low; DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; DMA_Init(DMA1_Channel4, &DMA_InitStructure); В этом примере строка из text переписывается в text2. При этом были внесены изменения: 1) Вместо UART подставлен буфер text2. 2) Разрешено увеличивать номер указателя text2. 3) Разрешено использовать работу память - память. Теперь программа скопирует данные в text2 из text. Удачной работы с важной функцией. | |
Просмотров: 27524 | Комментарии: 4 | |
Всего комментариев: 4 | ||||
| ||||