基于SVPWM的霍尔传感器FOC实现过程

FOC算法笔记

起源:使用ST WorkBench配置的HALL BLDC控制算法抖动严重。

ST的电机控制算法,代码非常高端大气,值得学习。

HAL库与LL库混用,效率很高。很多中断回调都直接重写了,没有使用HAL库那一套。

只是好多地方不知道干嘛的,改动也无从下手。

为了加深理解,这里选择自己敲一遍。谨此记录。

1、硬件配置

stm32G474RET6 + X-NUCLEO-IHM07M1 ;

请添加图片描述

一对极BLDC;
请添加图片描述

12V直流电源;

逻辑分析仪/示波器;

红外测速仪器;

2、软件配置

这里使用keil5+cubeMX; 电机控制板和驱动板的原理图必须搞到;

  • 配置定时器1,四路PWM输出。1-3路为IHM07M1的mos管开关输入,该驱动不需要互补输出,第四路PWM用做触发ADC采样,不需要输出。具体配置如下图:

请添加图片描述

stm32g474主频为170M,定时器1主频也为170M,这里配置PWM中心对齐加6分频,实际的PWM频率为:
C L K P W M = 定时器主频 ( P r e s c a l e r + 1 ) ∗ 2 = 12.14 K CLK_{PWM} = \frac{定时器主频}{(Prescaler+1)*2} = 12.14K CLKPWM=(Prescaler+1)2定时器主频=12.14K
计数值这里暂定1K吧。

Trigger Output(TRGO)配置为 OC4REF,即为第四通道的比较输出功能。作为下文ADC采集的硬件触发功能。这样可以根据实际情况通过改变PWM通道的占空比灵活配置采样时刻。

  • 生成代码后,还需要手动开启PWM输出:

    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
    
  • 还需要开启UART功能,通过上位机Vofa+ 可以观察数据波形。开启功能简单叙述:

    请添加图片描述
    请添加图片描述

由于需要借助VOFA+软件观察波形,所以还需要简单的通信协议;
定义发送缓冲区:char buff[3*4+4] = {0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00, 0x80, 0x7f};
填充数据:*(float*)buff = MotorM1.CurVelocity;*((float*)buff+1) = MotorM1.CurPosition;*((float*)buff+2) = 0;
发送数据:HAL_UART_Transmit_DMA(&huart2,(uint8_t *)&buff,sizeof(buff));

2、开环运行

请添加图片描述

开环运行原理图如上;只需要左侧三个参数即可让电机动起来;其中 V_d = 0; θ 为自增变量;V_q为一个较小的任意值;下面贴出关键函数的实现:

SVPWM:
void Motor::SvpwmTransform(Ualpha_beta_t vol, Dabc_t* duty)
{uint32_t timelimit = sqrt3 * 1000 / 2;uint32_t t0 = 0, t1 = 0, t2 = 0;uint8_t sectiontable[] = { 0, 2, 6, 1, 4, 3, 5, 0 };int32_t U1 = vol.beta;int32_t U2 = (sqrt3 * vol.alpha - vol.beta) / 2;int32_t U3 = -(sqrt3 * vol.alpha + vol.beta) / 2;int16_t ta = 0, tb = 0, tc = 0;uint8_t a = U1 > 0 ? 1 : 0;uint8_t b = U2 > 0 ? 1 : 0;uint8_t c = U3 > 0 ? 1 : 0;uint8_t n = 4 * c + 2 * b + a;//CurSector = sectiontable[n];switch (sectiontable[n]) {case 1:t1 = U2;//4t2 = U1;//6break;case 2:t1 = -U3;//6t2 = -U2;//2break;case 3:t1 = U1;//2t2 = U3;//3break;case 4:t1 = -U2;//3t2 = -U1;//1break;case 5:t1 = U3;//1t2 = U2;//5break;case 6:t1 = -U1;//5t2 = -U3;//4break;default:break;}//最大到六边形的内切圆,if (t1 + t2 > timelimit) {uint32_t temp = t1 + t2;t1 = t1 * (timelimit) / temp;t2 = t2 * (timelimit) / temp;}t0 = (timelimit - t1 - t2) >> 1;switch (sectiontable[n]) {case 1:ta = t0 >> 1;tb = (t0 + t1) >> 1;tc = (t0 + t1 + t2) >> 1;break;case 2:ta = (t0 + t2) >> 1;tb = t0 >> 1;tc = (t0 + t1 + t2) >> 1;break;case 3:ta = (t0 + t1 + t2) >> 1;tb = t0 >> 1;tc = (t0 + t1) >> 1;break;case 4:ta = (t0 + t1 + t2) >> 1;tb = (t0 + t2) >> 1;tc = t0 >> 1;break;case 5:ta = (t0 + t1) >> 1;tb = (t0 + t1 + t2) >> 1;tc = t0 >> 1;break;case 6:ta = t0 >> 1;tb = (t0 + t1 + t2) >> 1;tc = (t0 + t2) >> 1;break;default:break;}duty->a = ta;duty->b = tb;duty->c = tc;
}
更新占空比:
Dabc_t Motor::UpdateDuty(void)
{int ccr_limit = 1000;Dabc_t duty = { 0,0,0 };duty.a = MotorM1.Duty.a / 1000;duty.b = MotorM1.Duty.b / 1000;duty.c = MotorM1.Duty.c / 1000;if(MotorM1.Enable){__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,duty.a * ccr_limit);__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,duty.b * ccr_limit);__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_3,duty.c * ccr_limit);}else{__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,duty.a * 0);__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,duty.b * 0);__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_3,duty.c * 0);}return duty;
}

连接好驱动板、控制板、电机,如果硬件没有问题应该就可以缓慢的转动了。可以适当调节Vq和θ的值。同时配合示波器观察实际输出。逐步排查问题,常见的问题如:驱动没有使能,没有供电,相关硬件保护被触发,跳线帽不正确,导线连接问题等。

实际占空比的波形如下:(vd = 200, θ += 0.001)

请添加图片描述

开环转动过程中可能会伴随着电机的噪声、抖动,驱动的发热等,均为正常现象,该步骤只是暂时的过渡阶段,仅用于验证代码和硬件。下面着手实现闭环:

3、电流检测

硬件原理

IHM07M1驱动采样电路简图:

一种三相电机的三相电流采样方法与流程

其中 Ra、Rb、Rc为采样电阻。由原理图知:只有对应相的下桥臂打开时才能测量该相的电流。否则没有电流经过测量无意义。同时该驱动板支持三电阻采样。

请添加图片描述

每个采样电阻阻值为0.33欧姆。

请添加图片描述

三路电流采样电路相同,放大倍数已经标注为1.53倍。电流采样非常简单,即使用采样电阻串联在电机某相回路中,通过测量单片机ADC采样电阻两端电压,由于采样电阻阻值已知,可以通过欧姆定律反推实际电流:
I 相电流 = U 采样电阻两端电压 R 采样电阻阻值 ∗ 放大倍数 I_{相电流} = \frac{U_{采样电阻两端电压}}{R_{采样电阻阻值}* 放大倍数} I相电流=R采样电阻阻值放大倍数U采样电阻两端电压

软件原理

​ 由于ADC采样在每个PWM周期,速度很快(12.14K),为节约CPU资源一般是使用DMA的,在DMA完成中断回调中进行计算和处理。

其中ADC配置如下图示,配置仅做参考。

通道1和通道7对应PA0和PC1,这两个GPIO连接了采样电流运放的输出。所以只能配置这两个通道。

请添加图片描述

其中外部触发源一定选择 Timer 1 Trigger Out Event,该触发源对应前文叙述的PWM通道4。

请添加图片描述

DMA功能也非常重要,12位ADC只能开启半字传输,也就是16位传输,同时开启循环模式,这样会自动更新索引并覆盖之前的数据。

请添加图片描述

配置完成后还需要手动开启:

uint16_t ADC_result[3] ; // ADC缓冲区
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_result, 2); //开启传输

同时设置定时器1第四通道的比较值:

__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_4,950);

在DMA中断回调中计算电机相电流:

void  HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{if(hadc->Instance==ADC1){		MotorM1.PhaseCurr.a = (ADC_result[0] - 1910) * 3.3 / 4096 / 1.528 / 0.33;MotorM1.PhaseCurr.b = (ADC_result[1] - 1910) * 3.3 / 4096 / 1.528 / 0.33;MotorM1.PhaseCurr.c = -MotorM1.PhaseCurr.a - MotorM1.PhaseCurr.b;HAL_GPIO_TogglePin(USER_LED_GPIO_Port, USER_LED_Pin); //翻转LED观察测量时刻是否正常}
}

采样电阻两端的电压计算公式为:
U 采样电阻 = ( 采样值 − 校零值 ) 3.3 4096 U_{采样电阻} = (采样值 - 校零值) \frac{3.3}{4096} U采样电阻=(采样值校零值)40963.3
将采样电阻的电压带入上文的相电流计算公式即得到电机相电流,计算前两相电流后,可以通过基尔霍夫定律得到第三相电流。

通过逻辑分析仪观察实际电流采样时刻:
请添加图片描述
请添加图片描述

可以看到;周期结束和开始时进行LED翻转,而LED翻转在DMA中断回调中进行,该配置与预期相符。

通过Vofa上位机观察实际的三相电流:

请添加图片描述

可以看到电流三相对称,与预期相符。

实际的电机电流可能有抖动、震荡,如下图:

请添加图片描述

这样需要根据实际情况修改采样时刻,在电流稳定的时刻进行采样。

4、电流闭环运行

将采样电流与目标电流进行误差控制,实现电流环控制,将电流环控制程序放在定时器1的溢出中断。每个PWM周期进行一次电流采样,采样后进行PI控制,同时调整下一周期的PWM占空比。θ依然为自增变量,目标电流与前文V_d、V_q相同。部分代码如下:

motor_t theta = 0;
void FOC_ControllerM1()
{Ialpha_beta_t cur_two;Ialpha_beta_t vol_two;Idq_t cur_rotate;Idq_t vol_rotate;theta += 0.001;if (theta > 2 * Pi){theta -= 2 * Pi;}if(theta < - 2 * Pi){theta += 2 * Pi;}cur_two = Clark_Transform(MotorM1.PhaseCurr);cur_rotate = Park_Transform(cur_two, theta);vol_rotate.d = PIDCtrlBlock(MotorM1.TargetCurr.d, cur_rotate.d*1000, &pid_Id);vol_rotate.q = PIDCtrlBlock(MotorM1.TargetCurr.q, cur_rotate.q*1000, &pid_Iq);vol_two = Inverse_Park_Transform(vol_rotate, theta);MotorM1.SvpwmTransform(vol_two, &MotorM1.Duty);MotorM1.UpdateDuty();
}

PI控制器的参数需要调整到比较稳定的数值,因为接下来是速度环,需要先将电流环稳定才行。

5、速度闭环运行

霍尔信号检测速度和位置,其实就是根据三个霍尔信号来判断的。根据三个信号的高低电平,霍尔信号分为 5->1->3->2->6->4 六个状态,并按照这样的顺序依次变化,反转就是逆序的变化。如图,120度摆放的霍尔传感器,每60度就会变化一次状态。

请添加图片描述

HALL捕获配置

将定时器配置为HALL Sensor Mode 模式。三个通道就会连接在一起,当三个通道状态变化时,就会发生中断。

软件中有两个点需要注意:

  • HALL Sensor Ratio 2分频后,只有上升沿触发中断。不分频则是上升沿下降沿都会中断。

  • HALL开启溢出中断,可以设置中断源,设置为只有计数器上/下溢才中断,否则会在每次HALL中断都触发溢出中断。
    请添加图片描述

    HAL_TIMEx_HallSensor_Start_IT(&htim2);  //开启HALL中断
    __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);//开启更新中断
    __HAL_TIM_URS_ENABLE(&htim2);				//设置中断源为溢出中断重写输入捕获中断回调;
    void  HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
    {
    // 中断中,读取HALL的三个信号的状态;
    // 通过判断HALL状态的顺序,判断正反转。
    // HALL状态对应了转子的位置;因为每次中断电机转动60度,所以实际位置 = 上一次位置 +(反转-) 60度;
    // 每次中断都需要读取通道1的捕获值;中断会中断清空计数值;
    // 捕获值记录了该次中断与上次中断的时间间隔;由此可以计算速度;因为每次中断都代表电机转动了60度,可以计算出瞬时速度;}
    
速度闭环

上面已经得到了离散的六个霍尔位置,并且计算得到了速度;

此时,开环运行的θ就不需要自加了。该位置信息为 六个离散的位置加对瞬时速度的积分,积分时间为FOC速度环的控制周期;

inter = ((float)(MotorM1.CurVelocity)* 2.0f * Pi ) / 60000.0f ;
//速度单位为转每分,转化为弧度,除60000后变为 1ms 经过的弧度。
theta = (float)MotorM1.CurPosition/(65536*2π) + inter;
//65535为霍尔定时器的计算器装载值;

速度环的执行频率暂定为1K。
至此,只需要调节PID的参数,即可实现正常运行。
请添加图片描述

实测轻松达到万转;

但是PID参数还是需要细调,速度不是一直很平稳。
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3267850.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript关键词

JavaScript 关键词 JavaScript 语句常常通过某个关键词来标识需要执行的 JavaScript 动作。 下面的表格列出了一部分将在教程中学到的关键词&#xff1a; 关键词 描述 break 终止 switch 或循环。 continue 跳出循环并在顶端开始。 debugger 停止执行 JavaScript&…

matplotlib 画图函数,最常用的

并排显示2个图片 import os import numpy as np from PIL import Image import matplotlib.pyplot as pltimage1 Image.open(a.png) image2 Image.open(a2.png)# Create a figure with two subplots (1 row, 2 columns) fig, axes plt.subplots(1, 2, figsize(10, 5))# Di…

双非二本毕业生如何进入嵌入式系统领域?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 二本的话学历上会吃点亏…

docker Docs相关使用文档链接

Docker Docshttps://docs.docker.com/ docker compose | Docker Docshttps://docs.docker.com/reference/cli/docker/compose/docker | Docker Docshttps://docs.docker.com/reference/cli/docker/

axios请求大全

本文讲解axios封装方式以及针对各种后台接口的请求方式 axios的介绍和基础配置可以看这个文档: 起步 | Axios中文文档 | Axios中文网 axios的封装 axios封装的重点有三个&#xff0c;一是设置全局config,比如请求的基础路径&#xff0c;超时时间等&#xff0c;第二点是在每次…

MySQL零散拾遗(四)--- 使用聚合函数时需要注意的点点滴滴

聚合函数 聚合函数作用于一组数据&#xff0c;并对一组数据返回一个值。 常见的聚合函数&#xff1a;SUM()、MAX()、MIN()、AVG()、COUNT() 对COUNT()聚合函数的更深一层理解 COUNT函数的作用&#xff1a;计算指定字段在查询结果中出现的个数&#xff08;不包含NULL值&#…

PostgreSQL 中如何重置序列值:将自增 ID 设定为特定值开始

我是从excel中将数据导入&#xff0c;然后再通过sql插入数据&#xff0c;就报错。 需要设置自增ID开始值 1、确定序列名称&#xff1a; 首先&#xff0c;需要找到与的增字段相关的序列名称。假设表名是 my_table 和自增字段是 id&#xff0c;可以使用以下查询来获取序列名称…

H264编码标准环路滤波原理

方块效应产生原因 原因 1&#xff1a;最重要的一个原因是基于块的帧内和帧间预测残差的 DCT 变换。变换系数的量化过程相对粗糙&#xff0c;因而反量化过程恢复的变换系数带有误差&#xff0c;会造成在图像块边界上的视觉不连续。原因 2&#xff1a;其次原因自于运动补偿预测。…

元素标签的attr属性的巧妙利用(值得收藏)

前言 需求如图&#xff1a; 虽然可以通过一个标签&#xff0c;直接赋值&#xff0c;然后通过定位也能实现需求;但是另一种方式更巧妙&#xff0c;有时候可以通过少量代码实现多样的需求&#xff0c;把一个元素展示在盒子上的方法&#xff0c;通过使用元素的attr属性&#xf…

【漏洞复现】APP分发签名系统index-uplog.php存在任意文件上传漏洞

漏洞描述 APP分发签名系统index-uplog.php存在任意文件上传漏洞 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事危害国家安全、荣誉和利益,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵…

刷题了:232.用栈实现队列| 225. 用队列实现栈 |20. 有效的括号 | 1047. 删除字符串中的所有相邻重复项

232.用栈实现队列 题目链接:https://leetcode.cn/problems/implement-queue-using-stacks/description/ 文章讲解:https://programmercarl.com/0232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.html 视频讲解:https://www.bilibili.com/video/BV1nY4y1w7VC/?sp…

[Meachines] Lame smbd3.0-RCE

信息收集 IP AddressOpening Ports10.10.10.3TCP:21,22,139,445,3632 $ nmap -p- 10.10.10.3 --min-rate 1000 -sC -sV 21/tcp open ftp vsftpd 2.3.4 |_ftp-anon: Anonymous FTP login allowed (FTP code 230) | ftp-syst: | STAT: | FTP server status: | …

k8s v1.30 完整安装过程及CNI安装过程总结

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G技术研究。 博客内容主要围绕…

union的特性和大小端

一、union在c和c语言中的特性 1.共享内存空间&#xff1a;union的所有成员共享同一块内存空间。意味着在同一时刻&#xff0c;union 只能存储其成员 中的一个值。当你修改了union中的一个成员&#xff0c;那么其它成员的值也会被改变&#xff0c;因为它们实际上都是指向同一块…

【Blockly图形化积木编程二次开发学习笔记】5.自动保存与恢复

文章目录 引用使用 引用 <script src"./blockly/appengine/storage.js"></script>使用 <script>window.setTimeout(BlocklyStorage.restoreBlocks, 0); // 从本地存储中恢复块BlocklyStorage.backupOnUnload(); // 用户离开页面时自动将块备份到…

Reactor 模型 和 Proactor 模型

在网络IO设计中&#xff0c;有两种高性能模型&#xff1a;Reactor模型和Proactor模型。 Reactor基于同步IO模式&#xff0c;Proactor基于异步IO模式。 不过&#xff0c;无论是 Reactor&#xff0c;还是 Proactor&#xff0c;都是一种基于「事件分发」的网络编程模式&#xff0…

MOZHE SQL手工注入漏洞测试(MySQL数据库)

主界面URL没有参数&#xff0c;无法判断是否有注入点 点击公告 【浏览器不便于查看返回包可以用burp】 测试URL 参数后加上单引号&#xff0c;报错&#xff0c;说明存在注入点 http://124.70.64.48:40021/new_list.php?id1 获取表列数 order by 4 返回200 order by 5 …

怎样在 Nginx 中配置基于请求客户端输入方式的路由?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; 文章目录 怎样在 Nginx 中配置基于请求客户端输入方式的路由一、Nginx 路由的基础概念二、理解客户端输入方式三、基于用户代理的路由配置四、基于请求头的路由配置五、基…

MATLAB基础:数组及其数学运算

今天我们继续学习MATLAB中的数组 我们在学习MATLAB时了解到&#xff0c;MATLAB作者秉持着“万物皆可矩阵”的思想企图将数学甚至世间万物使用矩阵表示出来&#xff0c;而矩阵的处理&#xff0c;自然成了这门语言的重中之重。 数组基础 在MATLAB中&#xff0c;数组是一个基本…

【Android】碎片—动态添加、创建Fragment生命周期、通信

简单用法 在一个活动中添加两个碎片&#xff0c;并让这两个碎片平分活动空间 先新建一个左侧碎片布局和一个右侧碎片布局 左侧碎片 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/…