This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC2340R5: NVS initialization at compiling/linking/FW download time

Part Number: CC2340R5
Other Parts Discussed in Thread: SYSCONFIG,

HI,

the title might be a little misleading, however the issue I have is similar to the one found here:

https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/1107424/cc1312r7-placing-a-struct-in-memory-shared-by-the-nvs-driver

My goal: to store some settings (I only need six bytes to store my structs) in NVS, so that they can be changed at runtime, but want to initialize them at compile/link/FW load time, not at boot time (i.e. the values that have been changed by the user should be preserved even if the device shuts off completely due to a power failure and reboots when power is available again). I'm also trying to follow a design pattern that I have successfully used with the MSP430 and its information memory, in order to keep coherence in my framework, which targets both platforms, both in its bare-metal version and in its OS-based one (mandatory for the CC2340 using BLE).

Basically I would like to define an NVS instance/region in Sysconfig, so that I can use the recommended NVS APIs at runtime. Then, I would like to use the TI Clang compiler/linker attributes to initialize the values when flashing the device. Disclaimer: I have read all the available documentation and searched e2e across all devices/forums, the closest thread that I found was the one linked above, however there was no definitive answer from the TIer and the user reverted to a workaround that doesn't solve the issue at all.

Here's what I try and what happens:

This is my valid code (tested working when not creating the NVS in Sysconfig, as exposed in the memory browser with the expected endianness, I'm using uint32_t types for better compatibility with this core, as opposed as uint16_t types in the MSP430):

#DEFINE SEGMENT_D 0x7F800 // (address chosen because it's at the last 0x800 segment at the top of the flash, correct me if I'm wrong that this is the minimum amount of memory for an NVS instance)

const uint32_t setting_a __attribute__((persistent, location (SEGMENT_D + 0), used)) = 0xdead;

const uint32_t setting_b __attribute__((persistent, location (SEGMENT_D + 4), used)) = 0xdeaf;

const uint32_t setting_c __attribute__((persistent, location (SEGMENT_D + 8), used)) = 0xbeef;

1) If I add a GENERATED NVS instance in Sysconfig at a different base address (in this case 0x7f000, with a 0x800 sector), this is what I get after compiling/linking/loading:

MEMORY BROWSER:

0x000 7F800 setting_a
0x000 7F800 0xAD 0xDE 0x00 0x00
0x000 7F804 setting_b
0x000 7F804 0xAF 0xDE 0x00 0x00
0x000 7F808 setting_c
0x000 7F808 0xEF 0xBE 0x00 0x00

MEMORY MAP:

0007f000 0007f000 00000800 00000000 rw-
0007f000 0007f000 00000800 00000000 rw- .TI.bound:flashBuf0 // The NVS instance
0007f800 0007f800 00000004 00000004 rw-
0007f800 0007f800 00000004 00000004 rw- .TI.bound:setting_a
0007f804 0007f804 00000004 00000004 rw-
0007f804 0007f804 00000004 00000004 rw- .TI.bound:setting_b
0007f808 0007f808 00000004 00000004 rw-
0007f808 0007f808 00000004 00000004 rw- .TI.bound:setting_c

2) If I add a POINTER NVS instance in Sysconfig at a different base address (in this case 0x7f000, with a 0x800 sector), this is what I get after compiling/linking/loading:

The memory browser is unchanged (as expected), while the memory map shows no entry at 0007f000 (also expected).

HOWEVER, IF

1) I create a GENERATED NVS instance at the same 0x7f800 address, it won't compile/link/load because I get an:

