On the Nano explorer there is an MCP9808, so we will add the ability to use this sensor with the OLED. So we will modify the previous OLED example to add this.
The MCP9808 temperature sensor adds a practical measurement stage to the existing PIC18F56Q71 Curiosity Nano Explorer OLED project.
Rather than displaying fixed information, the firmware now communicates with the MCP9808 over I2C, reads its digital temperature data, converts the returned value into a usable temperature measurement and presents the result on the OLED.
The project is developed in Visual Studio Code and builds on the previously established display code, allowing the new work to concentrate on sensor communication and handling the temperature data.
This example also demonstrates a typical embedded sensor-to-display data path. The PIC18F56Q71 acts as the I2C controller, communicates with the MCP9808 at its configured bus address, obtains the temperature register data and processes the result before updating the OLED.
This provides a useful foundation for more substantial monitoring projects where several I2C sensors may share the same bus or where measurements need to be filtered, logged, compared against thresholds or used to trigger alarms.
It also provides a practical introduction to I2C transactions before moving on to more complex sensors with larger register maps and multiple measurement values.
Demo

- MCP9808 + SSD1306
Code example
// Configuration Bits for PIC18F56Q71
#pragma config FEXTOSC = OFF
#pragma config RSTOSC = HFINTOSC_1MHZ // Software switches to 16MHz
#pragma config CLKOUTEN = OFF
#pragma config CSWEN = ON
#pragma config MCLRE = EXTMCLR
#pragma config PWRTS = PWRT_OFF
#pragma config WDTE = OFF
#define _XTAL_FREQ 16000000
#include <xc.h>
// I2C Addresses on Explorer Board
#define SSD1306_I2C_ADDR 0x3D
#define MCP9808_I2C_ADDR 0x1C // Fixed address: 0x1C
#define MCP9808_REG_TA 0x05
#define HEARTBEAT_LED LATCbits.LATC7
// Software I2C Pin Definitions (RC3 = SCL, RC4 = SDA)
#define SCL_PIN TRISCbits.TRISC3
#define SDA_PIN TRISCbits.TRISC4
#define SDA_IN PORTCbits.RC4
// Font Table (ASCII 32 to 90: ' ' through 'Z')
const unsigned char FONT_5x7[][5] = {
{0x00, 0x00, 0x00, 0x00, 0x00}, // ' '
{0x00, 0x00, 0x5F, 0x00, 0x00}, // '!'
{0x00, 0x07, 0x00, 0x07, 0x00}, // '"'
{0x14, 0x7F, 0x14, 0x7F, 0x14}, // '#'
{0x24, 0x2A, 0x7F, 0x2A, 0x12}, // '$'
{0x23, 0x13, 0x08, 0x64, 0x62}, // '%'
{0x36, 0x49, 0x55, 0x22, 0x50}, // '&'
{0x00, 0x05, 0x03, 0x00, 0x00}, // '\''
{0x00, 0x1C, 0x22, 0x41, 0x00}, // '('
{0x00, 0x41, 0x22, 0x1C, 0x00}, // ')'
{0x14, 0x08, 0x3E, 0x08, 0x14}, // '*'
{0x08, 0x08, 0x3E, 0x08, 0x08}, // '+'
{0x00, 0x50, 0x30, 0x00, 0x00}, // ','
{0x08, 0x08, 0x08, 0x08, 0x08}, // '-'
{0x00, 0x60, 0x60, 0x00, 0x00}, // '.'
{0x20, 0x10, 0x08, 0x04, 0x02}, // '/'
{0x3E, 0x51, 0x49, 0x45, 0x3E}, // '0'
{0x00, 0x42, 0x7F, 0x40, 0x00}, // '1'
{0x42, 0x61, 0x51, 0x49, 0x46}, // '2'
{0x21, 0x41, 0x45, 0x4B, 0x31}, // '3'
{0x18, 0x14, 0x12, 0x7F, 0x10}, // '4'
{0x27, 0x45, 0x45, 0x45, 0x39}, // '5'
{0x3C, 0x4A, 0x49, 0x49, 0x30}, // '6'
{0x01, 0x71, 0x09, 0x05, 0x03}, // '7'
{0x36, 0x49, 0x49, 0x49, 0x36}, // '8'
{0x06, 0x49, 0x49, 0x29, 0x1E}, // '9'
{0x00, 0x36, 0x36, 0x00, 0x00}, // ':'
{0x00, 0x56, 0x36, 0x00, 0x00}, // ';'
{0x08, 0x14, 0x22, 0x41, 0x00}, // '<'
{0x14, 0x14, 0x14, 0x14, 0x14}, // '='
{0x00, 0x41, 0x22, 0x14, 0x08}, // '>'
{0x02, 0x01, 0x51, 0x09, 0x06}, // '?'
{0x32, 0x49, 0x79, 0x41, 0x3E}, // '@'
{0x7E, 0x11, 0x11, 0x11, 0x7E}, // 'A'
{0x7F, 0x49, 0x49, 0x49, 0x36}, // 'B'
{0x3E, 0x41, 0x41, 0x41, 0x22}, // 'C'
{0x7F, 0x41, 0x41, 0x22, 0x1C}, // 'D'
{0x7F, 0x49, 0x49, 0x49, 0x41}, // 'E'
{0x7F, 0x09, 0x09, 0x09, 0x01}, // 'F'
{0x3E, 0x41, 0x49, 0x49, 0x7A}, // 'G'
{0x7F, 0x08, 0x08, 0x08, 0x7F}, // 'H'
{0x00, 0x41, 0x7F, 0x41, 0x00}, // 'I'
{0x20, 0x40, 0x41, 0x3F, 0x01}, // 'J'
{0x7F, 0x08, 0x14, 0x22, 0x41}, // 'K'
{0x7F, 0x40, 0x40, 0x40, 0x40}, // 'L'
{0x7F, 0x02, 0x0C, 0x02, 0x7F}, // 'M'
{0x7F, 0x04, 0x08, 0x10, 0x7F}, // 'N'
{0x3E, 0x41, 0x41, 0x41, 0x3E}, // 'O'
{0x7F, 0x09, 0x09, 0x09, 0x06}, // 'P'
{0x3E, 0x41, 0x51, 0x21, 0x5E}, // 'Q'
{0x7F, 0x09, 0x19, 0x29, 0x46}, // 'R'
{0x46, 0x49, 0x49, 0x49, 0x31}, // 'S'
{0x01, 0x01, 0x7F, 0x01, 0x01}, // 'T'
{0x3F, 0x40, 0x40, 0x40, 0x3F}, // 'U'
{0x1F, 0x20, 0x40, 0x20, 0x1F}, // 'V'
{0x3F, 0x40, 0x38, 0x40, 0x3F}, // 'W'
{0x63, 0x14, 0x08, 0x14, 0x63}, // 'X'
{0x07, 0x08, 0x70, 0x08, 0x07}, // 'Y'
{0x61, 0x51, 0x49, 0x45, 0x43} // 'Z'
};
// --- Low-Level Software I2C Engine ---
void I2C_Delay(void) { __delay_us(4); }
void SW_I2C_Init(void)
{
OSCCON1 = 0x60; OSCFRQ = 0x05; while (!OSCCON3bits.ORDY); // 16 MHz
ANSELCbits.ANSELC3 = 0; ANSELCbits.ANSELC4 = 0;
LATCbits.LATC3 = 0; LATCbits.LATC4 = 0;
WPUCbits.WPUC3 = 1; WPUCbits.WPUC4 = 1;
SCL_PIN = 1; SDA_PIN = 1;
I2C_Delay();
}
void SW_I2C_Start(void)
{
SDA_PIN = 1; SCL_PIN = 1; I2C_Delay();
SDA_PIN = 0; I2C_Delay();
SCL_PIN = 0; I2C_Delay();
}
void SW_I2C_Stop(void)
{
SDA_PIN = 0; SCL_PIN = 0; I2C_Delay();
SCL_PIN = 1; I2C_Delay();
SDA_PIN = 1; I2C_Delay();
}
void SW_I2C_Write(unsigned char byte)
{
for (unsigned char i = 0; i < 8; i++)
{
SDA_PIN = ((byte >> (7 - i)) & 0x01) ? 1 : 0;
I2C_Delay();
SCL_PIN = 1; I2C_Delay();
SCL_PIN = 0; I2C_Delay();
}
SDA_PIN = 1; I2C_Delay();
SCL_PIN = 1; I2C_Delay();
SCL_PIN = 0; I2C_Delay();
}
unsigned char SW_I2C_Read(unsigned char ack)
{
unsigned char byte = 0;
SDA_PIN = 1; // Release line
for (unsigned char i = 0; i < 8; i++)
{
SCL_PIN = 1; I2C_Delay();
byte = (byte << 1) | (SDA_IN & 0x01);
SCL_PIN = 0; I2C_Delay();
}
SDA_PIN = ack ? 0 : 1; // Send ACK or NACK
I2C_Delay();
SCL_PIN = 1; I2C_Delay();
SCL_PIN = 0; I2C_Delay();
SDA_PIN = 1;
return byte;
}
// --- MCP9808 Temperature Sensor Driver ---
float MCP9808_ReadTemperature(void)
{
SW_I2C_Start();
SW_I2C_Write((MCP9808_I2C_ADDR << 1) | 0); // Write Address (0x38)
SW_I2C_Write(MCP9808_REG_TA); // Select TA Register (0x05)
SW_I2C_Start();
SW_I2C_Write((MCP9808_I2C_ADDR << 1) | 1); // Read Address (0x39)
unsigned char upperByte = SW_I2C_Read(1); // Read with ACK
unsigned char lowerByte = SW_I2C_Read(0); // Read with NACK
SW_I2C_Stop();
// Clear flag bits 15, 14, 13
upperByte &= 0x1F;
if ((upperByte & 0x10) == 0x10) // Sign Bit Check (< 0°C)
{
upperByte &= 0x0F;
return 256.0f - (((float)upperByte * 16.0f) + ((float)lowerByte / 16.0f));
}
return (((float)upperByte * 16.0f) + ((float)lowerByte / 16.0f));
}
// --- SSD1306 Display Engine ---
void SSD1306_Command(unsigned char cmd)
{
SW_I2C_Start();
SW_I2C_Write((SSD1306_I2C_ADDR << 1) | 0);
SW_I2C_Write(0x00);
SW_I2C_Write(cmd);
SW_I2C_Stop();
}
void SSD1306_Data(unsigned char data)
{
SW_I2C_Start();
SW_I2C_Write((SSD1306_I2C_ADDR << 1) | 0);
SW_I2C_Write(0x40);
SW_I2C_Write(data);
SW_I2C_Stop();
}
void SSD1306_Init(void)
{
__delay_ms(100);
SSD1306_Command(0xAE);
SSD1306_Command(0xD5); SSD1306_Command(0x80);
SSD1306_Command(0xA8); SSD1306_Command(0x3F);
SSD1306_Command(0xD3); SSD1306_Command(0x00);
SSD1306_Command(0x40);
SSD1306_Command(0x8D); SSD1306_Command(0x14);
SSD1306_Command(0x20); SSD1306_Command(0x00);
SSD1306_Command(0xA1);
SSD1306_Command(0xC8);
SSD1306_Command(0xDA); SSD1306_Command(0x12);
SSD1306_Command(0x81); SSD1306_Command(0xCF);
SSD1306_Command(0xD9); SSD1306_Command(0xF1);
SSD1306_Command(0xDB); SSD1306_Command(0x40);
SSD1306_Command(0xA4);
SSD1306_Command(0xA6);
SSD1306_Command(0xAF);
}
void SSD1306_Clear(void)
{
for (unsigned char page = 0; page < 8; page++)
{
SSD1306_Command(0xB0 + page);
SSD1306_Command(0x00);
SSD1306_Command(0x10);
for (unsigned char col = 0; col < 128; col++)
{
SSD1306_Data(0x00);
}
}
}
void SSD1306_DrawChar(char c, unsigned char page, unsigned char col)
{
if (c >= 'a' && c <= 'z') c -= 32;
if (c < 32 || c > 90) c = ' ';
unsigned char font_index = c - 32;
SSD1306_Command(0xB0 + page);
SSD1306_Command(0x00 + (col & 0x0F));
SSD1306_Command(0x10 + ((col >> 4) & 0x0F));
for (unsigned char i = 0; i < 5; i++)
{
SSD1306_Data(FONT_5x7[font_index][i]);
}
SSD1306_Data(0x00);
}
void SSD1306_DrawString(const char *str, unsigned char page, unsigned char col)
{
while (*str && col < 122)
{
SSD1306_DrawChar(*str++, page, col);
col += 6;
}
}
void Format_Temp_String(float tempC, char* buffer)
{
int whole = (int)tempC;
int decimal = (int)((tempC - (float)whole) * 100.0f);
if (decimal < 0) decimal = -decimal;
buffer[0] = (whole / 10) + '0';
buffer[1] = (whole % 10) + '0';
buffer[2] = '.';
buffer[3] = (decimal / 10) + '0';
buffer[4] = (decimal % 10) + '0';
buffer[5] = ' ';
buffer[6] = 'C';
buffer[7] = '\0';
}
int main(void)
{
ANSELCbits.ANSELC7 = 0;
TRISCbits.TRISC7 = 0; // Heartbeat LED (RC7)
SW_I2C_Init();
SSD1306_Init();
SSD1306_Clear();
SSD1306_DrawString("PIC18F56Q71 NANO", 1, 14);
SSD1306_DrawString("MCP9808 SENSOR", 3, 20);
char tempStr[10];
while (1)
{
float tempC = MCP9808_ReadTemperature();
Format_Temp_String(tempC, tempStr);
SSD1306_DrawString("TEMP: ", 5, 20);
SSD1306_DrawString(tempStr, 5, 56);
HEARTBEAT_LED = !HEARTBEAT_LED;
__delay_ms(500);
}
return 0;
}
Flash this code using MPLAB: Program Device to start displaying live temperature readings on the OLED screen.
Video
This shows me touching the sensor and you can see the values increasing

