#define bit of byte for CAN Bus packet

Hello,
First post, lots of PLC programming experience, new to Arduino / C++.

I'm building an air suspension controller for a vehicle. One Adafruit Feather M4 CAN Express controls the suspension and communicates with a 2nd unit via CAN bus which is a user interface on the dash. I have the electronics working. and CAN bus talking.

The data being sent is in an eight byte array.
I need one byte to represent eight bools and the remaining seven bytes will be int values.
I want to give easy names to all of the data, so I used #define to reference bytes in the array, which works as expected.
I'd like to do the same with the eight bits (bit 0 = pump_on, bit2 = pump_fail, etc...) but can figure out a way to name the individual bits.
Any ideas?
I tried sending a structure, but couldnt get it to work.

Thank you.

// BODY MODULE (blue light)

#include <CAN.h>
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip(1, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);

#define MY_PACKET_ID 0xAD

uint32_t timestamp;

//initilaize arrays for inbound and outbound data

struct outstruct {   //tried sending a structure, no luck so far
  bool bit0;
  bool bit1;
  bool bit2;
  bool bit3;
  bool bit4;
  bool bit5;
  bool bit6;
  bool bit7;
  int int0;
  int int1;
  int int2;
  int int3;
  int int4;
  int int5;
  int int6;
};

byte sendbuff[8];
byte rcvbuff[8];



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

  pinMode(PIN_CAN_STANDBY, OUTPUT);
  digitalWrite(PIN_CAN_STANDBY, false); // turn off STANDBY
  pinMode(PIN_CAN_BOOSTEN, OUTPUT);
  digitalWrite(PIN_CAN_BOOSTEN, true); // turn on booster

  //Light the neopixel blue so we can identify the BODY MODULE on the testing bench
  strip.begin();
  strip.setBrightness(5);
  strip.setPixelColor(0, 0, 0, 255);  //set color to green
  strip.show();

  //define easy names for data
  #define TANKPRES sendbuff[0]
  #define LEFTPRES sendbuff[1]
  #define RIGHTPRES sendbuff[2]

      // send some test values.....
      TANKPRES = 55;
      LEFTPRES = 66;
      RIGHTPRES = 77;
  


  // start the CAN bus at 250 kbps
  if (!CAN.begin(250000)) {
    Serial.println("Starting CAN failed!");
    while (1);
  }

  timestamp = millis();
}


