FB pixel

Renesas RA - 20. Design an RTD-based Thermometer using ADC and Op-AmpsNew

Published


Hello again! Today we are finishing consideration of the analog modules of the RA2A1 family. The only analog module we still haven’t discovered is the in-built operational amplifier (op-amp, or OA).

Operational amplifiers are very well-known and widely used in analog and digital circuits. There are tons of tutorials about them on the internet which describe, in detail, their principle of operation, basic and more complex circuits with them. For example, you can refer to this series of op-amp tutorials: https://www.circuitbread.com/tutorials/series/operational-amplifiers-op-amps.

Actually, a lot of microcontrollers have in-built operational amplifiers, so this is not something unique in the RA2A1 series. And these in-built op-amps are actually the same ones as the standalone chips, they only share the pins with the MCU IOs, and may have some internal connections controlled by the MCU registers.

In our previous tutorial, we have created a thermometer based on a thermocouple. This time we will also create a thermometer but this time based on another industrial sensor - resistance temperature detector, or RTD. Its principle of operation is simpler than a thermocouple and is based on the dependence of the resistance of the metal from temperature. The most widespread sensors are made from platinum.

Someone may exclaim, “Wow! A sensor made of real platinum”. Don’t deceive yourself. The amount of platinum in this sensor is several milligrams and its price of $1-3 USD speaks for itself.

Among the platinum sensors, the most widespread are so-called Pt100 sensors which have the basic resistance at 0°C of 100 Ohm.

The RTD probes usually look like a metal sleeve with two, three, or four wires (Figure 1). The number of wires depends on the connection schematic, which we will consider later.

Figure 1 - Appearance of the RTD sensors
Figure 1 - Appearance of the RTD sensors

There also can be sensors like in Figure 2.

Figure 2 - Yet another appearance of the RTD sensor
Figure 2 - Yet another appearance of the RTD sensor

Don’t be deceived (like I was) with the photo thinking that this sensor is similar size to a LED or something. In fact, the size of the sensor is just 2.0x2.3x1.1 mm, so it’s really tiny with very thin and fragile wires.

Generally, in this tutorial, we will not consider the principle of the RTD operation deeply, so you can read about it on Wikipedia.

As usual, before proceeding, let’s consider the new MCU module that will be used in this project.

Introduction to the Operational Amplifier Module (OPAMP)

Well, it’s really a regular operations amplifier, and everything you can read about it can be applied to the op-amp module. Let’s consider the integration of this module into the MCU.

There are up to three operational amplifiers in RA2A1 series which are not equal, meaning they have different internal connections. For example, OPAMP0 and OPAMP1 outputs can be used as input signals for the low-power analog comparator and the 24 bit SD ADC, and all three of them can be used as inputs for the 16-bit SAR ADC. Also, the DAC8 and DAC12 modules can be internally connected to the positive inputs of the op-amps to provide the output buffer and increase the load capability of the DACs.

Each of the op-amps can operate in the high-speed, medium-speed, and low-speed modes which differ the consumed current and the response speed (I guess, the dependency is obvious enough that I don’t need to explain it).

Operational amplifiers can be started and stopped by the software or by the modules which use them.

All op-amps have switches to select the connection of the inputs and outputs. They can be connected internally to the ADC, AC or DAC modules, or directly to the IO pins of the MCU. In this case, these pins apparently can’t be used as digital IOs.

Unlike other MCUs which also have in-built op-amps, in the RA2A1, there are no internal resistors which allow to set the gain of the amplifiers, so you need to connect these resistors externally.

And that’s actually everything about the OPAMP module. One may ask “Why do we need this op-amp at all?”. The answer will be found in the next section in which we will consider the schematic diagram.

Schematic Diagram of the Thermometer

Before we proceed with the schematics diagram of the device, let’s first consider the schematic diagram that illustrates the principle of operation of RTD measurement (Figure 3).

Figure 3 - General schematic diagram of the RTD thermometer
Figure 3 - General schematic diagram of the RTD thermometer

