|
|
View previous topic :: View next topic |
Author |
Message |
jamesjl
Joined: 22 Sep 2003 Posts: 52 Location: UK
|
CAN Interrupt code |
Posted: Tue Aug 23, 2005 9:26 am |
|
|
Hi all,
I'm trying to develop a CAN app that uses interrupts. I see that the PIC18F458 supports interrupts for the CAN, namely:
Code: | #INT_CANIRX
#INT_CANERR |
however, I don't seem to be able to find any information about how to implement the interrupts. Does anyone have any working CAN interrupt code (even a starting point) that might help me with my application. The CAN examples that ship with the compiler poll the CAN RX buffers and this is too slow to ensure that all the transmitted information is process before the next incoming packet.
Many thanks,
Jason. |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1907
|
|
Posted: Tue Aug 23, 2005 10:01 am |
|
|
Try this, it works.
I was in the same boat, so I decided to play with the receive interrupts. I can do without the transmit buffer empty interrupts, so I didn't implement them.
I was also playing with creating my own "can_getc()" functions to try & speed up the generic one that comes with the compiler. They're basically a cut & paste of the CCS code, except they don't check which buffer has the message.
Code: | #include <18F458.h>
#device adc=8
#FUSES WDT
#FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale
#FUSES XT //Crystal osc <= 4mhz
#FUSES NOPROTECT //Code not protected from reading
#FUSES NOOSCSEN //Oscillator switching is disabled, main oscillator is source
#FUSES BROWNOUT //Reset when brownout detected
#FUSES BORV20 //Brownout reset at 2.0V
#FUSES PUT //Power Up Timer
#FUSES NOCPD //No EE protection
#FUSES STVREN //Stack full/underflow will cause reset
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES NOCPB //No Boot Block code protection
#FUSES NOWRTC //configuration not registers write protected
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOEBTRB //Boot block not protected from table reads
#use delay(clock=4000000,RESTART_WDT)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
#define CAN_ENABLE_DRIVE_HIGH 1
#include <can-18xxx8.c>
int1 clear_to_transmit = FALSE, message0 = FALSE, message1 = FALSE;
struct rx_stat rxstat;
int32 rx_id, tx_id = 30;
int in_data[8], out_data[8] = {0,1,2,3,4,5,6,7};
int rx_len, tx_len, tx_pri = 3;
int count = 0, i;
int1 tx_rtr = 0, tx_ext = 1;
void get_0_data(int32 &id, int *data, int &len, struct rx_stat &stat) {
int i, *ptr;
CANCON.win = CAN_WIN_RX0;
stat.buffer = 0;
stat.err_ovfl = COMSTAT.rx0ovfl;
COMSTAT.rx0ovfl = 0;
if (RXB0CON.rxb0dben) {
stat.filthit = RXB0CON.filthit0;
}
len = RXBaDLC.dlc;
stat.rtr = RXBaDLC.rtr;
stat.ext = TXRXBaSIDL.ext;
id = can_get_id(TXRXBaID, stat.ext);
ptr = &TXRXBaD0;
for (i = 0; i < len; i++) {
*data = *ptr;
data++;
ptr++;
}
// return to default addressing
CANCON.win = CAN_WIN_RX0;
stat.inv=CAN_INT_IRXIF;
CAN_INT_IRXIF = 0;
RXB0CON.rxful = 0;
}
void get_1_data(int32 &id, int *data, int &len, struct rx_stat &stat) {
int i, *ptr;
CANCON.win = CAN_WIN_RX1;
stat.buffer = 1;
stat.err_ovfl = COMSTAT.rx1ovfl;
COMSTAT.rx1ovfl = 0;
stat.filthit = RXB1CON.filthit;
len = RXBaDLC.dlc;
stat.rtr = RXBaDLC.rtr;
stat.ext = TXRXBaSIDL.ext;
id = can_get_id(TXRXBaID, stat.ext);
ptr = &TXRXBaD0;
for (i = 0; i < len; i++) {
*data = *ptr;
data++;
ptr++;
}
// return to default addressing
CANCON.win = CAN_WIN_RX0;
stat.inv=CAN_INT_IRXIF;
CAN_INT_IRXIF = 0;
RXB1CON.rxful = 0;
}
#int_TIMER2
TIMER2_isr() {
if (++count == 100) {
clear_to_transmit = TRUE;
count = 0;
}
}
#int_CANRX1
CANRX1_isr() {
//get_1_data(rx_id, &in_data[0], rx_len, rxstat);
//can_getd(rx_id, &in_data[0], rx_len, rxstat);
message1 = TRUE;
}
#int_CANRX0
CANRX0_isr() {
//get_0_data(rx_id, &in_data[0], rx_len, rxstat);
//can_getd(rx_id, &in_data[0], rx_len, rxstat);
message0 = TRUE;
}
void main() {
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_psp(PSP_DISABLED);
setup_spi(FALSE);
setup_wdt(WDT_ON);
setup_timer_0(RTCC_INTERNAL);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DIV_BY_16,39,16);
setup_timer_3(T3_DISABLED);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
enable_interrupts(INT_TIMER2);
enable_interrupts(INT_CANRX1);
enable_interrupts(INT_CANRX0);
enable_interrupts(GLOBAL);
setup_low_volt_detect(FALSE);
setup_oscillator(False);
tx_len = 1;
can_init();
can_set_mode(CAN_OP_LOOPBACK);
while (TRUE) {
restart_wdt();
if (clear_to_transmit) {
clear_to_transmit = FALSE;
can_putd(tx_id, out_data, tx_len, tx_pri, tx_ext, tx_rtr);
printf("\r\nSent to address %LU\r\n", tx_id);
if (--tx_id == 19) {
tx_id = 30;
}
if (++tx_len == 9) {
tx_len = 1;
}
can_putd(tx_id, out_data, tx_len, tx_pri, tx_ext, tx_rtr);
printf("\r\nSent to address %LU\r\n", tx_id);
}
if (message0) {
message0 = FALSE;
get_0_data(rx_id, &in_data[0], rx_len, rxstat);
printf("\r\nGOT: BUFF=%U ID=%LU LEN=%U OVF=%U ", rxstat.buffer, rx_id, rx_len, rxstat.err_ovfl);
printf("FILT=%U RTR=%U EXT=%U INV=%U", rxstat.filthit, rxstat.rtr, rxstat.ext, rxstat.inv);
printf("\r\n DATA = ");
for (i=0;i<rx_len;i++) {
printf("%X ",in_data[i]);
}
printf("\r\n");
}
if (message1) {
message1 = FALSE;
get_1_data(rx_id, &in_data[0], rx_len, rxstat);
printf("\r\nGOT: BUFF=%U ID=%LU LEN=%U OVF=%U ", rxstat.buffer, rx_id, rx_len, rxstat.err_ovfl);
printf("FILT=%U RTR=%U EXT=%U INV=%U", rxstat.filthit, rxstat.rtr, rxstat.ext, rxstat.inv);
printf("\r\n DATA = ");
for (i=0;i<rx_len;i++) {
printf("%X ",in_data[i]);
}
printf("\r\n");
}
}
} |
|
|
|
jamesjl
Joined: 22 Sep 2003 Posts: 52 Location: UK
|
|
Posted: Tue Aug 23, 2005 1:48 pm |
|
|
Newguy,
many thanks. I'll give it a try when I get into the office tomorrow. You have used and instead of as specified in the 18F458 header file. Is there any reason for this. Is there are list of what the CAN interrupts are used for.
Is there any reason why you've only set a flag in the ISR and not actually read in the data. How did this work when you tried it? I see that you have commented it out in your code sample.
Only again, many thanks and I'll let you know how I get on.
Thanks,
Jason. |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1907
|
|
Posted: Tue Aug 23, 2005 2:04 pm |
|
|
jamesjl wrote: |
many thanks. I'll give it a try when I get into the office tomorrow. You have used and instead of as specified in the 18F458 header file. Is there any reason for this. Is there are list of what the CAN interrupts are used for.
Is there any reason why you've only set a flag in the ISR and not actually read in the data. How did this work when you tried it? I see that you have commented it out in your code sample.
Jason. |
Jason,
I can't remember the specific reason for using those interrupts instead of the CANIRX. I did spend a lot of time looking through the datasheet, and I remember there was a reason for me doing it this way, but I really can't think of it at the moment.
The bottom of the 18F458.h header file has a listing of the valid interrupts, but I don't think they are commented/explained there. For a detailed explanation, you have to look at the 18F458's data sheet from Microchip. Once you read that, then the interrupts listed in the header file make sense.
The reason why I just set a flag in the ISR and extract the data afterward was just me experimenting. I like to keep ISRs as short as possible, and this was the shortest, obviously. My app will never send data so quickly that the can rx buffer will "overflow", so this works for me. If you don't like it - just extract the data inside the ISR, that works too. |
|
|
jamesjl
Joined: 22 Sep 2003 Posts: 52 Location: UK
|
|
Posted: Wed Aug 24, 2005 4:06 am |
|
|
newguy,
many thanks for the information you have shared with me. Still some work to be done on my specific code, but the ISR is working great.
Regards,
Jason. |
|
|
Bill24
Joined: 30 Jun 2012 Posts: 45
|
|
Posted: Sat Jul 21, 2012 10:30 am |
|
|
jamesjl wrote: | Newguy,
many thanks. I'll give it a try when I get into the office tomorrow. You have used and instead of as specified in the 18F458 header file. Is there any reason for this. Is there are list of what the CAN interrupts are used for.
..
Jason. |
I have tried to port CAN Code code from a PICF458 to a PICF46K80 and the compiler complains about the CAN interrupts.
Please could someone steer me into finding what the CCS #int_CANIRX should be using for this device.
TIA |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sat Jul 21, 2012 1:40 pm |
|
|
The INT_CANIRX constant should be in the list at the end of the
18F46K80.h file. Search for it. Is it there ?
What's your compiler version ?
You didn't give any description of the error messages or your test program.
Try compiling a minimal test program like the one shown below and see
if you get any errors. If so, post the error messages.
Code: |
#include <18F46K80.h>
#fuses INTRC_IO,NOWDT,PUT,BROWNOUT
#use delay(clock=4M)
#INT_CANIRX
void canirx_isr(void)
{
int8 c;
c = 0x55;
}
//======================================
void main()
{
while(1);
}
|
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19512
|
|
Posted: Sun Jul 22, 2012 1:11 am |
|
|
As a general reply to 'what interrupts do what', look in the file ints.txt in the compiler directory.
CANIRX, is invalid message
CANRX0 and CANRX1 are the receive interrupts
So using CANRX0 and CANRX1 for receive is 'right'.
Best Wishes |
|
|
Bill24
Joined: 30 Jun 2012 Posts: 45
|
|
Posted: Mon Jul 23, 2012 2:28 am |
|
|
PCM programmer wrote: | The INT_CANIRX constant should be in the list at the end of the
18F46K80.h file. Search for it. Is it there ?
What's your compiler version ?
You didn't give any description of the error messages or your test program.
Try compiling a minimal test program like the one shown below and see
if you get any errors. If so, post the error messages.
Code: |
#include <18F46K80.h>
#fuses INTRC_IO,NOWDT,PUT,BROWNOUT
#use delay(clock=4M)
#INT_CANIRX
void canirx_isr(void)
{
int8 c;
c = 0x55;
}
//======================================
void main()
{
while(1);
}
|
|
Compiler version is 4.119. The supplied 18F46K80.h file does not have interrupts for CAN. However the file 18F4680.h has the following.
#define INT_CANIRX 0x00A380
#define INT_CANWAKE 0x00A340
#define INT_CANERR 0x00A320
#define INT_CANTX2 0x00A310
#define INT_CANTX1 0x00A308
#define INT_CANTX0 0x00A304
#define INT_CANRX1 0x00A302
#define INT_CANRX0 0x00A301
I am thinking the hardware designer made a bit of a slip up. |
|
|
RF_Developer
Joined: 07 Feb 2011 Posts: 839
|
Re: CAN Interrupt code |
Posted: Mon Jul 23, 2012 2:59 am |
|
|
jamesjl wrote: | The CAN examples that ship with the compiler poll the CAN RX buffers and this is too slow to ensure that all the transmitted information is process before the next incoming packet. |
What is the bit rate of your CAN system? Even at 1Mbps CAN messages are something like 50-100us long. There are two receive buffers, so you have almost two message worth of time to service any interrupt.
I suspect you're making assumptions about what the problem really is. Any ISR should read and buffer all messages that the CAN peripheral has got. Simply flagging and then reading in the main loop will cause all sorts of time related problems with heavy CAN traffic, the ISR should read and do its own buffering. In my applications, with the CAN bus running at 100kbs, I buffer 16 messages in my own code. I use something like this:
Code: |
// CAN receive circular buffer counters.
// If they are equal then the buffer is empty.
// If Read - Write mod Buffers is 1 then the buffer is full.
// The interrupt routines write in to the buffer, incrementing Write mod Buffers
// The access routines, interrupt locked to prevent buffer corruption, decrement Read mod Buffers.
// This is classic circular buffer behaviour.
//int Can_Rx_Buffer_Write = 0;
//int Can_Rx_Buffer_Read = 0;
signed int Can_Rx_Buffer_Count = 0;
struct Can_Buffer {
BYTE Data[CAN_MESSAGE_LENGTH];
int Length;
long int ID;
#ifdef CAN_TIMESTAMP
long long int Timestamp;
#endif
};
struct Can_Buffer can_rx_buffer[CAN_RX_BUFFERS];
// In this implementation, to avoid 16 bit multiplies in interrupt routines,
// and to get more compact interrupt code we use real C pointers rather
// than array indicies. As a result this code has a distinct C++ feel to it
// as it uses the -> operator extensively.
struct Can_Buffer* Can_Rx_Buffer_Write_Ptr;
struct Can_Buffer* Can_Rx_Buffer_Read_Ptr;
#define CAN_BUFFER_FULL (Can_Rx_Buffer_Count == CAN_RX_BUFFERS)
#define CAN_BUFFER_EMPTY (Can_Rx_Buffer_Count == 0)
int rx_len;
struct rx_stat rxstat;
int32 rx_id;
int buffer[CAN_MESSAGE_LENGTH]; // Create a dummy buffer...
void Read_CAN(void)
{
// With this arrangement of interrupt handling, with two interrupts served
// by one handler, it is possible to service a pending interrupt before its
// raised. When it is finally raised there is no data to receive. Thus it
// possible for this routine to not receive any data under some heavy load
// circumstances. That appears to be normal... hopefully...
while (can_kbhit())
{
if(!CAN_BUFFER_FULL) // Write to buffer if one is available.
{
// Copy the data and the message length into our buffer. It is
// possible for can_getd() to fail, returning FALSE. This should never
// happen thanks to the while above.
can_getd(rx_id, Can_Rx_Buffer_Write_Ptr->Data, rx_len, rxstat);
// For some reason the compiler didn't like an array reference in the can_getd call, so we copy
// the length here instead.
Can_Rx_Buffer_Write_Ptr->Length = rx_len;
Can_Rx_Buffer_Write_Ptr->ID = rx_id;
#ifdef CAN_TIMESTAMP
// Can_Rx_Buffer[Can_Rx_Buffer_Write].Timestamp = Time_ms;
Can_Rx_Buffer_Write_Ptr->Timestamp = Time_ms;
#endif
// Update the buffer write pointer, wrapping if required.
// The % below involves a divide. This is an interrupt routine and
// the PIC implements a divide by a subroutine. This causes trouble
// as it is not atomic nor re-entrant. Hence the compiler will
// warn that it has to disable interrupts. If we change the number of
// buffers to 2, 4 or 8 and use bit masking to do the mod, then this
// issue will go away. Another way round it would be to use an if.
// mod version:
//New_Write_Pointer = (Can_Rx_Buffer_Write + 1) % CAN_RX_BUFFERS;
// The if pointer version follows the same pattern.
if (++Can_Rx_Buffer_Write_Ptr >= Can_Rx_Buffer + CAN_RX_BUFFERS)
Can_Rx_Buffer_Write_Ptr = Can_Rx_Buffer;
Can_Rx_Buffer_Count++;
}
else
{
// No buffer was free, we still have to read the data to somewhere
can_getd(rx_id, buffer, rx_len, rxstat); // ...and fill with the new data. We wont do anything with it.
// No buffer was available. We should perhaps send out a debug message.
// However sending messages from interrupt routines is generally
// a bad idea, and on many processors/compilers is a total no-no.
}
// Loop round if there is any more data available.
}
}
#int_canrx0
void canrx0_int()
{
Read_CAN();
}
#int_canrx1
void canrx1_int()
{
Read_CAN();
}
BOOLEAN Can_Buffer_Read(struct Can_Buffer *Buffer)
{
// This is a flag, set by default to false.
BOOLEAN Got_Data = FALSE;
// We have to disable CAN receive interrupts to prevent incoming
// messages messing us up. This is interrupt locking.
disable_interrupts(INT_CANRX0);
disable_interrupts(INT_CANRX1);
if (!CAN_BUFFER_EMPTY)
{
*Buffer = *Can_Rx_Buffer_Read_Ptr;
// The % below involves a divide. The PIC implements a divide by a
// subroutine. This causes trouble as it is not atomic nor re-entrant.
// Hence the compiler may warn that it has to disable interrupts
// if we use divides in any interrupt routine. If we change the number
// of buffers to 2 or 4 and use bit masking to do the mod, then this
// issue will go away. Another way round the issue is to use an if.
// Actually the problem shouldn't happen as we have interrupts turned
// off and therefore the problem shouldn't arise. However, it may be that
// the compiler isn't smart enough to suss that.
// mod version:
//Can_Rx_Buffer_Read %= (Can_Rx_Buffer_Read + 1) % CAN_RX_BUFFERS;
// if version:
// The pointer version follows the same pattern.
if (++Can_Rx_Buffer_Read_Ptr >= Can_Rx_Buffer + CAN_RX_BUFFERS)
Can_Rx_Buffer_Read_Ptr = Can_Rx_Buffer;
Can_Rx_Buffer_Count--;
Got_Data = TRUE;
}
// Re-enable the interrupts now that we've finished.
enable_interrupts(INT_CANRX0);
enable_interrupts(INT_CANRX1);
// Return the flag to show we have data... or not as the case may be.
return Got_Data;
}
|
The problems are that messages may be received in any of the received buffers, and that you may have more than one message to read. The ISR(s - it really is only one in disguise) may appear complex for an ISR, but does what it needs to do: reads the data and buffers it. It handles the buffers by pointers as a speed and code size optimisation.
Note the read buffer routine is interrupt locked to avoid circular buffer corruption.
This should work at considerable higher CAN bus speeds than 100kbps. I have worked hard to ensure it works under high CAN bus traffic, even with a relatively slow, i.e. several milliseconds, main loop.
RF Developer
RF Developer |
|
|
Douglas Kennedy
Joined: 07 Sep 2003 Posts: 755 Location: Florida
|
|
Posted: Mon Jul 23, 2012 8:00 am |
|
|
RF_Developer has this right. CAN is asynchronous. No matter what a specific PIC is doing a message can arrive. The proven and possibly the only effective way to deal with this is a circular buffer fed by an isr. It is true built in PIC CAN hardware is more extensive in that it will buffer two messages unlike RS232 and it's two chars but the CAN messages may come in fast bursts. A typical CAN enabled PIC has enough ram for a 256 byte buffer. For me I just define the circular buffer as 256 bytes assign a couple of unsigned 8 bit integers for the pointers and let them roll over to do the modulo 256 for the buffer. Now the circular buffer needs some minimal structure since not all messages are the same length .... basically a minimal structure is the message length followed by the ID data etc. This allows a burst of dozens of messages and as long as the code in main can on average keep up nothing is dropped. |
|
|
|
|
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
|