5 years, 8 months ago.

Initialize peripherals before main

Hi,

I am trying to initialize peripherals such as CAN or I2C. If I do this before main and one of the peripherals isn't connected properly the nucleo never reaches the main function and no serial feedback is given. Is there a convention on where to initialize these peripherals and is it possible to first check if, for example, a can interface is connected before assigning the pins?

Peripherals before main

I2C i2c_bq(D15, D14);
CAN can(PA_11, PA_12, canFrequency); // RX, TX
int main(){
    // program
}

vs

Peripherals in main

int main(){
    I2C i2c_bq(D15, D14);
    CAN can(PA_11, PA_12, canFrequency); // RX, TX
    // program
}

Board: Nucleo NUCLEO-F446RE Mbed version: mbed-os-5.12.0

Hmmm...the initialize routines will use the default serial settings to output a message if an issue occurs during initialization. Typically the baud rate is set up to 9600 baud for the serial port that talks to the USB micro (on a Nucleo board). However I do recall having an issue with the ST supplied init routines for CAN that I had to fix by properly initializing registers. I used the free version of IAR and downloaded the source code off of github to walk thru the init to find that issue....

posted by Bill Bellis 01 May 2019

1 Answer

5 years, 8 months ago.

I used the CAN module/library a couple years ago and (as I recall) if micro RX was not pulled high at startup it would halt the whole program. That might be what you’re running into. By the design of the library, you need to have the right physical connection (RX pin must see a high level) or the CAN module will not initialize and the program stops. I believe a connected CAN transceiver will provide the right levels all the time as long as the chip is powered.

Creating the object, should work in either location. If it works in one location and not another, it may be an accident of timing.

A good way to get started with micro CAN is to short the micro’s tx and rx pins, the tx line will provide the high level and allow rx to initialize. This allows you to do a loopback test with just the micro’s CAN module – don’t even need to connect the transceiver.

I have a product with a CAN module and worrying about waiting to enable CAN has never been an issue. But if you need to delay starting the CAN module, you can’t create it in the ctor. You could make another top level CAN wrapper module that is created at startup and then is responsible for dynamically creating actual CAN driver when conditions are right. If you’re waiting for RX to go high, you should be able to figure out how to test for this.

If you create them before main they are global scope. They get their very own space in RAM not on main() stack and they live for the duration of the program. And other files/modules can access those objects (so you could share object with another file). And the ctors are called before main() of course.

Regarding organizing a larger program, in most cases your micro is probably dedicated to doing a small number of tasks in perpetuity, so global scope often makes the most sense for the building blocks of a program. I can show you my approach for C++ firmware (which is probably pretty normal). I have top level classes that contain major functionality. Basically you are creating semi-independant blocks of functionality and wiring them up through the ctors. This is dependency injection where an object gets its dependencies passed down from the top.

For scheduling, I like the mbed EventQueue. You pass it around and objects simply tell it which member function to call and when, using call_in() and call_every(). Simple polling loop in main is another way to do it, but I like EventQueue better. The Updater class here contains the EventQueue and a Thread so that top level is as clean as possible.

Non functioning example:

#include "mbed.h"

Watchdog  watchdog;            // Owns a thread. Manages IWDG hardware. Monitors other threads.  Threads register at runtime with contract check-in time.
I2C       i2c;                 // More or less standard mbed I2C library
Updater   updater(watchdog);   // Owns a thread. Most things run in this thread. Pass around and objects can schedule themseleves.
API       api(watchdog);       // Owns its own thread because comm channel is slow.
Sensors   sensors(updater);    // All AD Data acquisition.
Eeprom    eeprom(i2c);         // Eeprom peripheral.  I2C needs to be mocked for testing, so probably should be passed in from top.
Config    config(api, eeprom); // Registers with API at runtime so it can handle User Config API calls. Saves config to eeprom.
Stats     stats(api, updater); // Track maxes, mins, avgs, totals, times, etc. Respond to API request to get stats.

int main() {

    // threads created in class ctors, just need to start
    watchdog.start();
    api.start();
    updater.start();

    while(true) {
        Thread::wait(100);
    }
}

Accepted Answer

Thanks a lot for your response. I don't think the timing of the can initialization is a problem. The can initialization succeeds even with a non powered or a non connected can transceiver. Do you think there is a way in which a properly connected can transceiver can give a low signal on the nucleo's rx pin?

And another unrelated question, using another can transceiver, is it possible that a can transceiver is broken such that it can still receive messages but is not able to transmit any messages?

Do you think that there is a way in which a properly connected can tranceiver can still give the rx nucleo pin a low signal?

posted by Mo K 30 Apr 2019