2024-11-20 08:16 AM - edited 2024-11-20 08:36 AM
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)
{
}
Solved! Go to Solution.
2024-11-21 11:41 AM - edited 2024-11-21 11:49 AM
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.
2024-11-21 07:03 AM
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.
2024-11-21 07:49 AM - edited 2024-11-21 08:00 AM
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.
2024-11-21 07:59 AM
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.
2024-11-21 09:21 AM
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.
2024-11-21 11:41 AM - edited 2024-11-21 11:49 AM
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.