Jump to content

Can-bus, some rambling, some code!


skruf
 Share

Recommended Posts

Can-bus is a really simple communication protocol originally made for cars, but these days they are used for anything, even in subsea christmas trees. For starting with CAN the wiki page is surprisingly good and is a nice starting point along with this url:https://opensource.lely.com/canopen/docs/cmd-tutorial/

Anyways, so I will share two simple codebases, be warned! The code is shitty and both together was coded in less than a week which is why its a uncommented mess (literally made with a knife on my throat as a project saving kung-fu in an EU project).

The code is without any lisence, but sadly I cannot show you the actual usage of the code as its properitary but its farily simple so I will just inline it here:

    canbus_communicator = new CanThread("vcan0");
    paxterGen3Tpdo = new PaxterGen3Tpdo();
    canbus_communicator->addNode((CanMethodResolver *) paxterGen3Tpdo);
    canbus_communicator->start();

The C version is very hacky, the first constraint was to write the software in C which is nice as I like C, but I havent programmed in it in a couple of years but it uses the deadly sin of "OOP function pointers", which can be hacky when distributing multiple signals in parallel. 

So we start with defining a simple canbus reader (implementation in the .c file):

enum {
    INVALID_LENGTH_ARGUMENT = -1
};
struct canbus_reader {
    int canbus_socket;
    char *ifname;

    int(*read_frame)(struct canbus_reader *, int *, char [8], unsigned *);
    int(*write_frame)(struct canbus_reader, int, const char *, unsigned);
};

typedef struct canbus_reader canbus_reader_t;

canbus_reader_t *canbus_reader_create(char *ifname, bool block);
void canbus_reader_destroy(canbus_reader_t *reader);

So far, pretty clean, the pointers here are just for doing reading and writing contained within the namespace.

To ease the parallelization processes we thus wrap this into a canbus thread with the following api:

struct canbus_thread;
typedef struct canbus_thread canbus_thread_t;

typedef int (*frame_handler_func)(int, char*, unsigned);

enum {
    MAXIMUM_AMOUNTS_OF_METHODS_PER_THREAD = 1 << 4
};

//__BEGIN_API
/**
 * Creates a handle for a canbus thread
 *
 * @param ifname The network interface name to listen to, preferably a can interface
 * @return A new canbus thread wrapper
 */
canbus_thread_t *canbus_thread_create(char *ifname);
/**
 *
 * The canbus thread can handle a frame in multiple ways depending on how the different listeners requires the data
 * @param canbus_reader The reader itself
 * @param func_pointer A function pointer which parses the processed can data on the format (id, data, len)
 * @return 0 if successful else -1
 */
int add_method_to_canbus_thread_handler(canbus_thread_t *canbus_reader, frame_handler_func func);
int start_thread(canbus_thread_t *thread); // THIS SHOULD PROBABLY BE REFACTORED TO THREAD STRUCT FOR OOPness :D, note: this is retarded
void canbus_thread_destroy(canbus_thread_t *canbusThread);

//__END_API_

Still seems... kinda clean, but also shit. Whatever, it was hastely pulled together. So we inspect this retarded programmers C file to see the struct, because surely, they know how to program C in an embedded environment...right?

The thread wrapper has the following struct: 

struct canbus_thread {
    canbus_reader_t *reader;
    bool isRunning;
    pthread_t _thread;
    int num_methods;
    frame_handler_func frame_handler_functions[MAXIMUM_AMOUNTS_OF_METHODS_PER_THREAD];
};

WTF, no one would be stupid enough to have an array of function handles in order to reduce the code to work like in a modern OOP env in C? Well, sorry to say that I am that retard. So doing simple things such as creating a running a thread turns into this abomination:

 

