Zytemp TN9 Overview

Hi all, this seemed like the best place to post this…

I recently had to get a Zytemp tn9 infrared temperature sensor working with an Arduino and it seemed needlessly difficult so I wrote up a quick overview of the sensor for newbies like me who might have to use this sensor in the future. Hopefully it helps someone and please tell me if there’s anything that I got wrong. The datasheet can be found easily online but it is not terribly helpful.

Hardware: The TN9 is a small sensor with 5 pads. The circular pad is the power pin, followed by (in order) the data pin, clock pin, ground pin, and action pin. This sensor will run of the Arduino default 5V. See data sheet for more information.

Software: This is not a typical sensor because it cannot really be polled for a value. With this sensor, if the action line is grounded, temperature data will be sent. If the action line is written high, the sensor will not send any data and it can be written to. The only reason you should write to the sensor is to modify the emissivity, which is not recommended according to the data sheet. However, writing the action line high is still used to prevent the sensor from sending data. When the sensor is sending data, the data line is synchronized with the clock line; every time the clock line falls a bit is available to be read on the data line (see data sheet for more information).

The data from this sensor comes in 5-byte “packets” at approximately 1.4 Hz. The packets are sent in a certain order: Infrared, Ambient, Infrared, Junk. I’m not sure if it always starts at the beginning of that sequence, but it will follow that sequence. Neither I, nor anyone I have communicated with knows what the junk packet is so I have chosen to ignore it (it is not covered in the data sheet).

Each packet that is received is 5 bytes long (40 bits). While the data sheet covers this, it is reviewed below in order:
Byte 1: Object indicator. Indicates whether following reading is Infrared, Ambient, or Junk. If
infrared, Byte 1 == 0x4C. If ambient, Byte 1 == 0x66. If junk, Byte 1 == 0x53.
Byte 2: Most Significant Byte. This constitutes the first half of the temperature reading and is in hex.
Byte 3: Least Significant Byte. This constitutes the second half of the temperature reading and is in hex
Byte 4: Checksum. The checksum == Byte 1 + Byte 2 + Byte 3 (in hex). Remember, on the sensor, the checksum byte will rollover but it will not when being calculated on the arduino. For example, if the bytes 1, 2, and 3, are 0x66, 0x12, and 0xA0 respectively, the checksum as calculated by the Arduino will be 0x18 = 280. However, the checksum that will be sent by the TN9 is 0x18 = 24. This is because one byte cannot hold the number 280, so it rolls over. 280-256 = 24. If you do not account for this, you will think you are getting a lot of bad readings.?Byte 5: End of message byte. Will always be 0xD

To calculate the temperature, the following steps are followed. The packet that the operation will be performed on is : [0x66, 0x12, 0xA0, 0x18, 0xD].

  1. Concatenate Bytes 2 and 3. 0x12 and 0xA0 become 0x12A0
  2. Convert the concatenated word to decimal. 0x12A0 becomes 4768
  3. Divide by 16 and subtract 273.15. 4768/16 - 273.15 = 24.85 degrees. Since byte 1 ==
    0x66, this temperature is an ambient temperature.

To gather all of this data, an interrupt must be set on the clock line for falling. The interrupt function will need to read the bit off the data line and store it to a variable. See the example code.

byte n = 0;						                // Interrupt Bit Count				
volatile byte pos = 0;						// Values Position Count
volatile byte values[5] = {
  0,0,0,0,0};		                                                // Values to be stored by sensor
byte cbit = 0;						        // Current bit read in

boolean irFlag = false;						// Flag to indicate IR reading has been made
boolean ambFlag = false;					// Flag to indicate ambient temp reading has been made


byte irValues[5] = {
  0,0,0,0,0};				                               // Variable to store IR readings
byte ambValues[5] = {
  0,0,0,0,0};			                                      // Variable to store Ambient readings

const int len = 5;					              // Length of values array
const int clkPin = 3;			                      // Pins
const int dataPin = 2;
const int actionPin = 4;

void setup(){
  Serial.begin(9600);						

  pinMode(clkPin, INPUT);					// Initialize pins
  pinMode(dataPin, INPUT);
  pinMode(actionPin, OUTPUT);
  digitalWrite(clkPin, HIGH);
  digitalWrite(dataPin, HIGH);
  digitalWrite(actionPin, HIGH);

  Serial.println("Type to Start...");		                // Wait for input to start
  while(!Serial.available());
  Serial.println("Starting...");
  Serial.println("IR (C), Ambient (C), Time Since Start (ms)");

  attachInterrupt(1,tn9Data,FALLING);  		        // Interrupt
  digitalWrite(actionPin,LOW);			  	// Make sensor start sending data
}

void loop(){



  if(pos == len && values[0] == 0x4C){		        // If sensor has sent IR packet...
    for(int i = 0; i < len; i++){			                // Store values to irValues
      irValues[i] = values[i];
    }
    irFlag = true;				                       // Indicate IR reading
    pos = 0;
    digitalWrite(actionPin,LOW);			       // Make sensor start sending data
  }

  if(pos == len && values[0] == 0x66){		      // If sensor has sent ambient packet...
    for(int i = 0; i < len; i++){			              // Store values to ambValues
      ambValues[i] = values[i];
    }
    ambFlag = true;				              // Indicate Ambient reading
    pos = 0;
    digitalWrite(actionPin,LOW);			     // Make sensor start sending data    
  }

  if(pos == len && values[0] == 0x53){		     // If sensor has sent junk packet
    pos = 0;
    digitalWrite(actionPin,LOW);			    // Make sensor start sending data   
  }

  if(irFlag && ambFlag){				// If successful IR and Ambient reading...
    digitalWrite(actionPin,HIGH);		// Make sensor stop sending data.  Because Timing is weird, I want to ensure the interrupts do not happen during this section.   
    word tempword = 0;  			// Next 4 lines isolate temperature component of values
    tempword = tempword | irValues[1];
    tempword = tempword << 8;
    tempword = tempword | irValues[2];
    if(tn9Check(irValues)){				// If checksum is valid, print IR temperature
      //Serial.print("IR = ");
      Serial.print(int(tempword)/16.0 - 273.15);
      Serial.print(", ");       
    }
    else{					        // If checksum isn't valid, print impossible temp
      //Serial.print("IR = ");
      Serial.print("-273.15, "); 
    }

    tempword = 0;					// Isolate temperature component again for ambient
    tempword = tempword | ambValues[1];
    tempword = tempword << 8;
    tempword = tempword | ambValues[2];
    if(tn9Check(ambValues)){				// If checksum is valid, print ambient temperature
      //Serial.print("Amb = ");
      Serial.print(int(tempword)/16.0 - 273.15);        
    }
    else{						// If checksum isn't valid, print impossible temp
      //Serial.print("Amb = ");
      Serial.print("-273.15"); 
    }
    irFlag = false;					// Reset flags
    ambFlag = false;
    
    Serial.print(", ");
    Serial.println(millis());                           // Print time for logging purposes
    delay(2000);					     // Simulate other sensors or code
    digitalWrite(actionPin,LOW);	             // Make sensor start sending data  
}											


}


void tn9Data(){						// Interrupt Function
  cbit =  digitalRead(dataPin);			// Read bit
  if(pos >= len) pos = 0;				        // Keep index below 5
  values[pos] = (values[pos] << 1) | cbit;	        // Store to values
  n++;							// Increment bit count
  if(n == 8){						// Increment position count based on bits read in
    pos++;
    n = 0; 
  }
  if(pos == len){ 					// If complete "packet" sent, stop sensor from sending 
    digitalWrite(actionPin,HIGH);  		// again until main loop allows it.
  }
}


boolean tn9Check(byte tn9Values[]){			// Checksum calculating function
  int mcheck = (int)tn9Values[0] + (int)tn9Values[1] + (int)tn9Values[2];	// Checksum calculation
  int scheck = (int)tn9Values[3];			       // Checksum sent by sensor
  boolean crc = false;						// Initialize return value
  if(mcheck > 510) mcheck = mcheck - 512;	        // Handle sensor byte rollover
  if(mcheck > 255) mcheck = mcheck - 256;	        // Handle sensor byte rollover
  if(mcheck == scheck) crc = true;			// Check checksum
  return(crc);						// Return
}

This code may not be the best way to do it but it worked for me, let me know if there are better ways.

hello i’m new to programming and converted this code for Fahrenheit just for fun

byte n = 0;						                // Interrupt Bit Count				
volatile byte pos = 0;						// Values Position Count
volatile byte values[5] = {
  0,0,0,0,0};		                                                // Values to be stored by sensor
byte cbit = 0;						        // Current bit read in

boolean irFlag = false;						// Flag to indicate IR reading has been made
boolean ambFlag = false;					// Flag to indicate ambient temp reading has been made


byte irValues[5] = {
  0,0,0,0,0};				                               // Variable to store IR readings
byte ambValues[5] = {
  0,0,0,0,0};			                                      // Variable to store Ambient readings

const int len = 5;					              // Length of values array
const int clkPin = 3;			                      // Pins
const int dataPin = 2;
const int actionPin = 4;

