STM32 с нуля. Таймеры. Генерация ШИМ.

Чуть ранее (в этой статье) мы рассмотрели в общих чертах таймеры в STM32 и написали простенькую программку. Теперь, как и обещал, поподробнее покопаем генерацию ШИМ при помощи все того же таймера TIM4.

Итак…

Честно говоря, писать то особо нечего) Думаю многие знают что такое ШИМ и с чем его едят, а если нет то об этом можно прочитать, например, в Википедии, так что нет, наверное, смысла отдельно описывать то, что уже многократно и хорошо описано  =) Давайте писать пример для генерации ШИМ. Просто замутить такой сигнал не так интересно, так что давайте хоть немного усложним задачу. Будем генерировать ШИМ с разным заполнением в зависимости от состояния кнопки. Если кнопка нажата – генерируем сигнал с периодом 2.5 мс и заполнением 1.5 мс, а если кнопка не нажата – то период – 2.5 мс, заполнение – 0.5 мс. Ничего сложного )

Так что создаем новый проект и пишем код. Для начала набор includ’ов:

#include "stm32f10x.h" 
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"

Добавили все файлы, необходимые нам для работы.
Объявим переменные:

#define TIMER_PRESCALER		720
#define EXT_TIM_PULSE 	        150
#define TIM_PULSE		 50

TIMER_PRESCALER мы уже упоминали — это предделитель частоты.
EXT_TIM_PULSE – увеличенная длительность заполнения (то есть при нажатой кнопке), аналогично TIM_PULSE – обычная длительность (кнопка не нажата).
Продолжаем:

uint16_t previousState;
GPIO_InitTypeDef port;
TIM_TimeBaseInitTypeDef timer;
TIM_OCInitTypeDef timerPWM;
uint16_t buttonPreviousState;

Просто переменные, которые нам понадобятся в проекте. Пока все понятно )

И вот наконец-то кое-что поинтереснее, а именно наша разросшаяся функция initAll ().

void initAll()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
 
    GPIO_StructInit(&port);
    port.GPIO_Mode = GPIO_Mode_AF_PP;
    port.GPIO_Pin = GPIO_Pin_6;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOB, &port);
 
    port.GPIO_Mode = GPIO_Mode_IPD;
    port.GPIO_Pin = GPIO_Pin_3;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &port);
 
    TIM_TimeBaseStructInit(&timer);
    timer.TIM_Prescaler = TIMER_PRESCALER;
    timer.TIM_Period = 250;
    TIM_TimeBaseInit(TIM4, &timer);	
 
    TIM_OCStructInit(&timerPWM);
    timerPWM.TIM_Pulse = 50;
    timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
    timerPWM.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OC1Init(TIM4, &timerPWM);
}

Давайте прямо по строчкам смотреть, что же тут написано. Ну, вначале уже привычное включение тактирования необходимой периферии. Шестую ножку порта GPIOB настраиваем на работу в режиме альтернативной функции(!). Лезем в даташит на контроллер и видим, что альтернативной функцией у этого вывода является первый канал таймера TIM4. То что надо )

STM32 Таймеры

Вывод PA3 настраиваем на вход – там будет наша воображаемая кнопка. Почему именно третья ножка? Да фиг его знает, просто так ) Итак, с инициализацией портов закончили, идем настраивать таймер. Поначалу все конфигурируем, как и в предыдущем проекте. А вот дальше что-то новенькое:

TIM_OCStructInit(&timerPWM);
timerPWM.TIM_Pulse = 50;
timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
timerPWM.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC1Init(TIM4, &timerPWM);

Для использования режима генерации ШИМ нам понадобилась структура TIM_OCInitTypeDef. Поле TIM_Pulse – длительность заполнения, пусть будет сначала 50 тиков (0.5 мс). Далее задаем режим — TIM_OCMode_PWM1. Помимо PWM1 есть еще PWM2. Это всего лишь разные режимы ШИМ – с выравниванием по границе и по центру. В поле TIM_OutputState забиваем – Enable и инициализируем таймер. Готово!

