Created
February 17, 2010 21:51
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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("%"); | |
} | |
} |
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.
Nowadays with ESP32 you can do it pretty easily (also from Arduino IDE): https://github.com/morcibacsi/esp32_rmt_van_rx
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
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: