chipKIT® Development Platform

Inspired by Arduino™

Max32 CAN Sniffer

Created Mon, 14 Oct 2019 08:22:07 +0000 by bushwookie


bushwookie

Mon, 14 Oct 2019 08:22:07 +0000

I have embarked on a project to analyse the CAN traffic of a vehicle, using a Max32 and the Network Shield.

So far, I have got MPIDE running & successfully programmed the Max32 with the CANDemo. However, I am struggling to adapt this code to sniff the vehicle's CAN data.

Is my approach, to probe the CAN lines, flawed? I have received no CAN data, not even garbage data.

Can this traffic only be sniffed with a "man-in-the-middle" strategy?

[attachment=0]Max32 setup.png[/attachment] [attachment=1]Max32.png[/attachment]


unexpectedly

Wed, 16 Oct 2019 15:23:06 +0000

You'll have to ensure the pins are correct. I never trust parts... must verify with my own eyes. I found below pic in my old research files for bench testing an ECU.

When you tap off of those CAN pins, terminating resistors shouldn't be required, however, reality does not adhere to what books say. Terminating resistor might be needed for it to work. It'd be fantastical if you could verify anything is present via an o'scope. Mine is from aliexpress but amazon shows some, too.

[attachment=0]obd_CAN_connector_test_rig.jpg[/attachment]


bushwookie

Thu, 17 Oct 2019 07:44:41 +0000

Thanks for the reply!

The timing is much less confusing than I thought.

Looks like my settings should be:

canBitConfig.phaseSeg2Tq            = CAN::BIT_2TQ;
  canBitConfig.phaseSeg1Tq            = CAN::BIT_12TQ;
  canBitConfig.propagationSegTq       = CAN::BIT_1TQ;
  canBitConfig.phaseSeg2TimeSelect    = CAN::TRUE;
  canBitConfig.sample3Time            = CAN::TRUE;
  canBitConfig.syncJumpWidth          = CAN::BIT_1TQ;

I'll try these settings and report back.


bushwookie

Mon, 28 Oct 2019 11:41:04 +0000

The tq code did not work, as

"canBitConfig.phaseSeg1Tq            = CAN::BIT_12TQ;

is not recognised as a valid parameter. The maximum value that can be used here is "8TQ", so I made up the difference by increasing "

canBitConfig.propagationSegTq       = CAN::BIT_1TQ;

to "5TQ".

Result;

canBitConfig.phaseSeg2Tq            = CAN::BIT_2TQ;
  canBitConfig.phaseSeg1Tq            = CAN::BIT_8TQ;
  canBitConfig.propagationSegTq       = CAN::BIT_5TQ;
  canBitConfig.phaseSeg2TimeSelect    = CAN::TRUE;
  canBitConfig.sample3Time            = CAN::TRUE;
  canBitConfig.syncJumpWidth          = CAN::BIT_1TQ;

However this still yields nothing on the serial monitor.

I spent some time with a multimeter checking the wiring, and I'm pretty confident that CANH (pin6) and CANL (pin14) are correct i.a.w this pinout diagram. I have also added a wire to connect to SIGNAL_GND, which I did not previously have. Still nothing. I have soldered wires to the underside of the 2x6 CAN headers. I have shorted the pins vertically with the wire. The pins look like they are shorted anyway.

Next, I tried adding/removing the Network Shield jumper for the termination resistor; no dice.

There are some other jumpers which are unpopulated which I am unsure as to their purpose. In particular, JP4 is unpopulated and so are the 4-way I2C headers.

The documentation says;

JP4 – USB Host Connector Selection When the Max32/Network Shield is used as a USB host, this jumper is used to select which USB connector is being used.

I do not believe I am using the Network Shield as a USB Host, it should be a USB device.

And regarding the 4-way I2C Header, which I believe is JP10 and JP11;