There are actually two widely used schematics which are used with the RTD. The first one is based on the Wheatstone bridge, but its main application is analog circuits. The second one is based on measuring the voltage drop on the RTD which is generated by the current source. In Figure 3 the operational amplifier OP1 and resistors R1-R3 form the current source. Let’s consider how it works. As you can see, the circuit looks very similar to an inverting amplifier with an offset but the load (RTD PTC1) is connected into the negative feedback loop. What does this bring?

Resistors R1 and R2 represent the voltage divider which sets the reference voltage of the current source. As the supply voltage VCC is about 3.3 V. I selected the nominal values of these resistors so this reference voltage is about 1 V. Really,.

Now, the output of the op-amp produces some voltage and apparently some current I. If we consider that the input current of the ADC is 0, and the input current of the op-amp is 0 as well, we may see that this current I flows through the RTD PTC1 and resistor R3. We don’t know the output voltage but we know that the voltage on both inputs of the op-amp is the same when the negative feedback loop is applied. Which means that the or in general form

.

The voltage drop on resistor R3 (which uses the voltage between the negative op-amp input and ground, and is V-) can be thus easily calculated by the Ohm law:

From the last expression it is easy to find the current I:

.

This means that the current that flows through the RTD is unambiguously defined with the supply voltage and three resistances R1, R2, and R3 and does not depend on the resistance of the RTD itself.

So, now we have a stable current that runs through the RTD, and now it’s easy to calculate the voltage drop on it with the ADC or voltmeter:

.

If the ADC is supplied with the same voltage source (like in our MCU) and has the maximum of ADC_MAX then the ADC reading will be (regular formula of reading an ADC):

,

or

From this expression it’s easy to find the RTD resistance:

Thus, the RTD resistance which we can calculate based on the ADC value depends on resistors R1, R2, and R3 only and doesn’t depend on the supply voltage which is really good.

The thing that is not good is that you need to have the resistances of these resistors to be very highly accurate. You can’t just put the resistors of 10 kOhm, 4.3 kOhm, and 1 kOhm and put these values into the equation. You need to measure the value of each resistor manually with some ohmmeter and put the actual value. This is needed because the changes of the RTD resistance are very insignificant in comparison to the R1-R3 resistances, so even a small deviation will cause a great error (when I put the nominal values into the equation instead of actual values, I got an error of 10 °C !).

Now let’s talk about the two-, three- and four-wired connection of the RTD. The thing is that when we use the RTD in real conditions, the object can be located several meters (or even tens of meters) from the MCU. In this case the resistance of the wires can’t be neglected, and it can cause another additional error. Unfortunately, we can’t measure the resistance directly on the RTD, so we need to consider the voltage drop on the connection wires somehow.

The two-wired connection is the simplest but has the greatest error because the voltage drop on the wires can’t be compensated for at all (unless you preliminarily measure this voltage and consider it somehow).

In the three-wired connection (see Figure 3) There are two wires that are connected to the same pin of the RTD. Through one of them, the supply voltage is applied to the RTD, and the other is connected to the measurement unit. What does it give us? As the input resistance of the measurement unit is usually very high (much higher than the RTD resistance) the current that runs from the RTD to the measurement unit is very small and can be neglected, and thus the resistance of one of the supply wires is excluded from consideration. In this approach, the resistance of another wire is still left but the error is half that of the 2-wired circuit.

And finally, with the four-wired circuit there are two pairs of wires that are connected to the RTD pins - one wire of each pair is used for supplying the sensor, and another - for reading the voltage drop on it. In this case the resistance of the wires is totally compensated for but this connection is the most expensive as it uses twice the wires than required.

So the three-wired circuit is the most widespread as a trade-off between the accuracy and the cost.

Now, let’s consider the actual schematic diagram we will use in this tutorial (Figure 4).

Figure 4 - Schematic diagram of the device
Figure 4 - Schematic diagram of the device

Here, I want to show you how to use the ADC16 module in the differential mode so we will use it instead of SDADC which would be more logical to use because of its higher resolution and thus higher accuracy (you can try to do this by yourself based on the previous tutorial). In the ADC16 module, only the first eight channels can be used as the differential inputs, that’s why we are connecting the RTD sensor to the pins P014 and P013 which are merged with the AIN04 and AIN05 inputs, respectively (Figure 4).

