|
|
View previous topic :: View next topic |
Author |
Message |
demedeiros
Joined: 27 Dec 2013 Posts: 71
|
PIC18F45K50 Using I2C and USB |
Posted: Thu Jul 13, 2023 1:28 pm |
|
|
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: 19358
|
|
Posted: Fri Jul 14, 2023 12:59 am |
|
|
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
|
|
Posted: Fri Jul 14, 2023 6:21 am |
|
|
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
|
|
Posted: Fri Jul 14, 2023 6:25 am |
|
|
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: 1903
|
|
Posted: Fri Jul 14, 2023 7:12 am |
|
|
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
|
|
Posted: Fri Jul 14, 2023 8:32 am |
|
|
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: 19358
|
|
Posted: Sat Jul 15, 2023 12:09 am |
|
|
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: 1924 Location: Norman, OK
|
|
Posted: Sat Jul 15, 2023 8:32 am |
|
|
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
|
|
Posted: Sat Jul 15, 2023 6:47 pm |
|
|
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
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: 1924 Location: Norman, OK
|
|
Posted: Sat Jul 15, 2023 8:41 pm |
|
|
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: 19358
|
|
Posted: Sun Jul 16, 2023 1:01 am |
|
|
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.
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. |
|
|
|
|
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
|