#10010 errors encountered during linking; "test_01.out" not built test_01 C/C++ Problem
<a href="file:/home/disegni/ti/ccs1230/ccs/tools/compiler/dmed/HTML/10099.html">#10099-D</a> program will not fit into available memory, or the section contains a call site that requires a trampoline that can't be generated for this section. run placement with alignment fails for section ".TI.bound:flashBuf0" size 0x800, overlaps with ".TI.bound:seting_a", size 0x4 (page 0) test_01 C/C++ Problem
gmake: *** [all] Error 2 test_01 C/C++ Problem
gmake[1]: *** [Esachron_02.out] Error 1 test_01 C/C++ Problem
recipe for target 'all' failed makefile /test_01/Debug line 246 C/C++ Problem
recipe for target 'test_01.out' failed makefile /test_01/Debug line 250 C/C++ Problem

2) I create a POINTER NVS instance at the same 0x7f800 address, it does compile/link/load but this is what I see in the memory browser:

MEMORY BROWSER:

0x000 7F800 setting_a
0x000 7F800 0xFE 0xE0 0x0F 0x96
0x000 7F804 setting_b
0x000 7F804 0xFF 0xFF 0xFF 0x96
0x000 7F808 setting_c
0x000 7F808 0xFF 0xFF 0xFF 0x96

This behavior occurs no matter which base address I choose (of course they must be aligned to a 2048 page boundary).

Looking at the NVS section of the generated ti_drivers_config.c (when I use different base addresses), I find the following, for the different options:

1) GENERATED NVS at different base address (notice the initial comment, which is not present in the following POINTER generated file):

/*
* =============================== NVS ===============================
*/

#include <ti/drivers/NVS.h>
#include <ti/drivers/nvs/NVSLPF3.h>

/*
* NVSLPF3 Internal NVS flash region definitions
*
* Place uninitialized char arrays at addresses
* corresponding to the 'regionBase' addresses defined in
* the configured NVS regions. These arrays are used as
* place holders so that the linker will not place other
* content there.
*
* For GCC targets, the char arrays are each placed into
* the shared ".nvs" section. The user must add content to
* their GCC linker command file to place the .nvs section
* at the lowest 'regionBase' address specified in their NVS
* regions.
*/

#if defined(__TI_COMPILER_VERSION__) || defined(__clang__)

static char flashBuf0[0x800] __attribute__ ((retain, noinit, location(0x7f000)));

#elif defined(__IAR_SYSTEMS_ICC__)

__no_init static char flashBuf0[0x800] @ 0x7f000;

#elif defined(__GNUC__)

__attribute__ ((section (".nvs")))
static char flashBuf0[0x800];

#endif

NVSLPF3_Object NVSLPF3_objects[1];

static const NVSLPF3_HWAttrs NVSLPF3_hwAttrs[1] = {
/* CONFIG_NVS_0 */
{
.regionBase = (void *) flashBuf0,
.regionSize = 0x800
},
};

#define CONFIG_NVS_COUNT 1

const NVS_Config NVS_config[CONFIG_NVS_COUNT] = {
/* CONFIG_NVS_0 */
{
.fxnTablePtr = &NVSLPF3_fxnTable,
.object = &NVSLPF3_objects[0],
.hwAttrs = &NVSLPF3_hwAttrs[0],
},
};

const uint_least8_t CONFIG_NVS_0_CONST = CONFIG_NVS_0;
const uint_least8_t NVS_count = CONFIG_NVS_COUNT;

***************************************************************************************

2) POINTER NVS at different base address:

/*
* =============================== NVS ===============================
*/

#include <ti/drivers/NVS.h>
#include <ti/drivers/nvs/NVSLPF3.h>

NVSLPF3_Object NVSLPF3_objects[1];

static const NVSLPF3_HWAttrs NVSLPF3_hwAttrs[1] = {
/* CONFIG_NVS_0 */
{
.regionBase = (void *) 0x7f000,
.regionSize = 0x800
},
};

#define CONFIG_NVS_COUNT 1

const NVS_Config NVS_config[CONFIG_NVS_COUNT] = {
/* CONFIG_NVS_0 */
{
.fxnTablePtr = &NVSLPF3_fxnTable,
.object = &NVSLPF3_objects[0],
.hwAttrs = &NVSLPF3_hwAttrs[0],
},
};

