TIM2 PWM newbie needs help with bare metal code

Post here first, or if you can't find a relevant section!
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: TIM2 PWM newbie needs help with bare metal code

Post by dannyf »

Fairly simple.

My way of doing it requires two functions at a minimum

1. The isr. It clears the ovf bit and executes a function pointer that you will install in the second function. The isr's name needs to match that in the startup file for your particular interrupt.

2. Activate the isr. It sets the ovf interrupt enable bit, points the function pointer to your user routine, and enable the interrupt in nvic for the irq - the irq name needs to match that in the device .h file.

You will see that in the code I posted earlier. For different purposes but the same principle and process
RobertoBerner
Posts: 36
Joined: Tue May 09, 2023 10:45 pm

Re: TIM2 PWM newbie needs help with bare metal code

Post by RobertoBerner »

Thank you dannyf, I have compiled your suggested code from:
I added empty setup() and loop() and all compiled OK, except for 2 lines I temporarily commented.
Now trying to find why these 2 lines don't work with my STM32F103C8T6 BluePill.

Code: Select all

TIM2->ARR = PWM_PR;           //auto reload register / period = 0; - need to change for downcounters
AFIO->MAPR = (AFIO->MAPR &~AFIO_MAPR_TIM2_REMAP) | (AFIO_MAPR_TIM2_REMAP & TIM2REMAP);  //select the remap scheme, configure CCP1..4
In the meantime, could you help me find out why my code doesn't work? Where is device.h? Is it STM32F1xxxx.h ? I am using an original ST core installed.

Maybe is something very simple, until I can get to know all the details about registers and structure. It is yet not so easy.

With reference to interrupts (in the Arduino environment && using CMSIS bare metal coding), there is very poor information. I am trying to preserve direct register programming instead of HAL for initialization and some other details. So, literature and info on how to use the Arduino environment with interrupts is not easy.

For example, in the examples here:
...all is based in HAL. However, it uses AttachInterrupt() but I don't see interrupts() to enable events, as is usual in the Arduino IDE.

So some little help to understand the do and don'ts , some clarifying help is needed :) .
RobertoBerner
Posts: 36
Joined: Tue May 09, 2023 10:45 pm

Re: TIM2 PWM newbie needs help with bare metal code

Post by RobertoBerner »

dannyf, I have your initialization function for TIM2 running, from you previous post:
I will ask you to teach me how to use TIM2CH1toPIN()
The compiler complaints here:

Code: Select all

RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;       //enable AFIO
AFIO->MAPR = (AFIO->MAPR &~AFIO_MAPR_TIM2_REMAP) | (AFIO_MAPR_TIM2_REMAP & TIM2REMAP);  //select the remap scheme
...but I think that some definitions here are not for the STM32F!03C8T6, but tomorrow I will Google the symbols and look for its equivalents in my core.

About the function, the neat, ordered coding is helping me to understand better what each bit does in the timer. The remapping is something very nice in this chip, but I will test it later.

Attached, is my working code with your function.

Now I need to concentrate on adding the interrupt to this PWM event.
Thank you for your help !

Roberto

Code: Select all

//  https://www.stm32duino.com/viewtopic.php?p=12300#p12300
//  the output on a given pin is enabled by the TIM2CHntoPIN() macros:
//  #define TIM2CH1toPIN()    TIM2CH1toPA0()      //PA0, PA15
//  by commenting out this macro, you are muting the pwm output.
//  mapping the macro to "...toPA0()" or "...toPA15()" redirects the output.
//  In this case, TIM2CH1toPA0() is further defined as:
//  #define TIM2CH1toPA0() do {GIO_AFPP(GPIOA, 1<< 0); AFIO->MAPR = (AFIO->MAPR &~AFIO_MAPR_TIM2_REMAP) | (0x00 << 8);} while (0)
//  essentially enabling the right af function on a given pin.
//  the code will be different on different chips but the basic logic works.
//  hope it helps.  dannyf

#define PWM_PR  50000
#define DUTY    5000

