Why no binary reuse?

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
As I play around with these STM32 devices I am continually puzzled as to why we don't see more binary reuse. All of the projects I see or find online seem to require the consumption of source code, to use some feature or gadget often means we must drag in various header and code files, and just to build some simple app also seems to requires a huge plethora of manufacturer supplied library source.

Why is there no binary reuse? In larger scale non-MCU work we have nuget packages for example, and in the world of .net or java we routinely consume binary files, the files generated when that code was compiled.

This is not a new idea, it has existed for a long time and C and C++ are no strangers to this but we just do not see it in the world of MCUs it seems.

If we reuse a library by consuming the source then we carry huge risk, the code we build will vary depending on the compiler we use, the version of that compiler, the specific language options we're using, the language version we're using etc etc.

Why can't I just download some gadget library that's built for some STM32 family?

Is it me, or is it the case the binary reuse just doesn't happen in the world of MCUs?
 

nsaspook

Joined Aug 27, 2009
13,087
There are such things as a bare-metal ABI for a MCU or module. It can be a self-contained statically-linked executable that normally goes by the name firmware. Extensive binary reuse normally happens on systems were there is virtualization of hardware resources instead of direct manipulation of registers and such. This virtualization of hardware allows for emulation of missing or different hardware functions from a common binary calling things like privileged processor traps instead of direct function manipulation of registers. For most low-level embedded processor applications this virtualization usually is a waste for platform resources.
https://en.wikipedia.org/wiki/Application_binary_interface

https://microchipdeveloper.com/mplabx:projects-library
For most bare-metal embedded programs a source code API is used in concert with specific processor application libs with supplied headers to provide linkage hooks because there are so many variations in processor hardware and configurations in the same family that a general case binary would be larger, slower and impractical when compared to the possible optimizations (smaller, lower cost chips) compiling directly from source. It's all about price/efficiency reducing of the per unit costs when million of units are made.

I don't see the huge risk in having source level tool-chain access to functionality vs binary blobs of dubious origin from places like China.
 
Last edited:

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
There are such things as a bare-metal ABI for a MCU or module. It can be a self-contained statically-linked executable that normally goes by the name firmware. Extensive binary reuse normally happens on systems were there is virtualization of hardware resources instead of direct manipulation of registers and such. This virtualization of hardware allows for emulation of missing or different hardware functions from a common binary calling things like privileged processor traps instead of direct function manipulation of registers. For most low-level embedded processor applications this virtualization usually is a waste for platform resources.
https://en.wikipedia.org/wiki/Application_binary_interface

https://microchipdeveloper.com/mplabx:projects-library
For most bare-metal embedded programs a source code API is used in concert with specific processor application libs with supplied headers to provide linkage hooks because there are so many variations in processor hardware and configurations in the same family that a general case binary would be larger, slower and impractical when compared to the possible optimizations (smaller, lower cost chips) compiling directly from source. It's all about price/efficiency reducing of the per unit costs when million of units are made.

I don't see the huge risk in having source level tool-chain access to functionality vs binary blobs of dubious origin from places like China.
Binary reuse in the context I'm referring to means an equivalent to say a Windows DLL file, an opaque, unit tested and compiled binary file. To build an app, that is to "use a library" is then to simply consume the DLL during the link stage. So far as I can tell, code for - say - an STM32 family (ARM) could be written that is independent of specific hardware other than the hardware supporting the peripherals used by the library.

Yes there are variations in hardware but up to a point these can be abstracted, I mean looking at the SPI on my F446RE device, it seems that peripheral has the same "interface" so to speak across multiple devices. The specific pins etc that are used can easily be supplied as configuration inputs to library functions.

All these "DLL" files would contain is ARM instructions and manipulations of memory addresses where the memory mapped peripheral registers are defined.

I may be over simplifying this, I don't know enough about some of the details, but this is the kind of thing I'm driving at.
 

nsaspook

Joined Aug 27, 2009
13,087
Yes, you are simplifying and there are likely some pre-compiled higher function library files being used on a typical bare-metal micro-controller tool-chain.

The basic low level stuff you are doing will likely always be at the source code level because who do you trust, beyond basic device functionality from the device OEM, to deliver that compiled binary (DLL) file for a bare-metal life and safety critical system being designed? I don't see lower reliability or side effects that will be very hard to diagnose and will only get worse as you add more source files and more bloated blob static libraries like this as an advantage.

Having a standard API at the source level to handle different implementations between devices is much more efficient in resources in real-world embedded projects than the equivalent of a Windows DLL likely to be completely obsolete on the next device revision.
https://www.st.com/resource/en/user...l-and-lowlayer-drivers-stmicroelectronics.pdf
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
Yes, you are simplifying and there are likely some pre-compiled higher function library files being used on a typical bare-metal micro-controller tool-chain.

The basic low level stuff you are doing will likely always be at the source code level because who do you trust, beyond basic device functionality from the device OEM, to deliver that compiled binary (DLL) file for a bare-metal life and safety critical system being designed? I don't see lower reliability or side effects that will be very hard to diagnose and will only get worse as you add more source files and more bloated blob static libraries like this as an advantage.

Having a standard API at the source level to handle different implementations between devices is much more efficient in resources in real-world embedded projects than the equivalent of a Windows DLL likely to be completely obsolete on the next device revision.
https://www.st.com/resource/en/user...l-and-lowlayer-drivers-stmicroelectronics.pdf
Well one of the problems I'm seeing is that the term "library" is used very casually. The libraries for nRF24L01 for example are all various combinations of headers and source code and their exact compatibility with some device or with other code one might be developing, is far from clear.

I've seen various libraries the past few days for this device, but exactly what files one must include, in what order and what compiler options to use and so on, is far from clear.

Why use source code that we must compile when we could use the object files that the creator produced when they compiled the code!

I'm sure if one worked professionally in this world for some time, this would be clearer.

In .Net development a great many libraries are open source but also fully unit tested builds are made available as nuget packages too where the consumer never really sees or needs the source code, but it is out there in Github if one really wants it. .Net assemblies are self describing too, so its easy for the tooling to probe them for all types and other metadata.

Of course .Net has devoted a lot of time and energy to making that kind of ecosystem work and the platform is bigger.

But I have started crafting a simple, rudimentary API for working with the nRF24L01 device (just playing, nothing really serious or like a professional might work) and when I build the project I get a file - NRF24L01.o - that in principle anybody could just use as-is, they'd need the function prototypes header of course but not the implementation source code.

I don't know a great deal about all this yet, but the source code currently relies on stm32f4xx_hal.h so I guess the code is tied to the F4 family.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
Most of us don't trust creators to do the right things when financial motives are involved.
View attachment 279460

Just wait until you've been burned like during the FTDI driver DLL bricking.
https://www.zdnet.com/article/ftdi-admits-to-bricking-innocent-users-chips-in-silent-update/