void setup(){
  Serial.begin(9600);						

  pinMode(clkPin, INPUT);					// Initialize pins
  pinMode(dataPin, INPUT);
  pinMode(actionPin, OUTPUT);
  digitalWrite(clkPin, HIGH);
  digitalWrite(dataPin, HIGH);
  digitalWrite(actionPin, HIGH);

  Serial.println("Type to Start...");		                // Wait for input to start
  while(!Serial.available());
  Serial.println("Starting...");
  Serial.println("IR (F), Ambient (F), Time Since Start (ms)");

  attachInterrupt(1,tn9Data,FALLING);  		        // Interrupt
  digitalWrite(actionPin,LOW);			  	// Make sensor start sending data
}

void loop(){



  if(pos == len && values[0] == 0x4C){		        // If sensor has sent IR packet...
    for(int i = 0; i < len; i++){			                // Store values to irValues
      irValues[i] = values[i];
    }
    irFlag = true;				                       // Indicate IR reading
    pos = 0;
    digitalWrite(actionPin,LOW);			       // Make sensor start sending data
  }

  if(pos == len && values[0] == 0x66){		      // If sensor has sent ambient packet...
    for(int i = 0; i < len; i++){			              // Store values to ambValues
      ambValues[i] = values[i];
    }
    ambFlag = true;				              // Indicate Ambient reading
    pos = 0;
    digitalWrite(actionPin,LOW);			     // Make sensor start sending data    
  }

  if(pos == len && values[0] == 0x53){		     // If sensor has sent junk packet
    pos = 0;
    digitalWrite(actionPin,LOW);			    // Make sensor start sending data   
  }

  if(irFlag && ambFlag){				// If successful IR and Ambient reading...
    digitalWrite(actionPin,HIGH);		// Make sensor stop sending data.  Because Timing is weird, I want to ensure the interrupts do not happen during this section.   
    word tempword = 0;  			// Next 4 lines isolate temperature component of values
    tempword = tempword | irValues[1];
    tempword = tempword << 8;
    tempword = tempword | irValues[2];
    if(tn9Check(irValues)){				// If checksum is valid, print IR temperature
      Serial.print("IR = ");
      float irc = (int(tempword)/16.0 - 273.15);
      float irf = (irc  * 9 / 5 + 32);
      Serial.print(irf);
      Serial.print(", ");       
    }
    else{					        // If checksum isn't valid, print impossible temp
      Serial.print("IR = ");
      Serial.print("-273.15, "); 
    }

    tempword = 0;					// Isolate temperature component again for ambient
    tempword = tempword | ambValues[1];
    tempword = tempword << 8;
    tempword = tempword | ambValues[2];
    if(tn9Check(ambValues)){				// If checksum is valid, print ambient temperature
      Serial.print("Amb = ");
      float ambc = (int(tempword)/16.0 - 273.15);
      float ambf = (ambc  * 9 / 5 + 32);
      Serial.print(ambf);
            
    }
    else{						// If checksum isn't valid, print impossible temp
      Serial.print("Amb = ");
      Serial.print("-273.15"); 
    }
    irFlag = false;					// Reset flags
    ambFlag = false;
    
    Serial.print(", ");
    Serial.println(millis());                           // Print time for logging purposes
    delay(2000);					     // Simulate other sensors or code
    digitalWrite(actionPin,LOW);	             // Make sensor start sending data  
}											


}


void tn9Data(){						// Interrupt Function
  cbit =  digitalRead(dataPin);			// Read bit
  if(pos >= len) pos = 0;				        // Keep index below 5
  values[pos] = (values[pos] << 1) | cbit;	        // Store to values
  n++;							// Increment bit count
  if(n == 8){						// Increment position count based on bits read in
    pos++;
    n = 0; 
  }
  if(pos == len){ 					// If complete "packet" sent, stop sensor from sending 
    digitalWrite(actionPin,HIGH);  		// again until main loop allows it.
  }
}


boolean tn9Check(byte tn9Values[]){			// Checksum calculating function
  int mcheck = (int)tn9Values[0] + (int)tn9Values[1] + (int)tn9Values[2];	// Checksum calculation
  int scheck = (int)tn9Values[3];			       // Checksum sent by sensor
  boolean crc = false;						// Initialize return value
  if(mcheck > 510) mcheck = mcheck - 512;	        // Handle sensor byte rollover
  if(mcheck > 255) mcheck = mcheck - 256;	        // Handle sensor byte rollover
  if(mcheck == scheck) crc = true;			// Check checksum
  return(crc);						// Return
}

Thank you for sharing the code!

Unfortunately im a total newbie, how did you connect the TN9 with the Arduino exactly ? For Clock,Data,Action pins digital:3,2,4 respectively ?

Cause im not getting any feedback (temperature) in the serial monitor.

Thank you for your attention!

Hi,

i have got some problems using this code with a Atmega 328 SMD-Kit.

Maybe someone can help me i discribed my problem here :

http://forum.arduino.cc/index.php?topic=207498.0

Thanks a lot.

Cu kami