MCU learning, Intermediate-Level, Part 2, Using Timer & Interrupt of NXP MPC5744P
We’ve shown you how it feels about using a timer function in the previous exercise: MCU learning, Entry-Level, Part 2, Delay function.
But…what we did not mention is that exercise more or less is a software timer. So, this exercise tend to show you how to access a Hardware timer in a more profesional MCU: NXP MPC5744P.
Furthermore, we will also show you how to use this project which is inherited from the original NXP-provided Etimer example.
What do you need for this exercise?
- MPC5744P dev-board: LINK
- S32 design studio for MPC5744P: LINK
- The example that we provided: LINK
- MPC5744P User manual Rev6(2016/06)
What are we focusing on today?
- HW timer basic function(ETimer).
- How to initialize an HW timer?
- How to register/link an ISR (Interrupt Service Routine)?
HW timer basic function.
First, let’s have a look at the block diagram of the ETimer in MPC5744. You can find more detail information in the user manual of MPC5744P.
(Chapter 39 Enhanced Motor Control Timer (eTimer))
In the description, MPC5744P’s ETimer has 6 identical counter/timer channels and 1 watchdog timer. Since it has “Primary input” & “Secondary input”, it can be a counter as well.
We also can tell that the timer has an important relationship with the CLOCK signal. As we know, one of the basic function of the timer is the counter will increase 1 / decrease 1 when the clock signal is ticking. So, in this case, the timer’s speed will be related to how fast the clock ticking.
In this exercise, we will use the timer to calculate time pass. As result, we will take the clock as the main input for the timer. How do we know how fast for the clock? After all, not all the clock is as fast as the main system clock. From the clock distribution diagram below, we can tell that the eTimer clock is based on MOTC_CLK(Motor control clock).
Another topic for this section is there are so many registers that a single timer channel has, what register we should configure?
For this exercise, we hope to let the timer’s counter “increase ” when the MOTC_CLK is ticking. Once it reaches a certain value, it should trigger an interrupt and interrupt controller will call an interrupt service routine(ISR). So, we will use one of the comparator register(ETIMER_CHn_COMP1) for this application. ETIMER_CHn_COMP1 will hold the value that we want the timer triggers an interrupt when it is the same with the counter.
We also need to configure one of the channel behaviour by setting the control register (ETIMER_CHn_CTRL1). To trigger an interrupt, the interrupt register of the timer also need to be set(ETIMER_CHn_INTDMA). The last but not least, we will need to enable and disable each channel during the initialization process by using the Enable register(ETIMER_ENBL).
That is all we need to know about this exercise! Let’s coding!
How to initialize an HW timer?
In the example we provide, there is a piece of code called “ETimer_Init(void)”. This part of code should be called once during MCU initialization process.
I also included very sophisticated information about where are these numbers come from in “register_calculation.xlsx”. It is important to make it correct otherwise it won’t work correctly.
Let’s have a look register by register.
- Enable register: To Enable or Disable each channel.
Disable ALL of the channels by:
ETIMER_0.ENBL.R = 0x0000;
You must disable the module during its initialization phase.
- Control register: How each channel works.
ETIMER_0.CH.CTRL1.R = 0x3F40;
CNTMODE: 0b001(Count rising edges of the primary source)
PRISRC: 0b11111(Select IP Bus clock as the primary source and divide the clock by 128 Prescaler)
ONCE: 0b1(Count repeatedly)
LENGTH:0b1(Count until compare, then reinitialize the counter)
DIR: 0b0(Count up)
SECSRC: 0b0(Counter #0 input pin), this part of the setting is not important since we only using the primary source.
- Compare register: What value we would like to compare.
ETIMER_0.CH.COMP1.R = 0x9896;
The SysClk_Init(void) will set the MOTC_CLK to 5MHz which is the ETimer’s clock. We also assigned PRISRC register to divide this clock by 128.
5MHz / 128 = 39,062 KHz
=> It means every second pass, the counter value will add 39,062 = 0x9896.
Assign 39062(0x9896) to the Compare 1 Register will cause an ETimer trigger event exactly 1-second period.
- Interrupt and DMA Enable Register: Interrupt and DMA configuration.
ETIMER_0.CH.INTDMA.R = 0x0002;
Only need to set TCF1E: Timer Compare 1 Flag Interrupt Enable.
- Compare and Capture Control Register: Behaviour of Compare and Capture.
ETIMER_0.CH.CCCTRL.R = 0x0200;
CLC2 and CLC1: all 0(We do not want to preload a value into the counter,)
CMPMODE:0b10 (COMP1 register is used when the counter is counting up.)
CPT2MODE, CPT1MODE, CFWM, ONESHOT, ARM: All 0 (We do not need capture function.)
If you checked the original official example from NXP, the CCCTRL will set to 0x240. I’m not sure why the engineer did it since we do not use capture 2.
Then, we need to enable the timer channel 1 by
ETIMER_0.ENBL.R = 0x0001;
If you checked the original official example from NXP, you will find ENABLE register is set to 0x0003…Again, it is confusing me for the first time.
That is all we need to do to configure an Etimer channel!
How to register an ETimer ISR (Interrupt Service Routine)?
As you can find in the “main.c”, there is an ETimer_ISR(). But…when the interrupt event is triggered, how does MCU know what ISR it should be called?
In “src ”folder a file called “intc_SW_mode_isr_vectors_MPC5744P.c” will define the interrupt vector. These vector IDs are related to what interrupt is triggered and what ISR should be called. You can find the “Table 7–16 Interrupt vector table” in the user manual.
All we need to do is to find the source module is “eTimer_0 ”and source signal is “TC0IR”. The result will be vector #611.
Then, we replace the &dummy by “ETimer_ISR()”. It will create the result that when eTimer_0 TC0IR is triggered, the interrupt controller will call “ETimer_ISR()”.
(uint32_t) &ETimer_ISR, /* Vector # 611 TC0IR eTimer_0 */
Note: There is no very clear explanation about TC0IR. But I guess it represents “Timer Compare 0 Interrupt Register ”
Once you registered it, you also need to assign the priority of the interrupt. MPC5744P offers 32 interrupt priority levels, 16 SW programmable interrupts.
By setting “INTC Priority Select Register (INTC_PSRn)” you can assign the ISR with corresponding priority.
INTC_0.PSR.R = 0x8001; //set interrupt priority
PRC_SELN0: 0b1 (Interrupt request sent to processor 0)
SWTN: 0b0 (No need to set since this interrupt is caused by HW)
We also need to clear the Status Register which causing an interrupt event.
ETIMER_0.CH.STS.R = 0x0002; //clear interrupt flag
TCF1(Timer Compare 1 Flag): This bit is set when a successful compare occurs with COMP1. This bit is cleared by writing a one to this bit location.
Putting all together will be :
That is a complete main.c for this exercise.
Once you flash it into MPC5744P Dev-board you should see the LED flashing around 1Hz. And if you turn the user potentiometer you should see the colour changing as well.
We will talk about GPIO and ADC in this exercise later since it is another important topic. Assigning correct clock to each module is another difficult task. So we will leave it to later exercise too.
Now you should have a rough idea of how different compare the Particle development process with MPC5744P(Power Architecture Z200e4 core). Actually, Particle Photon also comes with ARM Cortex M3 MCU core. But Particle's engineer already did a great job to build some easy-to-use library for you.
Accessing low-level hardware of MPC5744P which is Power PC architecture is similar to accessing low-level hardware of ARM Cortex-M3. Particle just adds some library layers to make it more friendly to use.
NXP S32 design studio also has a graphical configuration tool called “Processor Expert” which can do the module initialization task, including the clock speed, for you. Then, you just need to call the SDK library to use it.
From this exercise, we can tell there is another down-side of using Particle Photon for some professional project in some case: You CANNOT DIRECT access HW timer in Particle Photon like this exercise by the library Particle provides which means you will lose some flexibility and timing accuracy in this case.
For example, we can use capture mode of ETimer and assign secondary input to a pin to measure a digital signal period in very accuracy level. And let Etimer keep update this value without keep causing an interrupt to the system. Each value can be extremely accurate if you are running the timer in 5MHz without Prescaler. (1 count = 0.2 micro-second)
But Particle only has software timer API in micro-second level(micros()). Even we use interrupt to calculate the period of signal the accuracy is still micro-second level accuracy. (1 count = 1 micro-second). Particle also not allows the user direct access the Capture register in ARM cortex-M3.
That is already 5 times different. If we use the System Timer Module(STM) in MPC5744P which clock speed can run at 200MHz speed, it will make a huge difference.
From the prospect of OS, the application shouldn't directly access very low-level HW. But from the bare-metal programming side of view, it will reduce the performance in some way.
Does it matter? Yes…only if you are dealing with something that timing accuracy will have a significant impact.