Anyone here using C to compose 'object-based' Microcontroller programmes?

nsaspook

Joined Aug 27, 2009
13,079
A quick rewrite of imu_SPIreadBytes and imu_SPIwriteByte to something that compiles and should work for a PIC32.
C:
/*
* PIC32: changed to use hardware SPI module and plib_gpio pin functions
*/
void imu_SPIreadBytes(unsigned char csPin, unsigned char subAddress, unsigned char *dest, unsigned char count)
{
    int i;
   
    // To indicate a read, set bit 0 (msb) of first byte to 1
    unsigned char rAddress = 0x80 | (subAddress & 0x3F);

    // Mag SPI port is different. If we're reading multiple bytes,
    // set bit 1 to 1. The remaining six bytes are the address to be read
    if ((csPin == __pinM) && count > 1) rAddress |= 0x40;

    //  low(csPin);
    if (csPin == __pinM)
        csPin_m_Clear();
    else
        csPin_ag_Clear();
   
    //  shift_out(__pinSDIO, __pinSCL, MSBFIRST, 8, rAddress);
    SPI2_Write(&rAddress, 1);
    for (i = 0; i < count; i++) {
        //    dest[i] = shift_in(__pinSDIO, __pinSCL, MSBPRE, 8);
        SPI2_Read(&dest[i], 1);
    }
    // high(csPin);
    if (csPin == __pinM)
        csPin_m_Set();
    else
        csPin_ag_Set();
   
}


/*
* PIC32: changed to use hardware SPI module and plib_gpio pin functions
*/
void imu_SPIwriteByte(unsigned char csPin, unsigned char subAddress, unsigned char data)
{
    uint8_t tmp = subAddress & 0x3F;
    //  low(csPin);
    if (csPin == __pinM)
        csPin_m_Clear();
    else
        csPin_ag_Clear();

    //  shift_out(__pinSDIO, __pinSCL, MSBFIRST, 8, subAddress & 0x3F);
    SPI2_Write(&tmp, 1);
    //  shift_out(__pinSDIO, __pinSCL, MSBFIRST, 8, data);
    SPI2_Write(&data, 1);
    //  high(csPin);
    if (csPin == __pinM)
        csPin_m_Set();
    else
        csPin_ag_Set();

}
I don't have the IMU module installed but the MOSI output looks like we have data going out during the IMU init function. The modules uses 3-wire SPI bidirectional data line so a resistor will be needed on
the controller MOSI.
lf60O.jpg

 
Last edited:

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
Thanks nsaspook. Although we are at opposite ends of the learning curve, your ideas and examples give me helpful glimpses into important areas and have made me realise how 'application-specific' are my efforts: I let the hardware push the code around and consider changing the hardware design when coding effort elicits new hardware insights - a long way from abstraction. My project is in flux as I shovel blocks of code between my OBjects until each seems best to meet its narrow purpose and can co-operate, usually with a sibling, most effectively. That may sound chaotic, but the method somehow keeps me feeling safe and productive while doing it - much assisted, I suspect, by the parent-child hierarchy. My rarely using in-function variables or passing parameters probably stems from assembler habits but, although it may not look like it, run-times and real-time are constantly in my mind. I've also experienced 'insights' into 'messaging' (incl 'spaghetti'). Since no one seems interested in my code, I might as well soon give my take on some of those areas in the main thread; might just strike a chord with someone. On the 'we', surely surveys cost?
 

nsaspook

Joined Aug 27, 2009
13,079
IMO programming styles (beyond standards for conforming, modern, and portable C) are like social preferences in bars. The music, interiors and ambiance reflects the clientele if the establishment has been around for a while. It's unrealistic to expect a drastic makeover to be an overnight success. Even the best ideas take time to merge into a nice comfortable culture that sees little wrong with the current establishment because they helped to build it into what they see as a nice easy chair with a good drink on the side table.


UV7BEys.png
 

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
IMO programming styles (beyond standards for conforming, modern, and portable C) are like social preferences in bars. The music, interiors and ambiance reflects the clientele if the establishment has been around for a while. It's unrealistic to expect a drastic makeover to be an overnight success. Even the best ideas take time to merge into a nice comfortable culture that sees little wrong with the current establishment because they helped to build it into what they see as a nice easy chair with a good drink on the side table.


View attachment 219694
A very fine opening summary and that glorious naming catalogue is so funny; must have taken some creating - thank you. I laughed on identifying with the 'Large Firm's Code' extremism - suits me now but could shift. Doesn't 'horses for courses' also apply: name lengths increasing according to the likely (and probably reluctant) readership's position on an abstract-to-application-specific 'axis'?

I don't seek to influence priesthoods or be seen as evangelising but have a thesis centred on a programming method I plagiarised from ESA and find extremely helpful. Despite being a learner, I will continue reporting on the benefits and insights it daily yields me, hope it elicits some interest and feedback, not least because I find learning some parts of C so confusing.

I habitually experience 'stuckisms' on my learning curves and retrospectively find about half were due to my missing something staring me in the face while the rest were more or less justified. I'm currently confused by almost every aspect of C scoping, which I want to exploit to strengthen 'hiding', probably not in ways C intended. Two examples from the generally thorough Microchip XC8 Compiler User's Guide (my italics):