void setup()
{
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // clock to GPIOA
  // Set PA0 as alternate function output push-pull
  GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0);
  GPIOA->CRL |= GPIO_CRL_CNF0_1;  /* 10: Alternate function output Push-pull */
  GPIOA->CRL |= GPIO_CRL_MODE0_1; /* 10: Output mode, max speed 2 MHz. */
  
  pwm2Init(1);
}
void loop()
{
  TIM2->CCR1 = DUTY; 
  delay(500);
  TIM2->CCR1 = DUTY*5; 
  delay(500); 
}

//initialize pwm to TxCCP_PS (prescaler) and TxCCP_PR (period)

void pwm2Init(uint16_t TxCCP_PS) {
  //route the clock to timer
  //route the clock to timer
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
  //source from internal clock -> disable slave mode
  TIM2->SMCR &=~TIM_SMCR_SMS;     //clear sms->use internal clock

  //stop the timer to configure it
  //TIM2->CR1 &=~TIM_CR1_CEN;     //clear cen. 0=disable the timer, 1=enable the timer
  //TIM2->CR1 &=~TIM_CR1_CKD;     //clear CKD0..1. 0b00->1x clock; 0b01->2:1 clock, 0b10->4:1 clk; 0b11->reserved
  //TIM2->CR1 &=~TIM_CR1_DIR;     //clear DIR bit. 0=upcounter, 1=downcounter
  //TIM2->CR1 &=~TIM_CR1_OPM;     //clear opm bit. 0=periodic timer, 1=one-shot timer
  //or to simply zero the register
  TIM2->CR1 = 0;            //much easier
  
  TIM2->CR1 = (0<<8) |  //0->1:1 clock, 1->2:1 clock, 2->4:1 clock, 3->reserved
        (0<<7) |        //1->ARPE autoreload/preload ARR 1-> buffered, 0->ARPE not buffered
        (0<<5) |        //0->edge-aligned, 1->center-aligned mode 1, 2->center-aligned mode 2, 3->center-aligned mode 3
        (0<<4) |        //0->upcounter, 1->downcounter
        (0<<3) |        //0->continous mode, 1->one pulse mode
        (0<<2) |        //0->: update only OVF/UF generates 1-> update interrupt or DMA request if enabled.
        (0<<1) |        //0->UEV update enabled, 1->UEV update disabled
        (0<<0) |        //0->counter disabled, 1->counter enabled
        0x00;
  TIM2->CR2 = 0;            //default value
  TIM2->SMCR= 0;            //default value

  //set the prescaler
  TIM2->PSC = TxCCP_PS - 1; //set the prescaler
  TIM2->RCR = 0;            //repetition counter = 0 (=no repetition)
  TIM2->ARR = PWM_PR;       //auto reload register / period = 0; - need to change for downcounters
  TIM2->CNT = 0;            //reset the counter

  //clear the status register bits for capture / compare flags
  TIM2->SR &=~(TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF | TIM_SR_CC4IF | TIM_SR_UIF);
  //disable the interrupt by clearing the enable bits
  TIM2->DIER &=~(TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE | TIM_DIER_UIE);

  //RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;       //enable AFIO
  //AFIO->MAPR = (AFIO->MAPR &~AFIO_MAPR_TIM2_REMAP) | (AFIO_MAPR_TIM2_REMAP & TIM2REMAP);  //select the remap scheme
  
  // **** configure CCP1..4  ****
  //configure CCP1
  TIM2->CCMR1 =   (TIM2->CCMR1 &~0x00ff) |
          (0<<7) |      //0->OC1REF not affected by ETRF, 1->OC1REF cleared by ETRF high
          (6<<4) |      //0->frozen (for time base), 1->active on match, 2->inactive on match, 3->toggle, 4->inactive, 5->active, 6->pwm mode 1, 7->pwm mode 2
          (0<<3) |      //0->preload disabled, 1->preload enabled
          (0<<2) |      //0->fast disabled, 1->fast enabled
          (0<<0) |      //0->ch1 as output, 1->ch1 as input, 2->ch1 as input, 3->ch1 as input
          0x00;
  TIM2->CCER =  (TIM2->CCER &~(0x0f << 0)) |
          (0<< 3) |     //0->normal polarity for CC1N, 1->inverse polarity
          (0<< 2) |     //0->disable CC1N, 1->enable CC1N
          (0<< 1) |     //0->normal polarity for CC1, 1->inverse polarity
          (1<< 0) |     //1->enable CC1, 0->disable CC1
          0x00;
  TIM2->CCR1 = 0;           //0% duty cycle

  //configure gpio for pwm output
#if defined(TIM2CH1toPIN)
  TIM2CH1toPIN();
#endif

  //configure CCP2
  TIM2->CCMR1 =   (TIM2->CCMR1 &~0xff00) |
          (0<<15) |     //0->OC1REF not affedted by ETRF, 1->OC1REF cleared by ETRF high
          (6<<12) |     //0->frozen (for time base), 1->active on match, 2->inactive on match, 3->toggle, 4->inactive, 5->active, 6->pwm mode 1, 7->pwm mode 2
          (0<<11) |     //0->preload disabled, 1->preload enabled
          (0<<10) |     //0->fast disabled, 1->fast enabled
          (0<<8) |      //0->ch1 as output, 1->ch1 as input, 2->ch1 as input, 3->ch1 as input
          0x00;
  TIM2->CCER =  (TIM2->CCER &~(0x0f << 4)) |
          (0<< 7) |     //0->normal polarity for CC2N, 1->inverse polarity
          (0<< 6) |     //0->disable CC2N, 1->enable CC2N
          (0<< 5) |     //0->normal polarity for CC2, 1->inverse polarity
          (1<< 4) |     //1->enable CC2, 0->disable CC2
          0x00;
  TIM2->CCR2 = 0;           //0% duty cycle

  //configure gpio for pwm output
#if defined(TIM2CH2toPIN)
  TIM2CH2toPIN();
#endif

  //configure CCP3
  TIM2->CCMR2 =   (TIM2->CCMR1 &~0x00ff) |
          (0<<7) |      //0->OC1REF not affedted by ETRF, 1->OC1REF cleared by ETRF high
          (6<<4) |      //0->frozen (for time base), 1->active on match, 2->inactive on match, 3->toggle, 4->inactive, 5->active, 6->pwm mode 1, 7->pwm mode 2
          (0<<3) |      //0->preload disabled, 1->preload enabled
          (0<<2) |      //0->fast disabled, 1->fast enabled
          (0<<0) |      //0->ch1 as output, 1->ch1 as input, 2->ch1 as input, 3->ch1 as input
          0x00;
  TIM2->CCER =  (TIM2->CCER &~(0x0f << 8)) |
          (0<<11) |     //0->normal polarity for CC3N, 1->inverse polarity
          (0<<10) |     //0->disable CC3N, 1->enable CC3N
          (0<< 9) |     //0->normal polarity for CC3, 1->inverse polarity
          (1<< 8) |     //1->enable CC3, 0->disable CC3
          0x00;
  TIM2->CCR3 = 0;           //0% duty cycle

  //configure gpio for pwm output
#if defined(TIM2CH3toPIN)
  TIM2CH3toPIN();
#endif

  //configure CCP4
  TIM2->CCMR2 =   (TIM2->CCMR1 &~0xff00) |
          (0<<15) |     //0->OC1REF not affedted by ETRF, 1->OC1REF cleared by ETRF high
          (6<<12) |     //0->frozen (for time base), 1->active on match, 2->inactive on match, 3->toggle, 4->inactive, 5->active, 6->pwm mode 1, 7->pwm mode 2
          (0<<11) |     //0->preload disabled, 1->preload enabled
          (0<<10) |     //0->fast disabled, 1->fast enabled
          (0<<8) |      //0->ch1 as output, 1->ch1 as input, 2->ch1 as input, 3->ch1 as input
          0x00;
  TIM2->CCER =  (TIM2->CCER &~(0x0f << 12)) |
          (0<<15) |     //0->normal polarity for CC4N, 1->inverse polarity
          (0<<14) |     //0->disable CC4N, 1->enable CC4N
          (0<<13) |     //0->normal polarity for CC4, 1->inverse polarity
          (1<<12) |     //1->enable CC4, 0->disable CC4
          0x00;
  TIM2->CCR4 = 0;           //0% duty cycle

  //configure gpio for pwm output
#if defined(TIM2CH4toPIN)
  TIM2CH4toPIN();
#endif

  TIM2->EGR = 0xff;               //force an update
  TIM2->BDTR |= TIM_BDTR_MOE;     //enable MOE bit
  //enable the timer.
  TIM2->CR1 |= TIM_CR1_CEN;       //enable the timer

}

dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: TIM2 PWM newbie needs help with bare metal code

Post by dannyf »

The simplest would be to set the uie bit in the dier register and put in an ovf isr.

But this approach may fail in the extreme cases.
RobertoBerner
Posts: 36
Joined: Tue May 09, 2023 10:45 pm

Re: TIM2 PWM newbie needs help with bare metal code

Post by RobertoBerner »

Hi dannyf, I need your help with this.
I spent almost the whole day to try to make this work.
Some values maybe wrong, but with my errors in the code, I can't make this thing interrupt.
Could you check my code, taken from the one you posted before?
Surely it must be simple solution. Excuse my ignorance, I am learning. I switched to TIM2, because your original TIM4 does not work when compiling.
Rest, OK.


Here is my code, please tell me why I don't get my interrupts to toggle the pin.
Thank you !
Roberto

Code: Select all

//tmr2
//global variables

#define PIN_MASK (1 << 13) 
void empty_handler(void);

static void (* _tim2_ovfisrptr)(void)=empty_handler;        //TIM2_ptr pointing to empty_handler by default
static void (* _tim2_cc1isrptr)(void)=empty_handler;        //TIM2_ptr pointing to empty_handler by default
static void (* _tim2_cc2isrptr)(void)=empty_handler;        //TIM2_ptr pointing to empty_handler by default
static void (* _tim2_cc3isrptr)(void)=empty_handler;        //TIM2_ptr pointing to empty_handler by default
static void (* _tim2_cc4isrptr)(void)=empty_handler;        //TIM2_ptr pointing to empty_handler by default

static uint16_t _tim2_cc1=0;        //output compare registers
static uint16_t _tim2_cc2=0;
static uint16_t _tim2_cc3=0;
static uint16_t _tim2_cc4=0;

void empty_handler(void)
{
 // Toggle PA8
  GPIOC->ODR ^= PIN_MASK;  // Toggle the pin state   
}

void setup()
{
  // Enable GPIOA clock
  RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
  // Configure PA8 as general-purpose output, push-pull mode, maximum speed
  GPIOC->CRH &= ~(0xF << 20);  // Clear configuration bits
  GPIOC->CRH |= (0x3 << 20);   // Set mode bits to general-purpose output


  tmr2Init(100); //initialize tmr2 to 1:1 prescaler, free running.
  //tmr2OC1SetPR(10*clockCyclesPerMicrosecond); //set the period for compare ch1 to be invoked. 10us in this case
  tmr2OC1SetPR(5000); //set the period for compare ch1 to be invoked. 10us in this case
  tmr2OC1AttachISR(empty_handler);  //install user isr - where you flip the output pin(s)

}

void loop()
{
  //GPIOC->ODR ^= PIN_MASK;  // Toggle the pin state 
  //delayMicroseconds(300);   
}

//isr for timer1 capture / compare
void TIM2_IRQHandler(void) 
{
  //oc1..4 portion
  //if ((TIM2->DIER & TIM_DIER_CC1IE) && (TIM2->SR & TIM_SR_CC1IF)) {   //output compare 1 flag is set
  if ((TIM2->SR & TIM_SR_CC1IF)) {TIM2->SR &=~TIM_SR_CC1IF; TIM2->CCR1 += _tim2_cc1; _tim2_cc1isrptr();}
  //if ((TIM2->SR & TIM_SR_CC2IF)) {TIM2->SR &=~TIM_SR_CC2IF; TIM2->CCR2 += _tim2_cc2; _tim2_cc2isrptr();}
  //if ((TIM2->SR & TIM_SR_CC3IF)) {TIM2->SR &=~TIM_SR_CC3IF; TIM2->CCR3 += _tim2_cc3; _tim2_cc3isrptr();}
  //if ((TIM2->SR & TIM_SR_CC4IF)) {TIM2->SR &=~TIM_SR_CC4IF; TIM2->CCR4 += _tim2_cc4; _tim2_cc4isrptr();}

  //ovf isr
  if ((TIM2->SR & TIM_SR_UIF)) {TIM2->SR &=~TIM_SR_UIF; _tim2_ovfisrptr();}
}

