MyTetra Share
Делитесь знаниями!
гидра
FreeRTOS. PWM
Время создания: 22.05.2018 14:30
Раздел: Electronics - Microcontrollers - STM32 - CMSIS

Смотрим по даташиту на каком порту висит интересующий нас таймер с функцией ШИМ. Рассмотрим на примере TIM2_CH2. Согласно ДШ он на PA1, тактируется от шины APB1.


Объявляем:


void PWM_init(void);


Заполняем функцию инициализации.

Включаем тактирование порта А:


RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //enable RCC for PortA


Включаем тактирование для таймера 2:


RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; //enable TIM2 clocking


Находим Figure 26. Selecting an alternate function и видим, что таймеру 2 соответствует AF1 младшего регистра AFRL. Включаем альтернативную функцию:


//PA1 alternate function AF1 (TIM2 = 0001)

GPIOA->AFR[0] &= ~GPIO_AFRL_AFSEL1; //clr all bits

GPIOA->AFR[0] |= GPIO_AFRL_AFSEL1_0; //set bit 0


Переключаем порт в альтернативный режим:


//PA1 Alternate Push-Pull (10)

GPIOA->MODER &= ~GPIO_MODER_MODE1;

GPIOA->MODER |= GPIO_MODER_MODE1_1;


Настраиваем предделитель PSC. В регистре TIMx_PSC записывается значение, исходя из формулы:

fCK_PSC / (PSC[15:0] + 1), т.е. на единицу меньше. Если требуется предделитель, равный 10 000, то в регистр записывается значение 9 999.

Реализуем через дефайн:


#define TIM2_PRESC 24

TIM2->PSC = TIM2_PRESC - 1;


Приод счета ARR, т.е. значение, до которого таймер будет считать, начиная с 0. Для точности вычитаем 1 из значения, т.к. отсчет идет с 0. Сделаем через define:


#define TIM2_ARR_VALUE 1000

TIM2->ARR = TIM2_ARR_VALUE - 1;


Начальный коэффициент заполнения (CCRx) или, проще говоря, скважность. В регистре CCR2 (отвечает за OC2) указываем значение, при достижении которого выход изменит значение (на какое именно см. далее).


#define TIM2_CCR_VALUE 500

TIM2->CCR2 = TIM2_CCR_VALUE - 1; //compare/caprute value


Учитывая, что ARR = 1000, то с CCR = 500 скважность (коэф. заполнения) будет равен 50%. При CCR = 300, коэф. заполнения = 30%.


Настраиваем полряность. Каналы 1 и 2 настраиваются через регистр CCMR1. За упомянутый выше ОС2 отвечают биты OC2M. В РМ есть описание битов OC1M, согласно которому имеется два режима PWM. В 1-м режиме 1 соответствует положительному уровню на выходе, во 2-м режиме - нулевому уровню.

Т.е. в 1-м режиме пока значение таймера < значения CCR, на выходе будет 1. Во 2-м режиме наоборот.

Используем прямую полярность PWM, т.е. режим 1, которому соответствуют биты 110.


//PWM mode 1 (110)

TIM2->CCMR1 |= TIM_CCMR1_OC2M;

TIM2->CCMR1 &= ~TIM_CCMR1_OC2M_0;


Включаем канал 2 для нашего таймера через регистр CCER.


TIM2->CCER |= TIM_CCER_CC2E; //enable channel 2 output


Активный уровень - высокий (необязательный параметр).


TIM2->CCER &= ~TIM_CCER_CC2P; //polarity active high


Настраиваем направление счета. Счет может быть восходящим (от 0 до ARR) или нисходящим (от ARR до 0).

Определяется битом DIR в регистре TIMx_CR1.


TIM2->CR1 &= ~TIM_CR1_DIR; //counter used as upcounter


Запускаем генерацию. Бит CEN регистра TIMx_CR1.


TIM2->CR1 |= TIM_CR1_CEN; //counter enable


Полный код инициализации:


#define TIM2_ARR_VALUE 1000 // 1 kHz

#define TIM2_CCR_VALUE 500 // 100 Hz

#define TIM2_PRESC 24


void PWM_init(void);


void PWM_init(void)

{

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //enable RCC for PortA

RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; //enable TIM2 clocking

//PA1 alternate function AF1 (TIM2 = 0001)

GPIOA->AFR[0] &= ~GPIO_AFRL_AFSEL1; //clr all bits

GPIOA->AFR[0] |= GPIO_AFRL_AFSEL1_0; //set bit 0

//PA1 Alternate Push-Pull (10)

GPIOA->MODER &= ~GPIO_MODER_MODE1;

GPIOA->MODER |= GPIO_MODER_MODE1_1;

TIM2->PSC = TIM2_PRESC - 1; //prescaler

TIM2->ARR = TIM2_ARR_VALUE - 1; //auto-reload value

TIM2->CCR2 = TIM2_CCR_VALUE - 1; //compare/caprute value

//PWM mode 1 (110)

TIM2->CCMR1 |= TIM_CCMR1_OC2M;

TIM2->CCMR1 &= ~TIM_CCMR1_OC2M_0;

TIM2->CCER |= TIM_CCER_CC2E; //enable channel 2 output

TIM2->CCER &= ~TIM_CCER_CC2P; //polarity active high

TIM2->CR1 &= ~TIM_CR1_DIR; //counter used as upcounter

TIM2->CR1 |= TIM_CR1_CEN; //counter enable

}


Программа с плавным розжигом/гашением (пульсирующий) светодиода:


#include "stm32f4xx.h"

#include "FreeRTOS.h"

#include "task.h"

#include "queue.h"