2.4.1.4 HOW CAN I USE A VARIABLE DEFINED IN ANOTHER SOURCE FILE?
Provided the variable defined in the other source file is not static (see Section 4.5.2.1.1 “Static Objects”) or auto (see Section 4.5.2.2 “Automatic Storage 'Duration Objects”), then adding a declaration for that variable into the current file will allow you to access it. A declaration consists of the keyword extern in addition to the type and the name of the variable, as specified in its definition, e.g.,
extern int systemStatus;
This is part of the C language. Your favorite C textbook will give you more information.

2.4.1.5 HOW CAN I USE A FUNCTION DEFINED IN ANOTHER SOURCE FILE?
Provided the function defined in the other source file is not static, then adding a declaration for that function into the current file will allow you to call it. A declaration optionally consists of the keyword extern in addition to the function prototype. You can omit the names of the parameters, e.g.,
extern int readStatus(int);
This is part of the C language. Your favorite C textbook will give you more information.

Copy-pasting is evident. I sieze on the first sentences of both answers as firmly implying a way to hide a variable or function only to be thrown into doubt by their second sentences starting with the indefinite article 'A', implying 'declarations in general', rather than of 'That' or 'The'. C textbooks and endless Searching rarely bring the clarity I crave. I can't help wondering whether anyone else finds this or is it all a breeze?
 
Last edited:

bogosort

Joined Sep 24, 2011
696
I'm currently confused by almost every aspect of C scoping, which I want to exploit to strengthen 'hiding', probably not in ways C intended.
C is pretty straightforward with regards to identifier visibility, though it helps to understand the compilation and linking process that happens behind the scenes. I recommend search terms equivalent to "C scope vs linkage"; perusing a few results should quickly get you on the right track to thorough understanding.

I sieze on the first sentences of both answers as firmly implying a way to hide a variable or function only to be thrown into doubt by their second sentences starting with the indefinite article 'A', implying 'declarations in general', rather than of 'That' or 'The'. C textbooks and endless Searching rarely bring the clarity I crave. I can't help wondering whether anyone else finds this or is it all a breeze?
The generality of scoping and linkage -- hence the indefinite article -- actually simplifies the rules. With a single exception, the scope of an identifier is always determined by where in the source code the identifier is declared. (The sole exception are labels, as in goto statements, which always have function scope.) There are only four possible scopes: function prototype, block, function, and file. Scope is a compile-time concept.

But it seems that you are actually asking about linkage: how can I hide/make visible identifiers in different translation units? It's the linker's job to associate an identifier used in multiple translation units to the appropriate object (memory address). There are only three possible linkages: none, internal, or external. Reading up on this and experimenting should greatly reduce your confusion, but for a quick example, suppose you define a function readStatus in the file device.c and you want it to be invisible to any other parts of the program, i.e., you want it to be a private function for the code in device.c. Give the function internal linkage by declaring it with the static keyword, either in device.c or in device.h that is included in device.c (which then comprise a single translation unit). The other code in device.c will be able to resolve readStatus, but as far as the rest of your program is concerned, it does not exist.
 

BobaMosfet

Joined Jul 1, 2009
2,110
@Hugh Riddle Read what I wrote you in an earlier post. scoping in C is very straightforward. You need to understand that when you write code, you're simply creating a text file. A text file that is written according to a specific grammar. The compiler then starts at the top of the file and begins concatenating all your includes and c files together into one great big text file. On the fly it builds a Symbol Table. A Symbol Table is a table that contains the name of every referenced item (variables, functions, procedures, constants, and their scope. Next a Linker resolves all addressing and references to addressing (every variable, function, or precedure) in a relative way. 'Relative' meaning based as an offset from zero from some start point, since it doesn't actually know in memory where things will reside. Usually this is the start of main or the start of a function inside a stackframe on the stack. Along the way it checks the syntax or grammar of it all, and then tries to convert that into assembly language (binary), aka 'object code' for the final, resulting executable.

In C there are only 2 scopes. Global, and local/static. That is it. The problem you face is that few people understand the correct way to use C files and header files- but they should be used exactly as the compiler uses them in its construction (which I outlined in a post in this thread, above).
 

nsaspook

Joined Aug 27, 2009
13,079
The key thing to remember with 'hiding' when you have full access to source code is why you are using scope and limiting linkage.

Some downloaded code I'm using for a 3-d tracker using the Parallax LSM9DS1 9-axis IMU Module.

It converts the 9-axis result into a four-dimensional complex number called a quaternion in these variables: volatile float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f; // quaternion of sensor frame relative to auxiliary frame
https://stanford.edu/class/ee267/lectures/lecture10.pdf
C:
//=====================================================================================================
// MahonyAHRS.c
//=====================================================================================================
//
// Madgwick's implementation of Mayhony's AHRS algorithm.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date            Author            Notes
// 29/09/2011    SOH Madgwick    Initial release
// 02/10/2011    SOH Madgwick    Optimised for reduced CPU load
//
//=====================================================================================================

//---------------------------------------------------------------------------------------------------
// Header files

#include "MahonyAHRS.h"
#include <math.h>

//---------------------------------------------------------------------------------------------------
// Definitions

#define sampleFreq    512.0f            // sample frequency in Hz
#define twoKpDef    (2.0f * 0.5f)    // 2 * proportional gain
#define twoKiDef    (2.0f * 0.0f)    // 2 * integral gain

//---------------------------------------------------------------------------------------------------
// Variable definitions

volatile float twoKp = twoKpDef; // 2 * proportional gain (Kp)
volatile float twoKi = twoKiDef; // 2 * integral gain (Ki)
volatile float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f; // quaternion of sensor frame relative to auxiliary frame
volatile float integralFBx = 0.0f, integralFBy = 0.0f, integralFBz = 0.0f; // integral error terms scaled by Ki

//---------------------------------------------------------------------------------------------------
// Function declarations

static float invSqrt(float x);

//====================================================================================================
// Functions

//---------------------------------------------------------------------------------------------------
// AHRS algorithm update

void MahonyAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz)
{
    float recipNorm;
    float q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;
    float hx, hy, bx, bz;
    float halfvx, halfvy, halfvz, halfwx, halfwy, halfwz;
    float halfex, halfey, halfez;
    float qa, qb, qc;

    // Use IMU algorithm if magnetometer measurement invalid (avoids NaN in magnetometer normalisation)
    if ((mx == 0.0f) && (my == 0.0f) && (mz == 0.0f)) {
        MahonyAHRSupdateIMU(gx, gy, gz, ax, ay, az);
        return;
    }

    // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
    if (!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {

        // Normalise accelerometer measurement
        recipNorm = invSqrt(ax * ax + ay * ay + az * az);
        ax *= recipNorm;
        ay *= recipNorm;
        az *= recipNorm;

        // Normalise magnetometer measurement
        recipNorm = invSqrt(mx * mx + my * my + mz * mz);
        mx *= recipNorm;
        my *= recipNorm;
        mz *= recipNorm;

        // Auxiliary variables to avoid repeated arithmetic
        q0q0 = q0 * q0;
        q0q1 = q0 * q1;
        q0q2 = q0 * q2;
        q0q3 = q0 * q3;
        q1q1 = q1 * q1;
        q1q2 = q1 * q2;
        q1q3 = q1 * q3;
        q2q2 = q2 * q2;
        q2q3 = q2 * q3;
        q3q3 = q3 * q3;

        // Reference direction of Earth's magnetic field
        hx = 2.0f * (mx * (0.5f - q2q2 - q3q3) + my * (q1q2 - q0q3) + mz * (q1q3 + q0q2));
        hy = 2.0f * (mx * (q1q2 + q0q3) + my * (0.5f - q1q1 - q3q3) + mz * (q2q3 - q0q1));
        bx = sqrt(hx * hx + hy * hy);
        bz = 2.0f * (mx * (q1q3 - q0q2) + my * (q2q3 + q0q1) + mz * (0.5f - q1q1 - q2q2));

        // Estimated direction of gravity and magnetic field
        halfvx = q1q3 - q0q2;
        halfvy = q0q1 + q2q3;
        halfvz = q0q0 - 0.5f + q3q3;
        halfwx = bx * (0.5f - q2q2 - q3q3) + bz * (q1q3 - q0q2);
        halfwy = bx * (q1q2 - q0q3) + bz * (q0q1 + q2q3);
        halfwz = bx * (q0q2 + q1q3) + bz * (0.5f - q1q1 - q2q2);

        // Error is sum of cross product between estimated direction and measured direction of field vectors
        halfex = (ay * halfvz - az * halfvy) + (my * halfwz - mz * halfwy);
        halfey = (az * halfvx - ax * halfvz) + (mz * halfwx - mx * halfwz);
        halfez = (ax * halfvy - ay * halfvx) + (mx * halfwy - my * halfwx);

        // Compute and apply integral feedback if enabled
        if (twoKi > 0.0f) {
            integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki
            integralFBy += twoKi * halfey * (1.0f / sampleFreq);
            integralFBz += twoKi * halfez * (1.0f / sampleFreq);
            gx += integralFBx; // apply integral feedback
            gy += integralFBy;
            gz += integralFBz;
        } else {
            integralFBx = 0.0f; // prevent integral windup
            integralFBy = 0.0f;
            integralFBz = 0.0f;
        }

        // Apply proportional feedback
        gx += twoKp * halfex;
        gy += twoKp * halfey;
        gz += twoKp * halfez;
    }

    // Integrate rate of change of quaternion
    gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors
    gy *= (0.5f * (1.0f / sampleFreq));
    gz *= (0.5f * (1.0f / sampleFreq));
    qa = q0;
    qb = q1;
    qc = q2;
    q0 += (-qb * gx - qc * gy - q3 * gz);
    q1 += (qa * gx + qc * gz - q3 * gy);
    q2 += (qa * gy - qb * gz + q3 * gx);
    q3 += (qa * gz + qb * gy - qc * gx);

    // Normalise quaternion
    recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
    q0 *= recipNorm;
    q1 *= recipNorm;
    q2 *= recipNorm;
    q3 *= recipNorm;
}

//---------------------------------------------------------------------------------------------------
// IMU algorithm update

void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az)
{
    float recipNorm;
    float halfvx, halfvy, halfvz;
    float halfex, halfey, halfez;
    float qa, qb, qc;

    // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
    if (!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {

        // Normalise accelerometer measurement
        recipNorm = invSqrt(ax * ax + ay * ay + az * az);
        ax *= recipNorm;
        ay *= recipNorm;
        az *= recipNorm;

        // Estimated direction of gravity and vector perpendicular to magnetic flux
        halfvx = q1 * q3 - q0 * q2;
        halfvy = q0 * q1 + q2 * q3;
        halfvz = q0 * q0 - 0.5f + q3 * q3;

        // Error is sum of cross product between estimated and measured direction of gravity
        halfex = (ay * halfvz - az * halfvy);
        halfey = (az * halfvx - ax * halfvz);
        halfez = (ax * halfvy - ay * halfvx);

        // Compute and apply integral feedback if enabled
        if (twoKi > 0.0f) {
            integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki
            integralFBy += twoKi * halfey * (1.0f / sampleFreq);
            integralFBz += twoKi * halfez * (1.0f / sampleFreq);
            gx += integralFBx; // apply integral feedback
            gy += integralFBy;
            gz += integralFBz;
        } else {
            integralFBx = 0.0f; // prevent integral windup
            integralFBy = 0.0f;
            integralFBz = 0.0f;
        }

        // Apply proportional feedback
        gx += twoKp * halfex;
        gy += twoKp * halfey;
        gz += twoKp * halfez;
    }

    // Integrate rate of change of quaternion
    gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors
    gy *= (0.5f * (1.0f / sampleFreq));
    gz *= (0.5f * (1.0f / sampleFreq));
    qa = q0;
    qb = q1;
    qc = q2;
    q0 += (-qb * gx - qc * gy - q3 * gz);
    q1 += (qa * gx + qc * gz - q3 * gy);
    q2 += (qa * gy - qb * gz + q3 * gx);
    q3 += (qa * gz + qb * gy - qc * gx);

    // Normalise quaternion
    recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
    q0 *= recipNorm;
    q1 *= recipNorm;
    q2 *= recipNorm;
    q3 *= recipNorm;
}

//---------------------------------------------------------------------------------------------------
// Fast inverse square-root
// See: http://en.wikipedia.org/wiki/Fast_inverse_square_root
// change to standards version

static float invSqrt(float x)
{
    const float x2 = x * 0.5F;
    const float threehalfs = 1.5F;

    union {
        float f;
        unsigned long i;
    } conv = {.f = x};
    conv.i = 0x5f3759df - (conv.i >> 1);
    conv.f *= (threehalfs - (x2 * conv.f * conv.f));
    return conv.f;
}

//====================================================================================================
// END OF CODE
//====================================================================================================
Code:
//=====================================================================================================
// MahonyAHRS.h
//=====================================================================================================
//
// Madgwick's implementation of Mayhony's AHRS algorithm.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date            Author            Notes
// 29/09/2011    SOH Madgwick    Initial release
// 02/10/2011    SOH Madgwick    Optimised for reduced CPU load
//
//=====================================================================================================
#ifndef MahonyAHRS_h
#define MahonyAHRS_h

//----------------------------------------------------------------------------------------------------
// Variable declaration

extern volatile float twoKp;            // 2 * proportional gain (Kp)
extern volatile float twoKi;            // 2 * integral gain (Ki)
extern volatile float q0, q1, q2, q3;    // quaternion of sensor frame relative to auxiliary frame

//---------------------------------------------------------------------------------------------------
// Function declarations

void MahonyAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz);
void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az);

