When we last looked at the Parallella I had explained the basics of the timers within the epiphany and promised to follow up with a few simple examples, so in this blog that is exactly what we are going to do.
We are going to use the both timers within the Epiphany to demonstrate simple concepts like how we can measure events e.g. how long a function takes to execute and how we can check that the number of instructions executed and stalls within the pipelines along with setting and using interrupts.
The idea of this is to demonstrate how easily we can get the timers up and running as such we are going to use a workgroup of just one core and run the timers in a number of different modes.
To communicate back to the Host we will use a number of memory locations with the local memory of the selected core and then access them from the host using the e-hal e_read function as per the memory map below.
The code is going to the following
- Read and confirm the initial value of the timer before we do anything with it
- Configure timer 0 to count clocks and start it counting down from its maximum value
- Configure timer 1 to count IALU instructions while timer 0 is counting down
- Configure timer 0 to count down and issue a interrupt when it is counts down
- Set up a timer interrupt
Of course the first thing we need to do is access the timer we can do this using the type definition e_ctimer_id_t which defines two types one for each timer. Not unsurprisingly called E_CTIMER_0 and E_CTIMER1 it is these timer id’s we use within the e_lib provided timer functions to identify the timer we wish to interact with.
The first thing the code is read the value of timer 0 as this has not been used yet it should be at its reset value of zero. We do this using the function e_ctimer_get(E_CTIMER_0) using the timer id of the timer we wish to get the value of.
Having establish it is at zero we next configure both timers 0 and 1 to count clocks and IALU instructions respectively, as both counters count down we have to first set them to a pre-defined value. We can as we will do in the interrupt example set them to any value we want however, for this part of the blog I am going to set them to the maximum value of the counter. Rather helpfully the e_lib defines a constant called E_CTIMER_MAX which will set them to the maximum value being 32 bits wide this is rather large.
With this achieved the next step is to configure both timer 0 and 1 to run again we do this using the e_lib function e_ctimer_start(E_CTIMER_0, E_CTIMER_CLK) where we identify the timer we wish to start using the timer id and define the configuration using the type e_ctimer_config_t such that is counts the events we desire. In this case for timer 0 it is E_CTIMER_CLK and for timer 1 it is E_CTIMER_IALU_INST. Both of these timers are then stopped using the e_ctimer_stop function to demonstrate both are decrementing and counting down as expected.
Of course the final stage is to demonstrate how we can use an interrupt from the timer for this we need to define an Interrupt Service Routine to be called when the event occurs and configure the IRQ to call this ISR.
The first stage is to attach the timer interrupt (E_TIMER0_INT) to the ISR handler we have defined in this case I have called the function timer_hand. With the interrupt attached to the ISR the next stage is to enable the interrupt for the timer using the e_irq_mask function setting the interrupt mask to false. We also need to enable interrupts on a global scale to ensure the interrupts are enabled todo this we need to set the global mask to false using the e_irq_global_mask.
We can then configure timer 0 as we had above and when it expires (counts down to 0) it will generate an interrupt that will call the ISR.
The ISR is just like any other function however to ensure it is handled correctly we use need to use complier attribute to identify it as the ISR. In this very simple attribute all the ISR does is write a defined pattern to the last register identified of 55 when called.
The code for the example is available here