#define TIM2_ARR_VALUE 1000 // 1 kHz

#define TIM2_CCR_VALUE 100 // 100 Hz

#define TIM2_PRESC 24


uint32_t gCCR_VALUE = TIM2_CCR_VALUE;


void RCC_init(void);

void GPIO_init(void);

void PWM_init(void);

//***FreeRTOS functions

void vTaskPwmDemo(void *argument);


int main(void)

{

RCC_init();

GPIO_init();

PWM_init();


xTaskCreate(vTaskPwmDemo, "PWM", 32, NULL, 1, NULL);

vTaskStartScheduler();

while(1)

{

/*Code Section for Error Indication*/

}

}


void RCC_init(void)

{

RCC->CR |= (RCC_CR_HSEON); //Enable HSE

while( !(RCC->CR & RCC_CR_HSERDY) ) {}; //ready to start HSE


//FLASH

FLASH->ACR |= FLASH_ACR_LATENCY //latency for flash memory

| FLASH_ACR_PRFTEN

| FLASH_ACR_ICEN

| FLASH_ACR_DCEN;

//PLL - HSE

RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; //set HSE as PLL source

RCC->CR &= ~(RCC_CR_PLLON); //disable PLL before changes

//PLL M

RCC->PLLCFGR &= ~(RCC_PLLCFGR_PLLM); //clear all PLLM bits

RCC->PLLCFGR |= RCC_PLLCFGR_PLLM_2; //set PLLM = 4 (100)


//PLL P

RCC->PLLCFGR &= ~(RCC_PLLCFGR_PLLP); //main PLL division PLLP = 2: 00

//PLL N

RCC->PLLCFGR &= ~(RCC_PLLCFGR_PLLN); //clear all PLLN bits with mask

RCC->PLLCFGR |= (RCC_PLLCFGR_PLLN_3 //set PLLN = 168 (1010 1000)

| RCC_PLLCFGR_PLLN_5 //for 8 or 16 MHz HSE

| RCC_PLLCFGR_PLLN_7); //

//PLL Q 7 (0111)

RCC->PLLCFGR &= ~(RCC_PLLCFGR_PLLQ);

RCC->PLLCFGR |= (RCC_PLLCFGR_PLLQ_0

| RCC_PLLCFGR_PLLQ_2

| RCC_PLLCFGR_PLLQ_2);

//AHB Prescaler

RCC->CFGR &= ~(RCC_CFGR_HPRE); //Prescaler = 1

RCC->CFGR |= RCC_CFGR_HPRE_DIV1; //AHB = SYSCLK/1

//APB1 Prescaler 4

RCC->CFGR &= ~(RCC_CFGR_PPRE1);

RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;

//APB2 Prescaler 2

RCC->CFGR &= ~(RCC_CFGR_PPRE2);

RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;

//PLL enable

RCC->CR |= RCC_CR_PLLON; //enalbe PLL

while((RCC->CR & RCC_CR_PLLRDY) == 0) {} //wait for PLL is ready

//PLL System

//RCC->CFGR &= ~RCC_CFGR_SW;

RCC->CFGR |= RCC_CFGR_SW_PLL; //PLL selected as system clock

while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) {} //wait for PLL is used

}


void GPIO_init(void)

{

//RCC

RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; //enable RCC for PortD

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //enable RCC for PortA

//Port A

GPIOA->MODER &= ~(1 << 0); //Set output mode for PA0 (User Button)

//Port D

GPIOD->MODER = 0x55000000; //0b0101.0101 0000.0000 0000.0000 0000.0000

GPIOD->OTYPER = 0; //push-pull enable

GPIOD->OSPEEDR = 0; //low speed

GPIOD->BSRR = 0xF0000000; //turn off all LED

}


void PWM_init(void)

{

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //enable RCC for PortA

RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; //enable TIM2 clocking

//PA1 alternate function AF1 (TIM2 = 0001)

GPIOA->AFR[0] &= ~GPIO_AFRL_AFSEL1; //clr all bits

GPIOA->AFR[0] |= GPIO_AFRL_AFSEL1_0; //set bit 0

//PA1 Alternate Push-Pull (10)

GPIOA->MODER &= ~GPIO_MODER_MODE1;

GPIOA->MODER |= GPIO_MODER_MODE1_1;

TIM2->PSC = TIM2_PRESC - 1; //prescaler

TIM2->ARR = TIM2_ARR_VALUE - 1; //auto-reload value

TIM2->CCR2 = TIM2_CCR_VALUE - 1; //compare/caprute value

//PWM mode 1 (110)

TIM2->CCMR1 |= TIM_CCMR1_OC2M;

TIM2->CCMR1 &= ~TIM_CCMR1_OC2M_0;

TIM2->CCER |= TIM_CCER_CC2E; //enable channel 2 output

TIM2->CCER &= ~TIM_CCER_CC2P; //polarity active high

TIM2->CR1 &= ~TIM_CR1_DIR; //counter used as upcounter

TIM2->CR1 |= TIM_CR1_CEN; //counter enable

}


void vTaskPwmDemo(void *argument)

{

uint8_t pwmGoesUp = 0;


while(1)

{

if(pwmGoesUp == 1)

{

if(gCCR_VALUE < TIM2_ARR_VALUE) ++gCCR_VALUE;

else pwmGoesUp = 0;

}

else

{

if(gCCR_VALUE > 1) --gCCR_VALUE;

else pwmGoesUp = 1;

}

TIM2->CCR2 = gCCR_VALUE - 1;

vTaskDelay(10);

}

}















 
MyTetra Share v.0.53
Яндекс индекс цитирования