2010年07月 文档列表

悲剧的一周

上周夸下海口,说准备做一个两轮的平衡小车。当时的想法实在是非常肤浅:把手机捆在NXT身上,不断的向NXT发送当前的倾斜角度,NXT根据角度进行调整。当小车向前倾的时候,轮子就向前滚,小车向后倾时,轮子就向后滚。同时根据角度倾斜的大小来设置轮子的滚动速度。

按照这个思路开始搭建,因为手头的乐高颗粒严重不够,只好从萝卜头身上割了点肉下来,即使这样也只能用小件拼大件,最后搭了个像怪物似的“两轮平衡小车”:

未成功的两轮平衡小车

未成功的两轮平衡小车

细心的同学可能发现小车的背后有个亮度传感器,这个是后话,等会儿解释。最初我的想法是手机通过蓝牙和NXT连接并控制电机运动。HTC手机控制NXT的代码如下(其中Timer设置的时间间隔是50ms):

private Nxt nxt;
private void ConnectButton_Click(object sender, EventArgs e)
{
    try
    {
        nxt = new Nxt();
        nxt.Connect("COM9");
    }
    catch (Exception)
    {
        nxt.Dispose();
        nxt = null;
    }
}

private void nxtTimer_Tick(object sender, EventArgs e)
{
    if (nxt != null)
    {
        try
        {
            GVector gvector = mySensor.GetGVector();
            //1.75 is the fix number for center line
            double gz = gvector.Z - 1.75;

            sbyte power = Convert.ToSByte(-8 * gz);

            nxt.SetOutputState(MotorPort.PortB, power, MotorModes.Regulated,
                MotorRegulationMode.Speed, 0, MotorRunState.Running, 0);
        }
        catch (Exception) { }
    }
}

冒着被丈母娘训斥的危险折腾到半夜,终于完工了。一般来说,工作的完成意味着悲剧的开始:这个小车反应非常迟钝,先躺到地上,然后轮子才开始动,就像个耍赖的小P孩。我试过把50ms的Timer调的更小,但是这时候手机已经处理不过来,反而更慢了。

本来想把这个失败的作品马上拆了(萝卜头的胳膊还处于脱臼状态),但是有点儿不甘心。于是第二天又想了个“好办法”:也许是蓝牙传输太慢,我能不能根据倾斜角度来改变屏幕的颜色,然后NXT用亮度传感器通过读屏幕颜色来控制电机呢?于是脱臼的萝卜头又被拆掉了一个传感器,凑成了这样一个小车:

背着手机的小车

背着手机的小车

用来改变屏幕颜色的代码如下:

private void UpdateSpeed()
{
    GVector gvector = mySensor.GetGVector();
    int gray = Convert.ToInt32(256 * (gvector.Z + 9.8) / 19.6);
    if (gray < 0) gray = 0;
    if (gray > 255) gray = 255;

    this.BackColor = Color.FromArgb(gray, gray, gray);
}

再次折腾到半夜。事实证明,变色的反应速度比蓝牙要快一点,但还是不足以让小车保持平衡。小车吱吱嘎嘎前后晃动几下,就倒在了地上。这个平衡小车的尝试到此宣告失败。失败的原因总结如下:
1,小车重心太高,以至于倒下的速度太快,这个因为零件太少实在没办法
2,数据反应速度太慢,经我测试,WM手机+C#的最快反应速度只能到50ms,对于平衡这样的工作来说实在是太漫长了

搭车再提供一项悲剧,我用了两年的笔记本坏掉了,虽然还能凑合用,但是显卡貌似没有绿色了。没错,这个就是315曝光的HP笔记本,从症状判断应该是显卡损坏,坏掉的时候正好过了保修期几天…..

昨晚拆性大发,把这个烂笔记本拆了个七零八落:

臭名昭著的HP Presario笔记本

臭名昭著的HP Presario笔记本

拆完发现主板的显卡是集成的,没啥好玩的,只好又装回去。还好,重新装好后只剩下两颗螺丝,开机居然还可以使用,创下我破坏史上的最低纪录。

这就是一周总结了,奋战了三个晚上,没做出啥成果,只能供大家娱乐一下了。后来在网上搜了一下,还真有NXT的两轮平衡小车,用亮度传感器+两个电机,有兴趣的同学自己去研究一下吧:

http://www.nxtprograms.com/segway/index.html

NXT两轮平衡小车

NXT两轮平衡小车

蛋疼的小球 – 重力感应器小试牛刀

上周,网友“忧郁飞花”提了个建议,说是可以用手机的重力感应器来控制小车,这样就可以把手机变成遥控方向盘。不知道这位同学为何如此忧郁,也许“飞花”形容的是该同学的工资流逝速度吧,在此假惺惺的慰问并感谢一下。

言归正传,手机当方向盘的想法虽然好,细节却还需要仔细推敲,例如倒退,调节转速,波动过大等情况的处理。一个应该可行的方案是用仰角来调节转速,把转速扩展到负值来实现倒退。(这时候我眼前出现了幻觉:圈圈在屋里拿着手机乱晃,小爱在外面剧烈抽风….)

汗,遥控小车还是先缓缓吧,这几天做了一个非常蛋疼的小程序来测试重力感应器。这个程序只有一个小球,在屏幕上滚来滚去。我特地找了个金色的珍珠,阿弥陀佛,保佑程序作者大富大贵,提前退休。

使用的手机是HTC的钻石2代,操作系统是windows mobile 6.5

滚动的金色小球

滚动的金色小球

写这个程序之前,先复习下高中的物理知识。假如一个小球在某时刻 t1,位置坐标是(x1,y1),速度方向是(vx1,vy1),受到的重力加速度是(ax,ay),那么当时光流逝到 t2 时,该小球的位置和速度应该是多少(假设t1,t2间隔很短,期间加速度没有变化)。其实x,y是矢量合成,咱们以x分量为例进行计算:

首先根据加速度的定义,vx2=vx1+ax*(t2-t1),注意ax可能为负值,所以速度方向也可能会改变。
x2的坐标则可以用x2=x1+(t2-t1)*(vx1+vx2)/2 来计算,即位移等于平均速度乘以时间。

然后是考虑碰壁的情况,以x轴负临界值为例,在Δt时间之后,发现x轴坐标已经小于0(我这里按质点做假设,实际上应该是小于小球半径的时候就碰壁了),这时候的速度大多数情况下也应该是负值(不解释了)。这时候假设小球的弹性系数是f,那么反弹回来的速度分量,应该是vx2′=-vx2*f ;而x坐标,可以估算为把负值那部分反射回来,再乘弹性系数,即x2′=-x2*f

如果程序写成这样,会发现大多数情况下,这个小球像模像样的蹦跶着,但是在接近停止的时候会突然跳的更高,永远停不下来。这个是因为临界值处理的不好,如果我们的Δt无限小,这个公式是正确的,但是真正计算时,Δt只能根据手机的承受能力设定(我设置的是50ms),这就会发生下面的情况:

边界条件超出合理范围

边界条件超出合理范围

当x1非常小,而Δt比较大的时候,x2的绝对值已经远超过x1,即使乘上弹性系数仍然大于x1。这就造成了接近壁面时,小球弹起的高度反而比原来更高。最后我加上了一些懒汉修正,当高度较低时,直接把速度和位置全设置成0。

物理复习完了,剩下的事情就简单了。其实就是找个图片,然后用一个timer定时,每隔一段时间更新一下(x,y)坐标即可。看看最终的效果视频:

下面贴一段大家可能有兴趣的“核心代码”:

// http://www.diy-robots.com
// apply physics to a ball
Ball ApplyDevicePhysics(GVector gVector, Ball ball, int millisecondsElapsed)
{
    int maxX = ClientSize.Width - ball.Radius;
    int maxY = ClientSize.Height - ball.Radius;
    double t = millisecondsElapsed / 1000.0;

    #region calculate x values
    gVector.X *= frictionForce;
    double v1 = ball.Velocity.X;
    double x1 = ball.Position.X;
    ball.Velocity.X = v1 + gVector.X * t;
    ball.Position.X = x1 + v1 * t + 0.5 * gVector.X * t * t;
    if (ball.Position.X < ball.Radius)
    {
        ball.Position.X = ball.Radius;
        if (ball.Velocity.X < 0)
        {
            ball.Velocity.X *= (-elasticRation);
            if (gVector.X < 0 && (-ball.Velocity.X / gVector.X) < (t * 2 * elasticRation))
            {
                ball.Velocity.X = 0;
            }
        }
    }
    if (ball.Position.X > maxX)
    {
        ball.Position.X = maxX;
        if (ball.Velocity.X > 0)
        {
            ball.Velocity.X *= (-elasticRation);
            if (gVector.X > 0 && (-ball.Velocity.X / gVector.X) < (t * 2 * elasticRation))
            {
                ball.Velocity.X = 0;
            }
        }
    }
    #endregion

    #region calculate y values
    gVector.Y *= frictionForce;
    v1 = ball.Velocity.Y;
    double y1 = ball.Position.Y;
    ball.Velocity.Y = v1 + gVector.Y * t;
    ball.Position.Y = y1 + v1 * t + 0.5 * gVector.Y * t * t;
    if (ball.Position.Y < ball.Radius)
    {
        ball.Position.Y = ball.Radius;
        if (ball.Velocity.Y < 0)
        {
            ball.Velocity.Y *= (-elasticRation);
            if (gVector.Y < 0 && (-ball.Velocity.Y / gVector.Y) < (t * 2 * elasticRation))
            {
                ball.Velocity.Y = 0;
            }
        }
    }
    else if (ball.Position.Y > maxY)
    {
        ball.Position.Y = maxY;
        if (ball.Velocity.Y > 0)
        {
            ball.Velocity.Y *= (-elasticRation);
            if (gVector.Y > 0 && (-ball.Velocity.Y / gVector.Y) < (t * 2 * elasticRation))
            {
                ball.Velocity.Y = 0;
            }
        }
    }
    #endregion
    return ball;
}

实验证明,HTC手机的重力感应器还是挺灵敏的,我设置的时间是50毫秒。因为暂时不方便加工小爱,接下来我打算试着做一个简易的两轮平衡小车。

圈圈满月了

网友老四同学呼吁要看满月照,考虑到圈圈年纪还小,作为一个眉清目秀的小美女,还是不要太早抛头露面比较好 :)

转贴一个宝宝版钢铁侠,实在是太酷了。如果把圈圈打扮成这个样子,不知道圈圈妈会不会跟我拼命:

宝宝版钢铁侠

宝宝版钢铁侠