Sup, guys. I'm in the violent war with PIC MCUs and they're clearly winning, so I'm asking once again for your intellectual support.

I have PIC16F877A on a breadboard, external 8Mhz+2x20pf, bypassed, Vpp pulled up with 47k. Windows 10, MPLAB X 5.40, XC8 2.20.

I finally got the stuff running, turned out I had breadboard side issues all the time and not lack of IQ, my code was actually correct all the time (which I realized with half of my hair already pulled out). Now I blink like a boss, so I decided to connect to common 0.96 inch OLED (I have ssd1306 datasheet). The OLED is connected appropriately, powered etc. I2C has 10k pullups that worked like a charm for me in other non-PIC projects with the very same OLED.

I feel comfortable around datasheets and registers.

So I found a nice vid and followed it with MCU's datasheet open on the 2nd monitor to make sure I understand what I'm doing. Good news: I understand what I'm doing. Bad news: it doesn't work.

This is the vid: PIC I2C

I have an oscilloscope, so I hooked up to SCL and SDA, set up a trigger and saw this wonderful thing:

enter image description here

Considering it's already second PIC MCU where I can't get I2C running (the other one is PIC16F18326). For starters, I just want to see some communication and not a flatline. I use LED at RC6 pin for debugging to make sure the code reaches certain part and doesn't lock somewhere up and loop forever. So far, the LED always goes off:

You'll probably want to see my code, there you go:

// PIC16F877A Configuration Bit Settings

// 'C' source line config statements

#pragma config FOSC = HS        // Oscillator Selection bits (XT oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = ON         // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3/PGM pin has PGM function; low-voltage programming enabled)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#include <xc.h>
#include <htc.h>
#include <stdio.h>
#include <stdint.h>

#define _XTAL_FREQ 8000000

void i2c_is_idle(void){
    PORTCbits.RC6=1; //RC6 LED ON
    while(SSPCON2bits.SEN==1 || SSPCON2bits.RSEN==1 || SSPCON2bits.PEN==1 || SSPCON2bits.RCEN==1 || SSPCON2bits.ACKEN==1 || SSPSTATbits.R_nW==1){};
    PORTCbits.RC6=0; //RC6 LED OFF

void i2c_start(void){
    SSPCON2bits.SEN = 1;

void i2c_rep_start(void){
    SSPCON2bits.RSEN = 1;

void i2c_stop(void){
    SSPCON2bits.PEN = 1;

void i2c_write(uint8_t i2c_data){
    SSPBUF = i2c_data;
    while(SSPSTATbits.BF != 0);
    while(SSPCON2bits.ACKSTAT != 0);

uint8_t i2c_read(uint8_t ack){
    uint8_t recieve =0;
    SSPCON2bits.RCEN = 1;
    while(SSPSTATbits.BF != 1);
    recieve = SSPBUF;
    SSPCON2bits.ACKEN = ack;
    return recieve;

void i2c_init(void){
    TRISCbits.TRISC3 = 1;
    TRISCbits.TRISC4 = 1;

    SSPSTATbits.SMP = 1;
    SSPSTATbits.CKE = 0;
    SSPCONbits.SSPM = 0x08;
    SSPADD = 19;
    SSPCONbits.SSPEN = 1;


void main(void) {
    TRISC = 0x00;
    ADCON0 &=0b11111110;

    PORTCbits.RC6=1; //RC6 LED ON

So yeah, the LED goes off. The funny thing is that while my I2C is in a forever while loop, SCL and SDA go up to 5V, sit there for a few hundred ms and then sit firmly at 0V.

Tbh, I'm totally lost and I have no idea what I'm doing wrong. Any help would be appreciated. Help me save the rest of my hair please :D

  • 1
    \$\begingroup\$ You say "they go up to 5V"? If you've pulled them up correctly, they should already be at 5V the whole time, till the Start condition is asserted. Next, you're checking way too many flags, you should get rid of all your while() checks and replace them with simply checking only while(!PIR1bits.SSPIF);, which seems to indicate all the relevant events, for now at least. You'll need to clear it everytime afterwards of course. Even if there's no slave present, you should still see some activity on those lines. You should also connect your LED up to the scope to be sure it toggles continuously. \$\endgroup\$ Jun 7, 2020 at 2:04

1 Answer 1


So fair warning, I've never handled any PIC's before.

That being said, the I2C section of the datasheet for PIC16F87XA runs through an Master I2C transmission sequence pretty clearly.

The upshot: The only way to tell if an I2C transmission sequence is complete is by checking the in SSPIF bit in P1R1 Register.

Datasheet, page 96 (DS39582B, https://ww1.microchip.com/downloads/en/devicedoc/39582b.pdf):

A typical transmit sequence would go as follows:

  1. The user generates a Start condition by setting the Start Enable bit, SEN (SSPCON2<0>).
  2. SSPIF is set. The MSSP module will wait the required Start time before any other operation takes place.
  3. The user loads the SSPBUF with the slave address to transmit.
  4. Address is shifted out the SDA pin until all 8 bits are transmitted.
  5. The MSSP module shifts in the ACK bit from the slave device and writes its value into theSSPCON2 register (SSPCON2<6>).
  6. The MSSP module generates an interrupt at the end of the ninth clock cycle by setting the SSPIFbit.
  7. The user loads the SSPBUF with eight bits of data.
  8. Data is shifted out the SDA pin until all 8 bits are transmitted.
  9. The MSSP module shifts in the ACK bit from the slave device and writes its value into theSSPCON2 register (SSPCON2<6>).
  10. The MSSP module generates an interrupt at the end of the ninth clock cycle by setting the SSPIF bit.
  11. The user generates a Stop condition by setting the Stop Enable bit, PEN (SSPCON2<2>).
  12. Interrupt is generated once the Stop condition is complete

So even though the buffer is "empty", the transaction may not be complete, and ACK may or may not be yet set properly, until the interrupt flag (SSPIF) is set.

It appears that you should use interrupts, or at least the interrupt flags, to do this properly.

Remember to clear your interrupt flags, if they're not done so automatically.

I hope that this is somewhat near the mark for you. Good luck!

  • \$\begingroup\$ Thank you for summary. I know how I2C functions on protocol level, I made sure I knew that before trying to implement it. In theory, I could even bitbang it, but I don't think it's worth time and effort in my specific application (to make I2C running). The only new thing for me that wasn't covered in Microchip's lecture on I2C and other vids were the interrupts.I'm not sure my problem is with the interrupts, since my SDA/SCL behave as if no single bit is transferred. But I still wouldn't know how to incorporate them in I2C as of now. And why others don't need them implemented manually. but thx \$\endgroup\$
    – Ilya
    Jun 6, 2020 at 8:10
  • \$\begingroup\$ Enabled global interrupts and peripheral interrupts, INTCONbits.GIE = 1; INTCONbits.PEIE = 1; no change in behavior \$\endgroup\$
    – Ilya
    Jun 6, 2020 at 9:40
  • 2
    \$\begingroup\$ Just enabling the interrupts will not change your results at all. You need to either poll SSPIF to determine that the transaction is complete, or turn on and use interrupts and write an interrupt routine to handle SSPIF (which gets set when the transaction is complete). BTW interrupt code will be much cleaner in the long run. \$\endgroup\$ Jun 8, 2020 at 12:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.