Considerations

Allocating RAM

Any variables you declare within your C functions will typically live on the stack within SNAP. Typically, by the time you are calling a C function, your stack will already have a half dozen or so entries on it. These entries will include parameters for any SNAPpy functions or built-ins already called or details associated with whatever hook or RPC has triggered your code to run.

Given that the SNAP stack is 512 bytes, it’s possible that C functions that allocate a lot of RAM could overflow the stack, especially if you have function calls nested several layers deep.

There are two ways to avoid overflowing the stack by consuming RAM from your SNAPpy string buffer space rather than from the stack. Each large string normally consumes 257 bytes of RAM: a reference count byte, a length byte, and 255 bytes of string data. With nine large strings normally available in SNAP, if you are willing to give them all up for your C functions , you can have up to 2,295 bytes available. Beyond that, SNAPbuild does not start pulling from medium strings to expand your memory space available.

SNAP will only consume as many string buffers as necessary for the variables you have declared, regardless of how (or where) they are declared. An array of 100 (two-byte) integers plus a pair of Boolean values and a handful of other integers would collectively all come from the same one large string buffer.

If you assign values to these variables as you declare them, SNAP will populate the variable space when it initializes the SNAP virtual machine (as there is no main() C function to run in this environment). While it is obvious that rebooting your device will reinitialize your C variables, it may be less obvious that calling initVm() on your device from within your SNAPpy script or by RPC from another device will also reinitialize your C variables (as well as your SNAPpy variables).

Persistent, Statically Allocated RAM

The first way to allocate RAM from your string space is to declare your variables within your function definition using the static keyword. This has two effects:

  1. The variable gets a fixed address in RAM within the string buffer space.

  2. The contents of the variable persist between function calls. A second call to the same function will find the variable holding whatever it held at the end of the first call to the function. If you need the variable to be initialized on each call to the function, then you need to explicitly do that in your code. Variable contents do not persist through reboots.

To allocate RAM for your C functions, use the static keyword:

static unit16_t num_samples = 0;
static unit16_t samples[125];

This declaration of a total of 126 two-byte variables (num_samples plus an array of 125 samples) consumes 252 bytes of RAM but still consumes only one of the large string buffers available to SNAPpy.

Global Variable Declarations

The second way to ensure that your variables consume from your string space rather than existing on the stack is to declare them within the global space in your C code file. Variables declared in the global space will be available to all functions in all imported C code files for a given SNAPpy script (though C code files other than the primary one in which they are declared will need to tag them with the extern keyword in their variable declarations). Of course, there is no requirement that more than one function access a globally declared variable.

If you use the static keyword on a global variable, the variable still persists between function calls and remains available to all functions in the compilation unit. This means that the variable is available to functions in the file in which it is declared and in files that are included using #include. However, if your SNAPpy script uses import_c() to include more than one C code file, global static variables declared in one file will not be available in the other(s).

Function Wrappers

So far, the explanations and examples provided have been biased toward making C coding in the SNAP environment as easy as possible. However, C coders are not always known for sticking to the easiest path when some other path may provide a more direct route to their destinations. When you specify the API between your SNAPpy script stub and your C function, SNAPbuild generates a wrapper for your C code, to verify that the passed variables match the expected types and to handle the manipulation of parameters as they are extracted from the stack (a Python object) and forwarded to your C function.

If you have special needs contrary to this assistance (such as extremely specific timing requirements or a need to not validate types passed in to or returned from your functions), you can have SNAPbuild bypass the creation of the wrapper functions by never defining the API between the SNAPpy and the C code. You must still generate the SNAPpy function stub and decorate it as an @c_function. This still specifies the function name you will use to invoke your C code from SNAPpy (or by RPC).

If you will not be specifying the API, SNAPbuild will expect your C function to have the following prototype:

void func(uint8_t num_args, py_obj *stack, uint8_t *unused);

The first parameter, an unsigned 8-bit integer, is the number of arguments on the stack. The second parameter is a pointer to the stack. The third parameter, an unsigned 8-bit integer, is unused (reserved for future development). Additionally, any returned values that you expect SNAPpy to interpret as Boolean values should be restricted to 0 for False or 1 for True.

For examples of how to work with accessing parameters from the stack, you can write a sample function with the appropriate number and types of variables, define an API for it, and run SNAPbuild. Then, view the main.c file that SNAPbuild generates. You can also view the plus_one() and minus_1() functions in the wrap_test.c file in the Advanced Example for a demonstration.

Note

SNAPbuild regenerates main.c, snappy.h, snap_utils.h, and snap_utils.c to be appropriate for your current program each time that it is run. Any changes you make to these files will be overwritten the next time you generate a .spy file.