void *run_can_thread(void *arg) {
    int id;
    unsigned len;
    char data[8];
    canbus_thread_t *canbus_thread = (canbus_thread_t *) arg;

    DLOG(INFO, "[%s] Thread func start \n", (canbus_thread->reader->ifname));
    while (canbus_thread->isRunning) {
        if (canbus_thread->reader->read_frame(canbus_thread->reader, &id, data, &len) > 0) {
            for (int i = 0; i < MAXIMUM_AMOUNTS_OF_METHODS_PER_THREAD; i++) {
                if ((*canbus_thread->frame_handler_functions[i]) != NULL) {
                    fprintf(stdout, "I am thread %s calling the func now!\n", canbus_thread->reader->ifname);
                    (*canbus_thread->frame_handler_functions[i])(id, data, len);
                }
            }
        }
    }
    DLOG(INFO, "[%s] Thread func stop \n", (canbus_thread->reader->ifname));
    return NULL;
}

With the implementation of sensors as you see in the C repository we got the message that we could use C++. This wa actually one of my first time using C++, but given it was an embedded env it was basically just really nice C.

Which means we solve the above things with simple classes like: 

class CanMethodResolver {
public:
    virtual int handle_frame(int id, char *data, unsigned len) = 0;
};

Which allows you to define in interface with an external component (like NodeJ1939 in a car) as following:

NodeJ1939::NodeJ1939() {
    msgCount1 = 0;
    msgCount2 = 0;

    msg3State = false;
}

int NodeJ1939::handle_frame(int id, char *data, unsigned len) {
    if ((id & CAN_EFF_MASK) == ID.MESSAGE1) {
        return appendMessage1(data, len);
    } else if ((id & CAN_EFF_MASK) == ID.MESSAGE2) {
        return appendMessage2(data, len);
    } else if ((id & CAN_EFF_MASK) == ID.MESSAGE3) {
        if (!msg3State) {
            msg3State = true;
            return appendMessage30(data, len);
        } else {
            msg3State = false;
            return appendMessage31(data, len);
        }
    }
    return 0;
}

int NodeJ1939::appendMessage1(char *data, unsigned len) {
    maxVolt = ((float) ((data[0] << 8) | data[1])) / 10;
    maxCurr = ((float) ((data[2] << 8) | data[3])) / 10;

    charging = !data[4];

    msgCount1++;

    return 0;
}

int NodeJ1939::appendMessage2(char *data, unsigned len) {
    volt = ((float) ((data[0] << 8) | data[1])) / 10;
    curr = ((float) ((data[2] << 8) | data[3])) / 10;

    hwFail = (data[4] & 0x1);
    tempFail = (data[4] & 0x2);
    voltFail = (data[4] & 0x4);
    comFail = (data[4] & 0x10);

    msgCount2++;

    return 0;
}

int NodeJ1939::appendMessage30(char *data, unsigned len) {
    nomAhr = ((float) ((data[0] << 8) | data[1])) / 10;
    storedAhr = ((float) ((data[2] << 8) | data[3])) / 10;
    actualCurr = ((float) (((data[4] & 0x7f) << 8) | data[5])) / 10;
    actualPackVolt = ((float) ((data[6] << 8) | data[7])) / 10;
    soc = 100 * (storedAhr) / (nomAhr);
    return 0;
}

int NodeJ1939::appendMessage31(char *data, unsigned len) {
    maxCellVolt = ((float) ((data[0] << 8) | data[1])) / 1000;
    minCellVolt = ((float) ((data[2] << 8) | data[3])) / 1000;
    maxCellTemp = ((float) (((data[4] << 8) | data[5]) - 200)) / 10;
    minCellTemp = ((float) (((data[4] << 8) | data[5]) - 200)) / 10;
    return 0;
}

int NodeJ1939::appendMessage1X(char *data, unsigned len) {
    return 0;
}

By simple inheritence.


class NodeJ1939 : CanMethodResolver {
public:
    NodeJ1939();
    int handle_frame(int id, char * data, unsigned len);

    struct ID{
        static const int MESSAGE1 = 0x1806E5F4;
        static const int MESSAGE2 = 0x18FF50E5;
        static const int MESSAGE3 = 0x18075000;
        static const int MESSAGE1X = 0x1806E6F4;
    } ID;
  
  .................omitted.

I will upload both the C and C++ repositories once I find a decent way of sharing with the members of HAXME without exposing it completlly 

  • I Like This! 4
Link to comment
Share on other sites

  • cwade12c featured this topic

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...