Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PIC16F877AA - NON-CONTACT THERMOMETER

gabrielgu

New Member
I'm developing a project with the PIC16F877A, the MLX90614 sensor and the oled display SSD1306, the code compiled normally but in the simulation the display receives a temperature that has nothing to do with the sensor. I believe I'm missing out on setting up something of I2C in the code, but I don't know what it might be. Can you help me?
C:
/********************************************** *******************************************
Used Crystal Oscillator @ 8MHz
This is free software with no warranty.
[URL]http://simple-circuit.com/[/URL]
************************************************** *************************************/
#use FAST_IO(C)
//OLED SSD1306 Reset Pin Setting

#define SSD1306_RST PIN_D4

//MLX90614 I2C Address
#define MLX90614_I2C_ADDRESS 0x5A

#define AMB_TEMP 0X06 #define OBJ_TEMP 0X07

#incluir <16F877A.h>
#fusíveis HS, NOWDT, NOPROTECT, NOLVP #use
delay(clock = 8MHz)
#use I2C(MASTER, I2C1, FAST = 400000, stream = SSD1306_STREAM) // initializes I2C

// includes the source code of the SSD1306 OLED
driver #incluir <SSD1306.c>

int16 temperature;
character degree[] = {0, 7, 5, 7, 0}; custom



degree symbol // Function to read the temperature of the MLX90614
int16 Ler_Sensor(char Temp_Source) {
int16 Var_Temp;

i2c_start();
if(!i2c_write(MLX90614_I2C_ADDRESS << 1)) {
returns false; // If unable to initiate I2C communication, return false
}
i2c_write(Temp_Fonte);
i2c_start();
i2c_write((MLX90614_I2C_ADDRESS << 1) | 1); Least significant bit (LSB) set to read
Var_Temp = i2c_read(0);
Var_Temp = (i2c_read(0) << 8) + Var_Temp;
i2c_stop();

return Temp_var;

}
// Function to clean only the temperature area on the OLED
display void Limpar_Temperatura() {
SSD1306_GotoXY(6, 7); // Position the cursor at the temperature
position SSD1306_PutC(" "); Write blanks to clear the area of the temperature
SSD1306_GotoXY(11, 7); Positions the cursor at the position of the degree
symbol SSD1306_PutC(" "); Clears the degree
symbol }

//Function to show the temperature on the OLED
display void Mostrar_Temperatura(char Temp_Source, int16 temperature) {
if(Temp_Source == AMB_TEMP || Temp_Source == OBJ_TEMP) {
SSD1306_GotoXY(6, 7);
printf(SSD1306_PutC, "%ld.%02ld", temperature / 100, abs(temperature) % 100); // Displays the temperature to two decimal
places SSD1306_GotoXY(11, 7);
SSD1306_PutCustomC(grade); degree symbol (°)
}
other {
SSD1306_GotoXY(6, 7); // Go to column 4, row 2
SSD1306_PutC("Error!");
}
}

//main
function main empty(empty) {
atraso_ms(1000);

// Initializes OLED SSD1306 with an I2C address = 0x7A (default address)
setup_comparador(NC_NC_NC_NC);
setup_adc_ports(NO_ANALOGS);
SSD1306_Init(SSD1306_SWITCHCAPVCC, SSD1306_I2C_ADDRESS);

SSD1306_GotoXY(5, 2);
SSD1306_PutC("SENSOR MLX90614");
SSD1306_GotoXY(6, 5);
SSD1306_PutC("TEMPERATURE:");

i2c_Init(MLX90614_I2C_ADDRESS);
atraso_ms(2000);

while (TRUE) {
// Clean only the temperature area before displaying the new reading
Limpar_Temperatura();

// Read the ambient
temperature Temp = Ler_Sensor(AMB_TEMP);
Mostrar_Temperatura(AMB_TEMP,Temp);

atraso_ms(2000); Wait 2 seconds before clearing the display and taking a new reading

// Clear the screen before displaying the next reading
Limpar_Temperatura();

// Read the object's
temperature Temp = Ler_Sensor(OBJ_TEMP);
Mostrar_Temperatura(OBJ_TEMP,Temp);

atraso_ms(2000); Wait 2 seconds before clearing the display and taking a new scan
}

}

