makerplane / CAN-FIX-ArduinoLib

An Arduino library that implements the CAN-FIX avionics protocol.
GNU General Public License v2.0
3 stars 6 forks source link

Change the libary to use a different CAN driver #2

Open birkelbach opened 1 year ago

birkelbach commented 1 year ago

Right now we are using our own MCP2515 CAN driver. There are other Arduino CAN libraries out there that would also give us the ability to use internal CAN hardware for Arduinos that have those (i.e. ESP32 or STM32) or other external controller chips if someone has done that. Also this would keep us from having to maintain that code base.

birkelbach commented 1 year ago

I took a quick glance at the state of the CAN libraries in the Arduino and it seems pretty haphazard. There doesn't seem to be any common interface to these libraries. Maybe I'm missing it.

If we are going to do this it'd be nice if it would work with other CAN hardware than the MCP2515 only. If this worked with the internal ESP32 CAN controller then Vern's Huvver devices would immediately be CANFiX compatible. If it worked with the internal STM32 CAN controller then this pile of Nucleo boards that I have would be compatible. I'm sure there are other boards as well.

Now the question is... what is the best library to use (there are dozens of them) and/or do we commit to that library or write a wrapper in our code? It's been a while since I messed with Arduino libraries. My instinct is that we create some wrapper functions that present a standard CAN interface and then it would use different backend libraries based on #defines that the user would put in their code. This has the added advantage that the existing CANFiX code doesn't have to change much.

This doesn't seem very Arduino'ish to me. Suggestions are welcome!

danderflieger commented 1 year ago

Thanks Phil.

I like the idea of abstracting the underlying library away to give the developer a simple way to interface one of several libraries. Like you said, maintaining all of that code could be tough and there's no reason to re-write it all if someone already has (provided we test our "approved" libraries thoroughly).

I'm not a hardware guy, so Arduino is my only experience with microcontrollers of any kind. I'm happy to learn more, but I don't really even know where to start. Also finding the MakerPlane projects two weeks ago started my foray into the mysterious world of CAN. So from a hardware standpoint, I'm not much help, but I'm happy to work on documentation or write object classes for CANFix parameters (if that's helpful), or whatever I can do.

Dan

birkelbach commented 1 year ago

What do you think about these two for starters...

This one is being maintained by Adafruit and supports the ESP32 as well as SAME5x series uCs and the MCP2515. I don't really care for the interface but my guess is that it'll work well. https://www.arduino.cc/reference/en/libraries/can-adafruit-fork/

This is the one that was mentioned on the MakerPlane forum. I like it as well. If you only want the MCP2515 then you might be able to save some space with this. https://www.arduino.cc/reference/en/libraries/mcp_can/

Might keep ours too since it's pretty small because it only uses the features that we need for CANFiX. No extended messages or RTRs etc.

danderflieger commented 1 year ago

I can confirm that the CAN Adafruit Fork library works with my 8Mhz MCP2515. Using the CANSender.ino example this is a working example of what I to set my setup() block to look like:

void setup() {
  Serial.begin(9600);
  while (!Serial);
  CAN.setPins(10, 2);
  CAN.setClockFrequency(8E6);
  CAN.setSPIFrequency(16E6);

  Serial.println("CAN Sender");

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

I was also able to set my SPI Frequency up to 20Mhz, since I'm using an Arduino Nano with an ATmega328P chip on it. It worked both ways, I was just testing out various settings.

I was also able to crank my CAN frequency all the way up to 1Mhz.

And, as you know, I already tested the mcp_can library with good results too.

Seems like the Adafruit library supports more hardware (proven now on the MCP2515), so I suggest just using that one. If someone needs a slimmer library, maybe look at adding mcp_can as an option later??

Dan

birkelbach commented 1 year ago

Okay that’s good info. I’ll try to carve out some time this weekend to write a quick wrapper, and test it on a few boards. I don’t think it’ll be terribly difficult to do.

birkelbach commented 1 year ago

I thought about this more last night. I have a similar library that I wrote in C that I use for non-Arduino projects. I had a baked in CAN driver in that one for a while and I finally removed the CAN portion altogether and used user defined callback functions for the CAN stuff. So basically the calling program would be responsible for reading and writing the CAN frames. It would simply pass the received frames to the exec() function in the CANFiX object. If the exec() function needs to send a frame it would use a callback function that the calling program would have to provide for sending the CAN frames.

I couldn't think of a way of using multiple libraries without some kind of conditional compilation mechanism. That seems like bad form for a library to me, maybe I'm missing something. We'd also still have to maintain the wrapper code. This way we get out of the CAN sending/receiving business and can concentrate on protocol issues. It would also make unit testing brainlessly easy.

We would include examples for the different CAN libraries instead of maintaining wrapper code that would have to be conditionally compiled. It's the same thing we're just moving a little bit more responsibility (and freedom) to the end user.

danderflieger commented 1 year ago

Yeah, that sounds like a good path forward to me. I agree: If we're relying on the user to do more, we definitely need to have several simple example snippets for "supported" CAN libraries. I would certainly appreciate that as a new user. Thanks again for taking the time on this!

danderflieger commented 1 year ago

I've been fiddling with the CAN Adafruit Fork library and have had good success in reproducing your joystick example with it. The data is just manually hand-jammed into a message (e.g. no callbacks) for now. If you haven't already started on it, maybe I'll take a stab at updating the callbacks this week. I'm getting more and more comfortable with the spec the more I look at it.

birkelbach commented 1 year ago

I refactored the library files and wrote an example. The example uses the mcp_can library. I did a little bit of testing so the exec() call works and the can_write_callback() function works but I didn't test much after that. I have not pulled the code into this repo yet. It's currently available in my fork that you can find here...

https://github.com/birkelbach/CAN-FIX-ArduinoLib

While I was fiddling with it I realized that I was using the EEPROM library internally as well. I'd like to replace this with callback functions as well so the user has control of how this information actually gets stored instead of assuming EEPROM. Not all uCs have EEPROM.

I think I'm happy with this idea. The user required interface code is pretty minimal and should be pretty easy for users to figure out, even if we don't offer examples for all of them. As always, comments are welcome. I may fiddle with it some more later.

birkelbach commented 1 year ago

Dan, I sent you a PM on the MakerPlane forum. Send me an email and we can coordinate on working on this library. I'd love the help.

brightproject commented 1 month ago

Good day @birkelbach 🙂 It would be great to add an example to transmit some aviation parameters. For example, altitude or engine RPM. I can't figure out the CAN-FIX-ArduinoLib yet.

birkelbach commented 1 month ago

There is an example in the library that reads a joystick and sends pitch and roll.

brightproject commented 1 month ago

There is an example in the library that reads a joystick and sends pitch and roll.

The can-fix implementation for python has a list of all possible PIDs for transmitting data on engine parameters or altitude and flight speed. I did not find anything like this in the implementation for arduino. Could you help me understand the can-fix protocol?

brightproject commented 3 weeks ago

Thank you @birkelbach 🙂 Found the specification of the CAN-FIX protocol

https://github.com/makerplane/FIX-Gateway/blob/master/fixgw/config/canfix/map.yaml

Probably these canid's correspond to the general ID base. At least they correspond to this: https://github.com/makerplane/CAN-FIX-ArduinoLib/blob/afcc983bd6f63a3763c70d85ced7c325ca0fdde5/examples/joystick.ino#L43 and https://github.com/makerplane/CAN-FIX-ArduinoLib/blob/afcc983bd6f63a3763c70d85ced7c325ca0fdde5/examples/joystick.ino#L60 I have a pressure sensor, the data from which I receive using the MCU esp32 and output data to the serial port. I want to output data from the esp32 using the example code:

https://github.com/makerplane/CAN-FIX-ArduinoLib/blob/master/examples/joystick.ino

If I transfer the altitude data, which is calculated based on the pressure from the sensor, from the serial port of the microcontroller to the serial port of pyEfis

Main

will this altitude be displayed in the program or do I need to take other steps?

birkelbach commented 3 weeks ago

CAN-FIX is not serial port based. It is CAN Bus. The CAN-FIX specification is at https://github.com/makerplane/canfix-spec. The CAN Bus Specification is referenced in that document. You will need an adapter of some kind to convert CAN Bus into something your PC/Raspberry Pi can read.

You can download a pre-built PDF of the specification from my Dropbox. https://www.dropbox.com/scl/fi/7w9okdmhin1vlvqqfij5p/CANFiX-Draft.pdf?rlkey=18md51ulzohrmfk2dm8pkrogu&st=614cpt0m&dl=0

Also, to answer your question. PyEFIS only talks to FiXGateway. FiXGateway is a data aggregator/translator. It has plug-ins that talk to the different types of external devices (serial, CAN, BT, Ethernet, etc), simulators or other software, the keyboard etc. The idea here is that someone may want to develop their own EFIS software. With FiXGateway they don't have to worry about all these other devices / software, and they can just use the interface to FiXGateway. If you can talk to FiXGateway, then you can talk to all the devices that FiXGateway can talk to.

If you want to do serial port based communication to FiXGateway then you'd have to figure out how to talk to one of the plugins in FiXGateway or develop your own plugin that talks to your device. This library will not help you do that.

brightproject commented 3 weeks ago

CAN-FIX is not serial port based. It is CAN Bus.

Yes, I understand that, but in fact, it is not the CAN bus that comes in the USB port of a single-board PC, but the RX/TX from the USB or UART.

PyEFIS only talks to FiXGateway. FiXGateway is a data aggregator/translator.

I understand this concept too, so I came up with this diagram of the device:

can-fix-arduinolib

The scheme is as follows:

  1. The sensor calculates orientation angles and transmits them via the I2C bus to the microcontroller, this can be STM32, ESP32, Atmega32 or other.
  2. The MCU processes the library code

https://github.com/birkelbach/CAN-FIX-ArduinoLib

and outputs data (roll, pitch, yaw) to the CAN bus using a transceiver based on the CAN Bus MCP2515 module

  1. Orientation angle data via the CAN bus goes to the CAN adapter/converter toUSB/UART

  2. Orientation angle data goes to the USB port of the single-board PC and is transmitted to the FIX-Gateway, where it is processed by the plugin

https://github.com/makerplane/FIX-Gateway/tree/master/fixgw/plugins/canfix

I wrote a code example based on these examples:

https://github.com/birkelbach/CAN-FIX-ArduinoLib/blob/master/examples/joystick.ino https://github.com/birkelbach/CAN-FIX-ArduinoLib/blob/master/examples/mcp_can_test.ino/mcp_can_test.ino.ino

The physical CAN library for the MCP2515 module used this:

#include <mcp_can.h>
#include <mcp_can_dfs.h>

Directly example code, but I'm not sure if it works correctly.

// CANFiX  library
#include "canfix.h"

// CAN physical layer library
#include <mcp_can.h>
#include <mcp_can_dfs.h>

#define BAUD_RATE 115200 // bitrate or baudrate ~ 100 кГц
// #define BAUD_RATE 921600 // bitrate or baudrate ~ 1 mHz

#define TIMER_COUNT 5
#define DELAY 0

// CAN
const int CAN_CS_PIN = PA4;   //CS pin for CAN board

// Setup the mcp_can object
MCP_CAN CAN0(CAN_CS_PIN );  // Set CS

// CANFiX object
CanFix cf(0x77);  // This sets the device Id for our node

void sendPitchAngle(float angle) {
  // CanFixFrame frame; //Response frame
  CFParameter p; // set parameters
  int x;

  //if(cf->checkParameterEnable(0x180)) { /* Check that it's enabled */
    // frame.id = 0x180;
    // p.type = frame.id; // canID PITCH
    p.type = 0x180; // canID PITCH
    p.index = 0;
    p.fcb = 0x00;
    x = angle;
    Serial.print("Pitch angle: ");
    Serial.println(x);
    p.data[0] = x;
    p.data[1] = x>>8;
    p.length = 2; // in Bits

    // cf.sendParam(p);
    CAN0.sendMsgBuf(p.type, 0, p.length, p.data);
  //}
}

void report_callback(void) {
  Serial.println("Report");
}

void setup() {

Serial.begin(BAUD_RATE);

  // cf.setDeviceId(0x77);  // This sets the device Id for our node
  // cf.setModel(0x12345);  // This sets the model number for our node
  // cf.setFwVersion(2);    // This sets the firmware revision for our node
  // This sets the write_callback function that the CanFix object will use to send CAN frames
  // cf.set_write_callback(can_write_callback);
  // These functions set the callbacks that the CanFix object uses when it recieves these
  // specific frames
  // cf.set_report_callback(report_callback);

  // Initialize MCP2515 running at 8MHz with a baudrate of 250kb/s and the masks and filters disabled.
  //  while (CAN_OK != CAN0.begin(MCP_ANY, CAN_250KBPS, MCP_8MHZ))
    {
        Serial.println("CAN BUS Shield init fail");
        delay(100);
    }
    CAN0.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
    Serial.println("CAN BUS Shield init ok!");

} // SETUP

void loop() {

    static float pitch = 20.0;       // Initial value of the pitch angle
    static float pitchIncrement = 2.0; // Step of change for the pitch angle

  static unsigned long lasttime[TIMER_COUNT], now;

    // Update the values of pitch and roll angles
    pitch += pitchIncrement;

  // Change the direction of movement when reaching the limit for pitch
  if (pitch >= 40.0 || pitch <= -40.0) {
      pitchIncrement = -pitchIncrement; // Reverse pitch increment
      delay(DELAY); // Delay to create a pause at the limit
  }

/* Event Dispatch Function - This should be called every time
   through the loop.  It handles all of the communications on
   the bus and calls the callback functions when certain message
   are received. */
  // cf.exec();

  now = millis();
  /* Pitch and Roll */
  if(now - lasttime[0] > 100) {
    sendPitchAngle(pitch);
    lasttime[0] = now;
  }

} // LOOP

Using a logic analyzer, I connect to the CAN bus and receive data on it, and there is data, but mixed up, both a regular frame and an extended one, and constantly different IDs, although I seem to have specified canID PITCH

can ID = 180

@birkelbach, could you please suggest a correct code example so that I can implement the transmission of orientation angles in FIX-Gateway?🙂

danderflieger commented 3 weeks ago

I've had better luck using signed longs for my pitch values, but you can probably use signed INT values too.

I think your issue is that you're saying your ENTIRE message is only 2 bytes long thinking that you're only specifying the length of the data bytes.

Here's a code snippet from a working test message I wrote a while ago for pitch:

CFParameter pPitch;
pPitch.type = 0x180; // Pitch
pPitch.index = 0x00; // first of possibly many pitch sensors
pPitch.fcb = 0x00; // No issues with the data
pPitch.data[0] = pitch; 
pPitch.data[1] = pitch>>8; 
pPitch.data[2] = pitch>>16; 
pPitch.data[3] = pitch>>24; 
pPitch.length = 7;
if (sendPitch) cf.sendParam(pPitch); // I have a boolean called sendPitch that I can turn on or off at the top of my code to enable the full demo to report pitch or turn it off. If it's set, run cf.sendParam(pPitch);

Serial.print("pitch: "); Serial.println(pitch); // write the pitch to the console so you can see it in your Arduino IDE

Notice I'm setting the length of the CFParameter to 7 (not 2). You need to account for all of the 7 bytes in your CAN message (including the type, the index, the fcb, and the data). I use a signed long (4 bytes) for my pitch values. You should be able to use a signed INT (2 bytes) as well, but in that case, your parameter length value will be 5 instead of 7.

Here's my full demo code repository: https://github.com/danderflieger/mcd_can_simple_demo/

I'm using this board (which has an arduino and an MCP2515 on a single board): https://www.amazon.com/gp/product/B0BG7S918T/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&th=1

Here's a YouTube video I made where that board is in use running part of the demo code I mentioned above. In that case, I turned off the sendPitch boolean and captured the pitch data from an AHRS chip: https://www.youtube.com/watch?v=cIsv_dQTMyo

Hopefully that's helpful.

Dan

brightproject commented 3 weeks ago

Here's my full demo code repository: https://github.com/danderflieger/mcd_can_simple_demo/

Thanks @danderflieger for your help🙂 Please tell me

CanFix cf(0x82);

is this an arbitrary numbering of a specific device on the can-fix bus?

Also, please tell me, array

bool countup[15];

Created to create a simulation of increasing/decreasing test parameter values ​​and does not relate to the can-fix functionality?

And the last question - I have seen it in examples before and in your code I saw the purpose of the pin INT:

https://github.com/danderflieger/CAN-FIX-ArduinoLib/blob/f5f2e81d8ab89ea8806f197b14c45676795af6bb/examples/mcp_can_simple_demo/mcp_can_simple_demo.ino#L12C9-L12C17

Perhaps this is in case of using the functionality of this board?

DOC043568921

But nowhere in the code is an interrupt ever used, do you not see any advantages in bus performance when using interrupts? I understand that this is not very necessary in practice?

brightproject commented 3 weeks ago

Hopefully that's helpful.

I've changed your code a bit so that building for the platform won't cause compiler warnings - these are minor changes to the sizes and types of variables.

The same message is output to the CAN bus.

can_fix_frame

The message ends with NAK, and in my opinion that's not very good?

I uncommented this code and received the height value in the serial port, i.e. the parameters change, but for some reason they do not enter the CAN bus.

Serial.print("Airspeed: "); Serial.println(airspeed);

Changed the flag to

bool sendAirSpeed ​​= true;

Uploaded the firmware and got a completely different CAN ID

pTurnRate.type = 0x403;

After pressing RESET on the microcontroller, the CAN bus frame is 18D again

can_fix_frame_403

I feel like I'm missing some important information.

Added code here:

// can FIX library
#include <canfix.h>

// can physical layer library
#include <mcp_can.h>
#include <mcp_can_dfs.h>

#define MCP2515_BLUE
// #define MCP2515_BLACK

#define BAUD_RATE 115200 // bitrate or baudrate ~ 100 кГц
// #define BAUD_RATE 921600 // bitrate or baudrate ~ 1 мГц

#ifdef MCP2515_BLUE
MCP_CAN CAN0(PA4); // CS PIN for MCP2515 module (8Mhz)
#endif

#ifdef MCP2515_BLACK
MCP_CAN CAN0(9); // CS PIN for BLACK CAN SHIELD (16Mhz)
#endif

CanFix cf(0x82);

void can_write_callback(CanFixFrame frame);
int ConvertCelsiusToFahrenheit(int degreesCelsius);

unsigned long now;
unsigned long lasttime;
unsigned long messagedelay = 100;
int32_t airspeed = 1200;
// unsigned int airspeed = 1300;
int32_t verticalspeed;
// signed int verticalspeed;
int32_t turnrate;
// signed int turnrate;
int32_t lateralacceleration;
// signed int lateralacceleration;
int32_t cylinderheadtemperature[4] = {0,0,0,0};
// unsigned int cylinderheadtemperature[4] = {0,0,0,0};
int32_t exhaustgastemperature[4] = {0,0,0,0};
// unsigned int exhaustgastemperature[4] = {0,0,0,0};
int32_t rpm;
int32_t fuelquantity = 0;
int32_t fuelflow = 0;
int32_t fuelpressure = 4000;
int32_t oilpressure = 3000;
// unsigned int rpm;
// unsigned int fuelquantity = 0;
// unsigned int fuelflow = 0;
// unsigned int fuelpressure = 4000;
// unsigned int oilpressure = 3000;
int32_t voltage = 0;
// unsigned int voltage = 0;
int32_t amps = 0;
// unsigned int amps = 0;
signed long roll = 0;
signed long heading = 0;
signed long pitch = 0;
bool countup[15];
bool pitchCountUp;

volatile unsigned int counter = 0;
volatile float currentinHg = 30.01;

// Decide which parameters you want to send by setting these values to true or false
bool sendAirSpeed = false;
bool sendVerticalSpeed = true;
bool sendTurnRate = true;
bool sendLateralAcceleration = true;
bool sendCylinderHeadTemperature = true;
bool sendExhaustGasTemperature = true;
bool sendRPM = true;
bool sendMAP = true;
bool sendOilTemp = true;
bool sendOilPressure = true;
bool sendAltimeterSetting = true;
bool sendIndicatedAltitude = true;
bool sendFuelQuantity = true;
bool sendFuelFlow = true;
bool sendFuelPressure = true;
bool sendVoltage = true;
bool sendAmps = true;
bool sendHeading = false;
bool sendPitch = false;
bool sendRoll = false;

void setup() {
  Serial.begin(BAUD_RATE);
  cf.setDeviceId(0x82);
  cf.setModel(0x12345);
  cf.setFwVersion(2);

  cf.set_write_callback(can_write_callback);

#ifdef MCP2515_BLUE
  while(CAN0.begin(MCP_ANY, CAN_250KBPS, MCP_8MHZ) != CAN_OK) {
    Serial.println("Unable to begin CAN0");
    delay(1000);
  }
#endif
#ifdef MCP2515_BLACK
  while(CAN0.begin(MCP_ANY, CAN_250KBPS, MCP_16MHZ) != CAN_OK) {
    Serial.println("Unable to begin CAN0");
    delay(1000);
  }
#endif

  // if(CAN0.begin(MCP_ANY, CAN_250KBPS, MCP_16MHZ) == CAN_OK)
  //   Serial.println("MCP2515 Initialized Successfully!");
  // else
  //   Serial.println("Error Initializing MCP2515...");

  CAN0.setMode(MCP_NORMAL);
  // Serial.println("MCP2515 Initialized Successfully!");

  now = millis();
  lasttime = now;
  airspeed = 30;

}

void loop() {
  // Set the "now" variable to the number of seconds since power 
  // was applied to the Arduino device
  now = millis();

  // check the "now" variable against another one called "lasttime" which
  // is set the "last time" this code block was run. If it's been more 
  // than 150ms, run this block again. If not, don't do anything.
  if (now - lasttime > messagedelay) {

    // Serial.println("Doing something ...");

    // First, let's look at the countup[0] variable. The [0] shows that this
    // is an "array" of countup values (e.g. there is more than one that use 
    // the same name). Airspeed is countup[0], Vertical speed will be countup[1], etc.
    // Each of these items in the array is a simple boolean (either a yes or a no)
    // that denotes whether we are counting up (incrementing the airspeed, vertical
    // speed, etc.) or not (decrementing the airspeed, vertical speed, etc.)

    // 60 (translated as 6.0 knots in CAN-FiX) is our low speed. If the airspeed gets 
    // lower than that, set the countup[0] value to true and start counting up instead.

    if (airspeed <= 60) {
      countup[0] = true;
    } 

    // if the airspeed value is higher than 1600 (160.0 knots), set countup[0] to false
    // which will tell our sendor to start counting down instead

    else if (airspeed >= 1600) {
      countup[0] = false;
    }

    // Now that we know whether the airspeed should be incrementing or decrementing (depending
    // on the value of countup[0]), either add 5 (+= 5) or subtract 5 (-= 5) from the current
    // airspeed value

    if (countup[0]) airspeed += 5;
    else airspeed -= 5;

    // Now we will create a CAN-FiX Parameter object (CFParameter) named "pIndicatedAirspeed" 
    // a CFParameter object holds details about the message you want to send to the CAN bus and it
    // will contain several properties/values, each detailed below.

    CFParameter pIndicatedAirspeed;   

    // (.type) - 0x183 is a hexadecimal representation of the number 387. In the CAN-FiX standard, 0x183/387 
    // is the message type for Indicated Airspeed (IAS). Depending on the type of message you want to
    // send, this value will change. Indicated Altitude (ALT) is 0x184 or 388. There are hundreds of types. This 
    // example program will only demonstrate a few of them.

    pIndicatedAirspeed.type = 0x183; 

    // (.index) - Next is the index property, which denotes which sensor of xxx type are we talking about. In the case
    // of IAS, you will only have a single value for the speed of the aircraft, so this will alwasy be 0x00 (or just 0).
    // for IAS. But if we were sending a Cylinder Head Temperature (CHT) reading, for example, we likely have more
    // than one cylinder in our engine. So we might have an index of 0x01 or 0x02 which would tell the FiX Gateway 
    // the CHT that we're sending is with regard to the 2nd or 3rd cylinder, respectively.

    pIndicatedAirspeed.index = 0x00;

    // (.fcb) - the "Funcion Code Byte" is a pretty loaded property. Using what we discussed above, this is a single byte 
    // of data, but each bit (remember a byte is 8 bits). You'll probably remember (from a basic computer course that you've
    // taken at some point in your life) that computers communicate using binary. So, using 0s and 1s. But we 
    // can combine those "bits" into larger pieces of data. For example, we can represent the number 37 in three ways:
    // - 37 (decimal - humans have 10 fingers and increment the "10s" place after we reach 9
    // - 0x25 (hexadecimal - where we increment the "16s" once after we reach 0x0F - (0-9 then continue A-F))
    // - 0b00100101 - (binary - where we increment the next field each time we reach 1 - 000, 001, 010, 011, 100, 110, 111, etc)
    // all three of the above example mean the same thing to a computer; that is: 37.
    // The Functional Code Byte is used to determine things about this message, and each bit (0 or 1) means something
    // to the CAN-FiX standard:
    //
    // | Bit 7  | Bit 6   | Bit 5   | Bit 4   | Bit 3   | Bit 2     | Bit 1   | Bit 0   |
    // | Meta3  | Meta2   | Meta1   | Meta0   | Future  | Failure   | Quality | Annuc   |
    //
    // The first 4 bits are used to send updates ABOUT the data (e.g. meta data). Maybe you want to tell the FiX Gateway
    // that your stall speed should be set to 50 knots. You would set the IAS message's Meta value for Vs, which would be
    // 1010, so your fcb value would be 0b10100000 or 0xA0 (same value in binary and hex, respectively).
    // Then you would give the message a "data" value for the new stall speed (maybe 0x50 0x00) to denote the new stall speed
    // The other bits (Future, Failure, Quality, Annuc) are used to give information about the data you're sending. For example,
    // if your sensor has a reason to believe that the value it's about to send might be in question, perhaps you would 
    // set the Quality bit so FiX Gateway knows to warn the pilot about questionable data. You would do this with an fcb value of
    // 0b00000010 (the 1 in there meaning the Quality flag is set). See the Frame Definitions section of the CAN-FiX spec for more info

    // If you're just sending a normal reading and there's nothing wrong with the message, .fcb should be 0, 0x00, or 0b00000000 (all 
    // the same value).

    pIndicatedAirspeed.fcb = 0x00;

    // Depending on the data being sent, the CAN-FiX standard may require more or fewer Bytes for a specific message type. In the case of 
    // IAS, the range you can send is 0.0 to 999.0. However, the units are 0.1 knots. So an airspeed of 123.4 knots would be communicated
    // as 1234 and the FiX Gateway will divide by 10.
    // If you've been following along with bits and bytes, you'll realize that a single Byte can only contain 256 values (e.g. 0-255). 
    // However, we can combine Bytes of data into other, larger data types. In the case of IAS, we need to be able to contain 0-9999 
    // (ten thousand values). Combining two Bytes of data will allow us plenty of overhead for our range. 
    // For IAS, this value is always positive, so we will use an "unsigned" integer. Unsigned means there are no bits wasted to 
    // specify a negative value (see the Vertical Speed info below to learn about "signed" integers).
    // For now, we will use two combined Bytes for an IAS value. Our range will be from 0000000000000000 to 10011100010000 (0-9999). But
    // an unsigned int has an upper limit of 1111111111111111 or 65,535! To make this easier to read and, more specifically, to adhere
    // to the CAN standard we need to split the Integer into two Bytes: 11111111 and 11111111. 
    // Look at the two data[] fields below (data[0] and data[1]). You will see that we use a variable we set previously (airspeed)
    // for both Bytes. data[0] uses the first 8 bits, but data[1] uses bit shifting for the seconds set of bits: airspeed>>8. 
    // This tells the Arduino to use the last 8 bits of the airspeed integer as a second Byte value. 
    // Bit shifting is beyond the scope of this demo. Watch a YouTube video about it if you want to learn more. 
    // Serial.println(airspeed);
    pIndicatedAirspeed.data[0] = airspeed;
    pIndicatedAirspeed.data[1] = airspeed>>8;
    pIndicatedAirspeed.data[2] = airspeed>>16;
    pIndicatedAirspeed.data[3] = airspeed>>24;

    // Now we indicate how many total Bytes of data we plan to send to the FiX Gateway. 1 for message type (IAS), 1 for index, 1 for FCB,
    // and 2 for the actual data. That's a total of 5 Bytes

    pIndicatedAirspeed.length = 7;

    // Now that our CFParameter named "pIndicatedAirspeed" is complete, we'll send it out to the CAN bus where the FiX Gateway will
    // injest it and the pyEfis screen will display it.

    if (sendAirSpeed) { cf.sendParam(pIndicatedAirspeed); }
    delay(100);
    // Serial.println(airspeed);

    // Now we're just using the exact same data, but changing the type from IAS to True Airspeed (TAS) and resending it.
    // Note, IAS and TAS are likely not the same, depending on your altitude and air density. This is only a demonstration of
    // how to update a single property in CFParameter object and resend it.

    // pIndicatedAirspeed.type = 0x18D;
    // cf.sendParam(pIndicatedAirspeed);

    // setting max and min values for the demo just like before. In real life you would likely read this value 
    // from an Air Data encoder or do some math using previous and current altitude values divided by the time between
    // readings (adjusted to feet/minutes).

    if (verticalspeed <= -1000) {
      countup[1] = true;
    } else if (verticalspeed >= 1000) {
      countup[1] = false;
    }

    // Again, increment or decrement the value by 10 depending on whether the demo is counting up or down

    if (countup[1]) verticalspeed += 10;
    else verticalspeed -= 10;

    // Create a new CFParameter object for Vertical Speed.

    CFParameter pVerticalSpeed;

    // Set the message type. 0x186 or 390 denotes Vertical Speed (VERTSP)

    pVerticalSpeed.type = 0x186;

    // Like IAS you will only have 1 value for VS. 0x00 means this is the first value

    pVerticalSpeed.index = 0x00;

    // Sending a normal data update

    pVerticalSpeed.fcb = 0x00;

    // This is where it gets a little tricky. Because Vertical Speed can be positive or negative, we have to specify that. 
    // Unfortunately, to denote a negative, we have to set one of our bits to 1 or 0 to denote a positive or negative number.
    // However, using that bit effectively halves the number of values we can hold in a single integer. So Arduino requires us
    // to use a "signed" Integer for negative values. To overcome the lost values, a Signed Integer is four Bytes long (rather than
    // the two we used for IAS). Here we are cutting a Signed Integer ("verticalspeed") into four Bytes and using bit shifting
    // to use portions of that Signed Integer for each of the Bytes.

    pVerticalSpeed.data[0] = verticalspeed;
    pVerticalSpeed.data[1] = verticalspeed>>8;
    pVerticalSpeed.data[2] = verticalspeed>>16;
    pVerticalSpeed.data[3] = verticalspeed>>24;

    // Because this message contains two additional Bytes compared to IAS, we need to tell specify two more than the
    // 5 we used for IAS. Therefore, our entire message "length" will be 7 Bytes this time.

    pVerticalSpeed.length = 7;

    // Send the message to the CAN bus

    if(sendVerticalSpeed) cf.sendParam(pVerticalSpeed);

    if (turnrate <= -10) {
      countup[2] = true;
    } else if (turnrate >= 10) {
      countup[2] = false;
    }

    // Again, increment or decrement the value by 10 depending on whether the demo is counting up or down

    if (countup[2]) turnrate += 1;
    else turnrate -= 1;   

    CFParameter pTurnRate;
    pTurnRate.type = 0x403;
    pTurnRate.index = 0x00;
    pTurnRate.fcb = 0x00;
    pTurnRate.data[0] = turnrate;
    pTurnRate.data[1] = turnrate>>8;
    pTurnRate.data[2] = turnrate>>16;
    pTurnRate.data[3] = turnrate>>24;
    pTurnRate.length = 7;
    if(sendTurnRate) cf.sendParam(pTurnRate);

    if (lateralacceleration < -250.0) countup[3] = true;
    if (lateralacceleration > 250.0) countup[3] = false;
    if (countup[3]) lateralacceleration += 10;
    else lateralacceleration -= 10;

    CFParameter pLateralAcceleration;
    pLateralAcceleration.index = 0x00;
    pLateralAcceleration.fcb = 0x00;
    pLateralAcceleration.type = 0x18B;
    pLateralAcceleration.data[0] = lateralacceleration;
    pLateralAcceleration.data[1] = lateralacceleration>>8;
    pLateralAcceleration.data[2] = lateralacceleration>>16;
    pLateralAcceleration.data[3] = lateralacceleration>>24;
    pLateralAcceleration.length = 7;
    if (sendLateralAcceleration) cf.sendParam(pLateralAcceleration);

    if (cylinderheadtemperature[0] <= 1) countup[4] = true;
    if (cylinderheadtemperature[0] > 2800) countup[4] = false;

    if (countup[4]) {
      cylinderheadtemperature[0] += 100;
      cylinderheadtemperature[1] += 100;
      cylinderheadtemperature[2] += 100;
      cylinderheadtemperature[3] += 100;
    } else {
      cylinderheadtemperature[0] -= 100;
      cylinderheadtemperature[1] -= 100;
      cylinderheadtemperature[2] -= 100;
      cylinderheadtemperature[3] -= 100;
    } 

    ConvertCelsiusToFahrenheit(cylinderheadtemperature[0]);
    ConvertCelsiusToFahrenheit(cylinderheadtemperature[1]);
    ConvertCelsiusToFahrenheit(cylinderheadtemperature[2]);
    ConvertCelsiusToFahrenheit(cylinderheadtemperature[3]);

    CFParameter pCylinderHeadTemperature;
    pCylinderHeadTemperature.type = 0x500;
    pCylinderHeadTemperature.index = 0x00;
    pCylinderHeadTemperature.fcb = 0x00;
    pCylinderHeadTemperature.data[0] = cylinderheadtemperature[0];
    pCylinderHeadTemperature.data[1] = cylinderheadtemperature[0]>>8;
    pCylinderHeadTemperature.data[2] = cylinderheadtemperature[0]>>16;
    pCylinderHeadTemperature.data[3] = cylinderheadtemperature[0]>>24;
    pCylinderHeadTemperature.length = 7;
    if (sendCylinderHeadTemperature) cf.sendParam(pCylinderHeadTemperature);

    pCylinderHeadTemperature.index = 0x01;
    pCylinderHeadTemperature.data[0] = cylinderheadtemperature[1];
    pCylinderHeadTemperature.data[1] = cylinderheadtemperature[1]>>8;
    pCylinderHeadTemperature.data[2] = cylinderheadtemperature[1]>>16;
    pCylinderHeadTemperature.data[3] = cylinderheadtemperature[1]>>24;
    if (sendCylinderHeadTemperature) cf.sendParam(pCylinderHeadTemperature);

    pCylinderHeadTemperature.index = 0x02;
    pCylinderHeadTemperature.data[0] = cylinderheadtemperature[2];
    pCylinderHeadTemperature.data[1] = cylinderheadtemperature[2]>>8;
    pCylinderHeadTemperature.data[2] = cylinderheadtemperature[2]>>16;
    pCylinderHeadTemperature.data[3] = cylinderheadtemperature[2]>>24;
    if (sendCylinderHeadTemperature) cf.sendParam(pCylinderHeadTemperature);

    pCylinderHeadTemperature.index = 0x03;
    pCylinderHeadTemperature.data[0] = cylinderheadtemperature[3];
    pCylinderHeadTemperature.data[1] = cylinderheadtemperature[3]>>8;
    pCylinderHeadTemperature.data[2] = cylinderheadtemperature[3]>>16;
    pCylinderHeadTemperature.data[3] = cylinderheadtemperature[3]>>24;
    if (sendCylinderHeadTemperature) cf.sendParam(pCylinderHeadTemperature);

    if (exhaustgastemperature[0] <= 1) countup[5] = true;
    if (exhaustgastemperature[0] > 5000) countup[5] = false;

    if (countup[5]) {
      exhaustgastemperature[0] += 150;
      exhaustgastemperature[1] += 150;
      exhaustgastemperature[2] += 150;
      exhaustgastemperature[3] += 150;
    } else {
      exhaustgastemperature[0] -= 150;
      exhaustgastemperature[1] -= 150;
      exhaustgastemperature[2] -= 150;
      exhaustgastemperature[3] -= 150;
    } 

    ConvertCelsiusToFahrenheit(exhaustgastemperature[0]);
    ConvertCelsiusToFahrenheit(exhaustgastemperature[1]);
    ConvertCelsiusToFahrenheit(exhaustgastemperature[2]);
    ConvertCelsiusToFahrenheit(exhaustgastemperature[3]);

    CFParameter pExhaustGasTemperature;
    pExhaustGasTemperature.type = 0x502;
    pExhaustGasTemperature.index = 0x00;
    pExhaustGasTemperature.fcb = 0x00;
    pExhaustGasTemperature.data[0] = exhaustgastemperature[0];
    pExhaustGasTemperature.data[1] = exhaustgastemperature[0]>>8;
    pExhaustGasTemperature.data[2] = exhaustgastemperature[0]>>16;
    pExhaustGasTemperature.data[3] = exhaustgastemperature[0]>>24;
    pExhaustGasTemperature.length = 7;
    if (sendExhaustGasTemperature) cf.sendParam(pExhaustGasTemperature);

    pExhaustGasTemperature.index = 0x01;
    pExhaustGasTemperature.data[0] = exhaustgastemperature[1];
    pExhaustGasTemperature.data[1] = exhaustgastemperature[1]>>8;
    pExhaustGasTemperature.data[2] = exhaustgastemperature[1]>>16;
    pExhaustGasTemperature.data[3] = exhaustgastemperature[1]>>24;
    if (sendExhaustGasTemperature) cf.sendParam(pExhaustGasTemperature);

    pExhaustGasTemperature.index = 0x02;
    pExhaustGasTemperature.data[0] = exhaustgastemperature[2];
    pExhaustGasTemperature.data[1] = exhaustgastemperature[2]>>8;
    pExhaustGasTemperature.data[2] = exhaustgastemperature[2]>>16;
    pExhaustGasTemperature.data[3] = exhaustgastemperature[2]>>24;
    if (sendExhaustGasTemperature) cf.sendParam(pExhaustGasTemperature);

    pExhaustGasTemperature.index = 0x03;
    pExhaustGasTemperature.data[0] = exhaustgastemperature[3];
    pExhaustGasTemperature.data[1] = exhaustgastemperature[3]>>8;
    pExhaustGasTemperature.data[2] = exhaustgastemperature[3]>>16;
    pExhaustGasTemperature.data[3] = exhaustgastemperature[3]>>24;
    if (sendExhaustGasTemperature) cf.sendParam(pExhaustGasTemperature);

    if (rpm < 1) countup[6] = true;
    if (rpm > 3010) countup[6] = false;

    if (countup[6]) rpm += 32;
    else rpm -= 32;

    CFParameter pRPM;
    pRPM.type = 0x200;
    pRPM.index = 0x00;
    pRPM.fcb = 0x00;
    pRPM.data[0] = rpm;
    pRPM.data[1] = rpm>>8;
    pRPM.data[2] = rpm>>16;
    pRPM.data[3] = rpm>>24;
    pRPM.length = 7;
    if (sendRPM) cf.sendParam(pRPM);

    CFParameter pMAP;
    pMAP.type = 0x21E;
    pMAP.index = 0x00;
    pMAP.fcb = 0x00;
    pMAP.data[0] = (uint8_t)2685;
    pMAP.data[1] = (uint16_t)(2685>>8);
    pMAP.length = 5;
    if (sendMAP) cf.sendParam(pMAP);

    float oiltemperature = 100;
    unsigned int oiltemp = oiltemperature * 10;
    CFParameter pOilTemp;
    pOilTemp.type = 0x222;
    pOilTemp.index = 0x00;
    pOilTemp.fcb = 0x00;
    pOilTemp.data[0] = oiltemp;
    pOilTemp.data[1] = oiltemp>>8;
    pOilTemp.length = 5;
    if (sendOilTemp) cf.sendParam(pOilTemp);

    if (oilpressure < 3000) countup[7] = true;
    if (oilpressure > 7500) countup[7] = false;

    if (countup[7]) oilpressure += 15;
    else oilpressure -= 15;

    CFParameter pOilPressure;
    pOilPressure.type = 0x220;
    pOilPressure.index = 0x00;
    pOilPressure.fcb = 0x00;
    pOilPressure.data[0] = oilpressure;
    pOilPressure.data[1] = oilpressure>>8;
    pOilPressure.length = 5;
    if (sendOilPressure) cf.sendParam(pOilPressure);

    // float currentinHg = 30.01;
    // float currentMillibars = currentinHg * 33.864;

    signed int altimeterSetting = currentinHg * 1000;

    CFParameter pAltimeterSetting;
    pAltimeterSetting.type = 0x190;
    pAltimeterSetting.index = 0x00;
    pAltimeterSetting.fcb = 0x00;
    pAltimeterSetting.data[0] = altimeterSetting;
    pAltimeterSetting.data[1] = altimeterSetting>>8;
    pAltimeterSetting.length = 5;
    if (sendAltimeterSetting) cf.sendParam(pAltimeterSetting);

    float meters = 150;

    signed long indicatedAltitude = meters * 3.2804; //convert to feet

    // Serial.print("currentkPa: ");
    // Serial.print(currentkPa);
    // Serial.print("\tmeters: ");
    // Serial.print(meters);
    // Serial.print("\tindicatedaltitude: ");
    // Serial.println(indicatedaltitude);

    CFParameter pIndicatedAltitude;
    pIndicatedAltitude.type = 0x184;
    pIndicatedAltitude.index = 0x00;
    pIndicatedAltitude.fcb = 0x00;
    pIndicatedAltitude.data[0] = indicatedAltitude;
    pIndicatedAltitude.data[1] = indicatedAltitude>>8;
    pIndicatedAltitude.data[2] = indicatedAltitude>>16;
    pIndicatedAltitude.data[3] = indicatedAltitude>>24;
    pIndicatedAltitude.length = 7;
    if (sendIndicatedAltitude) cf.sendParam(pIndicatedAltitude);

    if (fuelquantity < 1) countup[8] = true;
    if (fuelquantity > 2000) countup[8] = false;

    if (countup[8]) fuelquantity += 15;
    else fuelquantity -= 15;

    CFParameter pFuelQuantity;
    pFuelQuantity.type = 0x226; // Left Fuel Tank
    pFuelQuantity.index = 0x00;
    pFuelQuantity.fcb = 0x00;
    pFuelQuantity.data[0] = fuelquantity;
    pFuelQuantity.data[1] = fuelquantity>>8;
    pFuelQuantity.data[2] = fuelquantity>>16;
    pFuelQuantity.data[3] = fuelquantity>>24;
    pFuelQuantity.length = 7;
    if (sendFuelQuantity) cf.sendParam(pFuelQuantity);

    pFuelQuantity.type = 0x227; // Right Fuel Tank
    if (sendFuelQuantity) cf.sendParam(pFuelQuantity);

    if (fuelflow < 400) countup[9] = true;
    if (fuelflow > 1000) countup[9] = false;

    if (countup[9]) fuelflow += 20;
    else fuelflow -= 20;

    CFParameter pFuelFlow;
    pFuelFlow.type = 0x21A; // Fuel Flow
    pFuelFlow.index = 0x00;
    pFuelFlow.fcb = 0x00;
    pFuelFlow.data[0] = fuelflow;
    pFuelFlow.data[1] = fuelflow>>8;
    pFuelFlow.data[2] = fuelflow>>16;
    pFuelFlow.data[3] = fuelflow>>24;
    pFuelFlow.length = 7;
    if (sendFuelFlow) cf.sendParam(pFuelFlow);

    if (fuelpressure < 4000) countup[10] = true;
    if (fuelpressure > 5000) countup[10] = false;

    if (countup[10]) fuelpressure += 20;
    else fuelpressure -= 20;

    CFParameter pFuelPressure;
    pFuelPressure.type = 0x21C; // Fuel Pressure
    pFuelPressure.index = 0x00;
    pFuelPressure.fcb = 0x00;
    pFuelPressure.data[0] = fuelpressure;
    pFuelPressure.data[1] = fuelpressure>>8;
    pFuelPressure.data[2] = fuelpressure>>16;
    pFuelPressure.data[3] = fuelpressure>>24;
    pFuelPressure.length = 7;
    if (sendFuelPressure) cf.sendParam(pFuelPressure);

    if (voltage < 10) countup[11] = true;
    if (voltage > 150) countup[11] = false;

    if (countup[11]) voltage += 2;
    else voltage -= 2;

    CFParameter pVoltage;
    pVoltage.type = 0x50E; // Voltage
    pVoltage.index = 0x00;
    pVoltage.fcb = 0x00;
    pVoltage.data[0] = voltage;
    pVoltage.data[1] = voltage>>8;
    pVoltage.data[2] = voltage>>16;
    pVoltage.data[3] = voltage>>24;
    pVoltage.length = 7;
    if (sendVoltage) cf.sendParam(pVoltage);

    if (amps < 150) countup[12] = true;
    if (amps > 300) countup[12] = false;

    if (countup[12]) amps += 2;
    else amps -= 2;

    CFParameter pAmps;
    pAmps.type = 0x512; // Amps
    pAmps.index = 0x00;
    pAmps.fcb = 0x00;
    pAmps.data[0] = amps;
    pAmps.data[1] = amps>>8;
    pAmps.data[2] = amps>>16;
    pAmps.data[3] = amps>>24;
    pAmps.length = 7;
    if (sendAmps) cf.sendParam(pAmps);

    //if (heading <= 0) heading += 360;

    signed int magneticHeading = heading - 1800;

    if (magneticHeading < -900) countup[14] = true;
    if (magneticHeading > 900) countup[14] = false;

    if (countup[14]) heading += 55;
    else heading -= 55;

    CFParameter pHeading;
    pHeading.type = 0x185; // Magnetic Heading
    pHeading.index = 0x00;
    pHeading.fcb = 0x00;
    pHeading.data[0] = heading;
    pHeading.data[1] = heading>>8;
    pHeading.data[2] = heading>>16;
    pHeading.data[3] = heading>>24;
    pHeading.length = 7;
    if (sendHeading) cf.sendParam(pHeading);
    delay(5);

    // Serial.print ("heading: ");
    // Serial.print (heading);
    // Serial.print ("\tmagneticHeading: ");
    // Serial.print (magneticHeading);

    if (pitch > 5000) { 
      //pitch = 0; 
      pitchCountUp = false;
    } 

    if (pitch <= -5000) {
      // pitch = 0;
      pitchCountUp = true;
    }

    if (pitchCountUp == true) {
      pitch = pitch + 50;
      // Serial.print("pitchCountUp: ");
      // Serial.println(pitchCountUp);
      // Serial.print("pitch: ");
      // Serial.println(pitch);
    } else {
      pitch = pitch - 50;
      // Serial.print("pitchCountUp: ");
      // Serial.println(pitchCountUp);
      // Serial.print("pitch: ");
      // Serial.println(pitch);
    }

    // Serial.println();

    CFParameter pPitch;
    pPitch.type = 0x180; // Pitch
    pPitch.index = 0x00;
    pPitch.fcb = 0x00;
    pPitch.data[0] = pitch;
    pPitch.data[1] = pitch>>8;
    pPitch.data[2] = pitch>>16;
    pPitch.data[3] = pitch>>24;
    pPitch.length = 7;
    if (sendPitch) cf.sendParam(pPitch);

    // Serial.print("pitch: ");
    // Serial.println(pitch);
    delay(5);

    if (roll < -3000) countup[13] = true;
    if (roll > 3000) countup[13] = false;

    if (countup[13] == true) { roll = roll + 200; }
    else { roll = roll - 200; }

    CFParameter pRoll;
    pRoll.type = 0x181; // Roll
    pRoll.index = 0x00;
    pRoll.fcb = 0x00;
    pRoll.data[0] = roll;
    pRoll.data[1] = roll>>8;
    pRoll.data[2] = roll>>16;
    pRoll.data[3] = roll>>24;
    pRoll.length = 7;
    if (sendRoll) cf.sendParam(pRoll);
    delay(5);

    // Serial.print("roll: ");
    // Serial.println(roll);
    // Serial.println();

    lasttime = now;
  }

}

// This function is a callback function that is used by the CanFix object
// for sending CAN frames on the network.  The CanFix class is agnostic
// toward the actual CAN communication mechanism.  This function should
// translate from the common CanFixFrame structure and send it on the Bus.
void can_write_callback(CanFixFrame frame) {
  CAN0.sendMsgBuf(frame.id, 0, frame.length, frame.data);
}

int ConvertCelsiusToFahrenheit(int degreesCelsius) {
  int fahrenheit = (degreesCelsius * 9/5) + 32;
  return fahrenheit;
}
brightproject commented 3 weeks ago

Good morning @danderflieger

Reduced the amount of code to send only one parameter - airspeed

Now in the CAN bus there is only a frame with ID = 0x183

can_fix_frame_continue

But the data does not change on the CAN bus, it only changes in the serial port.

Airspeed: 1480 Airspeed: 1485 Airspeed: 1490 Airspeed: 1495 Airspeed: 1500 Airspeed: 1505 Airspeed: 1510

// can FIX library
#include <canfix.h>

// can physical layer library
#include <mcp_can.h>
#include <mcp_can_dfs.h>

#define MCP2515_BLUE
// #define MCP2515_BLACK

#define BAUD_RATE 115200 // bitrate or baudrate ~ 100 кГц
// #define BAUD_RATE 921600 // bitrate or baudrate ~ 1 мГц

#ifdef MCP2515_BLUE
MCP_CAN CAN0(PA4); // CS PIN for MCP2515 module (8Mhz)
#endif

#ifdef MCP2515_BLACK
MCP_CAN CAN0(9); // CS PIN for BLACK CAN SHIELD (16Mhz)
#endif

CanFix cf(0x82);

void can_write_callback(CanFixFrame frame);

unsigned long now;
unsigned long lasttime;
unsigned long messagedelay = 100;
int32_t airspeed;
bool countup[1];

// Decide which parameters you want to send by setting these values to true or false
bool sendAirSpeed = true;

void setup() {
  Serial.begin(BAUD_RATE);
  cf.setDeviceId(0x82);
  cf.setModel(0x12345);
  cf.setFwVersion(2);

  cf.set_write_callback(can_write_callback);

#ifdef MCP2515_BLUE
  while(CAN0.begin(MCP_ANY, CAN_250KBPS, MCP_8MHZ) != CAN_OK) {
    Serial.println("Unable to begin CAN0");
    delay(1000);
  }
#endif
#ifdef MCP2515_BLACK
  while(CAN0.begin(MCP_ANY, CAN_250KBPS, MCP_16MHZ) != CAN_OK) {
    Serial.println("Unable to begin CAN0");
    delay(1000);
  }
#endif

  CAN0.setMode(MCP_NORMAL);

  now = millis();
  lasttime = now;
  airspeed = 30;

}

void loop() {

  now = millis();

  if (now - lasttime > messagedelay) {

    if (airspeed <= 60) {
      countup[0] = true;
    } 

    else if (airspeed >= 1600) {
      countup[0] = false;
    }

    if (countup[0]) airspeed += 5;
    else airspeed -= 5;

    CFParameter pIndicatedAirspeed;   

    pIndicatedAirspeed.type = 0x183;

    pIndicatedAirspeed.index = 0x00;

    pIndicatedAirspeed.fcb = 0x00;

    Serial.print("Airspeed: ");
    Serial.println(airspeed);
    pIndicatedAirspeed.data[0] = airspeed;
    pIndicatedAirspeed.data[1] = airspeed>>8;
    pIndicatedAirspeed.data[2] = airspeed>>16;
    pIndicatedAirspeed.data[3] = airspeed>>24;

    pIndicatedAirspeed.length = 7;

    if (sendAirSpeed) { cf.sendParam(pIndicatedAirspeed); }
    delay(100);

    lasttime = now;
  }

}

void can_write_callback(CanFixFrame frame) {
  CAN0.sendMsgBuf(frame.id, 0, frame.length, frame.data);
}
danderflieger commented 2 weeks ago

Ok, it looks like you're getting close. I'm not sure what microcontroller you are using. The data types might differ from what I was using. The standard Arduino Nano requires 4 bytes for a signed long type. Maybe int32_t is interpreted differently??

Or maybe your chip is using different significant bits on your microcontroller??? Perhaps you need to shift the data bits the other direction???

I really don't know - just mentioning things that I might look into next.

brightproject commented 2 weeks ago

I'm not sure what microcontroller you are using

I use stm32f411, but the code is assembled through the arduino ecosystem, and I don’t think there are any differences in this.

Tried specifying different variable types, the result is the same.

int32_t airspeed;
signed int airspeed;
int16_t airspeed;
danderflieger commented 2 weeks ago

Here's a working version.

Make sure you grab Phil's latest version of the CAN-FIX-ArduinoLib library: https://github.com/birkelbach/CAN-FIX-ArduinoLib/archive/refs/heads/master.zip

This code is making my pyEFIS's airspeed tape increase and decrease between 5kt and 150kt. The messages are sent every 100ms: image image

Since you can already see CAN messages, you probably have that part correct. But you may want to verify that your CAN module is using the correct clock speed. I've used the modules you included in your diagram and they can have either a 8Mhz or a 16Mhz crystal, so make sure you start the module with the correct one or you will get very incorrect data.

E.g. in the setup() function, change MCP_16MHZ to MCP_8MHZ if you have the 8Mhz crystal on your CAN module.

#include <canfix.h>
#include <mcp_can.h>
#include <mcp_can_dfs.h>

#include <SPI.h>

#define CAN0_INT 2
MCP_CAN CAN0(17);

CanFix cf(0x82);

unsigned long now;
unsigned long last_time;
unsigned long messagedelay = 100;

unsigned long airspeed = 50;
bool countup = true;

void setup() {
  Serial.begin(115200);
  cf.setDeviceId(0x82);
  cf.setModel(0x12345);
  cf.setFwVersion(2);

  cf.set_write_callback(can_write_callback);

  while(CAN0.begin(MCP_ANY, CAN_250KBPS, MCP_16MHZ) != CAN_OK) {
    Serial.println("Unable to begin CAN0");
    delay(1000);
  }

  CAN0.setMode(MCP_NORMAL);

  now = millis();
  last_time = now;
  airspeed = 30;

}

void loop() {

  now = millis();

  if (now - last_time > messagedelay) {

    last_time = now;

    if (countup == true) {
      airspeed += 5;
      if (airspeed > 1500) countup = false;
    } else {
      airspeed -= 5;
      if (airspeed < 50) countup = true;
    }

    CFParameter pIndicatedAirspeed;

    pIndicatedAirspeed.type = 0x183;
    pIndicatedAirspeed.index = 0x00;    
    pIndicatedAirspeed.fcb = 0x00;
    pIndicatedAirspeed.data[0] = airspeed;
    pIndicatedAirspeed.data[1] = airspeed>>8;
    pIndicatedAirspeed.data[2] = airspeed>>16;
    pIndicatedAirspeed.data[3] = airspeed>>24;
    pIndicatedAirspeed.length = 7;

    cf.sendParam(pIndicatedAirspeed);

    Serial.print("countup: "); Serial.println(countup);
    Serial.print("airspeed: "); Serial.println(airspeed);

  }

}

void can_write_callback(CanFixFrame frame) {
  CAN0.sendMsgBuf(frame.id, 0, frame.length, frame.data);
}
brightproject commented 2 weeks ago

Thanks @danderflieger again for helping me clarify some of the complexities with the code.

Make sure you grab Phil's latest version of the CAN-FIX-ArduinoLib library:

I downloaded the library files from your repo, they are the same, I checked both files.

https://github.com/danderflieger/CAN-FIX-ArduinoLib/tree/master/src

As you correctly asked above, I am compiling the code for the stm32 microcontroller and in the platformio + vscode development environment.

But in theory there shouldn't be any difference at all, but nevertheless, when I compile the firmware with the example code for the ATMEGA32U4 board, I get the following warnings:

pio_compile_warmimgs

src\airspeed.cpp: In function 'void loop()': src\airspeed.cpp:107:53: warning: right shift count >= width of type [-Wshift-count-overflow] pIndicatedAirspeed.data[2] = (uint8_t)airspeed>>16; ^~ src\airspeed.cpp:108:53: warning: right shift count >= width of type [-Wshift-count-overflow] pIndicatedAirspeed.data[3] = (uint8_t)airspeed>>24; ^~ lib\can_fix\canfix.cpp: In member function 'byte CanFix::writeFrame(CanFixFrame)': lib\can_fix\canfix.cpp:71:1: warning: no return statement in function returning non-void [-Wreturn-type] } ^ lib\can_fix\canfix.cpp: In member function 'void CanFix::parameterEnable(CanFixFrame)': lib\can_fix\canfix.cpp:77:10: warning: unused variable 'result' [-Wunused-variable] byte result; ^~ lib\can_fix\canfix.cpp: In member function 'void CanFix::sendParam(CFParameter)': lib\can_fix\canfix.cpp:311:10: warning: unused variable 'result' [-Wunused-variable] byte result; lib\can_fix\canfix.cpp: In constructor 'CanFix::CanFix(byte)': lib\can_fix\canfix.cpp:44:10: warning: unused variable 'bitrate' [-Wunused-variable] byte bitrate; ^~~

Having worked in the Arduino IDE environment before, I know that warnings are not displayed there when compiling code, so you don't see them.

I can only compile code for Arduino NANO, I don't have this microcontroller at hand. I also compile code for blackpill_f411ce + mcp2515 When compiling the code, I get exactly the same warnings as when compiling the code for atmega32U

The three warnings at the very bottom are simply that byte bitrate and byte result are not used in the code. What were these bytes needed for @birkelbach, tell me please 🙂? I simply commented out these lines.

Of other interest are warnings regarding bit shifts and the function

byte CanFix::writeFrame(CanFixFrame frame) {
if(__write_callback != NULL) {
__write_callback(frame);
}
// return 0;
}

If I don't add return 0; to the function, the code will compile with a warning and the code

Serial.print("Airspeed: ");
Serial.println(airspeed);

pIndicatedAirspeed.data[0] = (uint8_t)airspeed;
pIndicatedAirspeed.data[1] = (uint8_t)airspeed>>8;
pIndicatedAirspeed.data[2] = (uint8_t)airspeed>>16;
pIndicatedAirspeed.data[3] = (uint8_t)airspeed>>24;

 pIndicatedAirspeed.length = 7;

 if (sendAirSpeed) { cf.sendParam(pIndicatedAirspeed); } // delay(100);

 Serial.print("countup: "); Serial.println(countup[0]);
 Serial.print("airspeed: "); Serial.println(airspeed);

