前言:

最近用到了大疆的直流无刷(BLDC)减速电机M3508和M2006。做RoboMaster比赛的同学应该对它们很熟悉,这两款电机质量都不错,配套电调C620、C610功能强大,应用场景广泛。当然价格不算低。

我作为第一次接触电机控制的新手,在搜索PID和三环控制资料的时候常常得到的是一些理论论述,而且千篇一律。虽然PID是较为简单的控制算法,新人上手难度还是有些大。那么我就彻彻底底地回顾一下搭建最简单的电机控制算法的流程。提供一个新的理解视角。

本文依托大疆官方M2006电机例程,其与M3508电机配套例程在CAN通信驱动、PID等核心部分基本相似甚至可以直接替换(M2006例程里的一些文件注释写的是3508)。最大的不同就是M3508使用了FreeRTOS实时系统,这一点并不方便我们的学习。而2006就是裸机前后台,非常简单明晰。

例程均是基于Keil 、STM32F429(我使用407)、HAL库

例程下载:M2006例程M3508例程

0x00 PID到底怎么部署到电机上?

我们都知道PID应用广泛,效果良好,在网上能搜到大量生动形象的文章。这些文章讲述了比例、积分、微分有什么用处和缺陷,什么是死区,并且配有动态曲线图来展示调参效果。但是举的例子往往是这样:一个水缸要固定水位,加水量是输出,反馈是水位之差。我当时看过好多类似文章之后还是不明白怎么把PID应用于更复杂的系统,比如电机控制。

答案是 拆分 复杂系统,运用多次控制算法层层递进

比如在电机上,常用的就是三环控制:电流环、速度环、位置环。

三环层层递进并且具有因果关系

这个关系非常好理解,通过电磁学知识我们知道电机能转是因为有电流产生磁场。所以,电流是电机转动的根本原因,也就是:

graph LR
电流==>运动

中学物理也讲过,(角)速度是描述运动的物理量,包括方向、大小。如果希望控制速度,比如定速转动,那就也要控制电流。再进一步,运动能改变物体位置,如果想确定达到某个位置那就控制速度。然而控制速度归根结底是控制电流。得出:

graph LR
电流--导致-->运动--导致-->位置

得出结论:如果要控制位置,那就要控速度,如果要控速度,就必须控制电流。

位置环的输出是速度值,速度环的输出是电流值。而他们的反馈(输入)都是电机的实时数据:实时速度,实时位置。

就这样,最末端的电机^ 1反馈出三个值,依次给最外面的位置,中间的速度,里面的电流。形成了三个闭合的环。如下图

graph LR
subgraph 位置环
目标位置值-->位置调节
位置调节--速度值-->速度调节
电调==>位置
位置==>位置调节
subgraph 速度环
速度调节-->|电流值|电调
电调==>|转速|速度调节
subgraph 电流环
电调-->电机
电机-->|转速+位置|电调
BY:胡小安
end
end
end

不难发现,很多例子像水缸加水和直升机定高,他们都是直接的位置环,输出量是加水的速度和向上飞的升力。没有底层的两环。

接下来我们以M2006例程为例子看一看怎么实现上述过程。

0x01 剖析例程--多少行代码能实现电机控速?

先说明,M3508支持PWM和CAN两种控制方式,其中PWM可以直接控速但是没有数据反馈。M2006仅支持CAN总线。两种电机的总线编码都是从0x200开始,驱动文件bsp_can.ccan.c几乎一样可以替换。

本文仅介绍PID的部署实现,不涉及CAN通信内容。

总结下来,PID就是一个结构体三个函数。

一个结构体PID_TypeDef:

typedef struct _PID_TypeDef
{
float target; //目标值
float kp; //比例系数
float ki; //积分系数
float kd; //微分系数 float measure; //测量值
float err; //误差
float last_err; //上次误差 float pout; //比例项
float iout; //积分项
float dout; //微分项 float output; //本次输出
float last_output; //上次输出 float MaxOutput; //输出限幅
float IntegralLimit; //积分限幅
float DeadBand; //死区(绝对值)
float Max_Err; //最大误差 void (*f_param_init) //参数初始化
void (*f_pid_reset) //pid三个参数修改
float (*f_cal_pid) //pid计算
}PID_TypeDef;

注意:为了简单明晰,我删去了原文件里一些用不到的参数,最后三个函数指针的参数列表也被删除。其中不乏很重要的计算周期,但是在简单的控制下,时间间隔是可以忽略的。

