In this blog, I will show you how to deploy the SensiML model into the PSoC6 target. In the previous blog, I showed you how to download the SensiML knowledge pack for the M4 microcontroller. After downloading the knowledge pack you need to unzip the downloaded zip file.
For firmware development, we need the knowledgepack folder, and more specifically, the knowledgepack_project folder within. This contains both the library and full example code for running Recognition on the device. Downloads for other platforms will be similar, with the library file being in a folder titled libsensiml
.
Regardless of the platform being used for development, these are the steps to follow:
- Open knowledgepack_project
- Copy this directory to a location of your choosing in your source code (remember where you put it!)
- Setting up IDE workspace/project (see below for specific instructions)
- Add in library calls to your code.
For Linking the library to your own application
1. Copy `libsensiml` folder to your desired location
2. Add an include path to your Makefile/IDE project
Running in your application
1. Add `#include "kb.h"` to the file you wish to use the library in.
2. Before running any other code for the model, call `kb_model_init();`
3. To add sensor data a sample at a time, call `kb_run_model(<samples>, 0, <model_number>);`
The following sample code is developed for recognizing the level of the device using the model:
#include "cy_pdl.h" #include "cyhal.h" #include "cybsp.h" #include "cy_retarget_io.h" #include "kb.h" #include "kb_defines.h" #include "testdata.h" #include "kb_debug.h" static char str_buffer[2048]; static uint8_t fv_arr[MAX_VECTOR_LENGTH]; #if SML_PROFILER float recent_fv_times[MAX_VECTOR_LENGTH]; unsigned int recent_fv_cycles[MAX_VECTOR_LENGTH]; #endif #define BAUD_RATE 115200 //#define BAUD_RATE 230400 #define CONFIG_BUF_SIZE 200 /* ADC input pin */ #define VOLTAGE_CHANNEL (P10_0) #define CURRENT_CHANNEL (P10_1) /* Number of scans every time ADC read is initiated */ #define NUM_SCAN (1) ///String inputString = ""; // a string to hold incoming data bool config_receive = false; uint8_t read_data; /* Variable to store the received character through terminal */ /* ADC Channel constants*/ enum ADC_CHANNELS { CHANNEL_0 = 0, CHANNEL_1, NUM_CHANNELS } adc_channel; void adc_multi_channel_init(void); void adc_multi_channel_process(void); static void adc_event_handler(void* arg, cyhal_adc_event_t event); void calculate_real_power(int16_t no_of_sample); void handle_error(void) { /* Disable all interrupts. */ __disable_irq(); CY_ASSERT(0); } void sml_output_results(int model_index, int model_result) { bool feature_vectors = true; int size = 0; kb_print_model_result(model_index, model_result, str_buffer, 1, fv_arr); printf("%s\r\n", str_buffer); #if SML_PROFILER memset(str_buffer, 0, 2048); kb_print_model_cycles(model_index, model_result, str_buffer, recent_fv_cycles); printf("%s\r\n", str_buffer); memset(str_buffer, 0, 2048); kb_print_model_times(model_index, model_result, str_buffer, recent_fv_times); printf("%s\r\n", str_buffer); #endif }; void sml_recognition_run(signed short *data, int num_sensors) { int ret; ret = kb_run_model((SENSOR_DATA_T *)data, num_sensors, KB_MODEL_TESTMODEL_INDEX); if (ret >= 0){ sml_output_results(KB_MODEL_TESTMODEL_INDEX, ret); kb_reset_model(0); }; } static int16_t sensorRawData[3]; static SENSOR_DATA_T* data = (SENSOR_DATA_T*) &sensorRawData[0]; static int sensorRawIndex = -1; /******************************************************************************* * Global Variables *******************************************************************************/ /* ADC Object */ cyhal_adc_t adc_obj; /* ADC Channel 0 Object */ cyhal_adc_channel_t adc_chan_0_obj; /* ADC Channel 1 Object */ cyhal_adc_channel_t adc_chan_1_obj; /* Default ADC configuration */ const cyhal_adc_config_t adc_config = { .continuous_scanning=false, // Continuous Scanning is disabled .average_count=1, // Average count disabled .vref=CYHAL_ADC_REF_VDDA, // VREF for Single ended channel set to VDDA .vneg=CYHAL_ADC_VNEG_VSSA, // VNEG for Single ended channel set to VSSA .resolution = 12u, // 12-bit resolution .ext_vref = NC, // No connection .bypass_pin = NC }; // No connection /* Asynchronous read complete flag, used in Event Handler */ static bool async_read_complete = false; /* Variable to store results from multiple channels during asynchronous read*/ int32_t result_arr[NUM_CHANNELS * NUM_SCAN] = {0}; int main(void) { /* Variable to capture return value of functions */ cy_rslt_t result; /* Initialize the device and board peripherals */ result = cybsp_init(); /* Board init failed. Stop program execution */ if (result != CY_RSLT_SUCCESS) { CY_ASSERT(0); } /* Enable global interrupts */ __enable_irq(); /* Initialize retarget-io to use the debug UART port */ result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE); /* retarget-io init failed. Stop program execution */ if (result != CY_RSLT_SUCCESS) { CY_ASSERT(0); } /* Initialize Channel 0 and Channel 1 */ adc_multi_channel_init(); /* Update ADC configuration */ result = cyhal_adc_configure(&adc_obj, &adc_config); if(result != CY_RSLT_SUCCESS) { printf("ADC configuration update failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } int16_t no_smaple = 50; kb_model_init(); int ret = 0; int num_sensors = 1; for (;;) { calculate_real_power(no_sample); data = sensorRawData; sml_recognition_run(data, num_sensors); } } void adc_multi_channel_init(void) { /* Variable to capture return value of functions */ cy_rslt_t result; /* Initialize ADC. The ADC block which can connect to pin 10[0] is selected */ result = cyhal_adc_init(&adc_obj, VOLTAGE_CHANNEL, NULL); if(result != CY_RSLT_SUCCESS) { printf("ADC initialization failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } /* ADC channel configuration */ const cyhal_adc_channel_config_t channel_config = { .enable_averaging = false, // Disable averaging for channel .min_acquisition_ns = 1000, // Minimum acquisition time set to 1us .enabled = true }; // Sample this channel when ADC performs a scan /* Initialize a channel 0 and configure it to scan P10_0 in single ended mode. */ result = cyhal_adc_channel_init_diff(&adc_chan_0_obj, &adc_obj, VOLTAGE_CHANNEL, CYHAL_ADC_VNEG, &channel_config); if(result != CY_RSLT_SUCCESS) { printf("ADC voltage channel initialization failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } /* Initialize a channel 1 and configure it to scan P10_0 in single ended mode. */ result = cyhal_adc_channel_init_diff(&adc_chan_1_obj, &adc_obj, CURRENT_CHANNEL, CYHAL_ADC_VNEG, &channel_config); if(result != CY_RSLT_SUCCESS) { printf("ADC current channel initialization failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } /* Register a callback to handle asynchronous read completion */ cyhal_adc_register_callback(&adc_obj, &adc_event_handler, result_arr); /* Subscribe to the async read complete event to process the results */ cyhal_adc_enable_event(&adc_obj, CYHAL_ADC_ASYNC_READ_COMPLETE, CYHAL_ISR_PRIORITY_DEFAULT, true); printf("ADC is configured in multichannel configuration.\r\n\n"); printf("Voltage channel is configured in single ended mode, connected to pin \r\n"); printf("P10_0. Provide input voltage at P10_0\r\n"); printf("Current channel is configured in single ended mode, connected to pin \r\n"); printf("P10_1. Provide input voltage at P10_1\r\n"); //cyhal_system_delay_ms(200); } void adc_multi_channel_process(void) { /* Variable to capture return value of functions */ cy_rslt_t result; /* Variable to store ADC conversion result from channel 0 */ int32_t adc_result_0 = 0; /* Variable to store ADC conversion result from channel 1 */ int32_t adc_result_1 = 0; /* Initiate an asynchronous read operation. The event handler will be called * when it is complete. */ result = cyhal_adc_read_async_uv(&adc_obj, NUM_SCAN, result_arr); if(result != CY_RSLT_SUCCESS) { printf("ADC async read failed. Error: %ld\n", (long unsigned int)result); CY_ASSERT(0); } /* * Read data from result list, input voltage in the result list is in * microvolts. Convert it millivolts and print input voltage * */ adc_result_0 = result_arr[CHANNEL_0]/1000; adc_result_1 = result_arr[CHANNEL_1]/1000; printf("Voltage channel input: %4ldmV \t Current channel input: %4ldmV\r\n", (long int)adc_result_0, (long int)adc_result_1); /* Clear async read complete flag */ async_read_complete = false; } static void adc_event_handler(void* arg, cyhal_adc_event_t event) { if(0u != (event & CYHAL_ADC_ASYNC_READ_COMPLETE)) { /* Set async read complete flag to true */ async_read_complete = true; } } void calculate_real_power(int16_t no_of_sample){ cy_rslt_t adc_result; int16_t ins_voltage = 0; int16_t ins_current = 0; int32_t actual_ins_voltage = 0; int32_t actual_ins_current = 0; int32_t ins_power = 0; int16_t real_power = 0; for(int16_t i=0; i<no_of_sample; i++) { adc_result = cyhal_adc_read_async_uv(&adc_obj, NUM_SCAN, result_arr); if(adc_result != CY_RSLT_SUCCESS) { CY_ASSERT(0); } ins_voltage = result_arr[CHANNEL_0]/1000; ins_current = result_arr[CHANNEL_1]/1000; //printf("Voltage channel input: %4ldmV \t Current channel input: %4ldmV\r\n", (long int)ins_voltage, (long int)ins_current); actual_ins_voltage = (ins_voltage * 4)/1000; //multiplied by scale factor of sensor actual_ins_current = (ins_current * 1)/1000; //printf("ins voltage: %4ldmV \t ins current: %4ldmV\r\n", (long int)actual_ins_voltage, (long int)actual_ins_current); ins_power = ins_power + actual_ins_voltage * actual_ins_current; async_read_complete = false; //cyhal_system_delay_ms(1); cyhal_system_delay_us(7); //this delay is required to work adc read properly } real_power = ins_power/no_of_sample; sensorRawData[sensorRawIndex++] = ins_current; sensorRawData[sensorRawIndex++] = ins_voltage; sensorRawData[sensorRawIndex++] = real_power; ins_power = 0; //int16_t power = real_power; //printf("Power consumption: %4ldW\r\n", (long int)real_power); }
The key functions in the code for recognition are:
ret = kb_run_model((SENSOR_DATA_T *)data, num_sensors, KB_MODEL_rank_0_INDEX);
This is the call to pass a single sample of data to your model. The model will take care of any segmentation and feature extraction/classification from there.
if (ret >= 0)
If you get a value from kb_run_model greater than or equal to zero, you have received a classification. 0 means Unknown. Above zero pertains to the class-map you see when running SensiML Analytics Studio:
sml_output_results(KB_MODEL_rank_0_INDEX, ret);
For the above example in main.c, we are only printing the results out. Here is where your development comes in. You get to decide how you handle the output from the classifier.
kb_reset_model(KB_MODEL_rank_0_INDEX);
After classification is completed, you need to reset the model.
Configuring MQTT
For sending the detected device data to the cloud I used MQTT protocol. In this project I am using Mosquitto broker from https://mosquitto.org/. To work perfectly you need to configure your wifi credentials in configs/wifi_config.h: Modify the macros WIFI_SSID
, WIFI_PASSWORD
, and WIFI_SECURITY
to match with that of the Wi-Fi network that you want to connect.
MQTT configuration: Set up the MQTT client and configure the credentials in configs/mqtt_client_config.h. Some of the important configuration macros are as follows:
MQTT_BROKER_ADDRESS
: Hostname of the MQTT broker. For mosquitto it isMQTT_PORT
: Port number to be used for the MQTT connection. As specified by IANA (Internet Assigned Numbers Authority), port numbers assigned for MQTT protocol are 1883 for non-secure connections and 8883 for secure connections. However, MQTT brokers may use other ports. Configure this macro as specified by the MQTT broker.MQTT_SECURE_CONNECTION
: Set this macro to1
if a secure (TLS) connection to the MQTT broker is required to be established; else0
.MQTT_USERNAME
andMQTT_PASSWORD
: User name and password for client authentication and authorization, if required by the MQTT broker. However, note that this information is generally not encrypted and the password is sent in plain text. Therefore, this is not a recommended method of client authentication.CLIENT_CERTIFICATE
andCLIENT_PRIVATE_KEY
: Certificate and private key of the MQTT client used for client authentication. Note that these macros are applicable only whenMQTT_SECURE_CONNECTION
is set to1
. Not required for mosquitto.ROOT_CA_CERTIFICATE
: Root CA certificate of the MQTT broker. Not required for mosquitto.