Outputs the value airspeedonce and then hangs

Entering Configuration Mode Successful! Setting Baudrate Successful! Airspeed: 35

And accordingly, the data on the CAN bus is also frozen and does not change.

As soon as I write it in the function like this:

byte CanFix::writeFrame(CanFixFrame frame) {
if(__write_callback != NULL) {
__write_callback(frame);
}
 return 0;
}

countup: 1 airspeed: 1550 countup: 1 airspeed: 1555 countup: 1 airspeed: 1560 countup: 1 airspeed: 1565 countup: 1 airspeed: 1570 countup: 1 airspeed: 1575

Output to serial port works immediately.

I repeat that I am describing this oddity just like that, I do not plan to use can-fix on stm32, perhaps in the distant future, I mainly use atmega32 nano or uno others and everything works there. But just for understanding, I would like to understand this oddity.

I have another question for @danderflieger. In the code

https://github.com/danderflieger/CAN-FIX-ArduinoLib/blob/master/examples/mcp_can_test.ino/mcp_can_test.ino.ino

Does this example accept data sent to the CAN bus via CAN-FIX? I thought maybe bit shifting here

    pIndicatedAirspeed.data[0] = airspeed;
    pIndicatedAirspeed.data[1] = airspeed>>8;
    pIndicatedAirspeed.data[2] = airspeed>>16;
    pIndicatedAirspeed.data[3] = airspeed>>24;

