4  3-bit LED Counter Using HAL Delay, Timer Interrupts, and Button Interrupt

4.1 Objective:

By the end of this exercise, students will:

  • Implement a binary counter using three LEDs (LD1, LD2, LD3) on the STM Nucleo-144 F767ZI board.
  • Explore three methods of controlling the LEDs: using HAL delay and for loops, timer interrupts, and push button interrupts.
  • Understand the difference between using blocking delays, interrupt-driven operations, and external interrupts in embedded systems.

4.2 Materials:

  • STM Nucleo-144 F767ZI development board.
  • Onboard user LEDs:
    • LD1 (Green): PB0 (or PA5).
    • LD2 (Blue): PB7.
    • LD3 (Red): PB14.
  • Onboard user push button (USER_BUTTON): Connected to PC13.
  • CubeIDE installed on the PC.

4.3 CubeMX Configuration:

1. GPIO Configuration for LEDs:

  • LD1 (Green): Set PB0 (or PA5 depending on SB settings) as a GPIO Output.
  • LD2 (Blue): Set PB7 as a GPIO Output.
  • LD3 (Red): Set PB14 as a GPIO Output.
Note

If you are using the default peripheral configuration it should be already set this way.

2. TIM7 Configuration (For Task 2)

  • Enable TIM7 (or any other available timer).
  • Configure the timer with a prescaler and period to generate an interrupt every 1 second.
  • In the Parameter Settings, set the following configuration.
    • Prescaler - 48000-1
    • Counter Mode - Up
    • Counter Period - 2000-1
    • \[\begin{align*}\text{Interrupt Interval} &= \frac{(\text{Counter Period}+1)\times(\text{Prescaler}+1)}{\text{Clock Source Frequency}}\\ &=\frac{2000 \times 48000}{96\times10^6}=1 \text{s}\end{align*}\]
  • In the NVIC Settings, enable the TIM7 global interrupt.
Note

We are selecting TIM7 for timer as it is one of the basic timers. It uses the APB1 timer clock source as the clock source (Refer this block diagram and this section).

The frequency of the APB1 timer clock can be verified using the Clock Configuration tab.

3. Button Configuration (For Task 3):

  • Set PC13 (USER_BUTTON) as GPIO_EXTI13 in the pinout view and GPIO mode as “External Interrupt Mode with Falling edge trigger detection” and GPIO Pull-up/Pull-down to “No pull-up and no pull-down” (If default configurations of peripherals are used ith will be already enabled).
  • Enable the EXTI Line [15:10] interrupt for the button in the NVIC Settings.

4. Generate Code:

  • Click Project > Generate Code after setting up GPIO, Timer, and Button.

4.4 Explanation of Key Functions:

  1. HAL_Delay

    • Generates a blocking delay, pausing program execution for the specified number of milliseconds.

    • Syntax:

      HAL_Delay(milliseconds);
  2. HAL_GPIO_WritePin

    • Sets the state of a GPIO pin to HIGH or LOW, used to control the LEDs.

    • Syntax:

      HAL_GPIO_WritePin(GPIOx, GPIO_Pin, PinState);
    • Example:

      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);  // Set PB0 (LD1) to HIGH
      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); // Set PB0 (LD1) to LOW
  3. HAL_TIM_Base_Start_IT

    • Starts a timer in interrupt mode.

    • Syntax:

      HAL_TIM_Base_Start_IT(&htim7);  // Start TIM7 in interrupt mode
  4. HAL_TIM_PeriodElapsedCallback

    • The interrupt callback function triggered when the timer reaches the specified period.

    • Syntax:

      void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
  5. HAL_GPIO_EXTI_Callback

    • The interrupt callback function triggered when the external interrupt occurs.

      void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);

4.5 Tasks and Sample IO Behavior:

Task 1: 3-bit LED Counter Using HAL Delay and For Loop

  • Objective: Create a 3-bit binary counter using the LEDs, with a delay between each count, controlled by HAL_Delay() in the main loop.

  • Description:

    • Use a for loop in the main() function to count from 0 to 7 (binary 000 to 111).
    • Control the LEDs based on the binary value of the counter.
    • Use HAL_Delay() to wait for 1 second between each count.

Code Example

int main(void) {
  /* USER CODE BEGIN WHILE */
    while (1) {
      // 3-bit binary counter loop (0 to 7)
      for (uint8_t i = 0; i < 8; i++) {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, (i & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD1 (LSB)
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, (i & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD2
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (i & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LD3 (MSB)
        HAL_Delay(1000);  // 1-second delay
      }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
}

USER CODE BEGIN WHILE

// 3-bit binary counter loop (0 to 7)
for (uint8_t i = 0; i < 8; i++) {
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, (i & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD1 (LSB)
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, (i & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD2
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (i & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LD3 (MSB)
  HAL_Delay(1000);  // 1-second delay
}

Sample IO:

  • Output: The LEDs will count in binary, with a 1-second delay between each count.
    • 000: All LEDs off.
    • 001: LD1 on.
    • 010: LD2 on.
    • 011: LD1 and LD2 on.
    • 100: LD3 on.
    • 101: LD1 and LD3 on.
    • 110: LD2 and LD3 on.
    • 111: All LEDs on.

Task 2: 3-bit LED Counter Using Timer Interrupt

  • Objective: Create a 3-bit binary counter using the LEDs, with the counter updated by a timer interrupt instead of using HAL_Delay().

  • Description:

    • Use a timer to generate an interrupt every 1 second.
    • Inside the interrupt handler, increment the counter and update the LEDs to reflect the binary count.

Code Example

/* USER CODE BEGIN PV */
volatile uint8_t counter = 0;  // 3-bit counter
/* USER CODE END PV */

/* USER CODE BEGIN 0 */
void increment_counter(void) {
    counter = (counter + 1) % 8;  // Increment counter and wrap around using modulo
    // Set LED states based on counter value
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, (counter & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD1 (LSB)
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, (counter & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD2
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (counter & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LD3 (MSB)
}
/* USER CODE END 0 */

int main(void) {

  /* USER CODE BEGIN 2 */
  // Start timer in interrupt mode
  HAL_TIM_Base_Start_IT(&htim7);
  /* USER CODE END 2 */

}

/* USER CODE BEGIN 4 */
// Timer interrupt callback
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM7) {
        increment_counter();  // Update LED states based on counter value
    }
}
/* USER CODE END 4 */

USER CODE BEGIN PV

volatile uint8_t counter = 0;  // 3-bit counter

USER CODE BEGIN 0

void increment_counter(void) {
    counter = (counter + 1) % 8;  // Increment counter and wrap around using modulo
    // Set LED states based on counter value
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, (counter & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD1 (LSB)
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, (counter & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD2
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (counter & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LD3 (MSB)
}

USER CODE BEGIN 2

// Start timer in interrupt mode
HAL_TIM_Base_Start_IT(&htim7);

USER CODE BEGIN 4

// Timer interrupt callback
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM7) {
        increment_counter();  // Update LED states based on counter value
    }
}

Sample IO:

  • Output: The LEDs will count in binary, with a 1-second delay between each count, controlled by the timer interrupt.
    • 000: All LEDs off.
    • 001: LD1 on.
    • 010: LD2 on.
    • 011: LD1 and LD2 on.
    • 100: LD3 on.
    • 101: LD1 and LD3 on.
    • 110: LD2 and LD3 on.
    • 111: All LEDs on.

Task 3: 3-bit LED Counter with Push Button Interrupt and Debounce

  • Objective: Create a 3-bit binary counter using the LEDs, where the counter increments each time the push button is pressed, with debounce handling to ensure reliable button presses.

  • Description:

    • Configure the push button (USER) connected to PC13 to generate an interrupt on a button press.
    • Implement a debounce mechanism to filter out false triggers due to bouncing.
    • In the interrupt handler, increment the counter and update the LEDs to reflect the new binary count.

Code Example

/* USER CODE BEGIN PD */
#define DEBOUNCE_DELAY_MS 50  // Debounce delay in milliseconds
/* USER CODE END PD */

/* USER CODE BEGIN PV */
volatile uint8_t counter = 0;  // 3-bit counter
volatile uint32_t last_interrupt_time = 0;  // Last interrupt time in milliseconds
/* USER CODE END PV */

/* USER CODE BEGIN 0 */
void increment_counter(void) {
    counter = (counter + 1) % 8;  // Increment counter and wrap around using modulo
    // Set LED states based on counter value
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, (counter & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD1 (LSB)
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, (counter & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD2
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (counter & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LD3 (MSB)
}
/* USER CODE END 0 */

int main(void) {
    // Infinite loop
    while (1) {
        // No additional logic needed in the main loop, everything is handled in interrupts
    }
}

/* USER CODE BEGIN 4 */
// External interrupt handler
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == GPIO_PIN_13  ) {
          uint32_t current_time = HAL_GetTick();
          if ((current_time - last_interrupt_time) > DEBOUNCE_DELAY_MS) {
              last_interrupt_time = current_time;  // Update last interrupt time
              increment_counter();  // Update LED states based on counter value
          }
    }
}
/* USER CODE END 4 */

USER CODE BEGIN PD

#define DEBOUNCE_DELAY_MS 50  // Debounce delay in milliseconds

USER CODE BEGIN PV

volatile uint8_t counter = 0;  // 3-bit counter
volatile uint32_t last_interrupt_time = 0;  // Last interrupt time in milliseconds

USER CODE BEGIN 0

void increment_counter(void) {
    counter = (counter + 1) % 8;  // Increment counter and wrap around using modulo
    // Set LED states based on counter value
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, (counter & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD1 (LSB)
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, (counter & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);  // LD2
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (counter & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LD3 (MSB)
}

USER CODE BEGIN 4

// External interrupt handler
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == GPIO_PIN_13  ) {
          uint32_t current_time = HAL_GetTick();
          if ((current_time - last_interrupt_time) > DEBOUNCE_DELAY_MS) {
              last_interrupt_time = current_time;  // Update last interrupt time
              increment_counter();  // Update LED states based on counter value
          }
    }
}
Sample IO
  • Output: Each press of the push button increments the counter and updates the LEDs to reflect the binary count.
    • 000: All LEDs off.
    • 001: LD1 on.
    • 010: LD2 on.
    • 011: LD1 and LD2 on.
    • 100: LD3 on.
    • 101: LD1 and LD3 on.
    • 110: LD2 and LD3 on.
    • 111: All LEDs on.
Note

The debounce delay is set to 50 milliseconds in this example, but you can adjust this value depending on your specific hardware and requirements.

4.6 Conclusion:

In this exercise, you implemented a 3-bit binary counter using three different methods of LED control on the STM Nucleo-144 F767ZI board:

  • HAL Delay and For Loop: Demonstrated a straightforward approach for controlling LEDs with blocking delays, suitable for understanding basic timing but less effective for handling concurrent tasks.

  • Timer Interrupt: Showcased how to use a timer to handle periodic tasks efficiently, enabling the microcontroller to perform other operations while managing timing in the background. This method is particularly useful for tasks requiring precise timing without blocking the main execution.

  • Push Button Interrupt: Introduced the concept of external interrupts triggered by user input, allowing real-time interaction with the system. This method illustrated how to handle interrupts for external events, such as button presses, and provided a way to interact with the system dynamically.

These methods provide a comprehensive understanding of managing timing and interrupts in embedded systems, helping you to appreciate the trade-offs and applications of different techniques in real-time system design.