Support for j1850 VPW/PWM?

So the longest possible message (byte) in terms of how long it takes to transmit, would be:

“10101010” or $AA.

Because that would require 1024uS to transmit (128uS alternating high/low pulses), not including the start of frame (200uS), or end of frame (200uS)…

Quickest possible message is “01010101” or $55…it would only take 512uS to transmit byte value $55.

1 Like

J1850 message can be up to 12 bytes (3 byte header, 8 byte payload, 1 byte checksum).

So, doing the math, it could take up to ~13000uS all said and done from start of frame to end of frame (if the message was $AA $55 $AA $55, etc

up to a max of 13mS just to transmit one message on J1850 VPW. Obviously real world is much quicker because most messages arent the full 8 bytes etc.

Pretty slow compared to CAN. :slightly_smiling_face:

1 Like

So the code logic needs to go:

wait for a 200uS high pulse.

Once it sees a 200uS high pulse, it knows a message is starting.

As soon as the signal drops back low after that, start a timer.

When the signal goes back up high, look at the timer.

—> if timer reading = 64 uS, you know that first bit was a 0.
—> if timer reading = 128uS, you know that first bit was a 1.

(zero out the timer, start it over)

when the signal goes back down load, look at the timer.

—> if the timer reading = 64uS, you know that second bit was a 1.
—> if the timer reading = 128uS, you know that second bit was a 0.

start timer over, watch for the next high/low transition, etc.

Thats the basics…now who’s good at translating that into Arduino code!

1 Like

This library might make using the SAMX8E built-in timers a little easier.

2 Likes

I found the data sheet online…

Basically these chips give a direct connection to the MCU. In your case the Uno. Looks like you can connect all 4 lines directly to IO lines on the Uno.

The battery in takes +12 volts and needs a diode in series to protect against battery spikes.

The LOAD connection needs a resistor between it and the databus connection of 10.6k. This prevents destruction of the chip in the case of the ground being disconnected.

It also needs a 470picofarad capacitor going to ground off the databus line.

This data can be used with the hardware of the M2 so I am posting this here. It describes in short what we really want to know.

Receiver Protocol
The Class B communication scheme uses a variable pulse
width (VPW) protocol. The microcontroller provides the VPW
decoding function. Once the receiver detects a transition on Rx,
it starts an internal counter. The initial “start of frame” bit is a
logic [1] and lasts 200μs. For subsequent bits, if there is a bus
transition before 96μs, one logic state is inferred. If there is a
bus transition after 96μs, the other logic state is inferred. The
“end of data” bit is a logic [0] and lasts 200μs. If there is no
activity on the bus for 280μs to 320μs following a broadcast
message, multiple unit nodes may arbitrate for control of the
next message. During an arbitration, after the “start of frame”
bit has been transmitted, the secondary node transmitting the
most consecutive logic [0] bits will be granted sole
transmission access to the bus for that message.

Editing this message as I understand better how it is working…

Should be simple to write a library to work with either the chip or the M2.

The only difference I see at this stage is that the chip does wave forming for you. Zero trigger is actually anything below 20% of signal. High trigger is anything bigger than 80%. Not sure from the output what the chip actually returns. I am assuming it is returning the microseconds between changes?

So to mimic this the M2 would have to read the signal, start the timer at anything above 80% and stop it at anything below 20%. (This amount allows for differences in the voltages of devices in the bus if I understand properly.)

I am assuming the chip, based on the information given sends out a wave form that is slightly rounded off. Not sure without checking but not sure if the Due will be sending out a square wave or more of a rounded off wave. It appears to be necessary to be curved to pick up the trigger points. When I get my lab working I can probably do some testing to see for sure how it is handled.

So theoretically it should be possible to build a library with only minor changes to the code to support either this chip or the M2.

1 Like

Alllllright alllright alllriiiigghhhtttt… :sunglasses:

it works, for receiving.

I found some existing code online, and after some hacking and modification, it now successfully prints GM J1850-VPW traffic in the serial terminal window.

This is using nothing more than an arduino Uno, an MCZ33990, and a bus load resistor.

3 byte header (space) up to 8 byte payload (space) 1 byte checksum


//==============================================================

#define J1850_PIN  3
#define J1850_INT  1

#define LED_PIN 13



// Timing for start of frame
#define SOF_TIME      200
#define SOF_DEV        18

// Timing for end of frame
#define EOF_TIME      200
#define EOF_DEV        18

// Timing for a long bit pulse
#define LONGBIT_TIME   128
#define LONGBIT_DEV     16

// Timing for short bit pulse
#define SHORTBIT_TIME   64
#define SHORTBIT_DEV    15

// timeout after 250 microsec
#define  TMR_PRELOAD (65536 - (EOF_TIME*16))

#define TMROVF_INT_OFF   TIMSK1 &= (~_BV(TOIE1)) 
#define TMROVF_INT_ON    TIMSK1 |= _BV(TOIE1) 
#define TMROVF_INT_CLR   TIFR1 &= _BV(TOV1)


// forward decl
void j1850_interrupt(void);

volatile boolean idle = true;
// Storage, max 11 data bytes + CRC
#define BUFSIZE 13
volatile uint8_t msgbuf[BUFSIZE];
volatile uint8_t msgLen;


//
// Initialization
//
void setup(void) 
{  
  pinMode(J1850_PIN, INPUT);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  Serial.begin(115200);
  delay(2000);
  digitalWrite(LED_PIN, LOW);
  delay(1000);
  Serial.println(F("j1850decoder/v1.5"));

  TMROVF_INT_OFF;
  TCCR1A = 0;
  TCNT1 = TMR_PRELOAD;  // preload timer 65536-16MHz/256/2Hz
  TCCR1B = _BV(CS10);  // no prescaler, start timer 

  idle = true;
  msgLen = 0;
  attachInterrupt(J1850_INT, j1850_interrupt, CHANGE);
  interrupts();
}


//
// Background loop - print message when available
//
void loop(void)
{
  if (msgLen > 0) {
    Serial.print(F("> "));
    for (int i = 0; i < msgLen; i++) {
          if ((i == 3) || (i == msgLen -1)){
        Serial.print("   ");
      }
      if (msgbuf[i] < 16) Serial.print("0");
      Serial.print(msgbuf[i], HEX);
    }
    Serial.println();
    msgLen = 0;
  }
}

//
// Interrupt routine for changes on j1850 data pin
//

volatile unsigned long lastInt = 0;
volatile uint8_t bitCnt;
volatile long delta;
volatile unsigned long tstamp;
volatile uint8_t aByte;
volatile uint8_t buf[BUFSIZE];
volatile uint8_t bufIdx;


void j1850_interrupt(void)
{
  tstamp = micros();

  uint8_t pin = digitalRead(J1850_PIN);

  // reload the overflow timer with EOF timeout
  TCNT1 = TMR_PRELOAD;           

  delta = tstamp - lastInt;
  long longbit, shortbit;

  if (idle)
  {
    if (pin == 0) 
    {
      longbit = delta - SOF_TIME;
      if (abs(longbit) < SOF_DEV)
      {
        // found SOF, start header/data sampling
        idle = false;
        bitCnt = 0;
        bufIdx = 0;
        aByte = 0;
//        digitalWrite(LED_PIN, LOW);
      }
    }
  } 
  else
  {
    shortbit = delta - SHORTBIT_TIME;
    longbit = delta - LONGBIT_TIME;

    if (abs(shortbit) < SHORTBIT_DEV) {
      // short pulse
      if (pin == 0)
        // short pulse & pulse was high => active "1"
        aByte = (aByte << 1) | 0x01;
      else
        // short pulse & pulse was low => passive "0"
        aByte = (aByte << 1) & 0xFE;
      bitCnt++;

    } 
    else if (abs(longbit) < LONGBIT_DEV) {
      // long pulse
      if (pin == 0)
        // long pulse & pulse was high => active "0"
        aByte = (aByte << 1) & 0xFE;
      else
        // long pulse & pulse was low => passive "1"
        aByte = (aByte << 1) | 0x01;
      bitCnt++;

    } 
    else {
      // unknown bit, reset
      TMROVF_INT_OFF; 
      idle = true;
      lastInt = tstamp;
      return;
    }

    if (bitCnt >= 8) {

      buf[bufIdx++] = aByte;
      bitCnt = 0;
      if (bufIdx >= sizeof(buf)) {
        // too many data bytes, error
        TMROVF_INT_OFF; 
        idle = true;
      } 
      else {
        // if all is ok, start the EOF timeout
        TMROVF_INT_CLR; 
        TMROVF_INT_ON; 
      }
    }
  }
  lastInt = tstamp;
}

// Timer overlflow interrupt
// Occurs when the EOF pulse times out the timer
//
ISR(TIMER1_OVF_vect)  
{
  TCNT1 = TMR_PRELOAD;  
  TMROVF_INT_OFF; 
//  digitalWrite(LED_PIN, HIGH);

  // copy the data so that we can start to fill the buffer again
  // but only if the buffer has been consumed in the background
  if (bufIdx > 0 && msgLen == 0)
  {
    memcpy((void*)msgbuf, (const void*)buf, bufIdx);
    msgLen = bufIdx;
  }
  idle = true;
}

1 Like

I dont know why the code formatting got messed up.

Im not sure if this forum interface supports a insert function to make things easier to read?

The reading part looks to be easy… Writing is going to be interesting because you have to do the arbitration.

I just tweaked your post a little bit. The trick is to do add 3 ` characters before and after the code.

Well if I can get my simulator or lab working in the next couple days I will see if I can get the M2 reading. At least for testing purposes. I won’t be able to do much with it until I get done with finals but I should be able to use what we learned today to have something up and running.

Writing will be a little more difficult due to the arbitration. No promises yet but if I can get the reading going this weekend it is possible I could have the initial R/W software running by end of December.

Ok now you did it… I am up looking at the code instead of sleeping…

I will rework the code so I can keep the original and also have the M2 version. Thus it could work with either so I can build a library for it.

Think I understand now better how it works and can work with it.

I don’t have a working lab but I will go through the code and insure it looks like it should work. I could try running on my truck too once running.

When I have something to try I will post a link to my Github for it.No promise on how soon… If you don’t see it before end of Saturday I will probably have to wait until December 1 to look at it again.

@josh, I am looking at the documentation and have a few questions.

Trying to sort out what the schematics are saying so I can properly program for them…

For J1850_VPW_RX this will return a digital 1 if the signal is high and a digital 0 if the signal is low?
VPW is referenced against 5 volts? (Ie 5 volt is 1 and 0volts is 0)

For J1850_PWM_RX it looks like it returns a digital 1 if J1850+ and J1850- are different and a 0 if they are the same?

When looking at the J1850+ and - for transmitting it is a little more complex… Not really a hardware guy…
Looks like when J1850+_TX is set low it sets the bus pin high? and vise versa?

Looks like when J1850-_TX is set low it sets the pin at 5 volts and when it is set high the bus pin is at ground?

My head is spinning so hopefully you can clarify this for me. Did I mention I am not a hardware guy? lol. I can read basic schematics but certainly not an EE.

Ok here is a possible happy moment…

Test it out and see if it works please… I think I was able to just modify the defaults, add the switches we need and boom.

I have no way to test at the moment. Going to bed.

Note I have other sources so we will see if this works up front first… Reads only

One request… Can the docs be updated with the intended operation of the pins as questioned above? (Perhaps better to do it in the docs where it will stay instead of this thread where it will get lost)
Would help with development of the protocols. In the protocol portion of the docs that is where it now has code to turn on the J1850 and choose PMW or VPW

I tried that code, but it doesnt compile for me…

Says “TIMSK1 not declared in this scope”

After some googling, that is an error for the arduino due not having the same hardware as the uno/mega/etc.

Ahh ok… I guess that is what I get for working on it at 3am when I should have been in bed… I will look at it more tomorrow and try to get it corrected. I think the code will work once the constants are set correctly assuming I understand how the M2 is reading the signal.

I have to dig into the code a little more to see how it is actually done but from the surface it should work. Some of the timer stuff might need to be adjusted but to atleast have something working will be a positive. Again, I will try to build the library to support the M2 and using that chip directly. So either direction would work well.

I have 2 or 3 different implementations in code which should get me going in the right direction.

This code is short and feels like a hack but this along with the other stuff I have should allow me to work through it.

Thinking at this point that the baud rate really doesn’t mean much. Otherwise will have to consider that at some point if there is anything out there that isn’t 10.4k baud. Should just be an adjustment from the “standard” 64/128/200 microsecond timings but I will double check with the SAE documentation I have to insure that is accurate.

I am going to work at getting a test bed working so I can test it myself but I will get it to compile and load into the M2 and make the corrections on the repository.

No no…

VPW bus line when idle, is weakly pulled to ground…and when you want to drive the bus ACTIVE, you pull it up to 7v…when you want to drive the bus PASSIVE, you pull it down to solid 0v/ground.

PWM is a differential bus, 5v…just ignore 1850PWM for now, electrically its quite different from VPW.

10.4k is the standard VPW regular communications baud rate.

The only exception, is there is a 4x “high speed” mode, 41.6k. Its only used for module reprogramming, to increase reflash speed.

But the 4x high speed 41.6k mode is never used besides reprogramming. Regular communications for all J1850 VPW vehicles is 10.4k…41.6k high speed obviously isnt as robust and cant tolerate as much noise without messing up, for obvious reasons (because everything is divided by 4, so timing becomes much more critical with tighter tolerances).

Yes I am only dealing with VPW but want to understand how the pins are supposed to work. The J1850+ will be affected by the VPW and I want to understand how it works during transmission… I can also use a VOM and check that way too I suppose but would be nice to see the intended operation.

I ask only for base information of how the pins are supposed to work for PWM and will deal with actual protocols and such later.

As far as the databus pulled to ground. With your lab did you have to put a resistor from the databus to ground to get it to work right? I wonder if that is my issue if it is supposed to be tied to ground. Maybe the specifications page tells more but it would make sense that I am a resistor away from getting my stuff working.

Since I have more time than I thought I did I will try to get some part of my lab working this weekend and hopefully start testing some of this stuff. Believe I need the BCM to work properly to properly test but if I can get my Simulators talking to one another that would be a start.

The Arbitration portion could get interesting when getting ready to write a write driver. Hopefully one of these other sources I have will show a good method to handle that.

For 4x then can I assume that the timings are cut by 1/4? So SOF and EOF would be 50 microseconds instead of 200 and the short bit would be 16 and the long but would be 32 instead of 64 and 128?