📚 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
*PointerNameare 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
PA6throughPA9to 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;
- Maintain an incrementing 32-bit global millisecond counter:
- 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:
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
MasterCounteracross 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.