at90can128 Can driver

Go To Last Post
5 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,

I have some Can driver routine for my at90can128. I have tested them and so fare this seems good, but want to verify that this is a stable setup.

I want a interrupt driven RX and a polled TX in my system.
I use Mob 0 as reception mob and Mob 1 as tx mob. I also need a 29bit id mask and this is tested ok against external equipment. I use can_tx for sending on 1 sec and 500ms interval. The bus is by the way crowded with packets from many different can components.

Below I have added my code.
Any suggestions for changes and better performance

Regards ellile

void can_init (void)
{
   uint8_t mob;                

   CANGCON = (1 << SWRES);     // Reset the CAN cntr hw

   for (mob = 0; mob < AT90CAN_MOBS; mob++) 
   {
	CANPAGE = (mob << MOBNB0);   // Set the MOb page 
	CANCDMOB = MOB_DIS_CMD;           // Set each MOb 
	CANSTMOB = 0;                   // Clear all the MOb 
   }

	// This will enable all the MObs for interrupt usage. Only the MObs you write CANCDMOB
	// commands to will ever be used, all other MObs will remain disabled and this setting will not
	// matter for disabled MObs.
	
	CANIE2 = 1;		  //Enable interrupt on Mob 0
	CANPAGE = (0 << MOBNB0);  // Set the MOb page 
	CANSTMOB = 0;             // Clear all the MOb 

	CANIDM1 = 0;              //get all messages 
    	CANIDM2 = 0;              //1 = check bit 
    	CANIDM3 = 0;              //0 = ignore bit 
    	CANIDM4 = (1<<IDEMSK); 
	CANCDMOB = (1<<CONMOB1) | (1<<IDE);   // Set MOb to extended format and as reception mob.

	CANBT1 = 0x02;              // Timing - Baud Rate 
	CANBT2 = 0x0C;              // Timing - Re-
	CANBT3 = 0x1B;              // Timing - Phase Segment 2, Phase Segment 1 and Sample Point.	PS2=001, PS1=101

	// This is the minimum Rx/Tx interrupt enable. It does not include MOb errors, General errors,
	// the BXOK, BOFFI or the OVR IT interrupt.
	CANGIE = (1 << ENIT) | (1 << ENRX) | (1 << ENTX);
	
	CANGCON = (1 << ENASTB);    // Enable the CAN.
}



volatile MOb can_msg[RxCanSize];
volatile uint8_t can_tail;
volatile uint8_t can_head;
/*
Looptime approx 36,7uS
*/


char can_tx (MOb msg)
{
unsigned char i;
	uint8_t temp = CANPAGE;     //save CANPAGE	
	   
//enable MOb1, auto increment index, start with index = 0
	CANPAGE = (1<<MOBNB0);     			
	
	if((  CANCDMOB & 0b11000000) && ( (CANSTMOB & 0x40) ==0))	
	{
		CANPAGE = temp;     //restore CANPAGE
		return false;	   	    
	}		  
	   
	msg.id <<= 3;          //write 29 Bit identifier
	CANIDT4 = (unsigned char) (msg.id&0xF8);
	CANIDT3 = (unsigned char) (msg.id>>8);
	CANIDT2 = (unsigned char) (msg.id>>16);
	CANIDT1 = (unsigned char) (msg.id>>24);
	   
	for (i=0; i<8; i++)      //put data in mailbox
		CANMSG = msg.data [i];

	CANSTMOB = 0x00;        // cancel pending operation 
	CANCDMOB = (1<<CONMOB0) | (1<<IDE) | (8<<DLC0);               //enable transmission //set IDE bit, length = 8  
	
	CANPAGE = temp;                        //restore CANPAGE
	
	return true;     	 
}






ISR(CANIT_vect)                  //receive interrupt
{	
	uint8_t temp = CANPAGE;            //save CANPAGE
	unsigned char i;
	uint8_t head;
	MOb msg;	
	
	CANPAGE = (0 << MOBNB0);                        //select MOb0
	msg.len = CANCDMOB & 0x08;
	
	for (i=0; i<8; i++)      //get data
		msg.data [i] = CANMSG;
	
	msg.id = 0;
	msg.id |= ((unsigned long) CANIDT1<<24);   //get identifier
	msg.id |= ((unsigned long) CANIDT2<<16);
	msg.id |= ((unsigned long) CANIDT3<<8);
	msg.id |= (CANIDT4 & 0xF8);
	msg.id >>= 3;
	
	head = ((can_head + 1) & RxCanMask);
	can_msg[can_head] = msg;
		
	if ( head != can_tail)
		can_head = head;	
	
	CANSTMOB = 0;               //clear interrupt flag		
	CANCDMOB = (1<<CONMOB1) | (1<<IDE);      //reconfig mailbox
	
	CANPAGE = temp;                        //restore CANPAGE
	CANSTMOB = 0;               //clear interrupt flag	
}

Br Leif G

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Keep in mind none of the MOb storage has any reset default values. The MOb registers must be initialized by your program code or they will have garbage in them. The CAN General registers do have reset initialization default values (see the data sheet). There were some places where you did not initialize CANIDT or CANIDM.

Quote:
I want a interrupt driven RX and a polled TX in my system.

    CANIE2 = 1;       //Enable interrupt on Mob 0
…..
    CANGIE = (1 << ENIT) | (1 << ENRX) | (1 << ENTX);

The CANGIE.ENRX=1 and CANIE2.IEMOB0=1 allows MOb 0 to trigger the CANIT_vect ISR when MOb 0 CANSTMOB.RXOK is set by the CAN hardware. The CANIE2.IEMOB1=0 prevents MOb 1 from being able to trigger the CANIT_vect ISR or set the read only CANGIT.CANIT bit, which is what you want. However, since you will not be able to use CANGIT.CANIT for MOb 1 Tx polling why is CANGIE.ENTX=1 set? As long as you poll MOb 1 CANSTMOB.TXOK for Tx complete you do not need or really want ENTX to be set.

You command the CAN hardware to start the MOb 0 Rx before you ever set the CANGCON.ENASTB=1 (Enable the CAN). If this worked for you I do not see how or why it worked. You should move any MOb task commands (Rx or Tx) until after you enable the CAN.

This appears to be broken:

    if((  CANCDMOB & 0b11000000) && ( (CANSTMOB & 0x40) ==0))

How about trying this instead:

    if ((CANSTMOB & (1 << TXOK)) == 0)

or even better:

    if ((CANEN & (1 << ENMOB1)) != 0)		// Is MOb 1 still busy and in use?

When you write a MOb 1 command to CANCDMOB, it sets CANEN.ENMOB1. After the Tx completes, the MOb 1 CANSTMOB.TXOK=1 is set by the CAN hardware, the CANEN.ENMOB1 MOb 1 busy bit is automatically cleared by the CAN hardware (any RXOK or TXOK in any MOb will disable that MOb and clear its CANEN busy bit). This means you would not even be polling the CANSTMOB.TXOK for Tx complete and only check CANEN to see if MOb 1 is ready for a new task (MOb 1 is not busy) or not ready for a new task (MOb 1 is still is busy).

This is too much work for no reason:

    msg.id <<= 3;          //write 29 Bit identifier
    CANIDT4 = (unsigned char) (msg.id&0xF8);
    CANIDT3 = (unsigned char) (msg.id>>8);
    CANIDT2 = (unsigned char) (msg.id>>16);
    CANIDT1 = (unsigned char) (msg.id>>24);

Since you are using GCC which supports 32 bit AVR register read/write why not:

    CANIDT = (msg.id << 3);	// This leaves RTRTAG, RB1TAG and RB0TAG all zero.
// I assume msg.id is an unsigned long (uint32_t).

I noticed you set msg.id somewhere outside the can_tx() code.

In ISR(CANIT_vect):

    msg.id = 0;
    msg.id |= ((unsigned long) CANIDT1<<24);   //get identifier
    msg.id |= ((unsigned long) CANIDT2<<16);
    msg.id |= ((unsigned long) CANIDT3<<8);
    msg.id |= (CANIDT4 & 0xF8);
    msg.id >>= 3;

Again why not:

    msg.id = (CANIDT >> 3);

A problem here is unless msg.id is a global declared volatile you will loose this value when you return from the ISR. However, if msg.id is a global declared volatile then your msg.id code in can_tx() code must cli() disable AVR global interrupts when msg.id is set up and not sei() enable global interrupts until after can_tx() is finished using msg.id, or the CAN ISR may randomly clobber the msg.id value when you try and start a new Tx. It do not think it is a good idea to use msg.id in the CAN ISR and in the can_tx() code unless they use separate storage? Since you did not show your MOb msg structure (can_msg) or setup I'm not sure how it plays out (like if there are separate msg storage pointers for different MObs, etc.) and I really do not need to know anyway.

In ISR(CANIT_vect) which is always after the MOb 0 CANSTMOB.RXOK, unless CANIDT4.RTRTAG==0 (data frame) there are no CANSMG bytes to read. This may happen because you set CANIDM4.RTRMSK=0 which allows receiving data for remote frames (remote frames have no CAN data bytes, but DLC will have the number of bytes to reply with). This:

    msg.len = CANCDMOB & 0x08;

is not the DLC field. It is only the DLC3 bit and it ignores the DLC2, DLC1 and DLC0 bits. The CAN hardware allows DLC to be larger then 8, but it only allows up to 8 CANMSG bytes maximum. This is why you need to limit DLC to 8 maximum. During the CAN Rx process the CAN hardware will update your original CANCDMOB.DLC value with what it actually gets from the CAN Rx message (possibly larger then 8 ). You may tell the CAN hardware you want to Rx 8 bytes, but the hardware will only Rx as many bytes as are sent, then update DLC with this new updated actual count. I noticed you tell all your Rx commands to expect zero CAN data bytes which does not matter since you set msg.len for the actual DLC anyway.

In ISR(CANIT_vect) the last:

    CANSTMOB = 0;               //clear interrupt flag

is a bug and needs to be removed.

Here is a changed version of your code. It does not deal with the use of msg.id in the ISR (you need to figure that out).

void can_init (void)
{
   uint8_t mob;

   CANGCON = (1 << SWRES);     // Reset the CAN cntr hw

// Should can_tail and can_head be initialized here since the CAN hardware is also just initialized?

   for (mob = 0; mob < AT90CAN_MOBS; mob++)
   {
    CANPAGE = (mob << MOBNB0);   // Set the MOb page
    CANCDMOB = MOB_DIS_CMD;           // Set each MOb
    CANSTMOB = 0;                   // Clear all the MOb
   }

    CANIE2 = 1;       //Enable interrupt on Mob 0

// Do not give any CAN MOb commands until after the (1 << ENASTB) below.
//  CANPAGE = (0 << MOBNB0);  // Set the MOb page
//  CANSTMOB = 0;             // Clear all the MOb
//
//  CANIDM1 = 0;              //get all messages
//      CANIDM2 = 0;              //1 = check bit
//      CANIDM3 = 0;              //0 = ignore bit
//      CANIDM4 = (1<<IDEMSK);
//  CANCDMOB = (1<<CONMOB1) | (1<<IDE);   // Set MOb to extended format and as reception mob.

    CANBT1 = 0x02;              // Timing - Baud Rate 
    CANBT2 = 0x0C;              // Timing - Re-
    CANBT3 = 0x1B;              // Timing - Phase Segment 2, Phase Segment 1 and Sample Point.  PS2=001, PS1=101

    // This is the minimum Rx/Tx interrupt enable. It does not include MOb errors, General errors,
    // the BXOK, BOFFI or the OVR IT interrupt.
//  CANGIE = (1 << ENIT) | (1 << ENRX) | (1 << ENTX);
    CANGIE = (1 << ENIT) | (1 << ENRX);

    CANGCON = (1 << ENASTB);    // Enable the CAN.

    CANPAGE = (0 << MOBNB0);  // Set the MOb page
//  CANSTMOB = 0;             // Clear all the MOb -- Already just done above.

    CANIDT4 = 0;              // RB1TAG and RB0TAG must be zero values.
        // All other CANIDT bits do not matter because CANIDM.IDEMSK=1 (29 bit ID),
        // CANIDM.RTRMSK=0 (data frame or remote frame) so CANIDT4.RTRTAG is do not care,
        // all CANIDM.IDMSKn (n is 0 to 29) bits are zero so CANIDT.IDTn (0 to 29) bits are do not care.
//  CANIDM1 = 0;              //get all messages
//      CANIDM2 = 0;              //1 = check bit
//      CANIDM3 = 0;              //0 = ignore bit
//      CANIDM4 = (1<<IDEMSK);
        CANIDM = (1<<IDEMSK);			// Does the same thing.
    CANCDMOB = (1<<CONMOB1) | (1<<IDE);   // Set MOb to extended format and as reception mob.
}



volatile MOb can_msg[RxCanSize];
volatile uint8_t can_tail;
volatile uint8_t can_head;
/*
Looptime approx 36,7uS
*/


char can_tx (MOb msg)
{
unsigned char i;
    uint8_t temp = CANPAGE;     //save CANPAGE

//enable MOb1, auto increment index, start with index = 0
    CANPAGE = (1<<MOBNB0);

//  if((  CANCDMOB & 0b11000000) && ( (CANSTMOB & 0x40) ==0))
    if ((CANEN & (1 << ENMOB1)) != 0)       // Is MOb 1 still busy and in use?
    {
        CANPAGE = temp;     //restore CANPAGE
        return false;
    }

//  msg.id <<= 3;          //write 29 Bit identifier
//  CANIDT4 = (unsigned char) (msg.id&0xF8);
//  CANIDT3 = (unsigned char) (msg.id>>8);
//  CANIDT2 = (unsigned char) (msg.id>>16);
//  CANIDT1 = (unsigned char) (msg.id>>24);
    CANIDT = (msg.id << 3);                     // This leaves RTRTAG, RB1TAG and RB0TAG all zero.
    CANIDM = 0;                                 // Initialize all the reserved bits as per the data sheet.
                                                // CANIDM is not used for Tx, but the data sheet wants it initialized.

    for (i=0; i<8; i++)      //put data in mailbox
        CANMSG = msg.data [i];

    CANSTMOB = 0x00;        // cancel pending operation
    CANCDMOB = (1<<CONMOB0) | (1<<IDE) | (8<<DLC0);               //enable transmission //set IDE bit, length = 8

    CANPAGE = temp;                        //restore CANPAGE

    return true;
}



ISR(CANIT_vect)                  //receive interrupt
{   
    uint8_t temp = CANPAGE;            //save CANPAGE
    unsigned char i;
    uint8_t head;
    MOb msg;

    CANPAGE = (0 << MOBNB0);                        //select MOb0
//  msg.len = CANCDMOB & 0x08;
    msg.len = CANCDMOB & 0x0F;
    if (msg.len > 8)
        msg.len = 8;
    if ((CANIDT4 & (1 << RTRTAG)) != 0)
        msg.len = 0;                                // Remote frames to not have any CANMSG data bytes.
       
//  for (i=0; i<8; i++)      //get data
    for (i=0; i < msg.len; i++)      //get data
        msg.data [i] = CANMSG;

//  msg.id = 0;
//  msg.id |= ((unsigned long) CANIDT1<<24);   //get identifier
//  msg.id |= ((unsigned long) CANIDT2<<16);
//  msg.id |= ((unsigned long) CANIDT3<<8);
//  msg.id |= (CANIDT4 & 0xF8);
//  msg.id >>= 3;]
    msg.id = (CANIDT >> 3);

    head = ((can_head + 1) & RxCanMask);
    can_msg[can_head] = msg;

    if ( head != can_tail)
        can_head = head;

    CANSTMOB = 0;               //clear interrupt flag
    CANCDMOB = (1<<CONMOB1) | (1<<IDE);      //reconfig mailbox

    CANPAGE = temp;                        //restore CANPAGE
//  CANSTMOB = 0;               //clear interrupt flag
// After restoring CANPAGE you do not even know what CANPAGE is pointing at. The above CANSTMOB=0 will
// randomly clear some MOb CANSTMOB register. This is a bug that needs to be removed.
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You may also want to consider the type of Tx you want to use.

When CANGCON.TTC=0 (not in TTC mode) any Tx errors from your AVR CAN node will automatically retry the failed Tx. This will create additional CAN bus traffic if your CAN node ever gets any Tx errors.

When CANGCON.TTC=1 (in TTC mode) any Tx errors from your AVR CAN node will not automatically retry. Without the automatic retry when there is a Tx error your CAN Tx will be lost, but you know you will be repeating your Tx at 1 sec and 500ms intervals. Since your code is not aware of CAN Tx errors you will not know when a Tx error happens, but all the other CAN nodes will know a Tx error happened and not use any corrupted Tx error data from your CAN node (your error Tx is lost to the entire CAN network).

If the other CAN nodes on your CAN bus are already TTC mode then your AVR CAN node must be able to figure out what the TTC time slot timing is (this is custom on each CAN bus) and only Tx during an empty unique time slot. If you bust into another CAN node unique time slot with your Tx, you will cause Tx arbitration which may cause havoc with the CAN network TTC Tx timing. If you do bust into another Tx time slot that is not yours and you are not in TTC=1 mode, the automatic error retries could really wreck the TTC time slots for all the other CAN nodes.

Is it important that each CAN Tx from your CAN node must make a maximum effort to get each Tx through when you send it (TTC=0), even if it may create extra CAN bus activity (use more CAN bus bandwidth)? I have no way to evaluate which one will work best on your CAN bus with its unique needs, but you may want to think about it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks so much for the reply I will start going through this and test your changes.

Best regards
Leif G

Br Leif G

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have inserted your code and everything works fine. I do not need a timeout I inserted, because of a hang up on the can. So thanks very much and also the good explanation.

Br Leif G