English only please MOD edit
 
Last edited:
I assume you meant PIC16F877A as there is no such thing as a 977A

The PIC16F877A is getting a bit old and you might want to look at something newer.

The PIC16F877A has a Master Synchronous Serial Port (MSSP) which I assume is being used. There is also a library that you are using.

The problem is that you don't know what the library functions are doing to set up and send data to the MSSP, and you don't know what signals are being sent and received by the port, so you don't really have any way of knowing what step isn't working.

If you have an oscilloscope or logic analyser, use it to read the I2C lines. With an oscilloscope, it can help to add a resistor of a few hundred Ohms in series with the data line. That way, the voltages when low will be slightly different when pulled down by the master and when pulled down by the slave, and that can help when interpreting the oscilloscope.

If you don't have an oscilloscope, you may be able to run the I2C really, really slowly, so that LEDs or similar could be used to debug.

There is not a lot to setting up and running the MSSP on a PIC16F877A without a library, and Microchip have an application notes.

https://ww1.microchip.com/downloads/en/AppNotes/00000734C.pdf

The source code is now archived at

https://web.archive.org/web/20051101200354/http://ww1.microchip.com/downloads/en/AppNotes/00734.zip
 
Do you have the required pull-up resistors on the I2C clock and data lines?

you have SSD1306_Init rather than SSD1306_Begin ?? The library info uses Begin, the linked example uses Init - which is correct??
 
Do you have the required pull-up resistors on the I2C clock and data lines?

you have SSD1306_Init rather than SSD1306_Begin ?? The library info uses Begin, the linked example uses Init - which is correct??
in the library it is INIT, and it is in the code too. My display is working, however the temperature value is different. I believe that everything is fine about the display and the library, the problem I believe is in reading data from the sensor.

