CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

PIC18F45K50 Using I2C and USB

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
demedeiros



Joined: 27 Dec 2013
Posts: 71

View user's profile Send private message

PIC18F45K50 Using I2C and USB
PostPosted: Thu Jul 13, 2023 1:28 pm     Reply with quote

Hi all,

I am having a hard time trying to get I2C running on a PIC while also using USB (Interrupt Driven).

I have configured my pic as follows:

Code:
#include <18F45K50.h>
#device ADC=10

#FUSES NOWDT, MCLR, NOPROTECT,

#use delay(crystal=16MHz, clock=48MHz, USB_FULL)
#use i2c(master, slow,sda=PIN_B0, scl=PIN_B1, force_hw)
#use TIMER(TIMER=0, TICK=1ms, BITS=16, NOISR)


During the I2C transaction, the first is clocked (SCL) at 100KHz, however subsequent transactions see the clock change to 23KHz. See image below:



Stripped down code shown below.

Code:


struct BB2590_DATA Bat1Cell1 = {0,0,0,0,0,0,0,0};
struct BB2590_DATA Bat1Cell2 = {0,0,0,0,0,0,0,0};
struct BB2590_DATA Bat2Cell1 = {0,0,0,0,0,0,0,0};
struct BB2590_DATA Bat2Cell2 = {0,0,0,0,0,0,0,0};


//selectBattery
//
//Select which of the 4 battery cells to communicate to
//There are 4 battery cells in total, 2 per battery
void selectBattery(unsigned int8 batteryCell)
{
   unsigned int8 cellNum = 0;
   
   //Cell number has to be changed to an individual bit.
   //TCA9548 uses a single bit to determine channel enable disable
   //00000000 - All off/default power up state
   //00000001 - Channel 1 On
   //00000010 - Channel 2 On
   //00000100 - Channel 3 On
   //.....
   //10000000 - Channel 8 On
   
   switch (batteryCell)
   {
      case 1:
         cellNum = 1;
         break;
      case 2:
         cellNum = 2;
         break;
      case 3:
         cellNum = 4;
         break;
      case 4:
         cellNum = 8;
         break;
      default:
         cellNum = 0;
         break;
   }
   
   
   i2c_start();
   i2c_write(muxAddress << 1);
   i2c_write(cellNum);
   i2c_stop();
}


//batteryPresent
//
//Checks to see if battery cell is present
int1 batteryPresent()
{
   uint8_t status;
   //Write to the address of the battery, if it ACKs then the battery is present
   i2c_start();
   status = i2c_write(BB2559_ADDRESS);
   i2c_stop();
   
   //If battery ACKs, i2c_write returns 0
   if(status == 0)
      return(TRUE);
   else
      return(FALSE);
}



//batteryPoll
//Read information from the battery
void batteryPoll(struct BB2590_DATA *Battery)
{
   //Check to see if the battery is installed
   Battery->Presence = batteryPresent();
   //If the battery is present, read the parameters
   if(Battery->Presence)
   {
      //Read Voltage
      Battery->Voltage = readBB2559Voltage();
      delay_us(500);
      //printf("Read Voltage\r\n");
      //Read Current
      Battery->Current = readBB2559Current();
      //Read Temperature
      Battery->Temperature = readBB2559Temperature();
      //Read Relative State of Charge
      Battery->RSOC = readBB2559RSOC();
   }
}




//readBatteryInfo
//Reads the info from all of the batteries
void readBatteryInfo()
{
   selectBattery(1);
   ////printf("SELECT 1 OK\r\n");
   delay_us(500);
   batteryPoll(&Bat1Cell1);
   delay_us(500);
   
   selectBattery(2);
   //printf("SELECT 2 OK\r\n");
   delay_us(500);
   batteryPoll(&Bat1Cell2);
   delay_us(500);
   
   selectBattery(3);
   //printf("SELECT 3 OK\r\n");
   delay_us(500);
   batteryPoll(&Bat2Cell1);
   delay_us(500);
   
   selectBattery(4);
   //printf("SELECT 4 OK\r\n");
   delay_us(500);
   batteryPoll(&Bat2Cell2);
   delay_us(500);
}





void main()
{
   //Setup timer, clock is 16MHz, internal clock is 16MHz/4 = 4MHz
   //Increment every    4uS      = 1/(4MHz/16)
   //Overflow  every    1.024    = 4uS * 254
   //Interrupt every    19.84mS  = 1.024 * 16
   setup_timer_2(T2_DIV_BY_16, 254, 16);
   enable_interrupts(int_timer2);
   enable_interrupts(GLOBAL);

   startup();

   while(TRUE)
   { 
     

   readBatteryInfo();
   
   
    delay_ms(100); 
     
   }
}



The readVoltage function is here as an example.

Code:
//Read Voltage
//Returns battery cell voltage in mV
unsigned int16 readBB2559Voltage()
{
   i2c_start();
   i2c_write(BB2559_ADDRESS);
   i2c_write(BB2559_Voltage);
   i2c_stop();
   
   i2c_start();
   i2c_write(BB2559_ADDRESS + 1);
   unsigned int8 byte0 = i2c_read(1);
   unsigned int8 byte1 = i2c_read(0);
   i2c_stop();
   
   return make16(byte1, byte0);
}


Could something be impacting the operation of the I2C hardware? I would think since it is not software based that interrupts wouldnt effect it?

Thank you!
Ttelmah



Joined: 11 Mar 2010
Posts: 19503

View user's profile Send private message

PostPosted: Fri Jul 14, 2023 12:59 am     Reply with quote

Some comments, and questions:

First, you don't show any USB code here at all. Why do you think this
may be involved if you are testing without it?.

Then change your I2C configuration:
#use i2c(master, slow=100000,I2C1)
Being explicit on the rate you want, and saying 'use the peripheral',
ensures the hardware port is selected and used.

However, interrupts will only change the timings between the bytes. It
is the I2C hardware that actually generates the bit timings. So if this
still changes speed, then it sounds as if the issue may be with your clock.
I think by default FSCM will be enabled. I think your crystal is stalling
and the chip is dropping back to running off the HFINT oscillator. Result
speed plummets to about 1/3rd.

Do a much simpler test. Get rid of all the code, just have your clock
setups, and then have a main just doing a basic flash an LED test.
I think you will find the first flash may be at the expected speed, and
then the speed drops to 1/3rd the expected rate. The crystal is stopping
and the chip is switching down to running at just 16MHz. Obviously the
flash rate versus expected rate will tell you this. So then you need to
investigate your hardware. Quality of decoupling round the chip. Double
check your tuning capacitors on the crystal. Remember the value of
these has to take into account the capacitance of the pins and tracks.
How long are the tracks to the crystal?. The 16MHz crystal is harder to
get running right than lower frequencies like 4MHz and 8MHz, so more
care is needed in the layout here. A classic reason for the oscillator to
stall, would be too much load capacitance on the crystal. Is this a
crystal, or a ceramic resonator?. If the latter, then a series resistor
may be needed to avoid over-driving. In some cases also a parallel
resistor may be needed. Look here:

[url]
https://microchipdeveloper.com/8bit:lp
[/url]

Both can also apply to some crystals.

I think you actually have an oscillator problem. You need to get this
reliable first.
demedeiros



Joined: 27 Dec 2013
Posts: 71

View user's profile Send private message

PostPosted: Fri Jul 14, 2023 6:21 am     Reply with quote

Hi Ttelmah, thank you for your response.

What I posted above was a grouping of code from my larger project in order to get the idea across without posting the entire project itself.

Changing the I2C statement as you mentioned does not seem to make a difference.


Running a program with just a flashing LED does not show any difference between the first flash and subsequent flashes.


Code:
#include <18F45K50.h>
#device ADC=10

#FUSES NOWDT                    //No Watch Dog Timer

#use delay(crystal=16000000)

//Also tried #use delay(crystal=16000000,clock=48MHz, USB_FULL) instead, no difference

