Reading MOSI data from Bosch ASIC

It agrees with the diagram on the previous page. Take a look at the SI (bottom left) part. There are 4 x 8-bit bytes there, with the MSB first (most significant). So sending a zero first fills up the most significant bits with zero, followed by the register address. Then we fire off two more zeroes which do the transfer which lets the device reply.

There are four bytes? I've read the diagram as two bytes, due to the presence of the bit 14 and bit 13 labels on the same diagram?

However, I don't see what ADR0 & ADR1 refer to?

Does a 16 bit data frame not include the address? Does the fact that reading and writing from/to the same register uses a different address play a part here?

I've got a BASIC code that interfaces with this chip, however the notes are a rough German to English translation, and I know nothing yet of BASIC. I'll post it up in the morning, it's late this side of the planet :slight_smile:

OK, I misread that bit.

Still that clearly shows you send a byte and get one back afterwards.

So:

SPI.transfer(0x48);  // command
byte result = SPI.transfer(0);

If there's any more confusion, post all your code, not just a couple of lines. You need to set SS low before those two transfers.

Okay, so here's my full code - sorry, I'm still using binary at the moment. The device isn't connected to the O2 sensor yet, so I'm expecting a fault code on the serial monitor.

/* 
 This code segment is written to test functionality of the CJ125 O2 interface hardware
 
 It is not intended to be used as working code, ONLY for hardware testing purposes
 as there are multiple routes into a never ending such as a fault flag print for example
 
 Notes:
 
 - use PID gains of Kp = 80, Ki = 25 for LSU 4.2
 
 - DIAG_REG = 0x78 = 01111000
 - IDENT_REG = 0x48 = 01001000
 - INIT_REG1 RD = 0x6C = 01101100
 - INIT_REG1 WR = 0x56 = 01010110
 - INIT_REG2 RD = 0x7E = 01111110
 - INIT_REG2 WR = 0x5A = 01011010
 
 TODO
 
 */

#include <PID_v1.h>
#include <SPI.h>
#include <EEPROM.h>
#include <EEPROMAnything.h>

// define constants that won't change, such as assigning names to pin numbers

const int UR_pin = A0;
const int UA_pin = A1;
const int heater_pin = 3;
const byte chip_select = 10;

// define sampling timers

unsigned long fault_current_millis = 0;
unsigned long fault_previous_millis = 0;
unsigned long fault_interval_millis = 2000;
unsigned long pumpprint_current_millis = 0;
unsigned long pumpprint_previous_millis = 0;
unsigned long pumpprint_interval_millis = 100;

// define variables that will change

double UR_c = 0;
double UA_c = 0;
double UR_m = 0;
double UA_m = 0;
double heater_duty = 0;
int cold_threshold = 990;
double IP = 0;
boolean fault = false;
boolean cold_start = false;
byte ident_reg_contents = B00000000;
byte diag_reg_contents = B00000000;

PID heater_PID(&UR_m, &heater_duty, &UR_c, 80, 25, 0, DIRECT);

void setup()
{
  Serial.begin(9600);

  // assign pinModes - only outputs need to be set, defaults are input
  pinMode(chip_select, OUTPUT);
  digitalWrite(chip_select, HIGH); // set CS high, as CS is active low
  pinMode(heater_pin, OUTPUT);

  // initialise SPI bus
  SPI.begin();
  SPI.setBitOrder(MSBFIRST); // sets SPI data transfer to MSB first
  SPI.setDataMode(SPI_MODE1); // sets SPI mode to MODE1
  SPI.setClockDivider(SPI_CLOCK_DIV8); // sets shared clock rate to 16Mhz / 8 = 2Mhz

  /*read calibration values from EEPROM to allow heater PID and pump current
   calculations to function without recalibration or from hot start */
  UR_c = EEPROM_readAnything(0, UR_c);
  UA_c = EEPROM_readAnything(8, UA_c);

  // select chip
  digitalWrite(chip_select, LOW);

  // identify CJ125
  SPI.transfer(B01001000);
  ident_reg_contents = SPI.transfer(B00000000);
  if (ident_reg_contents != B00000000)
  {
    Serial.println("");
    Serial.println("SPI comms successful!");
    Serial.println("");
    Serial.print("CJ125 Device ID - ");
    Serial.print(ident_reg_contents);
  }
  else 
    Serial.println("SPI comms failed!");
  Serial.print(ident_reg_contents);
  
  // enable diagnostics and set reference pump current
  
  SPI.transfer(B01011010);
  SPI.transfer(B00010010); 

  // read diagnoses registers
  SPI.transfer(B01111000);
  diag_reg_contents = SPI.transfer(B00000000);

  // is there a fault? set fault boolean and print to serial
  if (diag_reg_contents != B11111111)
  {
    fault = true;
  }

  // deselect chip
  digitalWrite(chip_select, HIGH);

  // check probe temperature, set cold start boolean
  UR_m = analogRead(UR_pin);
  if (UR_m >= cold_threshold)
  {
    cold_start = true;
  }

  // enable heater PID, define sample time and set output limits
  heater_PID.SetMode(AUTOMATIC);
  heater_PID.SetSampleTime(100);
  heater_PID.SetOutputLimits(0, 255);
}

void probe_warmup()
{
  // heat up probe (assumes 20degC cold start, ramps from 8v effective duty at 0.4v/s to 100% duty. ~ 15sec)
  Serial.println("Probe Heating");
  for (heater_duty = 145; heater_duty <= 255; heater_duty++)
  {
    analogWrite(heater_pin, heater_duty);
    delay(138);
  }
  Serial.println("Probe Heat Complete");
  // set cold start flag to false
  cold_start = false;
}

void loop()
{
  if (fault == false && cold_start == true)
  {
    // enter calibration mode
    SPI.transfer(B01010110); // INIT_REG1 WR
    SPI.transfer(B00010100); // sets RA & LA to 1

    // sample calibration values of UR & UA
    UR_c = analogRead(UR_pin);
    UR_c = map(UR_c, 0, 5, 0, 255);
    UA_c = analogRead(UA_pin);
    UA_c = map(UA_c, 0, 5, 0, 255);

    // save UR_c & UA_c to EEPROM (flash in actual board, EEPROM for test)
    EEPROM_writeAnything(0, UR_c);
    EEPROM_writeAnything(8, UA_c);

    // warm up probe
    probe_warmup();

    // enter measurement mode - LA bit controls pump current offset cal, PA controls whether pump current can flow
    SPI.transfer(B01010110); // INIT_REG1 WR
    SPI.transfer(B00000000); // sets RA & LA to 0
  }
  else if (fault == false)
  {
    // run loop code here to update heater PID and calculate pump current periodically
          UR_m = analogRead(UR_pin);
      UR_m = map(UR_m, 0, 5, 0, 255);
      heater_PID.Compute();
      analogWrite(3, heater_duty);

      UA_m = analogRead(UA_pin);
      UA_m = map(UA_m, 0, 5, 0, 255);
	
	unsigned long pumpprint_current_millis = millis();
    if (pumpprint_current_millis - pumpprint_previous_millis > pumpprint_interval_millis)
    {
      pumpprint_previous_millis = pumpprint_current_millis;

      IP = (UA_m - UA_c) / (8 * 61.9); // check this will actually conform BODMAS style

      Serial.print("Pump Current = ");
      Serial.print(IP);
      Serial.println("");
    }
  }
  else
  {
    // print fault flag to serial every 2 sec
    unsigned long fault_current_millis = millis();
    if (fault_current_millis - fault_previous_millis > fault_interval_millis)
    {
      fault_previous_millis = fault_current_millis;
      Serial.println("");
      Serial.print("Fault Code = ");
      Serial.print(diag_reg_contents);
    }
  }
}

...and here's what I get on the serial monitor.

SPI comms successful!

CJ125 Device ID - 9999
Fault Code = 0
Fault Code = 0

