查看: 3273|回复: 1

【Arduino 101】 基于IMU和BLE的重力平衡球游戏

[复制链接]
  • TA的每日心情
    开心
    2017-7-3 15:02
  • 签到天数: 56 天

    [LV.5]常住居民I

    发表于 2017-5-1 17:12 | 显示全部楼层 |阅读模式
    本帖最后由 甲基红橙黄绿蓝 于 2017-5-1 22:15 编辑

    概述:
            平衡球游戏在手机平台上很常见,玩家操控手机的平衡来控制平衡球运动,到达指定地点。为了实现平衡球游戏,我使用Arduino 101作为游戏手柄,将101的加速度计数据通过串口或BLE发给PC机,在PC机上实现一个平衡球游戏的界面。(因为没学过美工,也没有时间去认真做这个游戏,现在界面很简陋,仅仅作为一个抛砖引玉)




    目的:
            使用Arduino 101作为手柄,用PC机作为界面,通过串口和BLE两种方式完成101与PC机的交互,实现平衡球游戏。





    使用串口传输的版本:
            这个版本没有使用BLE,需要把101和电脑通过USB连接。我写了一个Python2.7的脚本,调用串口库获取101发来的串口数据,调用pygame显示界面。
            需要:Windows XP及以上 、Python2.7 、pygame 、Arduino 101 、Arduino 101 1.0.x+库

    Arduino 101代码:
    [mw_shl_code=cpp,true]#include "CurieIMU.h"
    int ax, ay, az;         // accelerometer values
    int gx, gy, gz;         // gyrometer values

    const int ledPin = 13;      // activity LED pin
    boolean blinkState = false; // state of the LED

    int calibrateOffsets = 1; // int to determine whether calibration takes place or not

    void setup() {
      Serial.begin(9600);
      while (!Serial);
      CurieIMU.begin();

      if (calibrateOffsets == 1) {
        delay(1000);
        CurieIMU.autoCalibrateGyroOffset();
        CurieIMU.autoCalibrateAccelerometerOffset(X_AXIS, 0);
        CurieIMU.autoCalibrateAccelerometerOffset(Y_AXIS, 0);
        CurieIMU.autoCalibrateAccelerometerOffset(Z_AXIS, 1);
      }
      pinMode(ledPin, OUTPUT);
    }

    int8_t buf[4];

    void loop() {
      while(Serial.read()!='g');
      CurieIMU.readMotionSensor(ax, ay, az, gx, gy, gz);
      *((int16_t*)buf) = ax;
      *((int16_t*)(buf+2)) = ay;
      Serial.write(buf[0]);
      Serial.write(buf[1]);
      Serial.write(buf[2]);
      Serial.write(buf[3]);

      blinkState = !blinkState;
      digitalWrite(ledPin, blinkState);
    }[/mw_shl_code]

    Python2.7代码:
    [mw_shl_code=python,true]#coding: utf-8
    #Version:   Python2.7

    from time import sleep
    from serial.tools import list_ports
    from serial import Serial
    from struct import unpack
    import pygame,sys

    class MySerial():
        def __init__(self):
            port_list = list(list_ports.comports())
            if len(port_list) <= 0:
                self._ser = None
                return
            self._ser = Serial(list(port_list[0])[0],9600)
            sleep(1)
        def opened(self):
            return self._ser!=None
        def GetAngle(self):
            self._ser.write('g')
            s = self._ser.read(size=4)
            return unpack('hh',s)
            
            
    class Ball():
        def __init__(self):
            self.reset()
            self.radius = 20.0
        def reset(self):
            self.x = 100.0
            self.y = 100.0
            self.vx = 0.0
            self.vy = 0.0
        def move(self,a):
            self.vx += a[0]/131072.0
            self.vy += a[1]/131072.0
            self.x -= self.vx
            self.y += self.vy
            
            if self.x<self.radius:
                self.vx = 0.0
                self.x = self.radius
            
            if self.x>600.0-self.radius:
                self.vx = 0.0
                self.x = 600.0-self.radius
            
            if self.y<self.radius:
                self.vy = 0.0
                self.y = self.radius
                
            if self.y>600.0-self.radius:
                self.vy = 0.0
                self.y = 600.0-self.radius
            
            if self.y>200.0-2*self.radius and self.y<200.0 and self.x<400.0:
                self.vy = 0.0
                self.y = 200.0-2*self.radius
            
            if self.y<200.0+2*self.radius and self.y>200.0 and self.x<400.0:
                self.vy = 0.0
                self.y = 200.0+2*self.radius
                
            if self.y>400.0-2*self.radius and self.y<400.0 and self.x>200.0:
                self.vy = 0.0
                self.y = 400.0-2*self.radius
            
            if self.y<400.0+2*self.radius and self.y>400.0 and self.x>200.0:
                self.vy = 0.0
                self.y = 400.0+2*self.radius
            
            if (465-self.x)**2+(465-self.y)**2 < (1.4*self.radius)**2:
                        return 1
            
            for i in xrange(0,4):
                for j in xrange(0,4):
                    hole_x = 150*i + 15
                    hole_y = 150*j + 15
                    if (hole_x-self.x)**2+(hole_y-self.y)**2 < (1.4*self.radius)**2:
                        return -1
            return 0
       
    def main():
        ser = MySerial()
        if not ser.opened():
            print "No Serial"
            return
        else:
            print "Serial OK"
        ball = Ball()
        pygame.init()
        screencaption = pygame.display.set_caption('Balance Ball')
        screen = pygame.display.set_mode([600,600])
        screen.fill([255,255,255])
        pygame.display.update()

        while True:
            for event in pygame.event.get():
                if event.type==pygame.QUIT:
                    return
            screen.fill([255,255,192])
            pygame.draw.rect(screen,[0,0,0],[0,200-int(ball.radius),400,int(ball.radius)*2],3)
            pygame.draw.rect(screen,[0,0,0],[200,400-int(ball.radius),600,int(ball.radius)*2],3)
            for i in xrange(0,4):
                for j in xrange(0,4):
                    hole_x = 150*i + 15
                    hole_y = 150*j + 15
                    pygame.draw.circle(screen,[0,0,0],[hole_x,hole_y],int(ball.radius),int(ball.radius))
                   
            pygame.draw.circle(screen,[0,0,255],[465,465],int(ball.radius),int(ball.radius))
            pygame.draw.circle(screen,[255,30,60],[int(ball.x),int(ball.y)],int(ball.radius),int(ball.radius))
            pygame.display.update()
            
            result = ball.move(ser.GetAngle())
            
            if result == -1 :
                print "You Lose!"
                ball.reset()
            elif result == 1:
                print "You Win!"
                ball.reset()

    if __name__ == '__main__':
        main()
    [/mw_shl_code]

    运行方式:
    上传程序后,确保Arduino 101已连接电脑,且不要使用其它COM口。再运行Python脚本,这个脚本是自动寻找Arduino101的串口的,若找到串口,会在屏幕上显示"Serial Ready",然后弹出游戏界面,就可以玩了:
    101_BalanceBall_Wired.png

    图:有线连接的平衡球游戏








    使用BLE传输的版本:

            这个版本使用了BLE,不需要把101和电脑通过USB连接,只需要给101供电就行(比如充电宝)。在PC端我是修改的一个UWP应用,还没有编写墙壁、洞口等场景,仅有一个方向的地图。
    需要:Windows10 、Visual Studio 2015+ 、Arduino 101 、Arduino 101 1.0.x+库

    Arduino 101代码:
            因为BLE应用都需要借助相关的BLEService的,并没有哪个Service专门传输平衡球游戏数据,因此我借助HeartRateService传输重力数据。
    [mw_shl_code=bash,true]#include <CurieBLE.h>
    #include <CurieIMU.h>

    BLEPeripheral blePeripheral;
    BLEService S("180D");
    BLECharacteristic C("2A37", BLENotify, 3);

    void setup() {
      CurieIMU.begin();
      CurieIMU.setAccelerometerRange(1);

      CurieIMU.autoCalibrateAccelerometerOffset(X_AXIS, 0);
      CurieIMU.autoCalibrateAccelerometerOffset(Y_AXIS, 0);
      CurieIMU.autoCalibrateAccelerometerOffset(Z_AXIS, 1);
      
      blePeripheral.setLocalName("Arduino 101");
      blePeripheral.setAdvertisedServiceUuid(S.uuid());
      blePeripheral.addAttribute(S);
      blePeripheral.addAttribute(C);
      blePeripheral.begin();
    }

    uint8_t data[] = {1,0,0};

    void loop() {
      int ax, ay, az;
      CurieIMU.readAccelerometer(ax, ay, az);

      ax /= 32;
      ax = -ax;
      ay /= 32;
      if(ax>127) ax = 127;
      else if(ax<-127) ax = -127;
      if(ay>127) ay = 127;
      else if(ay<-127) ay = -127;
      ax += 127;
      ay += 127;
      data[1] = ax ;
      data[2] = ay ;
      C.setValue(data,3);
      delay(30);
    }[/mw_shl_code]

    Windows10 UWP应用代码:
            这是一个VisualStudio工程,工程太大没法贴……

    运行方法:
           上传Arduino101代码后,给101上电(不必用电脑供电),在Win10的蓝牙设置里配对Arduino 101。编译运行UWP程序,可以看到以下界面。依次点击“101 Detected! Press to Start”和“{00002A37-0000-1000-8000-00805F9B34FB}” 。晃动101,就能看到黄色的圆球随之运动了。
    101_BalanceBall.png

    图:BLE版本的平衡球






    总结:

           因为不太熟悉UWP编程,我也不打算继续用UWP编写BLE了。我看到python3.5有PyBluez库可以编写BLE应用,可以抽空尝试一下。




  • TA的每日心情
    开心
    2019-7-17 13:13
  • 签到天数: 264 天

    [LV.8]以坛为家I

    发表于 2017-5-1 17:48 | 显示全部楼层
    心率规约暂借给平衡球,赞
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    热门推荐

    新设计的一款遥控播放音乐机器人
    新设计的一款遥控播放音乐
    近期设计的一款遥控播放音乐歌曲的机器人终于完工了,DF mini mp3模块是真的强大。sol
    ESP8266物联网创意点阵时钟,女朋友看了都想要!
    ESP8266物联网创意点阵时
    [md]本文作者:默 & 铁熊 前段时间我在网上看到了一款很有意思的点阵时钟,它可以
    按键中断无法触发
    按键中断无法触发
    如题,按键中断无法触发,设成电平变化触发中断程序,但是flag的状态一直不变 void
    【原创】全球最小口袋3D打印机mini one直播教程贴
    【原创】全球最小口袋3D打
    最近闲得蛋疼,没事搞个掌上3D打印机,先放效果图吧。 搞了半天,终于能正常打印,
    OLED心率示波仪
    OLED心率示波仪
    在ARDUINO UNO开发板上插上一片0.96寸OLED显示屏并上传已下简单的程序就能构成一个OLE
    Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
    快速回复 返回顶部 返回列表