|
|
View previous topic :: View next topic |
Author |
Message |
Zilog750
Joined: 19 Oct 2013 Posts: 6
|
16F88 i2c SCL gets low |
Posted: Sat Dec 28, 2013 1:26 pm |
|
|
Hello all,
some issues with i2c SCL line on 16F88 slave mode using int_SSP interrupt.
Objective: 16F88 board for controlling light dimmer from 0 to 100% using i2c. Other i2c slave devices connected to this line.
Problem: After doing some rounds correctly, SCL line gets low and everything stops.
Tests: When the problem occurs, disconnecting SCL line on the 16F88 the system restores itself, so the problem is on the 16F88.
At this point I've done some searches on the forum but after implemented on the code - as you will see - none of them seems to solve this.
Any solution to this problem ?
Best regards
Code: | /////////////////////////////////////////////////////////////////////////
///
/// Fuses: MCLR,PUT,NOPUT,RC,EC,RC_IO,INTRC_IO,BROWNOUT,NOBROWNOUT
///
/////////////////////////////////////////////////////////////////////////
/************************************************************/
/* P R E P R O C E S S O R */
/************************************************************/
#include <16F88.h>
/************************************************************/
/* F U S E S */
/************************************************************/
#fuses NOWDT,NOPROTECT,NOLVP,NOMCLR,INTRC_IO, NOFCMEN, NOIESO, CCPB3
/************************************************************/
/* O S C I L A T O R & D E L A Y */
/************************************************************/
#use delay (clock=4000000)
/************************************************************/
/* A D C */
/************************************************************/
//
/************************************************************/
/* S E T U P R S - 2 3 2 */
/************************************************************/
#use RS232(baud=9600, xmit=PIN_A4,rcv=PIN_A5)
/************************************************************/
/* S E T U P I 2 C */
/************************************************************/
#use i2c(SLAVE,FORCE_HW, SDA=PIN_B1,SCL=PIN_B4,address=0xA0)
/************************************************************/
/* D E F I N I T I O N S */
/************************************************************/
#define LED PIN_A0
#define SCL PIN_B4
#define SDA PIN_B1
#define LED_GROC_ON() output_high(PIN_A0)
#define LED_GROC_OFF() output_low(PIN_A0)
/************************************************************/
/* G L O B A L V A R I A B L E S */
/************************************************************/
char i;
char duty;
char direction;
char rep;
char RB0_value;
char fade;
char start_dimmer;
char state;
char incoming;
BYTE address, buffer[0x10];
#byte SSPCON = 0x14
#bit CKP = SSPCON.4 // Some posts suggest to handle this manually
/************************************************************/
/* F U N C T I O N S */
/************************************************************/
void light_dimmer (void)
{
if (start_dimmer==1)
{
output_low (PIN_B2);
rep++;
for (i=0;i<=duty;i++)
{
delay_us (85);
}
output_high (PIN_B2);
start_dimmer = FALSE;
}
}
/******************************************************************/
/* I N T E R R U P T V E C T O R S */
/******************************************************************/
#INT_EXT
void int_ext_isr()
{
RB0_value = input_state(PIN_B0);
if (RB0_value == 1)
{
start_dimmer = TRUE;
}
clear_interrupt (INT_EXT);
}
#int_SSP
void ssp_ISR (void)
{
BYTE incoming, state;
disable_interrupts(GLOBAL); // Enabling Global Interrupts
state = i2c_isr_state();
if(state <= 0x80) //Master is sending data
{
incoming = i2c_read();
if(state == 1) //First received byte is address
{
address = incoming;
}
if(state == 2)
{ //Second received byte is data
output_toggle (PIN_A0);
buffer[address] = incoming;
duty = incoming;
}
}
if(state == 0x80) //Master is requesting data
{
//i2c_write(buffer[address]);
}
CKP = 1;
clear_interrupt (INT_SSP);
enable_interrupts(GLOBAL); // Enabling Global Interrupts
}
/******************************************************************/
/* M A I N P R O G R A M */
/******************************************************************/
void main()
{
setup_oscillator(OSC_4MHZ);
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_vref(FALSE);
setup_timer_1(T1_DISABLED);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
output_float(PIN_B0); // Make PIN_B0 into an input
ext_int_edge(L_TO_H); // Configure for Raising Edge Interrupt
enable_interrupts(INT_EXT);// Enabling External Interrupt
enable_interrupts(INT_SSP);// Enabling i2c Interrupt
enable_interrupts(GLOBAL); // Enabling Global Interrupts
duty = 50;
direction = 1;
i=0;
rep = 0;
fade = FALSE;
output_low(PIN_A1); // Some posts suggests to put unused lines to low
output_low(PIN_A2);
output_low(PIN_A4);
output_low(PIN_A5);
output_low(PIN_A6);
output_low(PIN_A7);
output_low(PIN_B3);
output_low(PIN_B5);
output_low(PIN_B6);
output_low(PIN_B7);
while(1)
{
light_dimmer();
}
} |
Master dimmer control routine (0<val<100)
Code: | void dimmer (char val)
{
i2c_start();
i2c_write(0xA0);
i2c_write(0x00);
i2c_write(val);
delay_ms(50);
i2c_stop();
delay_ms(50);
} |
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Sat Dec 28, 2013 1:39 pm |
|
|
You must _never_, _ever_ enable_interrupts(GLOBAL), inside a PIC interrupt handler.
The PIC _hardware_ disables the global bit when interrupts are called. It also re-enables the interrupts on the next instruction _after_ the interrupt exits. This way the interrupt handler cannot be called inside itself (recursion), which the PIC does not basically support. By enabling the global bit inside the handler you make it possible for this to happen, and the code will then crash....
You can also get rid of the clear_interrupt lines. The compiler does this for you, unless you specify 'noclear', so you are wasting an instruction in each case.
Best Wishes |
|
|
Zilog750
Joined: 19 Oct 2013 Posts: 6
|
|
Posted: Sat Dec 28, 2013 5:04 pm |
|
|
Hello ttelmah,
only corrected code
Code: | .................... #INT_EXT
.................... void int_ext_isr()
.................... {
.................... RB0_value = input_state(PIN_B0);
*
0039: CLRF 2C
003A: BTFSC 06.0
003B: INCF 2C,F
....................
.................... if (RB0_value == 1)
003C: DECFSZ 2C,W
003D: GOTO 040
.................... {
.................... start_dimmer = TRUE;
003E: MOVLW 01
003F: MOVWF 2E
.................... }
.................... }
....................
0040: BCF 0B.1
0041: BCF 0A.3
0042: GOTO 024
.................... #int_SSP
.................... void ssp_ISR (void)
.................... {
.................... BYTE incoming, state;
....................
.................... state = i2c_isr_state();
0043: BSF 03.5
0044: BTFSC 14.5
0045: GOTO 04E
0046: BCF 03.5
0047: CLRF 42
0048: BSF 03.5
0049: BTFSS 14.2
004A: GOTO 04E
004B: BCF 03.5
004C: BSF 42.7
004D: BSF 03.5
004E: BCF 03.5
004F: MOVF 42,W
0050: INCF 42,F
0051: MOVWF 44
....................
.................... if(state <= 0x80) //Master is sending data
0052: MOVF 44,W
0053: SUBLW 80
0054: BTFSS 03.0
0055: GOTO 071
.................... {
.................... incoming = i2c_read();
0056: BCF 14.6
0057: BTFSS 0C.3
0058: GOTO 057
0059: MOVF 13,W
005A: BSF 14.4
005B: MOVWF 43
.................... if(state == 1) //First received byte is address
005C: DECFSZ 44,W
005D: GOTO 060
.................... {
.................... address = incoming;
005E: MOVF 43,W
005F: MOVWF 31
.................... }
.................... if(state == 2)
0060: MOVF 44,W
0061: SUBLW 02
0062: BTFSS 03.2
0063: GOTO 071
.................... { //Second received byte is data
.................... output_toggle (PIN_A0);
0064: BSF 03.5
0065: BCF 05.0
0066: MOVLW 01
0067: BCF 03.5
0068: XORWF 05,F
.................... buffer[address] = incoming;
0069: MOVLW 32
006A: ADDWF 31,W
006B: MOVWF 04
006C: BCF 03.7
006D: MOVF 43,W
006E: MOVWF 00
.................... duty = incoming;
006F: MOVF 43,W
0070: MOVWF 29
.................... }
.................... }
.................... if(state == 0x80) //Master is requesting data
0071: MOVF 44,W
0072: SUBLW 80
0073: BTFSS 03.2
0074: GOTO 075
.................... {
.................... //i2c_write(buffer[address]);
.................... }
.................... CKP = 1;
0075: BSF 14.4
.................... } |
Results: After changing (erasing) enable/disable interrupts inside ISR, SCL still gets low. System blocked by 16F88 board after 20 or 30 rounds. In this situation, PIN_A0 still toggles, so SSP interrupt is running and the most strangest thing is that the INT is still running even with the SCL line disconnected. Using a scope, there's no relevant disturbance on the Power Supply, nor on the i2c lines during com transactions.
It's very confusing, obviously there's something that don't let SCL line go high.
Regards |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9228 Location: Greensville,Ontario
|
|
Posted: Sat Dec 28, 2013 5:43 pm |
|
|
um maybe a dumb question but Do you have good pullups on the I2C lines ?
jay |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sat Dec 28, 2013 10:46 pm |
|
|
Post your CCS compiler version. It could be the problem. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Sun Dec 29, 2013 1:56 am |
|
|
Agree with PCM programmer, give your compiler version.
There was an odd problem some time ago, where the I2C_ISR_STATE routine, would not return the status correctly till after a byte had been read.
However a couple of questions.
How fast is your master CPU?.
How fast are you clocking the I2C?.
The slave holds the SCL line low, when it hasn't yet completed 'handling' a transaction. Suggests that the code is entering the ISR, and something is not being done that needs to be done. I'd wonder if the slave is seeing a condition on the bus that is not handled by the code, which then suggests possibly inadequate pull-ups, or a noise problem.
I would write 'something' (just a dummy byte), when the state is >=0x80. With the master code you show, this should never be called, bur if the chip does get to this routine, something has to be loaded.
I have to ask 'why' you put the data both into a buffer, and the duty cycle register?. A waste to store it twice.
So something like:
Code: |
#bit WCOL=getenv("BIT:WCOL")
#bit SSPOV=getenv("BIT:SSPOV")
#INT_SSP
void i2c_isr(void)
{
int8 state, incoming;
state = i2c_isr_state();
if(state== 0 )
i2c_read();
if(state == 0x80)
i2c_read(2); //holds SCL low, till the write is performed
if(state >= 0x80)
i2c_write(0); //load the output buffer or the interrupt won't clear
else if(state > 0)
{
duty=i2c_read();
}
if (WCOL)
WCOL=FALSE; //brute force error handling.....
if (SSPOV)
SSPOV=FALSE;
}
//change your master to
void dimmer (char val)
{
i2c_start();
i2c_write(0xA0); //device address
i2c_write(val); //value to send
i2c_stop();
}
|
Since you are not addressing separate registers in the slave, there is no point in sending a register address...
I've added 'brute force' clearing of the possible error status bits.
Both a data read, or a data write, _should_ automatically release the CKP. This is where compiler version matters.
Best Wishes |
|
|
Zilog750
Joined: 19 Oct 2013 Posts: 6
|
|
Posted: Sun Dec 29, 2013 7:32 am |
|
|
Hi all,
thanks for the reply. As requested:
Compiler Version: 4.108
Master Board: PICDEM2 PLUS 16F877 XTAL @4MHz with i2c 4k7 Pull-Up resistors
Slave Board: 16F88 Internal Oscillator @4MHz
Master Code
Code: | ////////////////////////////////////////////////////////////////////////////////
// //
// //
////////////////////////////////////////////////////////////////////////////////
/************************************************************/
/* P R E P R O C E S S O R */
/************************************************************/
#include <16F877.h>
#device ADC=10
/************************************************************/
/* F U S E S */
/************************************************************/
#fuses XT,NOWDT,NOPROTECT,NOLVP,BROWNOUT
/************************************************************/
/* O S C I L A T O R & D E L A Y */
/************************************************************/
#use delay(clock=4000000) /* one instruction=1us? */
/************************************************************/
/* S E T U P R S - 2 3 2 */
/************************************************************/
// NONE
/************************************************************/
/* S E T U P I 2 C */
/************************************************************/
#use I2C(master, SCL=PIN_C3, SDA=PIN_C4, restart_wdt, SLOW)
/************************************************************/
/* G L O B A L V A R I A B L E S */
/************************************************************/
int duty;
/************************************************************/
/* F U N C T I O N S */
/************************************************************/
void dimmer (char val)
{
i2c_start();
i2c_write(0xA0); //device address
i2c_write(val); //value to send
i2c_stop();
}
/******************************************************************/
/* M A I N P R O G R A M */
/******************************************************************/
void main()
{
duty = 0;
while(TRUE)
{
dimmer (duty);
if (duty >= 100)
{
duty = 0;
}
else
{
duty = duty+1;
}
delay_ms (250);
}
} |
Slave code
Code: | /////////////////////////////////////////////////////////////////////////
///
///
///
/////////////////////////////////////////////////////////////////////////
/************************************************************/
/* P R E P R O C E S S O R */
/************************************************************/
#include <16F88.h>
/************************************************************/
/* F U S E S */
/************************************************************/
#fuses NOWDT,NOPROTECT,NOLVP,NOMCLR,INTRC_IO, NOFCMEN, NOIESO, CCPB3
/************************************************************/
/* O S C I L A T O R & D E L A Y */
/************************************************************/
#use delay (clock=4000000)
/************************************************************/
/* S E T U P R S - 2 3 2 */
/************************************************************/
#use RS232(baud=9600, xmit=PIN_A4,rcv=PIN_A5)
/************************************************************/
/* S E T U P I 2 C */
/************************************************************/
#use i2c(SLAVE,FORCE_HW, SDA=PIN_B1,SCL=PIN_B4,restart_wdt, address=0xA0)
/************************************************************/
/* D E F I N I T I O N S */
/************************************************************/
#define LED PIN_A0
#define SCL PIN_B4
#define SDA PIN_B1
/************************************************************/
/* G L O B A L V A R I A B L E S */
/************************************************************/
char i;
char duty;
char direction;
char rep;
char RB0_value;
char fade;
char start_dimmer;
char state;
char incoming;
BYTE address, buffer[0x10];
#byte SSPCON = 0x14
#bit CKP = SSPCON.4
#bit WCOL=getenv("BIT:WCOL")
#bit SSPOV=getenv("BIT:SSPOV")
/************************************************************/
/* F U N C T I O N S */
/************************************************************/
void light_dimmer (void)
{
if (start_dimmer==1)
{
output_low (PIN_B2);
rep++;
for (i=0;i<=duty;i++)
{
delay_us (85);
}
if (fade==1)
{
if (rep>=5)
{
if (direction == 1)
{
duty++;
if (duty == 100)
{
direction =0;
}
}
if (direction == 0)
{
duty--;
if (duty == 0)
{
direction =1;
}
}
rep=0;
}
}
output_high (PIN_B2);
start_dimmer = FALSE;
}
}
/******************************************************************/
/* I N T E R R U P T V E C T O R S */
/******************************************************************/
#INT_EXT
void int_ext_isr()
{
RB0_value = input_state(PIN_B0);
if (RB0_value == 1)
{
start_dimmer = TRUE;
}
}
#INT_SSP
void i2c_isr(void)
{
int8 state, incoming;
state = i2c_isr_state();
if(state== 0 )
i2c_read();
if(state == 0x80)
i2c_read(2); //holds SCL low, till the write is performed
if(state >= 0x80)
i2c_write(0); //load the output buffer or the interrupt won't clear
else if(state > 0)
{
duty=i2c_read();
}
if (WCOL)
WCOL=FALSE; //brute force error handling.....
if (SSPOV)
SSPOV=FALSE;
}
/******************************************************************/
/* M A I N P R O G R A M */
/******************************************************************/
void main()
{
setup_oscillator(OSC_4MHZ);
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_vref(FALSE);
setup_timer_1(T1_DISABLED);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
output_float(PIN_B0); // Make PIN_B0 into an input
ext_int_edge(L_TO_H); // Configure for Raising Edge Interrupt
enable_interrupts(INT_EXT);// Enabling External Interrupt
enable_interrupts(INT_SSP);// Enabling i2c Interrupt
enable_interrupts(GLOBAL); // Enabling Global Interrupts
duty = 50;
direction = 1;
i=0;
rep = 0;
fade = FALSE;
output_low(PIN_A1);
output_low(PIN_A2);
output_low(PIN_A4);
output_low(PIN_A5);
output_low(PIN_A6);
output_low(PIN_A7);
output_low(PIN_B3);
output_low(PIN_B5);
output_low(PIN_B6);
output_low(PIN_B7);
while(1)
{
light_dimmer();
}
} |
Results: In this case SDA line is low instead SCL, after 50 or 60 rounds. As you can see there's only one slave on the i2c bus and the codes on both sides are quite simple.
Any ideas?
Best Regards |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Sun Dec 29, 2013 8:10 am |
|
|
I think you are seeing noise on one or more of the lines.
It'd hold SDA low, if the slave thinks it has been asked to send a byte (so loads '0' into the data register), and then the master doesn't clock this data.
In the older code, since the register is not loaded, it'd hold the clock low instead.
I'd start by lowering the pull-up resistors being used. 4K7 is usually an acceptable value in 'low noise' environments, for a single slave, with reasonably short wires, but something lower like 1K8, will decrease the rise time, and lower the impedance when the lines are not driven.
I'd also add a short delay after sending each command in the master. Shouldn't be needed, but worth trying. Say 50uSec each.
Look into making sure you have a really good ground connection between the chips, and good suppression.
Best Wishes |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|