Modern technology offers several ways to electronically measure a temperature, with different kind of sensors or probes. Possible examples are termocouples, thermistors or digital sensors. All of them have their pros and cons, in terms of cost, temperature ranges and resolution.
In this article, we are going to see a very cost-effective solution to measure a temperature, using a digital sensor called “MCP9808” in combination with a STM32 Nucleo 64 board.
MCP9808 sensor
The MCP9808 is a digital sensor able to measure a temperature with a typical resolution of +/- 0.25 °C (minimum resolution of 0.0625 °C). The sensor allows user-selectable settings such as Shutdown or Low-Power modes and the specification of temperature Alert window limits. When the temperature changes beyond the specified boundary limits, the MCP9808 can outputs an Alert signal. The sensor works over a classical I2C bus.
As shown in Image 1, I bought the MCP9808 sensor already mounted on a small evaluation board, where all the pins can be easily accessed. This will simplify our test procedure.
In order to proceed with our project a sensor datasheet is necessary and a good one can be found at this link: https://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf.
Introduction over I2C bus
As mentioned before, the temperature sensor will work over an I2C bus, which is very common for such applications. I2C, which is the short form of Inter Integrated Circuit, is a communication bus which uses only two wires, and which is normally used for communication between ICs. The bus protocol was first developed in the 1982 by Philips and was later adopted by many other integrated circuits vendors. The bus is characterized by a master / slave architecture, where the communications happen always in a synchronous way. The clock is always generated by the master, in transmission and in reception.
Image 2 shows a relatively easy example, where a master and three slaves are connected.
The clock is generated by the master on the line SCL, while the SDA line is used for data transmission and it is actually bi-directional.
Every slave device has a specific address which must be unique and which is used by the master to access it.
I2C standard, defines several possible bus speeds, but every node may have a maximum speed rate which must be considered. Communication with a device above the maximum speed rates, can cause communication issues.
Possible bus speeds values are summarized in Table 1:
One important aspect of the I2C physical layer, is that the bus lines are driven in a mode called “open drain” for which the low level is always actively driven from the master or the slave, but the high level it is not. With an open drain driver, is always necessary to insert in the circuit some pull-up resistors, which basically allows the lines to be driven to a higher logical state.
The calculation of the resistors depends on the speed and on the bus capacity. The standard defines a formula which can be used for precise calculations, but as a rule of thumb, we can use a resistor of 4,7 Kohm. Usually the resistor value can be reduced, if the lines get too much “charged”.
Let’s start! How to setup the hardware
After this short introduction, we can now setup the hardware. The sensor will be connected to an STM32 Nucleo 64 evaluation board, for which a good introduction can be found already in this post: Using the USB Serial Port on the STM32 Nucleo-64 board.
In the following pictures is possible to see the complete electrical schematic of our project (Image 3) and also a picture of my actual setup (Image 4).
The schematic is pretty simple and do not use many external components. The pullup resistors must be connected to VCC, which for this project corresponds to 3,3 volts and which is directly provided by the STM32 board.
Another relevant part, is the connection of the sensor pins named A0, A1 and A2. These three pins allow to select the least significant part of the device address which is a useful feature allowing to change the device address even after production, for example acting on some switches on the board. In my setup, these pins have been set to ground, enforcing then a device address of “0x30”.
Create and configure an STM32 CubeMX project
For this project we will use the tool suite provided by ST Microelectronics, which was already discussed in the following post: Using the USB Serial Port on the STM32 Nucleo-64 board. The same approach can be followed, especially in configuring the UART, which will be used as a debug instrument for this project.
One extra part which needs to be configured is then the I2C. Click on Connectivity on the left side menu and then select the I2C1: a new view will be showed to configure the I2C. On the top of the window, in the panel named I2C1 mode and configuration, enable the peripheral selecting “I2C“: is now possible to proceed with the configuration of the module (Image 5).
From this point, multiple configurations are possible, like for example the bus speed, but for our project the default configuration is fine and therefore we can just click on Generate Code, to generate the project configuration code. STM32 CubeMx will not only create all the necessary code to use the microcontroller, the serial port and the I2C bus, but it will create also an STM32 CubeIDE project which we can now open. Once the IDE is opened, we can look at the projects files on left column named Project Explorer and look for a file named main.c (Image 6): this is the file where we will implement our temperature sensor test code.
Coding our temperature sensor test
The generated code already provides all the necessary functionalities to operate with the serial port and the I2C, therefore it’s just enough to write the code which use these functionalities. In the following image it is shown a small flow chart of the software (Image 7):
The first step which must be performed by the software is of course the reading of the temperature from the sensor. The read operation is performed in two steps, using at first the HAL_I2C_Master_Transmit function for requesting the register to be read (in our case, the temperature register) and then the HAL_I2C_Master_Receive function in order to read the register content. This “split” approach is imposed by the I2C protocol, because independently if the operation is a write operation or a read operation, the “addressing” of the register is always performed as a write operation, therefore, if a read register operation must be performed, it must be split between a write (register request) and a read (register read).
Once the register content has been read, it must be interpreted and stored into a float variable. Float is in this case a good choice, since the sensor as a pretty small resolution of 0,0625 °C, which can be easily stored through a floating point variable.
Once the value is available, it can be printed out in the serial port. The usage of the function sprintf can in this case simplify the creation of a formatted string, which includes also the temperature value.
The used code is showed in the following code snippet:
char msgstr[64]; /* String where to store the serial port output */
uint16_t devAddress = 0x30; /* Temperature sensor I2C address */
uint8_t tempReg = 0x05u; /* Temperature register address */
uint8_t dataReg[2]; /* Buffer for reading the register content */
uint16_t dataRegLong; /* Variable used to store the whole register content */
float tempVal = 0; /* Float variable used for storing the temperature value */
float tempValDec; /* Float variable used for calculation of the decimal part */
/* Infinite loop */
while (1)
{
/* Address the temperature register */
HAL_I2C_Master_Transmit(&hi2c1, devAddress, &tempReg, 1, 2000u);
/* Read the temperature register content */
HAL_I2C_Master_Receive(&hi2c1, devAddress | 0x01, dataReg, 2, 2000u);
/* Compose the register content, regardless of the endianess */
dataRegLong = ((dataReg[0] << 8u) | dataReg[1]);
/* Extract the integer part from the fixed point value */
tempVal = ((dataRegLong & 0x0FFF) >> 4);
/* Extract decimal part */
tempValDec = 0.0625;
for (int i=0; i < 4; i++)
{
tempVal += ((dataRegLong >> i) & 0x0001) * tempValDec;
tempValDec *= 2u;
}
/* Prepare a formatted string, with the temperature value */
sprintf(msgstr, "Temperature is %f °C\r\n", tempVal);
/* Transmit the message over UART */
HAL_UART_Transmit(&huart2, msgstr, strlen(msgstr), 1000u);
/* Wait one second */
HAL_Delay(1000);
}
}
Final step: testing the project
Now we can just connect the board to a PC through the USB port and use a serial port terminal to test our code. For this example I will use Putty, which can be downloaded from here. Once opened, the tool will show the following configuration window (Image 8).
Once the correct parameters are selected, like the right COM port or the right speed, we can click Open to start the serial port session.
Start then the project from Run->Debug and check the output on the Putty console:
The temperature is then shown in the console (Image 9).
Conclusion
With this simple example, it was shown how easily can be the configuration and the usage of the I2C bus over an STM32 microcontroller to read a temperature sensor. This can be intended as a start basis for even more complex and complete design in the field of IoT and domotics.
The used sensor MCP9808 has many configurations, which were not listed and tested in this short article, but which can be easily used with this very same code as a starting point.
1 thought on “Temperature measurement? Never so easy with STM32 and MCP9808!”