Introduction
In this blog I will show you how to deploy the Edge Impulse built model into the PSoC6 target. I faced some issues along the way and I will show you my fixes and workaround.
Creating C to C++ wrapper for the Edge Impulse library
Currently the Edge Impulse Studio does not support the PSoC6 target. But we can choose an option to build a C++ library using the Edge Impulse Studio deployment page.
After the library creation we can download it and use it with ModusToolBox. When you download it, it comes as a zip file. These are the contents inside the zip file
I extracted it into the ModusToolbox project folder under sources. To keep everything together, I put it into a new folder called ei-library.
As you can see, I made my own C function to wrap around the C++ functions I want to call. This is because the original ModusToolbox is written in C, and I did not want to make a lot of changes.
Thus, I kept the library in C++ to be interfaced to the C main code through this wrapper function
This is the outline of my C wrapper function. The wrapper is a C++ function that can call the Edge Impulse library. At the same time, it is a standalone function without classes, so it can be called by the C code in the main file.
It also implements other low-level functions needed for Edge Impulse to function.
Fixing Makefile Build Complications
If I place the ei-library inside the project sources directory, the Makefile will iterate through all the sub-directories in the include path as well (this is what we want).
Unfortunately, this results in the following error "Argument list too long". The problem is caused due to build.mk (Found in ~/ModusToolbox/workspace/mtb_shared/core-make/release-v1.8.0/make/core/build.mk). To include all the dependencies, all the filenames are echoed into a rpt (report) file using command line parameters. Because there is a maximum limit for bash command line parameters, the process will fail if there are too many files included.
A possible fix will be to use the Makefile $file directive. The $file function is for the Makefile to write values directly to a file. With this, we do not depend on bash parameter expansion and thus do not have the issue of having too many arguments
These are the changes I made:
Now, once we can compile, we realise again that the build fails, because .cc files are not being recognised and not compiled. The workaround is to rename .cc files to .cpp.
I used a python script fix_cc_files.py which simply renames all .cc files into .cpp files.
Inference Code with test dataset
Remember in the last blog post, I went to Live Classifications to test the inference on Edge Impulse itself. This time, go back to that screen and copy the raw features to the project. We will test this time if the model works on the board.
On the bottom left corner, there is a button to copy
I pasted the raw features into my project as follows.
float flick_test_features[300] = { -0.0012, 0.0018, 0.2559, -0.0013, 0.0001, 0.2677, 0.0002, -0.0050, 0.2787, -0.0022, -0.0043, 0.2811, -0.0025, 0.0020, 0.2770, -0.0019, 0.0036, 0.2690, -0.0008, 0.0081, 0.2632, -0.0021, 0.0040, 0.2665, 0.0012, 0.0001, 0.2650, 0.0013, -0.0008, 0.2675, 0.0001, 0.0008, 0.2695, 0.0006, -0.0030, 0.2750, -0.0008, -0.0079, 0.2759, 0.0030, -0.0134, 0.2761, 0.0032, -0.0135, 0.2756, 0.0005, -0.0104, 0.2719, 0.0001, -0.0091, 0.2666, 0.0018, -0.0088, 0.2579, 0.0000, -0.0108, 0.2556, 0.0013, -0.0101, 0.2577, -0.0023, -0.0079, 0.2662, -0.0027, -0.0050, 0.2725, -0.0013, -0.0050, 0.2712, 0.0015, -0.0056, 0.2673, 0.0017, -0.0066, 0.2615, 0.0014, -0.0081, 0.2609, 0.0010, -0.0054, 0.2572, 0.0013, -0.0026, 0.2599, 0.0008, -0.0027, 0.2647, 0.0027, -0.0061, 0.2780, 0.0168, -0.0234, 0.3177, 0.0215, -0.0543, 0.3958, 0.0217, -0.1028, 0.4767, 0.0427, -0.1607, 0.5000, 0.0243, -0.2238, 0.5000, -0.0246, -0.2872, 0.5000, -0.0738, -0.3390, 0.5000, -0.1049, -0.3277, 0.4725, -0.0272, -0.1154, 0.1424, -0.1361, 0.5000, -0.0646, -0.0737, 0.3787, 0.1631, -0.0194, -0.1022, 0.1937, -0.0687, -0.0986, -0.1071, 0.0194, -0.0825, -0.3551, 0.0573, -0.0682, -0.5000, -0.0164, 0.0321, -0.5000, 0.0692, 0.1144, -0.5000, 0.0752, 0.0353, -0.4355, 0.0569, -0.0310, -0.1805, 0.0272, 0.0400, 0.0867, -0.0474, 0.0692, 0.3643, -0.0319, 0.1151, 0.2115, 0.0169, 0.1776, -0.0457, 0.0091, 0.2443, -0.2266, 0.0287, 0.2654, -0.3042, 0.0356, 0.2248, -0.3088, 0.0204, 0.2280, -0.3235, 0.0044, 0.2685, -0.2924, -0.0095, 0.2429, -0.2127, -0.0060, 0.1542, -0.1289, 0.0101, 0.0582, -0.0186, 0.0192, -0.0185, 0.1429, 0.0076, -0.0058, 0.3127, -0.0489, 0.0575, 0.4305, -0.0043, 0.0894, 0.4264, 0.0010, 0.0765, 0.3806, 0.0149, 0.0345, 0.3411, 0.0287, 0.0031, 0.3025, 0.0418, -0.0230, 0.2767, 0.0556, 0.0322, 0.2443, 0.0998, -0.0398, 0.1574, 0.0073, 0.0532, 0.2225, 0.0144, 0.0083, 0.2305, -0.0531, 0.1018, 0.3565, 0.0176, -0.0142, 0.3753, -0.0047, 0.0032, 0.4043, 0.0149, -0.0087, 0.3634, 0.0089, 0.0013, 0.3269, 0.0093, 0.0213, 0.2880, 0.0079, 0.0469, 0.2626, 0.0122, 0.0519, 0.2604, 0.0159, 0.0495, 0.2552, 0.0160, 0.0497, 0.2560, 0.0144, 0.0466, 0.2698, 0.0121, 0.0415, 0.2853, 0.0115, 0.0310, 0.2940, 0.0103, 0.0243, 0.2973, 0.0094, 0.0131, 0.2981, 0.0127, 0.0049, 0.2936, 0.0043, 0.0102, 0.2800, 0.0057, 0.0205, 0.2571, 0.0089, 0.0252, 0.2399, 0.0092, 0.0298, 0.2364, 0.0075, 0.0311, 0.2346, 0.0093, 0.0291, 0.2427, 0.0111, 0.0233, 0.2556, 0.0070, 0.0203, 0.2658, 0.0098, 0.0094, 0.2771, 0.0115, 0.0072, 0.2832, 0.0039, 0.0102, 0.2790 };
To test that our model works, I formed the inference code which runs the classifier via the C wrapper function.
// Edge Impulse Inference
while (1) {
// Do classification
ei_c_wrapper_run_classifier(
total_length,
&get_feature_data,
&ei_result,
true);
// Print to screen
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
snprintf(task_accel_buffer, 100, "%s:%.5f",
ei_result.classification[ix].label,
ei_result.classification[ix].value
);
GUI_DispStringAt(task_accel_buffer, 0, 160 + ix*13);
}
}
Details changes here:
The result is printed onto the screen so we discover that the inference is giving 91% flick, so it matches that from Edge Impulse (as shown in my last blog)
Inference Code with live sensor data
Now to modify the program to classify our live sensor data instead of the hardcoded test dataset.
The additional part is shown in red -- it will collect 1 second of 100Hz data from the accelerometer and place it into the features buffer.
// Edge Impulse Inference
while (1) {
for (int i = 0; i < 300; i += 3) {
// Delay of 10ms (100 Hz frequency)
while ((xTaskGetTickCount() - previous) < 10);
previous = xTaskGetTickCount();
// Collect data into the array
mtb_bmi160_read(&motion_sensor, &data);
features[i + 0] = data.accel.x / (32768.0 * 2.0);
features[i + 1] = data.accel.y / (32768.0 * 2.0);
features[i + 2] = data.accel.z / (32768.0 * 2.0);
}
// Do classification
ei_c_wrapper_run_classifier(
total_length,
&get_feature_data,
&ei_result,
true);
// Print to screen
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
snprintf(task_accel_buffer, 100, "%s:%.5f",
ei_result.classification[ix].label,
ei_result.classification[ix].value
);
GUI_DispStringAt(task_accel_buffer, 0, 160 + ix*13);
}
}
With this, every 1 second, the results will be updated on the screen. It seems to be pretty accurate for my use case.
Conclusion
From today, the Makefile issue was a major setback if you have a very large project with many files included. I spent quite a significant time this week debugging the issue. Nevertheless, it is fixed now and I hope Infineon will be able to implement my fixes and workarounds in the next version. I have raised a bug report to the team on Github.
So now we have a working inference model on the microcontroller. In the next blog, I will revisit BLE and implement more features. Also I will interface an app to receive the data from the BLE communication for a simple demonstration