低端单片机(8bit)的16路舵机调速分析与实现

时间:2022-09-15 19:49:21

低端单片机(8bit)的16路舵机调速分析与实现

By 马冬亮(凝霜  Loki)

一个人的战争(http://blog.csdn.net/MDL13412)

        今年的英特尔杯准备做机器人方向,所以在淘宝上买了机器人套件,自己进行组装和编程。机器人装配完如下图所示:

低端单片机(8bit)的16路舵机调速分析与实现

        (注:这款机器人由17路舵机组成,其中左右手各2路,左右腿各4路,身体4路,头部1路。)

        装配好机器人,首要任务就是让机器人运动起来。看了一下卖家给的控制程序,写的很混乱,维护极差,于是准备自己写一套控制程序,也算是给学弟学妹们留下的一份礼物吧^_^。

        首先简单介绍一下我们使用的舵机参数:

        *型号.:KCS110M                  
        *速度 :5.0V: 0.14sec/60°
        *扭力 :5.0V: 9.5kg/cm
        *工作电压:5.0-7.4Volts  
        *转角范围  : 0~185度

        由于是控制机器人,所以我对转角范围的需求是0~180度,那么舵机由0度转到180度需要至少0.6s的时间。

        舵机的调速原理其实很简单,通过给定连续的PWM信号,舵机就可以旋转到相应的角度。我们的这款舵机和竞赛的通用舵机基本一致,PWM周期为20ms,复位需要T1=0.5ms的高电平,T2=(20 - 0.5)ms的低电平。每增加1度,需要在T1的基础上加10us。为了让舵机完整的转到对应角度,我们需要至少对舵机连续送0.6s的PWM信号。

        由于舵机调速对PWM精度要求很高,传统的12T 8051单片机每个指令周期占12个机器周期,速度不能达到要求。我们选择了STC12C5A60S2这款单片机,这款单片机较传统的12T 8051快至少6倍以上,并配合24MHZ的晶振来控制机器人。

        舵机调速一般有两种方法,一种是使用定时器中断进行调速,另外一种是使用软件延时进行调速。

        首先分析定时器中断调速的方法:

        我的调速精度要求是1度,所以定时器需要定时10us,那么,每200次定时器中断完成一个PWM调速周期。这种编程方式的优点是实现简单,代码容易读懂。其缺点是只能处理少数舵机,不能对大量舵机进行调速。因为每10us发生一次定时中断,而每个机器周期为(1 * 1000 * 1000) / (24 * 1000 * 1000)=0.0417us,则在每个定时器中断中有10 / 0.0417=240个指令周期。我们看下面的程序:

#include <STC_NEW_8051.H>

void main()
{
    volatile int cnt = 0;
    volatile int pwm = 0;
    volatile int pwmCycle = 10;
    
    cnt = (cnt + 1) % 40;
    
    if (++cnt < pwmCycle)
        pwm = 1;
    else
        pwm = 0;
}
        这段程序是模拟定时器中断中对一路舵机进行调速的情况,注意,这里一定要使用volatile关键字,否则程序会由于编译器的优化而错误。对于所有中断中用到的全局变量,我们都需要加上volatile关键字防止编译器优化。下面,我们对这段代码进行反汇编,并计算指令周期数。反汇编代码如下:

C:0x0097    F50C     MOV      0x0C,A                        3
C:0x0099    750D0A   MOV      0x0D,#0x0A               3

C:0x009C    E509     MOV      A,0x09                        2
C:0x009E    2401     ADD      A,#0x01                       2
C:0x00A0    FF       MOV      R7,A                             2
C:0x00A1    E4       CLR      A                                  1
C:0x00A2    3508     ADDC     A,0x08                        3
C:0x00A4    FE       MOV      R6,A                             2
C:0x00A5    7C00     MOV      R4,#0x00                     2
C:0x00A7    7D28     MOV      R5,#0x28                     2
C:0x00A9    120003   LCALL    C?SIDIV(C:0003)        
C:0x00AC    8C08     MOV      0x08,R4                       3
C:0x00AE    8D09     MOV      0x09,R5                       3

C:0x00B0    0509     INC      0x09                              4
C:0x00B2    E509     MOV      A,0x09                         2
C:0x00B4    7002     JNZ      C:00B8                          3
C:0x00B6    0508     INC      0x08                              4
C:0x00B8    AE08     MOV      R6,0x08                        4
C:0x00BA    C3       CLR      C                                    1
C:0x00BB    950D     SUBB     A,0x0D                         3
C:0x00BD    E50C     MOV      A,0x0C                         2
C:0x00BF    6480     XRL      A,#P0(0x80)                    2
C:0x00C1    F8       MOV      R0,A                               2
C:0x00C2    EE       MOV      A,R6                               1
C:0x00C3    6480     XRL      A,#P0(0x80)                     2
C:0x00C5    98       SUBB     A,R0                                2
C:0x00C6    5007     JNC      C:00CF                            3

C:0x00C8    750A00   MOV      0x0A,#0x00                   3
C:0x00CB    750B01   MOV      0x0B,#0x01                   3
//C:0x00CE    22       RET      
多个if情况时这里应该是一个跳转                                  3

C:0x00CF    E4       CLR      A                                      1
C:0x00D0    F50A     MOV      0x0A,A                            3
C:0x00D2    F50B     MOV      0x0B,A                            3
        由以上代码可知,一个舵机的调速周期为79+[LCALL C?SIDIV(C:0003) ]条指令周期,所以,对于10us的定时器中断,最多可以调速2路舵机。

       接下来我们来分析软件延时的方法进行调速,首先我们需要一个延时函数,那么我们定义一个延时10us的函数如下所示:

void Delay10us(UINT16 ntimes)
{
    for (delayVar1 = 0; delayVar1 < ntimes; ++delayVar1)
        for (delayVar2 = 0; delayVar2 < 21; ++delayVar2)   
    	    _nop_();     
}
        如果对每个舵机分别进行调速,那么我们的时间开销为0.6 * 16 = 9.6s,这个时间是不可接受的。由于8位单片机有P0-P3的管脚阵列,那么我们可以利用其中的某一列,同时调速8路舵机,那么我们的时间开销为0.6 * 2 = 1.2s,这个对于我们的机器人来说是完全可以接受的。

        同时对8路舵机进行调速的话,我们首先需要对舵机的调速角度进行排序(升序),然后计算出两两舵机舵机的差值,这样就可以同时对8路舵机进行调速了。对于出现的非法角度,我们给这路舵机送全为高电平的PWM信号,使此路舵机保持先前状态。代码如下所示:

void InitPwmPollint()
{
    UCHAR8 i;
    UCHAR8 j;
    UCHAR8 k;
    UINT16 temp;
    
    for (i = 0; i < USED_PIN; ++i)
    {
        for (j = 0; j < 7; ++j)
        {
            for (k = j; k < 8; ++k)
            {
                if (g_diffAngle[i][j] > g_diffAngle[i][k])
                {
                    temp = g_diffAngle[i][j];
                    g_diffAngle[i][j] = g_diffAngle[i][k];
                    g_diffAngle[i][k] = temp;
                    
                    temp = g_diffAngleMask[i][j];
                    g_diffAngleMask[i][j] = g_diffAngleMask[i][k];
                    g_diffAngleMask[i][k] = temp;
                }
            }
        }
    }
    
    for (i = 0; i < USED_PIN; ++i)
    {
        for (j = 0; j < 8; ++j)
        {
            if (INVALID_JOINT_VALUE == g_diffAngle[i][j])
            {
                g_diffAngle[i][j] = STEERING_ENGINE_FULL_CYCLE;
            }
        }
    }
    
    for (i = 0; i < USED_PIN; ++i)
    {
        for (j = 7; j >= 1; --j)
        {
            g_diffAngle[i][j] = g_diffAngle[i][j] - g_diffAngle[i][j - 1];
        }
    }    
}
        (注:对于上面用到的一些常量,请大家参考本文最后给出的完整程序。

         下面我们举例说明上述代码,假设 g_diffAngle[0][] = {10, 20, 40, 40, 50, 80, 90, 10},那么经过排序后,其变为g_diffAngle[0][] = {10, 10, 20, 40, 40, 50, 80, 90},而g_diffAngleMask[0][]中的值对应个角度使用的管脚的掩码,例如P0.0,则其掩码为0x01,对P0 & 0x01进行掩码计算,就可以对指定的管脚进行操作。我们对角度排序的同时,需要更新管脚的掩码,以达到相互对应的目的。

        接下来对角度进行校验,对于不合法的角度,我们使用STEERING_ENGINE_FULL_CYCLE来进行填充,使这路舵机在PWM调速周期内全为高电平。
       最后计算差值,计算后g_diffAngle[0][]={10, 0, 10, 20, 0, 10, 30, 10},这样就求出了后面的舵机相对于其前面一路的差值。我们对g_diffAngle[0][0]对应的舵机延时Delay10us(g_diffAngle[0][0])即Delay10us(10),延时完成后我们将这路舵机给低电平,这用到了前面定义的掩码操作。延时完成后,开始延时g_diffAngle[0][1]对应的舵机,Delay10us(g_diffAngle[0][1])即Delay10us(0),然后将此路舵机送低电平。再延时Delay10us(g_diffAngle[0][2])即Delay10us(10),然后再移除,依次类推。这样就能保证在一个PWM周期内对8路舵机同时调速。调速部分代码如下:

#define PWM_STEERING_ENGINE(group)                                                          \
{                                                                                           \
    counter = STEERING_ENGINE_INIT_DELAY;                                                   \
        for (i = 0; i < 8; ++i)                                                             \
            counter += g_diffAngle[PIN_GROUP_##group][i];                                   \
                                                                                            \
        for (i = 0; i < 30; ++i)                                                            \
        {                                                                                   \
            GROUP_##group##_CONTROL_PIN = 0xFF;                                             \
            Delay10us(STEERING_ENGINE_INIT_DELAY);                                          \
                                                                                            \
            for (j = 0; j < 8; ++j)                                                         \
            {                                                                               \
                Delay10us(g_diffAngle[PIN_GROUP_##group][j]);                               \
                GROUP_##group##_CONTROL_PIN &= ~(g_diffAngleMask[PIN_GROUP_##group][j]);    \
            }                                                                               \
                                                                                            \
            Delay10us(STEERING_ENGINE_CYCLE - counter);                                     \
        }                                                                                   \
}
while (1)
{
    PWM_STEERING_ENGINE(1);
}
        通过上面的操作,我们便完成了同时对8路舵机的调速操作。

        总结:

        对于只有1、2路舵机的情况,我们可以使用定时器中断进行调速,其具有实现简单,通俗易懂的有点。而对于多路舵机的情形,就需要使用软件延时进行操作,其优点是可以同时对8路舵机进行调速,效率高,但是其代码不容易实现与调试。

        附完整程序:

        (注:由于需求变动很大,包括功能和管脚分配等,所以本程序大量使用宏及enum)

/**
 * @file    ControlRobot.h
 * @author  马冬亮
 * @brief   用于对机器人进行控制.
 */

#ifndef CONTROLROBOT_H
#define	CONTROLROBOT_H

/**
 * @brief   关节角度非法值.
 * @ingroup ControlRobot
 * 
 * @details 当关节角度无法确定或捕获关节失败时, 设置为此数值, 机器人接收到此数值则不转动舵机.
 */
#define INVALID_JOINT_VALUE 200

/**
 * @brief   定义关节常量用于存取关节角度.
 * @ingroup ControlRobot
 */
typedef enum RobotJoint
{
    ROBOT_LEFT_SHOULDER_VERTICAL      = 0,  ///< 左肩膀, 前后转动的舵机
    ROBOT_LEFT_SHOULDER_HORIZEN       = 1,  ///< 左肩膀, 上下转动的舵机
    ROBOT_LEFT_ELBOW                  = 2,  ///< 左肘, 左臂肘关节的舵机

    ROBOT_RIGHT_SHOULDER_VERTICAL     = 3,  ///< 右肩膀, 前后转动的舵机
    ROBOT_RIGHT_SHOULDER_HORIZEN      = 4,  ///< 右肩膀, 上下转动的舵机
    ROBOT_RIGHT_ELBOW                 = 5,  ///< 右肘, 右臂肘关节的舵机

    ROBOT_LEFT_HIP_VERTICAL           = 6,  ///< 左髋, 左右转动的舵机
    ROBOT_LEFT_HIP_HORIZEN            = 7,  ///< 左髋, 前后转动的舵机
    ROBOT_LEFT_KNEE                   = 8,  ///< 左膝盖, 左膝关节的舵机
    ROBOT_LEFT_ANKLE                  = 9,  ///< 左踝, 这个舵机不使用

    ROBOT_RIGHT_HIP_VERTICAL          = 10, ///< 右髋, 左右转动的舵机
    ROBOT_RIGHT_HIP_HORIZEN           = 11, ///< 右髋, 前后转动的舵机
    ROBOT_RIGHT_KNEE                  = 12, ///< 右膝盖, 右膝关节的舵机
    ROBOT_RIGHT_ANKLE                 = 13, ///< 右踝, 这个舵机不使用

    ROBOT_LEFT_FOOT                   = 14, ///< 左脚, 这个舵机不使用
    ROBOT_RIGHT_FOOT                  = 15, ///< 右脚, 这个舵机不使用

    ROBOT_HEAD                        = 16, ///< 头, 这个舵机不使用
        
    ROBOT_JOINT_AMOUNT                = ROBOT_HEAD + 1
} RobotJoint;

typedef enum RobotSteeringEnginePinIndex
{
    ROBOT_PIN_INDEX_LEFT_SHOULDER_VERTICAL                  = 0,
    ROBOT_PIN_INDEX_LEFT_SHOULDER_HORIZEN                   = 1,
    ROBOT_PIN_INDEX_LEFT_ELBOW                              = 2,
    
    ROBOT_PIN_INDEX_RIGHT_SHOULDER_VERTICAL                 = 3,
    ROBOT_PIN_INDEX_RIGHT_SHOULDER_HORIZEN                  = 4,
    ROBOT_PIN_INDEX_RIGHT_ELBOW                             = 5,
    
    ROBOT_PIN_INDEX_LEFT_HIP_VERTICAL                       = 6,
    ROBOT_PIN_INDEX_RIGHT_HIP_VERTICAL                      = 7,
    
    ROBOT_PIN_INDEX_LEFT_HIP_HORIZEN                        = 0,
    ROBOT_PIN_INDEX_LEFT_KNEE                               = 1,
    ROBOT_PIN_INDEX_LEFT_ANKLE                              = 2,
    ROBOT_PIN_INDEX_LEFT_FOOT                               = 3,
    
    ROBOT_PIN_INDEX_RIGHT_HIP_HORIZEN                       = 4,
    ROBOT_PIN_INDEX_RIGHT_KNEE                              = 5,
    ROBOT_PIN_INDEX_RIGHT_ANKLE                             = 6,
    ROBOT_PIN_INDEX_RIGHT_FOOT                              = 7
} RobotSteeringEnginePinIndex;

typedef enum RobotSteeringEnginePinMask
{
    ROBOT_PIN_MASK_LEFT_SHOULDER_VERTICAL                  = 0x01,
    ROBOT_PIN_MASK_LEFT_SHOULDER_HORIZEN                   = 0x02,
    ROBOT_PIN_MASK_LEFT_ELBOW                              = 0x04,
    
    ROBOT_PIN_MASK_RIGHT_SHOULDER_VERTICAL                 = 0x08,
    ROBOT_PIN_MASK_RIGHT_SHOULDER_HORIZEN                  = 0x10,
    ROBOT_PIN_MASK_RIGHT_ELBOW                             = 0x20,
    
    ROBOT_PIN_MASK_LEFT_HIP_VERTICAL                       = 0x40,
    ROBOT_PIN_MASK_RIGHT_HIP_VERTICAL                      = 0x80,
    
    ROBOT_PIN_MASK_LEFT_HIP_HORIZEN                        = 0x01,
    ROBOT_PIN_MASK_LEFT_KNEE                               = 0x02,
    ROBOT_PIN_MASK_LEFT_ANKLE                              = 0x04,
    ROBOT_PIN_MASK_LEFT_FOOT                               = 0x08,
    
    ROBOT_PIN_MASK_RIGHT_HIP_HORIZEN                       = 0x10,
    ROBOT_PIN_MASK_RIGHT_KNEE                              = 0x20,
    ROBOT_PIN_MASK_RIGHT_ANKLE                             = 0x40,
    ROBOT_PIN_MASK_RIGHT_FOOT                              = 0x80
} RobotSteeringEnginePinMask;


    
#define PROTOCOL_HEADER             "\xC9\xCA"
#define PROTOCOL_END                "\xC9\xCB"
#define PROTOCOL_END_LENGTH         2
#define PROTOCOL_HEADER_LENGTH      2
#define COMMAND_STR_LENGTH          (PROTOCOL_HEADER_LENGTH + ROBOT_JOINT_AMOUNT + PROTOCOL_END_LENGTH)
#define COMMAND_STR_BUFFER_SIZE     ((COMMAND_STR_LENGTH) + 2)

#endif	/* CONTROLROBOT_H */
// main.c
#include <STC_NEW_8051.H>
#include "ControlRobot.h"
#include<intrins.h>

#define DEBUG_PROTOCOL

typedef unsigned char UCHAR8;
typedef unsigned int UINT16;

#undef TRUE
#undef FALSE
#define TRUE 1
#define FALSE 0

#define MEMORY_MODEL 

UINT16 MEMORY_MODEL delayVar1;
UCHAR8 MEMORY_MODEL delayVar2;

#define BAUD_RATE 0xF3

#define USED_PIN 2
#define PIN_GROUP_1 0
#define PIN_GROUP_2 1

#define GROUP_1_CONTROL_PIN P0
#define GROUP_2_CONTROL_PIN P2

#define STEERING_ENGINE_CYCLE 2000
#define STEERING_ENGINE_INIT_DELAY 50
#define STEERING_ENGINE_FULL_CYCLE ((STEERING_ENGINE_CYCLE) - (STEERING_ENGINE_INIT_DELAY))

volatile UCHAR8 MEMORY_MODEL g_angle[2][ROBOT_JOINT_AMOUNT];
volatile bit MEMORY_MODEL g_fillingBufferIndex = 0;
volatile bit MEMORY_MODEL g_readyBufferIndex = 1;
volatile bit MEMORY_MODEL g_swapBuffer = FALSE;

volatile UINT16 MEMORY_MODEL g_diffAngle[USED_PIN][8];
volatile UCHAR8 MEMORY_MODEL g_diffAngleMask[USED_PIN][8] = 
{
    {
        ROBOT_PIN_MASK_LEFT_SHOULDER_VERTICAL,  
        ROBOT_PIN_MASK_LEFT_SHOULDER_HORIZEN,  
        ROBOT_PIN_MASK_LEFT_ELBOW,  
        ROBOT_PIN_MASK_RIGHT_SHOULDER_VERTICAL,
        ROBOT_PIN_MASK_RIGHT_SHOULDER_HORIZEN,  
        ROBOT_PIN_MASK_RIGHT_ELBOW,  
        ROBOT_PIN_MASK_LEFT_HIP_VERTICAL,  
        ROBOT_PIN_MASK_RIGHT_HIP_VERTICAL 
    },
    {
        ROBOT_PIN_MASK_LEFT_HIP_HORIZEN,  
        ROBOT_PIN_MASK_LEFT_KNEE,  
        ROBOT_PIN_MASK_LEFT_ANKLE,  
        ROBOT_PIN_MASK_LEFT_FOOT,  
        ROBOT_PIN_MASK_RIGHT_HIP_HORIZEN,  
        ROBOT_PIN_MASK_RIGHT_KNEE,  
        ROBOT_PIN_MASK_RIGHT_ANKLE,  
        ROBOT_PIN_MASK_RIGHT_FOOT
    }
};

#ifdef DEBUG_PROTOCOL
sbit P10 = P1 ^ 0;  // 正在填充交换区1
sbit P11 = P1 ^ 1;  // 正在填充交换区2
sbit P12 = P1 ^ 2;  // 交换区变换
sbit P13 = P1 ^ 3;  // 协议是否正确
#endif 

void Delay10us(UINT16 ntimes)
{
    for (delayVar1 = 0; delayVar1 < ntimes; ++delayVar1)
        for (delayVar2 = 0; delayVar2 < 21; ++delayVar2)   
    	    _nop_();     
}

void InitPwmPollint()
{
    UCHAR8 i;
    UCHAR8 j;
    UCHAR8 k;
    UINT16 temp;
    
    for (i = 0; i < USED_PIN; ++i)
    {
        for (j = 0; j < 7; ++j)
        {
            for (k = j; k < 8; ++k)
            {
                if (g_diffAngle[i][j] > g_diffAngle[i][k])
                {
                    temp = g_diffAngle[i][j];
                    g_diffAngle[i][j] = g_diffAngle[i][k];
                    g_diffAngle[i][k] = temp;
                    
                    temp = g_diffAngleMask[i][j];
                    g_diffAngleMask[i][j] = g_diffAngleMask[i][k];
                    g_diffAngleMask[i][k] = temp;
                }
            }
        }
    }
    
    for (i = 0; i < USED_PIN; ++i)
    {
        for (j = 0; j < 8; ++j)
        {
            if (INVALID_JOINT_VALUE == g_diffAngle[i][j])
            {
                g_diffAngle[i][j] = STEERING_ENGINE_FULL_CYCLE;
            }
        }
    }
    
    for (i = 0; i < USED_PIN; ++i)
    {
        for (j = 7; j >= 1; --j)
        {
            g_diffAngle[i][j] = g_diffAngle[i][j] - g_diffAngle[i][j - 1];
        }
    }    
}


void InitSerialPort()
{
    AUXR    = 0x00;
    ES      = 0;
    TMOD    = 0x20;
    SCON    = 0x50;
    TH1     = BAUD_RATE;
    TL1     = TH1;
    PCON    = 0x80;
    EA      = 1;
    ES      = 1;
    TR1     = 1;
}

void OnSerialPort() interrupt 4
{
    static UCHAR8 previousChar = 0;
    static UCHAR8 currentChar = 0;
    static UCHAR8 counter = 0;

    if (RI)
    {
        RI = 0;
        currentChar = SBUF;

        if (PROTOCOL_HEADER[0] == currentChar)    	 // 协议标志
        {
            previousChar =     currentChar;
        }
        else
        {
            if (PROTOCOL_HEADER[0] == previousChar && PROTOCOL_HEADER[1] == currentChar)       // 协议开始
            {
                counter = 0;
                previousChar = currentChar;
                g_swapBuffer = FALSE;
            }
            else if (PROTOCOL_END[0] == previousChar && PROTOCOL_END[1] == currentChar)   // 协议结束
            {
                previousChar = currentChar;

                if (ROBOT_JOINT_AMOUNT == counter)    	// 协议接受正确
                {
                    if (0 == g_fillingBufferIndex)
                    {
                        g_readyBufferIndex = 0;
                        g_fillingBufferIndex = 1;
                    }
                    else
                    {
                        g_readyBufferIndex = 1;
                        g_fillingBufferIndex = 0;
                    }

                    g_swapBuffer = TRUE;
    
#ifdef DEBUG_PROTOCOL
                    P13 = 0;
#endif
                }
                else
                {
                    g_swapBuffer = FALSE;
                    
#ifdef DEBUG_PROTOCOL
                    P13 = 1;
#endif
                }

                counter = 0;
            }
            else   // 接受协议正文
            {
                g_swapBuffer = FALSE;
                previousChar = currentChar;

                if (counter < ROBOT_JOINT_AMOUNT)
                     g_angle[g_fillingBufferIndex][counter] = currentChar;

                ++counter;
            }
        }    // if (PROTOCOL_HEADER[0] == currentChar)

        SBUF = currentChar;
        while (!TI)
            ;
        TI = 0;
    }    // (RI)
}

void FillDiffAngle()
{
    // 设置舵机要调整的角度
    g_diffAngle[PIN_GROUP_1][ROBOT_PIN_INDEX_LEFT_SHOULDER_VERTICAL] = g_angle[g_readyBufferIndex][ROBOT_LEFT_SHOULDER_VERTICAL];
    g_diffAngle[PIN_GROUP_1][ROBOT_PIN_INDEX_LEFT_SHOULDER_HORIZEN] = g_angle[g_readyBufferIndex][ROBOT_LEFT_SHOULDER_HORIZEN];
    g_diffAngle[PIN_GROUP_1][ROBOT_PIN_INDEX_LEFT_ELBOW] = g_angle[g_readyBufferIndex][ROBOT_LEFT_ELBOW];

    g_diffAngle[PIN_GROUP_1][ROBOT_PIN_INDEX_RIGHT_SHOULDER_VERTICAL] = g_angle[g_readyBufferIndex][ROBOT_RIGHT_SHOULDER_VERTICAL];
    g_diffAngle[PIN_GROUP_1][ROBOT_PIN_INDEX_RIGHT_SHOULDER_HORIZEN] = g_angle[g_readyBufferIndex][ROBOT_RIGHT_SHOULDER_HORIZEN];
    g_diffAngle[PIN_GROUP_1][ROBOT_PIN_INDEX_RIGHT_ELBOW] = g_angle[g_readyBufferIndex][ROBOT_RIGHT_ELBOW];

    g_diffAngle[PIN_GROUP_1][ROBOT_PIN_INDEX_LEFT_HIP_VERTICAL] = g_angle[g_readyBufferIndex][ROBOT_LEFT_HIP_VERTICAL];
    g_diffAngle[PIN_GROUP_1][ROBOT_PIN_INDEX_RIGHT_HIP_VERTICAL] = g_angle[g_readyBufferIndex][ROBOT_RIGHT_HIP_VERTICAL];
    
    g_diffAngle[PIN_GROUP_2][ROBOT_PIN_INDEX_LEFT_HIP_HORIZEN] = g_angle[g_readyBufferIndex][ROBOT_LEFT_HIP_HORIZEN];
    g_diffAngle[PIN_GROUP_2][ROBOT_PIN_INDEX_LEFT_KNEE] = g_angle[g_readyBufferIndex][ROBOT_LEFT_KNEE];
    g_diffAngle[PIN_GROUP_2][ROBOT_PIN_INDEX_LEFT_ANKLE] = g_angle[g_readyBufferIndex][ROBOT_LEFT_ANKLE];
    g_diffAngle[PIN_GROUP_2][ROBOT_PIN_INDEX_LEFT_FOOT] = g_angle[g_readyBufferIndex][ROBOT_LEFT_FOOT];

    g_diffAngle[PIN_GROUP_2][ROBOT_PIN_INDEX_RIGHT_HIP_HORIZEN] = g_angle[g_readyBufferIndex][ROBOT_RIGHT_HIP_HORIZEN];
    g_diffAngle[PIN_GROUP_2][ROBOT_PIN_INDEX_RIGHT_KNEE] = g_angle[g_readyBufferIndex][ROBOT_RIGHT_KNEE];
    g_diffAngle[PIN_GROUP_2][ROBOT_PIN_INDEX_RIGHT_ANKLE] = g_angle[g_readyBufferIndex][ROBOT_RIGHT_ANKLE];
    g_diffAngle[PIN_GROUP_2][ROBOT_PIN_INDEX_RIGHT_FOOT] = g_angle[g_readyBufferIndex][ROBOT_RIGHT_FOOT];

    // 复位舵机管脚索引
    g_diffAngleMask[PIN_GROUP_1][ROBOT_PIN_INDEX_LEFT_SHOULDER_VERTICAL] = ROBOT_PIN_MASK_LEFT_SHOULDER_VERTICAL;
    g_diffAngleMask[PIN_GROUP_1][ROBOT_PIN_INDEX_LEFT_SHOULDER_HORIZEN] = ROBOT_PIN_MASK_LEFT_SHOULDER_HORIZEN;
    g_diffAngleMask[PIN_GROUP_1][ROBOT_PIN_INDEX_LEFT_ELBOW] = ROBOT_PIN_MASK_LEFT_ELBOW;

    g_diffAngleMask[PIN_GROUP_1][ROBOT_PIN_INDEX_RIGHT_SHOULDER_VERTICAL] = ROBOT_PIN_MASK_RIGHT_SHOULDER_VERTICAL;
    g_diffAngleMask[PIN_GROUP_1][ROBOT_PIN_INDEX_RIGHT_SHOULDER_HORIZEN] = ROBOT_PIN_MASK_RIGHT_SHOULDER_HORIZEN;
    g_diffAngleMask[PIN_GROUP_1][ROBOT_PIN_INDEX_RIGHT_ELBOW] = ROBOT_PIN_MASK_RIGHT_ELBOW;

    g_diffAngleMask[PIN_GROUP_1][ROBOT_PIN_INDEX_LEFT_HIP_VERTICAL] = ROBOT_PIN_MASK_LEFT_HIP_VERTICAL;
    g_diffAngleMask[PIN_GROUP_1][ROBOT_PIN_INDEX_RIGHT_HIP_VERTICAL] = ROBOT_PIN_MASK_RIGHT_HIP_VERTICAL;
    
    g_diffAngleMask[PIN_GROUP_2][ROBOT_PIN_INDEX_LEFT_HIP_HORIZEN] = ROBOT_PIN_MASK_LEFT_HIP_HORIZEN;
    g_diffAngleMask[PIN_GROUP_2][ROBOT_PIN_INDEX_LEFT_KNEE] = ROBOT_PIN_MASK_LEFT_KNEE;
    g_diffAngleMask[PIN_GROUP_2][ROBOT_PIN_INDEX_LEFT_ANKLE] = ROBOT_PIN_MASK_LEFT_ANKLE;
    g_diffAngleMask[PIN_GROUP_2][ROBOT_PIN_INDEX_LEFT_FOOT] = ROBOT_PIN_MASK_LEFT_FOOT;

    g_diffAngleMask[PIN_GROUP_2][ROBOT_PIN_INDEX_RIGHT_HIP_HORIZEN] = ROBOT_PIN_MASK_RIGHT_HIP_HORIZEN;
    g_diffAngleMask[PIN_GROUP_2][ROBOT_PIN_INDEX_RIGHT_KNEE] = ROBOT_PIN_MASK_RIGHT_KNEE;
    g_diffAngleMask[PIN_GROUP_2][ROBOT_PIN_INDEX_RIGHT_ANKLE] = ROBOT_PIN_MASK_RIGHT_ANKLE;
    g_diffAngleMask[PIN_GROUP_2][ROBOT_PIN_INDEX_RIGHT_FOOT] = ROBOT_PIN_MASK_RIGHT_FOOT;
}

#define PWM_STEERING_ENGINE(group)                                                          \
{                                                                                           \
    counter = STEERING_ENGINE_INIT_DELAY;                                                   \
        for (i = 0; i < 8; ++i)                                                             \
            counter += g_diffAngle[PIN_GROUP_##group][i];                                   \
                                                                                            \
        for (i = 0; i < 30; ++i)                                                            \
        {                                                                                   \
            GROUP_##group##_CONTROL_PIN = 0xFF;                                             \
            Delay10us(STEERING_ENGINE_INIT_DELAY);                                          \
                                                                                            \
            for (j = 0; j < 8; ++j)                                                         \
            {                                                                               \
                Delay10us(g_diffAngle[PIN_GROUP_##group][j]);                               \
                GROUP_##group##_CONTROL_PIN &= ~(g_diffAngleMask[PIN_GROUP_##group][j]);    \
            }                                                                               \
                                                                                            \
            Delay10us(STEERING_ENGINE_CYCLE - counter);                                     \
        }                                                                                   \
}

void main()
{
    UCHAR8 i;
    UCHAR8 j;
    UINT16 counter;

    InitSerialPort();
    
    P1 = 0xFF;

    // 初始化舵机角度
    for (i = 0; i < ROBOT_JOINT_AMOUNT; ++i)
    {
        g_angle[0][i] = 45;
        g_angle[1][i] = 45;
    }
    
    for (i = 0; i < USED_PIN; ++i)
        for (j = 0; j < 8; ++j)
            g_diffAngle[i][j] = 0;
            
    FillDiffAngle();
    InitPwmPollint();

    while (1)
    {
#ifdef DEBUG_PROTOCOL
        if (g_fillingBufferIndex)
        {
            P11 = 0;
            P10 = 1;
        }
        else
        {
            P11 = 1;
            P10 = 0;
        }

        if (g_swapBuffer)
            P12 = 0;
        else
            P12 = 1;
#endif

        if (g_swapBuffer)
        {
            FillDiffAngle();
            
            g_swapBuffer = FALSE;
            
            InitPwmPollint();
        }
                
        PWM_STEERING_ENGINE(1)
        PWM_STEERING_ENGINE(2)
    }

}