这个结构体的核心就是三个系数,调参调的也就是这三个。

target目标值和output输出值还有measure反馈值,再就是errlast_err两个误差。

其他的一些限幅,和死区[^2]无非就是防止问题发生的补丁。

三个函数:f_param_initf_pid_resetf_cal_pid

名副其实,分别是结构体的参数初始化,三个系数修改、最重要的输出值计算。

static float pid_calculate(PID_TypeDef* pid, float measure)
{ pid->measure = measure; //目标速度 pid->last_err = pid->err; //更新前一次误差
pid->err = pid->target - pid->measure; //计算当前误差 pid->last_output = pid->output; if((ABS(pid->err) > pid->DeadBand)) //是否进入死区,如果进入则直接跳过,返回上一次的output结果
{ pid->pout = pid->kp * pid->err;
pid->iout += (pid->ki * pid->err); //注意是加等于
pid->dout = pid->kd * (pid->err - pid->last_err); //积分是否超出限制
if(pid->iout > pid->IntegralLimit)
pid->iout = pid->IntegralLimit;
if(pid->iout < - pid->IntegralLimit)
pid->iout = - pid->IntegralLimit; //pid输出和
pid->output = pid->pout + pid->iout + pid->dout; //限制输出的大小
if(pid->output>pid->MaxOutput)
{
pid->output = pid->MaxOutput;
}
if(pid->output < -(pid->MaxOutput))
{
pid->output = -(pid->MaxOutput);
} }
return pid->output;
}

这是计算函数,我们要把实时测量值measure传入函数,用来更新误差,产生新的输出。

measure是电机发来的速度或者位置数据,在CAN中断函数里自动更新。

在这里我们用误差之差代替微分,并且对积分项和输出结果进行了限幅。这些幅度都是自定义的。

得出流程如下:

graph LR;
set_spd--人为更新-->PID_TypeDef
measure--实时更新-->PID_TypeDef
PID_TypeDef-->calc计算--电流值-->电调
电调-->measure

0x02 一个小例子

M2006例程相对M3508虽然简单,但还是包括了一些上位机通信控制的代码。

还是简单明晰的原则。下面是一个最最简单的demo框架。

#include "pid.h"
#define NUM_OF_MOTOR 1 PID_TypeDef moto_pid[NUM_OF_MOTOR];
float set_spd; int main(){ init_all(); for(int i=0;i<NUM_OF_MOTOR;i++){
pid_init(&moto_pid[i]);//把结构体里的函数指针赋值,三个函数
moto_pid[i].f_param_init(&moto_pid[i],PID_Speed,16384,5000,10,0,8000,0,1.5,0.1,0);
//确定结构体内的参数,幅值,死区大小,PID系数 } for(;;){ get_set_spd_from_USART();//从串口得到设定值set_spd for(int i=0; i<NUM_OF_MOTOR; i++)
{
motor_pid[i].target = set_spd; //更新目标值
motor_pid[i].f_cal_pid(&motor_pid[i],measure[i]); //PID计算。measure由CAN中断更新
}
set_moto_current(&hcan1,motor_pid[0].output, //将PID的计算结果通过CAN发送到电机
motor_pid[1].output,
motor_pid[2].output,
motor_pid[3].output); HAL_Delay(10);//延时10ms控制周期
} return 0;
}

到此为止,一个简单的PID电机控制就做好了。如果写的紧凑一点,代码可能不超过50行,还是非常简单的。

注意事项:

我选用的是F407,带有FPU浮点运算单元的MCU。尽量选择CM4,这样浮点运算会快很多。

实际使用的时候要把Keil的option里面target一栏里floating point Hardware选成Single Precision

演示视频在微信视频号上:扫码查看微信文章底部的视频

0x03 总结

以上就是最简单的电机控制部署,本着怎么简单怎么来的原则,希望能帮助朋友们节约一些学习时间。

除了官方例程,我还有自己移植的基于F407的版本,去除例程的无用部分,加入了串口的通信解析,可以比较方便的调参,在线修改速度、pid参数等。有需要的可以加微信公众号直接问我要。

关注嵌入式、电机控制的朋友也可以添加公众号,最近会更新有关上位机通信,CAN通信等电机控制相关内容


大一技术新人,如果发现文中错误请各位大佬不吝赐教,一定指出,如果有意见或建议同样欢迎。谢谢。


