I want to implement long-press button to turn a device on.
I use external interrupt button to launch the timer on press, and stop and reset it on release of the button. If you hold the button enough (1 sec) it will call a Timer_Update event and turn on LED.
However, when I load my code to the Discovery and press reset, the first press of the user button lights the LED immediately as if the interrupt is generated at the very first launch of the Timer. Then it works properly - changes LED state if you hold the button for >= 1sec.
Project is generated via CubeMX
This is button interrupt handler
void EXTI0_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
static uint8_t is_pressed = 0;
/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
/* USER CODE BEGIN EXTI0_IRQn 1 */
if (!is_pressed) {
HAL_TIM_Base_Start_IT(&htim7);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_13);
is_pressed = 1;
}
else {
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_13);
HAL_TIM_Base_Stop_IT(&htim7);
__HAL_TIM_SET_COUNTER(&htim7, 0);
is_pressed = 0;
}
/* USER CODE END EXTI0_IRQn 1 */
}
This is Timer interrupt handler
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_14);
HAL_TIM_Base_Stop_IT(htim);
__HAL_TIM_SET_COUNTER(htim, 0);
}
The trigger event for the Timer in CubeMX is set as "Update Event"
Well it is a bug in the generated code. During timer initialization the call TIM_Base_SetConfig is called. This call has as a side effect that update interrupt bit is set in the SR. This means that when interrupt are enable in the HAL_TIM_Base_Start_IT immediately an interrupt is generated. This is what you are experiencing. See also https://www.youtube.com/watch?v=3yvY7pLMHAg 5:40. There also a HAL compatible fix is explained.
I would recommend the following implementation:
Setup EXTI pin for rising and falling edges
Check pin state in interrupt handler(instead of is_pressed variable)
EXTI handler can be implemented like below:
#define BUTTON_GPIO GPIOA
#define BUTTON_PIN GPIO_PIN_0
#define BUTTON_PRESSED_STATE GPIO_PIN_RESET
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
if (HAL_GPIO_ReadPin(BUTTON_GPIO, BUTTON_PIN) == BUTTON_PRESSED_STATE ) {
HAL_TIM_Base_Start_IT(&htim7);
__HAL_TIM_SET_COUNTER(&htim7, 0);
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_RESET);
} else {
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_SET);
HAL_TIM_Base_Stop_IT(&htim7);
}
}
Related
My board is the NUCLEO-H743ZI2
I configured TIM4,CH2 as PWM such that TIM4 output should be HI for half a second and LO for half a second. Here is the block.ioc clock and pin configuration
I auto generated the code and tried to add my own to toggle the LED with the H/L of PWM. What I want to do is this:
if(timer_4 == HIGH) LED_state = !LED_state
Here is the actual code:
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ETH_Init();
MX_USART3_UART_Init();
MX_USB_OTG_FS_PCD_Init();
MX_DAC1_Init();
MX_TIM3_Init();
MX_TIM4_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_2);
uint16_t timer_init_val = __HAL_TIM_GET_COUNTER(&htim4);
uint16_t timer_poll;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
timer_poll = __HAL_TIM_GET_COUNTER(&htim4);
if(timer_poll - timer_init_val >= 500) {
HAL_GPIO_TogglePin(GPIOB,LD1_Pin);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
I know that this most likely does not work. In fact. I was confused because replacing the >=500 with <=100 actually made it seem like it was working..but the LED just blinks at seemingly random intervals.
I have tried to test if !timer{..} (timer reached 0) to toggle the LED but none of my methods have seemed to work.
Am I misunderstanding something about timers? Is there something super obvious I am missing? It does not seem like it would be difficult to blink an LED with a 1sec PWM
What you have written does the following:
for 500 counts of the timer do nothing
after that turn the LED repeatedly on and off (faster than the eye can see) until the timer wraps
when the timer wraps (65.536 seconds?) goto (1).
You can't really do this with GPIO_TogglePin because you will call it many many times during the same value of the counter.
Try something like:
if (((timer_poll - timer_init_val) % 1000) < 500)
{
HAL_GPIO_WritePin(GPIOB, LD1_Pin, 0);
}
else
{
HAL_GPIO_WritePin(GPIOB, LD1_Pin, 1);
}
But this still isn't completely clean when the timer wraps. To sort that out you can adjust the period of the timer to be a multiple of the period of your sequence.
for 4 days now, I am struggling to set up External interrupt on my STM32 and I have gone through tons of reading and other people's code to get it. But no luck.
I have two buttons and when pressing either one of them I expect to light up an LED, this example is only to get it working, I wanted to have something functional before proceeding and building rest of the code.
I am sorry if the code is a little messy, but I am working on neating my coding skills.
I've gone through manuals and datasheets but nothing seems to help.
Here is my main.c
#include "stm32l1xx_hal.h"
#include "buttons.h"
static void MX_GPIO_Init(void);
bool RightButtonFlag = 0;
bool LeftButtonFlag = 0;
int main(void)
{
HAL_Init();
SystemClock_Config();
controls_Interrupt_Init();
MX_GPIO_Init();
while (1)
{
if(RightButtonFlag){
Blue_ON();
}
if(LeftButtonFlag){
Green_ON();
}
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
{
Followed by part of stm32l1xx_it.c
#include "stm32l1xx_hal.h"
#include "stm32l1xx.h"
#include "stm32l1xx_it.h"
#include "buttons.h"
extern volatile uint8_t RightButtonFlag;
extern volatile uint8_t RightButtonFlag;
void EXTI15_10_IRQHandler(void)
{
if(GPIOC->IDR & GPIO_IDR_IDR_10){
RightButtonFlag = 1;
EXTI->PR |= EXTI_PR_PR10;
}
if(GPIOC->IDR & GPIO_IDR_IDR_11){
LeftButtonFlag = 1;
EXTI->PR |= EXTI_PR_PR11;
}
}
buttons.c
void controls_Interrupt_Init(void){
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; /* Enable System Configuration Register */
SYSCFG->EXTICR[3] |= SYSCFG_EXTICR3_EXTI11_PC; /* Set up External Interrupt for Pin 11 Port C */
SYSCFG->EXTICR[3] |= SYSCFG_EXTICR3_EXTI10_PC; /* Set up External Interrupt for Pin 10 Port C */
EXTI->IMR |= EXTI_IMR_MR11;
EXTI->IMR |= EXTI_IMR_MR10;
EXTI->FTSR |= EXTI_FTSR_TR11; /* Falling trigger Selection Reg. Trigger 11 */
EXTI->FTSR |= EXTI_FTSR_TR10; /* Falling trigger Selection Reg. Trigger 10 */
NVIC_SetPriority(EXTI15_10_IRQn,1); /* Set Interrupt priority for pins 10-15 */
NVIC_EnableIRQ(EXTI15_10_IRQn); /* Enable NVIC for Pins Between 10-15 */
}
and buttons.h
void controls_Interrupt_Init(void);
#define Blue_ON() (HAL_GPIO_WritePin(BLUE_LED_PORT, BLUE_LED_PIN, 1))
#define Green_ON() (HAL_GPIO_WritePin(GREEN_LED_PORT, GREEN_LED_PIN, 1))
I am fairly new to coding and with my poor experience I expect to have screwed up something very simple.
You have to declare the flags as volatile. That's a hint to the compiler that the variable can change any time, independent of the normal program flow.
volatile bool RightButtonFlag = 0;
volatile bool LeftButtonFlag = 0;
When there is no volatile there, the compiler can assume that the flags never change in the main loop, and optimize it that way, that the variables will be loaded only once, before the loop.
Declaring them volatile only in the scope of the interrupt handler does no good, because the optimization will still take place in main(), where the volatile declaration is not visible.
It is good practice to move the declaration to a header file, and include that header everywhere the variable is referenced. Then the compiler can check that the types are indeed compatible.
UPDATE
The interrupt handler makes little sense. You set EXTI to detect a falling edge, then check if the input is high. Checking the GPIO data register is not reliable anyway, due to the bouncing effect of mechanical buttons.
You should rather check EXTI->PR in the handler, and reset the pending bit with a simple assignment instead of |=, otherwise you could accidentally clear another pending bit too.
void EXTI15_10_IRQHandler(void)
{
if(EXTI->PR & EXTI_PR_PR10){
RightButtonFlag = 1;
EXTI->PR = EXTI_PR_PR10;
}
if(EXTI->PR & EXTI_PR_11){
LeftButtonFlag = 1;
EXTI->PR = EXTI_PR_PR11;
}
}
You can still check somewhere if GPIOC->IDR actually reflects the button state, to eliminate possible hardware problems.
You can also try setting EXTI->SWIER from the debugger, or in the code, to simulate a button press.
How I could have 2 interrupts with one handler by this code below:
SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI0_PB | SYSCFG_EXTICR1_EXTI1_PC;
EXTI->IMR = EXTI_IMR_MR0 | EXTI_IMR_MR1;
EXTI->RTSR = EXTI_RTSR_TR0| EXTI_RTSR_TR1;
/* Configure NVIC for External Interrupt */
/* (6) Enable Interrupt on EXTI0_1 */
/* (7) Set priority for EXTI0_1 */
NVIC_EnableIRQ(EXTI0_1_IRQn); /* (6) */
NVIC_SetPriority(EXTI0_1_IRQn,0); /* (7) */
This is the code that the handler excecute:
void EXTI0_1_IRQHandler(void)
{
if ((EXTI->PR & EXTI_PR_PR1) == EXTI_PR_PR1) /* Check line 1 has triggered the IT */
{
EXTI->PR = EXTI_PR_PR1; /* Clear the pending bit */
GPIOC->ODR |= 1<<0;
}
if ((EXTI->PR & EXTI_PR_PR0) == EXTI_PR_PR0) /* Check line 0 has triggered the IT */
{
EXTI->PR = EXTI_PR_PR0; /* Clear the pending bit */
GPIOC->ODR &= ~(1<<0);
}
}
The code works fine when I click on the button that is connected to PC1, the LED turns on and when I click on the button that is connected to PB0 the LED turns off.
In my if structures I check which line is active but I also want the LED only turns on by clicking on PC1 and not with a click on another pin on line 1, the same for line 0 but I don't know how I can change the conditions for the if structures.
The micro-controller is a STM32F091.
First: you can't connect more than one pin (A..Fx) per EXTIx line (see RM0091 page 177). So EXTI line 0 IRQ is strictly correspond to one pin: C0 in your code.
Second: don't use IRQs for serve buttons. You must implement bounce filter and best idea for this is check button's pin state by timer. Human reaction is about 200ms, really pushed button will produce pulse with duration 100-200ms. So you need timer with 12-24ms and two bytes in RAM for each button... See code example bellow:
uint8_t btn_state = (uint8_t)0x0, btn_mask = (uint8_t)0x1;
void some_tim_irq_handler(void)
{
if (GPIOC->IDR & (uint16_t)0x1) { // PC0 up
btn_state |= btn_mask;
} else { // PC0 down
btn_state &= (uint8_t)~btn_mask;
}
btn_mask <<= (uint8_t)0x1; // mask cycle
if (btn_state == (uint8_t)0x0) {
// One state
return;
}
if (btn_state == (uint8_t)0xFF) {
// Second state
}
}
I am currently using an interrupt to reset an ATTiny20. Here is the relevant code:
int main(void)
{
...
// Set up interrupt for reset button (PCINT5)
SREG |= 1<<7; // Enable global interrupts
GIMSK |= 1<<PCIE0; // Enable Pin Change Interrupt 0 (enables interrupts on PCINT[7:0]
PCMSK0 |= 1<<PCINT5; // Enable PCINT5 (physical pin 8) interrupt
...
}
The interrupt-handling function:
ISR(PCINT0_vect)
{
if (!(BUTTON_1_PORT & 1<<BUTTON_1_PIN)) // Only reset if button is pushed
{
wdt_enable(WDTO_2S);
while(1){};
}
}
This works quite well - when the button is pushed the system freezes for 2 seconds and then resets... and promptly gets stuck in a reset loop. A bit of googling uncovered the culprit: on newer chips the watchdog timer is left enabled (at its shortest delay setting) after a watchdog reset. The following code is meant to remedy this issue:
// Disable watchdog on reset
void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3")));
void wdt_init(void)
{
// MCUSR = 0; // See below for reason for commenting this line
wdt_disable();
return;
}
*N.B. MCUSR = 0 is commented out because MCUSR does not exist on the ATTiny20. I have tried replacing it with SREG = 0 but to no avail.
Even with this code in place, which should disable the watchdog timer, the issue persists. Flashing LEDs on the device indicate that the program is running through part of the main() function before it resets, but putting wdt_disable(); at the top of main() has not helped either.
Is there something critical that I'm missing re: the ATTiny20? Something I've missed in the datasheet? The problem - and solution - seem so obvious, but I'm stumped. I'm using Atmel Studio 6.1.
// Disable watchdog on reset
void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3")));
void wdt_init(void)
{
// This is the flag that must be cleared on an ATTiny20 before the WDT can be disabled
/***************/
/* RSTFLR = 0; */
/***************?
wdt_disable();
return;
}
I generated my code from STM32CubeMx and wanted to generate a update event every 1µs. I work with the internal clock at 48MHz, which should be with Prescaler:0 and Autoreload:47 result to 1µs.
I use a STM32F030 with TrueStudio V.9.0.0
generated code
/* TIM17 init function */
static void MX_TIM17_Init(void)
{
LL_TIM_InitTypeDef TIM_InitStruct;
/* Peripheral clock enable */
LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_TIM17);
/* TIM17 interrupt Init */
NVIC_SetPriority(TIM17_IRQn, 3);
NVIC_EnableIRQ(TIM17_IRQn);
TIM_InitStruct.Prescaler = 0;
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
TIM_InitStruct.Autoreload = 47;
TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
TIM_InitStruct.RepetitionCounter = 0;
LL_TIM_Init(TIM17, &TIM_InitStruct);
LL_TIM_EnableARRPreload(TIM17);
}
I added in my init:
LL_TIM_EnableIT_UPDATE(TIM17);
LL_TIM_EnableCounter(TIM17);
In the IRQ_Handler i toggle a PIN:
void TIM17_IRQHandler(void)
{
/* USER CODE BEGIN TIM17_IRQn 0 */
LL_GPIO_TogglePin(LED_D2_2_GPIO_Port,LED_D2_2_Pin);
/* USER CODE END TIM17_IRQn 0 */
/* USER CODE BEGIN TIM17_IRQn 1 */
/* USER CODE END TIM17_IRQn 1 */
}
After flashing my device with the code it generates a Signal with Frequency 889kHz with Pulsewidth of 564ns measured with Oscilloscope. Changes on Prescaler or Autoreload does not affect this output, it stays right away at T_Pulse=564ns or F=889kHz.
Any idea what I am missing here?
Register output from debugging:
CR1:0x81 CR2:0
DIER:0x01 SR:0x03
CCMR1_O/I:0 CCER:0
PSC:0 ARR:0x2f
RCR:0 CCR1:0
BDTR:0 DCR:0
DMAR:0x81
The solution was the clearance of the FLAG UIE in TIM17->SR in the IRQ_Handler.
I was stuck permanently in the IRQ routine.