#endif
//=====================================================================================================
// End of file
//=====================================================================================================
Here I have variables with the extern keyword in a .h file that support the source functions in the .c file. That means if I include this header file in another source file the external functions in that other source will have access to manipulate those variables and read the results in the q variables.

The 'main' source file. Testing code for the IMU and logging functions over a serial link for file capture of motion data.

C:
#include <stddef.h>                     // Defines NULL
#include <stdbool.h>                    // Defines true
#include <stdlib.h>                     // Defines EXIT_FAILURE
#include "definitions.h"                // SYS function prototypes
#include "../../../../../../Learn-Folder-Updated-2020.03.02/Learn/Simple Libraries/Sensor/liblsm9ds1/lsm9ds1.h"
#include "../pic32mk_mcj_curiosity_pro.X/MadgwickAHRS/MadgwickAHRS.h"
#include "../pic32mk_mcj_curiosity_pro.X/MahonyAHRS/MahonyAHRS.h"

#define rps    0.0174532925f  // degrees per second -> radians per second

char cbuffer[256] = "\r\n parallax LSM9DS1 9-axis IMU ";
const char imu_missing[] = " MISSING \r\n";
int gx, gy, gz, ax, ay, az, mx, my, mz;

// *****************************************************************************
// *****************************************************************************
// Section: Main Entry Point
// *****************************************************************************
// *****************************************************************************

int main(void)
{
    /* Initialize all modules */
    SYS_Initialize(NULL);


    /* Start system tick timer */
    CORETIMER_Start();
    /*
     * talk to the parallax LSM9DS1 9-axis IMU
     */

    UART1_Write((uint8_t *) cbuffer, strlen(cbuffer));
    if (!imu_init(1, 2, 3, 4)) {
        // Trouble in River-City, not talking to the IMU
        UART1_Write((uint8_t *) imu_missing, strlen(imu_missing));
    } else {
        imu_calibrateAG();
        //imu_calibrateMag();
    };

    while (true) {
        dtog_Set();
        if (imu_gyroAvailable()) imu_readGyro(&gx, &gy, &gz);
        if (imu_accelAvailable()) imu_readAccel(&ax, &ay, &az);
        if (imu_magAvailable()) imu_readMag(&mx, &my, &mz);
        /* Toggle LED after every 1s */
        //MadgwickAHRSupdate((float) gx*rps, (float) gy*rps, (float) gz*rps, (float) ax, (float) ay, (float) az, (float) mx, (float) my, (float) mz);
        MahonyAHRSupdate((float) gx*rps, (float) gy*rps, (float) gz*rps, (float) ax, (float) ay, (float) az, (float) mx, (float) my, (float) mz);
        dtog_Clear();
        sprintf(cbuffer, "Gyro %6d %6d %6d Accel %6d %6d %6d Mag %6d %6d %6d \r\n", gx, gy, gz, ax, ay, az, mx, my, mz);
        UART1_Write((uint8_t *) cbuffer, strlen(cbuffer));
        sprintf(cbuffer, "%6.4f, %6.4f, %6.4f, %6.4f \r\n", q0, q1, q2, q3);
        UART1_Write((uint8_t *) cbuffer, strlen(cbuffer));
        LED_Toggle();
        CORETIMER_DelayMs(100);
    }

    /* Execution should not come here during normal operation */

    return( EXIT_FAILURE);
}
I actually have two versions of the same functions and result variables. So as a testing measure I add the static keyword in the other version source file to change scope and comment out the extern variable line in the other version header file so it's not defined twice.
C:
//=====================================================================================================
// MadgwickAHRS.h
//=====================================================================================================
//
// Implementation of Madgwick's IMU and AHRS algorithms.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date            Author          Notes
// 29/09/2011    SOH Madgwick    Initial release
// 02/10/2011    SOH Madgwick    Optimised for reduced CPU load
//
//=====================================================================================================
#ifndef MadgwickAHRS_h
#define MadgwickAHRS_h

//----------------------------------------------------------------------------------------------------
// Variable declaration

extern volatile float beta;                // algorithm gain
//extern volatile float q0, q1, q2, q3;    // quaternion of sensor frame relative to auxiliary frame <<<<<<<<<<<<<<<<<<<<< edit, add // comment

//---------------------------------------------------------------------------------------------------
// Function declarations

void MadgwickAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz);
void MadgwickAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az);

#endif
//=====================================================================================================
// End of file
//=====================================================================================================
C:
//=====================================================================================================
// MadgwickAHRS.c
//=====================================================================================================
//
// Implementation of Madgwick's IMU and AHRS algorithms.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date            Author          Notes
// 29/09/2011    SOH Madgwick    Initial release
// 02/10/2011    SOH Madgwick    Optimised for reduced CPU load
// 19/02/2012    SOH Madgwick    Magnetometer measurement is normalised
//
//=====================================================================================================

//---------------------------------------------------------------------------------------------------
// Header files

#include "MadgwickAHRS.h"
#include <math.h>

//---------------------------------------------------------------------------------------------------
// Definitions

#define sampleFreq    512.0f        // sample frequency in Hz
#define betaDef        0.1f        // 2 * proportional gain

//---------------------------------------------------------------------------------------------------
// Variable definitions

volatile float beta = betaDef; // 2 * proportional gain (Kp)
static volatile float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f; // quaternion of sensor frame relative to auxiliary frame <<<<<<<<<<<<<<<<<<< edit, add static

//---------------------------------------------------------------------------------------------------
// Function declarations

float invSqrt(float x);
 
Last edited:

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
Thank you, nsaspook, BobaMosfet and bogosort for responding. Your guidance and explanations make me confident about gaining a sound understanding of C scope and linkage. I'm setting myself a parallel exercise of composing a concise but meticulous screed on how C scope and linkage, prototypes, positioning, static and extern work and can be applied as if explaining them to someone as easily confused as myself. It'll take time but when I get there, I'll submit it and be very thankful for any suggested corrections and amendments.
 

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
It's related to but much simpler than 'object-oriented' programme composition. I attempt it (for a mid-range PIC application) by having each .c source file in my project hold only the variable(s) and functions needed for a single purpose (e.g. PowerManager.c, BatteryMonitor.c, Timebase.c) and strive to maximise 'encapsulation' by using C scoping qualifiers to 'hide' everything but the functions which must be accessible from other C source files.

I find this approach's clarity of purpose somehow results in clear layout, truly contextual descriptions, near-grammatical naming, highly readable code and fewer comments within function definitions. It has given me a calmer and more pleasurable way to work in (highly preprocessed) assembler and now C although I'm still struggling to understand fully some of the C scoping defaults and qualifiers I seek to exploit.

I'm hoping some others are working along these lines, as there could be some interesting discussion. I'm pretty new to C and weak on computer science and fearing you'll all say either 'we've been doing that for years' or 'definitely not the way'.
Attached are my C source files (composed in C-configured Notepad++, 115 columns wide, tabs to 4), unfinished but stable in form (their functions' code is unimportant here), and a diagram illustrating the tight cooperation between OBject pair, PowerManager and PowerMonitor, only children of PowerManagement (unshown) which heads their family tree's branch and is one of head OBject LoneStar's several children.

On every (15.6msec) programme pass, parent Power Management commands PowerMonitor to update its estimate of the host's battery charge level (using 'coulomb counting') and to record whether the battery charging rate is lower than on the previous pass, then leaves PowerManager to manage battery loading and charging by reading and changing the host hardware's state through I/O OBject InterfaceWithHost (which, as a partly shared resource, is still mulling its own nature and looking for a home, perhaps on another plane). Initialisations are not shown.

After first drawing each OBject at an apex of an equilateral triangle I noticed that PowerManager's commands for changing and the replies to its questioning of the host's state, through InterfaceWithHost, both flowed clockwise around an information-carrying 'wheel' which rotates several times before handing control back to parent PowerManagement. I wondered whether I'd re-invented the wheel and/or a Turing machine had bowed into the picture. The OBject method seems, often and unasked, to reveal patterns, yield intriguing insights and hint at mathematical possibilities.

PowerManager provides core functionality yet has only a single (1bit) variable (to meet real-time needs). That PowerManager's operations could become a procedure which could also absorb PowerMonitor prompted a re-appraisal of the OBject-based approach, yielding further insights: by clearly showing the nature of and cooperations between its OBjects and the highways (the antithesis of spaghetti) followed by required operations, an OBject diagram can greatly help evolve a programme or section's high-level organisation. The flow diagrams of procedures seem more to centre on operational minutiae, often mix operations of different types (e.g. state-changing and state-testing) but help the coding of operations. Procedures, including those inside OBjects, can be rendered relatively systematic but, IMHO, can't, in themselves, deliver OBject levels of clarity. Open to other views but must get back to understanding prototypes and their baggage.
 

Attachments

Last edited:

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
I think your approach is excellent and at the risk of disappointing you, I have been doing exactly that for years. It sounds to me that any lacking in skills are more in the C language / syntax area. You sound very strong in good programming, organization and problem solving skills - that's the hard part. Thumbing through any decent C reference will clear up any language issues.

It takes more work to do it like you are but the payoffs are as you stated - a calmer and more pleasurable way to work. I would add 'safer' to the list. That approach leads to more reliable and maintainable solutions to the task at hand. In my systems, it is not unusual to have 75-100+ files. Each peripheral, attached IO etc. gets one. Each attached chip DAC, ADC, memory, etc. gets one. That low level functionality is used by system functionality which in turn is used by the main program logic. Setting a FAULT condition is never done in the main code flow. It is done by calling a SystemFault() routine which in turn calls whatever it needs to indicate a fault which in turn calls low level routines. That way, different setups can be accommodated without changing the main program flow. A system fault may turn on an LED, send a message to the display processor, send a message to a connected host or do nothing depending on the hardware configuration. If all of that were done in the main program flow it would be a nightmare to maintain and risk breaking the system when something minor changes. How the hardware is configured is defined by a big config.h file that includes 'snap-in' modules that all have entry points for all the system functions supported. How those functions are actually provided actually unknown to the main program logic.

I think you are exactly on the right track.
Well done.
JohnInTx, Yours was a very welcome encouragement. I'm intrigued to know whether the posts I have since shown (particularly today's (23 Oct) with the OBject diagram) on how I apply the method (including the parts I plagiarised from the European Space Agency) still indicate to you that our approaches are basically similar. It's been quite an exercise, but IMHO keeps coming up with new goods and now also has me believing that difficult logical tasks (e.g. solar maximum power point tracking, where the sun going behind a thick cloud can 'disappear'
the charging system) are best tackled using near-natural language.
 

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
No idea why but the most important and I hope interesting attachment, the OBject diagram, which I updated on Sat 24 Oct, mysteriously disappeared. I have reattached it.
 

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
No idea why but the most important and I hope interesting attachment, the OBject diagram, which I updated on Sat 24 Oct, mysteriously disappeared. I have reattached it.
Thank you, nsaspook, BobaMosfet and bogosort for responding. Your guidance and explanations make me confident about gaining a sound understanding of C scope and linkage. I'm setting myself a parallel exercise of composing a concise but meticulous screed on how C scope and linkage, prototypes, positioning, static and extern work and can be applied as if explaining them to someone as easily confused as myself. It'll take time but when I get there, I'll submit it and be very thankful for any suggested corrections and amendments.
Sorry to be back so soon on prototypes etc. I show below the relevant draft sections of my learning screed and the rock on which I seem forever destined to ground.

C FILES
C uses computer files as convenient clearly-bounded containers for parts of a C programme.

C FUNCTIONS
C provided means to create (aka 'define') a function which could call on (aka invoke) or be called on to execute by any other function in the programme.

C FUNCTION PROTOTYPES
1. C now requires every function definition to have an accompanying 'function declaration' (aka 'function prototype'), stated in the same file and ahead of the function's definition and any calls made to it. The prototype now provides a complete 'function interface' by specifying the function's name (aka 'function identifier') and the data types used for its 'return' and 'argument (aka parameter) passing' actions. This also now allows a function to be rendered 'inaccessible from' (aka 'invisible to') all other files by adding the keyword 'static' to its definition.

2. A function in the current file can also be rendered 'accessible' (aka 'visible') from another file(s) and to calls by that file's functions by placing a copy of its prototype carrying the keyword 'extern' in the other file(s), ahead of the first call made on it.

MY PROBLEM. I fear 2 is wishful thinking. I had hoped to use leading keywords in function definitions and/or prototypes to allow access to functions in the current file from within it AND from other file(s) in which a copy of the prototype carrying some applicable leading keyword has been placed. I now fear that isn't possible and so must fall (a long way) back by leaving the many functions called on by other files as globally visible by default and rely on extending the identifiers of calling functions to include the calling OBject/file's name - to avoid ambiguities, contradictions and/or undefined behaviour with 'inter-file calls', exactly as for, albeit much more understandably, assembler. Is it really that bad?
 

nsaspook

Joined Aug 27, 2009
13,079
This is where you use header .h files to supply the needed prototypes for what you want. I'm modifying some C code to dupe needed functions on a different hardware platform.

Here is a header file for some NVRAM functions. They use a block of flash to save calibration data but the applications functions that use the NVRAM functions don't care and don't need to know about that.
I could have 'hidden' the flash variables myflash and pmyflash in the NVRAM function source file with some helper functions but I'm testing some code memory related functions so it's cleaner from the debugging point of view to have direct access.
C:
/*
* File:   imu.h
* Author: root
*
* Created on October 24, 2020, 3:05 PM
*/

#ifndef IMU_H
#define    IMU_H

#ifdef    __cplusplus
extern "C" {
#endif
#include <stddef.h>                     // Defines NULL
#include <stdbool.h>                    // Defines true
#include <stdlib.h>                     // Defines EXIT_FAILURE
#include "definitions.h"                // SYS function prototypes

#define rps    0.0174532925f  // degrees per second -> radians per second
#define NVM_STARTVADDRESS    0x9d070000  // virtual address
#define NVM_STARTPADDRESS    0x1d070000  // physical address

    extern const uint32_t myflash[256] __attribute__((section("myflash"), address(NVM_STARTVADDRESS), space(prog)));
    extern uint32_t *pmyflash;
    extern char tbuf[];

    uint32_t nvram_in(uint8_t);
    uint32_t nvram_out(void *, uint32_t);
    bool get_nvram_str(uint8_t, char *); // <<<<<<<<<<<<<<<<<<<<<<<<<
    bool set_nvram_str(uint32_t *, char *); // <<<<<<<<<<<<<<<<<<<<<<<

#ifdef    __cplusplus
}
#endif

#endif    /* IMU_H */
This header is included in a .c file that needs to use the nvram functions to check for valid calibrations data. The original system used i2c external storage but here instruction FLASH is used with WORD writes.
C:
/**
* @file imu_init.c
*
* @author Matthew Matz
*
* @version 0.5
*
* @copyright
* Copyright (C) Parallax, Inc. 2016. All Rights MIT Licensed.
*
* @brief This Propeller C library was created for the Parallax 9-axis IMU Sensor, based
* on the STMicroelectronics LSM9DS1 inertial motion sensor chip.
*/


#include "definitions.h"                // SYS function prototypes
#include "../../Utility/libsimpletools/simpletools.h"
#include "lsm9ds1.h"
#include "imu.h"

//i2c *eeBus;                                   // I2C bus ID

int __pinAG, __pinM, __pinSDIO, __pinSCL;
char __autoCalc = 0;

int imu_init(int pinSCL, int pinSDIO, int pinAG, int pinM)
{
    __pinAG = pinAG;
    __pinM = pinM;
    __pinSDIO = pinSDIO;
    __pinSCL = pinSCL;

    // Set both the Accel/Gyro and Mag to 3-wire SPI mode
    /*
     * We don't use SPI bit-banging for 3-wire because it's possible to setup a hardware 3-wire mode on the PIC32 module
     * on the pic32mk hardware SPI module jumper mosi to miso for half-duplex 3-wire mode and config for SPI MODE 3
     * set the TRIS mode on the SPI TX port pin for input so we can turn-off the transmitter voltage during SPI receive
     * on the bidirectional SPI line
     *
     * SPIxCON: SPI CONTROL REGISTER
     * bit 12 DISSDO: Disable SDOx pin bit(4)
     * 1 = SDOx pin is not used by the module. Pin is controlled by associated PORT register
     * 0 = SDOx pin is controlled by the module
     */
    TRISCbits.TRISC7 = 1; // SPI2 TX port set to port control as a input so we don't affect received data when in port mode
    imu_SPIwriteByte(__pinAG, CTRL_REG8, 0b00001100);
    imu_SPIwriteByte(__pinM, CTRL_REG3_M, 0b10000100);

    // To verify communication, we can read from the WHO_AM_I register of
    // each device. Store those in a variable so we can return them.
    char xgTest = 0, mTest = 0;
    imu_SPIreadBytes(__pinM, WHO_AM_I_M, (unsigned char *) &mTest, 1); // Read the gyro WHO_AM_I
    imu_SPIreadBytes(__pinAG, WHO_AM_I_XG, (unsigned char *) &xgTest, 1); // Read the accel/mag WHO_AM_I
    int whoAmICombined = (xgTest << 8) | mTest;

    if (whoAmICombined != ((WHO_AM_I_AG_RSP << 8) | WHO_AM_I_M_RSP)) return 0;

    //Init Gyro
    imu_SPIwriteByte(__pinAG, CTRL_REG1_G, 0xC0);
    imu_SPIwriteByte(__pinAG, CTRL_REG2_G, 0x00);
    imu_SPIwriteByte(__pinAG, CTRL_REG3_G, 0x00);
    imu_SPIwriteByte(__pinAG, CTRL_REG4, 0x38);
    imu_SPIwriteByte(__pinAG, ORIENT_CFG_G, 0x00);

    //Init Accel
    imu_SPIwriteByte(__pinAG, CTRL_REG5_XL, 0x38);
    imu_SPIwriteByte(__pinAG, CTRL_REG6_XL, 0xC0);
    imu_SPIwriteByte(__pinAG, CTRL_REG7_XL, 0x00);

    //Init Mag
    imu_SPIwriteByte(__pinM, CTRL_REG2_M, 0x00);
    imu_SPIwriteByte(__pinM, CTRL_REG4_M, 0x0C);
    imu_SPIwriteByte(__pinM, CTRL_REG5_M, 0x00);

    //Set Scales
    imu_setGyroScale(500);
    imu_setAccelScale(8);
    imu_setMagScale(12);

    //Look for calibrations in EEPROM
    char biasStamp[7] = {0};
    char mBiasStored[7] = {0};
    char aBiasStored[7] = {0};
    char gBiasStored[7] = {0};
    //  i2c_in(eeBus, 0b1010000, 63280, 2, biasStamp, 7);
    get_nvram_str(0, biasStamp); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< NVRAM FUNCTIONS
    get_nvram_str(7, mBiasStored);
    get_nvram_str(14, aBiasStored);
    get_nvram_str(21, gBiasStored);
    //  i2c_in(eeBus, 0b1010000, 63287, 2, mBiasStored, 7);
    //  i2c_in(eeBus, 0b1010000, 63294, 2, aBiasStored, 7);
    //  i2c_in(eeBus, 0b1010000, 63301, 2, gBiasStored, 7);

    if (strcmp(biasStamp, "LSM9DS1") == 0) {
        if ((mBiasStored[0] = 'm')) {
            int mxB = (mBiasStored[1] << 8) | mBiasStored[2];
            int myB = (mBiasStored[3] << 8) | mBiasStored[4];
            int mzB = (mBiasStored[5] << 8) | mBiasStored[6];

            imu_setMagCalibration(mxB, myB, mzB);
        }

        if ((aBiasStored[0] = 'a')) {
            int axB = (aBiasStored[1] << 8) | aBiasStored[2];
            int ayB = (aBiasStored[3] << 8) | aBiasStored[4];
            int azB = (aBiasStored[5] << 8) | aBiasStored[6];

            imu_setAccelCalibration(axB, ayB, azB);
        }

        if ((gBiasStored[0] = 'g')) {
            int gxB = (gBiasStored[1] << 8) | gBiasStored[2];
            int gyB = (gBiasStored[3] << 8) | gBiasStored[4];
            int gzB = (gBiasStored[5] << 8) | gBiasStored[6];

            imu_setGyroCalibration(gxB, gyB, gzB);
        }
    }



    // Once everything is initialized, return the WHO_AM_I registers we read:
    return whoAmICombined;
}


