More TC74 programming on an STM32: Using sleep mode for low power

Previous posts showed how to use a TC74 temperature sensor with an STM32 microcontroller and how to handle communication errors . Since typically the temperature is not a value that changes rapidly, it is not essential to take multiple measurements with a high sampling rate, even if the I2C protocol is rather quick. For this reason, the TC74 sensor has a very low power standby mode when it not needed. The TC74 has an internal analog to digital converter which saves the value on an internal register. What is more this converter has a typical sampling rate of 8 samples per second. In standby mode this ADC is switched off. Concerning the power consumption, the information on the datasheet is a bit confusing. The datasheet provides a current consumption of 200uA in operating mode and 5uA in standby mode, which is a very significant difference. However, consumption in normal mode is measured for Vdd of 5.5V, whereas consumption in standby mode for Vdd of 3.3V. Based on my measurements of 2 TC74 operating in 3V, standby mode: 4uA, operating mode: 170uA. So indeed, the power saving is significant and should be used in low power applications. As it was analyzed, the TC74 has an 8-bit register which stores the temperature value. It has a second register (address 0x01), called the configuration register which has two bits. The first one (bit 7) enables the standby mode, whereas the second one (bit 6) shows when the sensor is ready to transmit the first data after entering operating operating. So the sequence to read the temperature using low power consumption is the following:

  1. Wake up
  2. Wait for the data to be ready (either using a delay or be polling the data ready bit of the config register
  3. Get temperature
  4. Sleep

After a wake up instruction, the TC74 needs about 300ms to transmit the first data. So in my implementation, a timer is used to wait for 500ms after a wake up instruction, get temperature and sleep the sensor and wait for 5s until the temperature is measured again. First of all, the TC74_Read_Temperature() function needs to be modified in order to read the 0x00 register (temperature) or the 0x01 register (configuration).

int TC74_Read_Register(uint8_t TC74address, uint8_t reg)

{

int8_t data1, data2;
if(I2C_start(I2C1, TC74address, I2C_Direction_Transmitter)>0) 
{ 
return -127; 
} 
if(I2C_write(I2C1, reg)>0) 
return -127; 
if(I2C_stop(I2C1)==1) 
return -127; 
if(I2C_start(I2C1, TC74address, I2C_Direction_Receiver)>0) 
{ 
return -127; 
} 
data1 = I2C_read_ack(I2C1); 
data2 = I2C_read_nack(I2C1); 
return data1; 
}



We need to have a status variable which will show whether the TC74 is in operating or standby mode.

typedef enum {TC74_SLEEPING, TC74_WAITING} TC74_statetypedef; 
TC74_statetypedef Temp_read_status=TC74_SLEEPING;



Two new functions are added, one that wakes up the sensor and another one that sends the instruction to enter standby mode.

int TC74_WakeUp(uint8_t TC74address) 
{ 
if(I2C_start(I2C1, TC74address, I2C_Direction_Transmitter)>0) 
{ 
return -127; 
} 
if(I2C_write(I2C1, 0x01)>0) 
return -127; 
if(I2C_write(I2C1, 0x00)>0) 
return -127; 
if(I2C_stop(I2C1)==1) 
return -127; 
return 0; 
} 
int TC74_Sleep(uint8_t TC74address) 
{ 
if(I2C_start(I2C1, TC74address, I2C_Direction_Transmitter)>0) 
{ 
return -127; 
} 
if(I2C_write(I2C1, 0x01)>0) 
return -127; 
if(I2C_write(I2C1, 0x80)>0) 
return -127; 
if(I2C_stop(I2C1)==1) 
return -127; return 0; 
}



After that we need to initialize a timer, TIM6 for the current example. The APB1 peripheral clock is 32MHz for the STM32L152.

void TIM6_Config(void) 
{ 
TIM_TimeBaseInitTypeDef timerInitStructure; 
NVIC_InitTypeDef NVIC_InitStructure; 
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); 
timerInitStructure.TIM_Prescaler=1000; 
timerInitStructure.TIM_CounterMode=TIM_CounterMode_Up; 
timerInitStructure.TIM_Period=32000; 
timerInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
TIM_TimeBaseInit(TIM6, &timerInitStructure); 
TIM_Cmd(TIM6, ENABLE); 
NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; 
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8; 
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 
NVIC_Init(&NVIC_InitStructure); 
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); 
}


The interrupt handler routine of TIM6 is:

void TIM6_IRQHandler(void) 
{ 
if (TIM_GetFlagStatus(TIM6, TIM_IT_Update)!=RESET) 
{ 
TIM_Cmd(TIM6, DISABLE);
if (Temp_read_status==TC74_SLEEPING) 
{ 
TC74_WakeUp(TC74_ADDRESS); 
TIM6->PSC = 5000; 
Temp_read_status=TC74_WAITING; 
} 
else 
{ 
Temperature=TC74_Read_Register(TC74_ADDRESS,0x00); 
TC74_Sleep(TC74_ADDRESS); 
TIM6->PSC = 500; 
Temp_read_status=TC74_SLEEPING; 
} 
TIM6->CNT=0; 
TIM_Cmd(TIM6, ENABLE); 
TIM_ClearITPendingBit(TIM6, TIM_IT_Update); 
} 
}

The full project to read the temperature from the TC74 sensor with error handling and standby mode can be found on my Github page.

More TC74 on STM32: Avoiding I2C bus failure when TC74 doesn't respond

On a previous post, the code to implement the I2C protocol for an STM32 microcontroller connected to an TC74 temperature sensor was implemented. However, what happens if no TC74 is connected or due to a failure does not respond. Since the TC74 sensor will be used on the UPSat mission, the microcontroller needs to be able to acknowledge that the sensor does not respond and continue with executing the main program.

One peripheral that can be used in case the TC74 doesn't respond is the watchdog counter. If no error handling is implemented on the I2C communication, the program enters into a deadlock. The watchdog's purpose is to reset the microcontroller if it doesn't reset the watchdog counter after a short period of time, acting as an auto reset. The STM32 micro controllers include independent and window watchdogs. However, on the next time that the temperature sensor will be called again, the microcontroller will freeze again. Of course, a "reset reason" detection code could be implemented and disable TC74 communication after a reset caused to an I2C error, but the damage to the procedure execution could already be done because of the reset.

A more correct way is to implement error handling on the I2C communication and continue code execution when an error is detected, discarding the measurement. The microcontroller after sending a message through the I2C bus waits for the peripheral to respond. If the peripheral does not respond, the code execution freezes at this point. The simplest solution is to implement a timeout function, meaning that if the peripheral does not respond after a certain amount of time, the code execution will continue. The I2C peripheral on the STM32 microcontroller does not implement a timeout support inherently. However, the System Management Bus (SMBus) mode can be used which implements timeout detection. So, on the initialization function of the I2C peripheral, the I2C_mode needs to be set to I2C_Mode_SMBusHost. Then, using the I2C Timeout flag or the I2C Timeout interrupt, the error can be handled.

Furthermore, another error that can appear is an acknowledge failure. This exists when the microcontroller sends a message to the device, awaits for an acknowledge bit on the next clock but it doesn't respond. Again, the Acknowledge Failure (AF) flag can be polled to detect the error.

So based on the above, the communication protocol functions are rewritten to return 0 is the communication is good and >0 if there is an error. The main function finally returns the temperature, or -127 in case of an error:

int TC74_Read_Temperature(uint8_t TC74address)
{
int8_t data1, data2;
if(I2C_start(I2C1, TC74address, I2C_Direction_Transmitter)>0) {
I2CBus_Reset();
return -127;
}
if(I2C_write(I2C1, 0x00)>0) return -127;
if(I2C_stop(I2C1)==1) return -127;

if(I2C_start(I2C1, TC74address, I2C_Direction_Receiver)>0) {
I2CBus_Reset();
return -127;
}
data1 = I2C_read_ack(I2C1);
data2 = I2C_read_nack(I2C1);

return data1;
}

So the modification that is implemented is inside the while statement to avoid the infinite loop. If an error flag is set, the code execution is stopped, returning the error code.

int I2C_start(I2C_TypeDef* I2Cx, uint8_t address, uint8_t direction)
{
int i2c_timeout=I2CTIMEOUT;
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));

I2C_GenerateSTART(I2Cx, ENABLE);

while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return 1;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return 2;
}
}
I2C_Send7bitAddress(I2Cx, address, direction);

if (direction== I2C_Direction_Transmitter) {
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return 1;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return 2;
}
}
}

else if(direction == I2C_Direction_Receiver) {
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return 3;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return 4;
}
}
}
return 0;
}
int I2C_write(I2C_TypeDef* I2Cx, uint8_t data)
{
I2C_SendData(I2Cx, data);

while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return 1;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return 2;
}
}
return 0;
}
int I2C_stop(I2C_TypeDef* I2Cx)
{
I2C_GenerateSTOP(I2Cx, ENABLE);
return 0;
}
int8_t I2C_read_ack(I2C_TypeDef* I2Cx)
{
int8_t data;

I2C_AcknowledgeConfig(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return -127;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return -127;
}
}
data=I2C_ReceiveData(I2Cx);

return data;
}
int8_t I2C_read_nack(I2C_TypeDef* I2Cx)
{
uint8_t data;

I2C_AcknowledgeConfig(I2Cx, DISABLE);
I2C_GenerateSTOP(I2Cx, ENABLE);

while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return -127;
}
}
data=I2C_ReceiveData(I2Cx);

