边缘AI应用是在MCU上运行训练好的机器学习模型,MCU采集不同的传感器数据,利用机器学习模型对其进行模式辨识。2025年,各家推出的边缘AI模型训练中,通过识别IMU六轴传感器数据来判断轨迹的类型。Micochip同样也有利用IMU数据进行轨迹识别的应用。
dsPIC33CK64MC105 Curiosity Nano是本次活动的开发板,搭载100MHz高性能数字信号控制器,配备64KB ECC Flash和8KB RAM,可以运行小型的机器学习模型。本文以dsPIC33CK64MC105 Curiosity Nano运行边缘AI模型对IMU数据进行识别,分辨轨迹的类型。
1、模型训练模型概述
边缘AI应用的开发流程一般分为数据收集、数据标注、数据训练、模型部署四个步骤。
使用Microchip的MPLAB X IDE开发边缘AI程序的流程如上图,各个阶段对应的工具如流程图中对应的描述。各个阶段完成的操作如下:
- 数据收集阶段:在设备(开发板)上烧录用于收集数据的固件,采集不同轨迹下的IMU传感器数据;
- 数据标注阶段:在MPLAB ML Model Builder中对采集到数据进行标注和分类,用于后续的数据训练;
- 模型训练阶段:在MPLAB ML Model Builder中选择训练模型,对模型进行训练,根据部署的平台,编译出可以在平台上运行的库文件和示例文件;
- 模型部署阶段:使用编译出的模型可执行库文件,创建边缘AI应用。
具体的操作流程可以参考Microchip官方的指南,MPLAB Machine Learning Development Suite User's Guide
下面对各个阶段的操作进行介绍
2、数据收集阶段
在dsPIC33CK64MC105 Curiosity Nano采集数据,需要用户参考示例程序编写具有相同功能的数据采集程序。Microchip官方提供有同系列的另一款芯片的采集例程可供参考。
https://github.com/microchip-pic-avr-examples/ml-dspic33ck-curiosity-imu-data-logger
在实际移植过程中发现,采用硬件I2C时,数据采样会有异常。这里采用软件模拟I2C的方式来进行替换,采用的程序是Microchip官方提供的I2C模拟驱动,其应用的平台正好是该款芯片。
https://www.microchip.com/en-us/software-library/dspic33c_i2c_softwarelibrary
在MPLAB X IDE中创建新的工程选择,选择芯片型号为dsPIC33CK64MC105。
进入工程后,点击工具栏的MCC图标,进入代码配置工具中,对MCU的外设进行设置。MCC工具启动有时会不成功,这时关闭空白的MCC窗口,重新点击MCC图标启动即可。
在左侧的工具栏中选择,修改开发的调试接口的引脚开启调试功能。
在界面上点击添加UART、GPIO、EXIT驱动库,并设置UART串口为中断传输、外部中断引脚等具体的配置信息。
完成上述配置,保存并生成代码。添加的I2C Software文件添加到工程中即可使用,使用的引脚为RB8(SCL)和RB9(SDA)。
参考imu-data-logger中的代码,将其中的部分文件和I2C驱动库复制到工程目录中并更新,修改其中不需要的部分,由于改动部分较多,可以查看附件中的工程源码。添加MPU6050的初始化函数和数据读取函数如下。
void mpu6050_sensor_init(void)
{
uint8_t regval=0;
uint8_t i2c_write_buffer[2];
//MPU6050 Initializer
i2c_write_buffer[0]=MPU6050_REG_PWR_MGMT_1;
i2c_write_buffer[1]=0x80;
//if(I2C1_Host_Write(MPU6050_ADDRESS_AD0_LOW,i2c_write_buffer,2))
// DELAY_milliseconds(1000);
mpu6050_write(i2c_write_buffer[0],i2c_write_buffer[1]);
DELAY_milliseconds(1000);
i2c_write_buffer[0]=MPU6050_REG_PWR_MGMT_1;
i2c_write_buffer[1]=0x01;
//if(I2C1_Host_Write(MPU6050_ADDRESS_AD0_LOW,i2c_write_buffer,2))
// printf("MPU6050_REG_PWR_MGMT_1 write\r\n");
mpu6050_write(i2c_write_buffer[0],i2c_write_buffer[1]);
DELAY_milliseconds(1000);
i2c_write_buffer[0]=MPU6050_REG_PWR_MGMT_2;
i2c_write_buffer[1]=0x00;
//if(I2C1_Host_Write(MPU6050_ADDRESS_AD0_LOW,i2c_write_buffer,2))
// printf("MPU6050_REG_PWR_MGMT_2 write\r\n");
mpu6050_write(i2c_write_buffer[0],i2c_write_buffer[1]);
DELAY_milliseconds(1000);
i2c_write_buffer[0]=MPU6050_REG_SMPRT_DIV;
i2c_write_buffer[1]=0x09;
///if(I2C1_Host_Write(MPU6050_ADDRESS_AD0_LOW,i2c_write_buffer,2))
// printf("MPU6050_REG_SMPRT_DIV write\r\n");
mpu6050_write(i2c_write_buffer[0],i2c_write_buffer[1]);
//if(I2C1_Host_WriteRead(MPU6050_ADDRESS_AD0_LOW,&i2c_write_buffer[0],1,®val,1))
regval=mpu6050_read(i2c_write_buffer[0]);
{
DELAY_milliseconds(1000);
printf("MPU6050_REG_SMPRT_DIV value is %x\r\n",regval);
}
i2c_write_buffer[0]=MPU6050_REG_CONFIG;
i2c_write_buffer[1]=0x06;
// if(I2C1_Host_Write(MPU6050_ADDRESS_AD0_LOW,i2c_write_buffer,2))
// printf("MPU6050_REG_CONFIG write\r\n");
mpu6050_write(i2c_write_buffer[0],i2c_write_buffer[1]);
regval=mpu6050_read(i2c_write_buffer[0]);
//if(I2C1_Host_WriteRead(MPU6050_ADDRESS_AD0_LOW,&i2c_write_buffer[0],1,®val,1))
{
DELAY_milliseconds(1000);
printf("MPU6050_REG_CONFIG value is %x\r\n",regval);
}
i2c_write_buffer[0]=MPU6050_REG_GYRO_CONFIG;
i2c_write_buffer[1]=0x18;
//if(I2C1_Host_Write(MPU6050_ADDRESS_AD0_LOW,i2c_write_buffer,2))
// printf("MPU6050_REG_GYRO_CONFIG write\r\n");
mpu6050_write(i2c_write_buffer[0],i2c_write_buffer[1]);
regval=mpu6050_read(i2c_write_buffer[0]);
//if(I2C1_Host_WriteRead(MPU6050_ADDRESS_AD0_LOW,&i2c_write_buffer[0],1,®val,1))
{
DELAY_milliseconds(1000);
printf("MPU6050_REG_GYRO_CONFIG value is %x\r\n",regval);
}
i2c_write_buffer[0]=MPU6050_REG_ACCEL_CONFIG;
i2c_write_buffer[1]=0x00;
//if(I2C1_Host_Write(MPU6050_ADDRESS_AD0_LOW,i2c_write_buffer,2))
// printf("MPU6050_REG_ACCEL_CONFIG write\r\n");
mpu6050_write(i2c_write_buffer[0],i2c_write_buffer[1]);
regval=mpu6050_read(i2c_write_buffer[0]);
//if(I2C1_Host_WriteRead(MPU6050_ADDRESS_AD0_LOW,&i2c_write_buffer[0],1,®val,1))
{
DELAY_milliseconds(1000);
printf("MPU6050_REG_ACCEL_CONFIG value is %x\r\n",regval);
}
i2c_write_buffer[0]=MPU6050_REG_INT_PIN_CFG;
i2c_write_buffer[1]=0x00;
//if(I2C1_Host_Write(MPU6050_ADDRESS_AD0_LOW,i2c_write_buffer,2))
// printf("MPU6050_REG_INT_PIN_CFG write\r\n");
mpu6050_write(i2c_write_buffer[0],i2c_write_buffer[1]);
regval=mpu6050_read(i2c_write_buffer[0]);
//if(I2C1_Host_WriteRead(MPU6050_ADDRESS_AD0_LOW,&i2c_write_buffer[0],1,®val,1))
{
DELAY_milliseconds(1000);
printf("MPU6050_REG_INT_PIN_CFG value is %x\r\n",regval);
}
i2c_write_buffer[0]=MPU6050_REG_INT_ENABLE;
i2c_write_buffer[1]=0x01;
//if(I2C1_Host_Write(MPU6050_ADDRESS_AD0_LOW,i2c_write_buffer,2))
// printf("MPU6050_REG_INT_ENABLE write\r\n");
mpu6050_write(i2c_write_buffer[0],i2c_write_buffer[1]);
regval=mpu6050_read(i2c_write_buffer[0]);
//if(I2C1_Host_WriteRead(MPU6050_ADDRESS_AD0_LOW,&i2c_write_buffer[0],1,®val,1))
{
DELAY_milliseconds(1000);
printf("MPU6050_REG_INT_ENABLE value is %x\r\n",regval);
}
mpu6050status=true;
}
void MPU6050_GetData_Conti(int16_t *AccX,int16_t *AccY,int16_t *AccZ,int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ)
{
uint8_t Data[14];
uint8_t reg;
reg=MPU6050_REG_ACCEL_XOUT_H;
for(uint8_t i=0;i<14;i++)
{
Data[i]=mpu6050_read(reg);
reg++;
}
*AccX=(Data[0] << 8) | Data[1];
*AccY=(Data[2] << 8) | Data[3];
*AccZ=(Data[4] << 8) | Data[5];
*GyroX=(Data[8] << 8) | Data[9];
*GyroY=(Data[10] << 8) | Data[11];
*GyroZ=(Data[12] << 8) | Data[13];
}
程序在读取到MPU6050的数据发送到PC端时,会在数据帧的头部和尾部添加标识符,用于MPLAB Data Visualizer中进行识别。
#if DATA_VISUALIZER_BUILD
//send out data as its read
uint8_t headerbyte = MPDV_START_OF_FRAME;
uint8_t *ptr = (uint8_t*)&ring_buffer[ringIdx][0];
for(uint16_t i=0;i<rdcnt;i++)
{
UART1_Write(headerbyte);
for(uint16_t j=0;j < sizeof(buffer_frame_t);j++)
{
UART1_Write(*ptr++);
}
headerbyte = ~headerbyte;
UART1_Write(headerbyte);
headerbyte = ~headerbyte;
}
#endif
数据采集程序的移植完成并烧录到开发板后。在MPLAB X IDE中的工具栏上打开MPLAB Data Visualizer插件,也可以通过MPLAB ML Model Builder(需要登录到Microchip账户)进入MPLAB Data Visualizer,后者可以在创建模型训练工程后,将数据直接导入到云端的工程中。
在通讯配置界面设置数据采集的通讯参数和数据帧的格式。然后开始采集数据。
手持开发板沿不同轨迹运动,采集IMU数据传输到PC,
可以使用数据观察窗口的时间工具选择数据区间,并将数据保存到本地便于数据的管理。
3、数据标注阶段
数据采集完成后,需要对数据进行标注和分类,用于后续的训练和测试。在工程中添加元数据用于表示不同的轨迹类型,便于后续的数据标注。
上述采集几种轨迹的数据,对其中的数据进行分段处理,每一段的时间为60s。
标注完成后,可以查看不同类型数据的样本数量。
4、模型训练阶段
在Model Train的部分,选择“Gesture Recognition”训练模板进行后续的模型训练。根据引导设置模型的参数后,即可开始模型的训练。
训练完成后,可以查看训练完成的模型的效果。
5、模型部署阶段
在DownloadModel选项卡,可以选择模型,并编译下载响应的库文件。
解压出来的库文件包含以下内容。
参考官方的示例程序和移植手册。在程序中添加以下代码,输出模型的辨识结果。
while (rdcnt--) {
int ret = sml_recognition_run((snsr_data_t *) ptr++, SNSR_NUM_AXES);
ringbuffer_advance_read_index(&snsr_buffer, 1);
if (ret >= 0) {
/* Update the voting counts */
votecounts[votehist[0]]--;
for (int i=1; i < NUM_VOTES; i++)
votehist[i-1] = votehist[i];
votehist[NUM_VOTES-1] = ret;
votecounts[ret]++;
/* If there's a new state that is consistently classified as the same class, update the class ID */
if (ret != clsid) {
/* Get the class with the most votes */
int maxval = -1, maxcls = -1;
for (int i=0; i < NUM_CLASSES; i++) {
if (votecounts[i] > maxval) {
maxval = votecounts[i];
maxcls = i;
}
}
/* Only touch the LEDs if we decided on a new class */
if (maxval >= MAJORITY_VOTES && maxcls != clsid) {
clsid = maxcls;
if (clsid == 2) {
printf("Gesture classified as wave %d\n",votecounts[clsid]);
}
else if (clsid == 0) {
}
else if (clsid == 3) {
printf("Gesture classified as up & down %d \n",votecounts[clsid]);
}
else if (clsid == 4) {
printf("Gesture classified as wheel %d \n",votecounts[clsid]);
}
else if (clsid == 1) {
printf("Gesture classified as idle %d \n",votecounts[clsid]);
}
else {
}
}
}
kb_reset_model(0);
}
}
模型的识别效果如下,视频演示见链接。
6、总结
本次项目中共完成两个工程,实现数据采集和边缘AI识别轨迹。感谢得捷电子和电子森林举办的Funpack活动。通过参加这次活动熟悉如何在Microchip平台上运行边缘AI应用,采集IMU传感器数据、标注数据和模型的训练、部署。作为网友们在开发边缘AI应用的一个参考。对于普通开发者而言,在开发过程中,想要效果好,需要用于训练的数据尽可能多,但是对应的模型文件也就更大。