CAN-Bus programing: Copy a variable section of an Array to convert to Dec

Hello everyone!
So, I have been reading for hours now trying to solve this problem - so I hope I can reach out to you guys with the following question.

What I want to do:
Read CAN-Bus messages from a car and turn the information in it into a human-readable form.

What I need to do:

  1. Read the CAN-message with a MCP2515 --> Works
  2. bitRead() all the bits in one message (8*8=64 bits) and copy them into a new Array in the correct order

This array contains all the bits I need to access. Some information on the Bus is just Boolean using 1 Bit to inform about the On/Off state. Others need to combine certain Bits, convert them into Dec and then offset and multiply them.

I managed to copy all the bits into a char Array[64] called CanMessage

Where my problem is:
I can seem to "copy" a variable combination of bits together to convert them into decimal.

Like I could have 0110110101110111 (the real array would have 64 chars)

Then I need to take like Bit 3 to 8 (so: 101101) and covert this into Dec (so: 45)
I just don't seem to get this...any hints?

--> My code is beneath, based on a CAN example for receiving data.
I tired to commend anything of importance.
Help is very much appreciated!

#include <mcp_can.h>
#include <SPI.h>

long unsigned int rxId;
unsigned char len = 0;
unsigned char rxBuf[8];
char msgString[128];                        // Array to store serial string
char CanMessage[64];



#define CAN0_INT 2                              // Set INT to pin 2
MCP_CAN CAN0(5);                               // Set CS to pin 10


void setup()
{
  Serial.begin(115200);
  
  // Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled.
  if(CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK)
    Serial.println("MCP2515 Initialized Successfully!");
  else
    Serial.println("Error Initializing MCP2515...");
  
  CAN0.setMode(MCP_NORMAL);                     // Set operation mode to normal so the MCP2515 sends acks to received data.

  pinMode(CAN0_INT, INPUT);                            // Configuring pin for /INT input
  
  Serial.println("MCP2515 Library Receive Example...");
}

void loop()
{
char Test[]=""; // Test-Array for a section of the total message to convert to Decimal


  
  if(!digitalRead(CAN0_INT))                         // If CAN0_INT pin is low, read receive buffer
  {
    CAN0.readMsgBuf(&rxId, &len, rxBuf);      // Read data: len = data length, buf = data byte(s)
    
    if((rxId & 0x80000000) == 0x80000000)     // Determine if ID is standard (11 bits) or extended (29 bits)
      sprintf(msgString, "Extended ID: 0x%.8lX  DLC: %1d  Data:", (rxId & 0x1FFFFFFF), len);
    else
      sprintf(msgString, "Standard ID: 0x%.3lX       DLC: %1d  Data:", rxId, len);
  
    Serial.print(msgString);
  
    if((rxId & 0x40000000) == 0x40000000)
    {    // Determine if message is a remote request frame.
      sprintf(msgString, " REMOTE REQUEST FRAME");
      Serial.print(msgString);
    } else 
    
    {
      //len
      for(byte i = 0; i<len; i++)
          {
            byte u=0;
            for ( byte Bit=8; Bit>0; Bit--)
              {
                Serial.print(bitRead(rxBuf[i],Bit-1));
                CanMessage[(i*8)+u]=bitRead(rxBuf[i],Bit-1);  // But all the 0s and 1s in ONE Array after each other
                u++;
              }

          }
    }
  


// The follwing part is just for testing of the Bits are in the correct order!
     
    Serial.println();
    Serial.print("0-7: ");
    for ( byte i=0; i<8; i++)
    {
      Serial.print(CanMessage[i],BIN); 
    }
  Serial.println();

  
  }


//Serial.println(Test[1]);
//Test conversion of a String containing "00101" --> works if I substitute it for "CanMessage"

//long value= strtol(CanMessage, NULL, 2);
//Serial.println(CanMessage);


}

So this:

void setup() {
 Serial.begin(115200);
 char input_binary_string[] = "00000111";
 long value= strtol(input_binary_string, NULL, 2);
 Serial.println(value);
}
void loop() {
}

does exactly what I want. Now all I need to do is cut out the part of the long array and paste it into a new, smaller array. Any thoughts?

For bit manipulation, always unsigned data types. So char in not OK but unsigned char (byte, uint8_t) is OK.
Using this format e.g. uint8_t is best because it is platform independent.

For your earlier example:

Then I need to take like Bit 3 to 8 (so: 101101) and covert this into Dec (so: 45)

conventionally, a byte is bits 0 to 7. Bit 0 is the most significant bit so you want bits 2 to 7.

So, if your byte contains 0b11101101 and you just want the 6 least significant bits, if necessary justify it, then simply mask it .

uint8_t byte01 = 0b11101101 ;  // your example with 2 unwanted bits at the start
byte01 &= 0b00111111 ;  // 0b00111111 is a mask which drops the 2 most significant bits
Serial.print( byte01 ) ; // prints 45 to the console.

If, instead, you want the 6 most significant bits:

uint8_t byte01 = 0b10110101 ;  // your example with 6 wanted bits at the left side
byte01 = byte01 >> 2 ;  // right shift 2. The 2 unwanted bits drop of the end
byte01 &= 0b00111111 ;  // 0b00111111 is a mask which drops the 2 most significant bits (just in case)
Serial.print( byte01 ) ; // prints 45 to the console.

Sme1986:
Hello everyone!
So, I have been reading for hours now trying to solve this problem - so I hope I can reach out to you guys with the following question.

What I want to do:
Read CAN-Bus messages from a car and turn the information in it into a human-readable form.

What I need to do:

  1. Read the CAN-message with a MCP2515 --> Works
  2. bitRead() all the bits in one message (8*8=64 bits) and copy them into a new Array in the correct order

This array contains all the bits I need to access. Some information on the Bus is just Boolean using 1 Bit to inform about the On/Off state. Others need to combine certain Bits, convert them into Dec and then offset and multiply them.

I managed to copy all the bits into a char Array[64] called CanMessage

Where my problem is:
I can seem to "copy" a variable combination of bits together to convert them into decimal.

Like I could have 0110110101110111 (the real array would have 64 chars)

Then I need to take like Bit 3 to 8 (so: 101101) and covert this into Dec (so: 45)
I just don't seem to get this...any hints?

from what I can understand your CAN message appears to be stuctured in a way similar to below where the each colour represents a set of your data within the message:
Untitled.png

If that's the case and you know the structure then a clever way of getting the data out would be to actually using "struct" and/or "union"

another way would be to just shift the bits from the respective bytes into a variable then output that data. (6v6gt best me to it! :slight_smile: )

Untitled.png

@sherzaad: Thanks a lot, yes, the Data is structured the way you put in the picture. I am not totally new to arduino but have never heard of the commands you are telling me to use.
My attempt was just to put everything in one long "String" and then take out the portions I would need.
Anyhow, your attempt looks a lot more professional, if you have the time, could you give me a quick example on how you would solve my problem with you commands?
Thanks a lot for your reply!

Sme1986:
@sherzaad: Thanks a lot, yes, the Data is structured the way you put in the picture. I am not totally new to arduino but have never heard of the commands you are telling me to use.
My attempt was just to put everything in one long "String" and then take out the portions I would need.
Anyhow, your attempt looks a lot more professional, if you have the time, could you give me a quick example on how you would solve my problem with you commands?
Thanks a lot for your reply!

if you can share you message structure ... it will make it easier help you developing that! :slight_smile:

I hope I get you right here.
The message is coming from a Car, a standard 500kbaud CAN Bus.
I receive 8 bytes of Data per message, leaving me with something like
"12 34 56 78 9A AB AC AD"

Now, I thought it be wise to bitRead() all of the bits inside the message and paste them into one new Array ( then having 8Bytes*8Bits= 64Bits of information). Then, regarding whatever the CAN-Database says I just take (for example) Bit 7 through 15 and put them into a new Array, char String or whatever makes sense to display the selected bits in Decimal value.

If you're going to keep what you have rather than doing direct bit manipulation, just use memmove to get the bytes you want into a buffer, null terminate it and then use strtol as you already did.

Sme1986:
I hope I get you right here.
The message is coming from a Car, a standard 500kbaud CAN Bus.
I receive 8 bytes of Data per message, leaving me with something like
"12 34 56 78 9A AB AC AD"

Now, I thought it be wise to bitRead() all of the bits inside the message and paste them into one new Array ( then having 8Bytes*8Bits= 64Bits of information). Then, regarding whatever the CAN-Database says I just take (for example) Bit 7 through 15 and put them into a new Array, char String or whatever makes sense to display the selected bits in Decimal value.

