Home Code PIC18F56Q71 I2C Scanner with OLED Display

PIC18F56Q71 I2C Scanner with OLED Display

by shedboy71
Curiosity Nano

An I2C scanner provides a practical way to test the PIC18F56Q71’s I2C interface and identify devices connected to the bus without needing to know their addresses in advance. The firmware systematically checks the valid I2C address range and records which addresses produce an acknowledgement.

Any responding addresses can then be displayed on the Curiosity Nano Explorer OLED, giving a quick visual indication of which devices the PIC18F56Q71 can actually communicate with.

This is particularly useful when experimenting with several sensor modules, GPIO expanders and other I2C peripherals on the same development setup.

The scanner is also a valuable troubleshooting tool when an I2C project fails to operate as expected. A device appearing at the expected address confirms several parts of the hardware path at once, including basic bus communication and the device’s ability to acknowledge a transaction.

An unexpected address can reveal address-selection differences between modules, while no detected devices provides a reason to investigate wiring, power, pull-up resistors, bus configuration and peripheral initialisation before debugging higher-level sensor code.

The project can therefore remain as a reusable utility alongside later PIC18F56Q71 projects, making it easy to check the state of an I2C bus whenever new hardware is connected.

 

Code

// 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>

#define SSD1306_I2C_ADDR 0x3D
#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

// --- Function Prototypes ---
void I2C_Delay(void);
void SW_I2C_Init(void);
void SW_I2C_Start(void);
void SW_I2C_Stop(void);
unsigned char SW_I2C_Write_8Bit_Frame(unsigned char frame_byte);
void SSD1306_Command_Data(unsigned char cmd);
void SSD1306_Command(unsigned char cmd);
void SSD1306_Data(unsigned char data);
void SSD1306_Init(void);
void SSD1306_Clear(void);
void SSD1306_DrawChar(char c, unsigned char page, unsigned char col);
void SSD1306_DrawString(const char *str, unsigned char page, unsigned char col);
void Byte_To_Hex(unsigned char byte, char* buffer);

// Font Table (ASCII 32 to 90)
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 Driver ---
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();
}

// Transmit 8-bit frame (Address << 1 | R/W) and sample ACK bit
unsigned char SW_I2C_Write_8Bit_Frame(unsigned char frame_byte) 
{
    for (unsigned char i = 0; i < 8; i++) 
    {
        SDA_PIN = ((frame_byte >> (7 - i)) & 0x01) ? 1 : 0;
        I2C_Delay();
        SCL_PIN = 1; I2C_Delay();
        SCL_PIN = 0; I2C_Delay();
    }
    
    // Release SDA line for ACK pulse
    SDA_PIN = 1; 
    I2C_Delay();
    SCL_PIN = 1; 
    I2C_Delay();
    
    unsigned char ack = SDA_IN; // 0 = ACK, 1 = NACK
    
    SCL_PIN = 0; 
    I2C_Delay();
    return ack;
}

// --- SSD1306 Display Engine ---
void SSD1306_Command_Data(unsigned char cmd)
{
    for (unsigned char i = 0; i < 8; i++) {
        SDA_PIN = ((cmd >> (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();
}

void SSD1306_Command(unsigned char cmd) 
{
    SW_I2C_Start();
    SW_I2C_Write_8Bit_Frame((SSD1306_I2C_ADDR << 1) | 0);
    SW_I2C_Write_8Bit_Frame(0x00);
    SSD1306_Command_Data(cmd);
    SW_I2C_Stop();
}

void SSD1306_Data(unsigned char data) 
{
    SW_I2C_Start();
    SW_I2C_Write_8Bit_Frame((SSD1306_I2C_ADDR << 1) | 0);
    SW_I2C_Write_8Bit_Frame(0x40);
    SSD1306_Command_Data(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 Byte_To_Hex(unsigned char byte, char* buffer) 
{
    const char hexChars[] = "0123456789ABCDEF";
    buffer[0] = hexChars[(byte >> 4) & 0x0F];
    buffer[1] = hexChars[byte & 0x0F];
    buffer[2] = '\0';
}

int main(void) 
{
    ANSELCbits.ANSELC7 = 0;
    TRISCbits.TRISC7   = 0; // Heartbeat LED (RC7)

    SW_I2C_Init();
    SSD1306_Init();
    SSD1306_Clear();

    SSD1306_DrawString("8-BIT I2C SCANNER", 0, 10);
    SSD1306_DrawString("7-BIT  8-BIT WRITE", 1, 0);

    unsigned char page = 3;
    char hex7Bit[3];
    char hex8Bit[3];

    for (unsigned char addr7 = 0x08; addr7 <= 0x77; addr7++) 
    {
        // Construct complete 8-bit wire frame: (7-bit address << 1) | Write Bit (0)
        unsigned char writeFrame8Bit = (addr7 << 1) | 0;

        SW_I2C_Start();
        unsigned char ack = SW_I2C_Write_8Bit_Frame(writeFrame8Bit);
        SW_I2C_Stop();

        if (ack == 0) // ACK received
        {
            Byte_To_Hex(addr7, hex7Bit);
            Byte_To_Hex(writeFrame8Bit, hex8Bit);

            // Output 7-bit and corresponding 8-bit shifted address
            SSD1306_DrawString("0X", page, 10);
            SSD1306_DrawString(hex7Bit, page, 22);
            SSD1306_DrawString(" -> ", page, 40);
            SSD1306_DrawString("0X", page, 70);
            SSD1306_DrawString(hex8Bit, page, 82);

            page++;
            if (page > 7) break;
        }

        HEARTBEAT_LED = !HEARTBEAT_LED;
        __delay_ms(10);
    }

    while (1) 
    {
        HEARTBEAT_LED = !HEARTBEAT_LED;
        __delay_ms(500);
    }

    return 0;
}

Image

 

Share

You may also like

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More