void loop() {
  // every 1000 ms send out a packet
  if ((millis() - timestamp) > 1000) {
    
  
    Serial.print("Sending packet");
        
    CAN.beginPacket(MY_PACKET_ID);
        
    CAN.write(sendbuff, 8);

    CAN.endPacket();
  
    
    Serial.print(sendbuff[0]);
    Serial.print("  ");
    Serial.print(sendbuff[1]);
    Serial.print("  ");
    Serial.print(sendbuff[2]);
    Serial.print("  ");
    Serial.print(sendbuff[3]);
    Serial.print("  ");
    Serial.print(sendbuff[4]);
    Serial.print("  ");
    Serial.print(sendbuff[5]);
    Serial.print("  ");
    Serial.print(sendbuff[6]);
    Serial.print("  ");
    Serial.print(sendbuff[7]);
    Serial.println();
    
    timestamp = millis();
  }

Did you look at bits and bytes on the reference page?

I did.
I (think that I) understand how to set and read bits. Once I start writing the bulk of my code, I want to be able to do something like PUMPON = 1 instead of bitset(sendbuff[0] , 2). (assuming bit #3 of the first bite represents the pump running state)
I'm just looking for a plain English way to reference the bits, like I'm doing with the #define on the bytes.

Thank you.

Could you use bitWrite(x, n, PUMPON); and just do all the writes in a function at the end of the loop?

#define CLR(x,y) (x&=(~(1<<y))) //bit y of x will 0
#define SET(x,y) (x|=(1<<y))      //1
#define CHK(x,y) (x & (1<<y))    // gives a value of bit y
#define TOG(x,y) (x^=(1<<y))    //will toggle
1 Like

another alternative

#define PUMPON(value) (bitWrite(pump,2,value))
byte pump=0;

sets bit 2 of variable pump to value (0 or 1)

// BODY MODULE (blue light)

#define PIN_NEOPIXEL 2
#define PIN_CAN_STANDBY 3
#define PIN_CAN_BOOSTEN 4
#include <CAN.h>
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip(1, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);

#define MY_PACKET_ID 0xAD
#define TANKPRES sendbuff[0]  //define easy names for data
#define LEFTPRES sendbuff[1]
#define RIGHTPRES sendbuff[2]
#define CLR(x,y) (x&=(~(1<<y))) //bit y of x will 0
#define SET(x,y) (x|=(1<<y))      //1
#define CHK(x,y) (x & (1<<y))    // gives a value of bit y
#define TOG(x,y) (x^=(1<<y))    //will toggle

uint32_t Timestamp;
const uint16_t Period=1000UL;
byte sendbuff[8];
byte rcvbuff[8];

void setup() {
  Serial.begin(9600);
  pinMode(PIN_CAN_STANDBY, OUTPUT);
  digitalWrite(PIN_CAN_STANDBY, false); // turn off STANDBY
  pinMode(PIN_CAN_BOOSTEN, OUTPUT);
  digitalWrite(PIN_CAN_BOOSTEN, true); // turn on booster

  //Light the neopixel blue so we can identify the BODY MODULE on the testing bench
  strip.begin();
  strip.setBrightness(5);
  strip.setPixelColor(0, 0, 0, 255);  //set color to green
  strip.show();

  // send some test values.....
  TANKPRES = 55;
  LEFTPRES = 66;
  RIGHTPRES = 77;
  if (!CAN.begin(250000)) {// start the CAN bus at 250 kbps
    Serial.println("Starting CAN failed!");
    while (1);
  }
  Timestamp = millis();
}

void loop() {
  // every 1000 ms send out a packet
  if ((millis() - Timestamp) > Period) {
    Serial.print("Sending packet");
    CAN.beginPacket(MY_PACKET_ID);
    CAN.write(sendbuff, 8);
    CAN.endPacket();

    for (int8_t i = 0; i < 8; i++) {
      Serial.print(sendbuff[i]);
      if (i % 7)Serial.print("  ");
      else Serial.println();
    }
    Timestamp += Period;
  }

The last time I looked a byte was 8 bits and an int was 16 bits in Arduino language, so what magic will be used to accomplish this. CAN is bytes, 0-7 what they are is entirely up to your software. Did you bytes instead of int?

Whoops, yes, I meant byte not int.

Thanks,
I this works:

//define easy names for data
  #define PUMPRUN(value) (bitWrite(sendbuff[0],0,value))
  #define PUMPFAULT(value) (bitWrite(sendbuff[0],1,value))
  #define TANKPRES sendbuff[1]
  #define LEFTPRES sendbuff[2]
  #define RIGHTPRES sendbuff[3]
  

      // send some test values.....
      TANKPRES = 55;
      LEFTPRES = 66;
      RIGHTPRES = 77;
      PUMPRUN(1);
      PUMPFAULT(1);

I missed the fact that #define can be passed a value. Thanks.

1 Like

You should endeavor to get that working. Structs provide bitfield capability which is exactly what you're asking for. Generally speaking, pre-processor macros should used only as a last resort.

struct ControllerData {
  uint8_t pumpOn : 1;
  uint8_t pumpFail : 1;
  uint8_t otherBit1 : 1;
  uint8_t otherBit2 : 1;
  uint8_t otherBit3 : 1;
  uint8_t otherBit4 : 1;
  uint8_t otherBit5 : 1;
  uint8_t otherBit6 : 1;

  uint8_t tankPres;
  uint8_t leftPres;
  uint8_t rightPres;
  uint8_t otherByte1;
  uint8_t otherByte2;
  uint8_t otherByte3;
  uint8_t otherByte4;
};

ControllerData data;

void setup() {
  data.pumpOn = 1;
  data.pumpFail = 0;
  data.tankPres = 100;
  //
  //
  //
  CAN.write(&data, sizeof(data));
1 Like

Amen :+1:

if ( data.pumpOn == 1 ) {...} // right?

Yes.

Hi,
I think I follow most of this, however the code won't compile with this line:
CAN.write(&data, sizeof(data));
If I comment out the one line it compiles.

The thrown error is:



D:\..some file path...\Body_Module_02.ino: In function 'void loop()':
D:\..some file path...\Body_Module_02.ino:94:34: error: no matching function for call to 'CANSAME5x::write(ControllerData*, unsigned int)'
   94 |     CAN.write(&data, sizeof(data));
      |                                  ^
In file included from c:\Users\..some file path...\libraries\CAN_Adafruit_Fork\src/CANSAME5x.h:5,
                 from c:\Users\..some file path...\libraries\CAN_Adafruit_Fork\src/CAN.h:8,
                 from D:\..some file path...\Body_Module_02.ino:3:
c:\Users\..some file path...\libraries\CAN_Adafruit_Fork\src/CANController.h:26:18: note: candidate: 'virtual size_t CANControllerClass::write(uint8_t)'
   26 |   virtual size_t write(uint8_t byte);
      |                  ^~~~~
c:\Users\..some file path...\libraries\CAN_Adafruit_Fork\src/CANController.h:26:18: note:   candidate expects 1 argument, 2 provided
c:\Users\..some file path...\libraries\CAN_Adafruit_Fork\src/CANController.h:27:18: note: candidate: 'virtual size_t CANControllerClass::write(const uint8_t*, size_t)'
   27 |   virtual size_t write(const uint8_t *buffer, size_t size);
      |                  ^~~~~
c:\Users\..some file path...\libraries\CAN_Adafruit_Fork\src/CANController.h:27:39: note:   no known conversion for argument 1 from 'ControllerData*' to 'const uint8_t*' {aka 'const unsigned char*'}
   27 |   virtual size_t write(const uint8_t *buffer, size_t size);
      |                        ~~~~~~~~~~~~~~~^~~~~~

exit status 1

Compilation error: no matching function for call to 'CANSAME5x::write(ControllerData*, unsigned int)'

If required, here is the content of CANSAME5x:

// Copyright 2020 © Jeff Epler for Adafruit Industries. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full
// license information.

#include "CANController.h"

class CANSAME5x : public CANControllerClass {
public:
  CANSAME5x();
  CANSAME5x(uint8_t tx_pin, uint8_t rx_pin);
  ~CANSAME5x() final;

  int begin(long baudRate) final;
  void end() final;

  int endPacket() final;

  int parsePacket() final;

  void onReceive(void (*callback)(int)) final;

  using CANControllerClass::filter;
  int filter(int id, int mask) final;
  using CANControllerClass::filterExtended;
  int filterExtended(long id, long mask) final;

  int observe() final;
  int loopback() final;
  int sleep() final;
  int wakeup() final;

  void dumpRegisters(Stream &out);

private:
  void bus_autorecover();

  void handleInterrupt();

  int _parsePacket();

private:
  int8_t _tx, _rx;
  int8_t _idx;
  // intr_handle_t _intrHandle;
  void *_state;
  void *_hw;
  static CANSAME5x *instances[2];

  static void onInterrupt();

  friend void CAN0_Handler(void);
  friend void CAN1_Handler(void);
};

extern CANSAME5x CAN;

And also CAN.h:



// Copyright (c) Sandeep Mistry. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#ifndef CAN_H
#define CAN_H

#if defined(ADAFRUIT_FEATHER_M4_CAN)
#include "CANSAME5x.h"
#elif defined(ARDUINO_ARCH_ESP32)
#include "ESP32SJA1000.h"
#else
#include "MCP2515.h"
#endif

#endif

Thank you.

Also,
What is the function of this line:

ControllerData data;

I assume we are assigning (defining?) the struct ControllerData to data? But why?
I tried looking it up but don't know what terminology to use. I tried Alias, etc.

I see that from this point on the struct is referenced as "data", i.e., data.pumpOn, etc.

Thank you again

Looks like you may just need a cast to the right pointer type:

CAN.write(reinterpret_cast<uint8_t *> (&data), sizeof(data));

It instantiates an object (variable) named 'data' of type 'ControllerData'.

The same as

int dog;

instantiates a variable named 'dog' of type 'int'.

Ahh, OK.
So you can therefore have multiple objects using the type defined by the structure?
Like:

ControllerData inbounddata;
ControllerData outbounddata;

That works! Thanks so much.
I'll go read up on casting.