Constant Error EI_IMPULSE_OUTPUT_TENSOR_NULL(-33) with EON Compiler on PIC32 (XC32)

Hi everyone,

(Note: I am not a native English speaker, so I am using AI to help translate this message. I apologize for any unnatural phrasing.)

I am experiencing a persistent issue where run_classifier() returns EI_IMPULSE_OUTPUT_TENSOR_NULL(-33) at the very end of the inference process. I have debugged the issue down to a specific pointer assignment in the SDK, but I need some help resolving it.

Here is the detailed report of my environment and findings:

1. Environment & Prerequisites

  • Hardware: PIC32MX270F256B (MIPS architecture)
  • Compiler: XC32 (v5.10)
  • IDE: MPLAB X IDE (v6.30) / Simulator environment
  • SDK Download Date: August 10, 2026 (Very recent build)
  • Data Type: Time-series data (Accelerometer)
  • Window Size / Stride: 2000ms / 500ms
  • Learning Blocks: Classification + Anomaly Detection (K-means)
  • Deployment Target: C++ Library
  • Inference Engine: EON Compiler
  • Estimated Performance: RAM usage 3.0k / FLASH usage 15.8k

2. The Problem

Whenever I execute run_classifier(), it always fails at the end of the inference process, resulting in the output tensor being evaluated as NULL (EI_IMPULSE_OUTPUT_TENSOR_NULL).

3. Attempted Solutions (Unsuccessful)

  • Increased the MCU Heap Size to 48000.
  • Increased EI_CLASSIFIER_TFLITE_LARGEST_ARENA_SIZE to 7000.
  • Rebuilt the model on the Edge Impulse dashboard, re-downloaded the C++ library, and re-integrated it into the project.

4. Deep-Dive Debugging Results

I used the MPLAB X debugger to step through the code and monitor the variables in the watch window. Here is what I found:

  • Suspected Location: The issue seems to occur in the run_nn_inference() function inside edge-impulse-sdk\classifier\inferencing_engines\tflite_eon.h.
  • Pointer Allocation Succeeds: Inside the case kTfLiteInt8: block, the memory allocation new matrix_i8_t(1, output_size) itself is successful. I assigned it to a temporary variable and confirmed that a valid memory address is allocated.
  • Assignment Fails / Potential Buffer Overrun: The code attempts to store the result into result->_raw_outputs[learn_block_index + output_ix].matrix_i8. However, when checking via the debugger, the matrix is NOT properly stored in the array. It behaves as if the pointer assignment fails or gets overwritten.

Given that the memory is successfully allocated but fails to be stored in the _raw_outputs struct array, could this be an architecture-specific issue (e.g., pointer size, struct alignment/padding on the XC32 compiler/MIPS architecture)?

Has anyone encountered a similar issue or has any advice on how to properly assign the allocated matrix to the result struct in this environment?

Any help would be greatly appreciated. Thank you!

Hello @nbures first of all welcome to the Edge Impulse community!

This error means that the output tensor cannot be found in result->_raw_outputs. Do you get anything else in the result object?

Could you please share more code of your application so the embedded engineers can try to replicate this situation?

1 Like

I apologize for the delayed response, and thank you for your reply.

To answer your questions:

  1. Do you get anything else in the result object?
    No, I don’t get anything else. When the error occurs, result->_raw_outputs is completely empty (all pointers inside are 0x00000000). I have attached a screenshot showing the contents of the result object right after executing run_classifier(). Please refer to the attached image for more details.

  1. Could you please share more code of your application?
    I am basically just calling the standard run_classifier() function.I am sharing the entire code of Main.cpp.

Main.cpp (Main application code including the main() function):

#include <xc.h>
#include <cstdlib>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

extern "C"{
    #include "definitions.h"
}
#include "Main.h"
#include "edge-impulse-sdk/classifier/ei_run_classifier.h"

extern void serial_printf(const char *format, ...);

using namespace std;
static float eiAccBuffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = {0};             // Buffer for accelerometer data (2000ms/4) used by EI (Edge Impulse AI)
template <typename T>
T my_map(T x, T in_min, T in_max, T out_min, T out_max);


// ---------------------------------------------------------
// Definition of the ring buffer and slice buffer
// ---------------------------------------------------------
#define RING_BUFFER_SIZE 200
volatile float ringBuffer[RING_BUFFER_SIZE] = {0};
volatile int writeIdx = 0;
int readIdx = 0;
uint32_t AccelReadSub(ADC_INPUT_POSITIVE pin);


// ---------------------------------------------------------
// TMR1 interrupt callback (Automatically executed exactly every 20ms)
// ---------------------------------------------------------
void Timer1_Callback(uint32_t status, uintptr_t context) {
    // Acquire 3-axis data and map it to a range of 0 to 255
    float x = (float)my_map<int>(AccelReadSub(PIN_ACC_X), 0, 1023, 0, 255);
    float y = (float)my_map<int>(AccelReadSub(PIN_ACC_Y), 0, 1023, 0, 255);
    float z = (float)my_map<int>(AccelReadSub(PIN_ACC_Z), 0, 1023, 0, 255);
    
    // Write to the ring buffer and advance the index
    ringBuffer[writeIdx] = x; writeIdx = (writeIdx + 1) % RING_BUFFER_SIZE;
    ringBuffer[writeIdx] = y; writeIdx = (writeIdx + 1) % RING_BUFFER_SIZE;
    ringBuffer[writeIdx] = z; writeIdx = (writeIdx + 1) % RING_BUFFER_SIZE;
}



int main(int argc, char** argv) {    
    // setup
    SYS_Initialize ( NULL );                                                    // Initialize PIC microcontroller related processes
    
    // Register the TMR1 callback and start the timer
    TMR1_CallbackRegister(Timer1_Callback, (uintptr_t)NULL);
    TMR1_Start();
    
    signal_t signal;                                                            // For passing raw data
    static ei_impulse_result_t result;                                          // For storing inference results
    bool debugFlg = true;                                                       // Debug output flag
    
     // Set the callback function and the required amount of data
    signal.total_length = EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE;
    signal.get_data     = &get_signal_data;
    
    int totalFloats = EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE;
    int sliceFloats = EI_CLASSIFIER_SLICE_SIZE * EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME;
    int shiftFloats = totalFloats - sliceFloats;
    
    ADC_Enable();                                                               // Start ADC (Analog-to-Digital) conversion
    
    serial_printf("////////////// Start ////////////////\r\n");
    
    // loop
    while(1){
        // Check the amount of new data available in the ring buffer
        int availableFloats = (writeIdx >= readIdx) 
                             ? (writeIdx - readIdx) 
                             : (RING_BUFFER_SIZE - readIdx + writeIdx);        
        // If new data for 1 slice (75 items) has accumulated, update the buffer and run inference
        if (availableFloats >= sliceFloats) {
            // [Step 1] Shift the old data in the main buffer to the left (forgetting process)
            for (int i = 0; i < shiftFloats; i++) {
                if(sliceFloats + i >= EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE){
                    serial_printf("sliceFloats + i %d\r\n", sliceFloats+i);
                }
                eiAccBuffer[i] = eiAccBuffer[i + sliceFloats];
            }
            
            // [Step 2] Write new data from the ring buffer into the freed space on the right
            for(int i = 0; i < sliceFloats; i++) {
                if(shiftFloats + i >= EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE){
                    serial_printf("shiftFloats + i %d , readIdx %d\r\n", shiftFloats+i, readIdx);
                }
                if(readIdx >= RING_BUFFER_SIZE){
                    serial_printf("shiftFloats + i %d , readIdx %d\r\n", shiftFloats+i, readIdx);
                }
                eiAccBuffer[shiftFloats + i] = ringBuffer[readIdx];
                readIdx = (readIdx + 1) % RING_BUFFER_SIZE;
            }
            
            // [Step 3] Execute the standard run_classifier!
            memset(&result, 0x00, sizeof(&result));
            EI_IMPULSE_ERROR res = run_classifier(&signal, &result, debugFlg);

            if (res == EI_IMPULSE_OK) {
                if(result.anomaly > 0.3){
                    serial_printf("anomaly %f\r\n", result.anomaly);
                }else{
                    serial_printf("attack :%f\r\n", result.classification[0].value);
                    serial_printf("circle :%f\r\n", result.classification[1].value);
                    serial_printf("poke   :%f\r\n", result.classification[2].value);
                    serial_printf("\r\n");
                }
            } else {
                serial_printf("fail %d\r\n", res);
            }
        }
    }
    
    return 0;
}

/////////////////////////////////////////////////////////////
//  Actual process for reading the accelerometer
/////////////////////////////////////////////////////////////
uint32_t AccelReadSub(ADC_INPUT_POSITIVE pin) {
    ADC_InputSelect(ADC_MUX_A, pin, ADC_INPUT_NEGATIVE_VREFL);
    
    // 1. Start sampling (charge capacitor for conversion)
    ADC_SamplingStart();
    
    // 2. Wait briefly for sampling
    CORETIMER_DelayUs(10);
    
    // Safe empty loop wait to prevent conflict with the Core Timer
    for(volatile int i = 0; i < 150; i++);
    
    // 3. Start conversion (end sampling)
    ADC_ConversionStart();
    
    // 4. Wait for the result
    uint32_t timeout = 100000;
    while(!ADC_ResultIsReady() && timeout > 0) {
        timeout--;
    }
    
    // If a timeout occurs, output a warning and return 0
    if (timeout == 0) {
        serial_printf("ADC Error!\r\n");
        return 0; 
    }
    
    return ADC_ResultGet(ADC_RESULT_BUFFER_0);
}