Also, we will use the OPAMP0 module whose pins are merged with the pins P500 (V+), P501 (V-), and P502 (Vout). The connection of the resistors and RTD is the same as in Figure 3. Also, don’t forget to short pins AVCC0 with VREFH0, and AVSS0 with VREFL0 to provide the reference voltage to the ADC16 module.

Project Creation and Configuration

Now we have enough information to start with the project. So let’s open e2 studio and create a regular project based on the C language using the “Bare Metal - Minimal” template.

Don’t forget to switch to the “Clock” tab first to change the PCLKD clock frequency from 48 MHz to 24 MHz (the same as we did in the previous two tutorials) to avoid the problems with the ADC16 clocking (Figure 5).

Figure 5 - Changing the PCLKD frequency
Figure 5 - Changing the PCLKD frequency

And now, let’s switch to the “Pins” tab and configure the ADC inputs. So, find and expand the “Analog:ADC” list and select the “ADC0” line. What we need to do is disable channel AN006 and then enable channels AN04 and AN05 (Figure 6).

Figure 6 - ADC0 module final pin configuration
Figure 6 - ADC0 module final pin configuration

Leave all other ADC channels and auxiliary pins unchanged.

Now, let’s configure the op-amp pins. Find and expand the “Analog:OPAMP” list and select the “OPAMP0” line. Change the “Operation Mode” field from “Disabled” to “Enabled” and make sure that AMP+, AMP-, and AMPO fields become P500, P501, and P502, respectively (Figure 7).

Figure 7 - OPAMP0 module pins configuration
Figure 7 - OPAMP0 module pins configuration

And that’s all about the pin configuration. So we can switch to the “Stacks” tab and add the required stacks. Let’s consider which ones we will need this time. Obviously, we will need the ADC16 module. Also, we will use some timers to trigger the conversion start of the ADCs. To enable the hardware triggering, we will need the ELC stack. Finally, we will need the op-amp stack. And that’s it! Let’s first start with the modules which we are already familiar with.

We have already used the ELC module several times, so you should already know how to add it. And if you don’t, please refer to tutorial 16. I will not add any pictures about ELC as it doesn’t have any configurable parameters, so please don’t miss adding it!

Next, let’s add the timer. We will use the same “Timer, Low-power (r_agt)” as in the previous tutorial and configure it in the same way according to Figure 8.

Figure 8 - Configuration of the AGT stack
Figure 8 - Configuration of the AGT stack

The AGT timer was discussed in detail in Tutorial 15, so please refer to it for more information. Here we set the period of the timer as 1 second. Such a period can’t be reached using the PCLKB source, which is 24 MHz (see Figure 5) because the AGT is just 16 bit. But it can be easily reached with the LOCO source, which is just 32768 Hz, so we need to change the “Count Source” field from “PCLKB” to “LOCO.” Also, we need to enable the timer underflow interrupt by changing the “Underflow Interrupt Priority” from “Disabled” to “Priority 2” (or any other priority you wish). This interrupt will be used only by the ELC module to start the ADC16 conversion, so we don’t need to declare any callback function and leave the “Callback” field as “NULL.”

Now, let’s add the “ADC (r_adc)” stack, the same as we did in tutorial 18. Its configuration is quite similar. The only difference is that instead of Channel 0, we will enable Channel 4 (Figure 9 - 11).

Figure 9 - Configuration of the ADC stack (part 1)
Figure 9 - Configuration of the ADC stack (part 1)
Figure 10 - Configuration of the ADC stack (part 2)
Figure 10 - Configuration of the ADC stack (part 2)
Figure 11 - Configuration of the ADC stack (part 3)
Figure 11 - Configuration of the ADC stack (part 3)