it's convoluted but makes sense....

@Wildbill: Thanks for your reply! I tried using the command you suggested as described HERE.

I tried:

  memmove (CanMessage,CanMessage,2);
  Serial.println (CanMessage);

Sadly - no luck with this. All in all it seems like a good approach, can you post a snip of code that will explain the function?

@sherzaad: Yes, I know there are probably better ways and I am always trying to learn new things - but this is just the A-B-C variant that I could come up with. If you can hook me up with some code with your method I would be more then thankful - but as we all are here on our free time I understand if that just a bit too much :slight_smile:

Greetings all and a big thank you!

Sme1986:
@sherzaad: Yes, I know there are probably better ways and I am always trying to learn new things - but this is just the A-B-C variant that I could come up with. If you can hook me up with some code with your method I would be more then thankful - but as we all are here on our free time I understand if that just a bit too much :slight_smile:

I might be able to but I need a data set to work with and you are not sharing that! :slight_smile:

A table similar to what I had posted but with YOUR data would be handy for that.

@sherzaad: Ok, I hope I am getting this right - you are asking for the Dataset of where in the 64 Bits the information is that I am looking for?
Point is, this set will be different for every message that I take from the car.
The ABS will have different information in different place of the message than (for instance) a message form the engine control unit.

For example:

  • Turn signal info (Boolean - ON/OFF) is located at startbit 3 with a length of 1
  • Engine RPM (unsigned Int) is located at startbit 14 with a length of 4

How would you take these sections from the whole 64bit message?

Sme1986:
@sherzaad: Ok, I hope I am getting this right - you are asking for the Dataset of where in the 64 Bits the information is that I am looking for?
Point is, this set will be different for every message that I take from the car.
The ABS will have different information in different place of the message than (for instance) a message form the engine control unit.

For example:

  • Turn signal info (Boolean - ON/OFF) is located at startbit 3 with a length of 1
  • Engine RPM (unsigned Int) is located at startbit 14 with a length of 4

How would you take these sections from the whole 64bit message?

Ok... if you have 100s of CAN messages I'm not doing those!

However as a template for you (If I can manage to get a sensible code made out) if you can for ONE of those CAN messages share "the Dataset of where in the 64 Bits the information" I'm willing to give it a go.

a good example to work on would be a CAN messages with datasets of varying bit lengths.

sherzaad: I hope you are not thinking I wanted you to do all :slight_smile:
A template or just something to understand how it works would be wonderful!

So, this is one of the CAN-Messages:
ID: 0x033

Info inside this CAN Frame:

Acc1: Startbit 0, length 16
Acc2: Startbit 16, length 3
Acc3: Startbit 19, length 8

That was the most varying I could find :slight_smile:

Once again: Thanks!

So this:

Code: [Select]

void setup() {
Serial.begin(115200);
char input_binary_string[] = "00000111";
long value= strtol(input_binary_string, NULL, 2);
Serial.println(value);
}
void loop() {
}

does exactly what I want. Now all I need to do is cut out the part of the long array and paste it into a new, smaller array. Any thoughts?

You can use pointers and strncpy to extract pieces of the array.

void setup() {
  Serial.begin(115200);
  char CanMessage[] = "111110000011100000";
  char * ptr = CanMessage;
  //want to pull out "00000111"
  //8 chars plus null
  //starts at ptr+5
  char input_binary_string[9] = "";
  strncpy(input_binary_string, ptr + 5, 8); //extract 8 bits from 6 chars in
  Serial.println(input_binary_string);
  long value = strtol(input_binary_string, NULL, 2);
  Serial.println(value);
}

void loop() {}

Try this:

char CanMessage[64] = "010101011100010100101110101110111001010101010101010111100100101";

void setup()
{
  Serial.begin(115200);
  Serial.println("Example...");
  GetField(CanMessage, 0, 16);
  GetField(CanMessage, 16, 3);
  GetField(CanMessage, 19, 8);
}

void loop()
{
}

long GetField(char* Message, int start, int len)
{
  char buf[17];
  long number = 0;
  memmove(buf, &Message[start], len);
  buf[len] = 0;
  number = strtol(buf, NULL, 2);
  Serial.print("Binary: ");
  Serial.print(buf);
  Serial.print(" Decimal: ");
  Serial.println(number);
  return number;
}

Sme1986:
sherzaad: I hope you are not thinking I wanted you to do all :slight_smile:
A template or just something to understand how it works would be wonderful!

So, this is one of the CAN-Messages:
ID: 0x033

Info inside this CAN Frame:

Acc1: Startbit 0, length 16
Acc2: Startbit 16, length 3
Acc3: Startbit 19, length 8

That was the most varying I could find :slight_smile:

Once again: Thanks!

as psuedocode, something like this would probably work; hope you get the gist of it! :slight_smile:

/*
Acc1: Startbit 0, length 16
Acc2: Startbit 16, length 3
Acc3: Startbit 19, length 8
*/

uint8_t recv_arr[8];
uint16_t Acc1=0;
uint8_t Acc2=0;
uint8_t Acc3=0;

CAN0.readMsgBuf(&rxId, &len, recv_arr);

Acc1 |= recv_arr[1]; //bits 8-15 of Acc1 (upper byte)
Acc1 = ( Acc1<<8 )|recv_arr[1]; //now add the lower byte

Acc2 = recv_arr[2] & 0x07; //this gives you only bits 16-18

Acc3 = (recv_arr[2]>>3)| (recv_arr[3]<<5); //gets you bits 19-23 from [2] (shift right since lower bits) and bits 24-26 from [3] (shift left since upper bits)

EDIT:

thought of a probably simpler solution! those "defines" are optional btw!
defines:

#define BIT_LENGTH_1 0x01
#define BIT_LENGTH_2 0x03
#define BIT_LENGTH_3 0x07
#define BIT_LENGTH_4 0x0F
#define BIT_LENGTH_5 0x1F
#define BIT_LENGTH_6 0x3F
#define BIT_LENGTH_7 0x7F
#define BIT_LENGTH_8 0xFF
#define BIT_LENGTH_9 0x01FF
#define BIT_LENGTH_10 0x03FF
#define BIT_LENGTH_11 0x07FF
#define BIT_LENGTH_12 0x0FFF
#define BIT_LENGTH_13 0x1FFF
#define BIT_LENGTH_14 0x3FFF
#define BIT_LENGTH_15 0x7FFF
#define BIT_LENGTH_16 0xFFFF

pseudocode:

/*
  Acc1: Startbit 0, length 16
  Acc2: Startbit 16, length 3
  Acc3: Startbit 19, length 8
*/

#define ACC1_START_BIT 0
#define ACC2_START_BIT 16
#define ACC3_START_BIT 19

union {
  uint64_t val;
  uint8_t recv_arr[8]; //Little Endian order
} buff;

uint16_t Acc1 = 0;
uint8_t Acc2 = 0;
uint8_t Acc3 = 0;

CAN0.readMsgBuf(&rxId, &len, &buff.recv_arr);

Acc1 = (buff.val >> ACC1_START_BIT) & BIT_LENGTH_16;
Acc2 = (buff.val >> ACC2_START_BIT) & BIT_LENGTH_3;
Acc3 = (buff.val >> ACC3_START_BIT) & BIT_LENGTH_8;

cattledog:
You can use pointers and strncpy to extract pieces of the array.

void setup() {

Serial.begin(115200);
  char CanMessage[] = "111110000011100000";
  char * ptr = CanMessage;
  //want to pull out "00000111"
  //8 chars plus null
  //starts at ptr+5
  char input_binary_string[9] = "";
  strncpy(input_binary_string, ptr + 5, 8); //extract 8 bits from 6 chars in
  Serial.println(input_binary_string);
  long value = strtol(input_binary_string, NULL, 2);
  Serial.println(value);
}

void loop() {}

Thanks a lot for that piece of info! So far, I at least kind of understand that one... XD
If I just upload your sketch to my ESP - voila, it works like a charm!
Sadly, if I then add the lines to my code... it does not :frowning:

I add this:

char * ptr = CanMessage;                    // Pointer for array
  char input_binary_string[9] = "";
  strncpy(input_binary_string, ptr + 5, 8); //extract 8 bits from 6 chars in
  Serial.println(input_binary_string);
  long value = strtol(input_binary_string, NULL, 2);
  Serial.println(value);

to the end section of my code. The Array "CanMessage" is filled here. But the result of your code is empty.

Maybe a hint, something I noticed:

If I put:

Serial.println();
    Serial.print("0-7: ");
    for ( byte i=0; i<8; i++)
    {
    Serial.print(CanMessage[i]);
    }