Осталось немного )
Функция main() – как же без нее:

int main()
{
    __enable_irq ();
    initAll();
    buttonPreviousState = 0;
    TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM4, ENABLE);
    NVIC_EnableIRQ(TIM4_IRQn);
    while(1)
    {
    	__NOP();
    }		
}

Все, как и раньше – включаем прерывание по переполнению. Оно нам нужно, чтобы опрашивать кнопку именно в момент окончания периода ШИМ. Вот и сам код обработчика:

void TIM4_IRQHandler()
{	
    uint16_t button = 0;
    button = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3);		
    TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
    if ((button == 1) && (buttonPreviousState == 0))
    {
	TIM4->CCR1 = EXT_TIM_PULSE;		
	buttonPreviousState = 1;		
    }
    if ((button == 0) && (buttonPreviousState == 1))
    {
	TIM4->CCR1 = TIM_PULSE;		
	buttonPreviousState = 0;		
    }
}

Опрашиваем кнопку, ничего нового, знакомая функция ) Возможно возникнет вопрос – зачем так извращаться «(button == 1) && (buttonPreviousState == 0)» ? А чтобы работа с регистром таймера шла не на каждое прерывание, а только когда состояние кнопки изменяется. Упомянутая работа с регистром заключается в прямой записи длительности заполнения в регистр таймера TIM_CCR.

Компилируем, идем в отладчик, запускаем программу и эмулируем нажатие кнопки на PA3. Если кто забыл, открываем окно General Purpose I/O A (GPIOA) и вручную выставляем бит на входе PA3, то есть говорим контроллеру, что кнопка типа нажата, давай действуй =) В окошке логического анализатора видим:
Таймеры STM32
При изменении состояния кнопки меняется и скважность импульсов, как и задумывалось ) Ура, товарищи!

На сегодня это все, до скорого!

Понравилась статья? Поделись с друзьями!