/*
* Based on the Arduino Library for the LSM9SD1 by Jim Lindblom of Sparkfun Electronics
*/

/**
* TERMS OF USE: MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
Calibration data write functions
C:
/**
* @file LSM9DS1_setCalibration.c
*
* @author Matthew Matz
*
* @version 0.5
*
* @copyright
* Copyright (C) Parallax, Inc. 2016. All Rights MIT Licensed.
*
* @brief This Propeller C library was created for the Parallax 9-axis IMU Sensor, based
* on the STMicroelectronics LSM9DS1 inertial motion sensor chip.
*/



#include "lsm9ds1.h"
#include "imu.h"
//#include "simpletools.h"

//i2c *eeBus;                                   // I2C bus ID


int __gBiasRaw[3];
int __aBiasRaw[3];
int __mBiasRaw[3];
int __pinM;
char __autoCalc;

void imu_setMagCalibration(int mxBias, int myBias, int mzBias)
{
    int k;
    __mBiasRaw[X_AXIS] = mxBias;
    __mBiasRaw[Y_AXIS] = myBias;
    __mBiasRaw[Z_AXIS] = mzBias;

    unsigned char msb, lsb;
    for (k = 0; k < 3; k++) {
        msb = (__mBiasRaw[k] & 0xFF00) >> 8;
        lsb = __mBiasRaw[k] & 0x00FF;
        imu_SPIwriteByte(__pinM, OFFSET_X_REG_L_M + (2 * k), lsb);
        imu_SPIwriteByte(__pinM, OFFSET_X_REG_H_M + (2 * k), msb);
    }

    //i2c_out(eeBus, 0b1010000, 63280, 2, "LSM9DS1", 7);
    set_nvram_str((void*) pmyflash, tbuf); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    //while (i2c_busy(eeBus, 0b1010000));
    char biasBuf[7] = {'m', 0, 0, 0, 0, 0, 0};

    for (k = 0; k < 3; k++) {
        biasBuf[k * 2 + 1] = (__mBiasRaw[k] >> 8) & 0xFF;
        biasBuf[k * 2 + 2] = __mBiasRaw[k] & 0xFF;
    }

    set_nvram_str((void*) &pmyflash[7], biasBuf);
    //i2c_out(eeBus, 0b1010000, 63287, 2, biasBuf, 7);
    //while (i2c_busy(eeBus, 0b1010000));

}

void imu_setAccelCalibration(int axBias, int ayBias, int azBias)
{
    int k;
    __aBiasRaw[X_AXIS] = axBias;
    __aBiasRaw[Y_AXIS] = ayBias;
    __aBiasRaw[Z_AXIS] = azBias;

    //i2c_out(eeBus, 0b1010000, 63280, 2, "LSM9DS1", 7);
    set_nvram_str((void*) pmyflash, tbuf);
    //while (i2c_busy(eeBus, 0b1010000));
    char biasBuf[7] = {'a', 0, 0, 0, 0, 0, 0};

    for (k = 0; k < 3; k++) {
        biasBuf[k * 2 + 1] = (__aBiasRaw[k] >> 8) & 0xFF;
        biasBuf[k * 2 + 2] = __aBiasRaw[k] & 0xFF;
    }

    set_nvram_str((void*) &pmyflash[14], biasBuf);
    //i2c_out(eeBus, 0b1010000, 63294, 2, biasBuf, 7);
    //while (i2c_busy(eeBus, 0b1010000));

    __autoCalc |= 0b10;
}

void imu_setGyroCalibration(int gxBias, int gyBias, int gzBias)
{
    int k;

    __gBiasRaw[X_AXIS] = gxBias;
    __gBiasRaw[Y_AXIS] = gyBias;
    __gBiasRaw[Z_AXIS] = gzBias;

    //i2c_out(eeBus, 0b1010000, 63280, 2, "LSM9DS1", 7);
    set_nvram_str((void*) pmyflash, tbuf);
    //while (i2c_busy(eeBus, 0b1010000));
    char biasBuf[7] = {'g', 0, 0, 0, 0, 0, 0};

    for (k = 0; k < 3; k++) {
        biasBuf[k * 2 + 1] = (__gBiasRaw[k] >> 8) & 0xFF;
        biasBuf[k * 2 + 2] = __gBiasRaw[k] & 0xFF;
    }

    set_nvram_str((void*) &pmyflash[21], biasBuf);
    //i2c_out(eeBus, 0b1010000, 63301, 2, biasBuf, 7);
    //while (i2c_busy(eeBus, 0b1010000));

    __autoCalc = 0b01;
}



/*
* Based on the Arduino Library for the LSM9SD1 by Jim Lindblom of Sparkfun Electronics
*/

/**
* TERMS OF USE: MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
The actual nvram functions and data structures are in another .c file that also imports the imu.h header.
Most of the very machine specific code ( NVMInitiateOperation and NVMWriteWord) is here and is hidden from the users of the functions.
C:
#include <stddef.h>                     // Defines NULL
#include <stdbool.h>                    // Defines true
#include <stdlib.h>                     // Defines EXIT_FAILURE
#include "definitions.h"                // SYS function prototypes
#include "imu.h"
#include "../../../../../../Learn-Folder-Updated-2020.03.02/Learn/Simple Libraries/Sensor/liblsm9ds1/lsm9ds1.h"
#include "../pic32mk_mcj_curiosity_pro.X/MadgwickAHRS/MadgwickAHRS.h"
#include "../pic32mk_mcj_curiosity_pro.X/MahonyAHRS/MahonyAHRS.h"

const uint32_t myflash[256] __attribute__((section("myflash"), address(NVM_STARTVADDRESS), space(prog)));
uint32_t *pmyflash = (uint32_t *) NVM_STARTPADDRESS;

//  code frags of flash nvram routines
static void NVMInitiateOperation(void)
{
    unsigned int saved_state;
    int dma_susp; // storage for current DMA state

    saved_state = __builtin_get_isr_state();
    __builtin_disable_interrupts();
    // Disable DMA
    if (!(dma_susp = DMACONbits.SUSPEND)) {
        DMACONSET = _DMACON_SUSPEND_MASK; // suspend
        while ((DMACONbits.DMABUSY)); // wait to be actually suspended
    }
    NVMKEY = 0x0;
    NVMKEY = 0xAA996655;
    NVMKEY = 0x556699AA;
    NVMCONSET = 1 << 15; // must be an atomic instruction
    // Restore DMA
    if (!dma_susp) {
        DMACONCLR = _DMACON_SUSPEND_MASK; // resume DMA activity
    }
    // Restore Interrupts
    __builtin_set_isr_state(saved_state); /* Set back to what was before. */
}

static unsigned int NVMWriteWord(void* address, unsigned int data)
{
    unsigned int res = 0;
    // Load data into NVMDATA register
    NVMDATA0 = data;
    // Load address to program into NVMADDR register
    NVMADDR = (unsigned int) address;
    // Unlock and Write Word
    // set the operation, assumes WREN = 0
    NVMCONbits.NVMOP = 0x1; // NVMOP for Word programming
    // Enable Flash for write operation and set the NVMOP
    NVMCONbits.WREN = 1;
    // Start programming
    NVMInitiateOperation(); // see Example 52-1
    // Wait for WR bit to clear
    while (NVMCONbits.WR);
    // Disable future Flash Write/Erase operations
    NVMCONbits.WREN = 0;
    // Check Error Status
    if (NVMCON & 0x3000) // mask for WRERR and LVDERR
    {
        res = 1; // TESTING CLEAR POSSIBLE ERROR set 0
    }
    return res;
}