When you abandon source code based building of basic embedded functions you become a patsy for this kind of crap.
Well I am a novice in this area, so will heed your wise words! I am now pondering though whether to seriously start using C++ in earnest, I know C pretty much inside out (but admittedly have done little for almost a decade with that language) but C++ is a world of its own, I know OO languages well (C# is an extremely good language) buy C++ is full of idiosyncrasies.

When I create a new project in Visual GDB it never mentions or asks if I want to use C or C++, the into source file is named *.cpp so I guess it is all C++ but I've been simply writing C so far.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
Most of us don't trust creators to do the right things when financial motives are involved.
View attachment 279460

Just wait until you've been burned like during the FTDI driver DLL bricking.
https://www.zdnet.com/article/ftdi-admits-to-bricking-innocent-users-chips-in-silent-update/

When you abandon source code based building of basic embedded functions you become a patsy for this kind of crap.
The "trust no one" advice is well heeded, I recall when starting as a "trainee programmer" back in 1982 (Yes, that really was an official job title at that time) I was chatting in the pub (where most real software design decisions were made) to a much older experienced programmer, and he said "Here's the golden rule when all said and done - assume nothing".
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
Now, take a look at this other post I made:

https://forum.allaboutcircuits.com/...are-very-confusing.189740/page-4#post-1773692

Now, tell me, which of these many options is one to use when including a "library" into a project? What if we pulled in six libraries and found some need version XX of C but other needed version YY and we couldn't get a clean compile because there was some mutually exclusive thing going on?

Frankly, in order to use a library at the source code level we need to know exactly how the supplier compiled it because if we do not and we use some slightly different language version, slightly different options then we are - at the end of the day - running potentially different code. It isn't the source that runs, it isn't the source that we test, it's the binary code, it isn't the source that the supplier tested it was the binary!

It reminds me of the old warning I used to hear as a junior, people spend weeks and weeks building and testing and refining code in debug mode, only to then switch to release builds when the deliver the application, they spend weeks testing one thing and then deliver something else altogether!
 

nsaspook

Joined Aug 27, 2009
13,087
Now, take a look at this other post I made:

https://forum.allaboutcircuits.com/...are-very-confusing.189740/page-4#post-1773692

Now, tell me, which of these many options is one to use when including a "library" into a project? What if we pulled in six libraries and found some need version XX of C but other needed version YY and we couldn't get a clean compile because there was some mutually exclusive thing going on?

Frankly, in order to use a library at the source code level we need to know exactly how the supplier compiled it because if we do not and we use some slightly different language version, slightly different options then we are - at the end of the day - running potentially different code. It isn't the source that runs, it isn't the source that we test, it's the binary code, it isn't the source that the supplier tested it was the binary!

It reminds me of the old warning I used to hear as a junior, people spend weeks and weeks building and testing and refining code in debug mode, only to then switch to release builds when the deliver the application, they spend weeks testing one thing and then deliver something else altogether!
That should not be a problem if the programmer understands what the source code is doing. These are programmer problems, you have the source(s), fix it. If you don't understand how to fix it then get cracking learning instead of complaining.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
That should not be a problem if the programmer understands what the source code is doing. These are programmer problems, you have the source(s), fix it. If you don't understand how to fix it then get cracking learning instead of complaining.
Well I don't see how one can generalize and describe that situation as "not be a problem". These are real questions. If we don't compile library source with the exact same vendor's compiler and with the exact same version of the compiler and the exact same options that the vendor used when testing their code then we cannot assume the generated result will function identically to their code or pass all of the tests they performed.

This is not a complaint IMHO, this is a fact, a true statement that pertains the integrity of the resulting product.

Nor do I see how one can just say "get cracking learning" because what I say is not a question of knowledge but of uncertainty and uncertainty is the enemy of reliability. We have no idea if building their code with a different vendor's compiler or a different set of options might introduce a subtle timing bug or race condition or - worse - actually fix a bug that was present but they had compensated for.

Why would you "fix" code when a problem you're seeing might be solely due to a compiler difference or compile option difference?

I should no more need to "fix" supplied tested library source code than I should need to open up a logic chip and "fix" it in order to get to my system to build cleanly.

Of course we do and I do sometimes change open sourced library code when working with .Net. I do that though to fix a true bug or make a change that must be made for my specific usage, but this is different. this is not a result of me building with their source, it arises when the binary itself does not do what want or need.

There is simply no way one can argue that consuming library source code rather than library binaries is not an increase in risk and uncertainty unless one consumes that source with A) The exact same compiler, B) The same version of the compiler, C) The same target CPU and D) The same options the library supplier used for that compilation.

Now it might well be that such a practice is a necessity or close to, with this world of constrained devices and with the current industry practices in place, but that's a different argument, it does not mean the risks are not there.

Now, here's something I just stumbled upon - time for coffee: Why Code Reuse Matters
 
Last edited:

nsaspook

Joined Aug 27, 2009
13,087
IMO this is mostly a useless exercise about theoretical issues that assumes supplied (from some 'trusted' source) binaries will be superior to locally generated customized versions produced by a embedded programmers familiar with the lowest-level details (selected the devices, designed the board and revisions of that board) of X project.

