Skip to content

Commit 645697b

Browse files
Firmware and SDK release 27 Feb 2026
SDK version: v1.88.0
1 parent 879b0aa commit 645697b

3 files changed

Lines changed: 105 additions & 73 deletions

File tree

extension/edge_impulse_extension/ei-model/edge-impulse-sdk/classifier/ei_classifier_config.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,24 @@
8080
#endif // EI_CLASSIFIER_TFLITE_ENABLE_ARC
8181

8282
#ifndef EI_CLASSIFIER_TFLITE_ENABLE_ESP_NN
83-
#if defined(ESP32)
83+
// By default, if one of the CONFIG_IDF_TARGET_ESP32 is defined, it enables ESP-NN.
84+
// This can be overridden by defining EI_CLASSIFIER_TFLITE_ENABLE_ESP_NN to 0 if the user doesn't want to use ESP-NN for some reason.
85+
#if defined(ESP32)|| defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32P4) || defined(CONFIG_IDF_TARGET_ESP32C3)
8486
#include "sdkconfig.h"
8587
#define EI_CLASSIFIER_TFLITE_ENABLE_ESP_NN 1
86-
#define ESP_NN 1
88+
#else
89+
#define EI_CLASSIFIER_TFLITE_ENABLE_ESP_NN 0
8790
#endif // ESP32 check
91+
#endif
92+
93+
#if EI_CLASSIFIER_TFLITE_ENABLE_ESP_NN == 1
94+
#define ESP_NN 1
8895
#if defined(CONFIG_IDF_TARGET_ESP32S3)
8996
#define EI_CLASSIFIER_TFLITE_ENABLE_ESP_NN_S3 1
9097
#endif // ESP32S3 check
9198
#if defined(CONFIG_IDF_TARGET_ESP32P4)
9299
#define EI_CLASSIFIER_TFLITE_ENABLE_ESP_NN_P4 1
93100
#endif // ESP32P4 check
94-
#else
95-
#define ESP_NN 1
96101
#endif
97102

98103
// no include checks in the compiler? then just include metadata and then ops_define (optional if on EON model)

extension/edge_impulse_extension/ei-model/edge-impulse-sdk/classifier/inferencing_engines/nordic_axon.h

Lines changed: 95 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* The Clear BSD License
22
*
3-
* Copyright (c) 2025 EdgeImpulse Inc.
3+
* Copyright (c) 2026 EdgeImpulse Inc.
44
* All rights reserved.
55
*
66
* Redistribution and use in source and binary forms, with or without
@@ -44,56 +44,78 @@
4444
#include "model-parameters/model_metadata.h"
4545
#include <string>
4646
#include "edge-impulse-sdk/classifier/ei_model_types.h"
47-
#include "axon_platform.h"
48-
#include "axon_driver.h"
49-
#include "axon_nn_infer.h"
50-
#include "axon_nn_infer_test.h"
51-
#include "axon_stringization.h"
47+
#include "axon/nrf_axon_platform.h"
48+
#include "drivers/axon/nrf_axon_driver.h"
49+
#include "drivers/axon/nrf_axon_nn_infer.h"
50+
#include "drivers/axon/nrf_axon_nn_infer_test.h"
51+
#include "axon/nrf_axon_stringization.h"
5252
#include <inttypes.h>
5353

54+
#if !defined (NRF_AXON_MODEL_NAME)
55+
#error "NRF_AXON_MODEL_NAME is not defined. Please define NRF_AXON_MODEL_NAME"
56+
#endif
5457

5558
/*
5659
* Create the model include header file name and structure from
5760
* the model name.
5861
*/
59-
#define AXON_MODEL_FILE_NAME_ROOT axon_model_
60-
#define AXON_MODEL_LAYERS_FILE_NAME_ROOT AXON_MODEL_FILE_NAME_ROOT
61-
#define AXON_MODEL_TEST_VECTORS_FILE_NAME_ROOT AXON_MODEL_FILE_NAME_ROOT
62-
#define AXON_MODEL_TEST_VECTORS_FILE_NAME_END _test_vectors_.h
63-
#define AXON_MODEL_LAYERS_FILE_NAME_TAIL _layers_.h
62+
#define AXON_MODEL_FILE_NAME_ROOT nrf_axon_model_
63+
6464
#define AXON_MODEL_DOT_H _.h
6565