/*
* read data from the virtual program address of the nvram variable
*/
uint32_t nvram_in(uint8_t adr)
{
    return myflash[adr];
}

/*
* write data to the physical address of the nvram variable
* using the flash unlock sequence
*/
uint32_t nvram_out(void *adr, uint32_t data)
{
    return NVMWriteWord((void*) adr, data);
}

/*
* a few calibrations data specific sized read/write routine
*/

bool get_nvram_str(uint8_t adr, char * str)
{
    bool done = true;
    uint8_t sz = 7;

    while (sz--) {
        str[sz] = (char) nvram_in(adr + sz);
    }
    return done;
}

bool set_nvram_str(uint32_t * adr, char * str)
{
    bool done = true;
    uint8_t sz = 7;

    while (sz--) {
        if ((done = NVMWriteWord((void *) &adr[sz], (uint32_t) str[sz]))) {
            break; // stop on error
        }
    }
    return done;
}
The read/write functions are lightly tested just for functionality for now.
 
Last edited:

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
Attached are my C source files (composed in C-configured Notepad++, 115 columns wide, tabs to 4), unfinished but stable in form (their functions' code is unimportant here), and a diagram illustrating the tight cooperation between OBject pair, PowerManager and PowerMonitor, only children of PowerManagement (unshown) which heads their family tree's branch and is one of head OBject LoneStar's several children.

On every (15.6msec) programme pass, parent Power Management commands PowerMonitor to update its estimate of the host's battery charge level (using 'coulomb counting') and to record whether the battery charging rate is lower than on the previous pass, then leaves PowerManager to manage battery loading and charging by reading and changing the host hardware's state through I/O OBject InterfaceWithHost (which, as a partly shared resource, is still mulling its own nature and looking for a home, perhaps on another plane). Initialisations are not shown.

After first drawing each OBject at an apex of an equilateral triangle I noticed that PowerManager's commands for changing and the replies to its questioning of the host's state, through InterfaceWithHost, both flowed clockwise around an information-carrying 'wheel' which rotates several times before handing control back to parent PowerManagement. I wondered whether I'd re-invented the wheel and/or a Turing machine had bowed into the picture. The OBject method seems, often and unasked, to reveal patterns, yield intriguing insights and hint at mathematical possibilities.

PowerManager provides core functionality yet has only a single (1bit) variable (to meet real-time needs). That PowerManager's operations could become a procedure which could also absorb PowerMonitor prompted a re-appraisal of the OBject-based approach, yielding further insights: by clearly showing the nature of and cooperations between its OBjects and the highways (the antithesis of spaghetti) followed by required operations, an OBject diagram can greatly help evolve a programme or section's high-level organisation. The flow diagrams of procedures seem more to centre on operational minutiae, often mix operations of different types (e.g. state-changing and state-testing) but help the coding of operations. Procedures, including those inside OBjects, can be rendered relatively systematic but, IMHO, can't, in themselves, deliver OBject levels of clarity. Open to other views but must get back to understanding prototypes and their baggage.
Seems time I replied to my own post and lest you think my ideas 'sophomoric' (see Merriam-Webster's scathing definition), will approach it from a fresh angle: as if introducing another coder to a C programme I'd created but which they might later be asked to extend or modify.

I know you write sound functions and make good use of data structures and believe you'll find this easy; more a re-arrangement than anything new. Let's look at my programme section PowerManagement which mainly manages the loading and (solar) charging of the host's NiMH battery, a service that could have been done by one C function. These two .c source files, PowerManager and PowerMonitor, share that same common aim but their first few sections may be unfamiliar. Each file represents what I call an OBject that typically comprises data and operations, making the programme design 'object-based' but free of OOP complication.

The host's battery is managed by OBject PowerManager's principal function PowerManager_ManageBattery() which can change the state of the host hardware. Let's take the function's sub-action that limits the maximum battery charging current by adjusting the control register value of the on-chip DAC so as to change the voltage conversion ratio of the host's (solar panel-to-battery) DC-DC converter. To do this, PowerManager first requires a simple (Yes/No) answer to the question "Is the battery current above its allowed maximum?", effected by using the on-chip A-D convertor to sense the battery current, comparing the result to specification constant MAXIMUM_ALLOWED_BATTERY_CHARGING_kRATE then returning a Yes/No result. OBject PowerMonitor was created to harbour just such lengthy, I/O-involving, state-sensing chains, and greatly simplifies the task of PowerManager_ManageBattery(). Once created, PowerManager and PowerMonitor quickly assumed their own stable natures, reflecting their distinct yet cooperative duties.

The sections headed REQUIRED OPERATIONS and PROVIDED OPERATIONS respectively list the prototypes of all the function calls made on and by other OBjects' files. In contrast to their normally being out of sight in header files or buried inside functions, they promote a constant awareness of 'message flows', are easily updated and help avoid 'spaghetti'.

I don't think I can add anything significant to that. I've got work to do, most notably on static and extern (now wading through 'The C book' PDF) but have become less worried about 'hiding' as the ready visibility of all function calls and my preference for comprehensive identifiers makes collisions unlikely. Always glad to hear your views.
 

nsaspook

Joined Aug 27, 2009
13,079
Seems time I replied to my own post and lest you think my ideas 'sophomoric' (see Merriam-Webster's scathing definition), will approach it from a fresh angle: as if introducing another coder to a C programme I'd created but which they might later be asked to extend or modify.

I know you write sound functions and make good use of data structures and believe you'll find this easy; more a re-arrangement than anything new. Let's look at my programme section PowerManagement which mainly manages the loading and (solar) charging of the host's NiMH battery, a service that could have been done by one C function. These two .c source files, PowerManager and PowerMonitor, share that same common aim but their first few sections may be unfamiliar. Each file represents what I call an OBject that typically comprises data and operations, making the programme design 'object-based' but free of OOP complication.

The host's battery is managed by OBject PowerManager's principal function PowerManager_ManageBattery() which can change the state of the host hardware. Let's take the function's sub-action that limits the maximum battery charging current by adjusting the control register value of the on-chip DAC so as to change the voltage conversion ratio of the host's (solar panel-to-battery) DC-DC converter. To do this, PowerManager first requires a simple (Yes/No) answer to the question "Is the battery current above its allowed maximum?", effected by using the on-chip A-D convertor to sense the battery current, comparing the result to specification constant MAXIMUM_ALLOWED_BATTERY_CHARGING_kRATE then returning a Yes/No result. OBject PowerMonitor was created to harbour just such lengthy, I/O-involving, state-sensing chains, and greatly simplifies the task of PowerManager_ManageBattery(). Once created, PowerManager and PowerMonitor quickly assumed their own stable natures, reflecting their distinct yet cooperative duties.

The sections headed REQUIRED OPERATIONS and PROVIDED OPERATIONS respectively list the prototypes of all the function calls made on and by other OBjects' files. In contrast to their normally being out of sight in header files or buried inside functions, they promote a constant awareness of 'message flows', are easily updated and help avoid 'spaghetti'.

I don't think I can add anything significant to that. I've got work to do, most notably on static and extern (now wading through 'The C book' PDF) but have become less worried about 'hiding' as the ready visibility of all function calls and my preference for comprehensive identifiers makes collisions unlikely. Always glad to hear your views.
I love your ideas. I have a similar application from a few years ago. It's a spaghetti nightmare because it evolved over years and new hardware and ideas were being tested. It's been working for 10 years so most of the killer bugs have been stomped.

https://github.com/nsaspook/mbmc/blob/new_solar/swm8722/solar18f8722/design.txt
https://github.com/nsaspook/mbmc/blob/new_solar/swm8722/solar18f8722/mbmc_defs.h
https://github.com/nsaspook/mbmc/tree/new_solar/swm8722/solar18f8722

I started a new monitor device recently for a 24vdc system.
https://github.com/nsaspook/vtouch_v2/tree/mbmc
https://forum.allaboutcircuits.com/...c-controlled-battery-array.32879/post-1513872
 
in one FORTRAN program I wrote, the call structure mimiced a "device driver" . All of the functions were in one piece of code, so it had, init, display, log, write defaults, read defaults etc.

The init file was kinda cool because you could "back-up" a line read. "Devices" could have multiple units, so the read defaults main routine, dispatched it to the read device type x routine. That device would read all of the defaults it needs for all of the unit numbers and then set the read pointer to the previous line. It would go back to the main routine and dispatch again. So, defaults were not read by position.

Later with LabView, I made a data file that was extensible in the sense that variables had names and the x,y data was pointed too. When I managed the design, the programmer I was managing had no instrumentation. He wrote the code with simulated instruments.

In the end, if the instruments were not connected, the program was capable of entering simulation mode.
I wrote the routines that talked to the instrumentation. I had to do things in that code to make it fast, because of management's decisions.

Neither of us knew Labview when we started.

For the TS, labView is the defacto language to control instrumentation. The manuafacturers's generally create the VI's virtual instruments or "device drivers". Unfortunately, you can;t go back to a lower version.

The language is totally visual like writing a schematic. You can EASILY write parallel processing which means "race-conditions" are possible. The vi executes when all of the "data" is available.

Since I used the earliest version of Labview it was not very feature rich, nor could it handle errors well. later, they invented an "error cluster" which you had to have in every vi and had to programthat if an error occurred, the vi would exit until you reached the final error handler.

the language is available for free for non-commercial use.
 

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
Another thing for those that like pretty pictures of their programming ideas. Most modern IDE's will generate call graphs from program code.

Ah yes and again thank you nsaspook, I now recall seeing but delaying comprehending that facility in, I think, MPLAB. I'll touch on its context in a post soon.
 

Thread Starter

Hugh Riddle

Joined Jun 12, 2020
78
Seems time I replied to my own post and lest you think my ideas 'sophomoric' (see Merriam-Webster's scathing definition), will approach it from a fresh angle: as if introducing another coder to a C programme I'd created but which they might later be asked to extend or modify.

I know you write sound functions and make good use of data structures and believe you'll find this easy; more a re-arrangement than anything new. Let's look at my programme section PowerManagement which mainly manages the loading and (solar) charging of the host's NiMH battery, a service that could have been done by one C function. These two .c source files, PowerManager and PowerMonitor, share that same common aim but their first few sections may be unfamiliar. Each file represents what I call an OBject that typically comprises data and operations, making the programme design 'object-based' but free of OOP complication.

The host's battery is managed by OBject PowerManager's principal function PowerManager_ManageBattery() which can change the state of the host hardware. Let's take the function's sub-action that limits the maximum battery charging current by adjusting the control register value of the on-chip DAC so as to change the voltage conversion ratio of the host's (solar panel-to-battery) DC-DC converter. To do this, PowerManager first requires a simple (Yes/No) answer to the question "Is the battery current above its allowed maximum?", effected by using the on-chip A-D convertor to sense the battery current, comparing the result to specification constant MAXIMUM_ALLOWED_BATTERY_CHARGING_kRATE then returning a Yes/No result. OBject PowerMonitor was created to harbour just such lengthy, I/O-involving, state-sensing chains, and greatly simplifies the task of PowerManager_ManageBattery(). Once created, PowerManager and PowerMonitor quickly assumed their own stable natures, reflecting their distinct yet cooperative duties.

The sections headed REQUIRED OPERATIONS and PROVIDED OPERATIONS respectively list the prototypes of all the function calls made on and by other OBjects' files. In contrast to their normally being out of sight in header files or buried inside functions, they promote a constant awareness of 'message flows', are easily updated and help avoid 'spaghetti'.

I don't think I can add anything significant to that. I've got work to do, most notably on static and extern (now wading through 'The C book' PDF) but have become less worried about 'hiding' as the ready visibility of all function calls and my preference for comprehensive identifiers makes collisions unlikely. Always glad to hear your views.
After posting my 29 Oct ideas, I had expected to go quiet, while I caught up with C basics and worked on PowerManager_ManageBattery(). But the 'method' typically somehow drew my attention to some probably very significant aspects: that the OBjects PowerManager and PowerMonitor are respectively in clear 'master-slave' and 'client-server' relationships with the host hardware's state (both via I/O OBject InterfaceWithHost), also shifting the context into an area where inter-entity communications (in this case, C function calls and returns) are considered worthy of special attention.

Once again the OBject method has presented what has to be a stimulating insight. I was prepared to dismiss the earlier 'flow wheel' revelation, reported in my 23 Oct post as a curiosity, but now find it a significant view of the two OBjects' strong 'relationship', itself a 'thing' potentially as important as those OBjects. TBH, the method sometimes daunts me; fingers crossed it won't nudge me to confangle some new type of object: a 'relator'?

The method appears to endorse keeping all function prototypes in plain view but, with communications now more prominent, I also want easily to view which external OBjects (source files) are calling which Provided (current file) Operations. I fear C can't help and am reluctant to plunge into '#define land'.

I hope this conveys something of why I find the OBject-based method so helpful and stimulating.
 

nsaspook

Joined Aug 27, 2009
13,079
After posting my 29 Oct ideas, I had expected to go quiet, while I caught up with C basics and worked on PowerManager_ManageBattery(). But the 'method' typically somehow drew my attention to some probably very significant aspects: that the OBjects PowerManager and PowerMonitor are respectively in clear 'master-slave' and 'client-server' relationships with the host hardware's state (both via I/O OBject InterfaceWithHost), also shifting the context into an area where inter-entity communications (in this case, C function calls and returns) are considered worthy of special attention.

Once again the OBject method has presented what has to be a stimulating insight. I was prepared to dismiss the earlier 'flow wheel' revelation, reported in my 23 Oct post as a curiosity, but now find it a significant view of the two OBjects' strong 'relationship', itself a 'thing' potentially as important as those OBjects. TBH, the method sometimes daunts me; fingers crossed it won't nudge me to confangle some new type of object: a 'relator'?

The method appears to endorse keeping all function prototypes in plain view but, with communications now more prominent, I also want easily to view which external OBjects (source files) are calling which Provided (current file) Operations. I fear C can't help and am reluctant to plunge into '#define land'.

I hope this conveys something of why I find the OBject-based method so helpful and stimulating.
I'll let you on to a little secret. Everyone uses 'the OBject-based method' as you have described when they become familiar with the C language and intuitively develop a mental picture of the program that needs be created. It might not look that way at first because functionality is prime but when you refactor for style points it naturally flows into structured methods.

I'm not fond of using the CPP #define for function macros because eventually it becomes an additional mini language hidden (X really means Y) feature instead of a C helper.
https://gcc.gnu.org/onlinedocs/cpp/Macro-Arguments.html

or using #define for simple computation (vs symbolic) 'magic numbers'.
Using the const keyword for fixed variables (there are times when #define must be used) adds C scope structure to programs and results in better compile time error checking with static range type-safety.
C:
#ifndef MAGIC_H
#define    MAGIC_H

#ifdef    __cplusplus
extern "C" {
#endif

static const double rps = 0.0174532925f; // degrees per second -> radians per second
static const uint8_t CAL_DIS_MS = 1; // calibration data element screen display time
static const char *build_date = __DATE__, *build_time = __TIME__;
static const char imu_missing[] = " MISSING \r\n";
/*
 * NVRAM storage page variable
 * const volatile here means it's stored in FLASH for read-only program data access in normal program flow but
 * also set to volatile because the FLASH write controller can modify the contents out of normal program flow
 * so we need the compiler TO NOT optimize out reads from the actual memory locations because of read-only data caching
 * optimizations
 */
#include "nvram_page.h"
const volatile uint32_t myflash[4096] __attribute__((section("myflash"), space(prog), address(NVM_STARTVADDRESS)));

#ifdef    __cplusplus
}
#endif

#endif    /* MAGIC_H */
 
Top