Close

Persistent Settings

A project log for Careless WSPR

A desultorily executed Weak Signal Propagation Reporter beacon.

ziggurat29ziggurat29 08/11/2019 at 21:120 Comments

Summary

A simple means of persisting settings across boots is realized.  A Flash resource crisis has manifested.

Deets

Many projects need to have settings that are persistent across boots.  In this case, at a minimum is the setting that contains the operator call sign, since that can't be gleaned from the environment.  Practically, several other persistent settings will exist, such as the Transmit Power level, the frequency on which to operate, the bit rate of the GPS serial port, etc.

The STM32F103 processor does not have any EEPROM resources, but this is emulated by using the last flash page as the persistent store.  Essentially, the settings are defined in a struct, and this struct is persisted to that flash page.  There are some defaults that are defined if the page is found to be empty.

The settings presently defined are:

typedef struct
{
    uint32_t    _version;    //should always be first, should be PERSET_VERSION

    //'dial' frequency for the WSPR channel.  WSPR works in a USB narrow
    //(200 Hz) band within a conventional USB channel.  The center of that
    //200 Hz band is 1.5 KHz above the dial frequency.
    uint32_t    _dialFreqHz;
    //the WSPR signal is extremely narrow-band (6 Hz).  The 200 Hz WSPR band
    //can accommodate 33 1/3 of these 6 Hz sub-bands.  We can be configured to
    //use a specific one, or a negative number means randomly pick one at
    //transmit time (the usual case).
    int32_t     _nSubBand;        //0-32; or < 0 to randomize
    //duty cycle (i.e. how often to try to transmit, randomized)
    uint32_t    _nDutyPtc;        //percent

    //call sign
    char        _achCallSign[8];  //6 chars max
    //explicit grid locator
    char        _achMaidenhead[4];    //4 chars always
    //transmit power level
    int32_t     _nTxPowerDbm;    //0-60, though only 0, 1, 3, 7 endings

    //use GPS (i.e. auto time sync auto grid locator, and wait-for-lock)
    uint32_t    _bUseGPS;        //boolean
    //GPS bit rate
    int32_t     _nGPSbitRate;    //9600 default, but can be other

} PersistentSettings;

The defaults are:

const PersistentSettings g_defaultSettings = 
{
    ._version = PERSET_VERSION,    //must be this
    ._dialFreqHz = 14095600,       //the 20-meter conventional WSPR channel
    ._nSubBand = -1,
    ._nDutyPtc = 20,
    ._achCallSign = "",            //you must set this
    ._achMaidenhead = "",          //you must set this
    ._nTxPowerDbm = 20,            //100 mW
    ._bUseGPS = 1,
    ._nGPSbitRate = 9600,          //default for the ublox NEO-6M
};

The gist is that there is a RAM copy of the settings that the program operates off of.  Early in the execution of the program (in main()), this RAM copy is initialized from the persistent copy.  If there is no persistent copy, then it is initialized from the baked-in defaults.

The settings may be persisted by writing the struct to the last page (1 KiB on this device) of flash.  An elementary form of wear-leveling is done to reduce the likelihood of wearing out the flash.  This works by sequentially writing updates into the flash.  Since the erased state of the flash is to cause all values to be 0xff, this is easy to detect.  Initial depersistence involves walking through the memory forward to find the /last/ structure that has a valid version number.  This is effectively the value of the flash settings.  Similarly, persistence means walking through the memory to find the first structure that has the 0xffffffff version number.  That will be where the new copy is written.  If the page is full when trying to write then it will be erased first.  As it stands, this will reduce flash erasures by 25 x.  If the structure grows, this will become less effective, but it is also straightforward to add more pages, if needed.

Some test code was put in main.c to repeatedly write structures to verify the functionality.  You can use the 'STM32 ST-LINK Utility' to directly view the flash page.

The command processor was updated to include a 'set' command that if used by itself will dump the present settings values, and can be used to alter each of the settings.  This only alters the RAM copy of the settings.  There is a separate command 'persist' that will write those settings to flash when you get them dialed in the way you want to.  Similarly, there is a command 'depersist' to explicitly re-load them from flash.  The main() function was altered to do a depersist operation once early during program start up to initialize the RAM copy.

However, now we are in a state of crisis with flash usage.  I've been keeping an eye on it for the past few builds, and the 'maidenhead' feature took the flash size to 59188 over previously 57540, for 1648 bytes.  This persistent settings feature took the flash size to 63588, for 4400 bytes usage.  Now there are 65536 - 63588 = 1948 bytes of flash left, and moreover the persistent settings are on the last 1 KiB page, so really that's just 924 bytes left!  And we haven't even begun to support the synthesizer chip, implement the WSPR encoder, or the WSPR task.  I don't think all of that will fit in 924 bytes, so I need to address this issue now.

Next

Addressing the flash crisis

Discussions