Фев 032013
 

Завершаем тему с практическими примерами формирования сигнала PWM на выходе порта. В этой статье будет дано краткая инструкция по инициализации таймера для формирования ШИМ сигнала на выходе. Только теперь вся движуха электронов внутри контроллера будут управляться функциями стандартных библиотек StdPeriph_Lib. Пример проекта сделан в IAR и проверен на плате STM32L-DISCOVERY. Для микроконтроллеров серии STM32F10x код будет отличаться только в секции инициализации портов, а для таймера код будет совершенно одинаковым.

В примере снова задействован таймер общего назначения TIM2. Выходной сигнал PWM формируется каналом Capture/Compare 1 на выводе PA0.

Создаем в IAR стандартный проект, и дополнительно подключаем следующие библиотечные файлы:

  • stm32l1xx_rcc.c
  • stm32l1xx_gpio.c
  • stm32l1xx_tim.c

Далее текст программы:

#include "stm32l1xx.h" 

GPIO_InitTypeDef GPIO_InitStructure; 
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 
TIM_OCInitTypeDef TIM_OCInitStructure; 

uint16_t F_output = 1000;//Частота выходного сигнала, Гц (задается) 
uint16_t duty_cycle = 10;//Коэффициент заполнения (duty cycle), от 0% до 100% (задается) 
uint16_t AutoReload_Value = 0;//Значение для записи в регистр TIM2_ARR (определяет период сигнала PWM, вычисляется в программе) 
uint16_t Pulse_Length = 0;//Значение для записи в регистр TIM2_CCR1 (определяет длительность импульса сигнала PWM, вычисляется в программе) 

int main()
 {
  /*Обновляем значение переменной SystemCoreClock - системной тактовой частоты, понадобится при дальнейших вычислениях*/ 
  SystemCoreClockUpdate();

  //Инициализация порта
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //Включаем тактирование GPIOA

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//Настраивать будем вывод PA0
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//Выход, альтернативная функция
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//Push-Pull - двухтактный
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//Без подтягивающих резисторов
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz;//Работает на максимальной частоте
  GPIO_Init(GPIOA, &GPIO_InitStructure);//Прописываем в регистры заданные значения
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2);//PA0 подключаем к TIM2

  //Инициализация таймера TIM2
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//Включаем тактирование TIM2
  /*Расчет значения для регистра автоперезагрузки, которым задается периoд сигнала PWM*/
  AutoReload_Value = (uint16_t) (SystemCoreClock/F_output) - 1;
  //Расчет значения для регистра CCR1, которым задается длительность импульса
  Pulse_Length = (uint16_t) ((AutoReload_Value*duty_cycle)/100);

  //Инициализация модуля Time base unit
  /*Задаем коэффициенты деления внешних предделителей тактовой частоты.
  В данном случае делители не используются,
  входная частота таймера равна системной частоте*/
  TIM_TimeBaseStructure.TIM_Prescaler = 0;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//Направление счета - инкремент
  /*Указываем, что в TIM2_ARR необходимо записать
  вычисленное значение для нужного периода сигнала*/
  TIM_TimeBaseStructure.TIM_Period = AutoReload_Value;
  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);//Записываем все заданные выше значения в регистры

 //Настройка канала Capture/Compare для формирования PWM сигнала на выходе
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//Режим PWM1
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//Разрешаем подключение к выводу мк
 /*Указываем, что в TIM2_CCR1 необходимо записать
  вычисленное значение для нужной длительности импульса*/
  TIM_OCInitStructure.TIM_Pulse = Pulse_Length;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//Положительная полярность
  TIM_OC1Init(TIM2, &TIM_OCInitStructure);//Записываем все заданные выше значения в регистры

  TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);//Разрешаем предварительную загрузку регистра сравнения
  TIM_ARRPreloadConfig(TIM2, ENABLE);//Разрешаем предварительную загрузку регистра автоперезагрузки
  /* TIM2 enable counter */
  TIM_Cmd(TIM2, ENABLE);//Запускаем счет таймера

  while (1);
 }

Сделаю небольшое уточнение по проекту. У меня в библиотеках добавлен хидер “stm32l1xx_conf.h”, который в стандартных библиотеках в папке /inc изначально отсутствует. Он там есть, но в другой папке, по адресу ProjectSTM32L1xx_StdPeriph_Templates. Для удобства я сразу переместил этот файл в папку LibrariesSTM32L1xx_StdPeriph_Driverinc, чтобы не прописывать лишние пути в препроцессоре при настройках. В этом файле подключаются сразу все необходимые заголовочные файлы с расширением .h, а также отменяются проверки корректности введенных параметров вида assert_param(). Подключается этот файл директивой USE_STDPERIPH_DRIVER в опциях препроцессора

USE_STDPERIPH_DRIVER

То же самое можно сделать, убрав символ комментария со строки /*#define USE_STDPERIPH_DRIVER*/ в файлах stm32l1xx.h или stm32f10x.h, в зависимости от типа контроллера.

Если же этот файл не подключен, то все необходимые “хидеры” надо подключать к проекту. Вот так, например, будет выглядеть начало файла main.c для данного проекта:

#include "stm32l1xx.h"
#include "stm32l1xx_rcc.h"
#include "stm32l1xx_gpio.h"
#include "stm32l1xx_tim.h"

А еще в конце файла придется вставить такой вот код

#ifdef  USE_FULL_ASSERT

/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t* file, uint32_t line)
{ 
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %drn", file, line) */

  /* Infinite loop */
  while (1)
  {
  }
}

который обрабатывает результат некорректной проверки, иначе при компиляции вы увидите большую кучу “warning”. Об этом я уже писал или в комментариях где-то было, на всякий случай решил напомнить.

Также выкладываю архив с проектом

Другие статьи:

  10 Responses to “STM32. General-Purpose Timers. Часть 3. Формирование сигнала PWM. Работа со стандартными библиотеками периферии.”

  1. вот тут <<>>
    кажися начальный TIM2 лишний.

  2. //Инициализация таймера
    TIM2 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//Включаем тактирование TIM2

  3. почему TIM3 ?
    мы же с тим2 вроде работаем:

    TIM_ARRPreloadConfig(TIM3, ENABLE)

  4. а можешь написать статью, как управлять этой платой сервой обычной китайской голубого цвета Micro Servo 9G?
    Я пытался твой пример адаптировать для кручения сервой, инициализацию оставил такую же, а кручу примерно так:

    uint16_t F_output = 50;//Частота выходного сигнала, Гц (задается)

    void set_pos(uint8_t duty_cycle) {
    Pulse_Length = (uint16_t) ((AutoReload_Value*duty_cycle)/100);
    TIM2->CCR1 = Pulse_Length;
    }

    //и в цикле меняю скважинность:
    for(i=16;i=16;i—){
    set_pos(i);
    delay_ms(30);
    }
    GPIO_TOGGLE(LD_PORT,LD_BLUE);

    , где 16…40 — промежуток скважинности, при которой серва хоть как-то вращается (при других значениях она начинает вращаться бесконечно либо вибрирует и греется сильно).
    Но даже в этом промежутке уродство получается — в прямой ход она не на 180 градусов поворачивается, а где-то на 60 градусов и рывками, а на обратном ходу она ещё в два раза меньше проворачивается, так же рывками.
    Подскажи пожалуйста, что надо менять для сервы, или напиши статью?

  5. У меня нет такой сервы, поэтому ни проверить, ни посоветовать ничего не могу.