Demo time! As test setup, I’m going to use an F4-based STM32 µC, since it has two CAN bus controllers. It’s easier to write a quick test for a single system. And since I have these boards lying around, that’s what I’ll use: a 32F429-DISCO with an Open429Z-D:

The breakout board adds a USB-to-serial interface and headers to plug in two CAN bus drivers (for level translation, as described in part 1). They each have a 120 Ω termination resistor, so all that’s needed for a minimal CAN bus is the 2-wire jumper between them.

The F429-DISCO board includes an ST-Link (v2.0) for uploading code. No need to grab a Black Magic Probe or other programming tool.

There’s a lot more functionality on these two boards, none of which will be used here.

The demo code

As with all PlatformiIO projects, initial setup is a matter of creating a new empty project folder, with two files: platformio.ini and src/main.cpp.

Here is the platformio.ini file, it should look familiar by now:

[env:f429]
build_flags = -DSTM32F4
platform = ststm32
board = disco_f429zi
framework = stm32cube
upload_protocol = stlink
monitor_speed = 115200
lib_deps = JeeH

And here is the initial demo application I came up with for src/main.cpp:

#include <jee.h>

UartBufDev< PinA<9>, PinA<10> > console;
#include <../../common.h>

CanDev<0> can1;
CanDev<1> can2;

int main() {
    console.init();
    constexpr uint32_t hz = 180000000;
    enableClkAt180MHz();        // the F429 mac clock rate is 180 MHz
    console.baud(115200, hz/2); // APB2 is /2 to stay within 90 MHz max
    enableSysTick(hz/1000);     // recalibrate SysTick to fire every 1 ms

    printf("hello!\n");

    can1.init();
    can2.init();

    can1.filterInit(14); // always set filters via can1

    uint32_t last = 0;
    while (1) {
        if (ticks / 500 != last) {
            last = ticks / 500;
            printf("T %d\n", ticks);
            can1.transmit(0x123, "abcd1234", 8);
        }

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

Every 500 ms, an 8-byte packet is sent out on can1 with a fixed payload (”abcd1234”). And whenever a packet comes on can2, its details are printed on the console.

Let’s see it run

When all the configuration details are right, compiling and uploading is a matter of typing pio run -t upload. The serial USB port will then show the printf output:

hello!
T 500
R 500 @123 #8: 64636261 34333231
T 1000
R 1000 @123 #8: 64636261 34333231
T 1500
R 1500 @123 #8: 64636261 34333231
T 2000
R 2000 @123 #8: 64636261 34333231
T 2500
R 2500 @123 #8: 64636261 34333231
[...etc...]

With a scope that has CAN protocol decoding built-in, we can see what the bus looks like:

Some notes and observations:

The actual voltage levels are not too important. What matters is the difference between CAN-HI and CAN-LO, which varies from 0.0 V to ≈ 1.9V in this setup. These voltages are generated by a 3.3V driver chip, but they are fully compatible with 5V bus driver chips.