So we need to change the “Mode” from “Single Scan” to “Group Scan” to be able to use the hardware trigger of the conversion starting from the AGT underflow signal. Then in the “Channel Scan Mask” list, we need to select only the “Channel 4” channel and deselect the rest. Why is it so, if we connected the RTD to inputs AIN04 and AIN05? The thing is that if the channels are used in differential mode, only the even number should be triggered, and the result also should be read only from the even channel. As I already mentioned, only the first eight channels can be used in the differential mode, and they form four differential inputs - AIN00-AIN01, AIN02-AIN03, AIN04-AIN05, and AIN06-AIN07.

As the voltage drop on the RTD will not be quite low (you can make sure that with the given resistors it will be about 0.1 V) we will use the averaging feature. So we need to select “Channel 4” in the “Addition/Averaging Mask” list (Figure 10) (again, note that we use only the even channel number). Also, we need to change the “Add/Average Count” field from “Disabled” to “Average sixteen samples” to achieve the best averaging results.

The rest of the changes are the same as in the previous tutorial. First, we need to change the “Normal/Group A Trigger” and “Group B Trigger” from “Disabled” to “AGT0 INT (AGT Interrupt)”. When the conversion is completed, we will generate an interrupt and read the conversion result afterward. To do this, we need to enable the “Scan End Interrupt Priority” and “Scan End Group B Interrupt Priority” (the latter is not needed in this project, but if we don’t enable it, the ADC stack shows the error). Also, we need to declare the callback function, so we change the “Callback” field from “NULL” to some name, for example, “adc_callback.” These are all the changes in the ADC stack we need to do.

Some attentive readers may ask “Hey, but where do we configure that the ADC will work in differential mode?”. Good question. And the answer is: in the program code. For some reason the FSP doesn’t support this functionality, so we will need to manipulate the ADC registers directly to enable it. But don’t be afraid, it’s done quite simply.

Finally, let’s add the “Analog” - “Operational Amplifier” stack (Figure 12).

Figure 12 - Adding the Operational Amplifier stack
Figure 12 - Adding the Operational Amplifier stack

Then we should configure this stack according to Figure 13.

Figure 13 - Configuration of the Operational Amplifier stack
Figure 13 - Configuration of the Operational Amplifier stack

The Operational Amplifier stack has the following parameters:

  • “Name” is (as usual) the name of this stack in the program. We can leave it unchanged.
  • “AGT Start Trigger Configuration (N/A unless AGT Start Trigger is Selected for the Channel)” is used to select which AGT channel event triggers which op-amp channel. The AGT compare match event only starts the op-amp channel if the AGT Start trigger is selected in the Trigger configuration for the channel. This option is used if we want to switch on/off the op-amp during the operation. As we will keep the op-amp on all the time, we can leave this parameter unchanged, it’s not applicable anyway until we configure this functionality in the “Trigger Channel x” fields.
  • “Power Mode” selects one of the power modes: “Low Speed”, “Medium Speed”, and “High Speed”. These modes differ with the power consumption and the signal propagation delay. As we’re not in need to save power, let’s select the “High Speed” mode.
  • “Trigger Channel n” selects the event triggers to start or stop op-amp channel n. If the event trigger is selected for start, the start() API enables the event trigger for this channel. If the event trigger is selected for stop, the stop() API disables the event trigger for this channel. As I said before, we are not going to turn off the op-amp, so we leave this parameter with the default value “Software Start Software Stop”.
  • “OPAMP AMPnxx” sets the connection of the output of the OPAMP0 (AMP0OS), and positive (AMPnPS) and negative (AMPnNs) inputs of all the op-amps. All the outputs of the operational amplifiers are internally connected to the corresponding pins, so we don’t need to set the AMP0OS field, it’s used only for internal connections. But we need to set the connections of the positive pin to P500 (“Connect OPAMP0 Plus Input to (P500) AMP0+ Pin”) and the negative pin to P501 (“Connect OPAMP0 Minus Input to (P501) AMP0- Pin”).

Also make sure that the first set of “AMP+”, “AMP-” and “AMPO” fields are configured as “P500”, “P501”, and “P502”.

And that’s all about the Operational Amplifier stack configuration. For more information, please refer to the corresponding page of the FSP documentation. As usual, before proceeding to the programming code, let’s check a few things first. The whole stack configuration should look like in Figure 14.

