FB pixel

Renesas RL78 - 7. microsecond Delay Function using the Timer Array Unit (TAU)

Published


Hi! Welcome back again to CircuitBread. In our previous tutorial, we discussed the RL78 12-bit interval timer and created a millisecond delay function, delay_ms(). In this tutorial, we will create a microsecond delay function using the timer array unit (TAU) and create a delay library which combines the millisecond and microsecond delay functions.

Before we proceed, I just want to tell you in advance that the result you’ll get here is not perfect. It has some limitations, such as the minimum delay which is 5us. I believe this is due to the overhead generated by the compiler which can be reduced by using optimization. But I don’t want to increase the optimization level as it sometimes causes problems. However, I added 4 functions that you can call to generate delays from 1-4us.

Timer Array Unit (TAU)

Figure 1. RL78 Timer Array Unit.
Figure 1. RL78 Timer Array Unit.

The TAU is another piece of hardware inside an RL78 MCU that has four 16-bit timers. The number of units in an RL78 device depends on the device pin count. 30 to 64-pin devices only have one TAU while 80 and 100-pin devices have two.

The TAU can do the following functions:

Table 1. RL78 Timer Array Unit Functions.
Table 1. RL78 Timer Array Unit Functions.

As you can see, the TAU has a lot of functions. However, we will just focus on the interval timer function of the TAU in this tutorial as our goal here is just to create a microsecond delay function.

TAU Configuration and Registers

Figure 2. Timer Array Unit 0 Entire Configuration.
Figure 2. Timer Array Unit 0 Entire Configuration.

Figure 2 shows the configuration of the RL78 TAU 0. As you can see, there are four channels, clock inputs, clock prescaler, channel outputs, etc. The TAU is controlled by these registers:

  • Peripheral Enable Register 0 (PER0)
  • Timer Clock Select Register m (TPSm)
  • Timer Channel Enable Status Register m (TEm)
  • Timer Channel Start Register m (TSm)
  • Timer Channel Stop Register m (TTm)
  • Timer Input Select Register 0 (TIS0)
  • Timer Output Enable Register m (TOEm)
  • Timer Output Register m (TOm)
  • Timer Output Level Register m (TOLm)
  • Timer Output Mode Register m (TOMm)

m: Unit number (m = 0, 1)

Figure 3. Timer Array Unit 0 Channel 0 Internal Block Diagram.
Figure 3. Timer Array Unit 0 Channel 0 Internal Block Diagram.

Aside from the registers that control the TAU, we also need to set the registers that control the operation of the channel. Figure 3 shows the internal block diagram of channel 0 of the TAU 0 and the list below shows the registers that control the channel:

  • Timer Count Register mn (TCRmn)
  • Timer Data Register mn (TDRmn)
  • Timer Mode Register mn (TMRmn)
  • Timer Status Register mn (TSRmn)
  • Input Switch Control Register (ISC)
  • Noise Filter Enable Registers 1, 2 (NFEN1, NFEN2)
  • Port Mode Control Register (PMCxx)
  • Port Mode Register (PMxx)
  • Port Register (Pxx)

m: Unit number (m = 0, 1), n: Channel number (n = 0 to 3)

It seems like there’s a lot of registers to be configured. But don’t worry, for the interval timer function, we don’t need to set all of the registers mentioned above. We only need to configure these registers:

Peripheral Enable Register 0 (PER0)

Figure 4. PER0 Register TAUmEN bits.
Figure 4. PER0 Register TAUmEN bits.

We are already familiar with this register. The RTCEN bit of this register enables or stops the 12-bit interval timer input clock supply. This time, what we need to set or clear are the TAU0EN (for TAU0) and TAU1EN (for TAU1) bits. Writing 1 to the TAUmEN bits will enable the input clock supply of the TAUs and will also allow reading or writing to the special function registers (SFRs) used by the TAUs. Writing 0 to the TAUmEN bits will disable the input clock supply and the TAUs will be in reset status.

The PER0 register can be set by a 1-bit or 8-bit memory manipulation instruction and a reset signal generation clears the PER0 register to 00H (hex).

Timer Clock Select Register m (TPSm)

As shown in the TAU0 configuration (figure 2), there are four operation clock sources that can supply the TAU channels: CKm0, CKm1, CKm2, and CKm3. All channels of the TAU can use CKm0 and CKm1. However, CKm2 and CKm3 can be only used for channel 1 and channel 3.

Now, to be able to count or measure longer time durations, we need to use a slower clock source and one way to have a slower clock source is by dividing the input clock fCLK. The TAU includes a prescaler which can divide the fCLK based on what we set on the TPSm bits.

Figure 5. Timer Clock Select Register.
Figure 5. Timer Clock Select Register.

The division factor for operation clock sources CKm0 and CKm1 depends on what we write on bits PRSm03 to PRSm00 and PRSm13 to PRSm10, respectively. For example, we want to use CKm0 as the operation clock source of TAU0 channel 0, we want CKm0 to be 1MHz (fCLK = 32MHz), in this case we need to set PRSm03 to PRSm00 to 0101 (5 in decimal). The output frequency is fCLK divided by 2x. x is the decimal equivalent of the binary value in PRSm03 to PRSm00 bits. So 32MHz/25 = 1MHz.

Figure 6. Selection of CKm0 and CKm1 Operation Clock Sources.
Figure 6. Selection of CKm0 and CKm1 Operation Clock Sources.