As the designer/engineer, the integrity of the resulting product is in your hands. Own the process from Top to Bottom as much as possible.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
IMO this is mostly a useless exercise about theoretical issues that assumes supplied (from some 'trusted' source) binaries will be superior to locally generated customized versions produced by a embedded programmers familiar with the lowest-level details (selected the devices, designed the board and revisions of that board) of X project.

As the designer/engineer, the integrity of the resulting product is in your hands. Own the process from Top to Bottom as much as possible.
This isn't "theoretical", please explain to me what C language standard would you / do you, use for say a new project running on say STM32 devices? How does one analyze the problem domain and decide on C18, C17, C99 or C2x or these standards with the "GNU extensions"? right there we have eight options to choose from.

If we choose one option and later then find that while trying to leverage some other library, that library won't compile under our chosen standard, how is that resolved?

These are extremely important questions, certainly to me, a software engineer by trade, these are the kinds of "loose ends" that can wreak havoc if simply swept under the carpet, if one simply takes a "It'll be alright on the night" approach.
 

nsaspook

Joined Aug 27, 2009
13,087
This isn't "theoretical", please explain to me what C language standard would you / do you, use for say a new project running on say STM32 devices? How does one analyze the problem domain and decide on C18, C17, C99 or C2x or these standards with the "GNU extensions"? right there we have eight options to choose from.

If we choose one option and later then find that while trying to leverage some other library, that library won't compile under our chosen standard, how is that resolved?

These are extremely important questions, certainly to me, a software engineer by trade, these are the kinds of "loose ends" that can wreak havoc if simply swept under the carpet, if one simply takes a "It'll be alright on the night" approach.
They don't wreak havoc because the controller tool chain is not some random collection of parts from various sources. In the vast majority of cases the complete tool chain is supplied by the device hardware manufacturer and is specifically tailored to work with their devices. The differences at the embedded level between C18, C17, C99 or C2x are trivial as most controller tool-chain (and most public domain C embedded source) releases are very conservative with C99 or maybe C11 as the compiler standard.

So in actual practice the problem is mainly "theoretical" because embedded hardware guys that are also software engineer by trade are smart and try not to do things that are risky and overly complex just to be cute.

Most consider source code delivered by vendors as reference material, something that works but is not optimal for a specific application. This means the version of C vendor/public domain code in your application will likely be modified (many times highly modified) from the official tool-chain release so using binary blobs is usually not practical or even desired compared to source code reuse where you can see all, know all and modify all easily.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
Well reuse is a very important thing to aspire to in software development. If we build a system and write some generic component like a thread safe queue or or thread safe memory allocator, something non trivial, then how can we reuse that at a source level across projects if those projects might use different tool chains with different language standards or different kinds of undefined behaviors?

We can't, our code might be developed, debugged and tested for say STM32F4 and later we must do a similar project for a different manufacturer's target. Well that code might not be unconditionally reusable, "portable" and so right there we have a practical problem - not theoretical - how do we maintain two, three subtly differing versions of our queue/allocator library? answer? one cannot, that is a problem that has plagued software for decades, these various versions of the same library will get out of step, no matter how hard one tries, they will, its a huge risk for this reason alone.

If reuse is not important then I agree, much of what I've said is unimportant but reuse is important, software is notoriously expensive be to develop, programmer productivity is often low at the best of times. Anybody that's in the business of developing software needs to consider reuse and portability, it's the kind of thing that can carry huge costs.

Now you did allude to this a little because you kind of advocate being "conservative" with the standard like C99 or C11 but that is tantamount to an admission that there's is a problem because you can't leverage language improvements.
 

nsaspook

Joined Aug 27, 2009
13,087
..
Now you did allude to this a little because you kind of advocate being "conservative" with the standard like C99 or C11 but that is tantamount to an admission that there's is a problem because you can't leverage language improvements.
Yes, "conservative" means knowing there are landmines when jumping into the latest and greatest language 'improvements' that are rarely designed to solve basic embedded problems. The common solution is to use a RTOS that provides a somewhat standard API for things like a thread safe queue or or thread safe memory allocator. For most smaller embedded or critical applications you don't use dynamic memory allocation in software where ‘mission critical’ doesn’t mean “the database goes down and has to be rebooted”.