/////////////////////////////////////////////////////////////
//  Buffer data acquisition function for EI
/////////////////////////////////////////////////////////////
int get_signal_data(size_t offset, size_t length, float *out_ptr) {
    // The SDK requests data from the slice buffer
    for (size_t i = 0; i < length; i++) {
        out_ptr[i] = eiAccBuffer[offset + i];
    }
    
    return 0;
}

template <typename T>
T my_map(T x, T in_min, T in_max, T out_min, T out_max) {
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

ei_pic32_porting.cpp (Porting functions such as memory allocation required by the Edge Impulse SDK):

#include <xc.h>
extern "C"{
    #include "definitions.h"
}
#include "edge-impulse-sdk/porting/ei_classifier_porting.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

char text_buffer[256];

void serial_printf(const char *format, ...) {
    va_list args;
    
    // 可変長引数を処理して buffer に文字列を作成
    va_start(args, format);
    vsnprintf(text_buffer, sizeof(text_buffer), format, args);
    va_end(args);
    
    // 確実に動くことが分かっているUART関数で送信
    UART1_Write((void*)text_buffer, strlen(text_buffer));
    // while(UART1_WriteIsBusy());
}

__attribute__((weak)) EI_IMPULSE_ERROR ei_run_impulse_check_canceled() {
    return EI_IMPULSE_OK;
}

__attribute__((weak)) void ei_printf(const char *format, ...) {
    serial_printf(format);
}

__attribute__((weak)) void ei_printf_float(float f) {
    ei_printf("%f", f);
}

__attribute__((weak)) void *ei_malloc(size_t size) {
    void *ret = malloc(size);
    if(ret == NULL){
        serial_printf("malloc err \r\n");
    }
    return ret;
}

__attribute__((weak)) void *ei_calloc(size_t nitems, size_t size) {
    void *ret = calloc(nitems, size);
    if(ret == NULL){
        serial_printf("calloc err\r\n");
    }
    return ret;
}

__attribute__((weak)) void ei_free(void *ptr) {
    free(ptr);
}

#if defined(__cplusplus) && EI_C_LINKAGE == 1
extern "C"
#endif
__attribute__((weak)) void DebugLog(const char* s) {
    ei_printf("%s", s);
}

__attribute__((weak)) EI_IMPULSE_ERROR ei_sleep(int32_t time_ms) {
    if (time_ms > 0) {
        // Harmony標準のコアタイマー遅延関数を使用
        CORETIMER_DelayMs((uint32_t)time_ms);
    }
    return EI_IMPULSE_OK;
}

uint64_t ei_read_timer_ms() {
    // コアタイマーのカウント値をミリ秒に変換して返す
    // 32ビットタイマーのオーバーフローを考慮し、内部で適切に処理された値を使用
    return (uint64_t)(CORETIMER_CounterGet() / (CORETIMER_FrequencyGet() / 1000));
}

uint64_t ei_read_timer_us() {
    // 同様にマイクロ秒(us)を返す関数
    return (uint64_t)(CORETIMER_CounterGet() / (CORETIMER_FrequencyGet() / 1000000));
}

extern "C" void _mon_putc(char c) {
    // UART1が空くまで待ってから1文字送信
    UART1_Write(&c, 1);
    // while(UART1_WriteIsBusy());
}

Thanks for sharing!

Let me ask internally to see if we can help you more @nbures

1 Like

Hi @nbures,

Can you globally define EI_CLASSIFIER_ALLOCATION_STATIC and rebuild / run the project? This will statically allocated the tensor arena. If this works, we know for sure something with dynamic allocation is failing.

Thanks,
Arjan

1 Like

Hi Arjan,

Thank you for your response and the suggestion.

I have globally defined EI_CLASSIFIER_ALLOCATION_STATIC, rebuilt, and ran the project. However, the issue still persists.

Regarding this matter, have you had the chance to execute run_classifier() in a similar environment? If so, could you please let me know if it operated without any problems?

Best regards,

Can you share the link to the Edge Impulse project?

1 Like

I believe you can verify this by accessing the following link:

Hi @nbures,
Thanks for sharing, I found the issue. In your project the classification and anomaly blocks are in the wrong order. For training and validation in studio order (in this case) doesn’t matter. On device, where we have constraints around memory, it does. I’ve rebuild your project with the 2 blocks swapped (see image) and was able to run it successfully on device. We will investigate how to prevent swapping of blocks further for future cases.

2 Likes

Hi Arjan,

Thank you so much for providing the solution!
I have verified that it works perfectly on my end too.
Thank you for your time and support.

Best,

1 Like