Create Your Own Microsecond Delay Function Using Timer Array Unit (TAU) | Renesas RL78 - 7
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)
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:
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 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)
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)
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.
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.
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.
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)
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.
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
- As mentioned earlier, we have four operation clock sources:
CKm0
,CKm1
,CKm2
, andCKm3
. The speed or frequency of these clock sources can be adjusted based on theTPSm
bits setting. Now, to select which of these clock sources will be supplied to aTAU
channel, we can use theCKSmn1
andCKSmn0
bits. For example, we want to usechannel 0
ofTAU0
and we want to selectCKm0
, we can just setCKSm01
andCKSm00
to00
.
- CCSmn Bit
- The
CCSmn
bit decides if the channel’s input is from the operation clock source or from theTImn
pin. Since we’re using the interval timer function of theTAU
, we will set theCCSmn
bit to0
.
- TMRmn Bit 11 (MASTERmn/SPLITmn/Fixed to 0)
- The
bit 11
of theTMRmn
register depends on the channel used. In channel 2, bit 11 isMASTERmn
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 isSPLITmn
. IfSPLITmn
is0
, the channel operates as a 16-bit timer. Setting theSPLITmn
bit to0
will also make the channel operate independently or as a slave when in simultaneous channel operation function. Setting theSPLITmn
bit to1
will make it operate as an 8-bit timer.
For channel 0,bit 11
of itsTMRmn
register is fixed to0
. 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 theTAU
. So in this tutorial, we would really want to setbit 11
of theTMR00
register to0
.
- STSmn2, STSmn1, and STSmn0 Bits
- The
STSmn2
,STSmn1
, andSTSmn0
bits of theTMRmn
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 theTImn
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 to000
later.
- CISmn1 and CISmn0 Bits
- The
CISmn1
andCISmn0
bits set the valid edge of theTImn
pin. However, since we’re not going to use theTImn
pin, we can just set this to00
or its default value.
- MDmn3, MDmn2, and MDmn1 Bits
- The
MDmn3
,MDmn2
, andMDmn1
bits of theTMRmn
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 to000
later.
- MDmn0 Bit
- The
MDmn0
bit of theTMRmn
register when the channel is in interval timer mode disables or enables timer interrupt generation when counting is started. If theMDmn0
bit is set to0
, no interrupt will be generated when counting is starter. If it is set to1
, an interrupt will be generated when counting starts.
Timer Output Enable Register m (TOEm)
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).
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)
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
.
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.
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
.
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.
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()
- TAU0EN Bit
As mentioned earlier, the first thing that we need to do to initialize theTAU
is to set theTAUmEN
bit in thePER0
register. This will enable supply of input clock to theTAU
and reading or writing to its SFRs. We are going to useTAU0
so we need to setTAU0EN
bit to1
. - TPS0 Register
After settingTAU0EN
bit to1
, we set the prescaler of theTAU0
operation clock sources (CK00 - CK03) by setting the bits of theTPS0
register. Please check the discussion earlier about theTPSm
register to review which bits are forCK00
,CK01
,CK02
, andCK03
.
TheTPSm
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 clockfCLK
, so I’ve set all theTPS0
bits to0
. - TT0 Register
After setting theTPSm
register, we set all the bits of theTT0
register to1
(except for the bits that are fixed to 0) just to make sure that all channels of theTAU0
are not operating. Again, we need to use a 16-bit memory manipulation instruction to set theTT0
register but as shown in figure 24, we can set the bits individually by using a bit-wise operator OR. - 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 useChannel 0
ofTAU0
so we setTMMK00
to1
andTMIF00
to0
.
- TMR00 Register
After disablingTAU0 Channel 0
interrupt, we’re going to set theTMR00
register. If you forgot about the function of theTMRmn
bits, please review our discussion earlier about theTMRmn
register.- The
CKSmn1
andCKSmn0
bits of theTMRm0
selects the operation clock source of theTAU0
channel. We’re usingChannel 0
and want to selectCK00
, so we set theCKS001
andCKS000
bits to00
. We set theCK00
prescaler to0000
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 clockfMCK
. So we should setCCS00
to0
. - For
bit 11
, it’s fixed to0
so we should just leave it like that. - The
STSmn2
,STSmn1
, andSTSmn0
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) to000
. - The
CISmn1
andCISmn0
bits are for theTImn
pin which we will not use, so I think it’s safe to just leave them to00
. - The
MDmn3
,MDmn2
, andMDmn1
bits select the operation mode of the channel. We want to select interval timer mode so we should set (MD003, MD002, MD001) to000
. - The
MDmn0
bit is for generating interrupt when the count starts. But we’re going to setMD000
to0
because we don’t wantChannel 0
to generate an interrupt.
- The
- TOm and TOEm Registers
We’re not going to useChannel 0
timer output so first we’re going to set its output value to0
orLOW
by setting theTO00
bit of theTO0
register to0
. We can set theTO0
register with a 16-bit memory manipulation instruction. Next, we’re going to disableChannel 0
timer output by setting theTOE00
bit of theTOE0
register to0
.
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)
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.
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
.
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.
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.
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.
Save the file as delay.h
inside a folder named delay
.
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.
Then save the file as delay.c
in the same delay folder where delay.h
is.
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.
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
.
Open r_main.c
file and include the delay header file as shown in figure 41.
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.
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.
Here’s the output waveform of pin P52 using the code above:
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! :)
Get the latest tools and tutorials, fresh from the toaster.