The division factor for operation clock sources CKm2 and CKm3 depends on what is written in PRS21 to PRS20 bits and PRSm31 to PRSm30 bits, respectively.

Figure 7. Selection of CKm2 and CKm3 Operation Clock Sources.
Figure 7. Selection of CKm2 and CKm3 Operation Clock Sources.

Unlike the PER0 register where you can set its bits individually using a 1-bit memory manipulation instruction, the TPSm register can only be set by a 16-bit memory manipulation instruction. So we need to set all of the TPSm bits at the same time. A reset signal generation clears the TPSm register to 0000H (hex).

Timer Mode Register mn (TMRmn)

Figure 8. Timer Mode Registers mn (TMRmn).
Figure 8. Timer Mode Registers mn (TMRmn).

If you check figure 6-12 of the hardware user’s manual, you’ll see three TMRmn registers. The first TMRmn register is for channel 2, the second is for channel 1 and 3, and the third is for channel 0. I think they just differ in bit 11. However, we will just focus on the third TMRmn register in this tutorial as we will only use channel 0.

Figure 9. Channel 0 Timer Mode Register (TMRm0).
Figure 9. Channel 0 Timer Mode Register (TMRm0).

The operation mode of the TAU channel is set through the TMRmn register. In this register, we can select the operation clock source, select the count clock, select if the channel will operate independently or simultaneously with another channel (as a slave or master), select if the channel operates as a 16 or an 8-bit timer (only for channels 1 and 3), specify the start trigger and capture trigger, select the valid edge of the timer input, and specify the operation mode (interval, capture, event counter, one-count, or capture and one-count).

The TMRmn register can be only set by a 16-bit memory manipulation instruction so we need to set all bits at the same time. A reset signal generation clears the TMRmn register to 0000H (hex).

  • CKSmn1 and CKSmn0 Bits
Figure 10. TMRmn Register CKSmn1 and CKSmn0 Bits Settings.
Figure 10. TMRmn Register CKSmn1 and CKSmn0 Bits Settings.
  • As mentioned earlier, we have four operation clock sources: CKm0, CKm1, CKm2, and CKm3. The speed or frequency of these clock sources can be adjusted based on the TPSm bits setting. Now, to select which of these clock sources will be supplied to a TAU channel, we can use the CKSmn1 and CKSmn0 bits. For example, we want to use channel 0 of TAU0 and we want to select CKm0, we can just set CKSm01 and CKSm00 to 00.
  • CCSmn Bit
Figure 11. TMRmn Register CCSmn Bit Settings.
Figure 11. TMRmn Register CCSmn Bit Settings.
  • The CCSmn bit decides if the channel’s input is from the operation clock source or from the TImn pin. Since we’re using the interval timer function of the TAU, we will set the CCSmn bit to 0.
  • TMRmn Bit 11 (MASTERmn/SPLITmn/Fixed to 0)
Figure 12. TMRmn Register Bit 11 Settings.
Figure 12. TMRmn Register Bit 11 Settings.
  • The bit 11 of the TMRmn register depends on the channel used. In channel 2, bit 11 is MASTERmn which sets channel 2 to operate independently or simultaneously with another channel as a slave if cleared to 0 or as master channel (simultaneous channel operation) if set to 1. In channel 1 or channel 3, bit 11 is SPLITmn. If SPLITmn is 0, the channel operates as a 16-bit timer. Setting the SPLITmn bit to 0 will also make the channel operate independently or as a slave when in simultaneous channel operation function. Setting the SPLITmn bit to 1 will make it operate as an 8-bit timer.

    For channel 0, bit 11 of its TMRmn register is fixed to 0. According to the hardware user’s manual: “regardless of the bit setting, channel 0 operates as master, because it is the highest channel”. Also, the interval timer function is under the independent channel operation function of the TAU. So in this tutorial, we would really want to set bit 11 of the TMR00 register to 0.
  • STSmn2, STSmn1, and STSmn0 Bits
Figure 13. TMRmn Register STSmn2, STSmn1, and STSmn0 Bits Settings.
Figure 13. TMRmn Register STSmn2, STSmn1, and STSmn0 Bits Settings.
  • The STSmn2, STSmn1, and STSmn0 bits of the TMRmn register sets the start trigger or capture trigger of the channel. We can select if the start or capture trigger is from a software, an edge of the TImn input pin, or from an interrupt. For this tutorial, we want to use the interval timer function in a delay function so we’re going to select software trigger. We’ll set these bits to 000 later.
  • CISmn1 and CISmn0 Bits
Figure 14. TMRmn Register CISmn1 and CISmn0 Bits Settings.
Figure 14. TMRmn Register CISmn1 and CISmn0 Bits Settings.
  • The CISmn1 and CISmn0 bits set the valid edge of the TImn pin. However, since we’re not going to use the TImn pin, we can just set this to 00 or its default value.
  • MDmn3, MDmn2, and MDmn1 Bits
Figure 15. TMRmn Register MDmn3, MDmn2, and MDmn1 Bits Settings.
Figure 15. TMRmn Register MDmn3, MDmn2, and MDmn1 Bits Settings.
  • The MDmn3, MDmn2, and MDmn1 bits of the TMRmn register select the operation mode of the channel. You can refer to figure 15 for other operations but since we want to use the interval timer operation or function, we will set these bits to 000 later.
  • MDmn0 Bit