Generally, only one set of pull-ups are used on the bus. Jumpers JP10 and JP11 can be used to disable the on-board pull-ups on I2C #2 if a different value is needed or some other device on the bus is providing the pull-ups or if I2C #2 isn’t being used and the pull-ups are interfering with the use of the pins. The on-board pull-ups are enabled by install shorting blocks on JP10 and JP11. Removing the shorting blocks disables the pull-ups.

Which should be irrelevant.

I'm at somewhat of a loose end with this at the moment.

Here is the full code I've got so far, a stripped down version of @unexpectedly's code

#include  <WProgram.h>
#include <chipKITCAN.h>

char text[128];  // Made buffer big enough to hold two lines of text

/* Network Node Addresses */
#define	node1can1	0x101L
#define node2can1	0x201L
#define node1can2	0x102L
#define node2can2	0x202L

#define SYS_FREQ	(80000000L)
#define CAN_BUS_SPEED   500000		// CAN Speed


/* CAN controller interface object instances. */
CAN    canMod1(CAN::CAN1);    // this object uses CAN module 1
CAN    canMod2(CAN::CAN2);    // this object uses CAN module 2

/* CAN Message Buffers */
uint8_t  CAN1MessageFifoArea[2 * 8 * 16];
uint8_t  CAN2MessageFifoArea[2 * 8 * 16];

/* These are used as event flags by the interrupt service routines. */
static volatile bool isCAN1MsgReceived = false;
static volatile bool isCAN2MsgReceived = false;

/*		Forward Declarations				*/
void initCan1(uint32_t myaddr);
void initCan2(uint32_t myaddr);
void doCan1Interrupt();
void doCan2Interrupt();
void txCAN1(uint32_t rxnode);
void txCAN2(uint32_t rxnode);
void rxCAN1(void);
void rxCAN2(void);
void doCan1Interrupt();
void doCan2Interrupt();


void setup() {
	pinMode(PIN_LED1, OUTPUT); 
	Serial.begin(9600);
	Serial.println("Init:");
	/* Init each CAN controller module for use. */ 
	initCan1(node1can1);
	initCan2(node1can2);
  
	/* Install the interrupt service routines.  */  
	canMod1.attachInterrupt(doCan1Interrupt);
	canMod2.attachInterrupt(doCan2Interrupt);
	Serial.println("can bus modules initialized.");

}

//int idx=0;
void loop() {
	
    rxCAN1(); 

}


/* ------------------------------------------------------------ */
/*      CAN Utility Functions                                   */
/* ------------------------------------------------------------ */
/***  initCan1
**
**  Parameters:
**      myaddr    - network address
**
**  Return Value:
**      none
**
**  Errors:
**      none
**
**  Description:
**      Initialize the CAN controller. See inline comments
**      for description of the process.
*/

