Some great tutorials have been put out there for building infrared thermometers with the MLX90614 Infrared Thermometer sensor from Melexis. Most of the projects we’ve seen use the Arduino. In one of our current sensor projects, we wanted to adapt the MLX90614 for use with an Atmega48. We programmed this project in AVR Studio 5 as a stepping-stone for another project. We’ll show you how we did it below with some diagrams, and some code.
The Circuit:
- This project uses the I2CMaster files from Fleury, modified to the Atmega48 with a 16 MHZ crystal (which is a little hard to see on the right hand side of the breadboard).
- We have a MLX90614 Temperature sensor that is read through SDA and SCL on pins PC5 and PC4.
- There are 2.2 k Ohm pulldown resistors on the SDA and SCL lines.
- The program spits out the temperature over the serial line, which can be read with an Arduino or a serial reader.
- The 6-pin terminals are where you attach the AVR Programmer.
The Code:
This code was written in AVR Studio 5.
Atmega48PandMLX90614.c
[sourcecode language=”cpp” autolinks=”true” collapse=”true” toolbar=”true”]<br />//// Atmega48P
// Copyright 2011 Dexter Industries. http://www.dexterindustries.com/howto
// Chip: Atmega48P
// Operations: This chip reads the MLX90614 temperature sensor, calculates out the temperature value, and sends it out
// on the serial line.
//
// Important to note two critical changes that must be made to adapt to other chips:
// In i2cmaster.S:
// – Match the pin and ports at the top. This example matches to PIN B 1, PIN B 2.
// – Match the i2c_delay_T2 to the clock. This example matches to 16 Mhz.
// http://www.avr-asm-tutorial.net/avr_en/beginner/JUMP.html
//
// Sensor Notes: needs at least a 2.2k resistor. Max operation is 100 kHz.
// Based on the I2C Bitbang protocol built by Fleury. http://jump.to/fleury
//
// Created: 4/21/2011
// Author: John
// This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation.
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#define F_CPU 16000000UL // Clock speed for UART // We’re using an external 16MHz Clock
#define FOSC 16000000UL // Clock Speed for UART // We’re using an external 16MHz Clock
#define BAUD 9600
#define MYUBRR FOSC/16/BAUD-1
#define SCL_CLOCK 100000L // JDC added.
#include <avr/io.h>
#include <util/delay.h>
#include "i2cmaster.h"
char ret1 = 0;
char ret2 = 0;
char ret3 = 0;
long celsius = 0;
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
// Initializes USART.
// Very well.
void USART_Init( unsigned int ubrr)
{
/*Set baud rate */
UBRR0H = (unsigned char)(ubrr>>8);
UBRR0L = (unsigned char)ubrr;
/*Enable receiver and transmitter */
UCSR0B = (1<<RXEN0)|(1<<TXEN0);
/* Set frame format: 8data, 2stop bit */
UCSR0C = (1<<USBS0)|(3<<UCSZ00);
}
// TRANSMIT
// This code sends an integer over the line.
void USART_Transmit(int data)
{
// If you look in the data sheet, there is not any register named UCSRnB.
// The little ‘n’ means that you substitue that with a number for the exact
// register that you want to use such as UCSR0B or UCSR1B.
/* Wait for empty transmit buffer */
while ( !(UCSR0A & (1<<UDRE0))); // Using register 0
/* Put data into buffer, sends the data */
UDR0 = data; // Using register 0
}
// WAIT: Take a breather.
void Wait()
{
uint8_t i=0;
for(;i<5;i++)
_delay_ms(10);
//_delay_loop_1(0);
}
// BLINK This will serve as a debugging tool
// just to let us know we’re still alive.
void blink_on(){
//Set PC0=High(+5v)
PORTC|=0b00111111;
Wait();
}
void blink_off(){
//Set PC0=Low(GND)
PORTC&=0b11000000;
Wait();
}
void blink(){
blink_on();
blink_off();
}
void i2c_refresh(){
int i2c_slave_address = 0x5A<<1; // Shifted address. 0x5A is the address, shifted is B4.
i2c_start(i2c_slave_address+I2C_WRITE); // Set device address and read mode
i2c_write(0x07); // Read the temp. 0x06 = ambient Temp. 0x07 = TObj1, 0x08 = TObj2.
i2c_rep_start(i2c_slave_address+I2C_READ); // Kick it.
ret1 = i2c_readAck(); // Read the first bit.
ret2 = i2c_readAck(); // Read the second bit.
ret3 = i2c_readNak(); // This is a NACK because it’s the last byte. We don’t care much about this.
i2c_stop();
// Process the data.
double tempFactor = 0.02; // 0.02 degrees per bit
double tempData = 0x0000; // zero out the data
int frac; // data past the decimal point
// This masks off the error bit of the high byte, then moves it left 8 bits and adds the low byte.
tempData = (double)(((ret2 & 0x007F) << 8) + ret1);
tempData = (tempData * tempFactor)-0.01;
celsius = ((tempData – 273.15)*100); // Final value of celsius is multipled by 100 so we don’t have decimals.
}
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
int main(void)
{
i2c_init(); // Initialize I2C library
USART_Init(MYUBRR); // Initialize USART
while(1)
{
USART_Transmit(0xFF); // Just markers for the serial stream.
USART_Transmit(celcius); // Transmit the value of celsius.
i2c_refresh(); // Refresh the data from the MLX90614
blink(); // Blink to let us know the microcontroller is alive
}
}
[/sourcecode]
#define _I2CMASTER_H 1
/*************************************************************************
* Title: C include file for the I2C master interface
* (i2cmaster.S or twimaster.c)
* Author: Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
* File: $Id: i2cmaster.h,v 1.10 2005/03/06 22:39:57 Peter Exp $
* Software: AVR-GCC 3.4.3 / avr-libc 1.2.3
* Target: any AVR device
* Usage: see Doxygen manual
**************************************************************************/
#ifdef DOXYGEN
/**
@defgroup pfleury_ic2master I2C Master library
@code #include <i2cmaster.h> @endcode
@brief I2C (TWI) Master Software Library
Basic routines for communicating with I2C slave devices. This single master
implementation is limited to one bus master on the I2C bus.
This I2c library is implemented as a compact assembler software implementation of the I2C protocol
which runs on any AVR (i2cmaster.S) and as a TWI hardware interface for all AVR with built-in TWI hardware (twimaster.c).
Since the API for these two implementations is exactly the same, an application can be linked either against the
software I2C implementation or the hardware I2C implementation.
Use 4.7k pull-up resistor on the SDA and SCL pin.
Adapt the SCL and SDA port and pin definitions and eventually the delay routine in the module
i2cmaster.S to your target when using the software I2C implementation !
Adjust the CPU clock frequence F_CPU in twimaster.c or in the Makfile when using the TWI hardware implementaion.
@note
The module i2cmaster.S is based on the Atmel Application Note AVR300, corrected and adapted
to GNU assembler and AVR-GCC C call interface.
Replaced the incorrect quarter period delays found in AVR300 with
half period delays.
@author Peter Fleury pfleury@gmx.ch http://jump.to/fleury
@par API Usage Example
The following code shows typical usage of this library, see example test_i2cmaster.c
@code
#include <i2cmaster.h>
#define Dev24C02 0xA2 // device address of EEPROM 24C02, see datasheet
int main(void)
{
unsigned char ret;
i2c_init(); // initialize I2C library
// write 0x75 to EEPROM address 5 (Byte Write)
i2c_start_wait(Dev24C02+I2C_WRITE); // set device address and write mode
i2c_write(0x05); // write address = 5
i2c_write(0x75); // write value 0x75 to EEPROM
i2c_stop(); // set stop conditon = release bus
// read previously written value back from EEPROM address 5
i2c_start_wait(Dev24C02+I2C_WRITE); // set device address and write mode
i2c_write(0x05); // write address = 5
i2c_rep_start(Dev24C02+I2C_READ); // set device address and read mode
ret = i2c_readNak(); // read one byte from EEPROM
i2c_stop();
for(;;);
}
@endcode
*/
#endif /* DOXYGEN */
/**@{*/
#if (__GNUC__ * 100 + __GNUC_MINOR__) < 304
#error "This library requires AVR-GCC 3.4 or later, update to newer AVR-GCC compiler !"
#endif
#include <avr/io.h>
/** defines the data direction (reading from I2C device) in i2c_start(),i2c_rep_start() */
#define I2C_READ 1
/** defines the data direction (writing to I2C device) in i2c_start(),i2c_rep_start() */
#define I2C_WRITE 0
/**
@brief initialize the I2C master interace. Need to be called only once
@param void
@return none
*/
extern void i2c_init(void);
/**
@brief Terminates the data transfer and releases the I2C bus
@param void
@return none
*/
extern void i2c_stop(void);
/**
@brief Issues a start condition and sends address and transfer direction
@param addr address and transfer direction of I2C device
@retval 0 device accessible
@retval 1 failed to access device
*/
extern unsigned char i2c_start(unsigned char addr);
/**
@brief Issues a repeated start condition and sends address and transfer direction
@param addr address and transfer direction of I2C device
@retval 0 device accessible
@retval 1 failed to access device
*/
extern unsigned char i2c_rep_start(unsigned char addr);
/**
@brief Issues a start condition and sends address and transfer direction
If device is busy, use ack polling to wait until device ready
@param addr address and transfer direction of I2C device
@return none
*/
extern void i2c_start_wait(unsigned char addr);
/**
@brief Send one byte to I2C device
@param data byte to be transfered
@retval 0 write successful
@retval 1 write failed
*/
extern unsigned char i2c_write(unsigned char data);
/**
@brief read one byte from the I2C device, request more data from device
@return byte read from I2C device
*/
extern unsigned char i2c_readAck(void);
/**
@brief read one byte from the I2C device, read is followed by a stop condition
@return byte read from I2C device
*/
extern unsigned char i2c_readNak(void);
/**
@brief read one byte from the I2C device
Implemented as a macro, which calls either i2c_readAck or i2c_readNak
@param ack 1 send ack, request more data from device<br>
0 send nak, read is followed by a stop condition
@return byte read from I2C device
*/
extern unsigned char i2c_read(unsigned char ack);
#define i2c_read(ack) (ack) ? i2c_readAck() : i2c_readNak();
/**@}*/
#endif
; Title : I2C (Single) Master Implementation
; Author: Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
; based on Atmel Appl. Note AVR300
; File: $Id: i2cmaster.S,v 1.12 2008/03/02 08:51:27 peter Exp $
; Software: AVR-GCC 3.3 or higher
; Target: any AVR device
;
; DESCRIPTION
; Basic routines for communicating with I2C slave devices. This
; "single" master implementation is limited to one bus master on the
; I2C bus.
;
; Based on the Atmel Application Note AVR300, corrected and adapted
; to GNU assembler and AVR-GCC C call interface
; Replaced the incorrect quarter period delays found in AVR300 with
; half period delays.
;
; USAGE
; These routines can be called from C, refere to file i2cmaster.h.
; See example test_i2cmaster.c
; Adapt the SCL and SDA port and pin definitions and eventually
; the delay routine to your target !
; Use 4.7k pull-up resistor on the SDA and SCL pin.
;
; NOTES
; The I2C routines can be called either from non-interrupt or
; interrupt routines, not both.
;
;*************************************************************************
#if (__GNUC__ * 100 + __GNUC_MINOR__) < 303
#error "This library requires AVR-GCC 3.3 or later, update to newer AVR-GCC compiler !"
#endif
#include <avr/io.h>
;***** Adapt these SCA and SCL port and pin definition to your target !!
;
#define SDA 1 // Changed by Dexter Industries
#define SCL 2 // Changed by Dexter Industries
#define SDA_PORT PORTB // Changed by Dexter Industries
#define SCL_PORT PORTB // Changed by Dexter Industries
;******
;– map the IO register back into the IO address space
#define SDA_DDR (_SFR_IO_ADDR(SDA_PORT) – 1)
#define SCL_DDR (_SFR_IO_ADDR(SCL_PORT) – 1)
#define SDA_OUT _SFR_IO_ADDR(SDA_PORT)
#define SCL_OUT _SFR_IO_ADDR(SCL_PORT)
#define SDA_IN (_SFR_IO_ADDR(SDA_PORT) – 2)
#define SCL_IN (_SFR_IO_ADDR(SCL_PORT) – 2)
#ifndef __tmp_reg__
#define __tmp_reg__ 0
#endif
.section .text
;*************************************************************************
; delay half period
; For I2C in normal mode (100kHz), use T/2 > 5us
; For I2C in fast mode (400kHz), use T/2 > 1.3us
;*************************************************************************
.stabs "",100,0,0,i2c_delay_T2
.stabs "i2cmaster.S",100,0,0,i2c_delay_T2
.func i2c_delay_T2 ; delay 5.0 microsec with 4 Mhz crystal
i2c_delay_T2: ; 4 cycles
; a nop delays 1 count.
; you have to align this up to the clock speed of the processor.
; To get 5.0 microseconds with a 16 Mhz crystal, you have to
; have 80 count delays. Without this, it won’t work.
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (5)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (10)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (15)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (20)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (25)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (30)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (35)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (40)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (45)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (50)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (55)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (60)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (65)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (70)
nop ; 1
nop ; 1 "
nop ; 1 "
ret ; 3 "
.endfunc ; total of 80 cycles = 5.0 microsec with 16 MHZ crystal
; total 20 cyles = 5.0 microsec with 4 Mhz crystal
;*************************************************************************
; Initialization of the I2C bus interface. Need to be called only once
;
; extern void i2c_init(void)
;*************************************************************************
.global i2c_init
.func i2c_init
i2c_init:
cbi SDA_DDR,SDA ;release SDA
cbi SCL_DDR,SCL ;release SCL
cbi SDA_OUT,SDA
cbi SCL_OUT,SCL
ret
.endfunc
;*************************************************************************
; Issues a start condition and sends address and transfer direction.
; return 0 = device accessible, 1= failed to access device
;
; extern unsigned char i2c_start(unsigned char addr);
; addr = r24, return = r25(=0):r24
;*************************************************************************
.global i2c_start
.func i2c_start
i2c_start:
sbi SDA_DDR,SDA ;force SDA low
rcall i2c_delay_T2 ;delay T/2
rcall i2c_write ;write address
ret
.endfunc
;*************************************************************************
; Issues a repeated start condition and sends address and transfer direction.
; return 0 = device accessible, 1= failed to access device
;
; extern unsigned char i2c_rep_start(unsigned char addr);
; addr = r24, return = r25(=0):r24
;*************************************************************************
.global i2c_rep_start
.func i2c_rep_start
i2c_rep_start:
sbi SCL_DDR,SCL ;force SCL low
rcall i2c_delay_T2 ;delay T/2
cbi SDA_DDR,SDA ;release SDA
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
rcall i2c_delay_T2 ;delay T/2
sbi SDA_DDR,SDA ;force SDA low
rcall i2c_delay_T2 ;delay T/2
rcall i2c_write ;write address
ret
.endfunc
;*************************************************************************
; Issues a start condition and sends address and transfer direction.
; If device is busy, use ack polling to wait until device is ready
;
; extern void i2c_start_wait(unsigned char addr);
; addr = r24
;*************************************************************************
.global i2c_start_wait
.func i2c_start_wait
i2c_start_wait:
mov __tmp_reg__,r24
i2c_start_wait1:
sbi SDA_DDR,SDA ;force SDA low
rcall i2c_delay_T2 ;delay T/2
mov r24,__tmp_reg__
rcall i2c_write ;write address
tst r24 ;if device not busy -> done
breq i2c_start_wait_done
rcall i2c_stop ;terminate write operation
rjmp i2c_start_wait1 ;device busy, poll ack again
i2c_start_wait_done:
ret
.endfunc
;*************************************************************************
; Terminates the data transfer and releases the I2C bus
;
; extern void i2c_stop(void)
;*************************************************************************
.global i2c_stop
.func i2c_stop
i2c_stop:
sbi SCL_DDR,SCL ;force SCL low
sbi SDA_DDR,SDA ;force SDA low
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
rcall i2c_delay_T2 ;delay T/2
cbi SDA_DDR,SDA ;release SDA
rcall i2c_delay_T2 ;delay T/2
ret
.endfunc
;*************************************************************************
; Send one byte to I2C device
; return 0 = write successful, 1 = write failed
;
; extern unsigned char i2c_write( unsigned char data );
; data = r24, return = r25(=0):r24
;*************************************************************************
.global i2c_write
.func i2c_write
i2c_write:
sec ;set carry flag
rol r24 ;shift in carry and out bit one
rjmp i2c_write_first
i2c_write_bit:
lsl r24 ;if transmit register empty
i2c_write_first:
breq i2c_get_ack
sbi SCL_DDR,SCL ;force SCL low
brcc i2c_write_low
nop
cbi SDA_DDR,SDA ;release SDA
rjmp i2c_write_high
i2c_write_low:
sbi SDA_DDR,SDA ;force SDA low
rjmp i2c_write_high
i2c_write_high:
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
rcall i2c_delay_T2 ;delay T/2
rjmp i2c_write_bit
i2c_get_ack:
sbi SCL_DDR,SCL ;force SCL low
cbi SDA_DDR,SDA ;release SDA
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
i2c_ack_wait:
sbis SCL_IN,SCL ;wait SCL high (in case wait states are inserted)
rjmp i2c_ack_wait
clr r24 ;return 0
sbic SDA_IN,SDA ;if SDA high -> return 1
ldi r24,1
rcall i2c_delay_T2 ;delay T/2
clr r25
ret
.endfunc
;*************************************************************************
; read one byte from the I2C device, send ack or nak to device
; (ack=1, send ack, request more data from device
; ack=0, send nak, read is followed by a stop condition)
;
; extern unsigned char i2c_read(unsigned char ack);
; ack = r24, return = r25(=0):r24
; extern unsigned char i2c_readAck(void);
; extern unsigned char i2c_readNak(void);
; return = r25(=0):r24
;*************************************************************************
.global i2c_readAck
.global i2c_readNak
.global i2c_read
.func i2c_read
i2c_readNak:
clr r24
rjmp i2c_read
i2c_readAck:
ldi r24,0x01
i2c_read:
ldi r23,0x01 ;data = 0x01
i2c_read_bit:
sbi SCL_DDR,SCL ;force SCL low
cbi SDA_DDR,SDA ;release SDA (from previous ACK)
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
rcall i2c_delay_T2 ;delay T/2
i2c_read_stretch:
sbis SCL_IN, SCL ;loop until SCL is high (allow slave to stretch SCL)
rjmp i2c_read_stretch
clc ;clear carry flag
sbic SDA_IN,SDA ;if SDA is high
sec ; set carry flag
rol r23 ;store bit
brcc i2c_read_bit ;while receive register not full
i2c_put_ack:
sbi SCL_DDR,SCL ;force SCL low
cpi r24,1
breq i2c_put_ack_low ;if (ack=0)
cbi SDA_DDR,SDA ; release SDA
rjmp i2c_put_ack_high
i2c_put_ack_low: ;else
sbi SDA_DDR,SDA ; force SDA low
i2c_put_ack_high:
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
i2c_put_ack_wait:
sbis SCL_IN,SCL ;wait SCL high
rjmp i2c_put_ack_wait
rcall i2c_delay_T2 ;delay T/2
mov r24,r23
clr r25
ret
.endfunc