Figure 16. TMRmn Register MDmn0 Bit Settings.
Figure 16. TMRmn Register MDmn0 Bit Settings.
  • The MDmn0 bit of the TMRmn register when the channel is in interval timer mode disables or enables timer interrupt generation when counting is started. If the MDmn0 bit is set to 0, no interrupt will be generated when counting is starter. If it is set to 1, an interrupt will be generated when counting starts.

Timer Output Enable Register m (TOEm)

Figure 17. Timer Output Enable Register.
Figure 17. Timer Output Enable Register.

If you remember the block diagram of TAU0 channel 0 shown in figure 3, the channel actually has an output controller which is connected to the timer output pin (TOmn). You can actually generate an output waveform from the TOmn pin if it is enabled. The bits in the TOEm register disable or enable the timer output of the channels. If the bit is set to 1, timer output is enabled while if it is cleared to 0, the timer output is disabled and the output is fixed (depending on the TOm bits settings). We will not use the timer output in this tutorial, so we'll set the TOEm register to 0000H (hex).

The lower 8 bits of the TOEm register can be set with a 1-bit or 8-bit memory manipulation instruction with TOEmL. However, the hardware user’s manual recommends to clear bits 15 to 4 to “0” so we will use a 16-bit memory manipulation instruction to set the register. A reset signal generation clears this register to 0000H (hex).

Timer Output Register m (TOm)

The bits in the TOm register set the level or value of the channel timer output pin. For example, if the TOm0 bit is 0, the value of channel 0 timer output pin is 0 or LOW. If the TOm0 bit is set to 1, the value of channel 0 timer output pin will be set to 1 or HIGH. Since we will not use the timer output, we will just clear the TOm bits to 0. Please take note that the TOm bits can be rewritten by software only when the timer output is disabled (TOEmn = 0). The software instruction will be ignored if the timer output is enabled (TOEmn = 1).

Figure 18. Timer Output Register.
Figure 18. Timer Output Register.

The lower 8 bits of the TOm register can be set with an 8-bit memory manipulation instruction with TOmL. But same with the TOEm register, the hardware user’s manual recommends to clear bits 15 to 4 to “0” so we will use a 16-bit memory manipulation instruction to set the register. A reset signal generation clears this register to 0000H (hex).

Timer Data Register mn (TDRmn)

Figure 19. Timer Data Register mn (TDRmn).
Figure 19. Timer Data Register mn (TDRmn).

The TDRmn register is a 16-bit register that can be used as a compare or a capture register. In interval timer mode, the TDRmn is used as a compare register. For now, we will just discuss its function as a compare register. We will discuss its function as a capture register in other tutorials.

The TDRmn register is like the ITCMP bits in the ITMC register of the 12-bit interval timer of the RL78 MCU. The value set in the TDRmn register will be loaded in the TCRmn register (which we will discuss next). The TDRmn register can be read or written in 16-bit units and its value can be changed at any time. However, its new value only becomes valid in the next period. A reset signal generation clears the TDRmn register to 0000H (hex).

Timer Count Register mn (TCRmn)

The TCRmn register is a read-only 16-bit register used to count clocks. It can count up or count down depending on the channel’s operation mode. In interval timer mode or function, the TCRmn register operates as a down counter. The TCRmn register loads the value of TDRmn at the first count clock after the channel start trigger bit of the timer channel start register m (TSm) is set to 1. Then the TCRmn register starts to count down. Interrupt is generated when MDmn0 is set to 1.

Figure 20. Timer Count Register mn (TCRmn).
Figure 20. Timer Count Register mn (TCRmn).

When the value of the TCRmn register becomes 0, the INTTMmn interrupt is generated and if the timer output is enabled, the TOmn is toggled at the next count clock. At the same time, the value of the TDRmn register is loaded again in the TCRmn register. After that, the same operation is repeated unless the channel’s operation is stopped.

The generation period of the interrupt is calculated using this formula:

Generation period of INTTMmn (timer interrupt) = Period of count clock (Set value of TDRmn + 1)

For example, fCLK is 32MHz, we select CKm0, and just set PRSm03 to PRSm00 bits to 0000, the operation clock source (CKm0) will still be 32MHz. So the Period of count clock is 1/32MHz or 31.25ns. So if we set TDRmn’s value to 31, the generation period will be equal to 31.25ns (31 + 1) or 1000ns or 1us.

Timer Channel Enable Status Register m (TEm), Timer Channel Start Register m (TSm), and Timer Channel Stop Register m (TTm)

The TEm register is used to start or stop the timer operation of the TAU channels when its bits are set to 1 or 0, respectively. However, the TEm register is a read-only register. To set or clear its bits, we will use the TSm and TTm registers.

Figure 21. Timer Channel Enable Status Register m (TEm).
Figure 21. Timer Channel Enable Status Register m (TEm).

Each bit of the TEm register corresponds to each bit of the TSm and TTm registers. So if we set TSm0 bit to 1 to enable operation, the TEm0 bit will be also set to 1 and if we write 0 to the TTm0 bit because we want to stop the operation, the TEm0 bit will be also set to 0.

Figure 22. Timer Channel Start Register m (TSm) and Timer Channel Stop Register m (TTm).
Figure 22. Timer Channel Start Register m (TSm) and Timer Channel Stop Register m (TTm).

