项目二--云端计步器

来自Microduino Wikipedia
Shengkai81@gmail.com讨论 | 贡献2015年6月20日 (六) 07:59的版本 ADXL34传感器介绍
跳转至: 导航搜索

本教程也是一个microduino和microWRT结合的项目。主要是通过microduino和传感器模块来检测被测对象的运动量,然后通过蓝牙模块将数据传送到microWRT上的microduino core模块。 Core模块进而将数据通过串口发送给microWRT。最后microWRT会把数据上传到yeelink,用户在任何地方只要能够访问网络,就可以看到运动量。

本教程有microWRT玩家“while(高群)”在microWRT内测活动中完成的。非常感谢“while”。最终解释权归“while”所有。

本教程中的所有代码可以通过下面链接直接下载。 源程序: 所有源程序

硬件设备

  • microWRT 一个
  • Microduino-Core 二个
  • Microduino-BLE 二个
  • Microduino-USBTTL 一个
  • ADXL345传感器 一个

ADXL34传感器介绍

项目初始设计阶段是计划使用MPU6050加速度传感器来监测运动数据,但MP6050在省电方面太难调试,之后就把传感器换成了ADXL345加速度传感器。

ADXL345是一款小而薄的低功耗3轴加速度计,分辨率高(13位),测量范围达±16g。数字输出数据为16位二进制补码格式,可通过SPI(3线或4线)或I2C数字接口访问。 ADXL345非常适合移动设备应用。它可以在倾斜检测应用中测量静态重力加速度,还可以测量运动或冲击导致的动态加速度。其高分辨率(4 mg/LSB),能够测量不到1.0°的倾斜角度变化。 该器件提供多种特殊检测功能。活动和非活动检测功能检测有无运动发生,以及任意轴上的加速度是否超过用户设置的限值。敲击检测功能可以检测单击和双击动作。自由落体检测功能可以检测器件是否正在掉落。这些功能可以映射到两个中断输出引脚中的一个。正在申请专利的32级先进先出(FIFO)缓冲器可用于存储数据,最大程度地减少主机处理器的干预。 低功耗模式支持基于运动的智能电源管理,从而以极低的功耗进行阈值感测和运动加速度测量。 ADXL345采用3 mm × 5 mm × 1 mm、14引脚小型超薄塑料封装。

选择ADXL345主要是看重了它的省电特性"测量模式下低至40 μA,待机模式下为0.1 μA",还配备了32组X,Y,Z FIFO, 功耗对于这个项目是非常重要的,谁也不想几天就去换一块电池!

ADXL345说明书: ADXL345 中文说明书

ADXL345使用参考: ADXL345 使用参考手册

计步器原理介绍

计步器是一种颇受欢迎的日常锻炼进度监控器,可以激励人们挑战自己,增强体质,帮助瘦身。早期设计利用加重的机械开关检测步伐,并带有一个简单的计数器。 晃动这些装置时,可以听到有一个金属球来回滑动,或者一个摆锤左右摆动敲击挡块。如今,先进的计步器利用MEMS(微机电系统)惯性传感器和复杂的软件来精确检测真实的步伐。 MEMS惯性传感器可以更准确地检测步伐,误检率更低。MEMS惯性传感器具有低成本、小尺寸和低功耗的特点,因此越来越多的便携式消费电子设备开始集成计步器功能, 如音乐播放器和手机等。ADI公司的3轴加速度计ADXL335, ADXL345和ADXL346小巧纤薄,功耗极低,非常适合这种应用。

