Multiple interrupts on the same EXTI Line STM32 - c

Is it possible to get multiple interrupts from te same EXTI line for par example for PA1 and PC1 they are both on EXTI1.
So that by clicking on a button on PA1 a LED go on at PB6, And by clicking on PC1 that a LED toggle on PC0.
Microcontroller ==> STM32F091
That is the code that i use for interrupts from 2 different lines:
//PC1
SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI1_PA;
EXTI->IMR = EXTI_IMR_MR1;
EXTI->RTSR = EXTI_RTSR_TR1;
EXTI->FTSR = EXTI_FTSR_TR1;
//PB0
SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI1_PC;
EXTI->IMR |= EXTI_IMR_MR1;
EXTI->RTSR |= EXTI_RTSR_TR1;
EXTI->FTSR |= EXTI_FTSR_TR1;
NVIC_EnableIRQ(EXTI0_1_IRQn);
NVIC_SetPriority(EXTI0_1_IRQn,0);
The interrupt Handler:
void EXTI0_1_IRQHandler(void)
{
// Check line 1 has triggered the IT.
if ((EXTI->PR & EXTI_PR_PR1) == EXTI_PR_PR1)
{
EXTI->PR = EXTI_PR_PR1; // Clear the pending bit.
GPIOC->ODR ^= 1 << 0;
}
// Check line 0 has triggered the IT.
if ((EXTI->PR & EXTI_PR_PR0) == EXTI_PR_PR0)
{
EXTI->PR = EXTI_PR_PR0; // Clear the pending bit.
GPIOB->ODR ^= 1 << 6;
}
}

No, you can't. Sorry.
Each of the 16 GPIO-driven EXTIs can only be connected to one of the six corresponding pins. For instance, EXTI0 can be connected to PA0, PB0, PC0, PD0, PE0, or PF0, etc. Values like SYSCFG_EXTICR1_EXTI1_PA are not pure bitmasks, and cannot be combined.
For additional details, see section 12.2.5 of the STM32F0 reference manual.

Related

SMT32 How do you make the LED toggle when the button is pressed and simultaneously with another button?

//setup
RCC->AHBENR |= RCC_AHBENR_GPIOAEN; //enable the bus for port IOPA.
GPIOA->MODER |= 0x400; // MODER5 (PA5) LD2,
GPIOA->MODER |= 0x100000; // MODER10 (PA10) LED
GPIOC->PUPDR |= 0x2000000; // (PC13) B1
GPIOA->PUPDR |= 0x20; //(PA2) B2
//main
while (1)
{
if(GPIOA->IDR |= 0x4){
GPIOA->BSRR = 0x400;
HAL_Delay(1000);
GPIOA->BRR = 0x400;
HAL_Delay(1000);
}
if(GPIOC->IDR &= 0x2000){
GPIOA->BRR = 0x20;
}
else{
GPIOA->BSRR = 0x20;
}
}
So the first if statement, the led is toggling on its own and the button is not working.
the second if statement is correct but it needs to work like simultaneously like millis() in Arduino IDE.
My questions are, is there anything I missed with configuring the button and how to make it work simultaneously?
You have made some very basic errors with C operators.
if(GPIOA->IDR |= 0x4)
means the same as:
temporary_value = (GPIOA->IDR | 0x4);
GPIOA->IDR = temporary_value;
if (temporary_value != 0)
This is wrong for two reasons: firstly, you aren't allowed to write to IDR, it is the input data register. Secondly the if control expression will always be true because anything OR 4 is always not equal to zero.
Similarly further down &= is also an assignment operator, you are trying to write to the input register again.
To test if a bit in a register is set, use:
if ((GPIOA->IDR & (1 << n)) != 0)
Where n is the bit number you want to test. Don't calculate the bitmask in your head (eg: 0x400) because you are more likely to make an error.

How to turn a digital output off after 2 seconds using a timer

I have an Atmega328p and want to turn on a digital output with a button press, then have it turn off automatically after 2 seconds.
I know how to use a hardware interrupt for the button, but how do I set up a timer interrupt to automatically turn the digital output back off?
UPDATE:
I was able to figure it out. Here's my solution (only showing the pertinent functions):
static inline void initTimer1(void) {
TCCR1B |= (1 << WGM12); // CTC Mode, immediate
TCCR1B |= (1 << CS10) | (1 << CS12); // Clock speed: 16 MHz / 1024, ~= 15.6 ticks per ms
}
void set_valve_on_time(uint16_t on_time) {
OCR1A = on_time; // set output compare register for valve on time
}
void open_valve(uint8_t state) {
if (state > 0) {
PORTD |= (1 << PIND6); //turn on PD6, open valve
PORTD &= ~(1 << PIND7); //turn off PD7, turn off close valve in case it was on
if (state == 2) {
TCNT1 = 0;
TIFR1 |= (1 << OCF1A); // clear output compare match flag
TIMSK1 |= (1 << OCIE1A); // enable output compare interrupt
}
}
else {
PORTD &= ~(1 << PIND6); //turn off PD6, stop opening valve
}
}
ISR(TIMER1_COMPA_vect) {
TIMSK1 &= ~(1 << OCIE1A); // disable output compare interrupt
open_valve(0); //turn off close valve output
}
The open_valve function is called by a button press (not shown). The hardest time I had was figuring out that I needed TIFR1 |= (1 << OCF1A) for it to work correctly. I still don't quite understand why, because I thought the ISR was supposed to do this automatically.
you have to roughly follow these steps:
on your button handling routine set up the timer with the folling properties:
best use 16bit timer in CTC mode (if you have free timers available)
set the prescaler so that the timer otherflows a bit slower than your compare value:
for 2sec and 10MHz CPU frequency i would run it on the 1/1024 prescaler, so a overflow would occure on (10.000.000/1024/65536) -> 1 overflow per ~6.7s (with 265 it would overflow more often than once per 2s)
set the ctc top value (see description of your selected mode - the specific register varies) to a value that is reached after 2s : 10.000.000/1024 * 2s --> 19531
implement the ISR (see which would be the correct one in the selected CTC mode) the and activate the Interupt in the mask register
in the ISR set your output, and stop the timer
Bonus: setup the timer that it uses the output compare pins to deactivate the output
Then no ISR is required at all, just set the Compare Output Mode to 'clear on Match'
If you do not have a free 16bit timer, i would suggest to use 1 timer in CTC mode to generate a (10) millisecond timebase, and implement the time-counting logic in this ms-event handling.

Getting DMA USART to work on STM32L053R8T6