void initCan1(uint32_t myaddr) {
  CAN::BIT_CONFIG canBitConfig;

  /* Step 1: Switch the CAN module
   * ON and switch it to Configuration
   * mode. Wait till the switch is 
   * complete */

  canMod1.enableModule(true);

  canMod1.setOperatingMode(CAN::CONFIGURATION);
  
  while(canMod1.getOperatingMode() != CAN::CONFIGURATION);			

  /* Step 2: Configure the CAN Module Clock. The
   * CAN::BIT_CONFIG data structure is used
   * for this purpose. The propagation, 
   * phase segment 1 and phase segment 2
   * are configured to have 3TQ. The CANSetSpeed()
   * function sets the baud. */
	
  canBitConfig.phaseSeg2Tq            = CAN::BIT_2TQ;
  canBitConfig.phaseSeg1Tq            = CAN::BIT_8TQ;
  canBitConfig.propagationSegTq       = CAN::BIT_5TQ;
  canBitConfig.phaseSeg2TimeSelect    = CAN::TRUE;
  canBitConfig.sample3Time            = CAN::TRUE;
  canBitConfig.syncJumpWidth          = CAN::BIT_1TQ;

  canMod1.setSpeed(&canBitConfig,SYS_FREQ,CAN_BUS_SPEED);

  /* Step 3: Assign the buffer area to the
   * CAN module.
   */ 
  /* Note the size of each Channel area.
   * It is 2 (Channels) * 8 (Messages Buffers) 
   * 16 (bytes/per message buffer) bytes. Each 
   * CAN module should have its own message 
   * area. */
   
  canMod1.assignMemoryBuffer(CAN1MessageFifoArea,2 * 8 * 16);	

  /* Step 4: Configure channel 0 for TX and size of
   * 8 message buffers with RTR disabled and low medium
   * priority. Configure channel 1 for RX and size
   * of 8 message buffers and receive the full message.
   */

  canMod1.configureChannelForTx(CAN::CHANNEL0,8,CAN::TX_RTR_DISABLED,CAN::LOW_MEDIUM_PRIORITY);
  canMod1.configureChannelForRx(CAN::CHANNEL1,8,CAN::RX_FULL_RECEIVE);
	
  /* Step 5: Configure filters and mask. Configure
   * filter 0 to accept SID messages with ID 0x200.
   * Configure filter mask 0 to compare all the ID
   * bits and to filter by the ID type specified in
   * the filter configuration. Filter 0 accepted 
   * messages are stored in channel 1.  */

  canMod1.configureFilter      (CAN::FILTER0, myaddr, CAN::SID);    
  canMod1.configureFilterMask  (CAN::FILTER_MASK0, 0xFFF, CAN::SID, CAN::FILTER_MASK_IDE_TYPE);
  canMod1.linkFilterToChannel  (CAN::FILTER0, CAN::FILTER_MASK0, CAN::CHANNEL1); 
  canMod1.enableFilter         (CAN::FILTER0, true);
	
  /* Step 6: Enable interrupt and events. Enable the receive
   * channel not empty event (channel event) and the receive
   * channel event (module event).
   * The interrrupt peripheral library is used to enable
   * the CAN interrupt to the CPU. */

  canMod1.enableChannelEvent(CAN::CHANNEL1, CAN::RX_CHANNEL_NOT_EMPTY, true);
  canMod1.enableModuleEvent(CAN::RX_EVENT, true);

  /* Step 7: Switch the CAN mode
   * to normal mode. */

  canMod1.setOperatingMode(CAN::LISTEN_ONLY);
  while(canMod1.getOperatingMode() != CAN::LISTEN_ONLY);		
  
}


/* ------------------------------------------------------------ */
/***  txCAN1
**
**  Parameters:
**      rxnode    - address of network node to receive the packet
**
**  Return Value:
**      none
**
**  Errors:
**      none
**
**  Description:
**      Initialize a packet buffer with the packet header and the
**      packet payload. The payload in this case is a single
**      ASCII character (0x31 = '1'). Transmit the packet.
*/

void txCAN1(uint32_t rxnode) {
  
  CAN::TxMessageBuffer * message;
  
  message = canMod1.getTxMessageBuffer(CAN::CHANNEL0);

  if (message != NULL) {
	// clear buffer
	message->messageWord[0] = 0;
	message->messageWord[1] = 0;
	message->messageWord[2] = 0;
	message->messageWord[3] = 0;

	message->msgSID.SID   = rxnode;	//receiving node		
	message->msgEID.IDE   = 0;			
	message->msgEID.DLC   = 1;			
	message->data[0]      = 0x31;
	//message->data[1]        = 0x31;
	//message->data[2]        = 0x32;
	//message->data[3]        = 0x33;
	//message->data[4]        = 0x34;
	//message->data[5]        = 0x35;
	//message->data[6]        = 0x36;
	//message->data[7]        = 0x37;

	/* This function lets the CAN module
	 * know that the message processing is done
	 * and message is ready to be processed. */
	 
	 canMod1.updateChannel(CAN::CHANNEL0);

	/* Direct the CAN module to flush the
	 * TX channel. This will send any pending
	 * message in the TX channel. */

	canMod1.flushTxChannel(CAN::CHANNEL0);
  }	

}


