1
\$\begingroup\$

I am trying to implement a basic I2C Master write sequence for a PIC12F1822 Microcontroller. I am programming the device using the PICKit 3 programmer.

I have heavily simplified the code to isolate the problem, however I cannot seem to narrow down the solution.

What I expect to happen:

  1. The bus lines are initially low, where TRISA = 0.
  2. The TRISA1 and TRISA2 are set to 1 and the MSSP module is enabled in I2C mode, bringing the SDA/SCL lines high.
  3. A start condition occurs, followed by data, then a stop condition.
  4. TRISA1 and TRISA2 are set to 0, causing the SDA/SCL lines to go low.
  5. A 50ms pause occurs, before starting from step 1.

What actually happens:

The SDA/SCL lines are low, then go high for a short duration, before going low again. There is no start condition, data or stop condition.

What I have tried:

I have tried different methods of addressing the MSSP registers, (i.e. SSP1CON2 = 0x01 instead of SEN = 1), and the debugger shows the SFRs are written to correctly.

I have provided logic analyser output below to illustrate the problem. The two peaks are ~50ms apart, and the SDA and SCL peaks begin at identical points in time. I have observed no bus capacitance issues when using an oscilloscope.

I am a PIC novice (though have a decent amount of experience on other platforms), and suspect that I have simply omitted a register set somewhere. Anyway, this issue is driving me nuts, and I would very much appreciate anyone who might help!

The code:

#include "config_bits.h"
#include <xc.h>

#define _XTAL_FREQ 4000000 /* Clock frequency */

void _i2c_wait(void)
{   
    /* Wait for transmission to finish OR Wait for start/stop condition clear */
    while(SSP1STAT & 0x04 || SSP1CON2 & 0x1F);
}


void main(void) 
{
    /* --- Config --- */
    /* Disable Watchdog Timer */
    WDTCON = 0;
    
    /* Set up Internal 4MHz HF osc */
    OSCCON = 0b01101010;
    
    /* Disable ADC */
    ANSELA = 0;
    WPUA = 0;
    
    uint8_t data = 115;
    
  while(1)
    {
        
        TRISA = 0x06; /* Enable RA1 and RA2 as input */
        SSP1CON1 = 0b00101000; /* Enable MSSP, Enable I2C with 7-bit addresses */
        SSP1CON2 = 0; /* Disable general call interrupt */
        SSP1STAT = 0;
        SSP1ADD = 0x09; /* Divide 4MHz clock to 100kHz */
    
        /* Send start condition */
        SEN = 1;
        _i2c_wait();
        PIR1bits.SSP1IF = 0;

        /* Write data, wait until finished */
        SSP1BUF = data;
        _i2c_wait();

        /* Send stop condition */
        PEN = 1;
        _i2c_wait();
        
        SSP1CON1 = 0; /* Disables I2C pins */
        TRISA = ~0x06;

        __delay_ms(50);
    }
    return;
}

Logic Analyser Output:

Logic Analyser Output

Config Bits:

// CONFIG 1
Here are my configuration bits:
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config DEBUG = OFF       // In-Circuit Debugger Mode (In-Circuit Debugger enabled, ICSPCLK and ICSPDAT are dedicated to the debugger)
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)
\$\endgroup\$
8
  • \$\begingroup\$ Please ask a specific question \$\endgroup\$
    – Voltage Spike
    Commented Feb 17 at 3:01
  • \$\begingroup\$ Do you have pullup resistors on SCL and SDA? \$\endgroup\$
    – Rodo
    Commented Feb 17 at 3:21
  • \$\begingroup\$ SCL and SDA are supposed to be "high" when the I2C bus is idle. Given the LSA output shows SCL and SDA are mostly "low" suggests a lack of pullup resistors on SCL and SDA, as Rodo has already commented. \$\endgroup\$ Commented Feb 17 at 8:45
  • \$\begingroup\$ There are pull up resistors on the SDA/SCL lines, in the order of 40k. The low period occurs during the 50ms delay, where TRISA = 0, which is what I expect. The small peak shows the time the I2C bus is active, but nothing happens on the bus. \$\endgroup\$
    – BlueShoes
    Commented Feb 17 at 12:40
  • 2
    \$\begingroup\$ @BlueShoes 40k pull up for I2C bus sounds way off. What frequency bus do you expect to achieve? What other stuff there is on the bus, with what device you are trying to communicate? Why is the sequence just start, one data byte, and stop? It also makes no sense as the first byte should be the I2C address which gets detected by the slave chip you are trying to address. Can you post schematics about the hardware? \$\endgroup\$
    – Justme
    Commented Feb 17 at 13:24

1 Answer 1

0
\$\begingroup\$

I ended up changing to a PIC16F18313 instead, but I hope the below details are useful for anyone with the same issue.

There are two problems with the original code. The MSSP module is never assigned to any external pins and the _i2c_wait() function is incorrect.

To fix the pin assignment issue, the MSSP module is assigned pins RA1 and RA2 using the peripheral pin select module. Both the input and output to the MSSP module must be selected with the PPS module.

SSP1CLKPPS = 0x1;  /* RA1->MSSP1:SCL1 */
RA1PPS = 24;  /* RA1->MSSP1:SCL1 */
SSP1DATPPS = 0x2;  /* RA2->MSSP1:SDA1 */
RA2PPS = 25;  /* RA2->MSSP1:SDA1 */

The _i2c_wait() function must monitor the SSP1IF flag. It is not sufficient to monitor whether the bits set in the SSP1CON2 register have been cleared.

The only way to know if the action you have requested has been sent to the bus is to enable the SSP1 interrupt flag bit and poll it.

Enable interrupt:

PIE1bits.SSP1IE = 1; /* Enable SSP1 interrupt */

Wait for bus action to be completed:

void _i2c_wait(void)
{   
    while(!PIR1bits.SSP1IF); /* Wait for interrupt generation by MSSP. */
    PIR1bits.SSP1IF = 0; /* Clear interrupt bit */
}

As a clarification, it is not necessary to implement an ISR, but the MSSP module must be able to set the interrupt flag bits. The global interrupt enable bit also need not be set.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thanks for coming back with the solution. Please consider áccepting this self-answer to effectively mark the topic as solved, which you can do 48 hours after you asked the question i.e. after 02.48Z on Feb 19 (that system-imposed delay is to allow other people a chance to provide answers, although your answer here indicates that there won't/can't be another answer!). TY \$\endgroup\$
    – SamGibson
    Commented Feb 17 at 18:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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