So, it looks like it is working, sort of - however, is looks like there is a problem printing binary bytes to the serial monitor? Do I have to do some conversion here?

I am expecting the identity to be 01100xxx, where xxx are unknown to me yet. I'm also expecting an 8 bit number for the diagnosis register.

You have a couple of bugs here:

	if (ident_reg_contents != B00000000)
	{
		Serial.println("");
		Serial.println("SPI comms successful!");
		Serial.println("");
		Serial.print("CJ125 Device ID - ");
		Serial.print(ident_reg_contents);
	}
	else 
        Serial.println("SPI comms failed!");
	Serial.print(ident_reg_contents);

Firstly, you don't print a newline after printing the device ID. CHange that call to println().

Secondly, the else clause only applies to the immediately-following statement. Your code is actually the same as this:

	if (ident_reg_contents != B00000000)
	{
		Serial.println("");
		Serial.println("SPI comms successful!");
		Serial.println("");
		Serial.print("CJ125 Device ID - ");
		Serial.print(ident_reg_contents);
	}
	else
        { 
                Serial.println("SPI comms failed!");
        }
	Serial.print(ident_reg_contents);

In other words, you print the device ID twice and because you haven't included a newline they are merged and appear to be a single number. The clue that this isn't the case is that the value 9999 is too big to be stored in a byte.

I recommend that you get in the habit of always following if, else, for, while and so on with a compound statement i.e. { and } even when there is only a single statement there.

The existence of these bugs is further hidden by the code you use to print the fault code, which prints a blank line before outputting the fault code rather than after. I recommend you output the newline at the point you finish writing the line out, not at the start of the next line.

The actual ID being printed is 99, which is binary 01100011.

Peter

You're an absolute star! Thank you! (and of course, Nick Gammon & his tutorial page!) I've made the changes you recommend and successfully printed the following on the serial monitor;

SPI comms successful!

CJ125 Device ID - 99

Fault Code = 0
Fault Code = 0

However, is there a way to - instead of displaying a decimal 99, to display the binary value in the serial monitor instead?

is there a way to - instead of displaying a decimal 99, to display the binary value in the serial monitor instead?

The Serial::print() method accepts an optional second argument - HEX, OCT, BIN, DEC, etc. The names should give you a clue as to the purpose of the second argument.

Ha, I'd be worried if I couldn't work out those second arguments! So, I've changed my code to include the binary arguments, but don't quite understand the prints I'm getting back out.

/* 
 This code segment is written to test functionality of the CJ125 O2 interface hardware
 
 It is not intended to be used as working code, ONLY for hardware testing purposes
 as there are multiple routes into a never ending such as a fault flag print for example
 
 Notes:
 
 - use PID gains of Kp = 80, Ki = 25 for LSU 4.2
 
 - DIAG_REG = 0x78 = 01111000
 - IDENT_REG = 0x48 = 01001000
 - INIT_REG1 RD = 0x6C = 01101100
 - INIT_REG1 WR = 0x56 = 01010110
 - INIT_REG2 RD = 0x7E = 01111110
 - INIT_REG2 WR = 0x5A = 01011010
 
 TODO
 
 */

#include <PID_v1.h>
#include <SPI.h>
#include <EEPROM.h>
#include <EEPROMAnything.h>

// define constants that won't change, such as assigning names to pin numbers

const int UR_pin = A0;
const int UA_pin = A1;
const int heater_pin = 3;
const byte chip_select = 10;

// define sampling timers

unsigned long fault_current_millis = 0;
unsigned long fault_previous_millis = 0;
unsigned long fault_interval_millis = 2000;
unsigned long pumpprint_current_millis = 0;
unsigned long pumpprint_previous_millis = 0;
unsigned long pumpprint_interval_millis = 100;

// define variables that will change

double UR_c = 0;
double UA_c = 0;
double UR_m = 0;
double UA_m = 0;
double heater_duty = 0;
int cold_threshold = 990;
double IP = 0;
boolean fault = false;
boolean cold_start = false;
byte ident_reg_contents = B00000000;
byte diag_reg_contents = B00000000;