The lower 8 bits of the TSm and TTm register can be set with a 1-bit or 8-bit memory manipulation instruction with TSmL and TTmL, respectively. But we will use a 16-bit memory manipulation instruction when we set them as the hardware user’s manual recommends to clear bits 15 to 12, 10, and 8 to 4. Another thing to take note is that the value of the TSm and TTm registers will always be equal to 0 when we read them, as its bits are immediately cleared when the timer operation is started or stopped as they are trigger bits. Both registers are cleared to 0000H (hex) after reset.

TAU Channel Interrupt and Mask Flags (TMIFmn and TMMKmn)

We are not going to use interrupt in the microsecond delay function. However, we still need the mask (TMMKmn) and interrupt (TMIFmn) flags of the TAU channel to disable the channel’s interrupt and check if the timer is done counting. So later, you’ll see these flags being used.

microsecond Delay Function

Before we discuss the microsecond delay function delay_us(), let’s check first the operation of the sample code. The operation is shown in figure 23 and as you can see, it’s just like the Arduino’s Blink example except that before the program enters the infinite while loop inside the main function, we still need to call the delay_us_TAU_Init() function which initializes the TAU0 Channel 0 so that our delay_us() function will be able to function.

Figure 23. delay_us() Function Sample Code Flowchart.
Figure 23. delay_us() Function Sample Code Flowchart.

You might wonder why we still need to do this. Why not just put the timer initialization code inside the delay_us() function just like in the delay_ms() function. The reason for this is that, unlike the delay_ms() function, we are trying to achieve a smaller delay here. The TAU initialization code adds around 3us to the delay so it’s better to separate it from the delay_us() function so we can generate microsecond delays as small as 5us.

Therefore, if you are going to use this delay_us() function, please don’t forget to call the delay_us_TAU_Init() function at the initialization stage or else the delay_us() function will not work. Now, let’s discuss the delay_us_TAU_Init() function. Please refer to figure 24 for the operation.

delay_us_TAU_Init()

Figure 24. delay_us_TAU_Init() Flowchart.
Figure 24. delay_us_TAU_Init() Flowchart.
  1. TAU0EN Bit
    As mentioned earlier, the first thing that we need to do to initialize the TAU is to set the TAUmEN bit in the PER0 register. This will enable supply of input clock to the TAU and reading or writing to its SFRs. We are going to use TAU0 so we need to set TAU0EN bit to 1.
  2. TPS0 Register
    After setting TAU0EN bit to 1, we set the prescaler of the TAU0 operation clock sources (CK00 - CK03) by setting the bits of the TPS0 register. Please check the discussion earlier about the TPSm register to review which bits are for CK00, CK01, CK02, and CK03.

    The TPSm register can be only set by a 16-bit memory manipulation instruction. However, you can still set the prescaler of the operation clock sources individually by using a bit-wise operator OR (|) as shown in figure 24. I don’t want to divide the input clock fCLK, so I’ve set all the TPS0 bits to 0.
  3. TT0 Register
    After setting the TPSm register, we set all the bits of the TT0 register to 1 (except for the bits that are fixed to 0) just to make sure that all channels of the TAU0 are not operating. Again, we need to use a 16-bit memory manipulation instruction to set the TT0 register but as shown in figure 24, we can set the bits individually by using a bit-wise operator OR.
  4. TMMK00 and TMIF00 Bits
    Next thing that we need to do is mask the interrupt and clear the interrupt flag of the channel that we’re going to use. We are going to use Channel 0 of TAU0 so we set TMMK00 to 1 and TMIF00 to 0.
  1. TMR00 Register
    After disabling TAU0 Channel 0 interrupt, we’re going to set the TMR00 register. If you forgot about the function of the TMRmn bits, please review our discussion earlier about the TMRmn register.
    • The CKSmn1 and CKSmn0 bits of the TMRm0 selects the operation clock source of the TAU0 channel. We’re using Channel 0 and want to select CK00, so we set the CKS001 and CKS000 bits to 00. We set the CK00 prescaler to 0000 so our clock will operate at 32MHz or 31.25ns.
    • The CCSmn bit selects the count clock and since we want to use the interval timer function, we should select the operation clock fMCK. So we should set CCS00 to 0.
    • For bit 11, it’s fixed to 0 so we should just leave it like that.
    • The STSmn2, STSmn1, and STSmn0 bits select the start or capture trigger of the channel. We’re going to select “Only software trigger start is valid (other trigger sources are unselected).” so we should set (STS002, STS001, STS000) to 000.
    • The CISmn1 and CISmn0 bits are for the TImn pin which we will not use, so I think it’s safe to just leave them to 00.
    • The MDmn3, MDmn2, and MDmn1 bits select the operation mode of the channel. We want to select interval timer mode so we should set (MD003, MD002, MD001) to 000.
    • The MDmn0 bit is for generating interrupt when the count starts. But we’re going to set MD000 to 0 because we don’t want Channel 0 to generate an interrupt.
  1. TOm and TOEm Registers
    We’re not going to use Channel 0 timer output so first we’re going to set its output value to 0 or LOW by setting the TO00 bit of the TO0 register to 0. We can set the TO0 register with a 16-bit memory manipulation instruction. Next, we’re going to disable Channel 0 timer output by setting the TOE00 bit of the TOE0 register to 0.

So here’s the code of the delay_us_TAU_Init() function:

void delay_us_TAU_Init(void)

{

TAU0EN = 1U; /* Supplies Input Clock */

TPS0 = 0x0000U | /* CKm0 = fCLK */

0x0000U | /* CKm1 = fCLK */

0x0000U | /* CKm2 = fCLK/2^1 */

0x0000U; /* CKm3 = fCLK/2^8 */

/* Stop all channels */

TT0 = 0x0001U | /* Channel 0 (TTm0) operation is stopped (stop trigger is generated) */

0x0002U | /* Channel 1 (TTm1) operation is stopped (stop trigger is generated) */

0x0004U | /* Channel 2 (TTm2) operation is stopped (stop trigger is generated) */

0x0008U | /* Channel 3 (TTm3) operation is stopped (stop trigger is generated) */

0x0200U | /* Channel 1 higher 8 bits (TTHm1) operation is stopped (stop trigger is generated) */

0x0800U; /* Channel 3 higher 8 bits (TTHm3) operation is stopped (stop trigger is generated) */

/* Mask Channel 0 Interrupt */

TMMK00 = 1U; /* Disable INTTM00 Interrupt */

TMIF00 = 0U; /* Clear INTTM00 Interrupt Flag */

/* Channel 0 used as Interval Timer */

TMR00 = 0x0000U | /* Selection of macro clock (fMCK) of Channel 0 (CKSmn1 and CKSmn0 bits). Selected CKm0. */

0x0000U | /* Selection of count clock (CCK) of channel n (CCSmn bit). Selected fMCK specified by CKSmn1 and CKSmn0 bits. */

0x0000U | /* Bit 11 of Channel 0 TMR00 Register. Fixed to 0. */

0x0000U | /* Setting of start trigger or capture trigger of Channel 0 (STSmn2, STSmn1, and STSmn0 bits). Selected "Only software trigger start is valid (other trigger sources are unselected)". */

0x0000U | /* Operation mode of Channel 0 (MDmn3, MDmn2, and MDmn1 bits). Selected Interval Timer Mode. */

0x0000U; /* Setting of starting counting and interrupt (MDmn0 bit). Selected "interrupt is not generated when counting is started". */

TO0 &= ~0x0001U; /* Sets Channel 0 timer output value (TOm0) to 0 or LOW. */

TOE0 &= ~0x0001U; /* Disables Channel 0 timer output (TOEm0). */

}

Now, let’s discuss the delay_us() function. Please refer to figure 25 for the operation.

delay_us(uint16_t delay_us)

Figure 25. delay_us() Function Flowchart.
Figure 25. delay_us() Function Flowchart.

The operation of the delay_us() function is similar to our delay_ms() function except that we removed the code for initializing the timer and put it on another function so that we can generate delays as small as 5us.

Ideally, we should be able to produce a 1us delay by loading a 31 value to TDR00.

delay (in microsecond) = period of count clock * (TDR00 value + 1) = 31.25ns * (31 + 1) = 1us

But, due to some reasons (maybe compiler overhead), the calculation above produces around 4.6 or 4.7us delay instead of 1us. So what I did is I modified the formula I used when converting time (delay in microseconds) to TDR00 value.

Instead of

TDR00 = (delay_us * 32) - 1;

I changed it to

TDR00 = ((delay_us - 4) * 32) - 1);

This way, we can correctly generate delays starting from 5us. However, if we pass a value to the function that is less than 5, the formula will produce a negative value which is an invalid TDRmn value.

So to avoid errors, I’ve added a code that will check first if the value passed is less than 5 (check the part below in figure 26 highlighted in orange). If it is, the program will just execute a NOP() instruction and then return. If the value is greater than 4, then the other operation or code will handle it. Surprisingly, the operation added (checking if delay_us value is < 5us + NOP() + return) generates a delay which is approximately 1us. So you can use this if you want to generate a 1us delay.

Figure 26. Added operation for delays less than 5us.
Figure 26. Added operation for delays less than 5us.

Now, if the value passed to the delay_us() function is greater than 4, the program will skip the operation that we’ve added for delays less than 5us and proceed to checking if the delay value is greater than 2048 (check figure 27).

If the delay value is greater than 2048, the program will enter a loop that will generate a 2048us delay each time the body of the loop is executed (check the part in figure 27 highlighted in orange) just like in our delay_ms() function when the delay exceeds 125ms. Inside the loop, the program loads 65535 to TDR00, starts count operation, and polls the TMIF00 flag. When the TMIF00 flag is set to 1, the program stops the count operation, clears the TMIF00 flag, and subtracts 2048 to the current delay_us value. The program will keep on executing the body of the loop. It will only exit the loop once the delay_us value is less than or equal to 2048.

Figure 27. Greater than 2048 us and less than 2049us delay_us() function operation.
Figure 27. Greater than 2048 us and less than 2049us delay_us() function operation.

After the program exits the loop, it will check the remaining value of the delay_us variable again if it is less than 5. I’ve added this operation again since there might be times when the remaining delay_us value after the loop will be less than 5us. For example if we pass a 2050 value, so the remaining delay_us value after the loop will be 2. This will load a negative value to the TDR00 register and will cause a problem.

