EECS 373 Lab 5: Clocks, Timers, and Counters

Copyright 2010-2011, Thomas Schmid, Matt Smith, Ye-Sheng Kuo, Lohit Yerva, and Prabal Dutta

rev 10/8/15


See Posted Schedule
This is a 2 week lab consisting of 2 distinct assignments.

Pre-Lab Assignment

  1. Read through this  C Language tutorial.
  2. Read through the lab 5 in-lab and post-lab.
  3. Read Chapter 11: Timer_A from MSP430x1xx Family User's Guide

In-Lab and Post Lab Assignment Overview

Assignment one (In-Lab) consists of several exercises developing software in C for a custom Verilog timer. There is no In-lab demo for this part, but you must submit answers to questions with supporting data.

Assignment two (Post -Lab) is an implementation of a virtual timer. It can be done independently of the In-Lab. Be sure to start this part as soon as you finish assignment 1. Assignment 2 may take longer than you think.

In-Lab: Verilog Based Timers (week 1)

Edit Libero Tool Profile (Be sure to do this before starting a new Libero Project)

Before starting a new project, you will need to add SoftConsole to the tool profile. Under project, select tool profiles. Select Software IDE. Add a profile by clicking the green plus button. Name it SoftConsole v3.3. Select SoftConsole for the tool integration. Finally browse for the SoftConsole executable in. Actel/Libero_v9.1\SoftConsole\Eclipse\eclipse.exe

It should look like this.

Save this and make sure SoftConsole v3.3 is selected as the software IDE.

Create a New Libero Project

As you did before, create a new Libero Project. In the following example, the project is called lab5.

Configure the Fabric Clock

You will need to provide a fabric clock as you have in the past, but this time we will want to configure the clock to be 40MHz. Open the MSS component in your new design and open the MSS clock configurator. Provide the selections as shown below. .

Configure the MSS Component

Next, configure your MSS as shown below. 

  1. Add  APB3 master interface
  2. Add a master reset to the fabric
  3. Add UART_0 
  4. De-select everything else
Generate the MSS component (MSS tab must be selected) when you are finished and update the the lab_MSS instance.

Create a Verilog Timer with an APB3 Interface

Next we will create a timer with the following Verilog. Notice that  this Verilog consists of a timer function module and a module that provides the APB3 interface. Simply put both modules in the same file and create the component. Create a component for your project and provide it with an APB3 slave bus interface like you did in lab 3. Refer back to lab 3 for details.
// timer.v