I don't get anything from the CanMessage*.*
If I then just add BIN like...
* *Serial.println();     Serial.print("0-7: ");     for ( byte i=0; i<8; i++)     {     Serial.print(CanMessage[i],BIN);     }* *
...it works and I get
* *0-7: 11111111* *
in the console.
Why does the Array seem empty if I don't add that "BIN"?
Once again... thanks... :confused:
Just for reference - here is the full code:
```
*// CAN Receive Example
//

#include <mcp_can.h>
#include <SPI.h>

long unsigned int rxId;
unsigned char len = 0;
unsigned char rxBuf[8];
char msgString[128];                        // Array to store serial string
char CanMessage[64];

#define CAN0_INT 2                              // Set INT to pin 2
MCP_CAN CAN0(5);                              // Set CS to pin 10

void setup()
{
  Serial.begin(115200);
 
  // Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled.
  if(CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK)
    Serial.println("MCP2515 Initialized Successfully!");
  else
    Serial.println("Error Initializing MCP2515...");
 
  CAN0.setMode(MCP_NORMAL);                    // Set operation mode to normal so the MCP2515 sends acks to received data.

pinMode(CAN0_INT, INPUT);                            // Configuring pin for /INT input
 
  Serial.println("MCP2515 Library Receive Example...");
}

void loop()
{

if(!digitalRead(CAN0_INT))                        // If CAN0_INT pin is low, read receive buffer
  {
    CAN0.readMsgBuf(&rxId, &len, rxBuf);      // Read data: len = data length, buf = data byte(s)
   
    if((rxId & 0x80000000) == 0x80000000)    // Determine if ID is standard (11 bits) or extended (29 bits)
      sprintf(msgString, "Extended ID: 0x%.8lX  DLC: %1d  Data:", (rxId & 0x1FFFFFFF), len);
    else
      sprintf(msgString, "Standard ID: 0x%.3lX      DLC: %1d  Data:", rxId, len);
 
    Serial.print(msgString);
 
    if((rxId & 0x40000000) == 0x40000000)
    {    // Determine if message is a remote request frame.
      sprintf(msgString, " REMOTE REQUEST FRAME");
      Serial.print(msgString);
    } else
   
    {
      //len
      for(byte i = 0; i<len; i++)
          {
            byte u=0;
            for ( byte Bit=8; Bit>0; Bit--)
              {
                CanMessage[(i8)+u]=bitRead(rxBuf[i],Bit-1);  // But all the 0s and 1s in ONE Array after each other
                //Serial.print((i
8)+u);
                //Serial.println(bitRead(rxBuf[i],Bit-1));             
                u++;           
              }

}
    }

// The follwing part is just for testing of the Bits are in the correct order!
   
    Serial.println();
    Serial.print("0-7: ");
    for ( byte i=0; i<8; i++)
    {
    Serial.print(CanMessage[i],BIN);
    }
//

char * ptr = CanMessage;                    // Pointer for array
  char input_binary_string[9] = "";
  strncpy(input_binary_string, ptr + 5, 8); //extract 8 bits from 6 chars in
  Serial.println(input_binary_string);
  long value = strtol(input_binary_string, NULL, 2);
  Serial.println(value);
  }

}

/*********************************************************************************************************
  END FILE
********************************************************************************************************/
* *Thanks for all the other reply's as well! I will try out everyone and finally will try to understand that BitShifting thing. I think its time for some tea+youtube tutorials once again :-)* *-->> Something hast just come to my mind - did I forget to NULL terminate the Array??* *EDIT: if I add* *
CanMessage[sizeof(CanMessage)+1]='\0';
```
nothing happens :frowning:

Something hast just come to my mind - did I forget to NULL terminate the Array??

The constructed CanMessage is indeed not null terminated.

EDIT: if I add
Code: [Select]

CanMessage[sizeof(CanMessage)+1]='\0';

nothing happens :frowning:

This is not correct

char CanMessage[64];

sizeof(CanMessage) is 64, and you are adding the null outside of the array bounds.

If you always receive 64 bits, then you can make char CanMessage[65] and CanMessage[64] ='\0'
Remember, arrays are 0 indexed.

If you want to terminate the array you receive if less than 64 bits, then you can use

CanMessage[strlen(CanMessage) +1] = '\0';