return data;
}

The full project to read the temperature from the TC74 sensor with error handling can be found on my Github page.

Temperature monitoring with TC74 on an STM32 microcontroller

The TC74 is a small thermal sensor with a wide temperature range (-40C to +125C) and a digital output (I2C). It comes in a TO-220 package or a small scale SOT-23 package and is powered by 3.3V or 5V. Based on the above it was for our UPSat, as two temperature values (board temperature and battery temperature) need to be measured every minute. The TC74 can withstand the temperature conditions found inside the Cubesat and can provide relatively accurate measurements, with low power consumption.

The majority of thermal sensors have an analog output, the value of which is given as a function of the measured temperature. So, the measurement is susceptible to noise, as the Analog to Digital converter used is found in the micro-controller and each sensor needs an extra pin. On the other hand the TC74 has an embedded ADC and transmits the data digitally, via the I2C Serial Port Interface. The I2C protocol is used for the communication of different ICs on a board with the micro-controller, for example EEPROMs, ADCs or GPIO expanders. It needs two lines, the SDA (data) and the SCL (clock). The lines are pulled high and the ICs pull the lines low. It uses a master/slave approach, where the micro controller is usually the master and therefore is responsible for the SCL line.

Having studied other communication protocols, the I2C is rather peculiar and especially the implementation of the protocol on the TC74. The default address length is 7bits. It can be 10 bits as well. To establish communication, the sender first issues a start condition. It then continues by sending the address of the peripheral that it needs to communicate with and finally a bit showing whether it wants to read data from the device or write data to the device. It then awaits for the an acknowledge (ACK) bit from the device on the next clock sequence and continues the communication accordingly.

The way this protocol is implemented on the TC74 is as follows. The TC74 has two registers, the temperature register, which has the temperature value and the control register (which is used to limit power consumption). So, to read the temperature, the micro-controller first issues a start bit, followed by the 7bit address of the TC74. The TC74 then sends an ACK. The micro-controller then sends the address of the register that it wants to read. In the case of the temperature register, the value is 0x00. The TC74 sends an ACK again. After that, the micro-controller sends a start condition again, the address of the TC74 again followed by an ACK and waits for the data followed by a NACK (not-acknowledge). It finally issues a stop condition.

The code shown bellow implements the communication of a single TC74 with an STM32L152 micro-controller. The pins used are PB6 and PB7 and the I2C1 peripheral was used. The STM32 internal pull-up resistors were used.

The TC74 have 8 different product codes depending on the factory written addresses of the chip. So on a single I2C bus, 8 different TC74 can be used. In this example the TC74A5 is used:

#define TC74_ADDRESS 0x9A

Next, the init function needs to be implemented:

void TC74_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 10000;
I2C_InitStructure.I2C_Mode = I2C_Mode_SMBusHost;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
}

This function initializes the I2C1 peripheral, the ports PB6, PB7 and activates the pull-up resistors.

Next is where the fun stuff begins. The timing sequence is implemented with different functions. The main function to return an integer value of the temperature is:

int TC74_Read_Temperature(uint8_t TC74address)
{
int8_t data1, data2;
I2C_start(I2C1, TC74address, I2C_Direction_Transmitter);
I2C_write(I2C1,0x00);
I2C_stop(I2C1);
I2C_start(I2C1, TC74address, I2C_Direction_Receiver);
data1 = I2C_read_ack(I2C1);
data2 = I2C_read_nack(I2C1);
return data1;
}

Finally each of these functions are described as well:

void I2C_start(I2C_TypeDef* I2Cx, uint8_t address, uint8_t direction)
{
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) ;
I2C_Send7bitAddress(I2Cx, address, direction);
if (direction== I2C_Direction_Transmitter) {
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
}
else if(direction == I2C_Direction_Receiver) {
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
}
}
void I2C_write(I2C_TypeDef* I2Cx, uint8_t data)
{
I2C_SendData(I2Cx, data);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}
void I2C_stop(I2C_TypeDef* I2Cx)
{
I2C_GenerateSTOP(I2Cx, ENABLE);
}
int8_t I2C_read_ack(I2C_TypeDef* I2Cx)
{
int8_t data;
I2C_AcknowledgeConfig(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));
data=I2C_ReceiveData(I2Cx);
return data;
}
int8_t I2C_read_nack(I2C_TypeDef* I2Cx)
{
uint8_t data;
I2C_AcknowledgeConfig(I2Cx, DISABLE);
I2C_GenerateSTOP(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));
data=I2C_ReceiveData(I2Cx);
return data;
}

The full project to read the temperature from the TC74 sensor can be found on my Github page.