cancel
Showing results for 
Search instead for 
Did you mean: 

LPTIM Multiple odd behaviours

JustSomeGuy
Senior

I am writing an LPTIM driver without HAL or LL drivers on the B-L072Z-LRWAN1 board. I have been getting a number of odd behaviours here that I am hoping someone here can shed some light on.

1) cannot enable continuous start

          -bit 2 of the CR register is never set. I added a bunch of __NOP();s in between enabling the timer and setting continuous start with no success. I have also tried setting both bits together with LPTIM->CR = 0b101U; with no success.

2) ISR only clears every now and then.

          -using the IRQ handler function, the ISR is cleared the first time, and subsequently only clears about once every 5 times afterwards. the IRQ is being called constantly, when CNT = ARR should be true once a minute (Sysclock = MSI = 131072 Hz, PSC DIV = 128, ARR = 60963).

3) other ISR flags are set

          -even though i only set bit 1 (ARRMIE) in the IER register, when the IRQ handler is called, the debugger is telling me that CMPM is also set when entering the IRQ handler.

Here is the relevant code:

 

extern bool TxTimerDone;
extern uint8_t LPTimRollOver;


void LPTIM_Init(LPTIM_TypeDef *LPTIM, lpTimerMode mode, uint16_t period)
{
	uint32_t tmpcfg = 0;
	uint16_t tmpier = 0;

	//Select clock source as APB clock
	RCC->CCIPR &= ~RCC_CCIPR_LPTIM1SEL;

	//enable the oscillator for the peripheral
	RCC->APB1ENR |= RCC_APB1ENR_LPTIM1EN;

	//encoder mode disabled
	tmpcfg &= ~LPTIM_CFGR_ENC;
	//counter mode disabled
	tmpcfg &= ~LPTIM_CFGR_COUNTMODE;
	//preload ARR
	tmpcfg |= LPTIM_CFGR_PRELOAD;
	//set wave polarity.
	tmpcfg &= ~LPTIM_CFGR_WAVPOL;
	tmpcfg &= ~LPTIM_CFGR_WAVE;
	//disable trigger event reset
	tmpcfg &= ~LPTIM_CFGR_TIMOUT;
	//disable hardware triggers
	tmpcfg &= ~LPTIM_CFGR_TRIGEN;
	//clear trigger selection
	tmpcfg &= ~LPTIM_CFGR_TRIGSEL;
	//set prescalar division
	tmpcfg |= LPTIM_PRESCALER_DIV128;
	//clear trig filter settings
	tmpcfg &= ~LPTIM_CFGR_TRGFLT;
	//external clock settings; clear.
	tmpcfg &= ~LPTIM_CFGR_CKFLT;
	//external source clock polarity. not relevant
	tmpcfg &= ~LPTIM_CFGR_CKPOL;
	//clock selection: internal clock
	tmpcfg &= ~LPTIM_CFGR_CKSEL;

	//write the configurations to the register
	LPTIM->CFGR = tmpcfg;

	//set the ARR before enabling interrupts, since an interrupt will be called right away.
	//this mitigates the number of false interrupts called.

	//enable the timer
	LPTIM->CR = LPTIM_CR_ENABLE;

	LPTIM->ARR = period;
        //CMP flag in ISR is being set when it shouldn't be, so set the CMP register to ARR so it doesn't call the IRQ too often.
	LPTIM->CMP = period;

	//disable the timer
	LPTIM->CR = 0;

	//set interrupts to trigger on every ARR match
	if(mode == lptim_mode_interrupt)
	{
		//NVIC_SetVector(LPTIM1_IRQn, (uint32_t)&LPTIM1_IRQ_Handler);
		NVIC_SetPriority(LPTIM1_IRQn, 1);
		NVIC_EnableIRQ(LPTIM1_IRQn);
		//select the interrupt trigger sources
		tmpier |= LPTIM_IER_ARRMIE;
		//write the configurations to the register
		LPTIM->IER = tmpier;
		//set the interrupt vector
	}
}

void LPTIM_Start(LPTIM_TypeDef *LPTIM, uint16_t period)
{
	//enable the timer
	LPTIM->CR = LPTIM_CR_ENABLE;

	//enable continuous mode
	LPTIM->CR |= LPTIM_CR_CNTSTRT;
}

void LPTIM_Stop(LPTIM_TypeDef *LPTIM)
{
	//clear control register, disabling the timer.
	LPTIM->CR &= ~(0b111U);
}

void LPTIM_Set_ARR(LPTIM_TypeDef *LPTIM, uint16_t period)
{
	bool state = true;

	//enable the timer if it is not
	if((LPTIM->CR & LPTIM_CR_ENABLE) != LPTIM_CR_ENABLE)
	{
		//enable the timer
		LPTIM->CR |= LPTIM_CR_ENABLE;
		//toggle a status flag to show that LPTIMER was disabled
		//upon entering this function
		state = false;
	}

	//set the auto reload register
	LPTIM->ARR = period;

	//disable lptimer if it was disabled upon entering the function call.
	if(state == false)
		LPTIM->CR = 0;
}

//this function is called when the LP timer's CNT register = ARR
//in human terms, when the timer has counted up to its set point.
void LPTIM1_IRQHandler(void)
{
	uint16_t timeout = 1000;
	//read registers for debugging purposes
	uint8_t CRRead = LPTIM1->CR;
	uint8_t isrRead = LPTIM1->ISR;

	if(LPTIM1->ISR & LPTIM_ISR_ARRM)
	{
		//clear the interrupt flag register.
		LPTIM1->ICR = 127U;//LPTIM1->ISR;

		//keep track of how many times (minutes) passed.
		LPTimRollOver++;
		//if 'mTxDelay' number of minutes have passed,
		if(LPTimRollOver >= mTxDelay)
		{
			//toggle flag to indicate that the set number of minutes have passed
			TxTimerDone = true;
			LPTimRollOver = 0;
		}
		//if the timer disabled itself due to not being in CNTSRT mode, enable it again
		if((LPTIM1->CR & LPTIM_CR_ENABLE) != LPTIM_CR_ENABLE)
			LPTIM_Start(LPTIM1, LPTIMER_PERIOD);
	}
	//wait for interrupts to be cleared
	while((LPTIM1->ISR != 0) && ((timeout) != 0))
		timeout--;

	//read ISR for debugging
	isrRead = LPTIM1->ISR;
}

 

 and then in the main I have

 

 LPTIM_Init(LPTIM1, lptim_mode_interrupt, LPTIMER_PERIOD);
 LPTIM_Start(LPTIM1, LPTIMER_PERIOD);
 while(1)
 {
 }

 

 

1 ACCEPTED SOLUTION

Accepted Solutions

So after doing a lot of digging on these forums and finding a few excellent analyses by community members (such as this one by Mr. Jan) I found a solution that was rather cumbersome, but it works. 

-after setting the ARR register in the init function, poll the ARROK flag and clear it. Poll until cleared. Then,

-set the CMP to the ARR so it doesn't fire every time CMP >= ARR. Poll CMPOK until cleared.

-Also, I was a dummy, and in that second version of the IRQ I shared, I was actually clearing the flags mask bit, not setting it to clear the ISR. classic case of staring at the same code for too long.

 

View solution in original post

5 REPLIES 5
Sarra.S
ST Employee

Hello @JustSomeGuy

For enabling a continuous start, it may be a timing issue, try waiting for the timer to be enabled before enabling continuous mode

while (!(LPTIM->CR & LPTIM_CR_ENABLE));

    // Enable continuous mode
    LPTIM->CR |= LPTIM_CR_CNTSTRT;

The issue with other ISR flags being set, even though only ARRMIE is enabled, might be related to the known issue where the ARRM and CMPM flags are not set when the APB clock is slower than the kernel clock: check LPTIM section in your product errata sheet: es0292-stm32l07xxxl08xxx-device-errata-stmicroelectronics.pdf

 

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

Hi Sarra, 

Thank you for your response.

I tried your method, but there was no change. For good measure, I tried polling for the CNTSTRT start using this method:

 

 

void LPTIM_Start(LPTIM_TypeDef *LPTIM, uint16_t period)
{
	//enable the timer
	LPTIM->CR = LPTIM_CR_ENABLE;

	while((!(LPTIM->CR & LPTIM_CR_ENABLE)) ||
			(!(LPTIM->CR & LPTIM_CR_CNTSTRT)))
	{
		//enable continuous mode
		LPTIM->CR |= LPTIM_CR_CNTSTRT;
	}
}

 

 

but it seems to get stuck in the while loop with CNTSTRT never being set.

Now for the interrupts, I read the errata sheet right after posting, and based on its suggestion of only clearing the active interrupt source, I tried this method:

 

 

void LPTIM1_IRQHandler(void)
{
	uint16_t timeout = 10000;

	if(LPTIM1->ISR & LPTIM_ISR_ARRM)
	{
		//clear the interrupt flag register.
		LPTIM1->ICR &= ~LPTIM_ISR_ARRM;

		HAL_GPIO_TogglePin(RED_LED_GPIO_Port, RED_LED_Pin);
		//if the timer disabled itself due to not being in CNTSRT mode, enable it again
		if((LPTIM1->CR & LPTIM_CR_ENABLE) != LPTIM_CR_ENABLE)
			LPTIM_Start(LPTIM1, LPTIM_1MIN_PERIOD_AT_128_PSC);
	}
	//wait for interrupts to be cleared
	while(((LPTIM1->ISR & LPTIM_ISR_ARRM) != LPTIM_ISR_ARRM) && (timeout != 0))
		timeout--;

	DBG_read_registers();
	__NOP();
}