So if the remaining delay_us value is less than 5, the program will just execute a NOP() instruction and then return. But if it’s greater than 4, the program will calculate the TDR value to be loaded and then do the operation similar to the operation inside the loop except the subtraction part.

After clearing the channel’s interrupt flag, I’ve added 12 NOP() instructions to round off the delay to the nearest whole number. I’ve mentioned earlier that the 31 TDR value doesn’t actually generate a 1us delay but generates around 4.6us. Adding that 12 NOP() instructions rounds off the 4.6us to 5us. After the NOP() instructions, the program exits the function.

For delay values equal or less than 2048, the program will just skip the loop and only execute the part or operation highlighted in blue. I think most of the time, the program will just execute this part since we already have the delay_ms() function to generate millisecond delays. So most of the time, we will be only passing delay values less than 2049 to the delay_us() function.

So here’s the code of the delay_us() function:

void delay_us(uint16_t delay_us)

{

if (delay_us < 5)

{

NOP();

return;

}

else

{

while (delay_us > 2048)

{

TDR00 = 65535; /* 65535 is equal to "(2048 * 32) - 1". */

TS0 |= 0x0001U; /* Operation enable (start) trigger of Channel 0 (TSm0). Operation is enabled (start trigger is generated). */

while (TMIF00 != 1) { } /* Wait until the INTTM00 Interrupt Flag is set. */

TT0 |= 0x0001U; /* Operation stop trigger of Channel 0 (TTm0). Operation is stopped (stop trigger is generated). */

TMIF00 = 0; /* Clear the INTTM00 Interrupt Flag. */

delay_us = delay_us - 2048; /* Subtract 2048 to the current delay_us value. */

}

if (delay_us < 5)

{

NOP();

return;

}

TDR00 = ((delay_us - 4) * 32) - 1;

TS0 |= 0x0001U; /* Operation enable (start) trigger of Channel 0 (TSm0). Operation is enabled (start trigger is generated). */

while (TMIF00 != 1) { } /* Wait until the INTTM00 Interrupt Flag is set. */

TT0 |= 0x0001U; /* Operation stop trigger of Channel 0 (TTm0). Operation is stopped (stop trigger is generated). */

TMIF00 = 0; /* Clear the INTTM00 Interrupt Flag. */

/* Added 12 NOP() to round off the delay. */

NOP();NOP();NOP();NOP();NOP();NOP();

NOP();NOP();NOP();NOP();NOP();NOP();

}

}

And here’s the sample code:

/***********************************************************************************************************************

* DISCLAIMER

* This software is supplied by Renesas Electronics Corporation and is only intended for use with Renesas products.

* No other uses are authorized. This software is owned by Renesas Electronics Corporation and is protected under all

* applicable laws, including copyright laws.

* THIS SOFTWARE IS PROVIDED "AS IS" AND RENESAS MAKES NO WARRANTIES REGARDING THIS SOFTWARE, WHETHER EXPRESS, IMPLIED

* OR STATUTORY, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND

* NON-INFRINGEMENT. ALL SUCH WARRANTIES ARE EXPRESSLY DISCLAIMED.TO THE MAXIMUM EXTENT PERMITTED NOT PROHIBITED BY

* LAW, NEITHER RENESAS ELECTRONICS CORPORATION NOR ANY OF ITS AFFILIATED COMPANIES SHALL BE LIABLE FOR ANY DIRECT,

* INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES FOR ANY REASON RELATED TO THIS SOFTWARE, EVEN IF RENESAS OR

* ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

* Renesas reserves the right, without notice, to make changes to this software and to discontinue the availability

* of this software. By using this software, you agree to the additional terms and conditions found by accessing the

* following link:

* http://www.renesas.com/disclai...

*

* Copyright (C) 2011, 2021 Renesas Electronics Corporation. All rights reserved.

***********************************************************************************************************************/

/***********************************************************************************************************************

* File Name : r_main.c

* Version : CodeGenerator for RL78/G14 V2.05.06.02 [08 Nov 2021]

* Device(s) : R5F104ML

* Tool-Chain : GCCRL78

* Description : This file implements main function.

* Creation Date: 09/11/2022

***********************************************************************************************************************/

/***********************************************************************************************************************

Includes

***********************************************************************************************************************/

#include "r_cg_macrodriver.h"

#include "r_cg_cgc.h"

/* Start user code for include. Do not edit comment generated here */

/* End user code. Do not edit comment generated here */

#include "r_cg_userdefine.h"

/***********************************************************************************************************************

Global variables and functions

***********************************************************************************************************************/

/* Start user code for global. Do not edit comment generated here */

void delay_us_TAU_Init(void);

void delay_us(uint16_t delay_us);

void delay_1us(void);

void delay_2us(void);

void delay_3us(void);

void delay_4us(void);

/* End user code. Do not edit comment generated here */

void R_MAIN_UserInit(void);

/***********************************************************************************************************************

* Function Name: main

* Description : This function implements main function.

* Arguments : None

* Return Value : None

***********************************************************************************************************************/

void main(void)

{

R_MAIN_UserInit();

/* Start user code. Do not edit comment generated here */

delay_us_TAU_Init();

PM5_bit.no2 = 0;

while (1U)

{

P5_bit.no2 = 1;

delay_us(5);

P5_bit.no2 = 0;

delay_us(5);

}

/* End user code. Do not edit comment generated here */

}