66-
#define AXON_MODEL_FILE_NAME STRINGIZE_3_CONCAT(AXON_MODEL_FILE_NAME_ROOT, AXON_MODEL_NAME, AXON_MODEL_DOT_H)
67-
#define AXON_MODEL_FILE_LAYERS_NAME STRINGIZE_3_CONCAT(AXON_MODEL_LAYERS_FILE_NAME_ROOT, AXON_MODEL_NAME, AXON_MODEL_LAYERS_FILE_NAME_TAIL)
68-
#define AXON_MODEL_TEST_VECTORS_FILE_NAME STRINGIZE_3_CONCAT(AXON_MODEL_TEST_VECTORS_FILE_NAME_ROOT, AXON_MODEL_NAME, AXON_MODEL_TEST_VECTORS_FILE_NAME_END)
66+
#define AXON_MODEL_FILE_NAME STRINGIZE_3_CONCAT(AXON_MODEL_FILE_NAME_ROOT, NRF_AXON_MODEL_NAME, AXON_MODEL_DOT_H)
6967

70-
// // generate structure name model_<model_name>
7168
#define THE_REAL_MODEL_STRUCT_NAME(model_name) model_##model_name
7269
#define THE_MODEL_STRUCT_NAME(model_name) THE_REAL_MODEL_STRUCT_NAME(model_name)
7370

74-
// // generate structure name model_<model_name>_layer_list
75-
#define THE_REAL_MODEL_LAYERS_STRUCT_NAME(model_name) model_##model_name##_layer_list
76-
#define THE_MODEL_LAYERS_STRUCT_NAME(model_name) THE_REAL_MODEL_LAYERS_STRUCT_NAME(model_name)
71+
#define NRF_AXON_MODEL_ALLOCATE_PACKED_OUTPUT_BUFFER 1 // model allocates a dedicated buffer for the output.
7772

78-
// // generate structure name model_<model_name>_test_vectors
79-
#define THE_REAL_MODEL_TEST_VECTORS_STRUCT_NAME(model_name) model_##model_name##_test_vectors
80-
#define THE_MODEL_TEST_VECTORS_STRUCT_NAME(model_name) THE_REAL_MODEL_TEST_VECTORS_STRUCT_NAME(model_name)
73+
#include AXON_MODEL_FILE_NAME
8174

82-
// generate structure name <model_name>_input_test_vectors
83-
#define THE_REAL_TEST_INPUT_VECTORS_LIST_NAME(model_name) model_name##_input_test_vectors
84-
#define THE_TEST_INPUT_VECTORS_LIST_NAME(model_name) THE_REAL_TEST_INPUT_VECTORS_LIST_NAME(model_name)
75+
const nrf_axon_nn_compiled_model_s *nrf_axon_compiled_model;
8576

86-
// generate structure name <model_name>_expected_output_test_vectors
87-
#define THE_REAL_expected_output_vectors_NAME(model_name) model_name##_expected_output_vectors
88-
#define THE_expected_output_vectors_NAME(model_name) THE_REAL_expected_output_vectors_NAME(model_name)
77+
static bool nrf_axon_init = false;
8978

90-
// generate structure name <model_name>_layer_vectors
91-
#define THE_REAL_layer_vectors_NAME(model_name) model_name##_layer_expected_output_vectors
92-
#define THE_layer_vectors_NAME(model_name) THE_REAL_layer_vectors_NAME(model_name)
79+
/**
80+
* This code needs to be run one-time only.
81+
*/
82+
int one_time_init()
83+
{
84+
nrf_axon_platform_init();
85+
nrf_axon_result_e result_axon = nrf_axon_platform_init();
9386

94-
#include AXON_MODEL_FILE_NAME
87+
if (result_axon != NRF_AXON_RESULT_SUCCESS){
88+
ei_printf("ERR: nrf_axon_platform_init failed!\n");
89+
return EI_IMPULSE_NORDIC_AXON_ERROR;
90+
}
9591

96-
const axon_nn_compiled_model_struct *model_static_info;
92+
nrf_axon_compiled_model = &THE_MODEL_STRUCT_NAME(NRF_AXON_MODEL_NAME);
93+
94+
result_axon = nrf_axon_nn_model_validate(nrf_axon_compiled_model);
95+
if (result_axon != NRF_AXON_RESULT_SUCCESS){
96+
ei_printf("ERR: nrf_axon_nn_model_validate failed!\n");
97+
return EI_IMPULSE_NORDIC_AXON_ERROR;
98+
}
99+
return EI_IMPULSE_OK;
100+
}
101+
102+
int one_time_cleanup()
103+
{
104+
nrf_axon_platform_close();
105+
return EI_IMPULSE_OK;
106+
}
107+
108+
/**
109+
* @brief Performs session initialization
110+
*
111+
* A "session" is when inference is occuring regularlly on continuously streaming input.
112+
* This function is needed for streaming style models that have internal state variables that need
113+
* to be initialized at the start of a session.
114+
*/
115+
int streaming_session_init()
116+
{
117+
return nrf_axon_nn_model_init_vars(nrf_axon_compiled_model);
118+
}
97119

