Skip to content

Instantly share code, notes, and snippets.

@projectgus
Created February 17, 2010 21:51
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save projectgus/307053 to your computer and use it in GitHub Desktop.
Save projectgus/307053 to your computer and use it in GitHub Desktop.
Arduino sketch to monitor a 125 kbps VAN automotive bus (similar to CAN bus)
// First cut of my VAN (similar to CAN) Monitor sktech. Underperforms.
//
// Latency measurement taken from http://billgrundmann.wordpress.com/2009/03/02/the-overhead-of-arduino-interrupts/
#include <avr/interrupt.h>
#define MEASURE_LATENCY 1
const int pin_rx = 3; // Pin hooked to the output of the CAN transceiver
enum message_state { vacant, loading, ready };
const unsigned char ticksPerTs = 128; // 16Mhz clock / 125Kbps / 8 = 128 ticks/bit
const unsigned char timerLoadValue = 256 - ticksPerTs;
// Latency measurement
unsigned char latency = 0;
unsigned long latencySum = 0;
unsigned int sampleCount;
const int buffer_len = 32;
volatile message_state state = vacant;
volatile unsigned char buffer[buffer_len];
// These bitmasks are unused in the current implementation (too slow)
const unsigned char sof = B00111101;
const unsigned char sof_mask = B11111111;
const unsigned char eof = B11111;
const unsigned char eof_mask = B11111;
void SetupTimer2()
{
// Prescaler 0/0/1 means run at no prescaler
TCCR2A = 0;
TCCR2B = 0<<CS22 | 0<<CS21 | 1<<CS20;
//load the timer for its first cycle
TCNT2=timerLoadValue - (ticksPerTs / 2); // Add an extra 1/2 TS because this is the first run
//Timer2 Overflow Interrupt Enable
TIMSK2 |= 1<<TOIE2;
}
void DisableTimer2()
{
TIMSK2 &= ~(1<<TOIE2);
}
// Enable interrupt 1 (pin 3)
void SetupInterrupt1()
{
EICRA = (1<<ISC01) | (0<<ISC00); // Falling edge (Rising 1/1, Falling 1/0)
EIMSK |= (1<<INT1); // Interrupt 1 (pin 3)
sei();
}
void DisableInterrupt1() {
EIMSK &= ~(1<<INT1);
}
// rx pin edge interrupt, indicates start of a possible message
ISR(INT1_vect)
{
if(state != vacant)
return;
DisableInterrupt1();
state = loading;
SetupTimer2(); // Start timer to clock in each bit
}
/*Timer2 overflow interrupt vector handler
*
* Called each time we need to read a new bit on rx pin (every 8us ie 128 clock cycles)
*
* This implementation is 100% simplified and just clocks 32 bytes into a buffer, without caring what they are (ie no start-of-frame/end-of-frame checking)
* and then sets a flag for the main routine to pick the full buffer up and use it.
*
* This is the method which underperforms. Needs to always complete in < 128 cycles (preferably a lot less), currently
* averages 122 cycles, which is too slow (if you look below, you'll see that one in every eight cycles runs longer than the other 7.)
*
* Main overhead looks like (a) using 10 registers, which is 40 cycles in push/pop instructions and (b) the "current |=" line below.
*
*
* I think the best solution will be to use an external shift register to accumulate bits, and call this function 1/8 as often to read an entire byte in parallel.
*
* Alternative solution I have is to rewrite this routine in assembler, but I'm not sure my assembler optimisation skills are up to it, and it still seems like it may run a bit slow.
*
* Haven't thought of anything else I could try and do that would likely help...
*/
ISR(TIMER2_OVF_vect)
{
static unsigned char count = 0;
static unsigned char current = 0;
unsigned char bitCount = count&7; // 0-7
/* This line is quite slow, but everything else I've tried (bitshifts, etc.) runs slower */
current |= (PIND & 1<<pin_rx) ? 0 : (1<<bitCount);
if (bitCount == 7) // Read entire byte
{
buffer[count>>3] = current;
current = 0;
if(count == (buffer_len * 8) - 1) // Filled buffer
{
DisableTimer2();
count = 0;
state = ready;
}
}
count++;
#ifndef MEASURE_LATENCY
unsigned char latency;
#endif
//Latency is how far the timer has run since we started
latency=TCNT2;
//Reload the timer
TCNT2=latency+timerLoadValue;
}
void readMessage(unsigned char *message)
{
for(int i = 0; i < buffer_len; i++) {
message[i] = buffer[i];
}
state = vacant;
SetupInterrupt1();
}
void setup(void) {
pinMode(pin_rx, INPUT);
//Start up the serial port
Serial.begin(9600);
//Signal the program start
Serial.println("Startup");
SetupInterrupt1();
//SetupTimer2();
}
void loop(void) {
while(state != ready) // Wait for a message
{
#ifdef MEASURE_LATENCY
checkLatency();
#endif;
}
unsigned char message[buffer_len];
readMessage(message);
for(int i = 0; i < buffer_len; i++)
{
if(message[i] < 16)
Serial.print("0");
Serial.print(message[i], HEX);
}
Serial.println("");
}
void checkLatency()
{
//Accumulate the current latency value from the ISR and increment the sample counter
latencySum+=latency;
sampleCount++;
//Once we have 100 samples, calculate and output the measurements
if(sampleCount>99) {
float latencyAverage;
float loadPercent;
//Calculate the average latency
latencyAverage=latencySum/100.0;
//zero the accumulator values
sampleCount=0;
latencySum=0;
//Calculate the Percentage processor load estimate
loadPercent=latencyAverage/(float)ticksPerTs;
loadPercent*=100; //Scale up from ratio to percentage;
//Output the average Latency
Serial.print("Latency Average:");
Serial.print((int)latencyAverage);
Serial.print(".");
latencyAverage-=(int)latencyAverage;
Serial.print((int)(latencyAverage*100));
//Output the load percentage estimate
Serial.print(" Load:");
Serial.print((int)loadPercent);
Serial.println("%");
}
}
@projectgus
Copy link
Author

Someone emailed me about this gist some 4+ years after it was posted here, so I figured I'd reproduce my email reply for future reference:

Wow, blast from the past! Thanks for getting in touch.

Unfortunately, there isn't much else I can tell you as it was all so
long ago. I sold the car I was using this with (Peugeot 406) some
years ago.

The older version of the gist (the one you linked in the email to me)
doesn't actually work - it's too slow. But the latest posted version
(with inline AVR assembly) does:
https://gist.github.com/projectgus/307053

Using the sketch in that gist I could capture 125kbps VAN bus frames,
but I never got around to adding support for filtering input or
injecting commands onto the VAN bus or anything else.

The PIC-based code for the "fake CDC" that's online elsewhere is what
I was aiming to eventually reproduce, so I could play MP3s via the CDC
connector, but even in 2010 a lot of the information for that had
already been lost from the internet. To move forward I think I needed
to find a Peugeot 406 with a CDC already fitted and take bus captures
from it, and I never got around to that. :(

I used a simple CAN transceiver chip to convert the differential
VAN/CAN levels to a simple digital input pin on the Arduino. I think
it was probably MAX3058 or similar, although I had it in a
through-hole DIP package so maybe not that one (it might be an older
part that's no longer available).

Regarding documentation I referred a bit to the Datasheet for the
TSS463 "VAN Data Link controller" by Atmel (possibly the only
mass-market IC that could talk VAN??):
http://www.atmel.com/images/doc4205.pdf

The datasheet has some timing diagrams for VAN frames. I also used
whatever I could turn up on the web for the PIC-based CDC
interface, but from what I remember it was pretty patchy.

That's probably about it. If you could find the VAN ISO standard
11519-3 then that'd probably be useful (I didn't have it).

If I was going to do this again today, I'd probably try to buy some of
the TSS463 chips (Atmel don't make them any more, but looks like there
is old stock available on ebay) and interface via those. A 16MHz AVR
doesn't really have enough processing power to interact meaningfully
on the VAN Bus without a lot of tricks and assembly programming, the
interface chip would make things a lot easier I think. That'd also
possibly let you talk to the "fast" VAN Bus that's connected to the
engine (I was only only the "slow" 125Kbps accessory VAN Bus).

Hopefully some of this helps and you get a bit further along than I
did. :)

@SERGIO0004
Copy link

Hello, I try to use you sketch for monitor the VANBUS of my citroen Xsara, but i connect the CAN transceiver (PCA82C250) and don't work. My board is an arduino mega 2560. Can you help me? Thank you.

@morcibacsi
Copy link

Nowadays with ESP32 you can do it pretty easily (also from Arduino IDE): https://github.com/morcibacsi/esp32_rmt_van_rx

@Mokrane82
Copy link

Bonjour
J'aimerais savoir a ce que je peux utiliser sa pour ma voiture

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment