A single core microcontroller will perform only one instruction at a time. In order to multitask, we make use of hardware timers and interrupts, rather than polling. The hardware itself will monitor the time or the state of the pins, and when an interrupt happens, it stops execution of the loop and runs the ISR, which performs quickly and then returns back to the loop.
Part 1: Hardware Setup
- LED RED (Pin PF11): Configured as a digital output to act as a visual indicator for Timer 2 interrupts.
- LED BLUE (Pin PF12): Configured as a digital output to act as a visual indicator for Timer 3 interrupts.
- User Button (Pin PA0): Configured as an external interrupt (EXTI) to demonstrate real-time, event-triggered scheduling.
- DEBUG PIN (Pin PE0): Configured as a high-speed digital output to measure interrupt execution and flag delays using a debugger or logic analyzer.
Part 2: Software Architecture
The design follows a rigid Foreground/Background Architecture to schedule system tasks safely:
- Background (Interrupt Service Routines): Driven entirely by hardware events. The ISR handlers (
HAL_TIM_PeriodElapsedCallbackandHAL_GPIO_EXTI_Callback) must execute instantly. They do not process calculations directly; instead, they toggle visual LEDs and altervolatileglobal flags to signal the foreground loop. - Foreground (Main Loop Executive): A continuous
while(1)loop that constantly checks the states of the software flags. Once a flag is recognized as active (1), the foreground architecture resets the flag and enters heavy task processing blocks (simulated withHAL_Delayprocessing blocks).
Part 3: STM32CubeMX Setup1. Create a new project
- Open STM32CubeMX and click File → New Project: This opens the MCU/Board selector window.
- Search for STM32F407ZGT6: Type it in the search box under the MCU/MPU Selector tab. Click the result to select it.
- Set Project Name and Toolchain: Go to the Project Manager. Set the name to LastName_Timer_Interrupts_Lab1 and set Toolchain/IDE to STM32CubeIDE.
- Click pin PF11 on the pinout view: Set it to
GPIO_Output. Then in System Core → GPIO → PF11, set User Label:LED_RED.
- Click pin PF12: Set to
GPIO_Output.Label:LED_BLUE.
- Click pin PE0: Set to
GPIO_Output.Label:DEBUG_PIN. This is used for timing measurement with a logic analyzer.
- Click PC5 → GPIO_EXTI5. In System Core → GPIO → PC5:set Mode = External Interrupt with Falling Edge, Pull-up/Pull-down = Pull-up.
- Expand Timers → TIM2: In the Pinout & Configuration tab left panel.
- Set Clock Source to Internal Clock
- Under Parameter Settings tab, enter:
(Formula: 84, 000, 000/(8400×10000)=1Hz)
- Enable TIM2 global interrupt: Click NVIC Settings tab → check TIM2 global interrupt → Preemption Priority to 0.
- Click TIM3 in the left panel: Set Clock Source to Internal Clock.
- Set these values:
(Formula: 84, 000, 000/(8400×5000)=2Hz)
- Enable TIM3 global interrupt: NVIC Settings → check "TIM3 global interrupt" → Preemption Priority to 1
- Go to System Core → NVIC
- Find EXTI line[9:5] interrupts: Enable it. Set Preemption Priority to 2.
(Priority order: TIM2 = 0 (highest), TIM3 = 1, EXTI = 2 (lowest). Lower number = higher priority).
6. Generate code- Go to Project Manager tab: Verify project name and STM32CubeIDE is selected as Toolchain/IDE
- Click Generate Code (top right): CubeMX will create all peripheral init files. Click Open Project when prompted to launch it directly in CubeIDE.
Part 4: Code Architecture in STM32CubeIDE
1. Global Volatile Communication Flags: Locate /* USER CODE BEGIN PV */ and declare the communication variables so that variables remain protected against aggressive compiler optimizations:
/* USER CODE BEGIN PV */
volatile uint8_t task_adc_ready = 0;
volatile uint8_t task_display_ready = 0;
volatile uint32_t tick_count = 0;
/* USER CODE END PV */ 2. Background Timer ISR Management: Locate /* USER CODE BEGIN 0 */ directly above the main() function entry point and implement your global timer callback logic:
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// Background Task 1: TIM2 - High priority, 1 Hz
if (htim->Instance == TIM2)
{
HAL_GPIO_WritePin(DEBUG_PIN_GPIO_Port, DEBUG_PIN_Pin, GPIO_PIN_SET);
HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin);
task_adc_ready = 1; // Signal foreground to process ADC
tick_count++; // Increment system tick
HAL_GPIO_WritePin(DEBUG_PIN_GPIO_Port, DEBUG_PIN_Pin, GPIO_PIN_RESET);
}
// Background Task 2: TIM3 - Lower priority, 2 Hz
if (htim->Instance == TIM3)
{
HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin);
task_display_ready = 1; // Signal foreground to update display
}
}
/* USER CODE END 0 */ 3. Hardware Peripheral Activation: Inside the main(void) function block, scroll to the /* USER CODE BEGIN 2 */ placeholder and start both timers with interrupt capabilities enabled:
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start_IT(&htim3);
/* USER CODE END 2 */ 4. Foreground Cyclic Executive Scheduling Loop: Update the while (1) loop structure inside the /* USER CODE BEGIN WHILE */ block to correctly route flag alerts:
/* USER CODE BEGIN WHILE */
while (1)
{
// Task A: Process ADC data (triggered by TIM2)
if (task_adc_ready)
{
HAL_GPIO_WritePin(DEBUG_PIN_GPIO_Port, DEBUG_PIN_Pin, GPIO_PIN_SET);
task_adc_ready = 0;
HAL_Delay(50); // Simulate 50ms processing time
HAL_GPIO_WritePin(DEBUG_PIN_GPIO_Port, DEBUG_PIN_Pin, GPIO_PIN_RESET);
}
// Task B: Update display (triggered by TIM3)
if (task_display_ready)
{
task_display_ready = 0;
HAL_Delay(20); // Simulate 20ms display update time
}
// Task C: Background monitoring (always runs)
if (tick_count >= 10)
{
tick_count = 0;
// Perform 10-second periodic task
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */ 5. Event-Triggered External Button Handling: Move below the main function close bracket to /* USER CODE BEGIN 4 */ and add the asynchronous external button interrupt routine:
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_0)
{
// Button pressed - toggle both LEDs immediately
HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin);
HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin);
}
}
/* USER CODE END 4 */Part 5: Results and Observations
After flashing the code, the following was observed:
- The Red LED (
LED_RED) updates uniformly at a clear 1 Hz frequency (toggling state exactly once every second). - The Green LED (
LED_BLUE) functions concurrently, pulsing at a fast 2 Hz rhythm (toggling state every 0.5 seconds).
(It flashes green because the microcontroller simply sends electrical power to pin PF12 and it cannot be setup in CubeMX on what color to flash)
- Pressing the physical User Button overloads the existing routine loop instantly, making both LEDs flip their current states immediately. This proves that the external hardware interrupt line successfully preempted the execution of lower priority loops.
Part 6: Analysis
Part 7: Conclusion
The current laboratory exercise was quite successful in showing how one could obtain concurrency with the use of a single core microcontroller STM32 by implementing a foreground/background cyclic executive. The hardware timers were used to perform periodical execution of tasks in the background while the physical button press was handled through an interrupt mechanism.
The system was able to maintain exact 1 Hz and 2 Hz execution schedule without spending unnecessary time on CPU polls. Although there was a bit of latency within the bare-metal loop due to blocking calls of foreground functions, the use of hardware interrupts clearly showed the responsiveness of the system.


Comments