找回密码
 立即注册
查看: 403|回复: 0

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

[复制链接]
  • 打卡等级:以坛为家II
  • 打卡总天数:458
  • 最近打卡:2025-05-01 07:48:22
已绑定手机
已实名认证

110

主题

2219

回帖

5452

积分

版主

积分
5452
发表于 2024-10-31 11:48:52 | 显示全部楼层 |阅读模式
本帖最后由 王昱顺 于 2024-10-31 13:45 编辑

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


  1. CAN_DataDef _CAN_DAT; // 缓冲区
  2. // 将CAN数据存入缓冲区
  3. void Can_Push_Temp(CAN_DataDef *can_dat)
  4. {
  5.     can_dat->DataBuffer[Dat_Ctrl] &= ~Ctrl_Sync; // 存入前关闭同步触发标志
  6.     memcpy(&_CAN_DAT, can_dat, sizeof(CAN_DataDef)); // 复制数据到缓冲区
  7. }
  8. // 从缓冲区读取CAN数据
  9. void Can_Get_Temp(CAN_DataDef *can_dat)
  10. {
  11.     can_dat->DataBuffer[Dat_SYNC] = 0x00; // 清空同步触发标志位
  12.     memcpy(can_dat, &_CAN_DAT, sizeof(CAN_DataDef)); // 从缓冲区复制数据
  13. }
  14. u8 Last_Mode = 0; // 历史模式,用于内部CAN总线线程判断
  15. long _out_postion = 0; // 输出位置
  16. bit _Run_Flag = 0; // 运行标志
  17. // 数据解码
  18. void Can_Dat_Decoding(CAN_DataDef *can_dat)
  19. {
  20.     if (can_dat->DLC == 8) // 数据长度必须是8字节,否则不处理
  21.     {
  22.         // 检查同步触发标志
  23.         if (can_dat->DataBuffer[Dat_SYNC] == 0x55) // 同步触发标志为0x55
  24.         {
  25.             Can_Get_Temp(can_dat); // 获取缓冲区数据并解析
  26.         }
  27.         // 检查CAN ID是否匹配且同步触发标志不为0x55
  28.         if (can_dat->ID == User_Can_ID && can_dat->DataBuffer[Dat_SYNC] != 0x55)
  29.         {
  30.             // 检查控制字节中的Get_Dat标志
  31.             if (can_dat->DataBuffer[Dat_Ctrl] & Get_Dat)
  32.             {
  33.                 _out_postion = Read_Postion_Out_Data(); // 读取当前位置
  34.                 Can_Send_Flag = 1; // 设置发送标志,准备发送数据
  35.                 // 准备发送的数据
  36.                 CAN2_Tx.FF = STANDARD_FRAME; // 标准帧
  37.                 CAN2_Tx.RTR = 0; // 0: 数据帧,1: 远程帧
  38.                 CAN2_Tx.DLC = 0x05; // 数据长度为5字节
  39.                 CAN2_Tx.ID = 0x0f; // CAN ID为15
  40.                 // 将输出位置拆分为4个字节
  41.                 CAN2_Tx.DataBuffer[0] = (u8)((long)(_out_postion) >> 24);
  42.                 CAN2_Tx.DataBuffer[1] = (u8)((long)(_out_postion) >> 16);
  43.                 CAN2_Tx.DataBuffer[2] = (u8)((long)(_out_postion) >> 8);
  44.                 CAN2_Tx.DataBuffer[3] = (u8)((long)(_out_postion));
  45.                 // 将用户CAN ID放入第5个字节
  46.                 CAN2_Tx.DataBuffer[4] = (u8)(User_Can_ID);
  47.             }
  48.             // 检查控制字节中的Ctrl_Sync标志
  49.             if (can_dat->DataBuffer[Dat_Ctrl] & Ctrl_Sync)
  50.             {
  51.                 Can_Push_Temp(can_dat); // 将数据存入缓冲区
  52.             }
  53.             else
  54.             {
  55.                 // 检查控制字节中的Ctrl_Auto标志
  56.                 if (can_dat->DataBuffer[Dat_Ctrl] & Ctrl_Auto)
  57.                 {
  58.                     // 优先级最高,不允许同时使能
  59.                     if (Auto_Look_Moto_Config_Flag == 0) // 防止运行中重复启动
  60.                     {
  61.                         Auto_Look_Moto_Config_Flag = 1; // 启动一次自动参数辨识
  62.                     }
  63.                 }
  64.                 else
  65.                 {
  66.                     // 更新运行标志
  67.                     _Run_Flag = (CAN2_Rx->DataBuffer[Dat_Ctrl] & Ctrl_Run);
  68.                     if (Run_Flag != _Run_Flag)
  69.                     {
  70.                         Run_Flag = _Run_Flag;
  71.                         // 清空累计角度,防止启动突变
  72.                         moto.set_postion = _angle_this_dat;
  73.                         _postion_add_dat = 0; // 进入速度模式清空累计位置
  74.                     }
  75.                 }
  76.                 // 检查模式字节
  77.                 if (can_dat->DataBuffer[Dat_Mode] >= 1 && can_dat->DataBuffer[Dat_Mode] <= 5)
  78.                 {
  79.                     Mode = CAN2_Rx->DataBuffer[Dat_Mode]; // 更新模式
  80.                 }
  81.                 // 解析CAN数据
  82.                 Can_Value =
  83.                     (long)can_dat->DataBuffer[Dat_HH] << 24 |
  84.                     (long)can_dat->DataBuffer[Dat_H] << 16 |
  85.                     (long)can_dat->DataBuffer[Dat_L] << 8 |
  86.                     (long)can_dat->DataBuffer[Dat_LL];
  87.                 // 根据模式处理数据
  88.                 switch (Mode)
  89.                 {
  90.                 case OpenLoop_Mode:
  91.                     // 开环模式,不做处理
  92.                     break;
  93.                 case Speed_Mode:
  94.                     // 速度模式
  95.                     if (Can_Value <= 5000 && Can_Value >= -5000) // 速度限位
  96.                     {
  97.                         if (Last_Mode != Speed_Mode)
  98.                         {
  99.                             moto.set_postion = _angle_this_dat;
  100.                             _postion_add_dat = 0; // 进入速度模式清空累计位置
  101.                         }
  102.                         moto.set_speed = (int)Can_Value; // 设置速度
  103.                     }
  104.                     break;
  105.                 case Postion_Mode:
  106.                     // 位置模式
  107.                     if (Can_Value < 1638400 && Can_Value > -1638400) // 100圈,增量模式
  108.                     {
  109.                         moto.set_postion += Can_Value; // 增量叠加
  110.                         //_postion_add_dat = 0; // 进入位置模式清空累计位置
  111.                     }
  112.                     break;
  113.                 case Set_ID_Mode:
  114.                     // 设置ID模式,不做处理
  115.                     break;
  116.                 case Servo_Mode:
  117.                     // 舵机模式
  118.                     if (Last_Mode != Servo_Mode)
  119.                     {
  120.                         moto.set_postion = _angle_this_dat;
  121.                         _postion_add_dat = 0; // 进入舵机模式清空累计位置
  122.                         Servo_Config_Flag = 1; // 启动一次自动位置识别
  123.                     }
  124.                     if (Can_Value < 1000 && Can_Value > -1000 &&
  125.                         (Servo_Max_Pos != Servo_Min_Pos) && Servo_Config_Flag == 0) // 范围限定
  126.                     {
  127.                         moto.set_postion = (long)(Servo_Min_Pos + ((float)(Servo_Max_Pos - Servo_Min_Pos) / 2000.0) *
  128.                                                   (float)(Can_Value + 1000));
  129.                     }
  130.                     break;
  131.                 default:
  132.                     break;
  133.                 }
  134.                 Last_Mode = Mode; // 更新历史模式
  135.             }
  136.         }
  137.     }
  138. }
复制代码

速度模式(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的数据)。
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-5-1 22:14 , Processed in 0.103818 second(s), 44 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表