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)
#include <avr/interrupt.h>
const int pin_rx = 3; // Pin hooked to the output of the CAN transceiver
enum message_state { vacant, loading, ready };
const int ticksPerTs = 132; // 16Mhz clock / 125Kbps / 8 = 128 ticks/bit
const int timerLoadValue = 257 - ticksPerTs;
const int oneHalfTimerLoadValue = 257 - (ticksPerTs * 1.25); // Add an extra 1/4TS when we know we're at start of a bit
const int buffer_len = 36;
unsigned char buffer[buffer_len];
unsigned char buffer_size = 0;
volatile message_state state = vacant;
struct message
{
unsigned char contents[buffer_len];
unsigned char size;
};
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); // Not sure we need to wait 2 full TS here for the first bitread, but it works...
//Timer2 Overflow Interrupt Enable
TIMSK2 |= 1<<TOIE2;
}
void DisableTimer2()
{
TIMSK2 &= ~(1<<TOIE2);
}
// Enable interrupt 1 (pin 3)
void SetupInterrupt1()
{
// Accidentally had this set to "low level" instead...?
EICRA = (1<<ISC11) | (0<<ISC10); // Falling edge (Rising 1/1, Falling 1/0, Any 0/1)
EIMSK |= (1<<INT1); // Interrupt 1 (pin 3)
sei();
}
void DisableInterrupt1() {
EIMSK &= ~(1<<INT1);
}
// rx pin interrupt
ISR(INT1_vect)
{
if(state != vacant)
return;
DisableInterrupt1(); // No more interrupts
state = loading;
SetupTimer2(); // Start timer to clock in next bit
}
/*Timer2 overflow interrupt vector handler
*
* Called each time we have a new bit on rx pin, apart from the 5th "Manchester" bit which comes via interrupt
*
*/
ISR(TIMER2_OVF_vect)
{
TCNT2+=timerLoadValue;
static unsigned char current = 0;
static unsigned char bitCounter = 1;
unsigned char byteFlag; // 0 = midbyte, 1 = finished full byte, 2 = finished packet or error
asm volatile(
"LDI %[byteFlag], 0" "\n\t"
"CLC" "\n\t"
"SBIC %[portd], %[pin_rx]" "\n\t"
"SEC" "\n\t"
"ROL %[current]" "\n\t"
"LSL %[bitCounter]" "\n\t"
"BRCS readFullByte" "\n\t" // Carry bit set means entire byte clocked in, waiting on Manchester
"SBRC %[bitCounter], 4" "\n\t" // Bit 5 set means first nibble clocked in, waiting on Manchester
"JMP waitManchester" "\n\t"
"JMP done" "\n\t"
"readFullByte:" "\n\t"
"LDI %[byteFlag], 1" "\n\t"
"waitManchester:" "\n\t"
// Wait for the last bit we read to flip over for Manchester
"MOV __tmp_reg__, __zero_reg__" "\n\t" // tmpreg for timeout
"SBRS %[current], 0" "\n\t"
"JMP waitForOne" "\n\t"
"waitForZero:" "\n\t" // 10 cycles top to bottom
"STS %[tcnt2], __zero_reg__" "\n\t"
"SBIS %[portd], %[pin_rx]" "\n\t"
"JMP gotManchester" "\n\t"
"INC __tmp_reg__" "\n\t"
"SBRS __tmp_reg__, 4" "\n\t" // bit 5 == 16 == >15 iterations = >150 clock cycles
"JMP waitForZero" "\n\t"
"JMP badManchesterBit" "\n\t"
"waitForOne:" "\n\t" // Almost same as waitForZero. Code reuse fail (should make a macro)
"STS %[tcnt2], __zero_reg__" "\n\t"
"SBIC %[portd], %[pin_rx]" "\n\t"
"JMP gotManchester" "\n\t"
"INC __tmp_reg__" "\n\t"
"SBRS __tmp_reg__, 4" "\n\t"
"JMP waitForOne" "\n\t"
// "JMP badManchesterBit" "\n\t"
"badManchesterBit:" "\n\t" // A "bad" Manchester bit hopefully means EOF, so bail out at this point
"LDI %[byteFlag], 2" "\n\t"
"JMP done" "\n\t"
"gotManchester:" "\n\t"
"MOV __tmp_reg__, r24" "\n\t" // Store r24 so we can use it. This is risky, but no other registers are used in these ops so it's OK.
"LDI r24, %[oneHalfTimerLoad]" "\n\t"
"STS %[tcnt2], r24" "\n\t" // We're at the beginning of the Manchester bit, so reset the timer to wait 1.5 bits to hit middle of next bit
"MOV r24, __tmp_reg__" "\n\t"
"done:" "\n\t"
: [current] "+r" (current), [byteFlag] "=r" (byteFlag), [bitCounter] "+r" (bitCounter)
: [oneHalfTimerLoad] "M" (oneHalfTimerLoadValue),
[pin_rx] "M" (pin_rx), [portd] "I" (_SFR_IO_ADDR(PIND)), [tcnt2] "i" (_SFR_MEM_ADDR(TCNT2))
);
if (byteFlag) // Read entire byte, or done
{
buffer[buffer_size++] = current;
if(byteFlag == 2 || buffer_size == buffer_len) // Done, due to bad bit or full buffer
{
state = ready;
DisableTimer2();
}
current = 0;
bitCounter = 1;
}
}
/* Scan the bytes in the buffer, read them into a new message structure
*
*/
boolean readMessage(struct message *to)
{
to->size = buffer_size;
for(unsigned char i = 0; i < buffer_size; i++) {
to->contents[i] = buffer[i];
}
memset((void *)&buffer, 0, sizeof(buffer));
buffer_size = 0;
state = vacant;
SetupInterrupt1();
}
void setup(void) {
pinMode(pin_rx, INPUT);
//Start up the serial port
Serial.begin(115200);
//Signal the program start
Serial.println("Startup");
Serial.println(timerLoadValue, DEC);
Serial.println(oneHalfTimerLoadValue, DEC);
struct message dummy;
readMessage(&dummy); // Initialise the buffer
}
void loop(void) {
while(state != ready) // Wait for a message
{
#ifdef MEASURE_LATENCY
checkLatency();
#endif
}
struct message msg;
readMessage(&msg);
Serial.print(millis());
Serial.print(" ");
for(int i = 0; i < msg.size; i++)
{
if(msg.contents[i] < 16)
Serial.print("0");
Serial.print(msg.contents[i], HEX);
}
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