//initialize the timer2 (16bit)
void tmr2Init(uint16_t ps) 
{
  //route the clock to timer
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

  //source from internal clock -> disable slave mode
  TIM2->SMCR &=~TIM_SMCR_SMS;     //clear sms->use internal clock

  //stop the timer to configure it
  //TIM2->CR1 &=~TIM_CR1_CEN;     //clear cen. 0=disable the timer, 1=enable the timer
  //TIM2->CR1 &=~TIM_CR1_CKD;     //clear CKD0..1. 0b00->1x clock; 0b01->2:1 clock, 0b10->4:1 clk; 0b11->reserved
  //TIM2->CR1 &=~TIM_CR1_DIR;     //clear DIR bit. 0=upcounter, 1=downcounter
  //TIM2->CR1 &=~TIM_CR1_OPM;     //clear opm bit. 0=periodic timer, 1=one-shot timer
  //or to simply zero the register
  TIM2->CR1 = 0;            //much easier

  //clear the status register bits for capture / compare flags
  TIM2->SR &=~(TIM_SR_UIF | TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF | TIM_SR_CC4IF);
  //disable the interrupt by clearing the enable bits
  TIM2->DIER &=~(TIM_DIER_UIE | TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE);

  //set the prescaler
  TIM2->PSC = ps - 1;       //set the prescaler
  TIM2->RCR = 0;            //repetition counter = 0 (=no repetition)
  TIM2->ARR = -1;           //auto reload register / period = 0; - need to change for downcounters
  TIM2->CNT = 0;            //reset the counter

  //enable the timer.
  TIM2->CR1 |= TIM_CR1_CEN;     //enable the timer
}

//set up the period
void tmr2SetPR(uint32_t pr) 
{
  TIM2->ARR = pr - 1;
}

//activate the isr handler
void tmr2OVFAttachISR(void (*isrptr)(void)) 
{
  NVIC_DisableIRQ(TIM2_IRQn);     //disable irq

  _tim2_ovfisrptr = isrptr;     //install user handler

  //clear the flag
  TIM2->SR &=~TIM_SR_UIF;     //clear the interrupt flag
  TIM2->DIER |= TIM_DIER_UIE;   //enable the isr

  NVIC_EnableIRQ(TIM2_IRQn);      //enable irq
  //priorities not set -> default values used.
}

//set TIM2_oc1 period
//pr is 16-bit. 32-bit used for compatability;
void tmr2OC1SetPR(uint16_t pr) 
{
  //save the period value
  _tim2_cc1 = pr - 0;
  TIM2->CCR1 = _tim2_cc1;

  //clear the flag
  //TIM2->SR &=~TIM_SR_CC1IF;     //clear the interrupt flag
  //TIM2->DIER &=~TIM_DIER_CC1IE;   //disable the isr
}

//activate the isr handler
void tmr2OC1AttachISR(void (*isrptr)(void)) 
{
  NVIC_DisableIRQ(TIM2_IRQn);     //disable irq

  _tim2_cc1isrptr = isrptr;     //install user handler

  //clear the flag
  TIM2->SR &=~TIM_SR_CC1IF;     //clear the interrupt flag
  TIM2->DIER |= TIM_DIER_CC1IE;   //enable the isr

  NVIC_EnableIRQ(TIM2_IRQn);      //enable irq
  //priorities not set -> default values used.
}

  
User avatar
fpiSTM
Posts: 1738
Joined: Wed Dec 11, 2019 7:11 pm
Answers: 91
Location: Le Mans
Contact:

Re: TIM2 PWM newbie needs help with bare metal code

Post by fpiSTM »

You should pay attention of 2 things:
1- ino is converted in cpp so all handler have to be prefixed with

Code: Select all

extern "C"
2- Timer irq handler are already defined by the core to use within Arduino API. So you have to disable this usage to be able to define your own one.
To achieve this you can simply disable the HAL TIM usage by the arduino API by defining -DHAL_TIM_MODULE_ONLY in a build_opt.h file.

For ref:
https://github.com/stm32duino/Arduino_C ... odule-only
https://github.com/stm32duino/Arduino_C ... uild_opt.h
Here an example of STM32cubeIDE project converted to be used with Arduino core:
viewtopic.php?t=110
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: TIM2 PWM newbie needs help with bare metal code

Post by dannyf »

will go through your code when i get more time later. but here is the tim2 code.

Code: Select all

//tmr2
//global variables
static void (* _tim2_ovfisrptr)(void)=empty_handler;				//TIM2_ptr pointing to empty_handler by default
static void (* _tim2_cc1isrptr)(void)=empty_handler;				//TIM2_ptr pointing to empty_handler by default
static void (* _tim2_cc2isrptr)(void)=empty_handler;				//TIM2_ptr pointing to empty_handler by default
static void (* _tim2_cc3isrptr)(void)=empty_handler;				//TIM2_ptr pointing to empty_handler by default
static void (* _tim2_cc4isrptr)(void)=empty_handler;				//TIM2_ptr pointing to empty_handler by default

static uint16_t _tim2_cc1=0;				//output compare registers
static uint16_t _tim2_cc2=0;
static uint16_t _tim2_cc3=0;
static uint16_t _tim2_cc4=0;

//isr for timer1 capture / compare
void TIM2_IRQHandler(void) {
	//oc1 portion
	//output compare 1 flag is set, clea the flag, update the output compare register, execute user handler
	if (TIM2->SR & TIM_SR_CC1IF) {TIM2->SR &=~TIM_SR_CC1IF; TIM2->CCR1 += _tim2_cc1; _tim2_cc1isrptr();}
	if (TIM2->SR & TIM_SR_CC2IF) {TIM2->SR &=~TIM_SR_CC2IF; TIM2->CCR2 += _tim2_cc2; _tim2_cc2isrptr();}
	if (TIM2->SR & TIM_SR_CC3IF) {TIM2->SR &=~TIM_SR_CC3IF; TIM2->CCR3 += _tim2_cc3; _tim2_cc3isrptr();}
	if (TIM2->SR & TIM_SR_CC4IF) {TIM2->SR &=~TIM_SR_CC4IF; TIM2->CCR4 += _tim2_cc4; _tim2_cc4isrptr();}

	//ovf portion
	if (TIM2->SR & TIM_SR_UIF) {TIM2->SR &=~TIM_SR_UIF; _tim2_ovfisrptr();}
}

//initialize the timer1 (16bit)
void tmr2Init(uint16_t ps) {
	//route the clock to timer
	RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

	//source from internal clock -> disable slave mode
	TIM2->SMCR &=~TIM_SMCR_SMS;			//clear sms->use internal clock

	//stop the timer to configure it
	TIM2->CR1 &=~TIM_CR1_CEN;			//clear cen. 0=disable the timer, 1=enable the timer
	TIM2->CR1 &=~TIM_CR1_CKD;			//clear CKD0..1. 0b00->1x clock; 0b01->2:1 clock, 0b10->4:1 clk; 0b11->reserved
	TIM2->CR1 &=~TIM_CR1_DIR;			//clear DIR bit. 0=upcounter, 1=downcounter
	TIM2->CR1 &=~TIM_CR1_OPM;			//clear opm bit. 0=periodic timer, 1=one-shot timer
	//or to simply zero the register
	//TIM2->CR1 = 0;						//much easier

	//clear the status register bits for capture / compare flags
	TIM2->SR &=~(TIM_SR_UIF | TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF | TIM_SR_CC4IF);
	//disable the interrupt by clearing the enable bits
	TIM2->DIER &=~(TIM_DIER_UIE | TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE);

	//set the prescaler
	TIM2->PSC = ps - 1;					//set the prescaler
	TIM2->RCR = 0;						//repetition counter = 0 (=no repetition)
	TIM2->ARR = -1;						//auto reload register / period = 0; - need to change for downcounters
	TIM2->CNT = 0;						//reset the counter

	//enable the timer.
	TIM2->CR1 |= TIM_CR1_CEN;			//enable the timer
}

void tmr2SetPR(uint32_t pr) {
	TIM2->ARR = pr - 1;
}

//activate the isr handler
void tmr2OVFAttachISR(void (*isrptr)(void)) {
	NVIC_DisableIRQ(TIM2_IRQn);			//disable irq

	_tim2_ovfisrptr = isrptr;			//install user handler

	//clear the flag
	TIM2->SR &=~TIM_SR_UIF;			//clear the interrupt flag
	TIM2->DIER |= TIM_DIER_UIE;		//enable the isr

	NVIC_EnableIRQ(TIM2_IRQn);			//enable irq
	//priorities not set -> default values used.
}
it basically follows the logic i laid out earlier.

the set up code:

Code: Select all

//flip the led
void led_flp(void) {
	pinFlip(LED);								//flip te led
}

//user defined set up code
void setup(void) {
	//configure the clock
	//SystemCoreClockHSI();						//use hsi
	//SystemCoreClockHSIPLL2x();				//use hsipll
	//SystemCoreClockHSE();						//use hse
	//SystemCoreClockHSEPLL2x();				//use hsepll

	//GPIO - for pb3/4
	//PB34GPIO();								//enable pb3/4 for gpio on afio - no swd after this

	//pinMode(GND, OUTPUT); digitalWrite(GND, LOW);	//gnd pin grounded
	pinMode(LED, OUTPUT);						//led as output pin
	
	//initialize the uart
	//uart1Init(UART_BR9600);					//initialize uart1
	//uart2Init(UART_BR9600);					//initialize uart2
	
	//initialize the rtc
	//rtcInitLSI(); time2rtc(946684800ul);		//initialize rtc to 1/1/2000

	//initialize tim2 ovf:
	//period: 65536
	//prescaler: F_CPU/65536/2 -> 0.5 sec
	//action: invoke led_flp()
	tmr2Init(F_CPU/65536/2); tmr2OVFAttachISR(led_flp);
	//PB34GPIO();								//enable pb3/4 for gpio on afio - no swd after this

	ei();										//enable interrupts
}
it basically sets up tmr as a free running timer, with a period of 65536 (=-1) ticks, and a prescaler of F_CPU / 65536 / 2. the user isr (=led_flp()) is invoked every 0.5 sec. led_flp() in my implementation blinks PC13. So you should have PC13 blinking every 0.5 sec.
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: TIM2 PWM newbie needs help with bare metal code

Post by dannyf »

I noticed your code uses OC1.

here is the equivalent code using OC1 -> everything else the same.

in the set-up section:

Code: Select all

	tmr2Init(F_CPU/65536/2); tmr2OC1AttachISR(led_flp);
OC1AttachISR:

Code: Select all

//activate the isr handler
void tmr2OC1AttachISR(void (*isrptr)(void)) {
	NVIC_DisableIRQ(TIM2_IRQn);			//disable irq

	_tim2_cc1isrptr = isrptr;			//install user handler

	//clear the flag
	TIM2->SR &=~TIM_SR_CC1IF;			//clear the interrupt flag
	TIM2->DIER |= TIM_DIER_CC1IE;		//enable the isr

	NVIC_EnableIRQ(TIM2_IRQn);			//enable irq
	//priorities not set -> default values used.
}
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: TIM2 PWM newbie needs help with bare metal code

Post by dannyf »

I think I know why your code isn't working.

the use of "OCn" assumes a free running timer all the way to 65536. you cannot set its period to anything other other than 65536 (=-1).

so two ways to get to a user desired period:
1. set the prescaler. That was my approach shown earlier.
2. set a prescaler and then choose a period for that particular OC channe, shown below:

Code: Select all

	tmr2Init(1000); tmr2OC1SetPR(F_CPU / 1000 / 2); tmr2OC1AttachISR(led_flp);
it sets tmr2 up with a prescaler of 1000 (so the result period is less than 65536, given the F_CPU).
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: TIM2 PWM newbie needs help with bare metal code

Post by dannyf »

the OC1 routines work by pushing the corresponding _oc1 variables to the next match point.

So by setting the period to 5000, any _oc1 match point beyond 5000 never triggers the interrupt.

So in your case, if you want to have a desired period, use the OCnSetPR() routine instead.
Post Reply

Return to “General discussion”