I am having an issue in getting my computer (virtual COM port, to be exact) to communicate with my STM32L053R8T6 (Nucleo) board by DMA and USART. Here is my code for the DMA and USART part:
#include "Device/Include/stm32l0xx.h" // Device header
#include "JB.h"
#include <string.h>
#define PCLK 32000000
#define BAUD 19200
uint8_t stringtosend[] = "test\n";
uint8_t stringtoreceive[] = " ";
void ENABLE_UART_DMA(void){
RCC->AHBENR |= RCC_AHBENR_DMA1EN; //enable periph.clk for DMA1
/**Enabling DMA for transmission
* DMA1, Channel 4 mapped for USART2TX
* USART2 TDR for peripheral address
* stringtosend for data address
* Memory increment, memory to peripheral | 8-bit transfer | transfer complete interrupt**/
DMA1_CSELR->CSELR = (DMA1_CSELR->CSELR & ~DMA_CSELR_C4S) | (4 << (3 * 4));
DMA1_Channel4->CPAR = (uint32_t)&(USART2->TDR);
DMA1_Channel4->CMAR = (uint32_t)stringtosend;
DMA1_Channel4->CCR = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE;
/**Enabling DMA for reception
* DMA1, Channel 5 mapped for USART2RX
* USART2 RDR for peripheral address
* stringtoreceive for data address
* Data size given
* Memory increment, peripheral to memory | 8-bit transfer | transfer complete interrupt**/
DMA1_CSELR->CSELR = (DMA1_CSELR->CSELR & ~DMA_CSELR_C5S) | (4 << (4 * 4));
DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR);
DMA1_Channel5->CMAR = (uint32_t)stringtoreceive;
DMA1_Channel5->CNDTR = sizeof(stringtoreceive);
DMA1_Channel5->CCR = DMA_CCR_MINC | DMA_CCR_TCIE | DMA_CCR_EN;
NVIC_SetPriority(DMA1_Channel4_5_6_7_IRQn, 0); //NVIC enabled, max priority, channels 4-7
NVIC_EnableIRQ(DMA1_Channel4_5_6_7_IRQn);
}
void CONFIGURE_UART_PARAM(void){
RCC->IOPENR |= ( 1ul << 0); //Enable GPIOA clock
RCC->APB1ENR |= ( 1ul << 17); //Enable USART#2 clock
GPIOA->AFR[0] &= ~((15ul << 4* 3) | (15ul << 4* 2) ); //Clear PA2,PA3
GPIOA->AFR[0] |= (( 4ul << 4* 3) | ( 4ul << 4* 2) ); //Set PA2,PA3
GPIOA->MODER &= ~(( 3ul << 2* 3) | ( 3ul << 2* 2) ); //Same as above
GPIOA->MODER |= (( 2ul << 2* 3) | ( 2ul << 2* 2) );
USART2->BRR = PCLK/BAUD;
USART2->CR3 = USART_CR3_DMAT | USART_CR3_DMAR; //Enable DMA mode in transmit and receive
/*UART enabled for transmission and reception*/
USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
while((USART2->ISR & USART_ISR_TC) != USART_ISR_TC)
{
/* add time out here for a robust application */
}
USART2->ICR = USART_ICR_TCCF;
}
void CONFIGURE_EXTI(void){
SYSCFG->EXTICR[0] = ((SYSCFG->EXTICR[0] & 0x0000) | SYSCFG_EXTICR4_EXTI13_PC); //clear EXTICR and set to PC13(B1)
EXTI->FTSR |= EXTI_FTSR_TR13; //falling edge trigger
EXTI->IMR |= EXTI_IMR_IM13; //unmask
NVIC_SetPriority(EXTI4_15_IRQn, 0); //def interrupt
NVIC_EnableIRQ(EXTI4_15_IRQn);
}
/*************************************************************************************************************************************************************************************************************************/
/*************************************************************************************************************************************************************************************************************************/
/*Interrupt Handlers*/
void DMA1_Channel4_5_6_7IRQHandler(void){
if((DMA1->ISR & DMA_ISR_TCIF4) == DMA_ISR_TCIF4){
DMA1->IFCR = DMA_IFCR_CTCIF4; //Clear Channel 4 Transfer Complete flag
}
else if((DMA1->ISR & DMA_ISR_TCIF5) == DMA_ISR_TCIF5){
DMA1->IFCR = DMA_IFCR_CTCIF5; //Clear Channel 5 Transfer Complete flag
DMA1_Channel5->CCR &= ~DMA_CCR_EN;
DMA1_Channel5->CNDTR = sizeof(stringtoreceive);/* Data size */
DMA1_Channel5->CCR |= DMA_CCR_EN;
}
}
void EXTI4_15_IRQHandler(void){
if(!(GPIOC->IDR & (1 << 13))){
/* Clear EXTI 13 flag */
EXTI->PR = EXTI_PR_PIF13;
/* start 8-bit transmission with DMA */
DMA1_Channel4->CCR &= ~DMA_CCR_EN; //channel disable
DMA1_Channel4->CNDTR = sizeof(stringtosend);/* Data size */
DMA1_Channel4->CCR |= DMA_CCR_EN; //channel enable
}
}
//void EXTI4_15_IRQHandler(void){
// if((EXTI->PR & EXTI_PR_PIF13) == EXTI_PR_PIF13){
// /* Clear EXTI 13 flag */
// EXTI->PR = EXTI_PR_PIF13;
//
// /* start 8-bit transmission with DMA */
// DMA1_Channel4->CCR &= ~DMA_CCR_EN; //channel disable
// DMA1_Channel4->CNDTR = sizeof(stringtosend);/* Data size */
// DMA1_Channel4->CCR |= DMA_CCR_EN; //channel enable
// }
//}
Now then, this specific code is based on an example from the STM32L0 snippets package 1.20, USART/Communcation Using DMA. USART 1 was simply redefined to USART 2 (as that is the one used by the virtual COM port), and the DMA channels were redefined according to that as well. However, the problem here is very simple: it will only print stringtosend once (would like to do it every time button B1 is pressed), and will not receive data by RX either - as if it completely ignores the DMA interrupt handler - which I am not sure how to test (no trace features available on this board). What I have seems to reflect the reference manual well enough, and all the main does is:
int main(){
SystemCoreClockInit();
CONFIGURE_UART_PARAM();
ENABLE_UART_DMA();
pushbutton_def();
CONFIGURE_EXTI();
while(1){
}
...which should just react to the defined interrupts, however it does not, and for the life of me, I cannot see why. I would love if you could help me - I would also like to avoid HAL or LL APIs - this is not a complex enough project to warrant their usage (several inputs, outputs, comms between two boards by USART/DMA), plus I would prefer to learn working closer to the register level.
Thanks!
edit (in response to Berendi's suggestions):
1. GPIOC was defined in another file, called with pushbutton_def():
RCC->IOPENR |= (1UL << 2); //enable GPIOC
I understand exactly what you mean by your explanation (indeed, the register referred by those two is "the same", 0x00000020U), but I am not sure as to how to redefine it: here is my attempt after looking at the reference manual (SYSCFG part) and the source (still, it does not work):
SYSCFG->EXTICR[3] = ((SYSCFG->EXTICR[3] & 0x0000) | SYSCFG_EXTICR4_EXTI13_PC);
As suggested, I have added USART2->ICR = USART_ICR_TCCF; to the EXTIhandler, right after the DMA channels. I have kept it in the USART definition. The message is still only being sent once, though.
GPIOC is not enabled
Here,
RCC->IOPENR |= ( 1ul << 0); //Enable GPIOA clock
you should enable GPIOC too.
EXTI13 is mapped to PA13
Here,
SYSCFG->EXTICR[0] = ((SYSCFG->EXTICR[0] & 0x0000) | SYSCFG_EXTICR4_EXTI13_PC); //clear EXTICR and set to PC13(B1)
you are setting the configuration register for EXTI0-EXTI3, actually mapping EXTI1 to PC1. EXTI13 remains mapped to PA13, which is actually SWDIO, connected to the onboard debugger. I guess the traffic on SWDIO triggers the EXTI interrupt, the handler checks PC13 which is always reading 0 because the port is disabled, and enables DMA. DMA transmit works only once though, because
USART_ISR_TC is not cleared in the interrupt
but only once at startup. You should move this line
USART2->ICR = USART_ICR_TCCF;
to the EXTI interrupt handler.
I'm not sure why receiving doesn't work, perhaps the DMA handler has no chance to run, because EXTI is constantly retriggered by SWD traffic. Both interrupts have the same priority, the one with lower interrupt number wins, which is the EXTI handler. If it's always retriggered before it finishes, then it will be called again, not letting the other handler to run.

Weird behaviour of timer2 on ATmega328

I'm currently working on a piece of code, which should wait for a comparator interrupt and execute some other code after a set amount of time. Now, I thought using Timer2 in CTC mode would be a good idea to make sure that the program waits for the right amount of time and came up with this:
void setup(){
...
// Set up the timer
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
OCR2A = 255; // compare match register
TCCR2A = (1 << WGM21); // CTC mode
TCCR2B = ((1 << CS22) | (1 << CS21)); // 256 prescaler
TIMSK2 &= ~(1 << OCIE2A); // disable interrupt
}
ISR(ANALOG_COMP_vect) {
// switchTime is in µs, usual value: around 500µs
// with a 16 Mhz crystal and a 256 prescale we need to devide
// the switchTime by 16 (2^4)
OCR2A = switchTime >> 4;
TCNT2 = 0; // reset counter
TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt
}
ISR(TIMER2_COMPA_vect) {
TIMSK2 &= ~(1 << OCIE2A); // disable interrupt
// do stuff
}
The awkward thing is, it doesn't work. The ISR timer is immediately called after we leave the ISR comparator (I checked this by toggling a pin in both routines and measuring with an oscilloscope). After a few hours of reading datasheets and randomly changing the code I came up with a line of code that fixed it:
ISR(TIMER2_COMPA_vect) {
TIMSK2 &= ~(1 << OCIE2A); // disable interrupt
OCR2A = 255; // <- apparently fixes all my problems
// do stuff
}
I'm quite confused about this because the frequency of the timer shouldn't be a matter after we call the routine and deactivate the interrupt.
Now I'm quite glad that I've found the solution but I want to know why it works. Something about knowing how to fish and accidentally catching a fish by randomly inserting code.
I think you missed the clearing of pending timer interrupts.
ISR(TIMER2_COMPA_vect) {
TIMSK2 &= ~(1 << OCIE2A); // disable interrupt
/* Clear pending interrupts */
TIFR2 = (1 << TOV2) | (1 << OCF2A) | (1 << OCF2B);
// do stuff
}

STM32 Interrupt Handeling if condition

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
}
}

Resources