STM32 Часть 10–Контроллер ПДП + Таймер 15 в режиме ШИМ

16 Марта 2011 К комментариям

На данный момент приостановились мои работы с STM32, так как пока приоритет у STM8L, но по просьбе пользователя LeftRadio, сегодня расскажу о контроллере прямого доступа к памяти (более привычно DMA) и в качестве примера рассмотрим работу контроллера ПДП с  таймером 15 в режиме ШИМ.

Попутно приглашаю всех принять участие в викторинах проводимых терраэлетроникой, мне удалось выиграть с четвертого раза :) :

image

Контроллер прямого доступа к памяти (ПДП)

Особенности:

ul> <li>семь независимых каналов <li>аппаратный приоритет для каналов <li>программный четырех уровневый приоритет для каждого канала <li>возможность цикличной работы <li>поддержка пересылки из памяти в память <li>число данных для передачи от 1 до 65536 <li>и т. д. </li></ul>

Функциональная схема:

image

Наибольший интерес представляют две таблицы. Первая таблица описывает все возможные форматы передачи между источником и приемником:

image

Вторая описывает соответствие сигналов периферийных модулей и каналов ПДП:

image

Как видим не так уж и гибко можно настраивать передачу данных (в LPC1768 более гибкий контроллер ПДП), но и этого достаточно при умелом использовании.

Работать с контроллером очень просто. Я да же не поверил что все работает как нужно когда описал и прошил :)

Согласно документации основные действия при конфигурировании любого канала:

  1. задать адрес приемника данных
  2. задать адрес источника данных
  3. указать количество данных для пересылки
  4. задать приоритет
  5. задать режим работы
  6. разрешить работу
  7. </ol> Мне кажется не плохо бы указывать первым пунктом разрешение работы модуля. Как видим все очень просто. Рассмотрим в качестве примера работу контроллера ПДП совместно с таймером 15. Пример И так цель: формировать произвольный ШИМ сигнал. Таймер 15 может работать только в режиме “PWM edge-aligned mode” по “нашему” ОШИМ (односторонний ШИМ), т.е. изменение ширины импульса происходит только в одну сторону, а не от центра импульса как при обычном ШИМ. Период (частота) определяется значением регистра автоперезагрузки TIMx_ARR (напомню этот регистр определяет значение при котором счетчик обнуляется) длительность импульса определяется значением в регистре TIMx_CCRx. Имеется возможность настраивать полярность ШИМ сигнала и ещё много чего (см. доку). Кратенько работу аналогичного таймера я уже описывал, поэтому не долго думаю и переношу код инициализации (с дополнениями): void mcu_tim15_init(void) { // разрешаем татирование модуля RCC->APB2ENR |= RCC_APB2ENR_TIM15EN; // устанавливаем предделитель TIM15->PSC = 10 - 1; // задаем период ШИМ TIM15->ARR = 255; // начальную длительность импульса TIM15->CCR1 = 1; // задаем режим работы ОШИМ TIM15->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE; // разрешаем выходной сигнал TIM15->CCER = TIM_CCER_CC1E; TIM15->BDTR = TIM_BDTR_MOE; // разрешаем запросы к ПДП TIM15->DIER = TIM_DIER_CC1DE; // разрешаем работу и автоперезагрузку TIM15->CR1 = TIM_CR1_CEN | TIM_CR1_ARPE; } Примечание: обычно я так сильно не комментирую код, но по просьбам трудящихся... По умолчанию выходной сигнал будет подключен к линии ввода-вывода CH1/PA2, но можно сделать ремап на CH1/PB14. Настроим линию ввода-вывода: #define TIM15_PWM1 A, 2, HIGH, ALTERNATE_OUTPUT_PUSH_PULL, SPEED_50MHZ PIN_CONFIGURATION(TIM15_PWM1); Ну вот осталось настроить ПДП:
    const uint8_t dma_buffer[] = { 64, 128, 20, 128, 64, 20 };
    
    //------------------------------------------------------------------------------
    void mcu_dma_channel5_init(void)
    {
    	// разрешаем тактирование ДМА1
    	if ((RCC->AHBENR & RCC_AHBENR_DMA1EN) != RCC_AHBENR_DMA1EN)
    	{
    		RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    	}
    
    	// зазадем адрес приемника данных
    	DMA1_Channel5->CPAR = (uint32_t) & TIM15->CCR1;
    	// задаем адрес источника данных
    	DMA1_Channel5->CMAR = (uint32_t) & dma_buffer[0];
    	// указываем число пересылаемых данных
    	DMA1_Channel5->CNDTR = ARRAY_LENGHT(dma_buffer);
    	// разрешаем работу + режим
    	DMA1_Channel5->CCR = DMA_CCR1_MINC | DMA_CCR1_CIRC | DMA_CCR1_DIR
    			| DMA_CCR1_EN | DMA_CCR1_PSIZE_0;
    }
    Что касается режимов: * DMA_CCR1_MINC – инкрементировать адрес источника * DMA_CCR1_CIRC – использовать цикличный режим, т.е. передавать данные по кругу * DMA_CCR1_DIR – направление данных “чтение из памяти” * DMA_CCR1_PSIZE = 1 – размер приемника данных 16 бит (см. доку) Логика работы проста: периодично по запросу таймера выбираем данные из буфера dma_buffer и помещаем их в регистр TIM15->CCR1 отвечающий за длительность импульса ШИМ, формируя тем самым последовательность импульсов с заданной длительностью. Конечно можно не использовать ПДП, а просто по соответствующему событию в обработчике прерывания изменять значение длительности, но тут как говориться все зависит от задачи. Код основной программы, для проверки: int main(void) { PIN_CONFIGURATION(TIM15_PWM1); mcu_dma_channel5_init(); mcu_tim15_init(); while (1) { } return 0; } Исходники не выкладываю так как они были ранее: 2011-01-02_tim17_interrupt Необходимо только исправить в майкфале неточность (спасибо читателю): FLAGS_LD = -Xlinker -Map=target/target.map FLAGS_LD += -Wl,--gc-sections Если что не понятно спрашивайте. Дополнение от 2011-06-27 Полный пример к приведенной выше статье: 2011-06-27-stm32-pwm-dma-demo


comments powered by Disqus