Figure 14 - Stacks configuration
Figure 14 - Stacks configuration

Also, let’s switch to the “Event Links” tab and ensure that the required links are established (Figure 15).

Figure 15 - Event links allocation
Figure 15 - Event links allocation

As you can see, both ADC16 groups are linked to the AGT0 INT (AGT interrupt) event, so everything is configured correctly.

And now that’s it. So we can press the button “Generate Project Content” and proceed to the program code.

Program Code of the Project

Before we proceed with creating our own code, we need to add the files for supporting the RTT. I have explained in detail how to do this in Tutorial 14, so please refer to it. Also, you can just copy and paste the RTT-related files from the project of Tutorial 14 (if you have them) into the current project.

Now, let’s open the “hal_entry.cpp” file and write the following code in it.

#include "hal_data.h"

#include "SEGGER_RTT.h"

#include <math.h>

FSP_CPP_HEADER

void R_BSP_WarmStart(bsp_warm_start_event_t event);

FSP_CPP_FOOTER

#define R1 9.77f //Actual value of R1 resistor in kOhm

#define R2 4.17f //Actual value of R2 resistor in kOhm

#define R3 960.0f //Actual value of R3 resistor in Ohm

volatile uint8_t adc_conversion_complete;//Flag indicating that the ADC conversion is complete

void adc_callback (adc_callback_args_t *p_args) //ADC interrupt callback

{

if (p_args->event == ADC_EVENT_SCAN_COMPLETE)//If interrupt was caused by the Scan complete event

{

adc_conversion_complete = 1; //Set the flag adc_conversion_complete

}

}

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

* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function

* is called by main() when no RTOS is used.

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

void hal_entry(void)

{

R_ELC_Open(&g_elc_ctrl, &g_elc_cfg); //Open the ELC stack

R_ELC_Enable(&g_elc_ctrl); //Enable ELC

R_AGT_Open(&g_timer0_ctrl, &g_timer0_cfg); //Open the AGT stack

R_AGT_Start(&g_timer0_ctrl); //Start AGT counting

R_ADC_Open(&g_adc0_ctrl, &g_adc0_cfg); //Open the ADC stack

R_ADC_ScanCfg(&g_adc0_ctrl, &g_adc0_channel_cfg);//Configure the ADC channels

g_adc0_ctrl.p_reg->ADANIM_b.ANIM2 = 1; //Configure Channels 4 and 5 as differential

R_ADC_ScanStart(&g_adc0_ctrl);//Start scanning waiting for the selected trigger event

R_OPAMP_Open(&g_opamp0_ctrl, &g_opamp0_cfg); //Open the OPAMP stack

R_OPAMP_Start(&g_opamp0_ctrl, 1); //Start the operational amplifier OPAMP0

opamp_info_t info; //Structure to read the op-amp parameters

R_OPAMP_InfoGet(&g_opamp0_ctrl, &info); //Get the information about the op-amp

R_BSP_SoftwareDelay(info.min_stabilization_wait_us, BSP_DELAY_UNITS_MICROSECONDS); // Wait for the OPAMP to stabilize.

while (1)

{

if (adc_conversion_complete) //If SDADC conversion is completed

{

int16_t adc_reading; //Variable for reading the SDADC conversion result

float resistance; //Resistance of the sensor

float temperature; //Calculated temperature

int32_t temp; //Auxiliary variable

R_ADC_Read(&g_adc0_ctrl, ADC_CHANNEL_4, &adc_reading); //Read the ADC value from the channel 4

resistance = (adc_reading * R3 * (R1 + R2) / R2) / 32768; //Calculate the resistance

temperature = (resistance/100 - 1)/0.00385f - 0.19f; //Calculate the temperature

temp = lroundf(temperature * 10); //Integer value of the temperature multiplied by 10

SEGGER_RTT_printf(0, "t = %d.%u C\r\n", temp / 10, temp % 10); //Sending the temperature value to the RTT

adc_conversion_complete = 0; //Clear the sdadc_conversion_complete flag

}

}

#if BSP_TZ_SECURE_BUILD

/* Enter non-secure code */

R_BSP_NonSecureEnter();

#endif

}

This program is even shorter than the previous one. Moreover, it doesn’t consist of huge calculations of the temperature like in tutorial 19. So, let’s start with its consideration.

In lines 1-3, we include the required header files. File “SEGGER_RTT.h” is needed to use the Segger RTT interface. We have already used it several times in the previous tutorials. File “math.h” is necessary to use the floating number rounding functions.

In lines 9-12, we define the macros R1-R3 which represent the resistances of the corresponding resistors measured with the ohmmeter. As you can see, the actual values differ significantly from the nominal ones, so don’t skip this step if you want to get reliable results. The value of resistors R1 and R2 can be both in kOhms or in Ohms, but the value of R3 should be in Ohms to meet the correct measurement units.

In line 12, we define a single global variable adc_conversion_complete, which is the flag that indicates that the ADC16 conversion is completed.

In lines 14-20, there is an ADC16 callback function whose name must correspond to the ADC16 stack configuration (Figure 11). The ADC16 interrupt can be caused by the Group A scan complete event or Group B scan complete event (we do not need the latter one). In line 16, we check if the event that caused the interrupt was ADC_EVENT_SCAN_COMPLETE. If so, we set the adc_conversion_complete flag (line 18), which also will be processed further in the program.

In lines 26-66, the program’s main function is hal_entry.

In lines 28-29, we open and enable the ELC stack, respectively. In line 31, we open the AGT stack, and in line 32, we start the AGT counting. Underflow of the AGT counter will trigger the ADC16 conversion, as you remember, I hope.

In lines 34-37, we initialize the ADC16 stack in the same way as we did in the previous tutorial, with one small exception. First, we open the ADC16 stack (line 34), then configure and enable the channels (line 35).

In line 36, we do this thing that I announced earlier - configure the pair of channels AIN04-AIN05 as a single differential channel. To implement this we need to set the corresponding bits of the ADANIM register of the ADC0 module. This register consists of four bits, each of which enables (when set to 1) or disables (when cleared to 0) the differential ADC channel. Bit ANIM0 is responsible for the channels AIN00-AIN01, bit ANIM1 is responsible for channels AIN02-AIN03, bit ANIM2 is responsible for channels AIN04-AIN05, and bit ANIM3 is responsible for channels AIN06-AIN07. As we’re using the pair AIN04-AIN05, we need to set bit ANIM2 as 1, which we do in line 36. We haven’t worked with the registers directly for a while, so I hope you still remember that in FSP each register have two instances - one is a regular register name (like ADANIM) which allows to assign some value to it directly, and another with the “_b” suffix (like ADANIM_b) which simplifies the manipulation with the bits of the register.

Finally, we need to start the ADC scanning waiting for the hardware trigger from the ELC module (line 37).

In lines 39-43 we initialize the op-amp module. Its initialization is similar to the DAC module. First, we as usual open the op-amp stack (line 39), then we start one or several of the available op-amps (line 40). We need to start only OPAMP0, so the second argument of the R_OPAMP_Start function is 1. This second argument is the op-amp channel mask. Bit #0 enables OPAMP0, bit #1 enables OPAMP1, and bit #2 enables OPAMP2.

Then we need to wait some time before the op-amp parameters stabilize. This time can be obtained with the R_OPAMP_InfoGet function (line 42). The second argument of this function is the pointer to the structure of the opamp_info_t type. The variable info of this type is declared in line 41. Actually, this structure has only one field - min_stabilization_wait_us which consists of the time in microseconds which is needed for the op-amp to stabilize. Once we know this value, we perform the corresponding delay (line 43).

In lines 45-60, the main loop is located, inside which we’re waiting for the adc_conversion_complete (line 47) to be set.

Before proceeding with the code, I need to explain how the temperature measurement with the RTD is done. You have probably already read about this at the link I provided you at the beginning of this tutorial, but if not, I will briefly explain it here so you can understand the code.

