1. What is NanoEdge AI Library for anomaly detection?
NanoEdge™ AI Library is an Artificial Intelligence (AI) static library for embedded C software running on Arm® Cortex® microcontrollers.
When embedded on microcontrollers, it gives them the ability to easily detect abnormal behavior from sensor patterns, by themselves, without the need for the user to have additional skills in Mathematics, Machine Learning, or data science.
The NanoEdge AI static library for anomaly detection is the code that contains an AI model designed to differentiate nominal behavior from abnormal behavior. The library output is a similarity score between 0 and 100, where 100 means highly similar to what has been learned.
1.1. Embedded learning
Anomaly detection is the only NanoEdge AI library type able to learn in situ, after deployment in the microcontroller.
This means the same library can be deployed on several devices, and each device can learn its own environment directly on target.
2. Install / Getting started
The main functions available via the library are:
neai_anomalydetection_init()
|
run first before the learning or detection process |
neai_anomalydetection_learn()
|
run a learning iteration using a nominal input buffer |
neai_anomalydetection_detect()
|
run a detection iteration and get a similarity score |
2.1. Static library
In NanoEdge AI Studio, after obtaining a library, click Compile on the "Deployment" screen, which follows the "Benchmark" and "Validation" screens.
The .zip file obtained contains:
libneai.a: the static precompiled NanoEdge AI libraryNanoEdgeAI.h: the NanoEdge AI header file, with the function declarations and the library constantsemulators/: the NanoEdge AI Emulator (Python scriptnanoedgeai_studio_emulator.pyand compiled librarieslibneai.dllfor Windows® andlibneai.sofor Linux®), see the NanoEdge AI Emulator pagemetadata.json: library metadata (target, algorithm type, estimated RAM and flash usage, performance)artifacts/: JSON files describing the model parameters and the preprocessing configuration and parametersdocs/: the documentation of the library in PDF formatfor_llms.txt: an example prompt to give to an LLM, together withmetadata.jsonand the JSON files ofartifacts/, to get a complete Markdown description of the library (preprocessing, ML configuration and parameters)
To use the library, add libneai.a and NanoEdgeAI.h to your project, then link the static library to your project code. For example in STM32CubeIDE, go to Project -> Properties -> C/C++ Build -> Settings -> MCU GCC Linker -> Libraries. Add neai in the Libraries section and the libneai.a path in the Library search path section. Click Apply and Close.
2.2. NanoEdge AI Library functions
Most NanoEdge AI functions return the status of the library in the following enum, neai_state:
enum neai_state {
NEAI_OK = 0,
NEAI_ERROR = 1,
NEAI_NOT_INITIALIZED = 2,
NEAI_INVALID_PARAM = 3,
NEAI_NOT_SUPPORTED = 4,
NEAI_LEARNING_DONE = 5,
NEAI_LEARNING_IN_PROGRESS = 6
};
Here are the possible statuses:
NEAI_OK: library working as expectedNEAI_ERROR: internal error with the libraryNEAI_NOT_INITIALIZED: learn or detect functions were called without running the init function first; initialize your libraryNEAI_INVALID_PARAM: neai function was called with one or more incorrect or missing parametersNEAI_NOT_SUPPORTED: board not supportedNEAI_LEARNING_DONE: returned bylearn()once the minimum number of learning iterations is reachedNEAI_LEARNING_IN_PROGRESS: fail-safe to prevent insufficient number of learning iterations; returned bylearn()until the minimum is reached, and bydetect()if called too early; run more iterations
2.2.1. Initialization
enum neai_state neai_anomalydetection_init(bool use_pretrained);
Initialization must be called at the beginning.
- Input:
use_pretrained:trueto use the pretrained model embedded in the libraryfalseto start with an empty model and learn on target
- Output:
- the
neai_stateenum (NEAI_OK == 0, in case of success)
- the
2.2.2. Learning
enum neai_state neai_anomalydetection_learn(float *in);
This function must be called with nominal signals only.
- Input:
float *in, pointer to the input buffer, the length of the buffer isNEAI_INPUT_SIGNAL_LENGTH * NEAI_INPUT_AXIS_NUMBER
- Output:
- the
neai_stateenum.
- the
The learning status may return:
NEAI_LEARNING_IN_PROGRESS: minimum number of learning iterations not reached yet, run more iterationsNEAI_LEARNING_DONE: minimum number of learning iterations reached; the library keeps returning this status on every further successfullearn()callNEAI_NOT_INITIALIZED:init()has not been called
learn() does not return NEAI_OK: a successful call always returns either NEAI_LEARNING_IN_PROGRESS or NEAI_LEARNING_DONE.
2.2.3. Detection
enum neai_state neai_anomalydetection_detect(float *in, uint8_t *similarity);
This function returns a similarity score between the input buffer and the nominal knowledge learned by the library.
- Input:
float *in, pointer to the input buffer, the length of the buffer isNEAI_INPUT_SIGNAL_LENGTH * NEAI_INPUT_AXIS_NUMBERuint8_t *similarity, pointer to the similarity percentage [0-100], 100 means completely similar, 0 means completely different
- Output:
uint8_t *similarity: similarity score between 0 and 100- the
neai_stateenum:NEAI_OK: detection performed,similarityis validNEAI_LEARNING_IN_PROGRESS: minimum number of learning iterations not reached yet, no detection is performedNEAI_NOT_INITIALIZED:init()has not been calledNEAI_INVALID_PARAM:inorsimilarityisNULL
2.3. Backing up and restoring the library knowledge
When using NanoEdge AI Library, knowledge is created on the go: after each learning iteration, the machine learning model gets incrementally richer.
For performance reasons, this knowledge lives in the microcontroller RAM. Since RAM is volatile, it is lost at every power cycle. To keep it, copy it to a non-volatile memory (internal flash, EEPROM, backup RAM, and so on) and restore it at the next boot.
Two functions are provided for that purpose:
enum neai_state neai_anomalydetection_get_knowledge(void **knowledge_ptr, size_t *knowledge_size);
enum neai_state neai_anomalydetection_set_knowledge(const void *knowledge_ptr, size_t knowledge_size);
2.3.1. Three values, three owners
Every save and restore sequence juggles three values that appear next to each other in the same few lines of code. They have different owners and different lifetimes, and only one of them is an address that you choose.
| Value | What it is | Who owns it | Lifetime |
|---|---|---|---|
knowledge_ptr
|
RAM address of the live knowledge, inside the library | the library | the address is fixed for the whole program run, but its content changes at every learn() |
knowledge_size
|
size of the knowledge, in bytes | the library, fixed when it was generated | identical at every boot of the same firmware; the library never stores it for you |
| NVM address | where you decide to put the bytes: flash sector base, EEPROM offset, and so on | your application | yours alone; the library never sees it |
Two consequences are worth remembering:
neai_anomalydetection_get_knowledge()copies nothing. It hands you the address of the buffer that the library is using right now, plus its size. Moving the bytes to and from the non-volatile memory is entirely your job: the library is not linked against any storage driver.- The NVM address never appears in the API. Neither function takes nor returns a flash address.
set_knowledge()reads from whatever address you give it, be it RAM or memory-mapped flash.
2.3.2. Reading the knowledge
enum neai_state neai_anomalydetection_get_knowledge(void **knowledge_ptr, size_t *knowledge_size);
Both parameters are output parameters: the library writes into them, it does not write into a buffer of yours. Passing your flash address as the first argument is a category error, that direction is the job of set_knowledge().
The address returned points inside the library context, which is in static storage: it is the same address at every call and stays valid for the whole program run. What changes is its content, because every learn() and every set_knowledge() rewrites it in place. The pointer is therefore a window on live state, not a snapshot: copy the bytes out before resuming the learning.
Calling this function right after init() is the supported way to discover knowledge_size at boot, before you know whether there is anything to restore. The content is meaningless at that point, but the size is already final.
- Output:
void **knowledge_ptr, receives the RAM address of the knowledge.size_t *knowledge_size, receives the size of the knowledge in bytes.- The
neai_stateenum:
| Return code | Meaning |
|---|---|
NEAI_OK
|
Pointer and size written. |
NEAI_NOT_INITIALIZED
|
init() has not been called.
|
NEAI_INVALID_PARAM
|
knowledge_ptr or knowledge_size is NULL.
|
2.3.3. Restoring the knowledge
enum neai_state neai_anomalydetection_set_knowledge(const void *knowledge_ptr, size_t knowledge_size);
Copies knowledge_size bytes from knowledge_ptr into the library, overwriting the current knowledge. The source can be any readable address: a RAM buffer, or directly a memory-mapped flash address.
Only the size is validated. There is no magic word, no CRC and no model identity check: integrity and versioning are your responsibility.
- Input:
const void *knowledge_ptr, the address of the saved bytes.size_t knowledge_size, their size, which must match the value returned byget_knowledge().
- Output:
- The
neai_stateenum:
- The
| Return code | Meaning |
|---|---|
NEAI_OK
|
Knowledge restored. |
NEAI_NOT_INITIALIZED
|
init() has not been called.
|
NEAI_INVALID_PARAM
|
knowledge_ptr is NULL, or knowledge_size differs from the size of this library's knowledge.
|
2.3.4. Saving to non-volatile memory
void save_knowledge(void)
{
void *kn_ptr;
size_t kn_size;
if (neai_anomalydetection_get_knowledge(&kn_ptr, &kn_size) != NEAI_OK) {
return;
}
/* Your driver. It reads kn_size bytes starting at kn_ptr and puts them
* wherever you decided. On STM32 internal flash: erase the sector first,
* then program the bytes with HAL_FLASH_Program(). */
my_nvm_write_knowledge(kn_ptr, kn_size);
}
- Never store
kn_ptritself. It is a RAM address of the current run; after a reset it means nothing. Store the bytes it points to, and storekn_sizenext to them if you want to validate the record later. - Do not let
learn()run during the write.kn_ptrpoints to live state: alearn()called from an interrupt halfway through the flash write produces a torn record, which still passes the size check when restored. - Keep the blob 4-byte aligned in NVM if your flash is programmed by words. A header made of two 32-bit words preserves that alignment naturally.
The choice of the flash sector is yours. Take care not to overwrite the .data or .text sections, as this leads to a hard fault. The sectors available on the NUCLEO-F401RE development board are:
2.3.5. Restoring at boot
A restore always follows the same shape: init() first, because the model must exist before it can be overwritten, then get_knowledge() to learn this library's size, then a size-checked set_knowledge().
Variant A: memory-mapped NVM (internal flash), no intermediate buffer
#define NVM_BASE_ADDR 0x08010000U /* your sector base */
#define NVM_HEADER_LEN 8U /* your header: magic + size */
#define NVM_MAGIC 0x4E454149U /* your record marker */
struct nvm_header { uint32_t magic; uint32_t size; }; /* your record, your layout */
neai_anomalydetection_init(false);
void *kn_ptr;
size_t kn_size;
neai_anomalydetection_get_knowledge(&kn_ptr, &kn_size); /* size of this library */
const struct nvm_header *hdr = (const struct nvm_header *) NVM_BASE_ADDR;
if (hdr->magic == NVM_MAGIC && hdr->size == kn_size) {
const void *blob = (const void *) (NVM_BASE_ADDR + NVM_HEADER_LEN);
neai_anomalydetection_set_knowledge(blob, kn_size); /* reads straight from flash */
}
The STM32 internal flash is readable with normal load instructions, so set_knowledge() can copy from it directly: no intermediate buffer, no extra RAM.
Variant B: serial NVM (SPI flash, I²C EEPROM), with an intermediate buffer
/* Ceiling chosen by the application, sized from a real measurement:
* print kn_size once during bring-up rather than guessing a value. */
#define NVM_MAX_KNOWLEDGE 16384
static uint8_t scratch[NVM_MAX_KNOWLEDGE];
neai_anomalydetection_init(false);
void *kn_ptr;
size_t kn_size;
neai_anomalydetection_get_knowledge(&kn_ptr, &kn_size);
if (kn_size > sizeof(scratch)) {
/* Refuse rather than overflow. Reaching this means the library grew:
* increase NVM_MAX_KNOWLEDGE and rebuild. */
} else if (my_nvm_read_knowledge(scratch, kn_size)) {
neai_anomalydetection_set_knowledge(scratch, kn_size);
}
The intermediate buffer exists only because the medium is not memory-mapped. Its size is a ceiling that you choose; kn_size is the actual number of bytes and the only value to pass to the API.
Once the knowledge is restored, you can switch to the detection mode directly, without running a new learning phase.
2.3.6. Troubleshooting
| Symptom | Cause |
|---|---|
set_knowledge() returns NEAI_INVALID_PARAM at every boot
|
The stored record comes from a library generated with different settings, so its size differs. Version your NVM records by firmware revision. |
set_knowledge() returns NEAI_OK but detect() scores are meaningless
|
The source address is off by the header length, or the record comes from another library that happens to have the same knowledge size. Only the size is checked. |
set_knowledge() returns NEAI_NOT_INITIALIZED
|
init() was not called first. A restore never replaces the initialization, it follows it.
|
| The restore worked on the bench and fails in the field | The record was saved while learn() was still running (torn write), or it was truncated by an intermediate buffer smaller than kn_size.
|
get_knowledge() does not fill my buffer
|
It is not supposed to. Both parameters are output parameters: the library returns its own address and size, and the copy is yours to make. |
| The knowledge size changed between two firmware versions | Expected: the size is fixed when the library is generated. Store it in your NVM header and compare it before restoring. |
2.3.7. Limitations
- The size is fixed by the library. It depends on the model and the hyperparameters chosen by NanoEdge AI Studio and frozen when the library was generated. If you regenerate a library with different settings, the records saved by the previous firmware are rejected with
NEAI_INVALID_PARAM. Treat the knowledge as an opaque blob, versioned by your firmware revision. - Not portable across architectures. Structure padding and float endianness depend on the target. Saving on one architecture and restoring on another is not supported.
- The blob contains the complete learning state. Both
detect()andlearn()read everything they need from the restored knowledge, so a save, restore and continue-learning sequence produces exactly the same knowledge as an uninterrupted learning session, and the same detection scores. - The library never accesses the non-volatile memory. Wear leveling, write throttling, atomic swap, CRC and recovery from a corrupted record are your application's responsibility.
- No concurrency guarantee.
get_knowledge()hands out a pointer to live state and the library does no locking. Serialize your saves againstlearn()yourself.
2.3.8. Frequently asked questions
Do I need an intermediate buffer to restore?
Only if your non-volatile memory is not memory-mapped. On the STM32 internal flash, pass the address of the stored blob straight to set_knowledge() and save the RAM.
Can I write to flash directly from the pointer returned by get_knowledge()?
Yes, that is the intended save path. The pointer is a valid source for memcpy() or for a flash programming routine. Just make sure that no learn() runs concurrently.
Can I call get_knowledge() in the middle of a learning phase?
Yes. The bytes reflect the current learning state. Detection scores computed after restoring them match the scores computed without the save and restore round trip, bit for bit.
Do I need init(true) before set_knowledge()?
No. init(false) is enough, since set_knowledge() overwrites the knowledge regardless of how init() was called. init(true) works equally well, the pretrained knowledge is simply overwritten.
How big is the knowledge?
Call get_knowledge() once after init() and read knowledge_size. The value is constant for a given library. Depending on the model and the signal length, it ranges from a few hundred bytes to a few tens of kilobytes.
What happens if I restore a record saved by another library?
set_knowledge() validates the size only. If the size matches by coincidence and the bytes are unrelated, the behavior is undefined: meaningless scores, without any crash. Always pair a saved record with a firmware version tag in your non-volatile memory.
2.4. Example "Hello World!"
Header file: NanoEdgeAI.h
NanoEdgeAI.h is provided in the .zip file that you download by clicking Compile in NanoEdge AI Studio. It declares the following elements:
#ifndef NANOEDGEAI_H
#define NANOEDGEAI_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
/* NEAI ID */
#define NEAI_ID "None"
/* Input signal configuration */
#define NEAI_INPUT_SIGNAL_LENGTH 256
#define NEAI_INPUT_AXIS_NUMBER 3
/* NEAI State Enum */
enum neai_state {
NEAI_OK = 0,
NEAI_ERROR = 1,
NEAI_NOT_INITIALIZED = 2,
NEAI_INVALID_PARAM = 3,
NEAI_NOT_SUPPORTED = 4,
NEAI_LEARNING_DONE = 5,
NEAI_LEARNING_IN_PROGRESS = 6
};
#ifdef __cplusplus
extern "C" {
#endif
/* ===== Anomaly Detection API ===== */
enum neai_state neai_anomalydetection_init(bool use_pretrained);
enum neai_state neai_anomalydetection_learn(float *in);
enum neai_state neai_anomalydetection_detect(float *in, uint8_t *similarity);
enum neai_state neai_anomalydetection_get_knowledge(void **knowledge_ptr, size_t *knowledge_size);
enum neai_state neai_anomalydetection_set_knowledge(const void *knowledge_ptr, size_t knowledge_size);
/* ===== Common getter functions ===== */
char* neai_get_id(void);
int neai_get_input_signal_size(void);
int neai_get_axis_number(void);
#ifdef __cplusplus
}
#endif
#endif /* NANOEDGEAI_H */
The knowledge functions neai_anomalydetection_get_knowledge() and neai_anomalydetection_set_knowledge() are described in Backing up and restoring the library knowledge.
Main program: main.c
This program must be completed by the user (depending for instance on the application or the desired features).
/* Includes ------------------------------------------------------------------*/
#include "NanoEdgeAI.h"
#include <stdbool.h>
#include <stddef.h>
/* Number of samples for learning: set by user -------------------------------*/
#define LEARNING_ITERATIONS replace_learning_samples
float input_signal[NEAI_INPUT_SIGNAL_LENGTH * NEAI_INPUT_AXIS_NUMBER]; // Buffer of input values
/*
* @brief Collect data process
*
* This function is defined by user, depends on applications and sensors
*
* @param input_signal: [in, out] buffer of sample values
* @note If NEAI_INPUT_AXIS_NUMBER = 3 (cf NanoEdgeAI.h), the buffer must be
* ordered as follows:
* [x0 y0 z0 x1 y1 z1 ... xn yn zn], where xi, yi and zi
* are the values for x, y and z axes, n is equal to
* NEAI_INPUT_SIGNAL_LENGTH (cf NanoEdgeAI.h)
*/
void fill_buffer(float *input_signal)
{
/* USER BEGIN */
/* USER END */
}
/* OPTIONAL: user-supplied NVM driver, only needed if the application wants the
* learned knowledge to survive a power cycle. Implementation depends on the
* target (internal Flash, external EEPROM, backup RAM, ...). The NEAI library
* never touches NVM itself. Skip these declarations if persistence is not used.
*/
extern bool my_nvm_has_saved_knowledge(void);
extern bool my_nvm_read_knowledge(void *dst, size_t size);
extern void my_nvm_write_knowledge(const void *src, size_t size);
int main(void)
{
/* Initialization ---------------------------------------------------------*/
bool use_pretrained = false; // true: pretrained model, false: learn from scratch
enum neai_state error_code = neai_anomalydetection_init(use_pretrained);
uint8_t similarity = 0;
if (error_code != NEAI_OK) {
/* Check the returned error code (cf NanoEdgeAI.h). */
}
/* OPTIONAL: restore previously saved knowledge, if any -------------------
* Remove this block if knowledge persistence across power cycles is not used.
*/
if (my_nvm_has_saved_knowledge()) {
void *kn_ptr;
size_t kn_size;
/* Called for the size only: kn_ptr is the library's own buffer. */
neai_anomalydetection_get_knowledge(&kn_ptr, &kn_size);
/* Ceiling chosen by the application; print kn_size once during
* bring-up to size it. On memory-mapped Flash this buffer is not
* needed at all: pass the stored address to set_knowledge() directly.
*/
static uint8_t scratch[16384];
if (kn_size <= sizeof(scratch)) {
if (my_nvm_read_knowledge(scratch, kn_size)) {
neai_anomalydetection_set_knowledge(scratch, kn_size);
}
}
}
/* Learning process -------------------------------------------------------*/
for (uint16_t iteration = 0 ; iteration < LEARNING_ITERATIONS ; iteration++) {
fill_buffer(input_signal);
neai_anomalydetection_learn(input_signal);
}
/* OPTIONAL: persist the freshly learned knowledge to NVM -----------------
* Remove this block if knowledge persistence across power cycles is not used.
*/
{
void *kn_ptr;
size_t kn_size;
if (neai_anomalydetection_get_knowledge(&kn_ptr, &kn_size) == NEAI_OK) {
my_nvm_write_knowledge(kn_ptr, kn_size);
}
}
/* Detection process ------------------------------------------------------*/
while (1) {
fill_buffer(input_signal);
neai_anomalydetection_detect(input_signal, &similarity);
/* USER BEGIN */
/* e.g.: Trigger functions depending on similarity
* (blink LED, ring alarm, etc.).
*/
/* USER END */
}
}


