Lab 1: FreeRTOS Stopwatch
Prerequisites
Section titled “Prerequisites”Overview
Section titled “Overview”In Lab 0, you built a stopwatch using a bare-metal while(1) loop. It worked — but what happens when you need to add more features? What if updateDisplay() takes longer than expected? What if you need precise 10 ms timing that never drifts?
In this lab, you will solve these problems by integrating FreeRTOS into your project from scratch. You will not receive a starter template. Instead, you will:
- Manually set up the FreeRTOS kernel in Code Composer Studio
- Understand what each kernel file does and why it’s needed
- Convert your bare-metal stopwatch into a multitasking application
- Design inter-task communication using queues
This is the most important lab of the course — every future lab builds on the FreeRTOS foundation you create here.
Learning Objectives
Section titled “Learning Objectives”After completing this lab, you will be able to:
- Explain the role of each FreeRTOS kernel source file and why it’s needed
- Create a CCS project with FreeRTOS integrated manually (not from a template)
- Identify why a bare-metal super-loop breaks down as complexity grows
- Create tasks with appropriate priorities, stack sizes, and periodic scheduling
- Use vTaskDelay and vTaskDelayUntil for non-blocking timing
- Implement queue-based inter-task communication
- Convert a single-threaded application into a task-based architecture
Required Materials
Section titled “Required Materials”- TI EK-TM4C1294XL LaunchPad
- BOOSTXL-EDUMKII BoosterPack
- Micro USB cable for programming and power
- FreeRTOS kernel source code (downloaded in Part 1)
Grading Rubric
Section titled “Grading Rubric”| Step | Task | Description | Points |
|---|---|---|---|
| 1 | FreeRTOS Integration | Manually set up FreeRTOS in CCS; explain kernel files | 10 |
| 2 | Scheduler Verification | Two-LED blink test proving multitasking works | 10 |
| 3 | Timekeeping Task | Stable ≤10 ms base period using vTaskDelayUntil | 15 |
| 4 | Button Task | Debounced S1 (Start/Pause) and S2 (Reset) via task | 15 |
| 5 | Display Task | Non-blocking UI rendering: HH:MM:SS:MS and state text | 15 |
| 6 | Buzzer Task with Queue | Design and implement a buzzer task that receives commands through a queue | 10 |
| Lab Report | Written report with analysis questions (see below) | 25 | |
| Total | 100 |
Part 1 — Understanding and Setting Up FreeRTOS
Section titled “Part 1 — Understanding and Setting Up FreeRTOS”1.1 Read the FreeRTOS Fundamentals Guide
Section titled “1.1 Read the FreeRTOS Fundamentals Guide”Before writing any code, read the FreeRTOS Fundamentals guide completely. This is not optional — you need to understand why an RTOS exists before you can use one effectively.
1.2 Download the FreeRTOS Kernel
Section titled “1.2 Download the FreeRTOS Kernel”Download the FreeRTOS kernel source code. Do not use a pre-built library — you will add the source files manually so you understand what each one does.
1.3 Understand the Kernel File Structure
Section titled “1.3 Understand the Kernel File Structure”After extracting the ZIP, examine the contents. You should see a structure like this:
- FreeRTOS.h
- FreeRTOSConfig.h
- startup_ccs.c
DirectoryFreeRTOS/
Directoryinclude/
- FreeRTOS.h
- task.h
- queue.h
- semphr.h
- …
Directoryportable/
DirectoryCCS/
DirectoryARM_CM4F/
- port.c
- portasm.asm
DirectoryMemMang/
- heap_4.c
- tasks.c
- queue.c
- list.c
- timers.c
- …
1.4 Create Your CCS Project and Integrate FreeRTOS
Section titled “1.4 Create Your CCS Project and Integrate FreeRTOS”Follow the FreeRTOS Project Setup guide step by step. You will create a new empty CCS project and manually add the kernel files.
Part 2 — Verify the Scheduler (Two-LED Test)
Section titled “Part 2 — Verify the Scheduler (Two-LED Test)”Before building the stopwatch, you need to confirm FreeRTOS is actually running. Create a simple test with two tasks that blink two different LEDs at different rates.
2.1 What to Implement
Section titled “2.1 What to Implement”If you are not familiar with how to use the on-board LEDs (GPIO configuration, GPIOPinWrite, etc.), review the Blink Example first:
Create a main.cpp file with:
- Standard system initialization (disable interrupts, enable FPU, configure 120 MHz clock)
- Enable GPIO Port N for the on-board LEDs (D1 = PN0, D2 = PN1)
- Two FreeRTOS tasks: one toggles D1 every 2 seconds, the other toggles D2 every 200 ms
- Start the scheduler
2.2 Task Structure Pattern
Section titled “2.2 Task Structure Pattern”Every FreeRTOS task follows the same pattern. Study this structure — you will use it for every task in this lab and all future labs:
void MyTask(void *pvParameters) { // === One-time setup (runs once when task starts) ===
// === Periodic loop === for (;;) { // Do work here
vTaskDelay(pdMS_TO_TICKS(period_ms)); }}Use xTaskCreate() to register each task with the scheduler. You need to specify: the task function, a name string, stack size (in words), parameters, priority, and an optional handle.
2.3 Verification
Section titled “2.3 Verification”Flash and run. You should see:
- D1 (PN0): Toggling every 2 seconds (slow blink)
- D2 (PN1): Toggling every 200 ms (fast blink)
If both LEDs blink at their respective rates, FreeRTOS is running and scheduling multiple tasks. This is a fundamental difference from bare-metal — in your Lab 0 approach, you couldn’t have two independent timing loops without managing them manually.
Demonstrate this working to a TA before moving on.
Part 3 — Build the Stopwatch Task by Task
Section titled “Part 3 — Build the Stopwatch Task by Task”Now you will convert your Lab 0 stopwatch into a FreeRTOS application. Instead of one while(1) loop doing everything, you will split the work into four independent tasks.
Architecture Overview
Section titled “Architecture Overview”Think about what your Lab 0 stopwatch did in its main loop:
- Track elapsed time (every ~10 ms)
- Scan buttons and handle debouncing (every ~20 ms)
- Update the display (every ~50 ms)
In FreeRTOS, each of these becomes a separate task with its own priority and timing. You will also add a fourth task for buzzer feedback.
┌─────────────────────────────────────────────────────────┐│ FreeRTOS Scheduler │├──────────┬──────────┬───────────┬───────────────────────┤│ Time │ Display │ Buttons │ Buzzer ││ Task │ Task │ Task │ Task ││ (high) │ (medium) │ (low) │ (low) ││ │ │ │ ││ Updates │ Reads │ Scans HW │ Receives queue ││ counters │ globals │ buttons, │ commands, ││ every │ and │ updates │ drives PWM ││ ≤10ms │ redraws │ state │ for duration ││ │ LCD │ │ │└──────────┴──────────┴───────────┴───────────────────────┘ Shared state: volatile globals Communication: FreeRTOS queue (buttons → buzzer)3.1 Shared State and Priorities
Section titled “3.1 Shared State and Priorities”Before writing tasks, plan your shared data. Globals that are read and written by multiple tasks (or tasks and ISRs) must be declared volatile so the compiler doesn’t optimize away accesses.
/* Shared stopwatch state */volatile bool gRunning = false;volatile uint16_t g_ms = 0;volatile uint8_t g_sec = 0;volatile uint8_t g_min = 0;volatile uint8_t g_hr = 0;Priority Assignment: Think about which task is most timing-critical and assign priorities accordingly. The timekeeping task must never miss a tick — it should have the highest priority. The display task should be responsive but can tolerate small delays. Button scanning and buzzer feedback are less time-critical.
You decide the exact priority values. Use tskIDLE_PRIORITY + N where higher N means higher priority.
Report Question 3: Explain your priority assignment. What would happen if the display task had the highest priority and took 40 ms to render a frame? How would this affect timekeeping accuracy?
3.2 Timekeeping Task (Step 3)
Section titled “3.2 Timekeeping Task (Step 3)”This task replaces the if (stopwatchTick >= 10) check from your Lab 0 main loop. It should:
- Run periodically at 10 ms or less
- Increment milliseconds, rolling over into seconds, minutes, and hours
- Only count when
gRunningistrue
3.3 Button Task (Step 4)
Section titled “3.3 Button Task (Step 4)”This task handles scanning physical buttons S1 and S2, debouncing them, and updating the stopwatch state.
Requirements:
- Scan buttons periodically (every ~20 ms is typical)
- Debounce both S1 and S2
- S1 toggles between Running and Stopped
- S2 resets all time counters to zero
- When a button event occurs, also notify the buzzer task (you’ll implement this in Step 6)
3.4 Display Task (Step 5)
Section titled “3.4 Display Task (Step 5)”This task reads the shared stopwatch state and renders it on the LCD.
Requirements:
- Update the display periodically (target ~20 FPS, i.e., every ~50 ms)
- Show time in
HH:MM:SS:MSformat - Show state text: Running or Stopped
- Must be non-blocking — use RTOS delays, never busy-wait
3.5 Buzzer Task with Queue (Step 6)
Section titled “3.5 Buzzer Task with Queue (Step 6)”This is the most interesting task because it introduces inter-task communication. Instead of calling a buzzer function directly from the button task, you will send a command through a FreeRTOS queue, and a separate buzzer task will consume and execute it.
If you are not familiar with how to configure PWM for the buzzer hardware, review the Buzzer Example first. It shows how to initialize PWM0 on PF1 and generate tones at a given frequency:
What you need to design:
-
A command structure — What information does the buzzer task need to produce a beep? At minimum: frequency and duration.
-
A queue — Created before the scheduler starts. How many items should it hold? What is the item size?
-
The buzzer task — An infinite loop that:
- Blocks on
xQueueReceive()waiting for a command - When a command arrives, starts PWM at the requested frequency
- Delays for the requested duration using
vTaskDelay()(notSysCtlDelay) - Stops PWM
- Loops back to wait for the next command
- Blocks on
-
A send function — Called from the button task (or anywhere else) to post a command to the queue without blocking.
Report Question 5: Draw a diagram showing how data flows from a button press to the buzzer producing sound. Label each FreeRTOS primitive used (task, queue, etc.). What would happen if the queue is full when a new command is sent with timeout 0?
Part 4 — Integration and Testing
Section titled “Part 4 — Integration and Testing”Once all four tasks are implemented:
- Build and flash your project. Fix any compilation errors.
- Test the scheduler — Do both LEDs from Part 2 still work? (You can remove the LED tasks or keep them for debugging.)
- Test timekeeping — Let the stopwatch run for several minutes. Does it drift? Compare against a phone timer.
- Test buttons — Press S1 rapidly. Does it always toggle correctly? Press S2 during running — does it reset cleanly?
- Test the buzzer — Press S1 and S2. Do you hear distinct beeps? Press both quickly — are both beeps produced?
- Test responsiveness — While the stopwatch is running, is the display smooth? Do buttons respond immediately?
Expected Results
Section titled “Expected Results”By the end of this lab:
- The project builds and launches the FreeRTOS scheduler without faults
- Stopwatch displays time in HH:MM:SS:MS with smooth updates
- S1 toggles Start/Pause; S2 resets; UI reflects state text
- Button presses produce short audible feedback via the buzzer queue
- No blocking delays anywhere — all timing uses RTOS primitives
- Code is modular and organized: separate functions/files for each task
Lab Report (25 points)
Section titled “Lab Report (25 points)”Your lab report must be written in clear, professional English. It should include:
Required Sections
Section titled “Required Sections”-
Introduction (2 pts) — Briefly describe the purpose of the lab and what you accomplished.
-
System Architecture (5 pts) — Include a diagram showing all tasks, their priorities, communication mechanisms (queues, shared variables), and timing relationships. Explain your design decisions.
-
Analysis Questions (13 pts) — Answer the following questions thoroughly:
-
Q1 (3 pts): Explain in your own words what
port.candportasm.asmdo. Why does FreeRTOS need processor-specific code? What would need to change if you ported your project to a different processor? -
Q2 (2 pts): What is
configTICK_RATE_HZset to in your project? If you callvTaskDelay(pdMS_TO_TICKS(10)), how many ticks is that? What happens ifconfigTICK_RATE_HZis set to 10 Hz — can you still achieve 10 ms timing? -
Q3 (3 pts): Explain your priority assignment for all four tasks. What would happen if the display task had the highest priority and took 40 ms per frame? Describe the specific impact on timekeeping.
-
Q4 (2 pts): Which delay function (
vTaskDelayvsvTaskDelayUntil) did you use for the timekeeping task? Explain the behavioral difference and justify your choice. -
Q5 (3 pts): Draw a data-flow diagram showing how a button press results in a buzzer beep. Label every FreeRTOS primitive. What happens if the queue is full when a new command is sent with timeout 0?
-
-
Conclusion (2 pts) — What was the most challenging part of this lab? Compare the bare-metal approach (Lab 0) vs. the RTOS approach (Lab 1) — what are the trade-offs?
-
Code quality (3 pts) — Is your code well-organized, properly commented where necessary, and modular?
Submitting Source Code
Section titled “Submitting Source Code”Exporting project:
- Make sure your project is named
ece3849_lab1_<username>. - Right-click the project in CCS →
Export...→General→Archive File. - Choose the output path and filename equal to the project name (
ece3849_lab1_<username>.zip). - Upload the
.zipfile and your lab report PDF to Canvas.