/***********************************************************************************************************************

* Function Name: R_MAIN_UserInit

* Description : This function adds user code before implementing main function.

* Arguments : None

* Return Value : None

***********************************************************************************************************************/

void R_MAIN_UserInit(void)

{

/* Start user code. Do not edit comment generated here */

EI();

/* End user code. Do not edit comment generated here */

}

/* Start user code for adding. Do not edit comment generated here */

void delay_us_TAU_Init(void)

{

TAU0EN = 1U; /* Supplies Input Clock */

TPS0 = 0x0000U | /* CKm0 = fCLK */

0x0000U | /* CKm1 = fCLK */

0x0000U | /* CKm2 = fCLK/2^1 */

0x0000U; /* CKm3 = fCLK/2^8 */

/* Stop all channels */

TT0 = 0x0001U | /* Channel 0 (TTm0) operation is stopped (stop trigger is generated) */

0x0002U | /* Channel 1 (TTm1) operation is stopped (stop trigger is generated) */

0x0004U | /* Channel 2 (TTm2) operation is stopped (stop trigger is generated) */

0x0008U | /* Channel 3 (TTm3) operation is stopped (stop trigger is generated) */

0x0200U | /* Channel 1 higher 8 bits (TTHm1) operation is stopped (stop trigger is generated) */

0x0800U; /* Channel 3 higher 8 bits (TTHm3) operation is stopped (stop trigger is generated) */

/* Mask Channel 0 Interrupt */

TMMK00 = 1U; /* Disable INTTM00 Interrupt */

TMIF00 = 0U; /* Clear INTTM00 Interrupt Flag */

/* Channel 0 used as Interval Timer */

TMR00 = 0x0000U | /* Selection of macro clock (fMCK) of Channel 0 (CKSmn1 and CKSmn0 bits). Selected CKm0. */

0x0000U | /* Selection of count clock (CCK) of channel n (CCSmn bit). Selected fMCK specified by CKSmn1 and CKSmn0 bits. */

0x0000U | /* Bit 11 of Channel 0 TMR00 Register. Fixed to 0. */

0x0000U | /* Setting of start trigger or capture trigger of Channel 0 (STSmn2, STSmn1, and STSmn0 bits). Selected "Only software trigger start is valid (other trigger sources are unselected)". */

0x0000U | /* Operation mode of Channel 0 (MDmn3, MDmn2, and MDmn1 bits). Selected Interval Timer Mode. */

0x0000U; /* Setting of starting counting and interrupt (MDmn0 bit). Selected "interrupt is not generated when counting is started". */

TO0 &= ~0x0001U; /* Sets Channel 0 timer output value (TOm0) to 0 or LOW. */

TOE0 &= ~0x0001U; /* Disables Channel 0 timer output (TOEm0). */

}

void delay_us(uint16_t delay_us)

{

if (delay_us < 5)

{

NOP();

return;

}

else

{

while (delay_us > 2048)

{

TDR00 = 65535; /* 65535 is equal to "(2048 * 32) - 1". */

TS0 |= 0x0001U; /* Operation enable (start) trigger of Channel 0 (TSm0). Operation is enabled (start trigger is generated). */

while (TMIF00 != 1) { } /* Wait until the INTTM00 Interrupt Flag is set. */

TT0 |= 0x0001U; /* Operation stop trigger of Channel 0 (TTm0). Operation is stopped (stop trigger is generated). */

TMIF00 = 0; /* Clear the INTTM00 Interrupt Flag. */

delay_us = delay_us - 2048; /* Subtract 2048 to the current delay_us value. */

}

if (delay_us < 5)

{

NOP();

return;

}

TDR00 = ((delay_us - 4) * 32) - 1;

TS0 |= 0x0001U; /* Operation enable (start) trigger of Channel 0 (TSm0). Operation is enabled (start trigger is generated). */

while (TMIF00 != 1) { } /* Wait until the INTTM00 Interrupt Flag is set. */

TT0 |= 0x0001U; /* Operation stop trigger of Channel 0 (TTm0). Operation is stopped (stop trigger is generated). */

TMIF00 = 0; /* Clear the INTTM00 Interrupt Flag. */

/* Added 12 NOP() to round off the delay. */

NOP();NOP();NOP();NOP();NOP();NOP();

NOP();NOP();NOP();NOP();NOP();NOP();

}

}

void delay_1us(void)

{

NOP();

NOP();

NOP();

NOP();

NOP();

NOP();

NOP();

NOP();

NOP();

NOP();

}

void delay_2us(void)

{

uint8_t i;

for(i=0;i<2;i++)

{

NOP();

NOP();

NOP();

NOP();

NOP();

}

}

void delay_3us(void)

{

uint8_t i;

for(i=0;i<5;i++)

{

NOP();

NOP();

}

NOP();

NOP();

}

void delay_4us(void)

{

uint8_t i;

for(i=0;i<7;i++)

{

NOP();

NOP();

NOP();

}

NOP();

NOP();

NOP();

}

/* End user code. Do not edit comment generated here */

To test the sample code, create a project and use the code generator to generate the code for initializing the hardware or system. Fix settings in the Pin assignment tab, disable the Watchdog timer, and enable the fSUB clock. We will combine the delay_us() function later with the delay_ms() function so I think we should make it a habit to always enable the fSUB clock. Generate the code and don’t forget to disable Power Target From The Emulator in the Debug Configurations.