const uint_least8_t CONFIG_NVS_0_CONST = CONFIG_NVS_0;
const uint_least8_t NVS_count = CONFIG_NVS_COUNT;

***************************************************************************************

3) POINTER NVS at same base address is identical and looks like the preferred option with no buffer allocated, but unusable since the memory is initialized automatically:

/*
* =============================== NVS ===============================
*/

#include <ti/drivers/NVS.h>
#include <ti/drivers/nvs/NVSLPF3.h>

NVSLPF3_Object NVSLPF3_objects[1];

static const NVSLPF3_HWAttrs NVSLPF3_hwAttrs[1] = {
/* CONFIG_NVS_0 */
{
.regionBase = (void *) 0x7f800,
.regionSize = 0x800
},
};

#define CONFIG_NVS_COUNT 1

const NVS_Config NVS_config[CONFIG_NVS_COUNT] = {
/* CONFIG_NVS_0 */
{
.fxnTablePtr = &NVSLPF3_fxnTable,
.object = &NVSLPF3_objects[0],
.hwAttrs = &NVSLPF3_hwAttrs[0],
},
};

const uint_least8_t CONFIG_NVS_0_CONST = CONFIG_NVS_0;
const uint_least8_t NVS_count = CONFIG_NVS_COUNT;

***************************************************************************************

So, I ask myself, where and how is it that, in the case I choose the POINTER NVS at the same address, the memory is not initialized the way I want, with my defaults at FW load time. Is there an obscure option within the linker or flashing tool (or CCS)? If those non 0xFF values are some NVS control structs, why isn't the driver initializing them at a later stage, after the first usage of the instance?

Can you please point me to a solution or at least to a workaround that is not too quick and dirty and hard to learn for the casual user, since I'm trying to use this functionality in the context of my framework.

Apologies if I'm missing something so self-evident or if I'm doing something completely wrong, but I'm new (like almost everyone else), to this excellent platform.

Thank you for your help,

kind regards