Of course there is a problem (a very messy at times problem) with solutions like being "conservative" to limit problems with source code reusability but your “plug-and-play”, interchangeable, with little understanding of the application domain. solution is IMO is not a practical answer.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
Yes, "conservative" means knowing there are landmines when jumping into the latest and greatest language 'improvements' that are rarely designed to solve basic embedded problems.
The degree to which there are "landmines" is a function of the language though. A well designed language fully anticipates change, and will be designed with change in mind (like for example having no reserved words) and backward compatibility being one of the goals.

When C was first born I think little if any, consideration was given to this and so its no surprise that isn't a strength of the language.

I'm seeing all sorts of source libraries for the nRF24L01 device out there, but darned if I can use any of them. The code either won't compile, or is tied to some specific C version or MCU. The device itself is "pluginabble" and it would be nice if there was a library that was as decoupled from the MCU as much as the device itself is.

The only interface the device has is SPI and so the device (most electronic devices) use an interface based design concept.

One way to improve this then is to adopt a callback strategy, where the library has no idea about the target hardware only an abstract interface. Then we - the consumer of the library - must write platform specific callbacks to do the SPI related steps.

With this approach we'd initialize the library by passing function pointers in to functions we implement that do little nRF processing but do perform the SPI IO.

I'm toying with this idea myself after struggling to just get a library that works, I suspect it would be rather easier to do in C++ than C but not massively so.

Look at this for example, just some basic code I put together the past few days:

Code:
static void _WriteMultiBytesRegister(NrfSpiDevice * SPI, uint8_t Register, uint8_t Value[], uint8_t * BytesWritten, STATUS * NrfStatus)
{
    uint8_t command = W_REGISTER | Register;
    uint8_t width;
    uint8_t bytes;
 
    _ReadSingleByteRegister(SPI, NrfRegister.SETUP_AW, &width, NrfStatus);
 
    switch (width)
    {
    case 1:
        bytes = 3;
        break;
    case 2:
        bytes = 4;
        break;
    case 3:
        bytes = 5;
        break;
    }
 
    *BytesWritten = bytes;
 
    spi_cs_lo(SPI);
    SPI->status = HAL_SPI_TransmitReceive(SPI->spi_ptr, &command, (uint8_t*)NrfStatus, 1, HAL_MAX_DELAY);
    SPI->status = HAL_SPI_Transmit(SPI->spi_ptr, Value, bytes, HAL_MAX_DELAY);
    spi_cs_hi(SPI);
}
That code "knows about" HAL, but it doesn't need to, those two HAL calls could be replaced by the invocation of a callback that the library consumer provides.

So in principle the library would export WriteMultiBytesRegister but would itself rely on user provided code for the two HA:L calls.

The device can then be represented by some abstract structure (in this case NrfSpiDevice) that itself contains a "consumer" pointer that points to consumer specific "blob" and the library simply passes that into callback invocations where consumer code can do what it needs to do.

This could be taken a step further:

Code:
typedef struct
{
    void(* InitDevice)(SPI_HandleTypeDef * SpiPtr, GPIO_TypeDef * GpioPtr, uint8_t CsPin, uint8_t CePin, NrfSpiDevice * Device);
    void(* ReadSingleByteRegister)(NrfSpiDevice * SPI, uint8_t Register, void * Value, STATUS * NrfStatus);
    void(* WriteSingleByteRegister)(NrfSpiDevice * SPI, uint8_t Register, void * Value, STATUS * NrfStatus);
    void(* ReadMultiBytesRegister)(NrfSpiDevice * SPI, uint8_t Register, uint8_t Value[], uint8_t * BytesRead, STATUS * NrfStatus);
    void(* WriteMultiBytesRegister)(NrfSpiDevice * SPI, uint8_t Register, uint8_t Value[], uint8_t * BytesWritten, STATUS * NrfStatus);
} NrfLibrary;
By exposing only function pointers that are user implemented, so that the library has no idea how the SPI IO is actually done by the consuming code.

