王昱顺 发表于 2024-10-31 11:48:52

STC-FOC Lite程序详解-CAN通讯协议实现

本帖最后由 王昱顺 于 2024-10-31 13:45 编辑

      本篇文章致力于对STC-FOC Lite的原版代码进行详尽的原理解释,并且提出更改/移植/裁剪建议,方便大家对这个项目进行更加适配自己的个性化更改。      CAN通讯因其高可靠性、实时性、灵活性、错误检测能力、抗干扰性、易于扩展、成本效益和广泛的应用领域,成为电机驱动控制等领域的首选协议。它采用非破坏性仲裁技术,支持多主通讯和优先级控制,支持多种数据传送方式,并具备循环冗余检查、帧检查、位填充等错误检测手段。此外,CAN协议在恶劣电磁环境中仍能保持稳定通讯,并且易于网络节点的增加和删除,硬件实现成本低,因此在汽车、工业自动化、医疗设备等多个领域得到广泛应用。      所以,STC-FOC Lite也使用了CAN通讯,保证其易于接入和移除电机,方便使用统一的总线进行链接(只需要CANH和CANL两根线)。      这里首先从代码开始,对CAN实现的协议进行解释

CAN_DataDef _CAN_DAT; // 缓冲区

// 将CAN数据存入缓冲区
void Can_Push_Temp(CAN_DataDef *can_dat)
{
    can_dat->DataBuffer &= ~Ctrl_Sync; // 存入前关闭同步触发标志
    memcpy(&_CAN_DAT, can_dat, sizeof(CAN_DataDef)); // 复制数据到缓冲区
}

// 从缓冲区读取CAN数据
void Can_Get_Temp(CAN_DataDef *can_dat)
{
    can_dat->DataBuffer = 0x00; // 清空同步触发标志位
    memcpy(can_dat, &_CAN_DAT, sizeof(CAN_DataDef)); // 从缓冲区复制数据
}

u8 Last_Mode = 0; // 历史模式,用于内部CAN总线线程判断
long _out_postion = 0; // 输出位置
bit _Run_Flag = 0; // 运行标志

// 数据解码
void Can_Dat_Decoding(CAN_DataDef *can_dat)
{
    if (can_dat->DLC == 8) // 数据长度必须是8字节,否则不处理
    {
      // 检查同步触发标志
      if (can_dat->DataBuffer == 0x55) // 同步触发标志为0x55
      {
            Can_Get_Temp(can_dat); // 获取缓冲区数据并解析
      }

      // 检查CAN ID是否匹配且同步触发标志不为0x55
      if (can_dat->ID == User_Can_ID && can_dat->DataBuffer != 0x55)
      {
            // 检查控制字节中的Get_Dat标志
            if (can_dat->DataBuffer & Get_Dat)
            {
                _out_postion = Read_Postion_Out_Data(); // 读取当前位置
                Can_Send_Flag = 1; // 设置发送标志,准备发送数据

                // 准备发送的数据
                CAN2_Tx.FF = STANDARD_FRAME; // 标准帧
                CAN2_Tx.RTR = 0; // 0: 数据帧,1: 远程帧
                CAN2_Tx.DLC = 0x05; // 数据长度为5字节
                CAN2_Tx.ID = 0x0f; // CAN ID为15

                // 将输出位置拆分为4个字节
                CAN2_Tx.DataBuffer = (u8)((long)(_out_postion) >> 24);
                CAN2_Tx.DataBuffer = (u8)((long)(_out_postion) >> 16);
                CAN2_Tx.DataBuffer = (u8)((long)(_out_postion) >> 8);
                CAN2_Tx.DataBuffer = (u8)((long)(_out_postion));

                // 将用户CAN ID放入第5个字节
                CAN2_Tx.DataBuffer = (u8)(User_Can_ID);
            }

            // 检查控制字节中的Ctrl_Sync标志
            if (can_dat->DataBuffer & Ctrl_Sync)
            {
                Can_Push_Temp(can_dat); // 将数据存入缓冲区
            }
            else
            {
                // 检查控制字节中的Ctrl_Auto标志
                if (can_dat->DataBuffer & Ctrl_Auto)
                {
                  // 优先级最高,不允许同时使能
                  if (Auto_Look_Moto_Config_Flag == 0) // 防止运行中重复启动
                  {
                        Auto_Look_Moto_Config_Flag = 1; // 启动一次自动参数辨识
                  }
                }
                else
                {
                  // 更新运行标志
                  _Run_Flag = (CAN2_Rx->DataBuffer & Ctrl_Run);
                  if (Run_Flag != _Run_Flag)
                  {
                        Run_Flag = _Run_Flag;
                        // 清空累计角度,防止启动突变
                        moto.set_postion = _angle_this_dat;
                        _postion_add_dat = 0; // 进入速度模式清空累计位置
                  }
                }

                // 检查模式字节
                if (can_dat->DataBuffer >= 1 && can_dat->DataBuffer <= 5)
                {
                  Mode = CAN2_Rx->DataBuffer; // 更新模式
                }

                // 解析CAN数据
                Can_Value =
                  (long)can_dat->DataBuffer << 24 |
                  (long)can_dat->DataBuffer << 16 |
                  (long)can_dat->DataBuffer << 8 |
                  (long)can_dat->DataBuffer;

                // 根据模式处理数据
                switch (Mode)
                {
                case OpenLoop_Mode:
                  // 开环模式,不做处理
                  break;

                case Speed_Mode:
                  // 速度模式
                  if (Can_Value <= 5000 && Can_Value >= -5000) // 速度限位
                  {
                        if (Last_Mode != Speed_Mode)
                        {
                            moto.set_postion = _angle_this_dat;
                            _postion_add_dat = 0; // 进入速度模式清空累计位置
                        }
                        moto.set_speed = (int)Can_Value; // 设置速度
                  }
                  break;

                case Postion_Mode:
                  // 位置模式
                  if (Can_Value < 1638400 && Can_Value > -1638400) // 100圈,增量模式
                  {
                        moto.set_postion += Can_Value; // 增量叠加
                        //_postion_add_dat = 0; // 进入位置模式清空累计位置
                  }
                  break;

                case Set_ID_Mode:
                  // 设置ID模式,不做处理
                  break;

                case Servo_Mode:
                  // 舵机模式
                  if (Last_Mode != Servo_Mode)
                  {
                        moto.set_postion = _angle_this_dat;
                        _postion_add_dat = 0; // 进入舵机模式清空累计位置
                        Servo_Config_Flag = 1; // 启动一次自动位置识别
                  }
                  if (Can_Value < 1000 && Can_Value > -1000 &&
                        (Servo_Max_Pos != Servo_Min_Pos) && Servo_Config_Flag == 0) // 范围限定
                  {
                        moto.set_postion = (long)(Servo_Min_Pos + ((float)(Servo_Max_Pos - Servo_Min_Pos) / 2000.0) *
                                                (float)(Can_Value + 1000));
                  }
                  break;

                default:
                  break;
                }

                Last_Mode = Mode; // 更新历史模式
            }
      }
    }
}