void DBG_read_registers(void)
{
	uint16_t ISR_tmp = LPTIM1->ISR;
	uint16_t ICR_tmp = LPTIM1->ICR;
	uint16_t IER_tmp = LPTIM1->IER;
	uint32_t CFGR_tmp = LPTIM1->CFGR;
	uint16_t CR_tmp = LPTIM1->CR;
	uint16_t CMP_tmp = LPTIM1->CMP;
	uint16_t ARR_tmp = LPTIM1->ARR;
	uint16_t CNT_tmp = LPTIM1->CNT;
	__NOP();
}

 

 

 Since I only set ARRMIE in the IER, this should work based on what the errata sheet says. Except, it does not, and the interrupt request is being called constantly with an ISR value of 3. This tells me that the ARRM flag is being set far more frequently than it should be. Do you know why this might be?

Edit: to be clear, during debug the ARR register is showing the correct number of 60963 and the PSC bits are confirmed to be set to 111 (i.e. 128 div.) and the APB clock is operating at the system clock frequency of 131.072 kHz. Based on Fout = Fclock / ((ARR + 1) * (PSC + 1)) the output frequency should be 60 seconds give or take a few millis.

Sarra.S
ST Employee

Have you tried the given workaround " resetting the LPTIM peripheral via the RCC controller by setting and resetting its respective LPTIMxRST bit in the relevant RCC register"? 

Also, regarding the ISR that's not clearing consistently, there is some known issue that can be related, where the interrupt status flag is cleared by hardware upon writing its corresponding bit in the LPTIM_DIER register! Please try that

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

Hi Sarra,

Thank you very much for your prompt response. It really helps with development when I don't have to wait a whole day or even hours for a response.

I implemented your suggestions like so. I am not sure if this is what you had in mind (please say so if not), but I am still getting the same behaviour even after resetting the RCC. 

void LPTIM1_IRQHandler(void)
{
	uint16_t timeout = 10000;

	DBG_read_registers();
	if(LPTIM1->ISR & LPTIM_ISR_ARRM)
	{
		//clear the interrupt flag register.
		LPTIM1->ICR &= ~LPTIM_ISR_ARRM;
		//workaround suggested by ST employee
		LPTIM1->IER = LPTIM_IER_ARRMIE;

		HAL_GPIO_TogglePin(RED_LED_GPIO_Port, RED_LED_Pin);
		//if the timer disabled itself due to not being in CNTSRT mode, enable it again
		if((LPTIM1->CR & LPTIM_CR_ENABLE) != LPTIM_CR_ENABLE)
			LPTIM_Start(LPTIM1, LPTIM_1MIN_PERIOD_AT_128_PSC);
	}
	//wait for interrupts to be cleared
	while(((LPTIM1->ISR & LPTIM_ISR_ARRM) != LPTIM_ISR_ARRM) && (timeout != 0))
		timeout--;

	//the last 2 conditions are to ensure that the RCC is reset only once
	//(and not after the initial interrupt triggering from initialization) 
        //in order to monitor its performance after a reset
	if((highestCNT < 10) && (firstScan == false) && !(RCCReset))
	{
		LPTIM1_Reset_RCC();
		LPTIM_Init(LPTIM1, lptim_mode_interrupt, LPTIM_1MIN_PERIOD_AT_128_PSC);
		LPTIM_Start(LPTIM1, LPTIM_1MIN_PERIOD_AT_128_PSC);
		RCCReset = true;
	}

	firstScan = false;
	DBG_read_registers();
	__NOP();
}
void LPTIM1_Reset_RCC(void)
{
	//set the reset bit
	RCC->APB1RSTR |= RCC_APB1RSTR_LPTIM1RST;
	//wait for the bit to be set
	while(!(RCC->APB1RSTR & RCC_APB1RSTR_LPTIM1RST));
	//reset the bit
	RCC->APB1RSTR &= ~(RCC_APB1RSTR_LPTIM1RST);
	//wait for bit to be cleared
	while((RCC->APB1RSTR & RCC_APB1RSTR_LPTIM1RST));
}

the two boolean variables i have added are declared like this:

bool firstScan = true;
bool RCCReset = false;

and in the main() I have this:

  LPTIM_Init(LPTIM1, lptim_mode_interrupt, LPTIM_1MIN_PERIOD_AT_128_PSC);
  LPTIM_Start(LPTIM1, LPTIM_1MIN_PERIOD_AT_128_PSC);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  currentCNT = LPTIM1->CNT;

	  if(currentCNT > highestCNT)
		  highestCNT = currentCNT;

	  DBG_read_registers();
  }

The behaviour is still the same after resetting the RCC. ISR is always 3 and still never clears, and now i am scanning the registers at both the beginning and end of the IRQ. furthermore, the CNT bit does not seem to go higher than 1.

 

So after doing a lot of digging on these forums and finding a few excellent analyses by community members (such as this one by Mr. Jan) I found a solution that was rather cumbersome, but it works. 

-after setting the ARR register in the init function, poll the ARROK flag and clear it. Poll until cleared. Then,

-set the CMP to the ARR so it doesn't fire every time CMP >= ARR. Poll CMPOK until cleared.

-Also, I was a dummy, and in that second version of the IRQ I shared, I was actually clearing the flags mask bit, not setting it to clear the ISR. classic case of staring at the same code for too long.