void main()
{

   while(TRUE)
   {
   
    output_high(PIN_D7);
    delay_ms(500); 
    output_low(PIN_D7);
    delay_ms(500);

   }

}



I then tried a basic program using I2C:

Code:

#include <18F45K50.h>
#device ADC=10

#FUSES NOWDT                    //No Watch Dog Timer

#use delay(crystal=16000000,clock=48MHz, USB_FULL)
#use i2c(Master,slow = 80000, I2C1)

void selectBattery(unsigned int8 batteryCell)
{
   unsigned int8 cellNum = 0;
   
   //Cell number has to be changed to an individual bit.
   //TCA9548 uses a single bit to determine channel enable disable
   //00000000 - All off/default power up state
   //00000001 - Channel 1 On
   //00000010 - Channel 2 On
   //00000100 - Channel 3 On
   //.....
   //10000000 - Channel 8 On
   
   switch (batteryCell)
   {
      case 1:
         cellNum = 1;
         break;
      case 2:
         cellNum = 2;
         break;
      case 3:
         cellNum = 4;
         break;
      case 4:
         cellNum = 8;
         break;
      default:
         cellNum = 0;
         break;
   }
   
   
   i2c_start();
   i2c_write(0x70);
   i2c_write(cellNum);
   i2c_stop();
}


void main()
{
   delay_ms(500);
   while(TRUE)
   {
   selectBattery(1);
   delay_ms(1);
   selectBattery(2);
   delay_ms(1);
   selectBattery(3);
   delay_ms(1);
   selectBattery(4);
   delay_ms(1);
   

    delay_ms(100);

   }

}


The SCL timing stays the same, at approx 80kHz (77kHZ, selected 80kHz due to battery smbus requirements). Strangely though I am writing 0x70 but see 0x38.. I'll have to investigate here.


Crystal layout is pretty tight, located about 1/4in from the PIC. Decoupling caps are 27pF. I have a series resistor, which was 330Ohm however I changed that to a 0Ohm this morning to test. No difference. I am not sure its crystal related since the clock timing is consistent when running this simpler program?


I am going to selectively add parts back into my project to see if I can narrow down a culprit.
demedeiros



Joined: 27 Dec 2013
Posts: 71

View user's profile Send private message

PostPosted: Fri Jul 14, 2023 6:25 am     Reply with quote

Code regarding USB

Code:
main.h

#include <18F45K50.h>
#device ADC=10

#FUSES NOWDT, MCLR, NOPROTECT,

#use delay(crystal=16MHz, clock=48MHz, USB_FULL)
//#use i2c(master, sda = PIN_B0, scl = PIN_B1, FAST=50000, SMBUS)
#use i2c(master, slow = 80000,I2C1)
#use TIMER(TIMER=0, TICK=1ms, BITS=16, NOISR)
//#use rs232(baud = 9600, xmit= PIN_C6 ,rcv= PIN_C7 ,bits=8, parity=N, errors)




#define USB_CONFIG_VID 0x6D04
#define USB_CONFIG_PID 0x14CB
#define USB_CONFIG_BUS_POWER 500
#define USB_STRINGS_OVERWRITTEN


//Pin definitions
#define nINT         PIN_B2
#define WDE          PIN_B3
#define nMUXRESET    PIN_B4
#define nKill        PIN_B5
#define G1STAT       PIN_C0
#define pLED         PIN_C1
#define MCENABLE     PIN_D0
#define V3P3         PIN_D1
#define nB1_VBA      PIN_D2
#define nB1_VBB      PIN_D3
#define nB2_VBA      PIN_D4
#define nB2_VBB      PIN_D5
#define WDI          PIN_D6
#define USB_LED      PIN_D7


#define muxAddress   0x70

unsigned int32 timer2Time = 0;
unsigned int1  ledFlashFlag = FALSE;
unsigned int1 ledFlashEn   = FALSE;
unsigned int1  pcShutdownreq = FALSE;

char USB_STRING_DESC_OFFSET[]={0,4,38};