Stefano

  • Hi Stefano,

    I apologize in advance as I am likely not understanding your use case despite your best efforts.  First, I set the SysConfig -> NVS module as so:

    Then I added the persistent variables to my application code:

    const uint32_t __attribute__((persistent, location(0x7F000), used)) setting_a = 0xdead;
    const uint32_t __attribute__((persistent, location(0x7F004), used)) setting_b = 0xdeaf;
    const uint32_t __attribute__((persistent, location(0x7F008), used)) setting_c = 0xbeef;

    Here is the map file after building:

    0007f000 0007f000 00000004 00000004 rw-
    0007f000 0007f000 00000004 00000004 rw- .TI.bound:setting_a
    0007f004 0007f004 00000004 00000004 rw-
    0007f004 0007f004 00000004 00000004 rw- .TI.bound:setting_b
    0007f008 0007f008 00000004 00000004 rw-
    0007f008 0007f008 00000004 00000004 rw- .TI.bound:setting_c

    And this is the CCS Debugger Memory Map after loading:

    0x0007F000 setting_a
    0x0007F000 0000DEAD
    0x0007F004 setting_b
    0x0007F004 0000DEAF
    0x0007F008 setting_c
    0x0007F008 0000BEEF 

    Can you please clarify how these results are different from your expectations?

    Regards,
    Ryan

  • Ryan,

    first of all I want to thank you for your incredibly quick and helpful reply. Even though it's very late at night here, I simply couldn't go to sleep without investigating further. I was slightly inaccurate in my explanation, when I described my Sysconfig settings, maybe a bit deceived by two factors (and that I, unlike you, have not pasted my Sysconfig screenshot):

    1) the 0x800 sector size that is greyed out (hardware dependent) and the region size, which I (wrongly) gave for granted as being understood to be 0x800 when I mentioned I was using only one sector;

    2) the fact that I wanted to use the minimum possible amount of flash, at its very end.

    So, basically, the only difference between our two setups is that I had set the region size to 0x800 (with region base 0x7F800), while you set it to 0x1000 (with region base 0x7F000).

    My understanding of region vs. sector could be flawed (I considered the region as a superset of multiple fixed-size 0x800/2048 aligned sectors) and the region base where the first (and only, in my original intentions) sector starts. Is this correct? What I still find puzzling is that even if I keep the region base at 0x7F800 and set the region size to 0x1000 (double my original setting), the outcome in the debugger/memory browser is indeed the (expected) one that you show in your reply (the same as if I initialize my defaults at a different address from the NVS region base), but wouldn't this setting overflow the flash boundary, according to the memory map (this is clearly impossible, so it's likely there is something wrong in my reasoning)? By the way, in this case the memory map I get is completely unaffected (expected with the POINTER NVS option, that doesn't affect it) as you can see:

    0007f800    0007f800    00000004   00000004    rw- .TI.bound:setting_a
    0007f804    0007f804    00000004   00000004    rw-
    0007f804    0007f804    00000004   00000004    rw- .TI.bound:setting_b
    0007f808    0007f808    00000004   00000004    rw-
    0007f808    0007f808    00000004   00000004    rw- .TI.bound:setting_c
    20000400    20000400    00000010   00000000    rw-
    20000400    20000400    00000010   00000000    rw- .TI.bound:dmaChannel0ControlTableEntry

    The Sysconfig-generated ti_drivers_config.c file however, in its NVS section does include what seem to me incorrect values (i.e. potential flash overflow due to region size), is this behavior correct or should Sysconfig check before generating? Would NVS_open() fail in this case?:

    /*
     *  =============================== NVS ===============================
     */

    #include <ti/drivers/NVS.h>
    #include <ti/drivers/nvs/NVSLPF3.h>

    NVSLPF3_Object NVSLPF3_objects[1];

    static const NVSLPF3_HWAttrs NVSLPF3_hwAttrs[1] = {
        /* CONFIG_NVS_0 */
        {
            .regionBase = (void *) 0x7f800,
            .regionSize = 0x1000
        },
    };

    #define CONFIG_NVS_COUNT 1

    const NVS_Config NVS_config[CONFIG_NVS_COUNT] = {
        /* CONFIG_NVS_0 */
        {
            .fxnTablePtr = &NVSLPF3_fxnTable,
            .object = &NVSLPF3_objects[0],
            .hwAttrs = &NVSLPF3_hwAttrs[0],
        },
    };

    const uint_least8_t CONFIG_NVS_0_CONST = CONFIG_NVS_0;
    const uint_least8_t NVS_count = CONFIG_NVS_COUNT;

    **********************************************************************

    Going back to your code, even if you keep your region base at 0x7F000, if you set your region size to 0x800 (something that is probably incorrect to do), do you also get my (unexpected and wrong) result of that flawed initialization in the memory browser (I tried it and it does)? i.e.:

    0x000 7F000 setting_a
    0x000 7F000 0xFE 0xE0 0x0F 0x96
    0x000 7F004 setting_b
    0x000 7F004 0xFF 0xFF 0xFF 0x96
    0x000 7F008 setting_c
    0x000 7F008 0xFF 0xFF 0xFF 0x96

    The empirical lesson I get from all of this is that the region size must be (at least) twice as large as the sector size (perhaps even despite the above mentioned overflow), to achieve the intended result.

    In all honesty, I only examined the TI ARM Clang Compiler User Manual and Migration guide documentation and the NVS driver headers and implementation (which shouldn't be operational at this stage, though), but, clearly, I missed this bit. The NVS.h File reference, in its initial overview, however, only states:

    "Each NVS object is used to manage a region of non-volatile memory. The size of the region is specified in the device specific driver's hardware attributes. A sector is the smallest unit of non-volatile storage that can be erased at one time. The size of the sector, or sector size, is hardware specific and may be meaningless for some non-volatile storage hardware. For flash memory devices, the region must be aligned with the sector size. That is, the region must start on a sector boundary. Additionally, the overall size of the region must be an integer multiple of the sector size."

    So, from all of the above, it must be that this integer cannot be 1 (or some other reason/mechanism in the toolchain/Sysconfig interaction)?

    Apologies if, once again, I'm messing things up and if I'm not very clear in my explanation, but I'm more used to dealing with bare-metal rather than API layers (which is the right way of doing things) and I tend to always re-invent the wheel, so I might get lost on the devil that is in the details.

    Thank you once again for your terrific help.

    Kind regards,

    Stefano

    PS my use case is carried on from some compatibility and coherence concerns regarding the MSP430 implementation of my framework, specifically also related to generic corner/edge cases, for example where flash writing cannot occur at runtime because of certain low voltage concerns (failure/corruption issues), but there are can be other reasons for not initializing the defaults at boot, even with checks.

  • I can use 0x800 as the Region Size as well and get similar results using my SimpleLink F3 SDK v7.20 setup:

      

    The Sector Size is fixed at 0x800 as the flash page size of the CC2340R5 hardware, the Region Size is dependent on the NVS application requirements.  You should not use a Region Base of 0x7F800 and Region Size greater than 0x800, as this would extend beyond the flash memory capabilities of the device, however no error is produced given that Region Type Pointer does not allocate the memory inside of ti_drivers_config.  If using Generated then a Linker issue would occur.

    Regards,
    Ryan

  • Ryan,

    after repeated investigations on my side, including swapping probes (stand-alone XDS-110 vs. LaunchPad) here's what happened. The exhibited behavior I reported is indeed present on the LP that I was utilizing for NVS testing, which, however, was the preliminary silicon one. It turns out that of the three CC2340 LPs that I have (they all state BOM Rev A. but one is preview and two are release silicon), one (release) is in permanent stability testing (unusable for development), but I can't use it with the standalone XDS-110 probe due to reconfiguration of the UART pins and necessary power lines, so I use a separate LP with the flat cable and power connections. Bottom line, I mixed up the preview LP (which I had not disposed of and was still in storage attached to an XDS-110) with the second release. I feel terribly sorry about this inexcusable mistake on my side that never happened to me before, which led to wasting your time. The preview LP is now in the electronics trash can for good, replaced by a release one, which behaves like yours (if you have five spare minutes and still a preview LP lying around, you can check it for yourself, perhaps this awkward behavior can be useful for future silicon revisions if it hadn't been detected before, which I doubt, since it's now corrected).

    Sincere apologies for the hassle and thank you for your terrific help.

    Kind regards,

    Stefano

  • Ryan,

    if you can, please could you hold on for a little while before locking this thread? I'm still finding some erratic behavior on the release LP and I must partially retract what I wrote marking my issue as resolved. This was, in fact, why I had tried to resurrect the preview LP to investigate whether the release LP was defective and then I accidentally left it connected. Basically what happens is that the values are written in Flash correctly at compile/link/load time, but, as soon as the application (the non-BLE) thread is called, something happens so they are overwritten to the erroneous ones I had reported and then a subsequent test NVS_open() that I use to try and overwrite them fails miserably, unlike in other simpler examples that I have produced with the same exact flow. I am trying to investigate further and set up a simple example to reproduce the issue without posting all of my code. I suspect this is happening due to the switch to the TI Clang toolchain, which seems a lot more unforgiving for initializations, compared to the previous original TI compilers/toolchain.

    Thank you,

    kind regards

    Stefano

  • Hi Stefano,

    E2E threads are not locked until after 30 days of inactivity (i.e. since the latest reply).  If you are using the BLE stack, albeit not the BLE application thread itself for your target NVS application, be sure you are using a completely separate NVS instance which does not overlap with the existing BLE usage.

    Regards,
    Ryan

  • Ryan,

    thanks, yes that was the problem, I was suspicious about the BLE stack/app threads being involved (however I couldn't find any explicit calls anywhere in the code), the giveaway was that the NVS operation was right before the application thread being called (I was inaccurate, of course not WHEN it was called, that's obviously where I set the breakpoint), I initially gave for granted that the BLE stack and the BLE app framework (I'm using both) would use a specific configuration segment of the Flash, but it must be that they are (at least in the examples, from which I have derived my code) using the first instance [0] of the NVS "behind the scenes", no matter what it's called. This happens when doing unit testing in parallel and it doesn't show up, I must have missed this bit somewhere in the documentation, perhaps it could be useful if the issue could be exposed more clearly in Sysconfig or elsewhere, especially when using the BLE stack and NVS together. Now I have created a separate second instance and everything seems to be working the way it should. You saved me from trying to set up a dummy BLE example to reproduce what was going on. Apologies if I have been so inaccurate in my description, but I've been banging my head around this for a while and was even investigating the NVOCMP_NO_RAM_OPTIMIZATION define in the nvocmp.c driver (which does some writing in Flash) but ultimately it had nothing to do with it. I was also looking at some potential probe and voltage issues and even reverted to the preview silicon to dig deeper, since the behavior with the pre-release SDKs might have been different (I can't honestly remember since it was at least from v6.30).

    Three more NVS questions, if I may:

    1) is it correct to assume that the BLE stack/app FW are always using the first NVS instance (no matter what it's called, it points to [0])?

    2) what is the minimum size (if there is a fixed or safe one) of this first NVS instance?

    3) looking at the basic_ble and data_stream examples, the first instance is pretty large (16KB) and it is located at the end of the Flash (so I assume it's not overflowing that size). Is it OK to relocate it elsewhere, as long as it's aligned and large enough?

    One last, perhaps slightly OT (for this BLE forum) question, which is the most appropriate e2e forum to ask questions regarding the migration from the original TI ARM compilers/toolchains (and also cross-platform implementations) to the new TI ARM Clang toolchain used with the CC2340? I kind of underestimated the compiler/linker/initialization portability issues with my code, also due to certain C version issues which can't be changed, given the huge amount of code from both the BLE stack and the BLE app FW and other bits here and there. I was working with tiny devices and doing a lot of packing and optimization, which doesn't always carry over smoothly.

    Thank you so much for your invaluable help,

    kind regards

    Stefano

  • Each non-OAD BLE example only allocates one NVS instance, but on-chip and off-chip have a second instance.  For each of these, the first instance is at 0x7C000 with a size of 0x4000.  On-chip OAD adds an instance at 0x32000 with a size of 0x4A000 and off-chip OAD's second instance is external.  Please consult the BLE 5-Stack Users Guide OAD section for explanations.  I have not experimented with moving the NVS area on the CC2340R5 device but it would certainly also require a change to the NVS_BASE definition from cc23x0_app_freertos.cmd

    You can address your TI ARM Clang toolchain on the BLE forum and the correct expert will address it accordingly.

    Regards,
    Ryan

  • ,

    apologies for this late thank you for your latest reply, enlightening as usual.

    Kind regards

    Stefano

    PS with the help of awesome TI E2Ers like you, I have successfully completed the entire migration of my framework and application code (I still need to optimize BLE power consumption but it shouldn't be a problem) to this new terrific CC2340 HW and SW platform. It's a bit unusual to say this on an engineering forum, but I had to give it a toast to Texas Instruments for this accomplishment. I'm seriously impressed by how easy it is to design something useful, functional and affordable, once you become familiar with the platform. Kudos++ to both the HW and SW Teams behind this amazing 4th-gen device, I was a staunch believer since the June 2022 announcement and I feel totally rewarded.