This is all interesting stuff, I'm quite fascinated by creating a real device library that is truly source code portable.
 
Last edited:

nsaspook

Joined Aug 27, 2009
13,087
What you describe is something as old as the hills in embedded programming at the source code level.
https://github.com/nsaspook/wfi32/tree/wfi/firmware/src

Here the main line code uses function pointers to each type of IMU device (that use the SPI interface) using a 'object' structure with function pointer elements to the needed code.
https://raw.githubusercontent.com/nsaspook/wfi32/wfi/firmware/src/imu.h

C:
// header stuff
       typedef struct {
                uint16_t id;
                double x; /**< X-axis sensor data */
                double y; /**< Y-axis sensor data */
                double z; /**< Z-axis sensor data */
                float xa; /**< X-angle sensor data */
                float ya; /**< Y-angle sensor data */
                float za; /**< Z-angle sensor data */
                float xerr;
                float yerr;
                float zerr;
                uint32_t sensortime; /**< sensor time */
                float sensortemp;
                uint8_t buffer[64]; // can-fd frame buffer space
        } sSensorData_t;

        /*
         * function pointer templates structure
         * for the device I/O routines and data
         */
        typedef struct _op_t {
                void (*info_ptr)(void);
                void (*imu_set_spimode)(void *);
                bool (*imu_getid)(void *);
                bool (*imu_getdata)(void *);
        } op_t;

        enum device_type {
                IMU_BMA490L = 0, // IMU chip model
                IMU_SCA3300,
                IMU_SCL3300,
                IMU_NONE,
                IMU_LAST,
        };

        /*
         * IMU data structure for driver
         */
        typedef struct _imu_cmd_t {
                uint16_t id;
                enum device_type device;
                uint8_t cs, acc_range, spi_bytes, acc_range_scl;
                uint32_t log_timeout, rs, ss;
                volatile bool online, run, update, features, crc_error, angles;
                uint8_t rbuf[64], tbuf[64];
                uint32_t rbuf32[2], tbuf32[2];
                uint16_t serial1, serial2;
                uint32_t board_serial_id;
                op_t op;
        } imu_cmd_t;

// main.c stuff

#ifdef BMA490L
/*
* BMA490L instance
*/
imu_cmd_t imu0 = {
    .id = CAN_IMU_INFO,
    .tbuf[0] = CHIP_ID | RBIT,
    .online = false,
    .device = IMU_BMA490L, // device type
    .cs = IMU_CS, // chip select number
    .run = false,
    .log_timeout = BMA_LOG_TIMEOUT,
    .update = true,
    .features = false,
    .spi_bytes = 1,
    .op.info_ptr = &bma490_version,
    .op.imu_set_spimode = &bma490l_set_spimode,
    .op.imu_getid = &bma490l_getid,
    .op.imu_getdata = &bma490l_getdata,
    .acc_range = range_2g,
};
#endif

#ifdef SCA3300
/*
* SCA3300-D01 instance
*/
imu_cmd_t imu0 = {
    .id = CAN_IMU_INFO,
    .tbuf32[SCA3300_TRM] = SCA3300_SWRESET_32B,
    .online = false,
    .device = IMU_SCA3300, // device type
    .cs = IMU_CS, // chip select number
    .run = false,
    .crc_error = false,
    .log_timeout = SCA_LOG_TIMEOUT,
    .update = true,
    .features = false,
    .spi_bytes = 4,
    .op.info_ptr = &sca3300_version,
    .op.imu_set_spimode = &sca3300_set_spimode,
    .op.imu_getid = &sca3300_getid,
    .op.imu_getdata = &sca3300_getdata,
    .acc_range = range_15gl,
    .acc_range_scl = range_inc1,
    .angles = false,
};
#endif
Main simply calls the set of functions needed for each type of device using a pointer to the device structure (&imu0) for configuration, init and data processing into a standard format.