typedef enum
{
   IDLE,
   READ_BATTERY_INFO,
   SEND_USB_DATA,
   OP_REQ_SHUTDOWN,
   PC_REQ_SHUTDOWN,
   SHUTDOWN_PROC,
   
}eSystemState;

eSystemState eCurrentState = IDLE;
eSystemState ePreviousState = IDLE;

unsigned int16 usbSendTimer = 0;
#define USB_UPDATE_RATE 500;

unsigned int16 batteryReadTimer = 0;
#define BATTERY_UPDATE_RATE 750

char const USB_STRING_DESC[]={
   //string 0 - language
      4,  //length of string index
      0x03,  //descriptor type (STRING)
      0x09,0x04,  //Microsoft Defined for US-English
   //string 1 - manufacturer
      34,  //length of string index
      0x03,  //descriptor type (STRING)
      'E',0,
      'E',0,   //Remove identifying info :)
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
      'E',0,
   //string 2 - product
      20,  //length of string index
      0x03,  //descriptor type (STRING)
      'U',0,
      'S',0,
      'B',0,
      ' ',0,
      'S',0,
      'e',0,
      't',0,
      'u',0,
      'p',0
};

#define USB_CONFIG_HID_TX_SIZE 51
#define USB_CONFIG_HID_RX_SIZE 4
#include <pic18_usb.h>
#include "pwr_pcb_usb_desc_hid.h"
#include <usb.c>




Top of pwr_pcb_usb_desc_hid.h before device descriptors
Code:


#IFNDEF __USB_DESCRIPTORS__
#DEFINE __USB_DESCRIPTORS__

#ifndef USB_CONFIG_PID
   #define USB_CONFIG_PID  0x0020
#endif

#ifndef USB_CONFIG_VID
   #define  USB_CONFIG_VID 0x0461
#endif

#ifndef USB_CONFIG_BUS_POWER
   //valid range is 0..500
   #define  USB_CONFIG_BUS_POWER 100   //100mA
#endif

#ifndef USB_CONFIG_VERSION
   //version number that is stored into descriptor, in bcd.
   //range is 00.00 to 99.99
   #define  USB_CONFIG_VERSION   0x0100      //01.00
#endif

#ifndef USB_CONFIG_HID_TX_SIZE
   //valid range is 0-255
   #define USB_CONFIG_HID_TX_SIZE   2     //compatible with hiddemo.exe
#endif

#ifndef USB_CONFIG_HID_RX_SIZE
   //valid range is 0-255
   #define USB_CONFIG_HID_RX_SIZE   2     //compatible with hiddemo.exe
#endif

#ifndef USB_CONFIG_HID_TX_POLL
   // for full speed devices, valid range is 1-255
   // for slow speed devices, valid range is 10-255
   #define USB_CONFIG_HID_TX_POLL   10
#endif

#ifndef USB_CONFIG_HID_RX_POLL
   // for full speed devices, valid range is 1-255
   // for slow speed devices, valid range is 10-255
   #define USB_CONFIG_HID_RX_POLL   10
#endif

//Tells the CCS PIC USB firmware to include HID handling code.
#ifdef USB_HID_DEVICE
#undef USB_HID_DEVICE
#endif

#include <stdbool.h>

#DEFINE USB_HID_DEVICE  true

//the following defines needed for the CCS USB PIC driver to enable the TX endpoint 1
// and allocate buffer space on the peripheral
#ifdef USB_EP1_TX_ENABLE
#undef USB_EP1_TX_ENABLE
#endif
#define USB_EP1_TX_ENABLE  USB_ENABLE_INTERRUPT   //turn on EP1 for IN bulk/interrupt transfers

#define USB_HID_ENDPOINT   1

#define USB_INTERNAL_PULLUPS

#ifndef USB_EP1_TX_SIZE
 #if (USB_CONFIG_HID_TX_SIZE >= 64)
   // interrupt endpoint max packet size is 64.
   #define USB_EP1_TX_SIZE    64
 #else
   // by making EP packet size larger than message size, we can send message in one packet.
   #define USB_EP1_TX_SIZE    (USB_CONFIG_HID_TX_SIZE+1)
 #endif
#endif

#ifdef USB_EP1_RX_ENABLE
#undef USB_EP1_RX_ENABLE
#endif
#define USB_EP1_RX_ENABLE  USB_ENABLE_INTERRUPT   //turn on EP1 for OUT bulk/interrupt transfers

#ifndef USB_EP1_RX_SIZE
 #if (USB_CONFIG_HID_RX_SIZE >= 64)
   // interrupt endpoint max packet size is 64.
   #define USB_EP1_RX_SIZE    64
 #else
   // by making EP packet size larger than message size, we can send message in one packet.
   #define USB_EP1_RX_SIZE    (USB_CONFIG_HID_RX_SIZE+1)
 #endif
#endif

#include <usb.h>



USB send/receive functions

Code:
void sendUSB()
{
   
   if(ledFlashFlag)
   {
      ledFlashFlag = 0;

      generateUSBPacket();
      usb_put_packet(USB_HID_ENDPOINT, usb_out_data, USB_CONFIG_HID_TX_SIZE, USB_DTS_TOGGLE);
     
   }
}

void receiveUSB()
{
   unsigned int8 in_data[USB_CONFIG_HID_RX_SIZE];
   
   if(usb_kbhit(USB_HID_ENDPOINT))
   {
     
      usb_get_packet(USB_HID_ENDPOINT, in_data, USB_CONFIG_HID_RX_SIZE);
      for(int i =0; i<4;i++)
      {
         //printf("Got %X\r\n", in_data[i]);
      }
     
      //PC Shutdown request data
      if(in_data[0] == 0xAA)
      {
         pcShutdownReq = TRUE;
         //printf("SHUT DOWN NOW!!!\r\n\n\n\n\n\n");
      }else
      {
         pcShutdownReq = FALSE;
      }
     
      //Media converter control
      if(in_data[1] == 0xFF)
      {
         output_high(MCENABLE);
      }else
      {
         output_low(MCENABLE);
      }
   }

}
newguy



Joined: 24 Jun 2004
Posts: 1907

View user's profile Send private message

PostPosted: Fri Jul 14, 2023 7:12 am     Reply with quote

Your crystal layout is bad. Because the ground plane is poured amongst the traces, the caps, and the crystal, it's adding a lot of stray capacitance to the circuit. I'm surprised the oscillator runs at all.

You need to place a cutout (Altium calls it a Polygon Pour Cutout) around the PIC's xtal pins, the xtal traces, the xtal itself, and the load caps. There should be a gap of at least 0.010" between the edge of any trace and the edge of the plane.

This only applies to planes on the same layer as the traces and the components. Ground pours on other layers are fine.

I speak from experience. Many years ago I did precisely the same thing. The oscillator wouldn't even start, and the PIC didn't have an internal oscillator to which it could fall back. I spent a long time with a scalpel and a needle file removing most of that copper before it started to work. ...I never did that again.
demedeiros



Joined: 27 Dec 2013
Posts: 71

View user's profile Send private message

PostPosted: Fri Jul 14, 2023 8:32 am     Reply with quote

Thank you, I will keep that in mind for future designs. I have generally done it like this, however I do realize thats probably not the best method...

Figured it out! We used EMI feedthroughs which are effectively capacitors. They are 0.01uF, much more than the specified 400pF I2C bus capacitance limit!

Thanks for all of your help!!
Ttelmah



Joined: 11 Mar 2010
Posts: 19503

View user's profile Send private message

PostPosted: Sat Jul 15, 2023 12:09 am     Reply with quote

Well done on finding it...
Lesson here, capacitance is of vital importance in electronic design.

A comment. The crystal capacitors are not 'decoupling', they are 'load'
capacitors. The crystal is a tuned circuit that requires a particular amount
of load. In your case I'd suggest running the circuit without these. Reason
is a quick estimate suggests your ground plane is actually introducing
more than the specified load. However the same estimate suggests that
it will be fairly close to the required value. (about 40pF). So with the
added capacitors, you will be over double the required capacitance.