void SSD1306_Init(uint8_t vccstate = SSD1306_SWITCHCAPVCC, uint8_t i2caddr = SSD1306_I2C_ADDRESS) {
_vccstate = vccstate;
_i2caddr = i2caddr;
#ifdef SSD1306_RST
output_low(SSD1306_RST);
output_drive(SSD1306_RST);
delay_ms(10);
output_high(SSD1306_RST);
#endif
 
Reduce the I2C clock speed from 400000 to 100000 or below.
100KHz is the maximum permissible, from the MLX datasheet.

How is the MLX sensor mounted? From the data, it must be thermally isolated - the metalwork shielded from any other heat source.


Before anything else, the SCL line must be held low for a time to switch the MLX from PWM output to I2C mode - you do not appear to be doing that?

Go through section 8.4.7 of the device data very carefully!

 
Well, I made some changes, I used the Arduino library for mlx as a base. I know the problem is with read(True), but I don't know how to solve it. the code is like this.
Code:
/***************************************************************************************
 Oscilador de cristal usado @ 8MHz
 Este é um software gratuito e sem garantia.
 http://simple-circuit.com/
***************************************************************************************/


// Definição do pino de reset do SSD1306 OLED
#define SSD1306_RST    PIN_D4


// Endereço I2C do MLX90614
#define MLX90614_DEFAULT_ADDRESS 0x5A   
#define I2C_READ_TIMEOUT 1000


//MLX614 RAM and EEPROM ADDRESS
#define MLX90614_REGISTER_TA      0x06
#define MLX90614_REGISTER_TOBJ1   0x07
#define MLX90614_REGISTER_TOBJ2   0x08
#define MLX90614_REGISTER_TOMAX   0x20
#define MLX90614_REGISTER_TOMIN   0x21
#define MLX90614_REGISTER_PWMCTRL 0x22
#define MLX90614_REGISTER_TARANGE 0x23
#define MLX90614_REGISTER_KE      0x24
#define MLX90614_REGISTER_CONFIG  0x25
#define MLX90614_REGISTER_ADDRESS 0x2E
#define MLX90614_REGISTER_ID0     0x3C
#define MLX90614_REGISTER_ID1     0x3D
#define MLX90614_REGISTER_ID2     0x3E
#define MLX90614_REGISTER_ID3     0x3F
#define MLX90614_REGISTER_SLEEP   0xFF

#include <16F877A.h>
#fuses HS, NOWDT, NOPROTECT, NOLVP
#use delay(clock = 8MHz)
#use I2C(MASTER, I2C1, FAST = 100000, stream = SSD1306_STREAM)  // inicializa I2C
#use standard_io(C)

// inclui o código-fonte do driver SSD1306 OLED
#include <SSD1306.c>
int16 _rawAmbient, _rawObject, _rawObject2, _rawMax, _rawMin;
unsigned char _deviceAddress;


typedef enum temperature_units{
   TEMP_RAW,
   TEMP_K,
   TEMP_C,
   TEMP_F,
};

temperature_units _defaultUnit;


unsigned char begin(unsigned char address = MLX90614_DEFAULT_ADDRESS)
{
   _deviceAddress = address;
   i2c_start();               
    if(!i2c_write(MLX90614_DEFAULT_ADDRESS << 1)) {
        return FALSE; // Se não for possível iniciar a comunicação I2C, retorne falso
    }
}

void setUnit(temperature_units unit)
{
   _defaultUnit = unit;
}

unsigned int16 id[4];



int16 calcRawTemp(float calcTemp)
{
   int16 rawTemp;
   if(_defaultUnit == TEMP_RAW)
   { 
      rawTemp = (int16) calcTemp;
   }
   else
   {
      float tempFloat;
      if(_defaultUnit == TEMP_F)
      {
         tempFloat = (calcTemp - 32.0) * 5.0 / 9.0 + 273.15;
      }
      else if (_defaultUnit == TEMP_C)
      {
         tempFloat = calcTemp + 273.15;
      }
      else if (_defaultUnit == TEMP_K)
      {
         tempFloat = calcTemp;
      }
      tempFloat *= 50;
      rawTemp = (int16) tempFloat;
   }
   return rawTemp;
}

float calcTemperature(int16 rawTemp)
{
   float retTemp;
   if (_defaultUnit == TEMP_RAW)
   { 
      retTemp = (float) rawTemp;
   }
   else
   {
      retTemp =  (rawTemp) * 0.02;
      if (_defaultUnit != TEMP_K)
      {
         retTemp -= 273.15;
         if (_defaultUnit == TEMP_F)
         {
            retTemp = retTemp * 9.0 / 5.0 + 32;
         }
      } 
   }
   return retTemp;
}
float minimo(void)
{
   return calcTemperature(_rawMin);
}


float maximo()
{
   return calcTemperature(_rawMax);
}

float object()
{
   return calcTemperature(_rawObject);
}

float ambient()
{
   return calcTemperature(_rawAmbient);
}
unsigned char crc8 (unsigned char inCrc, unsigned char inData)
{
   unsigned char i;
   unsigned char data;
   data = inCrc ^ inData;
   for ( i = 0; i < 8; i++)
   {
         if (( data & 0x80) != 0)
         {
            data <<=1;
            data ^= 0x07;
         }
         else
         {
            data <<= 1;
         }
         return data;
   }
}
unsigned char sleep()
{
   unsigned char crc = crc8(0, (_deviceAddress << 1));
   crc = crc8(crc, MLX90614_REGISTER_SLEEP);
  
   i2c_start();
   i2c_write(_deviceAddress);
   i2c_write(MLX90614_REGISTER_SLEEP);
   i2c_write(crc);
   i2c_stop();
  
   output_high(PIN_C3);
   output_bit(PIN_C3, 0);
   input(PIN_C4);
}
unsigned char wake()
{
   input(PIN_C3);
   output_high(PIN_C4);
   output_bit(PIN_C4,0);
   delay_ms(50);
   input(PIN_C4);
   delay_ms(250);
 //PWM SMBUS MODE
   output_high(PIN_C3);
   output_bit(PIN_C3,0);
   delay_ms(10);
   input(PIN_C3);
   i2c_start();
}
  
unsigned char I2CReadWord(byte reg, int16 *dest)
{
   int timeout = I2C_READ_TIMEOUT;
  
   i2c_start();
   i2c_write(_deviceAddress);
   i2c_write(reg);
   i2c_stop();
  
   i2c_start();
   i2c_write((_deviceAddress << 1) | 1);
  
   while (!i2c_poll() && (timeout --> 0))
      delay_ms(10);
   if (timeout <= 0)
   return 0;
  
   unsigned char lsb = i2c_read();
   unsigned char msb = i2c_read();
   unsigned char pec = i2c_read();
  
   unsigned char crc = crc8 (0, (_deviceAddress << 1));
   crc = crc8(crc, reg);
   crc = crc8(crc, (_deviceAddress << 1) + 1);
   crc = crc8(crc, lsb);
   crc = crc8(crc, msb);
  
   if (crc == pec)
   {
         *dest = (msb << 8) | lsb;
         return 1;
   }
   else
   { 
         return 0;
   }
}
unsigned char readMax()
{
   int16 toMax;
   if(I2CReadWord(MLX90614_REGISTER_TOMAX, &toMax))
   {
      _rawMax = toMax;
      return 1;
   }
   return 0;
}
unsigned char readMin()
{
   int16 toMin;
   if(I2CReadWord(MLX90614_REGISTER_TOMIN, &toMin))
   {
      _rawMin = toMin;
      return 1;
   }
   return 0;
}

unsigned char readRange()
{
   if(readMin() && readMax())
   {
      return 1;
   }
   return 0;
}

unsigned char readAmbient()
{
   int16 rawAmb;
   if(I2CReadWord(MLX90614_REGISTER_TA, &rawAmb))
   {
      _rawAmbient = rawAmb;
      return 1;
   }
   return 0;
}
unsigned char readObject()
{
   int16 rawObj;
   if(I2CReadWord(MLX90614_REGISTER_TOBJ1, &rawObj))
   {
      if(rawObj & 0x8000)
      {
         return 0;
      }
      _rawObject = rawObj;
      return 1;
   }
   return 0;
}
unsigned char readObject2()
{
   int16 rawObj;
   if(I2CReadWord(MLX90614_REGISTER_TOBJ2, &rawObj))
   {
      if(rawObj & 0x8000)
      {
         return 0;
      }
      _rawObject2 = rawObj;
      return 1;
   }
   return 0;
}

unsigned char read()
{
   if(readObject() && readAmbient())
   {
     return 1;
   }
     return 0;
}

unsigned char setAddress(unsigned char newAdd)
{
   int16 tempAdd;
   if((newAdd >= 0x80) || (newAdd == 0x00))
   return 0;
  
   if (I2CReadWord(MLX90614_REGISTER_ADDRESS, &tempAdd))
   {
      tempAdd &= 0xFF00;
      tempAdd |= newAdd;
   }
   return 0;
}

unsigned char readAddress(void)
{
   int16 tempAdd;
   if(I2CReadWord(MLX90614_REGISTER_ADDRESS, &tempAdd))
   {
      return (unsigned char) tempAdd;
   }
   return 0;

}

unsigned char readID(void)
{
   for (int i=0; i<4; i++)
   {
      int16 temp = 0;
      if(!I2CReadWord(MLX90614_REGISTER_ID0 + i, &temp))
      return 0;
      id[1] = (unsigned int16) temp;
   }
   return 1;
}

unsigned int getIDH(void)
{
   return ((unsigned int)id[3] << 16) | id[2];
}

unsigned int getIDL(void)
{
   return ((unsigned int)id[1] << 16) | id[0];
}   
  
unsigned char I2CWriteWord(byte reg, int16 data)
{ 
   unsigned char crc;
   unsigned char lsb = data & 0x00FF;
   unsigned char msb = (data >> 8);
  
   crc = crc8(0, (_deviceAddress <<1));
   crc = crc8(crc, reg);
   crc = crc8(crc, lsb);
   crc = crc8(crc, msb);
  
   i2c_start();
   i2c_write(_deviceAddress);
   i2c_write(reg);
   i2c_write(lsb);
   i2c_write(msb);
   i2c_write(crc);
   return i2c_poll(true);
   i2c_stop();
 
}
unsigned char writeEEPROM(byte reg, int16 data)
{
   if(I2CWriteWord(reg, 0) != 0)
      return 0;
   delay_ms(50);
  
   unsigned char i2cRet = I2CWriteWord(reg, data);
   delay_ms(50);
  
   if (i2cRet == 0)
      return 1;
   else
      return 0;
}
unsigned char setMin(float minTemp)
{
   int16 rawMin = calcRawTemp(minTemp);
   return writeEEPROM(MLX90614_REGISTER_TOMIN, rawMin);
}

unsigned char setMax(float maxTemp)
{
   int16 rawMax = calcRawTemp(maxTemp);
   return writeEEPROM(MLX90614_REGISTER_TOMAX, rawMax);
}


void setup()
{
setUnit(TEMP_C);
}

void Limpar_Temperatura() {
    SSD1306_GotoXY(6, 7);           // Posiciona o cursor na posição da temperatura
    SSD1306_PutC("        ");      // Escreve espaços em branco para limpar a área da temperatura
    SSD1306_GotoXY(11, 7);          // Posiciona o cursor na posição do símbolo de grau
    SSD1306_PutC(" ");              // Limpa o símbolo de grau
}

int16 Temp;
char degree[] = {0, 7, 5, 7, 0};    // símbolo de grau personalizado


// função principal
void main (void) {
    delay_ms(1000);
   {

    // Inicializa o OLED SSD1306 com um endereço I2C = 0x7A (endereço padrão)
    SSD1306_Init(SSD1306_SWITCHCAPVCC, SSD1306_I2C_ADDRESS);
    SSD1306_GotoXY(5, 2);
    SSD1306_PutC("SENSOR MLX90614");
    SSD1306_GotoXY(6, 5);
    SSD1306_PutC("TEMPERATURA:");
    
    i2c_Init(MLX90614_DEFAULT_ADDRESS);
    delay_ms(2000);    }
 
        
    while (TRUE)
    { 
          if (read())
    {
    int16 temp = readAmbient();
    int16 parteInteira = temp/100;
    int16 parteDecimal = abs(temp) % 100;
    
        SSD1306_GotoXY(6, 7);
        printf(SSD1306_PutC, "%ld.%02ld", parteInteira, parteDecimal); // Exibe a temperatura com duas casas decimais
        SSD1306_GotoXY(11, 7);
        SSD1306_PutCustomC(degree);     // símbolo de grau (°)
   }
   else {
       SSD1306_GotoXY(6, 7);           // Vá para a coluna 4, linha 2
       SSD1306_PutC("  Erro!  ");
}
}

        delay_ms(2000); // Aguarde 2 segundos antes de limpar o display e fazer uma nova leitura
        
        // Limpa a tela antes de exibir a próxima leitura
        Limpar_Temperatura();

    }
 
Reduce the I2C clock speed from 400000 to 100000 or below.
100KHz is the maximum permissible, from the MLX datasheet.

How is the MLX sensor mounted? From the data, it must be thermally isolated - the metalwork shielded from any other heat source.


Before anything else, the SCL line must be held low for a time to switch the MLX from PWM output to I2C mode - you do not appear to be doing that?

Go through section 8.4.7 of the device data very carefully!

I haven't built the physical prototype yet, I'm using proteus to simulate.
 
The wake() routine looks like it may be the part that switches the sensor to I2C mode - but I don't think you can manipulate the I2C bus pins while the hardware I2C is enabled, that takes them over.

You could use an additional pin in open collector (or open drain) mode, connected to the SCL line, to allow you to pull it low for a couple of milliseconds. I have no idea what that would do to the display though?
You may have to use separate I/O for the sensor and display. Or use display that works via SPI and just have the MLX on the U2C bus?
 

Latest threads

New Articles From Microcontroller Tips

Back
Top