Генерация ШИМ в STM32

В предыдущей статье про базовые таймеры, мы в очередной раз мигали светодиодами, а в этот раз пойдем гораздо дальше и попробуем вкурить как заставить контроллер STM32 генерировать ШИМ. Для этого нам придётся использовать один из таймеров общего назначения, ведь именно у них есть всё что для этого нужно. Весь остальной  функционал  этих таймеров конечно впечатляет, но в моей практике он пока не пригодился. Хотя возможно, что в будущем мне пригодятся такие полезные фичи как функция подсчёта внешних импульсов и возможность аппаратно обрабатывать повороты энкодера. Но пока займемся ШИМом. Есть вот такая схема из контроллера, трех резисторов и RGB светодиода которым мы будем управлять. Управление заключается в том, чтоб плавно зажечь и погасить каждый цвет. Разумеется можно взять три разных светодиода если нет RGB.
 
rgb leds
 
Мы подключили светодиод к этим выводам не случайно. Таймеры общего назначения могут генерировать ШИМ только на определённых ножках. Поскольку мы будем использовать таймер 2, то в нашем распоряжении есть 4 ноги (PA0-PA3).  Чтоб таймер мог их использовать нужно разрешить это аж в двух местах: Настроить три ноги (PA1-PA3) как выход с альтернативной функцией и разрешить в настройках таймера дергать эти ноги для генерации ШИМа. Для этого нам потребуется регистр CCER

TIMx CCER
Если установить в единицу один из битов выделенных синим цветом, то таймеру будет позволено использовать для ШИМа соответствующую ногу. Из схемы видно, что нам потребуется установить биты   CC2E, CC3E и CC4E. Теперь нам нужно настроить режим ШИМа: Прямой или инверсный (я не претендую на правильность терминологии). Разница вполне очевидна - при прямом ШИМе чем больше число в регистре сравнения - тем больше коэффициент заполнения ШИМа. В случае инверсного ШИМа все наоборот.  Записали ноль в регистр сравнения - коэффициент заполнения 100%. Для выбора режима используются два регистра CCMR1 и CCMR2:
 
 CCMR1 CCMR2
 
На настройку каждого канала выделяется аж по 8 бит! Но к счастью нам интересны только три бита OCxM[2:0] которые я отметил синим. То что отмечено серым - это те же самые биты но с другим названием, они используются если канал таймера работает в режиме захвата. Рассматривать все комбинации битов я не буду, так как большинство из них к ШИМу отношения не имеют. Нам потребуются только две комбинации бит: 
 

OCxM2

OCxM1

OCxM0

Режим

1

1

0

Прямой ШИМ

1

1

1

Инверсный ШИМ

 
RGB светодиод у меня с общим катодом и поэтому я использую инверсный ШИМ. Таким образом нам следует установить все три бита OCxM для трех каналов на которых висят светодиоды. И это всё! Настройка ШИМа закончена, теперь нужно только запустить таймер установив бит CEN в регистре CR1. Для управления скважностью просто пишем число от 0x0000 до 0xFFFF в регистры CCRx, где x номер канала. Собственно следующий код реализует то что было задумано в начале этой статьи: Наращивает яркость светодиода а потом снижает её до нуля и переходит к следующему. Процесс повторяется бесконечно, смотрится красиво :) 
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
void delay(void) {
  volatile uint32_t i;
    for (i=1; i != 0xF000; i++);
  }
 
int main()
{
  //Включем порт А
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
  //Включаем Таймер 2
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
 
  GPIO_InitTypeDef PORT;
  // Настроим ноги со светодиодами на выход
  PORT.GPIO_Pin = (GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3);
  //Будем использовать альтернативный режим а не обычный GPIO
  PORT.GPIO_Mode = GPIO_Mode_AF_PP;
  PORT.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_Init(GPIOA, &PORT);
  //Разрешаем таймеру использовать ноги PA1,PA2,PA3 для ШИМа
  TIM2->CCER |= (TIM_CCER_CC2E|TIM_CCER_CC3E|TIM_CCER_CC4E);
  // Для всех трех каналов задаем инверсный ШИМ.
  TIM2->CCMR1|=(TIM_CCMR1_OC2M_0| TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2);
  TIM2->CCMR2|=(TIM_CCMR2_OC3M_0 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2 |
  TIM_CCMR2_OC4M_0 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2);
  //Запускаем таймер!
  TIM2->CR1 |= TIM_CR1_CEN;
  //После этого пишем данные в TIM2->CCRx - и яркость светодиодов меняется
 
  uint32_t pwm_arr[]={0,0,6553,13107,19660,26214,32768,
                                  39321,45875,52428,58982,65535};
 
  uint8_t i;
  while(1)
  {
    for (i=1;i<=11;i++) {
        TIM2->CCR3=pwm_arr[i];
        delay();
    }
    for (i=11;i>=1;i--) {
      TIM2->CCR3=pwm_arr[i];
      delay();
    }
    for (i=1;i<=10;i++) {
      TIM2->CCR2=pwm_arr[i];
      delay();
    }
    for (i=11;i>=1;i--) {
      TIM2->CCR2=pwm_arr[i];
      delay();
    }
    for (i=1;i<=10;i++) {
      TIM2->CCR4=pwm_arr[i];
      delay();
    }
    for (i=11;i>=1;i--) {
      TIM2->CCR4=pwm_arr[i];
      delay();
    }
  }
}
Надеюсь, что этот код поможет вам запустить ШИМ на STM32 а мне он поможет не забыть то, что я раскуривал почти пол дня. Не сложные вопросы можно писать ниже.