Enough already of all this theory … it’s time to put the CAN bus into practice. Since my goal is not just to get CAN working, but also to fully explain how it can be implemented on an STM32 µC, I will present the following material in two sections: a description of the API, as defined in the JeeH library and a detailed walkthrough of the entire driver.

This driver is written in C++ with templates, and is very compact (under 80 lines of code).

CAN driver API

The CAN driver is implemented as a C++ class, with four member functions. This is the entire programming interface (with a few details adjusted for clarity):

template< int N >
class CanDev {
public:
    void init       ();
    void filterInit (int num, int id =0, int mask =0);
    bool transmit   (int id, const void* ptr, int len);
    int  receive    (int* id, void* ptr);
};

To use the first of two CAN devices (on F407), we create an instance and initialise it:

CanDev<0> can;
can.init();

We also need to set up a filter to receive packets - the default is to accept everything:

can.filterInit(0);

That’s it, the hardware is now active. Here is a loop which prints out incoming packets:

while (true) {
    int32_t len, id, dat [2];
    len = can.receive(&id, dat);
    if (len >= 0)
        printf("R @%x #%d: %08x %08x\n", id, len, dat[0], dat[1]);
}

This example sends out a 4-byte packet with id (i.e. address) 0x123:

uint32_t data = 0x12345678;
can.transmit(0x123, &data, sizeof data);

There is no more to it than that. Is this a production-ready driver? No - there is no status reporting, no error handling, no flow control, and the FIFOs are not being used optimally. This is a polled API, there are no interrupts. But it’s more than a toy: this driver will be usable for simple scenarios, and can transfer large amounts of data (in small packets).

Annotated CAN driver

As promised, here is the CAN driver code in full. Like everything in JeeH, it’s not based on any other libraries, it interacts directly with the hardware registers. There are some JeeH idioms, such as the first few lines of code in init(), but most of this code comes straight from reading STM’s RM0090 reference manual, turning the register setup into C++ code.

I’m using several C++11 features, such as the constexpr type, which is fully evaluated at compile time. I’m also defining the members as static, because there will never be more than one instance of this class (one per controller, that is). This means that the compiler has an extraordinary amount of information available to produce absolutely minimal code. There is virtually no run-time overhead.

The following code is complete, and in the original order. I’m merely inserting some comments to make it (hopefully) easier to understand what is going on:

template< int N >
struct CanDev {

In C++, a struct is nothing but a class where everything is public. The above is just shorthand I’ve gotten used to. The template argument must be “0” or “1” to select the proper addresses. The following constants were copied from the reference manual:

    constexpr static uint32_t base = N == 0 ? 0x40006400 : 0x40006800;

    constexpr static uint32_t mcr  = base + 0x000;
    constexpr static uint32_t msr  = base + 0x004;
    constexpr static uint32_t tsr  = base + 0x008;
    constexpr static uint32_t rfr  = base + 0x00C;
    constexpr static uint32_t btr  = base + 0x01C;
    constexpr static uint32_t tir  = base + 0x180;
    constexpr static uint32_t tdtr = base + 0x184;
    constexpr static uint32_t tdlr = base + 0x188;
    constexpr static uint32_t tdhr = base + 0x18C;
    constexpr static uint32_t rir  = base + 0x1B0;
    constexpr static uint32_t rdtr = base + 0x1B4;
    constexpr static uint32_t rdlr = base + 0x1B8;
    constexpr static uint32_t rdhr = base + 0x1BC;
    constexpr static uint32_t fmr  = base + 0x200;
    constexpr static uint32_t fsr  = base + 0x20C;
    constexpr static uint32_t far  = base + 0x21C;
    constexpr static uint32_t fr1  = base + 0x240;
    constexpr static uint32_t fr2  = base + 0x244;

Initialisation requires a few steps: putting the two associated GPIO pins in the proper mode, enabling the hardware clock (in rcc+0x40), and configuring the device for 1 Mbs operation (assuming the system clock is set at 168 MHz):

    static void init () {
        if (N == 0) {
            // alt mode CAN1:    5432109876543210
            Port<'B'>::modeMap(0b0000001100000000, Pinmode::alt_out, 9);
            Periph::bit(Periph::rcc+0x40, 25) = 1;  // enable CAN1
        } else {
            // alt mode CAN2:    5432109876543210
            Port<'B'>::modeMap(0b0000000001100000, Pinmode::alt_out, 9);
            Periph::bit(Periph::rcc+0x40, 26) = 1;  // enable CAN2
        }

        Periph::bit(mcr, 1) = 0; // exit sleep
        MMIO32(mcr) |= (1<<6) | (1<<0); // set ABOM, init req
        while (Periph::bit(msr, 0) == 0) {}
        MMIO32(btr) = (7<<20) | (5<<16) | (2<<0); // 1 MBps
        Periph::bit(mcr, 0) = 0; // init leave
        while (Periph::bit(msr, 0)) {}
        Periph::bit(fmr, 0) = 0; // ~FINIT
    }

Each filter can be configured separately. With the mask bits set to zero, we’re effectively creating a wildcard match that accepts any address:

    static void filterInit (int num, int id =0, int mask =0) {
        Periph::bit(far, num) = 0; // ~FACT
        Periph::bit(fsr, num) = 1; // FSC 32b
        MMIO32(fr1 + 8 * num) = id;
        MMIO32(fr2 + 8 * num) = mask;
        Periph::bit(far, num) = 1; // FACT
    }

Transmissions use just one of the mailboxes, and if it’s busy, the transmit call is ignored. But the return value can be checked, and the call can be retried until it succeeds:

    static bool transmit (int id, const void* ptr, int len) {
        if (Periph::bit(tsr, 26)) { // TME0
            MMIO32(tir) = (id<<21);
            MMIO32(tdtr) = (len<<0);
            // this assumes that misaligned word access works
            MMIO32(tdlr) = ((const uint32_t*) ptr)[0];
            MMIO32(tdhr) = ((const uint32_t*) ptr)[1];

            Periph::bit(tir, 0) = 1; // TXRQ
            return true;
        }
        return false;
    }

Reception is a matter of checking whether there is a packet waiting in the receive FIFO. If not, we return an invalid negative length, else we return the packet size (0..8). On exit, the id (pointer) argument will be set to the address of the received packet:

    static int receive (int* id, void* ptr) {
        int len = -1;
        if (MMIO32(rfr) & (3<<0)) { // FMP
            *id = MMIO32(rir) >> 21;
            len = MMIO32(rdtr) & 0x0F;
            ((uint32_t*) ptr)[0] = MMIO32(rdlr);
            ((uint32_t*) ptr)[1] = MMIO32(rdhr);
            Periph::bit(rfr, 5) = 1; // RFOM
        }
        return len;
    }

All set. End of story. Time to add the closing squiggly bracket of the CanDev definition:

};

References