Organize like here, through a loop, with a link to the data length.

https://github.com/danderflieger/CAN-FIX-ArduinoLib/blob/f5f2e81d8ab89ea8806f197b14c45676795af6bb/examples/mcp_can_test.ino/mcp_can_test.ino.ino#L76

danderflieger commented 2 weeks ago

I downloaded the library files from your repo, they are the same, I checked both files.

OK, so you should be good there.

================================

src\airspeed.cpp:107:53: warning: right shift count >= width of type [-Wshift-count-overflow] pIndicatedAirspeed.data[2] = (uint8_t)airspeed>>16; ^~ src\airspeed.cpp:108:53: warning: right shift count >= width of type [-Wshift-count-overflow] pIndicatedAirspeed.data[3] = (uint8_t)airspeed>>24;

I have never used the STM boards, but this leads me to believe that there are architecture differences between data type definitions of an integer on the STM chip vs. the ATM chip that I'm using.

Based on your warnings above, it sounds like bit shifting the integer reading beyond 16 bits is overrunning the variable, so STM probably only defines an integer value with 16 bits (00000000 00000010) rather than the long value I'm using in my code, which the ATM chips use 32 bits to describe (00000000 00000000 00000000 00000010). I don't have any STM chips to play with, so I can't say for certain either way. Just a thought.

Perhaps try this instead, if you haven't already:

    CFParameter pIndicatedAirspeed;

    pIndicatedAirspeed.type = 0x183;
    pIndicatedAirspeed.index = 0x00;    
    pIndicatedAirspeed.fcb = 0x00;
    pIndicatedAirspeed.data[0] = airspeed;
    pIndicatedAirspeed.data[1] = airspeed>>8;

    pIndicatedAirspeed.length = 5;

    cf.sendParam(pIndicatedAirspeed);

    Serial.print("countup: "); Serial.println(countup);
    Serial.print("airspeed: "); Serial.println(airspeed);

================================

I have another question for @danderflieger. In the code

https://github.com/danderflieger/CAN-FIX-ArduinoLib/blob/master/examples/mcp_can_test.ino/mcp_can_test.ino.ino

Does this example accept data sent to the CAN bus via CAN-FIX? I thought maybe bit shifting here

Yes, it does receive CAN messages, but not only for CAN-FiX - it would work with ANY standard (11-bit) CAN message. All it does is receive the message and print it to the Serial monitor. You would need to interpret the message to do anything with it.

For example, you could use this code on a second CAN-enabled microcontroller with a TFT screen to receive all messages from the CAN bus. Then you would ignore all messages except, for example, the Airspeed messages. If an Airspeed message arrived, you might use the data in the message to display an airspeed indicator with a moving needle on your TFT screen. But you would need to write the code to either ignore or do something with each CAN message that arrived on the bus.

By the way, I've done a little testing with this board from Adafruit that has an onboard CAN transceiver and a Cortex M4 microcontroller: https://www.adafruit.com/product/4759

Adafruit doesn't have any in stock, but DigiKey has 249 of them today: https://www.digikey.com/en/products/detail/adafruit-industries-llc/4759/13571587

There are good code samples on the Adafruit website, which includes an example of writing CAN messages from one to another that receives the messages: https://learn.adafruit.com/adafruit-feather-m4-can-express

That board uses a fairly different CAN library, though, so you'd need to change pretty much all of the CAN processing code.

brightproject commented 2 weeks ago

Perhaps try this instead, if you haven't already:

Yes, I tried that too, but the result is the same - messages on the can bus from can-fix do not go to the stm32. In my other project, where I also use

include

include

for physical transmission of can messages via the mcp2551, the code is

unsigned int Airspeed = 0;
unsigned char canMsg[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
canMsg[0] = Airspeed;
canMsg[1] = Airspeed >> 8;

It seems to me that the problem of unstable operation of can-fix on the mcu stm32f411ccu6 is this

/* This is a wrapper for the write frame callback function. If
 it has not been set then we'll simply ignore it */
// void CanFix::writeFrame(CanFixFrame frame) {
byte CanFix::writeFrame(CanFixFrame frame) {
 if(__write_callback != NULL) {
 __write_callback(frame);
 }
 return 0;
}

If you do not specify return 0; then the code is blocked for execution.

brightproject commented 2 weeks ago

Thank you @danderflieger for your efforts, but since you don't have an STM32 microcontroller at hand, the following doesn't make much sense😄 I think my questions to CAN-FIX-ArduinoLib are answered, now I have questions to the library mcp_can.h and module mcp2551. I have come across at least two versions of the library: Old

https://github.com/coryjfowler/MCP_CAN_lib/blob/master/mcp_can.cpp

New

https://github.com/Longan-Labs/Arduino_CAN_BUS_MCP2515/blob/master/mcp_canbus.cpp

But both codes work +/- the same.

#include <Arduino.h>
#include <mcp_can.h>

// CAN
const int CAN_CS_PIN = PA4;

// int8_t Airspeed = 0;
// int32_t Airspeed = 0;
  unsigned int Airspeed = 0;
// unsigned long Airspeed = 0;
bool countup[1];

unsigned char canMsg[8] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};

const unsigned int CAN_ID = 183; // CAN Msg ID in DEC
const unsigned int Period = 100; // How often message sent in milliseconds
unsigned long Timestamp = 0; // when was the last message sent

MCP_CAN CAN(CAN_CS_PIN);  // Set CS pin

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

    // while (CAN_OK != CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ)) { // init can bus : baudrate = 500k
    while(CAN.begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) != CAN_OK) {
    // while(CAN_OK != CAN.begin(CAN_125KBPS, MCP_8MHZ)) {
      Serial.println("Unable to begin CAN BUS");
      delay(1000);
    }
    CAN.setMode(MCP_NORMAL);
    Serial.println("CAN BUS Shield init ok!");

} // SETUP

void loop()
{

if (millis() > Timestamp + Period) {

  if (Airspeed <= 100) {
    countup[0] = true;
  } 

  else if (Airspeed >= 1000) {
    countup[0] = false;
  }

  if (countup[0]) Airspeed += 5;
  else Airspeed -= 5;

  // canMsg[0] = Airspeed;
  // canMsg[1] = Airspeed >> 8;
  // canMsg[2] = Airspeed >> 16;
  // canMsg[3] = Airspeed >> 24;

  canMsg[0] = 1;
  canMsg[1] = 2;
  canMsg[2] = 3;
  canMsg[3] = 4;

 Serial.print(canMsg[0], HEX);
  Serial.print(".");
  Serial.print(canMsg[1], HEX);
  Serial.print(".");
  Serial.print(canMsg[2], HEX);
  Serial.print(".");
  Serial.println(canMsg[3], HEX);

  CAN.sendMsgBuf(CAN_ID, 0, 8, canMsg); 

  Timestamp = millis();
}

} // LOOP

The serial port data goes as expected.

4E.2.0.0 49.2.0.0 44.2.0.0 3F.2.0.0 3A.2.0.0 35.2.0.0 30.2.0.0 2B.2.0.0 26.2.0.0 21.2.0.0

There's complete nonsense with the can, the first frame sends 15 and that's it, then it freezes in all frames.

One can_frame_1 Two can_frame_2

I read somewhere that the MCP2515 modules have an 8 MHz quartz crystal and it doesn't work with some library normally, maybe I'm in exactly this situation. I'll try to solder a 16 MHz quartz crystal or use another module.