项目二--云端计步器
本教程也是一个microduino和microWRT结合的项目。主要是通过microduino和传感器模块来检测被测对象的运动量,然后通过蓝牙模块将数据传送到microWRT上的microduino core模块。 Core模块进而将数据通过串口发送给microWRT。最后microWRT会把数据上传到yeelink,用户在任何地方只要能够访问网络,就可以看到运动量。 本教程有microWRT玩家“while(高群)”在microWRT内测活动中完成的。非常感谢“while”。最终解释权归“while”所有。 本教程中的所有代码可以通过下面链接直接下载。 源程序: 所有源程序 硬件设备
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. 硬件连接如下表所示。 引脚表
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分钟的步数和。 |