STM32 с нуля. Таймеры. Генерация ШИМ.: 34 комментария
  1. А можно поподробней строчку:
    timerPWM.TIM_Pulse = 50;
    если туда вбить ноль?
    или 5000?

  2. извиняюсь за бестолковый вопрос, но где в главной функции вызывается обработчик TIM4_IRQHandler?

    • Это обработчик прерывания, он явно нигде не вызывается, в этом и фишка ) Он вызывается аппаратно при выполнении определенных условий, в этом примере когда таймер досчитал до значения — вызывается обработчик прерывания.

      • а какие еще функции, связанные с таймерами, могут вызываться аппаратно и где про них можно прочитать?

  3. А на установленных на плате светодиодах ШИМ не пробовали? На зарубежных форумах ппадалось, но не компилируется выдает ошибки.

    #include «stm32F10x.h»
    #include «STM32vldiscovery.h»

    int main(void)
    {
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    TIM_OCInitTypeDef TIM_OCInitStruct;

    volatile int i;
    int n = 1;
    int brightness = 0;

    RCC_APB2PeriphClockCmd(
    RCC_APB2Periph_GPIOC |
    RCC_APB2Periph_AFIO, ENABLE );

    RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM3, ENABLE );

    GPIO_StructInit(&GPIO_InitStructure); // Reset init structure

    // Setup Blue & Green LED on STM32-Discovery Board to use PWM.
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // Alt Function — Push Pull
    GPIO_Init( GPIOC, &GPIO_InitStructure );
    GPIO_PinRemapConfig( GPIO_FullRemap_TIM3, ENABLE ); // Map TIM3_CH3 to GPIOC.Pin8, TIM3_CH4 to GPIOC.Pin9

    // Let PWM frequency equal 100Hz.
    // Let period equal 1000. Therefore, timer runs from zero to 1000. Gives 0.1Hz resolution.
    // Solving for prescaler gives 240.
    TIM_TimeBaseStructInit( &TIM_TimeBaseInitStruct );
    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV4;
    TIM_TimeBaseInitStruct.TIM_Period = 1000 — 1; // 0..999
    TIM_TimeBaseInitStruct.TIM_Prescaler = 240 — 1; // Div 240
    TIM_TimeBaseInit( TIM3, &TIM_TimeBaseInitStruct );

    TIM_OCStructInit( &TIM_OCInitStruct );
    TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
    // Initial duty cycle equals 0%. Value can range from zero to 1000.
    TIM_OCInitStruct.TIM_Pulse = 0; // 0 .. 1000 (0=Always Off, 1000=Always On)

    TIM_OC3Init( TIM3, &TIM_OCInitStruct ); // Channel 3 Blue LED
    TIM_OC4Init( TIM3, &TIM_OCInitStruct ); // Channel 4 Green LED

    TIM_Cmd( TIM3, ENABLE );

    while(1) // Do not exit
    {
    if (((brightness + n) >= 1000) || ((brightness + n) CCR3 = brightness; // set brightness
    TIM3->CCR4 = 1000 — brightness; // set brightness

    for(i=0;i<10000;i++); // delay
    }

    return(0); // System will implode
    }

    • Не, на светодиодах платы не пробовал. Можно в принципе прям на ту ногу, которая в примере используется напаять все что угодно

  4. Если есть на плате светодиоды, чего изголяться… уже получилось, код ниже.
    #include «stm32f10x.h» /* «stm32f10x.h»
    int main(void)
    {
    volatile int i; // Переменная отсчета паузы
    int n = 1; // Шаг увеличения яркости
    int brightness = 0; // Яркость

    RCC->APB2ENR |= (RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN); //Тактирование порта C и альтернативных функций
    RCC->APB1ENR | =RCC_APB1ENR_TIM3EN;//Тактирование таймера TIM3

    GPIOC->CRH &= ~(GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_CNF8 | GPIO_CRH_CNF9); //очистить разряды MODE, CNF (поскольку после сброса мк задан режим «Input Floating»)
    GPIOC->CRH |= GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_CNF8_1 | GPIO_CRH_CNF9_1;//Максимальная скорость порта PC8,9 = 50 MHz — выход Push-Pull в режиме альтернативной функции
    AFIO->MAPR |=AFIO_MAPR_TIM3_REMAP_FULLREMAP; //Full remap (CH1/PC6, CH2/PC7, CH3/PC8, CH4/PC9) TIM3->PSC = 240-1; // Настройка предделителя таймера (24МГц/240Кгц=100 Кгц)
    TIM3->ARR = 1000-1; // задаем период ШИМ 100000/1000=100 Гц
    TIM3->CCMR2 |= (TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1); // OCxM = 110 — ШИМ режим 1(прямой ШИМ)на каналы 3 & 4
    TIM3->CCR3 = 0; // Начальная длительность импульса.
    /* разрешаем выходной сигнал */
    TIM3->CCER |= (TIM_CCER_CC3E | TIM_CCER_CC4E);
    TIM3->CR1 |= TIM_CR1_CEN; //Запускаем таймер!
    while(1)
    {
    if (((brightness + n) >= 1000) || ((brightness + n) CCR3 = brightness; // установим яркость
    TIM3->CCR4 = 1000 — brightness; // установим яркость

    for(i=0;i<10000;i++); // пауза
    }
    }

  5. 1) Если в прерывании я буду менять CCR1 по синусоидальному закону (брать значения из таблицы синуса), а выходной сигнал пропущу через фнч, то получится синусоидальное напряжение?

    2) Если использовать режим ШИМ выравнивание по центру, то как настроить прерывание?

    • С прерыванием все без изменений.
      По первому пункту — да, какое-то подобие синуса можно так получить)

  6. Всем привет. Не пойму как считать значения prescaler , TIM_Period и TIM_Pulse. Хочу попробовать управлять сервоприводом. Ему нужно 50 Гц. Как мне расчитать параметры, чтобы получилась нужная частота ШИМа?

  7. Здравствуйте, коллеги!

    Очень простой вопрос. Я хочу, чтобы мой процессор работал на частоте 168 МГц (очень надо). Еще я хочу, чтобы один из таймеров делал ШИМ на одной из ног с частотой порядка 2 МГц с предельно высокой точностью. Но получается, что точность ШИМа моего таймера будет определяться не точностью и стабильностью кварца, а тем, как работает модуль PLL???

  8. Может это конечно и тупой вопрос, но подскажите как высчитывать PRESCALER? Точнее от какой частоты? Вот не могу найти эту информацию в гугле, везде попадается только такие же примеры, как и тут. Спасибо.

  9. Я предполагал что с 8 битных AVRок будет тяжеловато переходить на 32 битные но все же.
    Переделал чуть код для Stm32F3discovery:
    #include «stm32f30x_gpio.h»
    #include «stm32f30x_rcc.h»
    #include «stm32f30x_tim.h»
    #include «stm32f30x.h»

    #define TIMER_PRESCALER 720
    #define EXT_TIM_PULSE 150 //
    #define TIM_PULSE 50

    uint16_t previousState;
    GPIO_InitTypeDef port;
    TIM_TimeBaseInitTypeDef timer;
    TIM_OCInitTypeDef timerPWM;
    uint16_t buttonPreviousState;

    void initAll()
    {
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // для PA0 ( User кнопка ) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // тактирую TIM2 на шине APB1

    GPIO_StructInit(&port);
    port.GPIO_Mode = GPIO_Mode_AF; // альтерн. ф-ция
    port.GPIO_Pin = GPIO_Pin_2; // PA2 I/O TTa —
    USART2_TX, TIM2_CH3, ( из даташит )
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &port);

    port.GPIO_Mode = GPIO_Mode_IN;
    port.GPIO_Pin = GPIO_Pin_0; // вход PA0 User кнопка
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &port);

    TIM_TimeBaseStructInit(&timer);
    timer.TIM_Prescaler = TIMER_PRESCALER;
    timer.TIM_Period = 250;
    TIM_TimeBaseInit(TIM2, &timer);

    TIM_OCStructInit(&timerPWM);
    timerPWM.TIM_Pulse = 50; // длительность заполнения
    timerPWM.TIM_OCMode = TIM_OCMode_PWM1; // режим
    timerPWM.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OC1Init(TIM2, &timerPWM);
    }

    int main()
    {
    __enable_irq ();
    initAll();
    //buttonPreviousState = 0;
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, ENABLE);
    NVIC_EnableIRQ(TIM2_IRQn);
    while(1)
    {
    __NOP();
    }
    }

    void TIM2_IRQHandler()
    {
    uint16_t button = 0;
    button = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    if ((button == 1) && (buttonPreviousState == 0))
    {
    TIM2->CCR1 = EXT_TIM_PULSE;
    buttonPreviousState = 1;
    }
    if ((button == 0) && (buttonPreviousState == 1))
    {
    TIM2->CCR1 = TIM_PULSE;
    buttonPreviousState = 0;
    }
    }

    Все вроде перепроверил нету на выходе ШИМа (((
    Aveal что я не так делаю? Заранее спасибо.
    P.S. канал для таймера указывать не надо? ( TIM2->CCER | = TIM_CCER_CC3E; )

    • Надо пошагово все проверить — работает ли вообще таймер, уходит ли на обработчик прерывания итд

  10. Разобрался! Необходимо было добавить
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_1); это для контроллеров серии STM32f3xx — f4xx.

    Теперь пытаюсь разобраться дальше с частотой тактирования и надеюсь на вашу помощь.
    Шина от которой тактирую таймер 2 имеет максимальную частоту 36 мгц ( по даташиту ).
    здесь gpio.GPIO_Speed = GPIO_Speed_50MHz; выставляется 50 мгц так какая частота будет по факту?

  11. Добрый день! я тоже разбираюсь с stm32 на плате F3Discovery. Подскажите, GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_1); где инициализировать эту функцию?
    Че то у меня нету шима по-прежнему

    • После инициализации вывода:

      GPIO_StructInit(&port);
      port.GPIO_Mode = GPIO_Mode_AF; // альтерн. ф-ция
      port.GPIO_Pin = GPIO_Pin_2; // PA2 I/O TTa —
      USART2_TX, TIM2_CH3, ( из даташит )
      port.GPIO_Speed = GPIO_Speed_2MHz;
      GPIO_Init(GPIOA, &port);

      GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_1);

      • Вот мой код, и всеравно не работает, Кнопка на РА0, таймер 4, альтернативный пин PORTD12, по даташиту канал 1 таймера. Че еще не так?

        #include «stm32f30x.h»
        #include «stm32f30x_rcc.h»
        #include «stm32f30x_gpio.h»
        #include «stm32f30x_tim.h»
        #define TIMER_PRESCALER 711
        #define EXT_TIM_PULSE 150
        #define TIM_PULSE 50
        uint16_t previousState;
        GPIO_InitTypeDef port;
        TIM_TimeBaseInitTypeDef timer;
        TIM_OCInitTypeDef timerPWM;
        uint16_t buttonPreviousState;
        void initAll()
        {
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE);
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
        GPIO_StructInit(&port);
        port.GPIO_Mode = GPIO_Mode_OUT;
        port.GPIO_OType = GPIO_OType_PP;
        port.GPIO_Pin = GPIO_Pin_12;
        port.GPIO_Speed = GPIO_Speed_2MHz;
        GPIO_Init(GPIOD, &port);
        GPIO_PinAFConfig(GPIOD,GPIO_PinSource15,GPIO_AF_2);

        port.GPIO_Mode = GPIO_Mode_IN;
        port.GPIO_Pin = GPIO_Pin_0;
        port.GPIO_Speed = GPIO_Speed_2MHz;
        GPIO_Init(GPIOA, &port);

        TIM_TimeBaseStructInit(&timer);
        timer.TIM_Prescaler = TIMER_PRESCALER;
        timer.TIM_Period = 250;
        TIM_TimeBaseInit(TIM4, &timer);

        TIM_OCStructInit(&timerPWM);
        timerPWM.TIM_Pulse = 50;
        timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
        timerPWM.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OC1Init(TIM4, &timerPWM);
        }

        int main()
        {
        __enable_irq ();
        initAll();
        buttonPreviousState = 0;
        TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
        TIM_Cmd(TIM4, ENABLE);
        NVIC_EnableIRQ(TIM4_IRQn);
        while(1)
        {
        __NOP();
        }
        }
        void TIM4_IRQHandler()
        {
        uint16_t button = 0;
        button = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
        TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
        if ((button == 1) && (buttonPreviousState == 0))
        {
        TIM4->CCR1 = EXT_TIM_PULSE;
        buttonPreviousState = 1;
        }
        if ((button == 0) && (buttonPreviousState == 1))
        {
        TIM4->CCR1 = TIM_PULSE;
        buttonPreviousState = 0;
        }
        }

        • Там ошибка, вместо GPIO_PinSource15 должно быть GPIO_PinSource12, но всеравно не работает

          • Из того, что сразу бросается в глаза — вывод надо настроить в режиме альтернативной функции, а не обычного вывода

        • ШИМ осциллографом смотришь? Надо проверить все поэтапно — работает ли сам таймер, переходит ли программа на обработчик прерывания…..

          • GPIO_Mode_AF точно. проглядел. Спасибо. теперь вроде работает. А частота импульсов теоретически должна быть около 333Гц, я правильно понимаю?

          • Я даже не знаю.. А где можно посмотреть или как выставить?

          • Можно опытным путем вычислить например — измерить период ШИМА и рассчитать из нее частоту шины) Вообще в настройках тактирования

  12. В проекте есть файл типа такого:
    startup_stm32f10x_md.s

    В нём все названия и описаны. По сути — это просто указатель на функцию, записаный в нужный адрес. Так же, как и прерывания в MS-DOS для PC.

  13. Здравствуйте. Вы не подскажите, как пользоваться логическим анализатором? Хотя бы, как увидеть такую же картинку, как в примере.

    • Я им только в ознакомительных целях пользовался…В новой версии Keil’а его может уже и нет, точно не знаю. Лучше в железе все-таки отлаживать.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *