Dec2Bin - is there an equivalent function in Arduino IDE?

Hi there,

I'm attempting to write a little program that controls a car wiper motor via CAN bus. The way that it works is that I send 3 extended CAN frames which control various things on the motor. Each particular function has a value, a start bit and a length in the frame, so for example in the first frame I have the following:

  1. Wipe Frequency (this is a number between 15 and 63). Start bit 0, length 6.
    Example 25 = 011001 in binary

  2. Interval time (0, 1, 2, or 3). Start bit 6, length 2
    Example 2 = 10 in binary

  3. Wipe command (0, 1, 2, 3, 4, 5, 6 or 7). Start bit 8, length 3
    Example 6 = 110 in binary

Once I have decided the value for each function and converted to binary of the length I require, I then join these together into a complete frame (up to 64 bits) like this:
0000010100001111000000000111100000001010001110011010100000100000

Then I will slice the complete frame into bytes of 8 to send as packets on the CAN bus.

Most of this is no problem, but I'm struggling to find a neat function to do what I'm doing in Excel using the following formula: "=BASE(DEC2BIN(I28),10,K28)"

Where DEC2BIN converts the value to binary, then the BASE function adds any zeros required to ensure I have the correct length to fit in the allocated slot in the frame.

Is anyone able to recommend an efficient way of doing this? Since I have quite a lot of values to process, I'd like to make the code nice and short but a quick trawl through google hasn't revealed an obvious solution or equivalent function.

Any value held in any variable in the sketch is already in binary. You can read the value of a bit in an integer using the bitRead() function

why not just work with an array a bytes?

You just need to use bit-shifting (<< and >>), bit-wise-AND (&) and bit-wise-OR (|).

You can use an unsigned long long int to hold your 64-bit value.

Thanks for the input guys, I did some more reading and came up with the following to convert an integer to a bit string:

String DEC2BIT(int dec, int len) {
  Pkt = "";
  for (int i = 0; i < len; i++) Pkt = bitRead(dec, i) + Pkt;
  return Pkt;
}

I then built the following test code using a struct to hold each collection of variables. I just went to test it and I've definitely done something wrong because the value from the struct is not getting passed correctly to the functions. It's coming in as a number like "536839972". What have I done here.??

String Pkt;
String Frame = "";

//Packet 1 Variables
int WipeFrequ_Grp1    = 25;
int IntervalTime      = 1;
int WipeCmdGrp1       = 0;
int WipeCmdGrp2       = 0;
int TeachInGlobal     = 0;
int ColdFunction      = 0;
int WipeFrequ_Grp2    = 25;
int BcmCmdRes0        = 0;
int BcmCmdRes1        = 0;
int WipeCmdGrp3       = 0;
int MsgCntBcmCmd      = 0;

struct GlobalCmd {
  String    Name;
  int*      Value;
  int       StartBit;
  int       PktLen;
};
GlobalCmd GlobalCmdArray[] = {
  {"Wipe Frequency Grp 1",  &WipeFrequ_Grp1,  0,  6},
  {"Interval Time",         &IntervalTime,    6,  2},
  {"Wipe Command Grp 1",    &WipeCmdGrp1,     8,  3},
  {"Wipe Command Grp 2",    &WipeCmdGrp2,     11, 3},
  {"Global Teach Mode",     &TeachInGlobal,   14, 1},
  {"Cold Park Position",    &ColdFunction,    15, 1},
  {"Wipe Frequency Grp 2",  &WipeFrequ_Grp2,  16, 6},
  {"unused",                &BcmCmdRes0,      22, 2},
  {"unused",                &BcmCmdRes1,      24, 3},
  {"unused",                &WipeCmdGrp3,     27, 3},
  {"Message Counter",       &MsgCntBcmCmd,    30, 2}
};
byte GlobalCmdArraySize = sizeof(GlobalCmdArray) / sizeof(GlobalCmdArray[0]);

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

void loop() {

  FrameBuilder();
  Serial.println (Frame);

}


void FrameBuilder() {
  Frame = "";
  for (int j = 0; j < GlobalCmdArraySize; j++)Frame = Frame + DEC2BIT(GlobalCmdArray[j].Value, GlobalCmdArray[j].PktLen);
}

String DEC2BIT(int dec, int len) {
  Pkt = "";
  for (int i = 0; i < len; i++) Pkt = bitRead(dec, i) + Pkt;
  return Pkt;
}

Also, a side bar question... If I wanted to pass the name of the struct to the Frame Builder function so I could send whichever struct I want to turn into a Frame, how do I do that?

Thanks!

Why do you want the bit values in a String ?
What are you going to do with them once they are in the String ? Come to that, why is the text in your struct a String ?

I figured it would be easier to join them together then split them into bytes for sending. Is this not a good way to approach it?

The reason I want to join the whole frame together before splitting it into bytes is that some of the data packets run across two bytes so it's easier to assemble the frame then divide it into bytes

if you handle things in the final form, a byte array ready for transmission, there's no reason you can't manipulate individual bits in specific bytes which have specific IDs.

    byte buf [PKT_SIZE];

    buf [ID_A] |=   1 << ID_A_BIT_2;        // sets bit
    buf [ID_F] &= ~(1 << ID_F_BIT_3);       // clears bit

bit manipulation in registers is an age old task

As was pointed out earlier in the topic you can use an unsigned long long int to hold your 64-bit value.

How much RAM does your device have?

Its very long, over complicated and extremely inefficient. Did you read my earlier post?

This won't be exactly what you need, but it demonstrates the use of the operators I mentioned. I could not understand your example from post #5, for example why you need to encode a 6-bit, 2-bit and 3-bit value into 64 bits when 16 bits would be more than enough.

byte wipeFreq = 25;
byte interval = 2;
byte wipeCmd = 6;
unsigned long long int frame = wipeFreq << 16 | interval << 8 | wipeCmd;

for (byte b=0; b<7; b++) {
  byte sendMe = frame & 0xFF;
  // Do something with sendMe
  frame >>= 8;
  }

Doing this kind of thing, like dealing with packetised data formats required by hardware, is something C language is extremely good and extremely efficient at. C was designed for writing computer operating systems, after all. The way you have approached it is more like the kind of bloated, inefficient techniques you would be forced into if all you had was Visual Basic for Applications (VBA). Like using a hammer to crack a nut? No, more like using a hammer to slice a ripe tomato.

2 Likes

are the bytes that these bit fields straddle on integer boundaries?

@gcjr
First post, thanks I'll have to see if I can find some worked examples to better understand this. I guess I could work through each byte until it is full then continue filling the next.

To your second post, I'm not exactly sure what you mean by this? What I meant was that for example:

UpperRevPosLS_M1 variable is a 10 bit packet which represents an integer between 0 and 710. Start bit10, length 10. So it starts half way through byte 2, and runs across into byte 3.

@UKHeliBob
My worry here was that using a long int would treat it as a number so I wouldn't be able to join packets together without using math functions. Is this the case? My thinking was that a string would treat it as just a series of 1 and 0s without any regard for what they mean.
The text in the struct is not really doing much. I just put it there so that if I want to run any error catching then I can serial print the full name of the variable causing the issue as some of them in other frames are not obviously named.

@wildbill
It's a Teensy 3.5 so 256kbytes

@PaulRB Thanks for this (and love the analogy haha), I must admit I struggled to understand what this was. I still am struggling but thanks for taking the time to expand it. I'll work through it today and see whether I can understand and apply it and make something work.

To your question about post 5, you're absolutely correct about filling the frame. I've only shown a small example with 3 variables to keep things simple but in reality this wiper motor ridiculously over-engineered, and has in total:

  • 11 variables at the global level which makes a 4- byte Frame
  • 14 variables at the local level making a full 8 byte frame for motor 1
  • the same 14 for motor 2
  • 13 variables in a 4 byte frame to read the status of motor 1
  • the same 13 to read status of motor 2.

So in total I'll need to send 3 frames to set up each motor and then control them, then I'll need to receive 2 frames and break them back down into integers to read the status of the motors once in operation. I'm just looking at 3 variables in the global frame to start with though. I have a big spreadsheet which lists all functions and allows me to build and read examples - I could post that if that would help

Using this example, let's suppose you have captured your 64-bit data frame in an unsigned long long int called "myFrame" (we can help you with how to do that later) and you want to read that field:

int upperRevPosLS_M1 = myFrame >> 10 & 0b1111111111;
Serial.println(upperRevPosLS_M1);

What's going on here is that a copy of myFrame is taken and bit-shifted 10 binary positions right (using >>). So now that field you want starts at bit 0 instead of bit 10. The field is 10 bits long, and an int holds 32 bits (on a 32-bit Teensy, only 16 bits on an AVR Ardino like Uno) so you don't want to accidentally pick up any bits that belong to other fields. Therefore you "mask" the result using bit-wise-and (&) using a 10-bit mask of 1s (0b1111111111). That clears (sets to 0) all of the bits that don't belong to the field you want.

1 Like

Thanks Paul! It's still fairly murky but I think I understand this at the micro level. I don't understand how it scales up to the overall task?

So let's take the complete second frame as an example. This frame is 8 bytes long and consists of the following variables. I'll put the value, start bit and length on each row

  • LowerRevPosLS_M1, 230, 0, 10
  • UpperRevPosLS_M1, 240, 10, 10
  • LowerRevPosHS_M1, 30, 20, 10
  • AlternParkPos_M1, 10, 30, 10
  • UpperRevPosHS_SP_M1, 230, 40, 10
  • RotateSense_M, 0, 50, 1
  • TeachInLocal_M1, 0, 51, 1
  • MoveMode_M1, 2, 52, 2
  • SystemSize_M1, 3, 54, 2
  • RevAngleCorr_M1, 56, 0, 2
  • ParamValid_M1, 1, 58, 1
  • WiperGrp_Mx, 0, 59, 2
  • PantographCtrl_M1, 0, 61, 1
  • MsgCntBcmPar_M1, 0, 62, 2

The complete frame should look like this: 0011100110001111000000000111100000001010001110011000101100100000

And breaks down to
Byte 1 = 00111001
Byte 2 = 10001111
Byte 3 = 00000000
Byte 4 = 01111000
Byte 5 = 00001010
Byte 6 = 00111001
Byte 7 = 10001011
Byte 8 = 00100000

So if the total Frame is an unsigned long long int called myFrame, I want to make the first to bits (0-9) equal to 0011100110 in line with the first variable above, followed by 0011110000 for the second.

So from first principles, are we placing the first packet (0011100110) into myFrame and shifting it along to the zero position, then the second (0011110000) and shifting it up to the 11th?

I feel like it's going to click soon and it'll suddenly be easy to understand but not got there yet :exploding_head:

EDIT: I also realised that I can't print an unsigned long long int so I can't test it with the serial monitor to see what's going on...

EDIT 2: I also just realised that what you just described is for capturing a bit string from the overall frame. This will be useful for the 4th and 5th Frames where I'm reading data from the motors. Will park that bit until I've understood how to create the first 3 frames though..

Oh yes you can

void printFullBin(long long data)
{
  for (int c = 0; c < sizeof(data) * 8 ; c++)
  {
    Serial.print((byte)(data >> c) & 1);
  }
  Serial.println();
}

You might want to reverse the order of the bits, but I will leave that to you

1 Like

Magic - thanks that will definitely help!

Ok, for building a frame:

unsigned long long int myFrame = 0;
myFrame |= lowerRevPosLS_M1;
myFrame |= upperRevPosLS_M1 << 10;
myFrame |= lowerRevPosHS_M1 << 20;
myFrame |= alternParkPos_M1 << 30;
...
myFrame |= msgCntBcmPar_M1 << 62;

The << operator shifts the bit patterns to the left by the required number of binary digits. The |= operator does a bit-wise-or with the current pattern in myFrame and assigns the result back to myFrame.

This assumes all your field values will definitely fit into the number of bits they are supposed to occupy, like your example values are. Otherwise it won't work correctly. They can be zero but not negative.

@UKHeliBob please check my suggested code: do we need some ULL indicators or anything?

I don't know, but they cannot do any harm.

I am, however, suspicious of your left shift values. Shouldn't the shifts move the data to 8 bit boundaries in the destination and you need to be careful of the operator precedence so that the shift and OR occur in the correct order