Assignment #1:

📚 Assignment 1 CMPE2250: – Non-Blocking Timing with SysTick

Objective: Transition from crude busy-wait delays to deterministic, non-blocking timebases using the ARM Cortex-M SysTick peripheral and function-pointer encapsulation in C.

Primer: Function Pointers & “Object-Oriented” C

  • In C, variables hold data stored at memory addresses. Functions also reside in memory (in Flash/ROM), meaning every function has a specific entry-point memory address. A function pointer is simply a pointer variable that stores the address of an executable function rather than data.

  • This technique forms the foundation of interrupt vector tables, callback systems, dynamic dispatch, and object-oriented encapsulation in embedded C.

Syntax: How to Declare and Use a Function Pointer

  • A function pointer declaration mirrors the prototype of the function it points to, with the function name replaced by (*PointerName).

Syntax Pattern:

\[\text{return\_type}\quad (*\text{PointerName})(\text{parameter\_types});\]
  • The parentheses around *PointerName are mandatory.

  • Without them, the compiler interprets the statement as a standard function returning a pointer (return_type*).

Minimal Example:

// 1. A standard function matching the signature: void fn(void)
void toggle_led(void) {
    GPIOA->ODR ^= (1 << 6);
}

int main(void) {
    // 2. Declare a function pointer variable named 'action'
    void (*action)(void);

    // 3. Assign the address of the function (both forms are valid)
    action = toggle_led;       // Implicit address-of
    // action = &toggle_led;   // Explicit address-of

    // 4. Call the function through the pointer
    action();                  // Direct call
    // (*action)();            // Explicit dereference (equivalent)

    return 0;
}

1️⃣ Hardware Setup: Button & LED Board Interface

  • LED Counter Outputs: Connect PA6 through PA9 to four adjacent LEDs on the board (remember the LEDs are active-low. You must configure them accordingly).
    • PA6: Bit 0 (LSB) / D1
    • PA7: Bit 1 / D2
    • PA8: Bit 2 / D3
    • PA9: Bit 3 (MSB) / D4

Part 1 – Bare-Metal SysTick Binary Counter (Direct Interrupt Driven)

In this stage, you will establish a periodic system heartbeat to drive peripheral logic without stalling the processor in delay loops. Tasks:

  • Clock & GPIO Setup:
    • Enable the clock gate for GPIOA.
    • Configure PA6 through PA9 as standard general-purpose push-pull outputs (medium/low speed is sufficient, no pull-up/pull-down required).
  • SysTick Initialization:
    • Configure the core SysTick timer to trigger an interrupt (SysTick_Handler) every 1.0 ms based on your current core clock frequency ($SystemCoreClock / 1000$).
    • Enable the SysTick counter, interrupt request, and internal processor clock source.
  • Interrupt Service Routine (SysTick_Handler):
    • Maintain an incrementing 32-bit global millisecond counter: volatile uint32_t MasterCounter;
  • Counter execution:
    • Every 250 ms, advance a 4-bit count value ($0 \rightarrow 15 \rightarrow 0$) and write the value directly to PA6–PA9 using the GPIO Output Data Register (ODR) or usting your GPIO library. Ensure other pins on Port A remain untouched.
  • Questions:
    • If the MasterCounter is a 32-bit unsigned integer, how long will it take to overflow? How many days is that?
    • How can you use the MasterCounter to take snapshots of the current time in milliseconds for event logging or profiling?

Checkoff 1:

  • Demonstrate a working 4-bit binary sequence advancing cleanly every 250 ms. Verify with an oscilloscope or logic analyzer that the pin states transition at exactly 4 Hz

2️⃣ The Intuitive Mental Model: The Digital Wall Clock

Think of MasterCounter as a digital wall clock in your lab that ticks once every millisecond.

  • It never stops running, and nobody is allowed to change or reset it.
  • To measure an interval (like a runner doing laps), you don’t reset the clock to zero.
  • Instead, you take a snapshot of the current time on the clock when the event starts (Last = MasterCounter).
  • At any point later, you glance at the clock again (now = MasterCounter) and calculate the difference:
\[\Delta t = \text{now} - \text{Last}\]

If you want an event to happen periodically every 250 ms, you simply check if $\Delta t \ge 250$. Once it does, you do your task and update your baseline: Last = now.

    Timeline (ms):
    ---[Snapshot: Last = 1000]----------------------[Now = 1250]-------->
                            \                    /
                            \--- Δt = 250 ms --/
                                (Trigger Task!)
                                (New Last = 1250)

3️⃣ Part 2: Encapsulated Timing via the Stopwatch Library

  • Directly referencing a raw MasterCounter across multiple application tasks leads to tightly coupled, error-prone code.

  • In this part, you will implement an object-oriented timer abstraction using standard C structs and function pointers based on stopwatch.h.

  • Provided Header Reference (stopwatch.h):

/*
 * stopwatch.h
 *
 *  Created on: Nov 8, 2022
 *      Author: CarlosEstay
 */

#ifndef STOPWATCH_H_
#define STOPWATCH_H_

/****************************************************************************
 System Includes
 ****************************************************************************/
#include 
/****************************************************************************
 Project Includes
 ****************************************************************************/

/****************************************************************************
 Defines
 ****************************************************************************/
extern volatile uint32_t MasterCounter;
/****************************************************************************
 Numerated Types
 ****************************************************************************/

/****************************************************************************
 Structures
 ****************************************************************************/
typedef struct __StopwatchTypedef
{
  uint32_t Last;
  uint32_t Count;
  uint8_t Running;
  void (*Start)(struct __StopwatchTypedef*);
  uint32_t (*Read)(struct __StopwatchTypedef*);
  void (*Reset)(struct __StopwatchTypedef*);
  void (*Stop)(struct __StopwatchTypedef*);
}StopwatchTypedef;

/****************************************************************************
Function Prototypes
 ****************************************************************************/
void SW_Init(StopwatchTypedef*);
void SW_Start(StopwatchTypedef*);
uint32_t SW_Read(StopwatchTypedef*);
void SW_Reset(StopwatchTypedef*);
void SW_Stop(StopwatchTypedef*);

/****************************************************************************
                           End of File
 ****************************************************************************/
#endif /* STOPWATCH_H_ */
</pre>

### Tasks:

1. Implement stopwatch.c:

    - SW_Init(StopwatchTypedef *sw): Clears Last and Count, marks Running = 0, and binds the internal member function pointers (sw->Start = SW_Start, sw->Read = SW_Read, etc.).

    - SW_Start(StopwatchTypedef *sw): Sets Running = 1 and snaps the current baseline: sw->Last = MasterCounter;.SW_Stop(StopwatchTypedef *sw): Updates accumulated time if running, then sets Running = 0.
    
    - SW_Reset(StopwatchTypedef *sw): Clears Count = 0 and updates Last = MasterCounter.
    
    - SW_Read(StopwatchTypedef *sw): Returns elapsed time in milliseconds. If running, it computes:
    $$\Delta t = MasterCounter - sw\text{->}Last$$
    
    and returns $(sw\text{->}Count + \Delta t)$.

2. Refactor the Counter Application:

    - Instantiate an instance of StopwatchTypedef counterWatch;.
    - Initialize and start the stopwatch before the main loop:

    
    SW_Init(&counterWatch);
    counterWatch.Start(&counterWatch);
    
- Rewrite your while(1) superloop so that updating PA6–PA9 depends solely on checking:
    if (counterWatch.Read(&counterWatch) >= 250) {
        counterWatch.Reset(&counterWatch);
        // Advance 4-bit counter on PA6..PA9
    }
    
### Deliverables & Evaluation Criteria - Register Masking: Writing to PA6–PA9 must use non-destructive bitwise operations. (Do not modify other bits in the port) - Library Decoupling: stopwatch.c must depend only on the external continuous timebase MasterCounter. - Push assignment to the repository as instructed.