Actually unlike the thermocouple, this calculation is quite straightforward. First, we obtain the ADC reading, then we calculate the resistance of the RTD using the formula I provided above. Then we need to convert this resistance into the temperature. There is an unambiguous dependency between the temperature and the resistance. For the platinum RTD this dependency is relatively linear in the range from 0 to 100 °C. To increase the accuracy, the equation can be more complex, and contain the quadratic, cubic or polynome members. In our case, as the accuracy is not very high initially, we will use a linear equation. All these dependencies for RTD can be found in the same site as we used for the thermocouples. I’ll quote from it:

“As an example of a simple equation you might use, for the case of measurements between 0°C and 100°C, you could use a linear approximation as,

T = (R/R0 – 1) / α where R0 = 100, and α = 0.00385

This equation fits a line through the points at 0°C and 100°C (and so has zero error at those temperatures), and has a maximum error in the middle of the range of 0.38°C. The average error over the interval can be minimized by shifting the equation a little, as,

T = (R/R0 – 1)/α - 0.19

to provide a maximum error over the interval of ±0.19°C.”

In lines 49-52, we declare some local variables:

  • adc_reading is the raw value measured by the ADC. In differential mode the range of the values can be from 0 to 32767 for positive voltage (the same as for the single-ended channel) and from 0 to -32768 for negative voltage. So anyway the resolution is 15 bits.
  • resistance is the resistance of the RTD in Ohm.
  • temperature is the calculated temperature in °C.
  • temp is the auxiliary value which is used to transmit the temperature over the RTT which doesn’t support floating point numbers.

In line 53, we read the ADC16 conversion result of channel 4 into the adc_reading variable using the function R_ADC_Read. Again, for the differential ADC channels we read only the even channel value, as the odd channel reading will remain 0.

In line 54, we calculate the resistance of the RTD using a formula I provided in the beginning of this tutorial: Rt = ADC* (R1 + R2) * R3 / (R2 * ADC_MAX). Here R1, R2, and R3 are the macros we have defined in lines 9-11, and the ADC_MAX is 32768 - the number of ADC steps.

In line 55, we calculate the temperature using the provided recently equation

T = (R/R0 – 1)/α - 0.19, where R0 = 100, and α = 0.00385.

In line 56, we assign the rounded value of the temperature multiplied by 10 to the variable temp. It is used, as I mentioned, only to send the temperature with a 0.1 °C resolution to the RTT interface (line 57).

In line 58, we clear the adc_conversion_complete flag and wait while it is set again in the adc_callback function.

And that’s it! Now, let’s switch to practical work.

Testing of the Thermometer

Let’s assemble the circuit according to Figure 4, connect the EK-RA2A1 board to the PC, build the project, make sure that there are no errors, and start the debugging. Then run the J-Link RTT Viewer application and connect it to the board. If you need to learn how to do it, please refer to Tutorial 14, where I described it in detail.

If you did everything correctly, you will see something like this in the RTT Viewer (Figure 16).

Figure 16 - Result of the operation of the program
Figure 16 - Result of the operation of the program

As you can see, the temperature jumps a bit, which is not surprising as each ADC step corresponds to 0.2-0.3 °C.

Let’s try to warm up the RTD with the fingers and see what happens.

Figure 17 - Result of the operation when the RTD is heated
Figure 17 - Result of the operation when the RTD is heated

As you can see, the response of the sensor is extremely quick. The previous reading was 22.7 °C, and the next one is already 28 °C. This is because of the tiny size of the sensor (I used the one shown in Figure 2).

So, as you can see, everything works as desired. In this tutorial, we learned how to use the OPAMP module, and the ADC module in differential mode, how to measure the resistance of the RTD and convert it to the temperature.

As homework, I suggest you try different schematics of the RTD connection - 2-, 3-, or 4-wired, and see if there are any differences in the results. Also, I suggest you change the ADC module to SDADC as the latter has a higher resolution, and thus can measure the resistance with higher precision.

This is the last RA tutorial in which we will just consider new modules. Starting from the next ones we will level-up and consider how to use the ThreadX RTOS with the RA microcontrollers.

Make Bread with our CircuitBread Toaster!

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

What are you looking for?