98120
EI_IMPULSE_ERROR run_nn_inference(
99121
const ei_impulse_t *impulse,
@@ -114,79 +136,83 @@ EI_IMPULSE_ERROR run_nn_inference(
114136
ei_printf("INFO: Start Platform!\n");
115137
}
116138

117-
AxonResultEnum result_axon = axon_platform_init();
118-
119-
if (result_axon != kAxonResultSuccess) {
120-
ei_printf("ERR: axon_platform_init failed!\n");
121-
return EI_IMPULSE_INFERENCE_ERROR;
139+
if (nrf_axon_init == false) {
140+
if (one_time_init() != EI_IMPULSE_OK) {
141+
return EI_IMPULSE_NORDIC_AXON_ERROR;
142+
}
143+
nrf_axon_init = true;
122144
}
123-
void *axon_handle = axon_driver_get_handle();
124145

125146
if (debug) {
126147
ei_printf("INFO: Prepare and run Axon!\n");
127148
}
128149

129-
axon_nn_model_inference_wrapper_struct model_wrapper;
130-
model_static_info = &THE_MODEL_STRUCT_NAME(AXON_MODEL_NAME);
131-
132-
int init_result = axon_nn_model_init(&model_wrapper, model_static_info);
133-
if (init_result != 0) {
134-
ei_printf("ERR: axon_nn_model_init failed: %d\n", init_result);
135-
return EI_IMPULSE_INFERENCE_ERROR;
136-
}
137-
138-
axon_nn_model_init_vars(&model_wrapper);
139-
140-
uint32_t vector_size = model_static_info->inputs[0].dimensions.height * model_static_info->inputs[0].dimensions.width;
150+
uint32_t vector_size = nrf_axon_compiled_model->inputs[nrf_axon_compiled_model->external_input_ndx].dimensions.height * nrf_axon_compiled_model->inputs[nrf_axon_compiled_model->external_input_ndx].dimensions.width;
141151
int8_t input_vector[vector_size];
142152

143-
uint64_t ctx_start_us = ei_read_timer_us();
144-
145153
// copy rescale the input features to int8 and copy to input buffer
154+
/**
155+
* @FIXME!!! CHECK IF MODEL IS TRANSPOSED! THIS INFO IS NOT STORED IN THE MODEL, BUT SHOULD BE!
156+
*/
146157
for (size_t i = 0; i < matrix->rows * matrix->cols; i++) {
147158
//TODO: get scale and zero point from the model
148159
input_vector[i] = (int8_t)((matrix->buffer[i] / graph_config->input_scale) + graph_config->input_zeropoint);
149160
}
150161

151-
AxonResultEnum result_axon_infer = axon_nn_model_infer_sync(
152-
axon_handle, // your hardware handle
153-
model_static_info,
154-
&model_wrapper.cmd_buf_info,
162+
uint64_t ctx_start_us = ei_read_timer_us();
163+
164+
nrf_axon_result_e result_axon_infer = nrf_axon_nn_model_infer_sync(
165+
nrf_axon_compiled_model,
155166
input_vector,
156-
vector_size
157-
);
167+
nrf_axon_compiled_model->packed_output_buf);
158168

159-
if (result_axon_infer != kAxonResultSuccess) {
160-
printf("ERR: Inference failed!\n");
169+
if (result_axon_infer != NRF_AXON_RESULT_SUCCESS) {
170+
ei_printf("ERR: Inference failed!\n");
161171
return EI_IMPULSE_INFERENCE_ERROR;
162172
}
163173

164174
// ei_sleep(3); // let the axon finish processing
165175
result->timing.classification_us = (int64_t)(ei_read_timer_us() - ctx_start_us);
176+
result->timing.classification = (int)(result->timing.classification_us / 1000);
166177

178+
#if 0
179+
/**
180+
* get_classification isn't necessary to validate the result. It only applies to single
181+
* channel classification networks
182+
*/
167183
const char *label;
168184
int32_t score;
169185
int16_t class_idx = axon_nn_get_classification(&model_wrapper, NULL, &label, &score, NULL);
170186

171187
if (class_idx >= 0) {
172188
if (debug) {
173189
ei_printf("INFO: Axon inference successful.\n");
174-
printf("INFO: Predicted class: %d\n", class_idx);
175-
printf("INFO: Label: %s\n", label);
176-
printf("INFO: Score: %d\n", score);
190+
ei_printf("INFO: Predicted class: %d\n", class_idx);
191+
ei_printf("INFO: Label: %s\n", label);
192+
ei_printf("INFO: Score: %d\n", score);
177193
}
178194
// here channel is always one and byte width is always 1 for quantized models
179-
uint32_t output_size = model_static_info->output_dimensions.height * model_static_info->output_dimensions.width;
195+
uint32_t output_size = nrf_axon_compiled_model->output_dimensions.height * nrf_axon_compiled_model->output_dimensions.width * nrf_axon_compiled_model->output_dimensions.channel_cnt;
180196
result->_raw_outputs[learn_block_index + 0].matrix_i8 = new matrix_i8_t(1, output_size);
181-
memcpy(result->_raw_outputs[learn_block_index + 0].matrix_i8->buffer, (int8_t *)model_static_info->output_ptr, output_size * sizeof(int8_t));
197+
memcpy(result->_raw_outputs[learn_block_index + 0].matrix_i8->buffer, (int8_t *)nrf_axon_compiled_model->packed_output_buf, output_size * sizeof(int8_t));
182198
result->_raw_outputs[learn_block_index].blockId = block_config->block_id;
183199
} else {
184-
printf("ERR: axon Classification failed!\n");
200+
ei_printf("ERR: axon Classification failed!\n");
185201
return EI_IMPULSE_INFERENCE_ERROR;
186202
}
187-
188-
axon_platform_close();
189-
203+
#else
204+
// here channel is always one and byte width is always 1 for quantized models
205+
/**
206+
* Not sure how output channels will always be 1... fomo models have multiple output channels.
207+
* Also, is new matrix_i8_t(1, output_size) a memory leak? Or does it get freed elsewhere?
208+
* If the matrix_i8 was pre-allocated, it could be passed to the infer function directly, saving an extra memcpy.
209+
*/
210+
uint32_t output_size = nrf_axon_compiled_model->output_dimensions.height * nrf_axon_compiled_model->output_dimensions.width * nrf_axon_compiled_model->output_dimensions.channel_cnt;
211+
result->_raw_outputs[learn_block_index + 0].matrix_i8 = new matrix_i8_t(1, output_size);
212+
memcpy(result->_raw_outputs[learn_block_index + 0].matrix_i8->buffer, (int8_t *)nrf_axon_compiled_model->packed_output_buf, output_size * sizeof(int8_t));
213+
result->_raw_outputs[learn_block_index].blockId = block_config->block_id;
214+
#endif
215+
190216
return EI_IMPULSE_OK;
191217
}
192218

extension/edge_impulse_extension/ei-model/edge-impulse-sdk/dsp/returntypes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ typedef enum {
8080
EI_IMPULSE_OUTPUT_TENSOR_NULL = -33, /**< Error when the output tensor cannot be found in result->_raw_outputs */
8181
EI_IMPULSE_POSTPROCESSING_THRESHOLD_KEY_NOT_FOUND = -34, /**< Trying to set a threshold whose key cannot be found */
8282
EI_IMPULSE_CALL_SIGNATURE_REMOVED = -35, /**< This function has been removed, on GCC this will error out at compile time (with a migration message), but not all compilers support this */
83+
EI_IMPULSE_NORDIC_AXON_ERROR = -36, /**< Error in Nordic Axon inferencing engine */
8384
} EI_IMPULSE_ERROR;
8485

8586
#endif // _EIDSP_RETURN_TYPES_H_

0 commit comments

Comments
 (0)