function pointer code fragments
C:
    imu0.op.info_ptr(); // print driver version on the serial port
    imu0.op.imu_set_spimode(&imu0); // setup the IMU chip for SPI comms, X updates per second @ selected G range

    while (true) {
        /* Maintain state machines of all polled MPLAB Harmony modules. */
        SYS_Tasks();

        /*
         * data logging routine
         * convert the SPI XYZ response to standard floating point acceleration values and rolling integer time-stamps per measurement
         */
        if (imu0.update || TimerDone(TMR_LOG)) {
#ifdef SHOW_LCD 
            TP1_Set();
            OledClearBuffer();
            TP1_Clear();
#endif
            TP1_Set();
            imu0.op.imu_getdata(&imu0); // read data from the chip
            imu0.update = false;
            TP1_Clear();
            TP1_Set();
            getAllData(&accel, &imu0); // convert data from the chip
            TP1_Clear();
#ifdef __32MK0512MCJ048__
            MCPWM_ChannelPrimaryDutySet(MCPWM_CH_1, 1024 + (uint32_t) (10.0 * accel.xa));
            MCPWM_ChannelPrimaryDutySet(MCPWM_CH_4, 1024 + (uint32_t) (10.0 * accel.ya));
#endif
            accel.xerr = UpdatePI(&xpid, (double) accel.xa);
            accel.yerr = UpdatePI(&ypid, (double) accel.ya);
            accel.zerr = UpdatePI(&zpid, (double) accel.za);
//
}
Headers for the device function pointers
https://github.com/nsaspook/wfi32/blob/wfi/firmware/src/sca3300.h
https://github.com/nsaspook/wfi32/blob/wfi/firmware/src/bma490l.h

The code also has ifdefs and separate IDE (mplabx) config profiles for two different types of PIC32 controllers for compiling using the same source code.
 
Last edited:

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
What you describe is something as old as the hills in embedded programming at the source code level.
https://github.com/nsaspook/wfi32/tree/wfi/firmware/src

Here the main line code uses function pointers to each type of IMU device (that use the SPI interface) using a 'object' structure with function pointer elements to the needed code.
https://raw.githubusercontent.com/nsaspook/wfi32/wfi/firmware/src/imu.h

C:
// header stuff
       typedef struct {
                uint16_t id;
                double x; /**< X-axis sensor data */
                double y; /**< Y-axis sensor data */
                double z; /**< Z-axis sensor data */
                float xa; /**< X-angle sensor data */
                float ya; /**< Y-angle sensor data */
                float za; /**< Z-angle sensor data */
                float xerr;
                float yerr;
                float zerr;
                uint32_t sensortime; /**< sensor time */
                float sensortemp;
                uint8_t buffer[64]; // can-fd frame buffer space
        } sSensorData_t;

        /*
         * function pointer templates structure
         * for the device I/O routines and data
         */
        typedef struct _op_t {
                void (*info_ptr)(void);
                void (*imu_set_spimode)(void *);
                bool (*imu_getid)(void *);
                bool (*imu_getdata)(void *);
        } op_t;

        enum device_type {
                IMU_BMA490L = 0, // IMU chip model
                IMU_SCA3300,
                IMU_SCL3300,
                IMU_NONE,
                IMU_LAST,
        };

        /*
         * IMU data structure for driver
         */
        typedef struct _imu_cmd_t {
                uint16_t id;
                enum device_type device;
                uint8_t cs, acc_range, spi_bytes, acc_range_scl;
                uint32_t log_timeout, rs, ss;
                volatile bool online, run, update, features, crc_error, angles;
                uint8_t rbuf[64], tbuf[64];
                uint32_t rbuf32[2], tbuf32[2];
                uint16_t serial1, serial2;
                uint32_t board_serial_id;
                op_t op;
        } imu_cmd_t;

// main.c stuff

#ifdef BMA490L
/*
* BMA490L instance
*/
imu_cmd_t imu0 = {
    .id = CAN_IMU_INFO,
    .tbuf[0] = CHIP_ID | RBIT,
    .online = false,
    .device = IMU_BMA490L, // device type
    .cs = IMU_CS, // chip select number
    .run = false,
    .log_timeout = BMA_LOG_TIMEOUT,
    .update = true,
    .features = false,
    .spi_bytes = 1,
    .op.info_ptr = &bma490_version,
    .op.imu_set_spimode = &bma490l_set_spimode,
    .op.imu_getid = &bma490l_getid,
    .op.imu_getdata = &bma490l_getdata,
    .acc_range = range_2g,
};
#endif

#ifdef SCA3300
/*
* SCA3300-D01 instance
*/
imu_cmd_t imu0 = {
    .id = CAN_IMU_INFO,
    .tbuf32[SCA3300_TRM] = SCA3300_SWRESET_32B,
    .online = false,
    .device = IMU_SCA3300, // device type
    .cs = IMU_CS, // chip select number
    .run = false,
    .crc_error = false,
    .log_timeout = SCA_LOG_TIMEOUT,
    .update = true,
    .features = false,
    .spi_bytes = 4,
    .op.info_ptr = &sca3300_version,
    .op.imu_set_spimode = &sca3300_set_spimode,
    .op.imu_getid = &sca3300_getid,
    .op.imu_getdata = &sca3300_getdata,
    .acc_range = range_15gl,
    .acc_range_scl = range_inc1,
    .angles = false,
};
#endif
Main simply calls the set of functions needed for each type of device using a pointer to the device structure (&imu0) for configuration, init and data processing into a standard format.

function pointer code fragments
C:
    imu0.op.info_ptr(); // print driver version on the serial port
    imu0.op.imu_set_spimode(&imu0); // setup the IMU chip for SPI comms, X updates per second @ selected G range

    while (true) {
        /* Maintain state machines of all polled MPLAB Harmony modules. */
        SYS_Tasks();

        /*
         * data logging routine
         * convert the SPI XYZ response to standard floating point acceleration values and rolling integer time-stamps per measurement
         */
        if (imu0.update || TimerDone(TMR_LOG)) {
#ifdef SHOW_LCD
            TP1_Set();
            OledClearBuffer();
            TP1_Clear();
#endif
            TP1_Set();
            imu0.op.imu_getdata(&imu0); // read data from the chip
            imu0.update = false;
            TP1_Clear();
            TP1_Set();
            getAllData(&accel, &imu0); // convert data from the chip
            TP1_Clear();
#ifdef __32MK0512MCJ048__
            MCPWM_ChannelPrimaryDutySet(MCPWM_CH_1, 1024 + (uint32_t) (10.0 * accel.xa));
            MCPWM_ChannelPrimaryDutySet(MCPWM_CH_4, 1024 + (uint32_t) (10.0 * accel.ya));
#endif
            accel.xerr = UpdatePI(&xpid, (double) accel.xa);
            accel.yerr = UpdatePI(&ypid, (double) accel.ya);
            accel.zerr = UpdatePI(&zpid, (double) accel.za);
//
}
Headers for the device function pointers
https://github.com/nsaspook/wfi32/blob/wfi/firmware/src/sca3300.h
https://github.com/nsaspook/wfi32/blob/wfi/firmware/src/bma490l.h

The code also has ifdefs and separate IDE (mplabx) config profiles for two different types of PIC32 controllers for compiling using the same source code.
Yes that's exactly the kind of thing I'm referring to. I'm working toward the small NRF library I'm putting together, to work the same way, the library source code will have no compile time dependencies on the MCU it will be running on.

As you say this isn't a new concept, operating systems have abstracted like this for decades, a very good example of this is to be found in the Windows device driver model. A core goal of the WDM was that all device drivers be written in high level language like C and that the source code be 100% portable, to create a driver for say Windows running on ARM, all that's needed is to recompile the driver for that target CPU.

It seems to me that with the current and growing popularity of playing around with MCU boards, seems to focus on "getting something working" and pays not attention to code reuse. Many of the hobby sites and hobby libraries I see are relatively undisciplined and the code isn't easy to reuse, not as easy as it could be anyway.

I'm sure professionals like yourself do pay attention to that, but the general hobby and non-industrial consumers seem to pay little heed to these things.
 
Last edited:
Top