module timer(
bus_write_data, //data_in
bus_read_data //data_out

input pclk, nreset, bus_write_en, bus_read_en;
input [7:0] bus_addr;
input [31:0] bus_write_data;
output reg [31:0] bus_read_data;

reg [31:0] overflowReg;
reg [31:0] counterReg;
reg [31:0] controlReg;
reg [31:0] nextCounter;
reg overflowReset; // Resets counterReg when new overflow value is written

wire timerEn; // Timer Enable

//Control Bits
assign timerEn = controlReg[0];

always@(posedge pclk)
overflowReset <= 1'b0;
controlReg <= 32'h00000000;
overflowReg <= 32'h00000000;
else begin
if(bus_write_en) begin : WRITE
2'b00: // Timer Overflow Register
overflowReg <= bus_write_data;
overflowReset <= 1'b1;
2'b01: // Timer Value, Read Only
overflowReset <= 1'b0;
2'b10: // Timer Control
controlReg <= bus_write_data;
overflowReset <= 1'b0;
2'b11: // Spare
overflowReset <= 1'b0;
else if(bus_read_en) begin : READ
2'b00: // Timer Overflow register
bus_read_data <= overflowReg;
2'b01: // Timer Value, Read Only
bus_read_data <= counterReg;
2'b10: // Timer Control
bus_read_data <= controlReg;
2'b11: // Spare
overflowReset <= 1'b0;

assign timerEn = controlReg[0];

nextCounter <= counterReg + 1;

always@(posedge pclk)
counterReg <= 32'h00000000;
else begin
counterReg <= 32'h00000000;
else if(timerEn)
if(counterReg == overflowReg)
counterReg <= 32'h00000000;
counterReg <= nextCounter;
// timerWrapper.v

module timerWrapper(// APB Bus Interface

// Test Interface

// APB Bus Interface
input [31:0] PWDATA;
input [7:0] PADDR;
output [31:0] PRDATA;

// Test Interface
output [4:0] TPS; // Use for your debugging

assign BUS_READ_EN = (!PWRITE && PSEL); //Data is ready during first cycle to make it availble on the bus when PENABLE is asserted

assign PREADY = 1'b1;
assign PSLVERR = 1'b0;

timer timer_0( .pclk(PCLK),


Add APB3 bus core with one slave port. Refer back to lab 3 as neccessary for details. It should look something like this when you are finished. 

You can connect the test points (TPS) to the user IO pins for Debugging.  You do not have to assign the UART pins. They are automatically assigned.

Our timer is very simple and has only 3 registers defined:

Offset Type Reset State Name Description
0x0 Read/Write 0 overflowReg Timer counter gets reset (to 0) when it reaches the overflow value
0x4 Read 0 counterReg Timer Counter Value
0x8 R/W 0 controlReg Timer Control
Bit 0: Enable bit (timerEn)

The control bit 0 enables or disables the incrementing counter. When enabled, the counter will increment up to the value stored in the overflowReg register. Once reached, it resets to 0 and starts counting again. The counterReg register contains the current value of the counter.

Program the FPGA with this design.

Creating a C Project

By specifying SoftConsole as part of the tool flow earlier, Libero will automatically create a Softconsole workspace in the Libero project directory and import drivers for the MSS components. You can open SoftConsole to this workspace by clicking on  Write Application Code in the Libero Tool Flow window. 

SoftConsole should open with the following project directories.

As you can see, a starting point SoftConsole project is automatically generated. The application directory is populated with with a simple while(1) main function and standard includes. The platform directory contains a more comprehensive version of the startup code used in the assembly projects. You do not have specify the linker path or provide the linker.ld file as you did previously.  These are created automatically when the SoftConsole project is created from Libero. Lastly,  the UART drivers are included under drivers for the the MSS UART component.

Enable UART output for printf() in C.

Next, we have to define a C Symbol in the project properties. This symbol tells the compiler to send the output characters of printf() to the UART_0 driver. The driver uses the UART_0 hardware to send the characters to the computer terminal (Hyperterminal or Putty from lab4).

Select the lab5_MSS_MSS_CM3_0_hw_platform, right click on your project and select Properties. Then expand the C/C++ Build entry and select Settings. Under GNU C Compiler select Symbols. On the right hand side, click the paper with the plus sign and add the variable ACTEL_STDIO_THRU_UART.

Lastly, you will not need to include any of the uart.h files. The include paths are all automatically created when you the SoftConsole project is created from Libero. Now you can use standard printf() statements. 

Finally, lets set the -O1 for GCC so that it doesn't generate highly inefficient code and hard to read assembly.

Compile and Debug

Before going any further, make sure your SoftConsole project is working. Add a printf line to your main while loop to print "Hello World" using the following line.

printf("Hello World\n\r");

To build the project, select the applicaiton directory  lab5_MSS_MSS_CM3_0_app, right click and Build Project. Check for errors and correct as necessary.

You may should select clean all to be sure that the UART0 is included as the default output for printf. This will ensure that they entire project is built.

To configure the Debugger, select the application directory  lab5_MSS_MSS_CM3_0_app, right click and select Debug As. The configuration should automatically fill in the fields as follows.

Check and see if you are getting a hello message on one of the terminal programs "Fun Terminal" or "PUTTY". Setting should be

Port COM3
BAUD 57600
Data 8 bits
Parity none
Stop bit 1
Flow Control none

SoftConsole Libero Connection Conflict

Softconsole and Libero share the same serial connection to the kit.  Consequently, you cannot have a debug session of SoftConsole running when you program the FPGA via Libero. SoftConsole can be running, but you need to terminate the debug session before programming the FPGA. The error reporting from the FPGA programmer is not very helpful. It might report a programming failure or simply not complete. Keep this in mind when running SoftConsole and Libero at the same time.

Add Timer Functions

Add new files for main.c, mytimer.c, and mytimer.h.

Following is the content for main.c:

#include <stdio.h>
#include "drivers/mss_uart/mss_uart.h"
#include "mytimer.h"

int main()
/* Setup MYTIMER */
MYTIMER_setOverflowVal((1<<31)); // Set compare to a big number

uint32_t i;
printf("Time: %lu\r\n", MYTIMER_getCounterVal()); // Standard printf() now work
for(i=1e6; i>0; i--); // busy wait
return 0;
Following is the content for mytimer.h:
#ifndef MYTIMER_H_  // Only define once
#define MYTIMER_H_ // Only define once

#include "CMSIS/a2fxxxm3.h"


// The technique of using a structure declaration
// to describe the device register layout and names is
// very common practice. Notice that there aren't actually
// any objects of that type defined, so the declaration
// simply indicates the structure without using up any store.

typedef struct
uint32_t overflow; // Offset 0x0
uint32_t counter; // Offset 0x4
uint32_t control; // Offset 0x8
} mytimer_t;

#define MYTIMER_ENABLE_MASK 0x00000001UL

// Using the mytimer_t structure we can make the
// compiler do the offset mapping for us.
// To access the device registers, an appropriately
// cast constant is used as if it were pointing to
// such a structure, but of course it points to memory addresses instead.
// Look at at mytimer.c
// Look at the the functions's disassembly
// in .lst file under the Debug folder

#define MYTIMER ((mytimer_t *) MYTIMER_BASE)

* Initialize the MYTIMER
void MYTIMER_init();


void MYTIMER_enable();

void MYTIMER_disable();

* Set the limit to which the timer counts.

void MYTIMER_setOverflowVal(uint32_t value);
* Read the counter value of the timer.

uint32_t MYTIMER_getCounterVal();

#endif /* MYTIMER_H_ */
Following is the content for mytimer.c:
#include "mytimer.h"

void MYTIMER_init()
// we don't have to do anything.

void MYTIMER_enable()

void MYTIMER_disable()

void MYTIMER_setOverflowVal(uint32_t value)
// Yes it's inefficient, but it's written this way to
// show you the C to assembly mapping.
uint32_t * timerAddr = (uint32_t*)(MYTIMER);
*timerAddr = value; // overflowReg is at offset 0x0

uint32_t MYTIMER_getCounterVal()

// Yes it's inefficient, but it's written this way to
// show you the C to assembly mapping.
uint32_t * timerAddr = (uint32_t*)(MYTIMER);
return *(timerAddr+1); // counterReg is at offset 0x4

The technique of using a structure declaration to describe the device register layout and names is very common practice. Notice that there aren't actually any objects of that type defined, so the declaration simply indicates the structure without using up any store.

To access the device registers, an appropriately cast constant is used as if it were pointing to such a structure, but of course it points to memory addresses instead. Using a simple structure, we index all the registers available on the timer peripheral. For example, look at the files in drivers/mss_uart. You will see that they follow a similar structure.

Look at the disassembly in <project_name>.lst file in the Debug folder. Find the assembly that was generated for the MYTIMER_enable(), MYTIMER_setOverflowVal(uint32_t value) , and MYTIMER_getCounterVal() functions. Map the generated assembly back to the C code in the functions.

Don't forget to define the ACTEL_STDIO_THRU_UART to allow printf to work. See above. 

Put a breakpoint on the printf function line in main and restart the debug session. Invoke the instruction stepping feature. Resume the debugger. You will notice that the for loop does not exist in the disassembled code. The C optimizer (-O1 flag) notices that the loop variable i is not used after it is incremented, therefore it gets rid of the i and the for loop that goes with it. You can trick to compiler into keeping i by declearing it to be volatile. Add the volatile keyword before uint32_t i;. It tells the compiler to not make assumptions about i, because it can be used outside of the for loop. Try making the variable volatile. Read through How to Use C's volatile Keyword.

Connect the serial terminal application to the serial port of the eval board. You should now see numbers being printed on your terminal.

Several questions follow in the In-Lab. Use this answer sheet or create your own and provide notes or answers for submission in the Post lab.

Q1: You can see that the numbers overflow after a certain amount of time. Measure that time using a stop watch and calculate the clock frequency. Write down your result. Does it agree with the frequency specified in the MSS Configurator Clock Manager block? Explain in two sentences providing your data and caculations.

Timed Interrupts

So far, your timer unit provides you time through a software interface. We will now add some more functionality. One of the basic functions of a timer is a timed interrupt. You program the timer unit with a specific time, and an interrupt will fire upon reaching that time.

In the last lab, you learned how to create interrupts on the FPGA, and how to process them in the interrupt service routine. Let's add two interrupt sources to our timer, both sharing the same interrupt line FABINT. An interrupt register will indicate in software which interrupt actually fired. This is very similar to the last lab where we had two interrupt sources, the two switches, sharing the FABINT interrupt line.

The two interrupt sources should fire when the timer reaches two specific values:

  1. When the timer overflows and goes back to 0, i.e., when it reaches the Overflow value
  2. When the timer reaches a specific Compare value that is less than the overflow value.
The first interrupt is easy to implement. For the second interrupt, you will have to add a new register to your timer that contains the Compare value.

Together with the interrupts, we will also need some more control bits

  1. InterruptEnable: Enables interrupts
  2. CompareEnable: Enable the Compare interrupt
  3. OverflowEnable: Enable the Overflow interrupt

In summary, we will make the following modifications to the code:

  1. Add the FABINT interrupt inside the MSS Configurator
  2. Add an output interrupt port to the timer.v module
  3. Add a pulsed and synchronized to PCLK interrupt to the module.
  4. Add two new registers to the module. Note that we will have to widen the address bus for this. One register will be the interrupt status, the other the Compare register.
  5. Add the control signals to enable/disable the two interrupts
  6. Add the overflow interrupt logic, making sure to check for the interrupt enable signals.
  7. Add the reset interrupt register and maaking sure that it gets reset upon a read from the Cortex-M3.
  8. Add the compare interrupt and update the interrupt status register.
  9. Update the timer unit in the smart designer, re-add the bus definition, and connect the FABINT.

We perform all but the first step(s) above, use the following verilog code: timerInt.v and timerIntWrap.v. The functionality for these modules is complete. You do not have to modify them. Replace your previous timer.v file with this implementation and update your smart designer correspondingly.

Don't forget to add the FAB_INT to your MSS component and regenerate! The MSS should look like this.

Now our modified timer has 5 registers:

Offset Type Reset State Name Description
0x00 Read/Write 0 overflowReg Timer counter gets reset (to 0) when it reaches the overflow value
0x04 Read 0 counterReg Timer Counter Value
0x08 R/W 0 controlReg Timer Control
Bit 0: Timer Enable bit (timerEn)
Bit 1: Interrupt Enable bit (interruptEn)
Bit 2: Compare Enable bit (compareEn)
Bit 3: Overflow Enable bit (overflowEn)
0x0c R/W 0 compareReg Timer Compare Value (only works if less than Overflow val)
0x10 Read 0 interrupt_status Which interrupt fired?

A few interrupt enable examples.
1) To enable a compare that generates an interrupt, set the interrupt enable bit and the compare overflow bit.
2) To enable both they compare and overflow interrupt, set the interrupt enable bit, the compare bit and the overflow bit.
3)  To enable an overflow that generates an interrupt, set the interrupt enable bit and the overflow enable bit.

SoftConsole Code

Having more functionality in your peripheral necessitates to update your timer driver. Add the following additional functions form this header file to your library. Make sure that the different enable and disable functions really toggle the right bits in the configuration register.:

* Enable all interrupts
void MYTIMER_enable_allInterrupts();
* Disable all interrupts

void MYTIMER_disable_allInterrupts();
* Enable compare interrupt

void MYTIMER_enable_compareInt();
* Disable compare interrupt

void MYTIMER_disable_compareInt();
* Set Compare value

void MYTIMER_setCompareVal(uint32_t compare);
* Enable overflow interrupt

void MYTIMER_enable_overflowInt();
* Disable overflow interrupt

void MYTIMER_disable_overflowInt();
* Interrupt status

uint32_t MYTIMER_getInterrupt_status();

Implement the missing MYTIMER functions in your timer.c file.

Once implemented, replace your main function with the following code. Notice that we added the Fabric interrupt handler too:

uint32_t count;
__attribute__ ((interrupt)) void Fabric_IRQHandler( void )
uint32_t time = MYTIMER_getCounterVal();
uint32_t status = MYTIMER_getInterrupt_status();
printf("Interrupt occurred at %lu FABINT \n\r", time);
if(status & 0x01)
printf("Overflow latency %ld\n\r", 0-time);
if(status & 0x02)
printf("Compare latency %ld\n\r", (1<<27) - time);
NVIC_ClearPendingIRQ( Fabric_IRQn );
int main()
/* Setup MYTIMER */
MYTIMER_setCompareVal((1<<27)); // 50% of overflow




count = 1e6;
if(!count){ // Global variable checking
// count decremented inside the interrupt service routine
MYTIMER_disable(); // Disable after 1e6 interrupts
return 0;
Do a clean build and program your eval board with the application and connect the serial terminal. Observe the latency and convert it to seconds, either in your output by modifying the printf functions, or on paper. 

Q2: How does the latency compare to the latency measured with the oscilloscope in Lab 4? Remember that we measured the Fabric Interrupt latency in lab 4 that was initiated with a push button switch. The latency was measured on the scope by measuring the time between the FABINT and a GPIO signal driven high in the interrupt service routine. The timer is measuring latency by starting a  counter when FABINT is asserted and reading the count on entry of the interrupt service routine. The count difference times the clk period (period for 40MHz) is the latency time. Explain in two sentences why these values are different?

Pause the program, turn on Instruction Stepping, and look at the disassembly. Notice that it is looping at a branch to itself loop:

0x200004e4 <main+82>: b.n 0x200004e4 <main+82>

This is basically the while(1) loop in code. This time the C optimizer assumed that count cannot change (it cannot make the connection between count in the interrupt routine and count here.) Therefore, it moves the if(!count) outside of the while(1) and checks count once.  Make count volatile, and notice that it gets rid of the self loop. Use instruction stepping to step through the full loop now. Notice that it loads and checks count every time. Keep this mind when setting the compilier optimizaion using variables.  See How to Use C's volatile Keyword for more information. 

Pulse Width Modulated Signals

We have almost all the pieces together for a Pulse Width Modulated (PWM) signal generator. PWM signals are very common in embedded systems to deliver a variable amount of energy to an end system, such as LEDs, robotic servos, heating plates, etc. There are two parameters to a PWM system: frequency and duty-cycle. The frequency indicates how often we have a positive edge, while the duty-cycle describes the amount of high time vs. low time of the signal, and is usually expressed in percent. For example, a system that has a pulse every second, then stays on high for 100 milliseconds, before going low for 900 milliseconds would have a duty cycle of 10%.

With the timer unit we have, it is really easy to generate a PWM system. We just have to set a signal to high when we get to the Compare value, and set it back to low when we reach Overflow. In that respect, the Overflow value will determine the period, or frequency, of our signal, while the Compare value determines the duty cycle. Software writes the period and duty-cycle values to the Overflow and Compare registers, respectively.

Modify the timer unit and add the PWM output. Add a control register bit that can enable and disable that output. Connect the PWM output to one of the LEDs on your eval board.

Add PWM capabilities to your timer library, and write an application that generates a PWM signal at 100Hz and changes the duty cycle from 0 to 100% over 1 second. Observe the signal on an oscilloscope and the LED. Describe your observation in a few sentences and include a screenshot of the oscilloscope. Include this with the answers to your questions. 

Capturing Time

The compare capabilities of a timer allow timed events to happen. The Capture capabilities allow the inverse, i.e., they measure the time of external events. As you saw in the last lab, the handling of interrupt service routines can have significant latency and jitter. Thus, we can not just read the time in software once we entered an interrupt service routine. The capture unit instead grabs the current time inside the timer unit and stores it in a register. It then signals to the core using the timer interrupt that a capture happened. The software can then, at its own convenience, read the capture register.

Let's add capture capabilities to our timer unit:

Use the following file as a reference to implement the above capabilities: timerCapt.v and timerWrapCapt.v. This code is complete. You do not have to modify it. Make sure you connect one of the switches to the capture input. Synthesize, place and route, and program the eval board with your new code.

We now have to add the new capabilities to our timer library. Implement the following new functions in your mytimer.c file.

* Enable Capture
void MYTIMER_enable_capture();
* Disable Capture
void MYTIMER_disable_capture();

* Read the synchronous capture value

uint32_t MYTIMER_get_sync_capture();
* Read the asynchronous capture value

uint32_t MYTIMER_get_async_capture();

Keep in mind that the count register is reset when the count register equals the overrun register. Initialize the overrun register to a value greater than your expected count range (or just max value 0xFFFFFFFF) to allow the counter operation.

Write a little application that upon pressing the switch outputs the synchronous and asynchronous capture times, including the difference between them.

Q3: What do you observe between the synchronous and asynchronous capture values? Explain in a couple of sentences and provide  output examples.

Post-Lab Assignment: Virtual Timers (week 2)


For the post lab assignment  you will implement a virtual timer. A virtual timer is nothing more than one or more timers implemented in software that are clocked by a hardware timer. This can come in handy if you need several timers, but only have a limited number of hardware timers.

Consider a scenario where you have one hardware timer and you want to implement a timer that toggles a LED after 3 ms and another that toggles a LED after 5 ms. You could setup the hardware timer to count down from 3 ms, toggle the 3 ms LED then set the hardware timer to count down another 2 ms and then toggle the 5 ms LED. Then repeat the process.

While this process will effectively emulate a 3 ms and 5 ms timer to toggle a pair of LEDs, it is not very flexible. Adding a 4 ms timer will require inserting a 1 ms timer after the 3 ms timer and then adding a 1 ms timer to the end and removing the 2 ms timer. We desire a process that will allow us to insert, remove or add timers easily without rewriting code.

One way to generalize this process is with a linked list. Each record in the list can contain essential information about the timer. The list can be ordered or altered using  basic linked list manipulation  techniques.

Consider the previous example. Assume each record in the linked list consist of the timer period and a pointer to a function that will blink the LED. If the list is ordered from the shortest period to the longest, you can start by initiating the hardware timer with the the period from the first element in the list or in this example 3 ms. This will be considered the current timer. When the time elapses and the hardware timer generates an interrupt, you can call the function pointed to by function pointer in the current timer. Next you need to determine the next setting for the hardware timer determined by the next timers period minus the time that has elapsed which in this example is 2 ms. The current timer is now the 2nd or 5 ms timer. When the 2nd interrupt occurs you can call the LED toggle function. Since there are no more timers (end of the list), you can start the process over again assuming the timers are continuous or repetitive.

While this may seem like more trouble then the first implementation, it is much easier to add another timer. If you want to add a 4 ms timer, you simply insert the 4 ms in the list taking care to order the list from shortest period to longest period and the process takes care of everything else.


You can use the Verilog timer from the first part of the lab or more simply use one of the countdown timers in the MSS. Here is a link to a Libero project archive that implements a 1s MSS (microprocessor subsystem) timer. To blink control the LEDs you can use memory mapped IO from lab 3 or use the GPIO of the MSS. The example project blinks LED0 using the one of the GPIO ports for your reference. Try this project before beginning the virtual timer software. The project is archived. Simply extract it and click on the msstimer LiberoProject file. You should generate and rebuild the project before using.

We provided you a basic hardware timer so you could focus on implementing the virtual timers. Here is a basic list of some of the things you will need to do and some pseudo code. This is not complete so supplement as necessary. Also, you can use your own approach as long as you are using a linked list to implement the timers that allows for adding, removing and sorting the list as necessary.
  1. Initialize hardware (hardware timers, GPIO, etc.
  2. Hardware functions to toggle LEDs.
  3. Hardware timer interrupt handler.
  4. Linked list root pointer.
  5. Linked list data structure.
  6. Functions to populate linked list or start timers.
  7. Functions to sort list according to your scheme.
  8. Functions to update counts in the timer list.
  9. Possibly more?
//The following are suggestions for functions and data structures you will likely need.

//structure holding virtual timer info
//this may vary depending on your implementation
typedef struct Timer {
handler_t handler;//function pointer (called after timer period)
uint32_t time;//time remaining for this counter
uint32_t period;//period
uint32_t mode;//continuous or one shot timer
struct Timer* next;//points to next timer
} timer_t;

//pointer to start of linked list
timer_t *root = NULL;

//used to initialize hardware
void start_hardware_timer(uint32_t period){};

//declare function pointer type
typedef void (*handler_t)(void);

void led0() {code to toggle LED}

//add a continuous (periodic) timer to linked list.
void startTimerContinuous(handler_t handler, uint32_t period){};
startTimerContinuous(&led0, 50000000);

//add a one shot timer to the linked list.
void startTimerOneshot(handler_t handler, uint32_t period){};

//put new timer in list maintaining order least time remaining to most
void insert_timer(timer_t * newtimer){};

//update down count with elapsed time, call fnc if timer zero, update continuous timers
//with new down count
void update_timers(void){};

Debugging and Development
  1. Test your hardware timer and make sure it is working. For example, put a breakpoint in the interrupt handler.
  2. Test your link list with a simple timer list. Start with one or two timers  periodic (continuous) timers.
  3. Expand the list to 3  or more timers.
  4. Finally try some periodic timers with a single shot (one time) timer.
We must see and be able to identify that you can add items to the linked list, sort the list a necessary and remove items from the list. Simply creating fixed implementations that emulate demos below is not sufficient.
  1. Demonstrate the following: LED 1 blinking at 1/2 second, LED 2 blinking at 1 second and LED 3 blinking at 2 seconds. (10 points)
  2. Demonstrate the following: LED 1 blinking at 1/4 second, LED 2 blinking at 1/2 second, LED 3 blinking at 1/2 second, LED 4 blinking at 1 second. (10 points, if you do this part you don't have to demo 1)
  3. Demonstrate the following: LED 1 stays on for just one second, LED 2 is blinking at 1/10 second, LED 3 is blinking at 1/10 second, LED 4 blinking at 1/4 second and LED 5 is blinking at 1 second.(5 points, if you do this part you don't have to demo 1 or 2)
  4. Demo scenarios at the instructors discretion.