I am trying to do very simple interrupt code in STM32F401RE where I press the button and LED2 should turn on based on external interrupt triggered by the button.
I am using the user button(blue button) in nucleo board F401 which corresponds to PC13 according to the board datasheet pinout.
I tried different options but LED2 is still off, here is the code i am using:
int main(void)
{
sysconfig();
Interrupt_config();
while(1)
{
if(flag)
{
GPIOA->ODR |= (1<<5);
}
}
}
I used polling method (without interrupt) and the LED2 turns on fine when the button is pressed using only LED_initialize(); Button_init();
Haven't checked your IRQ setup code, but the handler you need for PC13 is EXTI15_10_IRQHandler.
Edit:
Another issue: EXTICR is 4 words long. This is incorrect: SYSCFG->EXTICR[4] |=(1<<5);.
Related
Objective
I am trying to interface a 4x3 matrix keypad and 7 segment LED display to PIC18f4550 microcontroller. When I press buttons on keypad, I want the 7 segment display to show the number accordingly.
What I have done so far
Based on my research, I can either use scanning (continuously polling) or use interrupts to interface the keypad with the MCU. I decided to use interrupts as that way the microcontroller can be free up for other operations.
The following is the connection of keypad to MCU.
I am using RB0-2 as input to MCU and RB4-7 are set as output from MCU. RB4-7 are permanently set high so when the user press on the keypad button, it will trigger the RB0-2(INT0-INT2) interrupt to process.
For simplicity in this post, I will only discuss about the Column 1 of the keypad.
This is how I initialize and setup the registers.
void main(void)
{
OSCCON = 0x72;
TRISD = 0;
LATD = 0;
ADCON1 = 0x0F;
TRISB = 0x07;
LATB = 0xF0;// keep the RB4-7 high
INTCONbits.GIE = 1;
INTCONbits.INT0IF = 0;
INTCONbits.INT0IE = 1;
INTCON2bits.INTEDG0 = 1;
while (1);
}
My interrupt handling is as below:
if(INTCONbits.INT0IF == 1)
{
for(char scan=0x10; scan>=0x80; scan <<= 1) // send 1 to each row starting from RB4 till RB7
{
LATB = scan;
if (PORTBbits.RB0 = 1)
{
if(scan == 0x10)
{
display_number(1);
}
if(scan == 0x20)
{
display_number(4);
}
if(scan == 0x40)
{
display_number(7);
}
}
}
LATB = 0xF0;
INTCONbits.INT0IF == 0;
My problem
When I run the simulation, I noticed that the GIE bit keep toggling very fast between 0 and 1 as soon as I press on any button in Column 1 and no display of any number on 7segment as well. I added the watch window screenshot and highlighted the GIE bit that is toggling.
What have I done wrong? Is my logic of handling interrupt flawed?
UPDATE 1
As DavidHoadley suggested, I have changed to use use unsigned char instead of char.
I have also corrected the for loop condition.
What I observed was, If I keep the loop inside the interrupt routine, the loop will get stuck for some reason.
For now, I have given up trying to use loop inside the interrupt function and instead resort to have a function in main while loop to output high at each row sequentially forever and the interrupt function is only used to check the output using switch statement.
Your for loop end condition looks like the loop will immediately exit. The line:
for(char scan=0x10; scan>=0x80; scan <<= 1)
Should probably be:
for(char scan=0x10; scan<=0x80; scan <<= 1)
Can’t test this - hope it works
It is a normal behaviour for PIC micros. When PIC goes to the interrrupt vector the GIE bit reset by hardware and when it finishes servicing the interrupt, it returns with the RETFIE assembly instruction by setting the GIE bit which is not visible in C code. So there is no fault in your code for this matter, this is not even a matter.
I see in your code you are using only INT0 interrupt to detect presses and the rest of INTx pins are not activated. That's why the PIC micro will be able to detect the changes of the only first column. I suggest you to use interrupt on change (IOC) which features on RB<7:4> bits. This way you can free the INTx pins for other purposes. And you can move the 3 pins to a PORT other than PORTB. Here is the procedure or my suggestion if you interest:
Configure RB<7:4> pins as inputs and enable their IOC feature.
Configure any 3 pins as output of any port.
If you use the positive logic set the 3 pins high otherwise, low.
In your ISR poll the RBIF to know if there is a change on RB<7:4> pins.
If so make a button scan to detect the pressed key.
Unfortunately can't tell you anything for your display issue since you haven't shared the codes and the configuration of display.
I am currently working on a low-power project using the Adafruit Feather M0 microprocessor. A requirement of my project is to be able to sleep the CPU and wake it again using an external interrupt triggered from the MPU6050 accelerometer.
I have tested the following code sample from GitHub - it works successfully! The question that I need answering is how to I alter this sample code to work on Pin 13 of the feather, rather than pin 6.
#define interruptPin 6
volatile bool SLEEP_FLAG;
void EIC_ISR(void) {
SLEEP_FLAG ^= true; // toggle SLEEP_FLAG by XORing it against true
//Serial.print("EIC_ISR SLEEP_FLAG = ");
//Serial.println(SLEEP_FLAG);
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
delay(3000); // wait for console opening
attachInterrupt(digitalPinToInterrupt(interruptPin), EIC_ISR, CHANGE); // Attach interrupt to pin 6 with an ISR and when the pin state CHANGEs
SYSCTRL->XOSC32K.reg |= (SYSCTRL_XOSC32K_RUNSTDBY | SYSCTRL_XOSC32K_ONDEMAND); // set external 32k oscillator to run when idle or sleep mode is chosen
REG_GCLK_CLKCTRL |= GCLK_CLKCTRL_ID(GCM_EIC) | // generic clock multiplexer id for the external interrupt controller
GCLK_CLKCTRL_GEN_GCLK1 | // generic clock 1 which is xosc32k
GCLK_CLKCTRL_CLKEN; // enable it
while (GCLK->STATUS.bit.SYNCBUSY); // write protected, wait for sync
EIC->WAKEUP.reg |= EIC_WAKEUP_WAKEUPEN4; // Set External Interrupt Controller to use channel 4 (pin 6)
PM->SLEEP.reg |= PM_SLEEP_IDLE_CPU; // Enable Idle0 mode - sleep CPU clock only
//PM->SLEEP.reg |= PM_SLEEP_IDLE_AHB; // Idle1 - sleep CPU and AHB clocks
//PM->SLEEP.reg |= PM_SLEEP_IDLE_APB; // Idle2 - sleep CPU, AHB, and APB clocks
// It is either Idle mode or Standby mode, not both.
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // Enable Standby or "deep sleep" mode
SLEEP_FLAG = false; // begin awake
// Built-in LED set to output and high
PORT->Group[g_APinDescription[LED_BUILTIN].ulPort].DIRSET.reg = (uint32_t)(1<<g_APinDescription[LED_BUILTIN].ulPin); // set pin direction to output
PORT->Group[g_APinDescription[LED_BUILTIN].ulPort].OUTSET.reg = (uint32_t)(1<<g_APinDescription[LED_BUILTIN].ulPin); // set pin mode to high
Serial.println("Setup() Run!");
}
void loop() {
// put your main code here, to run repeatedly:
if (SLEEP_FLAG == true) {
PORT->Group[g_APinDescription[LED_BUILTIN].ulPort].OUTCLR.reg = (uint32_t)(1<<g_APinDescription[LED_BUILTIN].ulPin); // set pin mode to low
Serial.println("I'm going to sleep now.");
__WFI(); // wake from interrupt
SLEEP_FLAG = false;
Serial.println("Ok, I'm awake");
Serial.println();
}
//Serial.print("SLEEP_FLAG = ");
//Serial.println(SLEEP_FLAG);
PORT->Group[g_APinDescription[LED_BUILTIN].ulPort].OUTTGL.reg = (uint32_t)(1<<g_APinDescription[LED_BUILTIN].ulPin); // toggle output of built-in LED pin
delay(1000);
}
As per the pinout diagram and Atmel datasheet, I am struggling to work out which changes to make to allow pin 13 to operate in the same way as pin 6.
Atmel Datasheet
The obvious solution is to change the following lines...
#define interruptPin 13
EIC->WAKEUP.reg |= EIC_WAKEUP_WAKEUPEN1; // Set External Interrupt Controller to use channel 4 (pin 6)
I suspected channel 1 (WAKEUPEN1) due to the ENINT^1 next to pin 13 on the pinout diagram. But this didn't work, the code pin operation did not exhibit the same behaviour as the pin 6 setup.
I would be very grateful for any suggestion of how to implement this code working on Pin 13. Many thanks for your support.
I'm not an authority here, and your code looks correct to me.
Except, the pin out shows Pin 13 is the built-in LED line, and you manipulate LED_BUILTIN several places in your code. That's almost certainly conflicting with your attempts to use 13 as an interrupt line.
I was recently trying to make an interrupt on my atmega328p using atmelstudio to make a LED that is connected to digitalpin 13/PB5/PCINT5 blink four times as slow as normal when the button that is connected to a 5V output and digitalpin 2/PD0/PCINT18 is pressed down.
But whenever I run the code and press the button it will never(as far as i can tell) go true the interrupt code.
#include <avr/io.h>
#include <avr/interrupt.h>
volatile int t = 1;
int main(void)
{
init();
Serial.begin(9600);
DDRB = (1 << 5);
DDRD &= ~(1 << DDD2); // Clear the PD2 pin
// PD2 (PCINT0 pin) is now an input
PORTD |= (1 << PORTD2); // turn On the Pull-up
// PD2 is now an input with pull-up enabled
EICRA |= (1 << ISC00); // set INT0 to trigger on ANY logic change
EIMSK |= (1 << INT0); // Turns on INT0
sei(); // turn on interrupts
Serial.println("loop started");
while(1)
{
PORTB ^= (1 << 5);
delay(500*t);
}
}
ISR (INT0_vect)
{
Serial.println("interrupt");
if(t=1){
t=4;
}
else{
t=1;
}
}
I went through the datasheet multiple times and eventually stumbled onto this code online (yea yea i know i'm a real piece of work) and added my own pieces to it.
but it even this does not work, does anybody know why?
There are a few possible problems in your code:
The most important is the assignment in the if condition that was already mentioned in the comments.
clearly another one is the also mentioned serial.print stuff in the ISR.
ISRs should be as short and simple as possible.
Another is the hardware. If you press a button, they bounces and usually give multiple interrupts. so look for some de-bouncing code or have a look in the arduino library if there is something there. you may have to change the code, because usually the hardware logic itself is handled in interrupts, but the actual testing of the button states should belong to main code.
advanced stuff - if you currently reading tutorials and teach your self - ignore this, but may be keep in mind for actual projects
Another issue is the program design: your processor now cannot do anything else then toggling LEDs because his main program flow waits.
Normally you would want to use a hardware timer for this kind of tasks.
Either use it as a time base, that signals passed intervals to the main via a volatile flag variable. or directly use the PWM-Feature to directly interface the LED via one of the Output Compare Pins (OC[012][AB]).
i am trying to understand a program but i have some questions, maybe you can help me. The microcontroller used is a ATMEL 2549 - 8bit. Thank you in advance.
Atmel-2549 8-bit AVR Microcontroller ATmega640 1280-1281-2560-2561 datasheet
Set up a stop watch with the following features:
• There are three push buttons: START, STOP, and RESET
• Your system generates an interrupt every 100ms by using Timer1.
• There is an LCD at port A. Use the usual library for controlling the
LCD!
• On the LCD, you display the time that has elapsed since the START
button was pushed. Show minutes, seconds and tenth of seconds.
• After 59 min 59.9 s, the display starts from scratch again.
#include <stdint.h>
#include <avr/io.h>
#include "lcd.h"
#include <stdio.h>
#include <avr/interrupt.h>
void timer1_config(void);
void exinterrupt_config(void);
void send_string(void);
display myLCD;
volatile char text[20];
volatile uint8_t minute=0,sekunde=0,zehnt=0;
ISR(TIMER1_COMPA_vect) { //Interrupt for a timer with minute, second and decisecond.
zehnt++; //from 0 to 59min 59,9sec. After that time is elapsed,
if (zehnt>9) { //it should be showed on LCD display.
zehnt=0;
sekunde=sekunde+1;
}
if (sekunde>59) {
sekunde=0;
minute=minute+1;
}
if (minute>59) {
minute=0;
sekunde=0;
zehnt=zehnt+1;
}
send_string();
}
ISR(INT0_vect) { //Interrupt for starting the timer.
// --- No. *1 ---
TCCR1B|= (1<<CS11);
}
ISR(INT1_vect) { //Interrupt for stopping the timer.
TCCR1B&=~((1<<CS10)|(1<<CS11)|(1<<CS12));
}
ISR(INT2_vect) { //Interrupt for resetting the timer.
minute=0; //Sets everything to 0 and shows time on LCD display.
sekunde=0;
zehnt=0;
TCNT1=0;
send_string();
}
int main(void) {
// --- No. *2 ---
DDRD&=~((1<<PIN0)|(1<<PIN1)|(1<<PIN2));
timer1_config(); //Load all three functions.
exinterrupt_config();
send_string();
lcd_init(&myLCD ,&PORTA); //Start the LCD display on port A.
lcd_send_string(&myLCD,"-------Watch-------",1,1);
sei();
for(;;){};
return(0);
}
void timer1_config(void){
TCCR1B|=(1<<WGM12);
OCR1A=12499;
// --- No. *3 ---
TIMSK1|=(1<<OCIE1A);
}
void exinterrupt_config(void){
EIMSK|=((1<<INT0)|(1<<INT1)|(1<<INT2)); //Enable all 3 interrupts.
// --- No. *4 ---
EICRA|=((1<<ISC01)|(1<<ISC11)|(1<<ISC21));
}
void send_string(void){ //Sends text to LCD display.
sprintf(text,"%i.min %i.sek %i.zehnt",minute,sekunde,zehnt);
lcd_send_string(&myLCD,text,3,1);
}
1: I understand this is the command to make the timer start counting, but on the description from the datasheet it says "clkI/O/8 (From prescaler)" for setting the bit CS11 high. I cant understand it and how it works.
2: Is it setting the bits from DDRD to input (0)? If so, why is it being done if port D inst even being used?
3: I dont understand what it does!
4: The description from the datasheet says "The falling edge of INTn generates asynchronously an interrupt request", but i dont really get what it does. Whats the difference to "The rising edge of INTn generates asynchronously an interrupt request"?
Thank you again!
1: Setting CS11 High
From Table 17-6, it really sets the clock to clk(I/O)/8. That means it will increment the internal counter on every eighth tick of the internal I/O clock. Maybe you couldn't count every tick in the timer's register in a second, so you need to prescale it.
2: Setting DDRD bits to input
Those are for the buttons. The buttons must be on PIND of your panel, one bit for each button. Although the program does not read PIND, the external interrupt handler does, so the data direction must be set up accordingly.
Buttons, switches are inputs, leds are outputs. It depend's on your developer panel on which ports are they wired.
3: Setting up TIMSK1
§17.11.36
• Bit 1 – OCIEnA: Timer/Countern, Output Compare A Match Interrupt
Enable When this bit is written to one, and the I-flag in the Status
Register is set (interrupts globally enabled), the Timer/Countern
Output Compare A Match interrupt is enabled. The corresponding
Interrupt Vector (see “Interrupts” on page 101) is executed when the
OCFnA Flag, located in TIFRn, is set.
The timer peripherial can operate in different modes. This setting is related to output compare mode, and it will tell the hardware to issue an interrupt when the timer's internal counter reaches a limit (the limit is set in OCR1A).
The timer is set to CTC (Clear Timer on Compare) mode in TCCR1B (Table 17-2), so it restarts counting when the limit is reached.
4: Falling edge
A falling edge is when a signal goes from high to low. A rising edge is when the signal goes from low to high. These buttons are usually Active-LOW, so a falling edge means the button is pressed. (and a rising edge means the button is released)
I have been looking all of for an answer to this problem and have not been able to find anything. When my ISR is triggered, it goes through and does everything that it is supposed to perfectly fine, then before exiting and returning back to the main loop the ISR executes again. Once it has gone though the second time it then returns back to the main loop. This only happens when I use a 115V relay to operate the interrupt.
I am trying to detect when there is a power outage or when the power comes back on. I am using a Pin change interrupt to sense if the relay is closed or open. When the power goes out the relay will open and will trigger the ISR. If I connect this setup to a normal push button or switch everything works as needed and there is no problem, it is only when it is connected to the relay that there is a problem.
Here is the code that I have:(I know I don't need cli I have just been trying everything)
ISR(PCINT2_vect){
cli();
sbi(PORTC,5);
_delay_ms(6000);
cbi(PORTC,5);
for(delay_counter=0;delay_counter<2;delay_counter++)
{
_delay_ms(6000);
}
sbi(PORTC,5);
_delay_ms(6000);
if(bit_is_set(PIND,2))
{
lcd_clrscr();
lcd_puts("Sending SMS");
usart_print("at");
USART_Transmit('\r');
_delay_ms(6000);
for(i=0;i<=1;i++)
{
usart_print("at*smsm2m=");
USART_Transmit('"');
for(j=0;j<11;j++)
{
USART_Transmit(Alert_Numbers[i][j]);
}
usart_print(" Power has been lost");
USART_Transmit('"');
USART_Transmit('\r');
_delay_ms(6000);
}
lcd_clrscr();
lcd_puts("SMS Sent");
_delay_ms(6000);
lcd_clrscr();
lcd_puts("Status:NO POWER");
cbi(PORTC,5);
}
else if(bit_is_clear(PIND,2))
{
lcd_clrscr();
lcd_puts("System Reset");
_delay_ms(6000);
_delay_ms(6000);
usart_print("at");
USART_Transmit('\r');
_delay_ms(6000);
for(i=0;i<=1;i++)
{
usart_print("at*smsm2m=");
USART_Transmit('"');
for(j=0;j<11;j++)
{
USART_Transmit(Alert_Numbers[i][j]);
}
usart_print(" Pump regained power");
USART_Transmit('"');
USART_Transmit('\r');
_delay_ms(6000);
}
lcd_clrscr();
lcd_puts("POWER ON");
_delay_ms(6000);
lcd_clrscr();
lcd_puts("Status: Good");
}
else
{
}
}
int main(void)
{ /*Initializations*/
DDRC = 0x20; // PORTC,5 is now output
sbi(PORTC,5);
USART_Init(51);
lcd_init(LCD_DISP_ON);
lcd_clrscr();
/*Set interrupts*/
DDRD = 0b11111011; // set PD2 to input
PORTD = 0b00000100; // set PD2 to high
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << PCINT0);
PCICR |= (1<<PCIE2);
PCMSK2 |= (1<<PCINT18);
sei();
lcd_clrscr();
lcd_puts("Status: Good");
/*Main Program Loop: NOP*/
while(1)
{
lcd_clrscr();
lcd_puts("MAIN LOOP");
for(delay_counter=0;delay_counter<3;delay_counter++)
{
_delay_ms(6000);
}
}
}
I am using a Pin change interrupt to sense if the relay is closed or
open.
Don't do that. Seriously. Do not try to hook mechanical switches to an interrupt pin to trigger ISRs.
If you insist on doing so, at least make sure the switch's signal is decently debounced in hardware before it hits the µC's input pin.
Besides, waiting of any kind (_delay_ms(6000);) is not something one wants to have in an ISR.
The reason your code fails: When the signal is bouncing, even if you wait 6 seconds in your interrupt, the flag is set again (while you are still in your ISR). The quick and dirty solution would be to reset the flag just before you exit the ISR.
Two (in my opinion) better solutions:
If you want to do this in software, just set a variable in the ISR routine which indicates a event. Then, in your main loop (or in a function), do the debouncing of the relais (10 to 100ms should be enough).
if you want to do this in hardware, just add a R C combination as a filter to the pin (the c to ground and the r to the relais. Just try some combinations like 100k and 10µF.
My personal recommendation:
do both ;)
Annotations:
- using a 6 second delay in an interrupt routine is really a bad practice. Better: Use a Timer which is called every say 10 ms and increase a counter if the flag is set and the relais indicate power loss. If the relais has power, reset the flag and counter to 0. If the counter reaches 600, this means the power outage is longer than 6 seconds.
Have fun!
I had the problem myself, so I decided to answer it even though it is an old entry:
The reason why the interrupt fires twice is that the interrupt flag is not reset. This is a problem of some atmega types. You can solve the problem by one simple line:
Post this at the very end of you ISR interrupt function:
PCIFR |= (1<<PCIF2);
This will write a "1" into the interrupt-flag bit for the PCINT2 interrupt. If you are using other Interrupts, you have to set other flags to 1. Notice that the interrupt flag is inverted. So a 1 disables the interrupt, whereas a 0 triggers the interrupt.
Have a look into the datasheet:
http://www.atmel.com/images/doc2545.pdf
See Point 13.2.5:
Bit 2 - PCIF2: Pin change interrupt flag 2 -
When a logic change on any PCINT23..16 pin triggers an interrupt request, PCIF2 becomes set
(one). If the I-bit in
SREG and the PCIE2 bit in PCICR are
set (one), the MCU will jump to the
corresponding Interrupt Vector. The flag is cleared
when the interrupt routine is executed. Alternatively, the flag can be cleared by writing a logical one to it.
I hope this helped you and maybe some other people who have the same problem. Just search the datasheet for the name of the register and the bit corresponding to the interrupt flag and set it to 1.
Bye