[应用相关] BP神经网络在stm32单片机上的应用

[复制链接]
 楼主| gaoke231 发表于 2019-4-30 17:07 | 显示全部楼层 |阅读模式
概述
硬件上使用STM32F4+MPU9150实现的神经网络识别手势,不过没有用IMU的地磁数据,只用了三轴加速度计和三轴陀螺仪的数据,板子是自己画的主要参照了意法官方的开发板的原理图(人生画的第一个板子还没有错误哈,容小逗比高兴一下。。。)。MPU9150的驱动是用的InvenSense提供的eMPL硬件抽象层,虽然这个driver配置imu内置DMP比较方便,但感觉这个bias矫正和姿态解算做的并不是很好,而且源码没公开不好改。不过识别部分用的是原始数据没用融合出的姿态数据,(姿态用在另一个功能上了)。考虑到单片机的计算性能不高(其实是训练部分不好移植^_^)于是把网络的训练部分放在在matlab上做的,然后把训练完的网络的阈值和权值导出来,放到单片机里。这里涉及到了单片机采集的数据怎么发给matlab的问题,幸好高版本的matlab对硬件的支持有大幅提升,可以通过串口来收数据。网络在单片机上的识别过程计算量还是挺大的,原始的imu数据(6Dof)经过一个巴特沃斯低通滤波器后放到一个类似于FIFO的数据结构中,从这个FIFO中首先进行间隔取数(间隔根据手势动作时间计算),并对取出的数进行归一化,然后将这些数据传给网络进行识别。过程中的滤波、归一化和网络计算都要进行大量的浮点运算,于是把logsig函数由泰勒展开改成了查表,还开了FPU,用了CMSIS-DSP。
 楼主| gaoke231 发表于 2019-4-30 17:09 | 显示全部楼层
Matlab串口接收
下面是创建串口obj的脚本
  1. try
  2.     try
  3.         obj=serial('COM15','baudrate',115200,'parity','none','databits',8,'stopbits',1);
  4.         flag_fetch=1;
  5.     catch
  6.         fprintf('Create Obj Error');
  7.     end
  8.     obj.BytesAvailableFcnMode = 'terminator';
  9.     obj.Terminator = 'c';
  10.     obj.BytesAvailableFcn =@serial_nn_callback;
  11.     try
  12.         fopen(obj);
  13.     catch
  14.         fprintf('Open Error\n');
  15.         break;
  16.     end

  17.     pause;
  18.     flag_fetch=0;
  19. catch

  20.     fprintf('Serial Read Error!\n');
  21. end
  22. fclose(obj);
  23. delete(obj);
  24. clear obj;


 楼主| gaoke231 发表于 2019-4-30 17:09 | 显示全部楼层
下位机将imu数据以数据帧的形式发送上来,数据帧自定义协议,帧以字符’c’结尾,这里将串口配置为terminator模式,设置terminator=’c’,这样该模式下接收到字符’c’就调用一次回调函数”serial_nn_callback”


 楼主| gaoke231 发表于 2019-4-30 17:09 | 显示全部楼层
下面是回调函数的框架:
  1. function [c]= serial_nn_callback(obj, ~)

  2. % var start
  3. ...
  4. % var end

  5. try
  6.     n = get(obj, 'BytesAvailable');
  7.     if n>20&& (flag_fetch==1)
  8.         a = fread(obj, 29, 'uchar');%数据帧长度
  9.         if a(1)~='A'
  10.             fscanf(obj) ;
  11.         end
  12.         % 协议解析 start
  13.         % 协议解析 end
  14.     end
  15.     catch
  16. end


 楼主| gaoke231 发表于 2019-4-30 17:09 | 显示全部楼层
这里读取之后要首先判断数据帧头是否吻合,如果不吻合立即调用 fscanf(obj) 清除串口的缓存,否则的话接收到的数据都是串的。这样就可以成功的接收到下位机发送的数据。
 楼主| gaoke231 发表于 2019-4-30 17:12 | 显示全部楼层

网络训练
这个部分比较简单,用的单S激活函数,mse能到的最小值有限,后来试了下双S激活函数,mse可以很小。不过实际测试时单S激活函数的识别率已经够用

  1. input=input';
  2. net = newff( minmax(input) , [mid_layer,output_layer] , { 'logsig' 'logsig' }  ,'traingdx') ;
  3. net.trainparam.show = 50 ;
  4. net.trainparam.epochs = 500 ;
  5. net.trainparam.goal = 0.00001 ;
  6. net.trainParam.lr = 0.001 ;


 楼主| gaoke231 发表于 2019-4-30 17:12 | 显示全部楼层
网络导出
w_i2l=net.IW{1,1};%输入层到中间层的权值
b_i2l=net.b{1,1};%输入层到中间层的阈值
w_l2o=net.LW{2,1};%中间层到输出层的权值
b_l2o=net.b{2,1};%中间层到输出层的阈值
% 导出函数 start
...
% 导出函数 end
 楼主| gaoke231 发表于 2019-4-30 17:12 | 显示全部楼层
单片机上网络计算函数
  1. void layer_to_layer(
  2.     int lin_num,
  3.     int lout_num,
  4.     float32_t* input,
  5.     float32_t* output,
  6.     float32_t* w,
  7.     float32_t* b
  8.     )
  9. {
  10.      float32_t *temp;
  11.      memset(output, 0,
  12.                 lout_num * sizeof(float32_t));
  13.      temp = (float32_t *)malloc(lin_num * sizeof(float32_t));
  14.      for (int i=0;i<lout_num;i++)
  15.      {
  16. #ifdef    ARM_M4        
  17.         arm_mult_f32(input,w+i*lin_num,temp,lin_num);    //CMSIS-DSP库 向量相乘
  18. #else
  19.                 for (int i=0;i<lin_num;i++)                      //非DSP指令
  20.                 {
  21.                   *(temp+i)=*(input)*(*(w+i*lin_num))
  22.                 }         
  23. #endif
  24.         for (int j=0;j<lin_num;j++)
  25.         {
  26.             *(output+i)=*(output+i)+*(temp+j);
  27.         }
  28.         *(output+i)=*(output+i)+*(b+i);
  29.         *(output+i)=logsig_fun(*(output+i));
  30.      }
  31.      free(temp);
  32. }


 楼主| gaoke231 发表于 2019-4-30 17:13 | 显示全部楼层
原文:https://blog.csdn.net/u011070641/article/details/50491826
zhuomuniao110 发表于 2019-4-30 21:42 | 显示全部楼层
都能跑这么高端的了
您需要登录后才可以回帖 登录 | 注册

本版积分规则

54

主题

1310

帖子

5

粉丝
快速回复 在线客服 返回列表 返回顶部

54

主题

1310

帖子

5

粉丝
快速回复 在线客服 返回列表 返回顶部