Open r_main.c file and insert void delay_us_TAU_Init(void);, void delay_us(uint16_t delay_us);, void delay_1us(void);, void delay_2us(void);, void delay_3us(void);, void delay_4us(void); between the comments generated by the code generator under Global variables and functions (see line 42 to line 47). As you can see, I’ve added four functions: delay_1us(), delay_2us(), delay_3us(), and delay_4us(). Since the delay_us() function can’t generate delays less than 5us, these functions will generate the 1us to 4us delays. But please take note that these functions will only work properly at 32MHz CPU clock.

Next, copy and paste the code of the delay_us_TAU_Init(), delay_us(), delay_1us(), delay_2us(), delay_3us(), and delay_4us() functions between the comments generated by the code generator at the bottom part of the r_main.c file (see line 89 to line 210). Inside the main() function, you can toggle a pin and call the delay_us() function to generate delays (see line 57 to line 72). Build the project and upload the code to the RL78/G14 FPB.

P52 output waveform
Figure 28. P52 Output Waveform (5us delay).
P52 output waveform 40us delay
Figure 29. P52 Output Waveform (40us delay).
P52 output waveform 100us delay
Figure 30. P52 Output Waveform (100us delay).
P52 output waveform 1000us delay
Figure 31. P52 Output Waveform (1000us delay).
P52 output waveform 2000us
Figure 32. P52 Output Waveform (2000us delay).
P52 output waveform 4200us delay
Figure 33. P52 Output Waveform (4200us delay).
P52 output waveform 1us delay
Figure 34. P52 Output Waveform (1us delay).

Delay Library

So we’re done with the delay_us() function. I’m sure you don’t want to repeat all the steps above every time you want to use the delay_us() function and even the delay_ms() function. So we are going to create a delay library. We’re going to create a header and a C source file and put all the delay functions that we’ve created so that we can just import the library to our projects.

Adding new header source file
Figure 35. Adding new Header and Source Files in e2 studio.

We can create a new header and source file by right clicking the src folder in our project, hover New, and click Header File and Source File (see figure 35). But we’ll just use Notepad to create the files so that I can show you how to import them in a new project.

So open Windows Notepad and then include the header files iodefine.h, iodefine_ext.h, and r_cg_macrodriver.h. Then declare all the functions in our delay_ms() and delay_us() functions. Check figure 36.

Delay header file
Figure 36. Creating delay library header file.

Save the file as delay.h inside a folder named delay.

Delay header file RL78
Delay header file RL78
Figure 37. delay.h file.

So we now have the header file of the delay library. Next, let’s open a new Notepad file again to create the source file. Inside our source file, let’s include the delay.h file and copy the all delay functions definitions. See figure 38.

Delay source file RL78
Figure 38. Creating delay library source file.

Then save the file as delay.c in the same delay folder where delay.h is.

Delay source file RL78
Delay source file RL78
Figure 39. delay.c file.

Now, the header and source files are ready. The next thing that we’re going to do is zip the files so that we can import it in e2 studio as an Archive File.

Zipping delay header source files
Zipped delay library
Figure 40. Zipping the delay header and source files to create an Archive File.
Project Files

Now, let’s try importing this to a new project. Open e2 studio and create a new RL78 project. Use the code generator again to generate the code for initializing the hardware or system. Fix settings in the Pin assignment tab, disable the Watchdog timer, and enable the fSUB clock. Click the Generate Code button and then disable Power Target From The Emulator in the Debug Configurations.

Including delay library
Figure 41. Including delay header file in a project.

Open r_main.c file and include the delay header file as shown in figure 41.

Including delay library RL78
Including delay library RL78
Including delay library RL78
Including delay library RL78
Figure 42. Importing the delay archive file.
Figure 42. Importing the delay archive file.

Next, right click the src folder and click Import. Select Archive File and click the Next button. Click the Browse button and open the delay zipped file. Then click the Finish button. See figure 42.

Including delay library RL78
Figure 43. delay library imported.

So we’re done importing the delay library. Try testing it using the code shown in figure 44. Build the project and upload it to the RL78/G14 FPB.

Including delay library RL78
Figure 44. Testing the delay library.

Here’s the output waveform of pin P52 using the code above:

P52 output waveform 30ms delay
P52 output waveform 1000us delay library
Figure 45. P52 Output Waveform using delay_ms(30) and delay_us(1000) functions.

So far our delay library works properly. But, please take note that the delay functions are tested by toggling a single pin (e.g. P52_bit.no2 = 1;). Setting a pin HIGH takes around 309ns (LOW is longer, around 409ns). The time it takes to toggle a port using an 8-bit memory manipulation instruction is shorter. So for example if you toggle a pin using an 8-bit instruction (e.g. P5 = 0x04;) and use the delay function, especially delay_us() function, the delay will not be accurate.

Finally, we’re done! I hope you’ve enjoyed this tutorial. I feel like this tutorial series is starting to get tedious so in our next tutorial we will use the delay library and our knowledge in GPIOs to interface a device usually used by hobbyists with the RL78/G14 MCU. If you have questions about this tutorial, please leave it in the comments section below or you can message us. See you in the next tutorial! Merry Christmas! :)

Make Bread with our CircuitBread Toaster!

Get the latest tools and tutorials, fresh from the toaster.

What are you looking for?