PID heater_PID(&UR_m, &heater_duty, &UR_c, 80, 25, 0, DIRECT);

void setup()
{
  Serial.begin(9600);

  // assign pinModes - only outputs need to be set, defaults are input
  pinMode(chip_select, OUTPUT);
  digitalWrite(chip_select, HIGH); // set CS high, as CS is active low
  pinMode(heater_pin, OUTPUT);

  // initialise SPI bus
  SPI.begin();
  SPI.setBitOrder(MSBFIRST); // sets SPI data transfer to MSB first
  SPI.setDataMode(SPI_MODE1); // sets SPI mode to MODE1
  SPI.setClockDivider(SPI_CLOCK_DIV8); // sets shared clock rate to 16Mhz / 8 = 2Mhz

  /*read calibration values from EEPROM to allow heater PID and pump current
   calculations to function without recalibration or from hot start */
  UR_c = EEPROM_readAnything(0, UR_c);
  UA_c = EEPROM_readAnything(8, UA_c);

  // select chip
  digitalWrite(chip_select, LOW);

  // identify CJ125
  SPI.transfer(B01001000);
  ident_reg_contents = SPI.transfer(B00000000);
  if (ident_reg_contents != B00000000)
  {
    Serial.println("");
    Serial.println("");
    Serial.println("SPI comms successful!");
    Serial.print("CJ125 Device ID - ");
    Serial.println(ident_reg_contents, BIN);
  }
  else 
  {
    Serial.println("SPI comms failed!");
  Serial.print(ident_reg_contents, BIN);
  }
  
  // enable diagnostics and set reference pump current
  
  SPI.transfer(B01011010);
  SPI.transfer(B00010010); 

  // read diagnoses registers
  SPI.transfer(B01111000);
  diag_reg_contents = SPI.transfer(B00000000);

  // is there a fault? set fault boolean and print to serial
  if (diag_reg_contents != B11111111)
  {
    fault = true;
  }

  // deselect chip
  digitalWrite(chip_select, HIGH);

  // check probe temperature, set cold start boolean
  UR_m = analogRead(UR_pin);
  if (UR_m >= cold_threshold)
  {
    cold_start = true;
  }

  // enable heater PID, define sample time and set output limits
  heater_PID.SetMode(AUTOMATIC);
  heater_PID.SetSampleTime(100);
  heater_PID.SetOutputLimits(0, 255);
}

void probe_warmup()
{
  // heat up probe (assumes 20degC cold start, ramps from 8v effective duty at 0.4v/s to 100% duty. ~ 15sec)
  Serial.println("Probe Heating");
  for (heater_duty = 145; heater_duty <= 255; heater_duty++)
  {
    analogWrite(heater_pin, heater_duty);
    delay(138);
  }
  Serial.println("Probe Heat Complete");
  // set cold start flag to false
  cold_start = false;
}

void loop()
{
  if (fault == false && cold_start == true)
  {
    // enter calibration mode
    SPI.transfer(B01010110); // INIT_REG1 WR
    SPI.transfer(B00010100); // sets RA & LA to 1

    // sample calibration values of UR & UA
    UR_c = analogRead(UR_pin);
    UR_c = map(UR_c, 0, 5, 0, 255);
    UA_c = analogRead(UA_pin);
    UA_c = map(UA_c, 0, 5, 0, 255);

    // save UR_c & UA_c to EEPROM (flash in actual board, EEPROM for test)
    EEPROM_writeAnything(0, UR_c);
    EEPROM_writeAnything(8, UA_c);

    // warm up probe
    probe_warmup();

    // enter measurement mode - LA bit controls pump current offset cal, PA controls whether pump current can flow
    SPI.transfer(B01010110); // INIT_REG1 WR
    SPI.transfer(B00000000); // sets RA & LA to 0
  }
  else if (fault == false)
  {
    // run loop code here to update heater PID and calculate pump current periodically
          UR_m = analogRead(UR_pin);
      UR_m = map(UR_m, 0, 5, 0, 255);
      heater_PID.Compute();
      analogWrite(3, heater_duty);

      UA_m = analogRead(UA_pin);
      UA_m = map(UA_m, 0, 5, 0, 255);
	
	unsigned long pumpprint_current_millis = millis();
    if (pumpprint_current_millis - pumpprint_previous_millis > pumpprint_interval_millis)
    {
      pumpprint_previous_millis = pumpprint_current_millis;

      IP = (UA_m - UA_c) / (8 * 61.9); // check this will actually conform BODMAS style

      Serial.print("Pump Current = ");
      Serial.print(IP);
      Serial.println("");
    }
  }
  else
  {
    // print fault flag to serial every 2 sec
    unsigned long fault_current_millis = millis();
    if (fault_current_millis - fault_previous_millis > fault_interval_millis)
    {
      fault_previous_millis = fault_current_millis;
      Serial.println("");
      Serial.print("Fault Code = ");
      Serial.print(diag_reg_contents, BIN);
    }
  }
}

Prints

SPI comms successful!
CJ125 Device ID - 1100011

Fault Code = 0
Fault Code = 0
Fault Code = 0

The device ID is coming out with 7 bits, not the full 8? ...and as for the fault code, I'm expecting 00000000, not 0?

It doesn't add leading zeroes.

You could write your own function which does.

Ah, I see. That would explain then why the fault code which I was expected to be 00000000 simply prints as 0. I shall get to work on editing the print function to print the whole byte. Thanks!

I shall get to work on editing the print function to print the whole byte.

It IS printing the whole byte. The whole byte is 0. If you want to print 0 as "00000000", that is your business, but the Print class is not the place to do that!

I shall get to work on editing the print function to print the whole byte.

Really, I should have typed - "...to print all bits in the byte".

But, why the practice of omitting leading zeros? I see no point in it, as the whole byte has to sent over serial regardless...

But, why the practice of omitting leading zeros?

Er, why do you type:

42

instead of:

00000000000042

I see no point in it, as the whole byte has to sent over serial regardless...

You've lost me there. Do you want to send a whole lot of zeroes? If you are sending the raw data, the single byte is sent anyway. The leading zeroes are just how it is displayed.

For a value of 0, I can completely understand why one wouldn't write 00000000, but for 1100011 - it seems strange to me to omit the leading 0.

I suppose just my inexperience with this sort of thing in general, still - I'm glad I've got it working. I'll take a look on a logic analyser in the next few days, and see if the data is sent back simultaneously with those dummy bits, or after those dummy bits. I'd be interested to know.

