请选择 进入手机版 | 继续访问电脑版

肌电sensor应用----脑洞够不够大

[复制链接]

5

主题

9

帖子

47

积分

新手上路

Rank: 1

积分
47
yuyu_morning 发表于 2017-8-4 18:24:24
398 3
本帖最后由 yuyu_morning 于 2017-8-16 20:13 编辑

最近看到了一个关于肌电式头戴鼠标帖子,觉得里面应用的肌电传感器很不错,仔细了解之后有了几个想法。用arduino做了几个小的demo,感觉还不错,分享给大家!
1.识别是否有信号发生。
2.判断每次信号的强度。
先不多说什么,上图。(具体的例子我是按照范例做的,挺好的)

这张图是在Arduino里用串口绘图工具绘制出来的,并且数据经过提供的EMG_Filter库函数滤波。图一表示了我们握拳的一刹那,肌电sensor检测到的电压信号经过处理之后的数值变化;图二的信号值是我们肌肉在放松状态下的;图三是减去放松状态下信号的握拳信号值,是不是很有趣!可能说到这里已经有很多网友明白了,上面提到的两个idea是怎么实现了!对的,在这里我拿到图三的数据之后,用积分的方法来判断是否有信号值或者是信号的强度。
好了,上具体的硬件连接图,具体的硬件电路请看范例

看完图之后再搞份代码看看(代码一:检测信号是否产生!)
  1. #if defined(ARDUINO) && ARDUINO >= 100
  2. #include "Arduino.h"
  3. #else
  4. #include "WProgram.h"
  5. #endif

  6. //调用滤波库,对AD采集到的数据做滤波处理,滤掉工频50Hz
  7. #include "EMGFilters.h"   
  8.                  
  9. #define TIMING_DEBUG 1  //打印调试内容相关宏定义

  10. #define sensorInputPin A0 // AD采集通道定义

  11. /*枚举型定义:对滤波之后数据的处理方式。
  12. 1.一个信号的持续时间(暂时没有)
  13. 2.某个时间段内的信号数量*/

  14. enum EMGCalculatMode {
  15.   FistTimeMode = 50,
  16.   FistNumMode = 200
  17. };

  18. /*滤波处理部分,详细的请看EMG_Fliter库中example*/
  19. EMGFilters myFilter;
  20. SAMPLE_FREQUENCY sampleRate = SAMPLE_FREQ_500HZ;
  21. NOTCH_FREQUENCY humFreq = NOTCH_FREQ_50HZ;

  22. /*threshold:放松下的基准值
  23.    fistNum:统计信号的数量
  24.    程序中展示的是测完基准值并重新烧录进去的情况
  25. */
  26. long threshold = 500, fistNum = 0;

  27. /*两个关于时间的变量,用于确定信号的消失时间*/
  28. unsigned long timeMillis = 0, timeBeginzero = 0;

  29. /*两个关于积分的变量,用于确定信号是否持续存在*/
  30. long integralData = 0, integralDataEve = 0;

  31. /*TimeStandard变量判断信号的产生和消失。
  32. 例如这里设置的200ms代表如果超过该值积分值无变化,认为一次信号结束*/
  33. int  TimeStandard = (uint8_t)FistNumMode;

  34. /*时间记录标志变量*/
  35. bool remainFlag = false;

  36. void setup() {
  37.   myFilter.init(sampleRate, humFreq, true, true, true);//滤波初始化
  38.   Serial.begin(115200);//串口设置
  39. }
  40. void loop() {
  41.   int value, dataAfterFilter, envlope;
  42.   value = analogRead(sensorInputPin);//读取AD数据
  43.   dataAfterFilter = myFilter.update(value);//滤波处理
  44.   envlope = sq(dataAfterFilter);//平方,将小于0的数据去除
  45.   envlope = (envlope > threshold) ? envlope : 0;//将小于基准值的数据置0,说明处于放松状态

  46. /*如果threshold = 0,说明处于校准过程,下列程序不执行,只将信号值打印出来*/
  47.   if (threshold > 0) {
  48.     /*积分处理,将信号值不断累加,并与前一次采样时积分值对比,判断信号是否持续*/
  49.     integralDataEve = integralData;
  50.     integralData += envlope;

  51.     if ((integralDataEve == integralData) && (integralDataEve != 0)) {
  52.     /*如果积分值不变,并且不等于0,记录下此时时间;
  53.       如果积分值重新开始变化,remainflag为真,下次进入时重新进行时间记录*/
  54.       timeMillis = millis();
  55.       if (remainFlag) {
  56.         timeBeginzero = timeMillis;
  57.         remainFlag = false;
  58.       }
  59.        /*如果积分值超过200ms不发生变化,积分值清0,fistNum 计数加1*/
  60.       if ((timeMillis - timeBeginzero) > TimeStandard) {
  61.         integralDataEve = integralData = 0;
  62.         fistNum ++;
  63.         Serial.print("fistNum=");   Serial.println(fistNum);
  64.       }
  65.     }
  66.     else {
  67.       remainFlag = true;
  68.     }
  69.    //打印相关调试数据
  70.     if (TIMING_DEBUG) {
  71.       //            Serial.println(envlope);
  72.       //Serial.print("fistNum=");
  73.       //Serial.println(fistNum);
  74.     }
  75.   }
  76.   else {
  77.     Serial.println(envlope);
  78.   }
  79.   delayMicroseconds(500);
  80. }
复制代码
这个代码的执行分为两次,第一次将放松状态下的信号值(也就是threshold)打印出来,求出来之后将该值赋给threshold。第二次进去串口监视器之后,每握一次拳就会看到fistNum的值加1.

是不是很好玩!接下来还有更好玩的!既然每次握拳都可以检测到,那么握拳时信号的累加值能不能有更好的玩法呢!当然可以了。
再上一份代码!(代码二:在代码一的基础上修改,通过灯环的颜色变化体现出强度的不同!)
注:相同代码不进行注释!
  1. #if defined(ARDUINO) && ARDUINO >= 100
  2. #include "Arduino.h"
  3. #else
  4. #include "WProgram.h"
  5. #endif

  6. #include "EMGFilters.h"
  7. #include "MakeblockOrion.h"  //采用灯环反映信号强度,需要使用mbot中的RGB底层处理函数
  8. #define TIMING_DEBUG 1

  9. #define sensorInputPin A0 // input pin number
  10. MeRGBLed led(PORT_3);   //定义RGB数据输入引脚

  11. #define RGB_Count 24     //RGB灯个数

  12. /*颜色枚举*/
  13. enum ColorSet {
  14.   RED = 1,
  15.   ORANGE,
  16.   YELLOW,
  17.   GREEN,
  18.   CYAN,
  19.   BLUE,
  20.   PURPLE,
  21.   BLACK
  22. };

  23. /*RGB灯次序枚举,分别对应1-24*/
  24. enum RGBNum {
  25.   RGB_1 = 1,
  26.   RGB_2, RGB_3, RGB_4, RGB_5, RGB_6,
  27.   RGB_7, RGB_8, RGB_9, RGB_10, RGB_11,
  28.   RGB_12, RGB_13, RGB_14, RGB_15, RGB_16,
  29.   RGB_17, RGB_18, RGB_19, RGB_20, RGB_21,
  30.   RGB_22, RGB_23, RGB_24
  31. };

  32. enum EMGCalculatMode {
  33.   FistTimeMode = 50,
  34.   FistNumMode = 200
  35. };

  36. EMGFilters myFilter;

  37. SAMPLE_FREQUENCY sampleRate = SAMPLE_FREQ_500HZ;

  38. NOTCH_FREQUENCY humFreq = NOTCH_FREQ_50HZ;

  39. long threshold = 500, fistNum = 0;
  40. unsigned long timeMillis = 0, timeBeginzero = 0;

  41. /*新增powerRatio 作为信号强度变量*/
  42. long integralData = 0, integralDataEve = 0, powerRatio = 0;
  43. int  TimeStandard = (uint8_t)FistNumMode;
  44. bool remainFlag = false;

  45. void setup() {
  46.   led.setNumber(RGB_Count);//设置RGB灯数量
  47.   myFilter.init(sampleRate, humFreq, true, true, true);
  48.   Serial.begin(115200);
  49. }
  50. void loop() {
  51.   int value, dataAfterFilter, envlope;
  52.   value = analogRead(sensorInputPin);
  53.   dataAfterFilter = myFilter.update(value);
  54.   envlope = sq(dataAfterFilter);
  55.   envlope = (envlope > threshold) ? envlope : 0;

  56.   if (threshold > 0) {
  57.     integralDataEve = integralData;
  58.     integralData += envlope;
  59.     if ((integralDataEve == integralData) && (integralDataEve != 0)) {
  60.       timeMillis = millis();
  61.       if (remainFlag) {
  62.         timeBeginzero = timeMillis;
  63.         remainFlag = false;
  64.       }
  65.       if ((timeMillis - timeBeginzero) > TimeStandard) {
  66.         if (fistNum > 0) {
  67.           powerRatio = integralDataEve / threshold; //powerRatio为积分值与基准值的比值
  68.           if (fistNum == 5)
  69.             fistNum = 0;
  70.           if (powerRatio != 0) {
  71.             Serial.print("powerRatio=");   Serial.println(powerRatio);
  72.             /*灯环显示,满量程为700,可以调节*/
  73.             if (powerRatio < 700)
  74.               RGB_LoopLight(RGB_1, RGB_24, (ColorSet)(powerRatio / 100 + 1), 20);
  75.             else
  76.               RGB_LoopLight(RGB_1, RGB_24, BLACK, 20);
  77.           }
  78.           else
  79.             RGB_LoopLight(RGB_1, RGB_24, BLACK, 20);
  80.         }
  81.         integralDataEve = integralData = 0;
  82.         fistNum ++;

  83.       }
  84.     }
  85.     else {
  86.       remainFlag = true;
  87.     }
  88.     if (TIMING_DEBUG) {
  89.       //            Serial.println(envlope);
  90.       //      Serial.print("fistNum=");
  91.       //      Serial.println(fistNum);
  92.     }
  93.   }
  94.   else {
  95.     Serial.println(envlope);
  96.   }
  97.   delayMicroseconds(500);
  98. }
  99. /*点亮单个RGB函数,颜色可选*/
  100. void RGB_SingleLight(RGBNum Num, ColorSet Color)
  101. {
  102.   uint8_t num = (uint8_t)Num - 1;
  103.   switch (Color) {
  104.     case RED: led.setColorAt(num, 255, 0, 0); //red
  105.       break;
  106.     case ORANGE: led.setColorAt(num, 255, 165, 0); //orange
  107.       break;
  108.     case YELLOW: led.setColorAt(num, 255, 255, 0); //yellow
  109.       break;
  110.     case GREEN: led.setColorAt(num, 0, 255, 0); //green
  111.       break;
  112.     case CYAN: led.setColorAt(num, 0, 255, 255); //cyan
  113.       break;
  114.     case BLUE: led.setColorAt(num, 0, 0, 255); //blue
  115.       break;
  116.     case PURPLE: led.setColorAt(num, 160, 32, 240); //purple
  117.       break;
  118.     case BLACK: led.setColorAt(num, 0, 0, 0); //black
  119.       break;
  120.     default : led.setColorAt(num, 0, 0, 0);
  121.       break;
  122.   }
  123.   led.show();
  124. }

  125. /*循环点亮任意固定范围内RGB函数,颜色可选,顺序可选(顺、逆时针),延时微秒级*/
  126. void RGB_LoopLight(RGBNum BeginNum, RGBNum EndNum,
  127.                    ColorSet Color, long delayms)
  128. {
  129.   uint8_t num1 = (uint8_t)BeginNum, num2 = (uint8_t)EndNum;
  130.   if (num1 < num2) {
  131.     do {
  132.       delayMicroseconds(delayms);
  133.       RGB_SingleLight((RGBNum)num1, Color);
  134.       num1 ++;
  135.     } while (num1 <= num2);
  136.   }
  137.   else {
  138.     do {
  139.       delayMicroseconds(delayms);
  140.       RGB_SingleLight((RGBNum)num1, Color);
  141.       num1 --;
  142.     } while (num1 >= num2);
  143.   }
  144. }
复制代码

通过定义了一个powerRatio 来反映积分与放松状态下threshold的比值。比值越大,说明每次握拳积累的能量越大!其实这里还有灯环的程序,拿到这个powerRatio之后,通过不同的powerRatio值灯环显示不同的颜色(七种彩虹色代表了从0到700,可能你会一下就把它捏爆也说不准哦)!视频可能更直观一点!

好了,今天的分享就到这里了!说实话这个肌电sensor还是蛮好玩的!这两个demo做的还是有点问题!比如:每次摘下都需要重新测量基准值;肌电sensor没有戴好时积分值还是会变大等等。。。不过这就需要聪明的你来修改了!更多好的玩法还需要大家一起去开动大脑!燥起来!





本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

 楼主| yuyu_morning 发表于 2017-8-4 18:25:17
看看脑洞够不够大了!!!
回复 支持 反对

使用道具 举报

suxiang198 发表于 2017-8-7 15:31:49
cool, 这个肌电传感器很灵敏了,不过能否有更人性化和更加实用的应用呢?
人体在运动过程中就能拉动肌肉动作,通过这个肌电传感器,通过人体动作就可以触发某些电子设备的开关。比如在举哑铃时,哈哈,可以用这个方案,每举一次就可以带动某些声光设备变色或者响动,更好的营造运动健身的氛围,而且在健身完还会自动统计好本次锻炼举哑铃的次数~
回复 支持 反对

使用道具 举报

 楼主| yuyu_morning 发表于 2017-8-16 20:15:10
代码新加了注释,希望共同进步
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

热帖滚动