/* ------------------------------------------------------------ */
/***  rxCAN1
**
**  Parameters:
**        none
**
**  Return Value:
**        none
**
**  Errors:
**        none
**
**  Description:
**      Check to see if a packet has been received. If so, read
**      the packet and print the packet payload to the serial
**      monitor.
*/

void rxCAN1(void) {
  
  CAN::RxMessageBuffer * message;

  if (isCAN1MsgReceived == false) { 
	/* CAN2 did not receive any message
	 * so exit the function. Note that the
	 * isCAN2MsgReceived flag is updated 
	 * by the CAN2 ISR. */
	return;
  }
	
  /* Message was received. Reset isCAN2MsgReceived flag
   * to catch the next message. */

  isCAN1MsgReceived = false;	
	
  message = canMod1.getRxMessage(CAN::CHANNEL1);

  /* Print the first byte of the packet payload area
   * as an ASCII character on the serial monitor. */
   
  Serial.print(byte(message->data[0]));

  /* Call the CAN::updateChannel() function to let
   * the CAN module know that the message processing
   * is done. Enable the event so that the CAN module
   * generates an interrupt when the event occurs.*/

  canMod1.updateChannel(CAN::CHANNEL1);
  canMod1.enableChannelEvent(CAN::CHANNEL1, CAN::RX_CHANNEL_NOT_EMPTY, true);

}

/* ------------------------------------------------------------ */
/*        Interrupt Handler Functions                           */
/* ------------------------------------------------------------ */
/***  doCan1Interrupt
**
**  Parameters:
**      none
**
**  Return Value:
**      none
**
**  Errors:
**      none
**
**  Description:
**      Interrupt service routine to handle interrupt level
**      events for CAN module 1.
*/

void doCan1Interrupt() {
  /* This is the CAN1 Interrupt Handler.
   * This is not the actual Interrupt Service Routine,
   * but is the user interrupt handler installed by
   * CAN::attachInterrupt. This is called by the ISR.
   * Note that there are many events in the CAN1 module
   * that can cause this interrupt. These events are 
   * enabled by the CAN::enableModuleEvent() function.
   * In this example, only the CAN::RX_EVENT is enabled. */


  /* Check if the source of the interrupt is CAN::RX_EVENT. 
   * This is redundant  since only this event is enabled
   * in this example but this shows one scheme for handling
   * interrupts. */

  if ((canMod1.getModuleEvent() & CAN::RX_EVENT) != 0) {
		
	/* Within this, you can check which event caused the 
	 * interrupt by using the CAN::getPendingEventCode() function
	 * to get a code representing the highest priority active
	 * event.*/ 
		
	if(canMod1.getPendingEventCode() == CAN::CHANNEL1_EVENT) {
	  /* This means that channel 1 caused the event.
	   * The CAN::RX_CHANNEL_NOT_EMPTY event is persistent. You
	   * could either read the channel in the ISR
	   * to clear the event condition or as done 
	   * here, disable the event source, and set
	   * an application flag to indicate that a message
	   * has been received. The event can be
	   * enabled by the application when it has processed
	   * one message.
	   *
	   * Note that leaving the event enabled would
	   * cause the CPU to keep executing the ISR since
	   * the CAN::RX_CHANNEL_NOT_EMPTY event is persistent (unless
	   * the not empty condition is cleared.) 
	   * */
			
	  canMod1.enableChannelEvent(CAN::CHANNEL1, CAN::RX_CHANNEL_NOT_EMPTY, false);
	  isCAN1MsgReceived = true;	
	}
  }

  /* The CAN1 Interrupt flag is cleared by the interrupt service routine
   * after this function returns. This will succeed because the event
   * that caused this interrupt to occur (CAN::RX_CHANNEL_NOT_EMPTY) is disabled.
   * The ISR's attempt to clear the CAN1 interrupt flag would fail if the
   * CAN::RX_CHANNEL_NOT_EMPTY event were still enabled because the base event
   * is still present. In this case, another interrupt would occur immediately */ 
	
}