Microchip and Texas, both have very good application notes on how to
layout the tracks and ground plane round the crystal. The black areas
on the image of this, suggest you may have tracks crossing the crystal
traces on the other side of the board. This is very much not a thing to
do.
dyeatman



Joined: 06 Sep 2003
Posts: 1933
Location: Norman, OK

View user's profile Send private message

PostPosted: Sat Jul 15, 2023 8:32 am     Reply with quote

FWIW, I ditched crystals years ago and all my designs uses oscillator packages.
The cost is slightly more and a little more real estate but they are much more
accurate and eliminates startup issues for me entirely. Haven't had any
oscillator issues in the last 10 years or more. Threw away all my remaining
crystals and resonators.
_________________
Google and Forum Search are some of your best tools!!!!
demedeiros



Joined: 27 Dec 2013
Posts: 71

View user's profile Send private message

PostPosted: Sat Jul 15, 2023 6:47 pm     Reply with quote

Ttelmah,

Thanks for the info, there is actually a continuous ground plane under the crystal, you just cant see it because its hidden. The red and green layers are layers 1 & 6, layers 2&5 are ground planes Smile

dyeatman,

I assume using an oscillator and just feeding into the CLKIN pin? How does this affect fuses? I have always used crystals, but will try oscillators for sure.
dyeatman



Joined: 06 Sep 2003
Posts: 1933
Location: Norman, OK

View user's profile Send private message

PostPosted: Sat Jul 15, 2023 8:41 pm     Reply with quote

You select the desired External Clock Fuse (i.e ECxx) depending on
whether you want to use the other xtal pin for IO or CLK output.
Datasheet pages 28-32 and the .H header file have the options.
Using these saves a lot of layout headaches, having to block the
area for the router software etc, and you gain a pin!
Personally I put a series resistor between the oscillator OUT and the CLKIN pin
to prevent over driving the input (typically I use 50-100 ohms but it is NOT
required). I started using using these in 2008 and have
never had an oscillator related issue in more than 100 designs.
_________________
Google and Forum Search are some of your best tools!!!!
Ttelmah



Joined: 11 Mar 2010
Posts: 19503

View user's profile Send private message

PostPosted: Sun Jul 16, 2023 1:01 am     Reply with quote

In your picture, on the gaps round the traces, there are breaks in the
green here, presumably representing tracks running through. If these
are tracks crossing the crystal traces, these are a very bad idea.
There are two nasty things that happen if you have signal tracks crossing
the crystal lines. The first is that the signal frequency can get modified
by these signals, and the second if that the crystal signal can get picked up
by these lines.

Then as has already been pointed out, the ground plane can be a problem.
Capacitances, need to be low around the oscillator tracks. There are a
number of separate recommendations here.

Get rid of any power planes immediately around the oscillator. Have
a loop round the area smoothly generating a hole in these.
Components must be placed close as possible to IC, with short traces
No high speed traces passing nearby or underneath.
Avoid crosstalk / coupling between traces. Increase gaps between traces
as quickly as possible as you move away from the chip. Your design
does this.
GND part must be isolated from "general" GND. If plane is used, it should
be separated by gap (even from "general" GND plane). Have this
couple to the main plane at the 'oscillator ground' point.
Some chips recommend a guard ring. However this is specific to chips
where the load capacitances have to be very low. Your 16MHz crystal
does not sound as if it needs this.
Widen the actual tracks to the crystal a little.

Now in your picture it is the topside red plane that is creating the huge
capacitance. A ground plane below the board, would not be too bad.
Only a couple of pF for the entire track length shows. This is why a ground
plane on the bottom of a PCB like this is a good idea, and not a problem.

I'd move your via above pin32, to be under the actual chip. You can then
bring the crystal across so it is placed symmetrically above it's pins. Key
is to keep the traces here as short as possible. Shortness is your friend
here. Smile

Oscillators are a very good idea. However be aware that the issues
around coupling of high speed signals (so don't run other signals across
etc.), still apply.

Now 16MHz, is not a very high frequency, so with some reduction
of the loading capacitors, what you have will probably work fine.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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