速度模式(Speed_Mode):系统首先检查接收到的CAN总线数据值(Can_Value)是否在预设的速度限位范围内(-5000至5000)。如果当前模式与上一次记录的模式不同,则将电机的目标位置设置为当前角度(_angle_this_dat),并重置位置累加器(_postion_add_dat)为0,以确保进入速度模式时不会有位置累积误差。随后,系统将Can_Value转换为整数并设置为电机的速度。位置模式(Postion_Mode):在此模式下,系统检查Can_Value是否在100圈的增量模式范围内(-1638400至1638400)。如果条件满足,则将Can_Value累加到电机的目标位置上,实现位置的增量调整。设置ID模式(Set_ID_Mode):此模式下代码不执行任何操作,用于未来功能的扩展或特定的配置任务。设置ID的功能在config.h文件中,定义为#define User_Can_ID ,修改这个值,可以完成电机本身ID的修改,在控制端就可以通过直接给这个ID发指令控制电机了。舵机模式(Servo_Mode):系统首先检查是否为首次进入舵机模式。如果是,则将电机的目标位置设置为当前角度,并重置位置累加器为0,同时启动一次自动位置识别(Servo_Config_Flag = 1)。接着,系统检查Can_Value是否在限定范围内(-1000至1000),并且舵机的最大位置(Servo_Max_Pos)和最小位置(Servo_Min_Pos)不相等,且自动位置识别标志(Servo_Config_Flag)为0。如果条件满足,则根据Can_Value计算电机的目标位置,该位置位于Servo_Min_Pos和Servo_Max_Pos中间,默认为两限位中心。模式更新:在所有模式处理完毕后,系统更新并保存当前模式(Last_Mode),以便在下一次循环中使用。这个主要是用来给进入某种模式的时候,选择性清空一些记忆值,防止出现电机抽搐。系统通过同步字节实现多电机同步执行的效果,通过首先判断同步字节是否进行同步。来完成电机同步执行指令的操作。具体的流程是:接收到需要同步的标志位,就将本次的设定值进行缓存,不执行。如此循环好几个电机,分别进行写入。写入完成后,通过对ID0发送同步数据,即可让所有电机同步将缓存区指令进行执行(其实所有ID都是广播,不过每个电机在设定本身ID后,通过CAN滤波器进行过滤,使得只能接收到ID0和自己ID的数据)。
页: [1]
查看完整版本: STC-FOC Lite程序详解-CAN通讯协议实现