Interfacing IR sensor with the STM32F407 Discovery Kit using External Interrupt
Introduction
Hi !!๐ Today we are going to interface an IR (Infrared) Sensor with our STM32F407 Discovery Kit. We will be doing this by using external interrupts and therefore go through the process of setting up an external interrupt on our MCU. The IR sensor is used for detecting objects or things in its vicinity. It sends out a LOW
signal from one of its pins when it detects that something is near it. In our application, we are going to toggle an external LED, whenever the IR sensor detects something.
Working Principle of the IR Sensor
The IR Sensor consists of an Infrared Transmitter and an Infrared Receiver, which helps it in detecting an obstacle or object in its neighborhood, it basically act as proximity sensors. It consists of three pins which are VCC, OUT, and GND. There exists an onboard potentiometer, which controls the distance from the transmitter and receiver that needs to be monitored. By turning it we can control its sensitivity. There is also an onboard Obstacle LED on the sensor that glows when the module detects something. The IR transmitter sends out IR waves and they bounce back from an object or an object in its surrounding, which is then captured by the receiver. Whenever there is something surrounding, the module, the OUT pin on the module goes to a LOW
state. We are going to use this fact to enable our external interrupt.
The IR sensor used here is known as an active IR sensor since it has both an IR emitter and transmitter. Passive IR sensors don't have a transmitter and work with the heat or IR rays emitted by a body, they are also known as PIR (Passive Infrared) sensors.
Components Required
An STM32F4 based MCU
An IR Sensor module
A few Jumper wires
With these components, you can go ahead and start programming your MCU. The connection details are provided in my GitHub repository, whose link I have given at the end of this blog.
Programming our MCU
I have used bare-metal programming for the coding, which involves Bitwise AND/OR/XOR operations on the SFRs (Special Function Registers) present on the MCU.
1. Including the Header Files
#include<stm32f4xx.h>
#define ARM_MATH_CM4
These are the header files included in the main.c
file at the top, for the MCU and the math operations required.
2. Declaring the User-Defined Functions
void GPIO_Init();
void EXTI0_Init();
void TIM3_ms_Delay(int delay);
void NVIC_Init();
void EXTI0_IRQHandler();
Here, I have used Timer 3, for generating delays in the code, which is initialized in void TIM3_ms_Delay(int delay)
. The external interrupt is enabled and initialized in void EXTI0_Init()
and void NVIC_Init()
. The void GPIO_Init()
function is used to initialize the pins for interfacing the sensor. Finally, the void EXTI0_IRQHandler()
is the interrupt service routine to perform the actions when an interrupt occurs.
3. Writing the User-Defined Functions
- Let's start by looking at
void GPIO_Init()
,
void GPIO_Init(){
RCC->AHB1ENR |= 1;// Enable the GPIO port A clock
GPIOA->MODER |= (1<<14); //Configuring PA7 as output if external LED indication is required
//Since we require PA0 to be input we do no make any changes to GPIOA->MODER
}
First, we enable the clock for PORT A
, by writing 1
to the 0th bit of RCC->AHB1ENR
register, since we will be using PA0
for the external interrupt and PA7
for the external LED. The EXTI0
or External Interrupt 0 is connected to PA0
. After that, we need to set PA7
in Output Mode to toggle the LED when the sensor detects something. This is done after enabling the clock for port A, using the GPIOA->MODER
register. We do this by writing 01
to MODER7[1:0]
in that register.
- Now, in the
void TIM3_ms_Delay(int delay)
we set up Timer 3, to give us the required delay which is given as an input to the function.
void TIM3_ms_Delay(int delay){
RCC->APB1ENR |= (1<<1); //Enable the clock for TIM3
TIM3->PSC = 16000-1; //Set the clock frequency to 1KHz
TIM3->ARR = (int)delay; // Get the required delay from user
TIM3->CNT = 0;
TIM3->CR1 |= 1; //Start the timer
while(!(TIM3->SR & 1)){} // Wait for the "Update Interrupt Flag"
TIM2->SR &= ~(0x0001); //Reset the update interrupt flag // Clear the "Update Interrupt Flag"
}
Firstly, we enable the clock signal for the Timer 3, by writing a 1
to the 1st bit in the RCC->APB1ENR
register.
Then we get the value from the user for the Auto-Reload Register, to generate the desired time delay in milliseconds. We set the Prescaler to 16000-1
to get the clock frequency down from 16MHz, which is the default clock frequency, to 1KHz.
The TIM3->ARR
register only takes int
values, hence the delay
is converted to int
. Then we set the count value of the timer to zero and start the timer with the lines TIM3->CNT = 0;
and TIM3->CR1 |= 1; //Start the Timer
respectively. After the timer starts ticking we continuously poll the Status register for the Update Interrupt Flag, to check if it's overflowed with the line while(!(TIM3->SR & 1)){} //Wait for the "Update Interrupt Flag"
. Once, it's overflowed we come out of the while loop and clear the UIF in the Status Register.
- Then comes the initialization and enabling of the external interrupt,
void EXTI0_Init(){
EXTI->IMR |= 1;// Interrupt is not masked on EXTI line 0 (Enabled Interrupt)
EXTI->FTSR |= 1;// Falling Edge trigger Enabled for the interrupt
}
Here, we access the Interrupt Mask Register and enable External Interrupt on line 0 by writing a 1
to the 0th bit of the EXTI->IMR
.
After that, we need to configure our interrupt to occur while detecting a Falling Edge, a Rising Edge or both. In our case, the OUT pin of the IR sensor module goes to a LOW
state when it detects something, so the output falls, hence we configure the interrupt to fire on a Falling Edge. We achieve this by writing a 1
to the 0th bit of the EXTI->FTSR
register.
Now, we need to configure the NVIC
or Nested Vector Interrupt Controller, which controls all the interrupts that are enabled.
void NVIC_Init(){
NVIC->ISER[0] |= 1<<6;// Setting the EXTI0 interrupt
}
We access the NVIC's Interrupt Set-Enable Register, to activate the interrupt based on its priority.
From the table shown above, we can see that the EXTI0
interrupt lies at the 6th position. So, we access the NVIC->ISER[0]
register and write a 1
to its 6th bit. Thereby, activating the interrupt.
- Then, comes the Interrupt Service Routine
void EXTI0_IRQHandler( )
{
GPIOA->ODR ^= (1<<7);
EXTI->PR |= 1;
}
The first line in the function is written to toggle the external LED whenever the OUT pin of the sensor module goes to a LOW
state, i.e. whenever there the IR sensor detects something in its vicinity. This LED toggling is achieved by carrying out a Bitwise XOR operation with the 7th bit of the GPIOA->ODR
register.
Then we clear the pending interrupt in the EXTI->PR
register by writing a 1
to its 0th position.
4. Writing the main function
int main(){
GPIO_Init();
EXTI0_Init();
NVIC_Init();
while(1){
}
}
In the int main()
function we call all the initialization functions that we defined above. Then, in the while loop, we just wait for the interrupt to occur which is serviced in the Interrupt Service Routine.
Conclusion
So, we were successfully able to interface the IR sensor with our STM32F407 Discovery Kit. The reason behind using interrupts is to free up the CPU from menial tasks like polling, which uses up its precious computational resources. These resources instead can be allocated to other useful tasks, that need them. This is where multitasking comes into the picture, which is a necessity for developing complex systems, and using interrupts help us realize them.
I hope you found this article useful and easy to understand๐. For the entire code, please visit my GitHub repository by clicking here.
And, the demo video for this tutorial can be found here.