EDIT - I suppose if my first encounters with this had been on a 32bit architecture for example, it would make more sense to me to not be printing 00000000000000000000000000000001 for example! (feel free to double check the 31 leading 0s, I can't say I checked myself!)

hi guys :

sorry ... i'm late ...

since i'm currently involved in an avr (poor avr) and a cj125, please allow me a few comments to the cj125 and the code found in the previous posts

i will include code or make more available upon request ... so don't jump on my "i would not"

i would not use floats on a humble 8-bit cpu
i would not use the built in spi functions
i would not use println, since printf was invented

no com err check on rd or wr to the cj125

you can not run pid and expect the I-err-term being correct if you don't assure that the sampling and err-term calculations are done on a fixed period ... you assume that your main loop execution is always at the same loop cycle run-time basis

yes ... this c++ code runs in the arduino ide

it is currently used to read the cj125 diag_reg with adr=0x78 and dat=0x00

it checks for the return of xx101xxx and if correct clocks the 2nd "dat" out and receives efiSpi.Dat and efiSpi.Adr

i would have more suggestions and some questions if anyone working with the cj125 is interested

i realize that it doesn't need to be in a class, but i like it this way

//---------------------------------------------------------------------------
#ifndef efiSpiH
#define efiSpiH
//---------------------------------------------------------------------------

class  TSpi ;
extern TSpi efiSpi ;

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

class  TSpi
{
public :

  enum { eRevSpi = 0x13030900 } ;

  static const DWORD cRevNum ;

//- - - - - - - - - - - - - - - - - - - - - - - - - - -	-

private :

public :

  BYTE  Adr, Dat ;

  bool	Com(BYTE adr, BYTE dat) ;

} ;

//---------------------------------------------------------------------------
#endif	/* efiSpiH	*/

and here the .cpp ...

i like to have a simple revision indicator in every module like ...

const DWORD TSpi::cRevNum = { eRevSpi } ;

Type.h contains all the

typedef unsigned short int WORD ;

#include <Arduino.h>

#include   "Type.h"
#include "efiSpi.h"

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

#define cCS   0x01
#define cMOSI 0x08
#define cMISO 0x10
#define cSCK  0x20

#define NOP __asm__ __volatile__ ("nop\n\t")

//---------------------------------------------------------------------------

TSpi efiSpi ;

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

const DWORD TSpi::cRevNum = { eRevSpi } ;

//---------------------------------------------------------------------------

bool TSpi::Com(BYTE adr, BYTE dat)
{
  bool ret = true ;      
        
/* noInterrupts() ;	not working	*/
  
  PORTB &= ~cSCK ;			/* 1st		*/
  NOP ;					/* 2nd.0	*/
  NOP ;					/* 2nd.1	*/
  NOP ;					/* 2nd.2	*/
  NOP ;					/* 2nd.3	*/
  NOP ;					/* 2nd.4	*/
  NOP ;					/* 2nd.5	*/
  NOP ;					/* 2nd.6	*/
  PORTB &= ~cCS ;			/* 3rd		*/
  NOP ;					/* space	*/

  for (BYTE i = 8 ; i-- ; ) {		/* adr, MSB-First 	*/

    PORTB |=  cSCK ;

    if (adr & 0x80) PORTB |=  cMOSI ;
    else	    PORTB &= ~cMOSI ;

    adr <<= 1 ;

    PORTB &= ~cSCK ;

    NOP ;
    NOP ;

    if (PINB & cMISO) adr |=  1 ;
    else	      adr &= ~1 ;

  }	    Adr = adr ;

  if ((adr &= 0x38) == 0x28) {		/* adr == xx101xxx 	*/

    for (BYTE i = 8 ; i-- ; ) {		/* dat, MSB-First 	*/

      PORTB |=  cSCK ;

      if (dat & 0x80) PORTB |=  cMOSI ;
      else	      PORTB &= ~cMOSI ;

      dat <<= 1 ;

      PORTB &= ~cSCK ;

      NOP ;
      NOP ;

      if (PINB & cMISO) dat |=  1 ;
      else		dat &= ~1 ;

    }	    Dat = dat ;
  } else {  Dat = 0 ;  ret = false ; }          
  
  NOP ;					/* space	*/
  PORTB |=  cCS ;			/* 1st		*/
  NOP ;					/* 2nd.0	*/
  NOP ;					/* 2nd.1	*/
  NOP ;					/* 2nd.2	*/
  NOP ;					/* 2nd.3	*/
  NOP ;					/* 2nd.4	*/
  NOP ;					/* 2nd.5	*/
  NOP ;					/* 2nd.6	*/
  PORTB |=  cSCK ;			/* 3nd		*/

/* interrupts() ;	not working	*/

  return ret ;
}
//---------------------------------------------------------------------------

while the cj125 is a sweet chip and now available, the correct application of the "pump current reference" in the "bosch diagram box 12" for the lsu 4.9 sensor only is for me unknown at this time, as well as the settings in the wr_init2 register

cheers, blue2u

Jtw11 - have yoy made progress on this project ?
Have you got the code runing and reading the lambda from the probe?
Thanks, best regards

For anyone considering to interface the Bosch CJ125 lambda controller with Arduino, Check out our example code on GitHub. Please also consider our Lambda Shield for Arduino.