Assignment #7: Introduction to Timers

šŸ“š Assignment 7 CMPE1250: Introduction to Timers

šŸ“‹ Overview

  • In this Assignment , you will transition from using ā€œMagic Numbersā€ in your main.c to building a reusable Timer Driver.

  • We will focus on the Basic Timers (TIM6/TIM7) of the STM32G0B1.

  • These timers are stripped-down counters designed specifically for timebase generation—they do not have input/output channels, making them the perfect starting point.

  • Objectives:

    • Calculate Prescaler (PSC) and Auto-Reload (ARR) values for specific time intervals.

    • Implement a structured peripheral library based on a provided header file.

    • Distinguish between Blocking (Delay) and Non-Blocking (Polling) timing logic.

1ļøāƒ£ Preparatory Work and calculations

  • Assuming the System Clock (fSCK​) is being tracked in the SystemCoreClock variable, calculate the register values required for the tick time.

Tick Time: \({TickTime} = \frac{PSC + 1}{f_{SCK​}}\)

Target Tick Desired Frequency Suggested PSC
1 ms 1[KHz] ?
1 µs 1[MHz] ?
  • Once the tick time has been defined, the value in the ARR register will determine how many ticks occur before the timer generates an update event.

Update Time Event Time: \({TimeEvent} = \frac{(PSC + 1)\cdot(ARR + 1)}{f_{SCK​}}={TickTime}\cdot(ARR + 1)\)

  • The event generated could be blocking or unblocking depending on how the logic is required to be implemented in your program.

2ļøāƒ£ Timer Basic Library Implementation

  • Use the header provided timer.h header file

  • Implement the source file timer.c

//******************************************************************************
// Timer / PWM Library Implementation
//
// COURSE:  CMPE1250
// NAME:    ________________
// DATE:    ________________
//
// FILE:    timer.c
// NOTES:   This library assumes the peripheral clocks have been enabled 
//          in RCC prior to calling these functions (e.g., in HAL_Init()).
//******************************************************************************

#include "timer.h"

/**
 * @brief  Configures the timer timebase (PSC and ARR).
 * @param  timer: Pointer to the timer peripheral (e.g., TIM6, TIM7)
 * @param  psc: Prescaler value
 * @param  period: Auto-reload value (ARR)
 */
void Timer_Init(TIM_TypeDef * timer, uint16_t psc, uint16_t period)
{
    // TODO: Set the Prescaler (PSC) register
    
    // TODO: Set the Auto-reload (ARR) register
    
    // TODO: Trigger an Update Event (UG bit) to load the PSC and ARR 
    // values into the shadow registers immediately.
    
    // TODO: The UG bit sets the Update Interrupt Flag (UIF) in the SR.
    // Clear the UIF flag now so the timer doesn't start with a pending event.
}

/**
 * @brief  Enables or Disables the timer counter.
 * @param  en: 1 to start, 0 to stop.
 */
void Timer_SetEnable(TIM_TypeDef * timer, uint16_t en)
{
    // TODO: Modify the CEN bit in the Control Register 1 (CR1)
    // HINT: Use bitwise OR to set, and bitwise AND with NOT to clear.
}

/**
 * @brief  Configures the timer for a 1ms tick rate.
 * @note   This should work for any System Clock Speed
 */
void Timer_SetmsTick(TIM_TypeDef * pTimer)
{
    // TODO: Calculate the PSC needed for 1ms.
    
}

/**
 * @brief  Configures the timer for a 1us tick rate.
 */
void Timer_SetusTick(TIM_TypeDef * pTimer)
{
    // TODO: Calculate the PSC needed for 1us.   
}

/**
 * @brief  Checks the Update Interrupt Flag (UIF).
 * @return 1 if a rollover occurred (flag set), 0 otherwise.
 * @note   This function MUST clear the flag if it is found to be set.
 */
uint8_t Timer_PollUIF(TIM_TypeDef * pTimer)
{
    // TODO: Check if the UIF bit in the Status Register (SR) is 1.
    
    // IF SET:
    // 1. Clear the UIF bit in the SR.
    // 2. Return 1.
    
    // ELSE:
    // Return 0.
    
    return 0; // Placeholder
}

/**
 * @brief  Creates a blocking delay.
 * @param  ticks: Number of ms/us to wait (depending on timer config).
 */
void Timer_DelayTicks(TIM_TypeDef * pTimer, uint16_t ticks)
{
    // 1. TODO: Modify the Auto-Reload Register (ARR) to match the requested ticks.
    // HINT: Remember the hardware counts from 0, so account for the off-by-one!
    
    // 2. TODO: Force an Update Event (UG bit in EGR) to reset the counter 
    // and immediately load the new ARR value into the shadow register.
    
    // 3. TODO: Forcing the update just set the UIF flag in the Status Register (SR).
    // Clear it now so we can accurately detect when the actual delay finishes.
    
    // 4. TODO: Wait in a blocking while-loop until the hardware sets the 
    // UIF flag in the Status Register (SR), indicating the time has elapsed.
    
    // 5. TODO: Clear the UIF flag again before exiting the function if you like to have it clean 
    // for the next time we use the timer.
}

3ļøāƒ£ Verification: : The Application Loop (Software PWM)

In this final section, you will combine both blocking and non-blocking timer strategies to generate a custom pulse on an LED (PA5). We are going to create a signal that fires every 125 ms, and stays high for 400 µs.

Step 1: System Clock Initialization

  • Use your Clock_InitPll(PLL_40MHZ) function to boost the system clock to 40 MHz.

  • Important: Ensure your calculations inside Timer_SetmsTick and Timer_SetusTick account for this 40 MHz bus speed!

Step 2: Timer Configuration

  • We need two timers with two distinct jobs:

  • TIM6 (The Pacemaker): Call Timer_SetmsTick(TIM6). By default, this configures a 1 ms tick. To make it poll every 125 ms, manually override the Auto-Reload Register right after initialization.

  • TIM7 (The Delay): Call Timer_SetusTick(TIM7) to establish a 1 µs timebase. We will leave its ARR alone for now, as Timer_DelayTicks() will dynamically modify it.

  • Start both timers using your Timer_SetEnable function.

Step 3: The Super Loop

Inside your while(1) loop, implement the following logic:

  1. Poll TIM6: Use an if statement to check Timer_PollUIF(TIM6). This is non-blocking, meaning the CPU is free to do other things while waiting for the 125 ms to pass.

  2. Generate the Pulse: Inside that if block:

    • Turn ON the LED on PA5.

    • Call Timer_DelayTicks(TIM7, 400) to block the CPU for exactly 400 µs.

    • Turn OFF the LED on PA5.

Result: You have just created a software-driven pulse!

4ļøāƒ£ The Blocking ā€œHiccupā€

  • If you want to visually see the CPU get blocked, add code to toggle PA6, in addition to the User LED, directly inside your while(1) loop, completely outside the TIM6 if statement.

  • Hook up Channel 2 of your oscilloscope to PA6. You will see a high-frequency toggle that completely freezes for exactly 400 µs every time the PA5 pulse fires!. Exaplain why this happens