本文以对步伐特征的研究为基础,描述一个采用3轴加速度计ADXL345的全功能计步器参考设计,它能辨别并计数步伐,测量距离、速度甚至所消耗的卡路里。 ADXL345专有的片内32级先进先出(FIFO)缓冲器可以存储数据,并执行计步器应用的相关操作,从而最大程度地减少主处理器干预,为便携式设备节省宝贵的系统功率。 其13位分辨率(4 mg/LSB)甚至允许计步器以合理的精度测量超低速步行(每步加速度变化约55 mg)。了解模型在可用于分析跑步或步行的特征当中, 我们选择“加速度”作为相关参数。个体(及其相关轴)的运动包括三个分量,分别是前向(“滚动”)、竖向(“偏航”)和侧向(“俯仰”),如图所示。 ADXL345检测其三个轴——x、y和z上的加速度。计步器处于未知方向,因此测量精度不应严重依赖于运动轴与加速度计测量轴之间的关系。

系统设计方案

1.设置ADXL345传感器为50Hz采样率,因为配备了FIFO,可以存储32组X,Y,Z数据,所以0.64秒才需要读取一次数据,其他时间arduino可以进入休眠模式 2.当ADXL345把FIFO数据填充好以后会触发中断,把arduino从休眠模式唤醒处理获取到的32组X,Y,Z传感器数据,判断用户(小动物)是否迈出了一步, 具体算法请参考analog公司计步器原理.(http://www.analog.com/library/analogDialogue/china/archives/44-06/pedometer.html) 3.统计用户(小动物)每10分钟走了多少步,唤醒机制是使用了arduino的看门狗模块,把数据通过蓝牙透明串口模块发送给MicroWRT模块。 4.MicroWRT获取到的数据通过上传到Yeelink,我们可以通过Yeelink网页或者客户端查看到运动数据。

Microduino端设计

1. 硬件连接如下表所示。

引脚表

Microduino引脚 ADXL345引脚
10 CS
11 SDA
12 SDO
13 SCL
3V3 VCC
GND GND

2. Microduino Core 代码

#include <avr/sleep.h>
#include <avr/wdt.h>
#include <Wire.h>  //调用arduino自带的I2C库
 
    #define ADXL345_REG_DEVID               0x00    // Device ID
    #define ADXL345_REG_THRESH_TAP          0x1D    // Tap threshold
    #define ADXL345_REG_OFSX                0x1E    // X-axis offset
    #define ADXL345_REG_OFSY                0x1F    // Y-axis offset
    #define ADXL345_REG_OFSZ                0x20    // Z-axis offset
    #define ADXL345_REG_DUR                 0x21    // Tap duration
    #define ADXL345_REG_LATENT              0x22    // Tap latency
    #define ADXL345_REG_WINDOW              0x23    // Tap window
    #define ADXL345_REG_THRESH_ACT          0x24    // Activity threshold
    #define ADXL345_REG_THRESH_INACT        0x25    // Inactivity threshold
    #define ADXL345_REG_TIME_INACT          0x26    // Inactivity time
    #define ADXL345_REG_ACT_INACT_CTL       0x27    // Axis enable control for activity and inactivity detection
    #define ADXL345_REG_THRESH_FF           0x28    // Free-fall threshold
    #define ADXL345_REG_TIME_FF             0x29    // Free-fall time
    #define ADXL345_REG_TAP_AXES            0x2A    // Axis control for single/double tap
    #define ADXL345_REG_ACT_TAP_STATUS      0x2B    // Source for single/double tap
    #define ADXL345_REG_BW_RATE             0x2C    // Data rate and power mode control
    #define ADXL345_REG_POWER_CTL           0x2D    // Power-saving features control
    #define ADXL345_REG_INT_ENABLE          0x2E    // Interrupt enable control
    #define ADXL345_REG_INT_MAP             0x2F    // Interrupt mapping control
    #define ADXL345_REG_INT_SOURCE          0x30    // Source of interrupts
    #define ADXL345_REG_DATA_FORMAT         0x31    // Data format control
    #define ADXL345_REG_DATAX0              0x32    // X-axis data 0
    #define ADXL345_REG_DATAX1              0x33    // X-axis data 1
    #define ADXL345_REG_DATAY0              0x34    // Y-axis data 0
    #define ADXL345_REG_DATAY1              0x35    // Y-axis data 1
    #define ADXL345_REG_DATAZ0              0x36    // Z-axis data 0
    #define ADXL345_REG_DATAZ1              0x37    // Z-axis data 1
    #define ADXL345_REG_FIFO_CTL            0x38    // FIFO control
    #define ADXL345_REG_FIFO_STATUS         0x39    // FIFO status
 
 
int ADXAddress = 0xA7>>1;  //转换为7位地址
 
int X0,X1,X_out;
int Y0,Y1,Y_out;
int Z1,Z0,Z_out;
double Xg,Yg,Zg;
 
boolean fifo=false; //fifo中断标志
 
int min_x,min_y,min_z;
int max_x,max_y,max_z;
 
int dynamic_threshold_x;//动态阀值x
int dynamic_threshold_y;//动态阀值y
int dynamic_threshold_z;//动态阀值z
int dynamic_threshold;
 
int dynamic_accuracy;//动态精度
 
byte sample_count;//采样计数
int time_1;//间隔计数
int time_2;
volatile byte timing;//看门狗计时
 
byte margin_max;//幅度最大的通道
 
int data_out;
 
int sample_new;
int sample_old;
 
int slope;//斜率变量
int Pedometer2;
int Pedometer3;//时间段内的计步数
 
int test;
 
void setup()
{
 
  delay(100);
  Serial.begin(115200,SERIAL_8N1);
  pinMode(10,OUTPUT);
  digitalWrite(10,HIGH);
  Wire.begin();  //初始化I2C
  delay(100);
   
  setup_watchdog(9);
// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
  ACSR |=_BV(ACD);//OFF ACD
  ADCSRA=0;//OFF ADC
   
 
   
  Reg(ADXL345_REG_POWER_CTL,0x00);//待机
  Reg(ADXL345_REG_BW_RATE,0x09);//数据速率及功率模式控制
  Reg(ADXL345_REG_DATA_FORMAT,0x28); //数据格式控制,中断电平
  Reg(ADXL345_REG_INT_MAP,0x03);//中断映射
  Reg(ADXL345_REG_FIFO_CTL,0xF4);//FIFO控制
  Reg(ADXL345_REG_INT_ENABLE,0x03);//中断使能控制
  Reg(ADXL345_REG_POWER_CTL,0x08);//测量 
  Clea(); 
  attachInterrupt(0, Watermark, FALLING);
   
}
 
 
void Watermark()
{
  fifo=true;
}
 
void Clea()
{
      for(int y=0;y<=32;y++)
    {
     Wire.beginTransmission(ADXAddress);
     Wire.write(ADXL345_REG_DATAX0);
     Wire.write(ADXL345_REG_DATAX1);
     Wire.endTransmission();
     Wire.requestFrom(ADXAddress,2);
    }
}  
 
void Reg(byte x1,byte x2)
{
  Wire.beginTransmission(ADXAddress);
  Wire.write(x1);
  Wire.write(x2);
  Wire.endTransmission();
}  
 
void setup_watchdog(int ii) {
   
  byte bb;
   
  if (ii > 9 ) ii=9;
  bb=ii & 7;
  if (ii > 7) bb|= (1<<5);
  bb|= (1<<WDCE);
   
  MCUSR &= ~(1<<WDRF);
  // start timed sequence
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  // set new watchdog timeout value
  WDTCSR = bb;
  WDTCSR |= _BV(WDIE);
   
   
}
 
ISR(WDT_vect) {
 timing++;
 if(timing>=75)//75*8s=600s
 {
  timing=0; 
  //Serial.println(test++);
  Serial.print(abs( Pedometer2-Pedometer3) );
  Pedometer3=Pedometer2;
 }
}
  
void Read_fifo()
{
  Wire.beginTransmission(ADXAddress);
  Wire.write(ADXL345_REG_DATAX0);
  Wire.write(ADXL345_REG_DATAX1);
  Wire.endTransmission();
  Wire.requestFrom(ADXAddress,6);
  if(Wire.available()<=6);
  {
    X0 = Wire.read();
    X1 = Wire.read();
    Y0 = Wire.read();
    Y1 = Wire.read();
    Z0 = Wire.read();
    Z1 = Wire.read();
    
    X1 = X1<<8;
    X_out = X0+X1;
    Y1 = Y1<<8;
    Y_out = Y0+Y1;
    Z1 = Z1<<8;
    Z_out = Z0+Z1;   
  }
 
}  
 
void Sleep_avr()//睡眠模式
{
  set_sleep_mode(SLEEP_MODE_PWR_DOWN  ); // sleep mode is set here
  sleep_enable();
  sleep_mode();                        // System sleeps here
}
  
void loop()
{
 
  if(fifo==true)
  { 
    fifo=false;
    for(byte x=0;x<20;x++)
    {
      Read_fifo();
      sample_count++;
 
      if(sample_count<=50)
        {
          max_x=max(max_x,X_out);//最大最小值
          min_x=min(min_x,X_out);
          max_y=max(max_y,Y_out);
          min_y=min(min_y,Y_out);
          max_z=max(max_z,Z_out);
          min_z=min(min_z,Z_out);               
        }
      else
        {  
           sample_count=0;
            
           if( abs(max_x-min_x) > abs(max_y-min_y) && abs(max_x-min_x) > abs(max_z-min_z) )  //动态精度
            {margin_max=1;dynamic_accuracy=abs(max_x-min_x)/8;}
           if( abs(max_y-min_y) > abs(max_x-min_x) && abs(max_y-min_y) > abs(max_z-min_z) )
            {margin_max=2;dynamic_accuracy=abs(max_y-min_y)/8;}
           if( abs(max_z-min_z) > abs(max_x-min_x) && abs(max_y-min_y) > abs(max_y-min_y) )
            {margin_max=3;dynamic_accuracy=abs(max_z-min_z)/8;}
           if(dynamic_accuracy < 10)
             {
               dynamic_accuracy=10;
              } //动态精度
               
           dynamic_threshold_x= (max_x+min_x)/2;//动态阀值
           dynamic_threshold_y= (max_y+min_y)/2;
           dynamic_threshold_z= (max_z+min_z)/2;           
           max_x=-10000;max_y=-10000;max_z=-10000;
           min_x=10000;min_y=10000;min_z=10000;               
        }//if..else
     
           switch(margin_max)
            {
              case 1:data_out=X_out; dynamic_threshold=dynamic_threshold_x-10;
              break;
              case 2:data_out=Y_out; dynamic_threshold=dynamic_threshold_y-10;
              break;
              case 3:data_out=Z_out; dynamic_threshold=dynamic_threshold_z-10;
              break;
            }
 
          time_1++;
             
         //  Serial.print(dynamic_threshold);
         //  Serial.print(","); 
            
           sample_old=sample_new;
           if( abs(data_out-sample_new) > dynamic_accuracy ) { sample_new=data_out;} 
        //   Serial.println(sample_old);
            
           if(sample_old>dynamic_threshold && abs(dynamic_threshold-sample_old)>20 ) { slope=1; }
           if(sample_old<dynamic_threshold && abs(dynamic_threshold-sample_old)>20 ) 
             { 
               if( slope==1 )
               {   
                    slope=0;
                    if( (time_1 -time_2) >10 && (time_1-time_2)< 100 ) //时间间隔,判断是否是有效步伐
                       {
                         Pedometer2++;
                       //  Serial.println(Pedometer2++);
                       }
                    time_2=time_1;                                             
               }
              }
     
    }//for
     
    Sleep_avr();//Sleep....
 }//if
 
 else
   {
    delay(1); 
    Sleep_avr();
   }
 
}

因为使用了ADXL345加速度传感器,所有0.64秒才唤醒arduino读取数据,然后使用低功耗蓝牙4.0透明串口传送数据给MicroWRT上传到Yeelink。 所以microduino端的功耗非常低,睡眠模式下的功耗只需要10ua,所以这个项目的计步器设计使用2032纽扣电池可以提供几个月的使用时间!


MicroWRT端设计

1. MicroWRT 代码

#include <stdio.h>         //标准输入输出定义
#include <stdlib.h>        //标准函数库定义
#include <unistd.h>        //Unix标准函数定义
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>         //文件控制定义
#include <termios.h>        //POSIX中断控制定义
#include <errno.h>        //错误号定义
  
#include <signal.h>
#include <unistd.h>
 
#include<time.h>
 
  
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
 
//using namespace std;
  
  
const char *YEELINK_IP="42.96.164.52";
 
 
//定义波特率数组
int speed_arr[] = {B115200, B38400, B19200, B9600, B4800, B2400, B1200, B300};
int name_arr[] = {115200,38400, 19200, 9600, 4800, 2400, 1200, 300};
  
//打开串口
int open_port(void)
{
        int fd;                //串口的标识符
        //O_NOCTTY用来告诉Linux这个程序不会成为“控制终端”
        //O_NDELAY用来告诉Linux这个程序不关心DCD信号
        fd=open("/dev/ttyS0",O_RDWR | O_NOCTTY | O_NDELAY);
        if(fd == -1)
        {
                //不能打开串口
                perror("open_port: Unable to open /dev/ttyS0 -");
                return(fd);
        }
        else
        {
                fcntl(fd, F_SETFL, 0);
                printf("open ttys0 .....\n");
                return(fd);
        }
}
  
//设置波特率
void set_speed_and_parity(int fd, int speed)
{
        int i=0;                //设置循环标志——注意不要在for内设置,否则会出错
        struct termios Opt;        //定义termios结构
        if(tcgetattr(fd,&Opt)!=0)
        {
                perror("tcgetattr fd");
                return;
        }
        for(i = 0; i < 8 ; i++)
            {
                if(speed == name_arr[i])
                {        
                            tcflush(fd, TCIOFLUSH);
                            cfsetispeed(&Opt, speed_arr[i]);
                            cfsetospeed(&Opt, speed_arr[i]);
                        /*tcsetattr函数标志:
                        TCSANOW:立即执行而不等待数据发送或者接受完成。
                        TCSADRAIN:等待所有数据传递完成后执行。
                        TCSAFLUSH:Flush input and output buffers and make the change
                        */
                            if(tcsetattr(fd, TCSANOW, &Opt) != 0)
                            {        
                                perror("tcsetattr fd");
                                return;
                            }
                            tcflush(fd, TCIOFLUSH);
                }
            }
        //设置奇偶校验——默认8个数据位、没有校验位
        Opt.c_cflag &= ~PARENB;
        Opt.c_cflag &= ~CSTOPB;
        Opt.c_cflag &= ~CSIZE;
        Opt.c_cflag |= CS8;
}
  
/*
//设置奇偶校验——默认8个数据位、没有校验位
int set_parity()
{
        Opt.c_options.c_cflag &= ~PARENB
        options.c_cflag &= ~CSTOPB
        options.c_cflag &= ~CSIZE;
        Opt.c_cflag |= CS8;
}
*/
  
 
void yeelink(float data)
{
  
           char tmp[10];
           sprintf(tmp,"%.3f",data);
  
           struct sockaddr_in addr ;
  
           int t=socket(AF_INET,SOCK_STREAM,0);  // TCP
  
           bzero(&addr,sizeof(addr));
           addr.sin_family =AF_INET;  //IPV4
           addr.sin_port=htons(80); //PORT
           addr.sin_addr.s_addr=htonl(INADDR_ANY);
           addr.sin_addr.s_addr=inet_addr(YEELINK_IP);
  
           while(connect(t,(struct sockaddr *)&addr,sizeof(addr))==-1)
           {
  
                   printf("connect eero \n");
           }
  
           char *data1="POST /v1.0/device/5167/sensor/30842/datapoints HTTP/1.0\r\nHost: api.yeelink.net\r\nAccept: */*\r\n"; //5167替换成你自己的device号,30842也需替换成你的sensor号
           send(t,data1,strlen(data1),0);
 
  
           char *data2="U-ApiKey: 67f4ce66959dd6d518af60289206f700\r\nContent-Length: ";//替换U-ApiKey
           send(t,data2,strlen(data2),0);
  
  
           char data3[5];
           sprintf(data3,"%d",strlen(tmp)+10);
           send(t,data3,strlen(data3),0);  //写入数据长度
  
  
           char *data4="\r\nContent-type: application/json;charset=utf-8\r\n\r\n";
           send(t,data4,strlen(data4),0);
  
  
           send(t,"{\"value\":",strlen("{\"value\":"),0);
           send(t,tmp,strlen(tmp),0);
           send(t,"}\r\n",strlen("}\r\n"),0);
  
           close(t);
  
        }
 
 
int main(void)
{
        int fd;
        int y;
        int nread,i;
        char buff[1024];
        int test;
        //打开串口
        if((fd=open_port())<0)
            {
                perror("open_port error");
                return 0;
            }
        //设置波特率和校验位
        set_speed_and_parity(fd,115200);
        //设置校验位
        //set_parity();
  
        printf("fd=%d\n",fd);
 
         
 
while (1) //循环读取数据
{   
        if((nread = read(fd, buff, 512))>0)
        { 
                //printf("\nLen %d\n",nread); 
                   
                tcflush(fd, TCIOFLUSH); 
                test=atoi(buff);
                //printf( "%s", buff);   
                printf("%d\n",test);
                yeelink(test);
        }
}
        //关闭串口
        close(fd);
        return 0;
}

2. 编译源程序 在以前的教程中,我们一般都是把源程序复制到openwrt的编译目录,或者是openwrt SDK的编译目录中去编译。其实我们也有一个简单的方法,就是安装好交叉编译工具链在 HOST主机中,设置环境变量,就可以自由的编译源程序了。具体操作步骤如下:

<1> 下载交叉编译工具链。当然你也可以自己用openwrt的source code编译。 下载路径: 链接:http://pan.baidu.com/s/1eQtnfIM 密码:qrye <2> 设置环境变量,登陆系统(非root用户),在终端输入:

   $ sudo gedit ~/.profile(or .bashrc) 

<3> 在此文件末尾加入PATH的设置如下,根据自己的路径修改: export STAGING_DIR='/home/while/OpenWrt-Toolchain/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2/' <4> 保存文件,注销在登陆,变量将生效。 <5> 安装编译环境软件包:

   sudo apt-get install gcc g++ binutils patch bzip2 flex bison make autoconf gettext texinfo unzip sharutils ncurses-term zlib1g-dev libncurses5-dev gawk

<6> 进入OpenWrt-Toolchain/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2/bin目录,新建文件hello.c

#include<stdio.h>
int main()
{
   printf("hello world!");
}

<7>然后终端进入OpenWrt-Toolchain/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2/bin 目录,根据你自己的目录修改, 输入./mipsel-openwrt-linux-gcc hello.c -o hello 回车,然后bin目录下就会出现hello文件,复制到openwrt开发板设置下0755权限就可以运行了。 写其他程序只有修改下hello.c的源代码就可以了!

经过上面的步骤,我们只要把microWRT的源程序复制到hello.c 中,就可以很容易的编译出执行文件。然后上传到microWRT去运行。

测试

MicroWRT会把前10分钟所走的步数上传到Yeelink上,所以下图中的每个数据都是10分钟的步数和。