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. Мне кажется не плохо бы указывать первым пунктом разрешение работы модуля. Как видим все очень просто. Рассмотрим в качестве примера работу контроллера ПДП совместно с таймером 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