欢迎转载,请注明原文链接:(https://www.cnblogs.com/huxiaoan/p/14727970.html)

小白学PID-以大疆M3508、M2006为例的更多相关文章

  1. 大疆M3508、M2006必备CAN总线知识与配置方法

    使用大疆M3508.M2006的CAN总线知识与配置方法 目录 使用大疆M3508.M2006的CAN总线知识与配置方法 前言: 0x00 需要额外的CAN收发器!!! 0x01 硬件层面分析 为什么 ...

  2. 小白学 Python 爬虫(3):前置准备(二)Linux基础入门

    人生苦短,我用 Python 前文传送门: 小白学 Python 爬虫(1):开篇 小白学 Python 爬虫(2):前置准备(一)基本类库的安装 Linux 基础 CentOS 官网: https: ...

  3. 双非本科进大疆(SP)!

    哈喽,大家好,我是仲一.今天和大家分享的是一位优秀双非本科生上岸大疆的经历(羡慕哭了...). 今年4月底的时候,这位学弟和我分享了他拿下oppo,京东,联发科实习offer的经历,当时我还发了朋友圈 ...

  4. 小白学react之网页获取微信用户信息

    通过上一篇<小白学react之EJS模版实战>我们学习了怎样通过EJS模版生成我们高定制化的index.html文件. 本篇我们将会继续延续我们的alt-tutorial项目的实战计划.去 ...

  5. 小白学 Python(2):基础数据类型(上)

    人生苦短,我选Python 引言 前文传送门 小白学 Python(1):开篇 接触一门新的语言,肯定要先了解它的基础数据类型.啥?你问我为啥要先了解基础数据类型? 为了你的生命安全,还是乖乖听我 B ...

  6. 小白学 Python(4):变量基础操作

    人生苦短,我选Python 引言 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 前面的文章中,我们介绍了 ...

  7. 小白学 Python(8):基础流程控制(下)

    人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...

  8. 小白学 Python(21):生成器基础

    人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...

  9. 小白学 Python 爬虫(1):开篇

    人生苦短,我用 Python 引言 各位同学大家好,好久不见(可能只有一两天没见:囧)~~~ 先讲一件事情,昨天为啥没更新. emmmmmmmmm,当然是因为加班啦,快到年底了,公司项目比较忙,最近的 ...

随机推荐

  1. 阿里云DataWorks实践:数据集成+数据开发

    简介 什么是DataWorks: DataWorks(数据工场,原大数据开发套件)是阿里云重要的PaaS(Platform-as-a-Service)平台产品,为您提供数据集成.数据开发.数据地图.数 ...

  2. Centos7 升级 sqlite3

    下载地址:https://www.sqlite.org/download.html [root@djangoServer ~]# wget https://www.sqlite.org/2019/sq ...

  3. JavaWeb实现用户登录注册功能实例代码(基于Servlet+JSP+JavaBean模式)

    一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...

  4. MongoDB 在评论中台的实践

    本文主要讲述 vivo 评论中台在数据库设计上的技术探索和实践. 一.业务背景 随着公司业务发展和用户规模的增多,很多项目都在打造自己的评论功能,而评论的业务形态基本类似.当时各项目都是各自设计实现, ...

  5. brew安装MySQL V5.7

    目录 安装 设置密码 启动 安装 brew install mysql@5.7 // 安装 brew link --force mysql@5.7 // 链接 brew services start ...

  6. MySQL索引由浅入深

    索引是SQL优化中最重要的手段之一,本文从基础到原理,带你深度掌握索引. 一.索引基础 1.什么是索引 MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构,索引对于 ...

  7. golang操作mysql2

    目录 Go操作MySQL 连接 下载依赖 使用MySQL驱动 初始化连接 SetMaxOpenConns SetMaxIdleConns CRUD 建库建表 查询 单行查询 多行查询 插入数据 更新数 ...

  8. MySQL数据库之一

    数据库简介 数据库分类 关系型数据库(SQL):(狭义可以理解为行和列) MySQL,Oracle,Sql Server, DB2 通过表和表之间,行和列之间的关系进行存储 非关系型数据库(NoSQL ...

  9. ElasticSearch入门篇(保姆级教程)

    本章将介绍:ElasticSearch的作用,搭建elasticsearch的环境(Windows/Linux),ElasticSearch集群的搭建,可视化客户端插件elasticsearch-he ...

  10. Sentinel高级

    Sentinel高级 sentinel和springCloud整合 减少开发的复杂度,对大部分的主流框架,例如:Web Servlet.Dubbo.Spring Cloud.gRPC.Spring W ...