本帖最后由 war360xy 于 2022-12-11 15:11 编辑
#申请原创# 无刷电机的 FOC 控制基于 APM32F407IGT6 (电流环与速度环)
前言与调试心得: 前段时间拿到了一些国产 MCU,来自极海半导体的APM32F407IGT6 芯片,于是就准备上些个人项目,试试国产芯片性能如何,正好之前想学习了一些 FOC 控制无刷电机的原理,正好在此芯片上实践一下。首先是 FOC 控制算法对 MCU 的要求还挺高的,因为控制电机旋转需要不停的计算驱动输出值,我这里用的是 10KHz 的频率,也就是 100us 就需要进行一次计算,也就是 SVPWM 的计算,然后改变 PWM 的占空比,在此需要先对电流进行采样,此采样需要在固定的周期去触发,并且需要采样的值不止一个,这里我使用的方案是双电阻下端采样,就是说每个周期需要完成 2 个电压值的采样,采样精度的要求不用很高,因为在电压送入 MCU 的 ADC 之前已经经过运算放大器进行了信号放大并加上了正电压偏置,完成电压值采样之后就需要对电压值进行计算,转换成实际 U、V 相的电流值,之后进行一系列 FOC 算法,计算出最终的一个定时器比较值,送入定时器产生带死区的六路互补 PWM。这里需要好几个时间:1、ADC采样转换双电压时间;2、电压转换成电流的计算、电流到 SVPWM 定时器比较值的计算,此处计算又都是浮点数运算。这两个时间是越短越好,如果是 4 us 那么 (100 - 4) / 100 = 96%,也就意味除去这里所花资源,我还有 96% 的 MCU 资源可以利用。实际上在用此芯片时,ADC转换 + FOC 计算时间所花时间只有 1.76 us + 3.3us = 5.06us。其实此时间还可以缩短,因为我这里 21MHz 的 ADC 12位分辨率转换是用了 15 个采样周期,并且三重注入采样 3 个电压量,但是 ADC (仍然保持12位分辨率)最低只需 3 个采样周期,2 个必需的电压量,那么可以缩短到 0.952 us,最终所花时间就可以减少到 4.25us。这里 ADC 转换是用到了一个叫做多重注入的功能,使用此功能可以一个触发使多个 ADC 模块同时开始采样,到最后采样结果保存到寄存器中只需要消耗一个 ADC 转换一次 + (N - 1) * 5 * ADC_CLK 的时间,而不需要花三次 ADC 转换的时间,详细内容可见 APM32F4xxx用户手册 HYPERLINK "https://geehy.com/uploads/tool/APM32F4xxx%E7%94%A8%E6%88%B7%E6%89%8B%E5%86%8C V1.5.pdf" 。
然后还有一个值得提及的地方,因为是从0开始写 FOC 控制方案,那些算法内容都需要自己手打,每个地方都不能出错,最简单的方法就是与 MATLAB 仿真出的模型进行各个位置的波形对比,如Clark变换的输出结果、park变换的结果,SVPWM 是否输出马鞍波等等。为了达到这个目的就需要通过上位机观察 MCU 传输的波形,通讯手段当然很多,比如说串口、CAN等等,最常用的就是串口了,而为了看波形所需要的就是波形精度了,这就需要较高的通讯速率,而串口的波特率不太能满足我的需求,但这款 AMO32F407IGT6 又有 USB 外设,于是就考虑用 USB 来进行通讯,但其实我之前是没有怎么使用过 USB 外设的,因为 USB 的协议比较复杂也不愿拿时间出来学习,因为想先把电机控制搞一下再说,不过就在阅读手册的时候想到可以用 USB 模拟串口,网上搜了一下就发现了类似的方案,链接于下 HYPERLINK "http://47.111.11.73/forum.php?mod=viewthread&tid=299796&highlight=%CC%BD%CB%F7%D5%DFF407ZGT6+USB" ,连接上上位机之后最高速度是可以达到 1MHz 的(后来发现其实可以用 freemaster 软件直接通过 J-LINK 读取变量观察波形)。
因为 APM32F407 芯片外设资源比较多,Flash 存储又大达到 1M,一开始就想试试双路 FOC 电机控制的,不过考虑到成本,搞双路 FOC 板子得画大,需要物料也增多了,因为不打算做隔离,万一烧毁,整块板子直接报废,于是决定先用一路 FOC 试试水,为了保险起见,其中是加了一个 5A 的保险丝,在后文会贴出原理图详细介绍其中的工作机理与器件。这里做的是霍尔传感器无刷电机 FOC 控制,速度环以及电流环,目前是已经实现速度环控制,但是响应与跟踪效果不好,PI 参数还需进一步调试。
主控 MCU:AMP32F407IGT6,驱动与采样 DRV8301,HSU0048 NMOS,下端采样 5ohm精密电阻。
电源:24V转5V XL4015,5V转3.3V AMS1117
无刷电机内置霍尔传感器 57BL75S10。
PCB 模型:
PCB 3D图如下:
实物图如下:
从图上可以看出,在此PCB引出了许多测试挂钩,主要用于前期调试采集一些信号以及调试 SVPWM 的时候采集相电压等等参数进行观察并对比经转换放大为电压值的电流参数是否与MCU通过ADC采集到的信号波形一致。
此外还有几处错误的地方,能从上图飞线部分看出,是实际调试的时候发现了几处原理图错误的问题比如1、串口的Tx 与 Rx 接反 2、NMOS 与PMOS 错用 3、电机U、V相电流到地走线太细,在某次调试时因为 V 电流走线太细,此时正值电流环调试阶段,电流的大小尚未得到有效的控制,只见调试时,电机突然抽搐一下就停机了,后经检查 PCB 上 V 相电流走线已经断开,当时就怀疑是电流过大,导致走线烧断,经后续检查,发现走线时线宽太窄仅 6mil,走成信号线的宽度了,后续检查发现驱动芯片 DRV8301 、V相 NMOS 驱动管全部损坏,这一块 DRV8301 当时不好买,只能从淘宝小商家处购买其多余的,花了我不少大洋。
所以在这里提醒各位,制版之前一定要好好检查原理图每一个环节都在脑海里过一遍检查检查哪些地方与预想不一样,还有PCB走线宽度一定要仔细考虑,PCB的规则检查也一定不要省。
芯片细节图:
APM32F407IGT6 使用到的 MCU 资源;
首先讲解一下使用到了哪些 MCU 资源:
1、高级定时器TIM1,用于产生带死区的六路互补 PWM 波,主要是 FOC 控制需要使用正弦波控制,普遍使用的是 SVPWM 算法配合逆变器在无刷电机三相输入口产生正弦波电压。(此 MCU 还有其他的高级定时器再做一路 FOC 控制是完全可以的)
2、TIM2 通用定时器,这里用来检测霍尔信号,得到电机的转速,因为目前水平不高,无感无刷电机控制暂时不会,就先用霍尔做着。
3、ADC + 7路采样通道,这里使用的是 MCU 内部ADC进行电压采样,1)主要采样电机的母线电压、2)以及电机电流的电压(通过下端采样电阻,再经过运算放大器进行放大后直接输入 MCU 采样,经测试是完全可以满足要求的)、3)热敏电阻经放大后的电压输出值,此热敏电阻是放在 MOS 管最中间的,主要用于取得此时 MOS 的温度值,从而检测是否有 MOS 过热的情况,如果过热的话,就得让 MOS 歇歇了。4)无刷电机相电压检测,可以对比仿真看看波形如何。
此 ADC 部分我为了缩短电流相关电压的采样时间,在使用 TIM1-CH4 每 100us 触发 ADC 采样的同时,还使能了多重 ADC 采样功能,可以让 ADC1、ADC2、ADC3 在这个 TIM1-CH4 的一个触发下都进行采样转换工作,大大缩短了使用单个 ADC 连续采样三次的时间,这样可以保证我可以在规定时间内(即 MOS 下管关闭的短暂时间内)完成电流转换。
4、UASRT 用于串口调试,这里不打算用串口来做电压采样数据或者 FOC 算法计算数据等的显示,因为串口的通讯速率最大可能就 1M,达不到我要看信号波形的目的,这里主要用来调 PID 参数,以及启动电机、停止电机、设置转速、设置电流等等的对电机的控制功能。
5、USB 首先 USB 的通讯速率可以稳定达到 1M,并且 USB 外设也可以用来模拟如鼠标、键盘、串口啥的,这里就用了一个 USB 模拟串口。就可以很好的完成我想要看数据的需求,此处是从B 站一位 UP 那了解到的方案。
6、SPI1 这里主要用于 OLED 显示,做一个交互界面,等之后控制方案完善了,就可以脱离电脑工作了,就留了这么一个 OLED 跟几个按键、拨盘编码器方便进行交互,目前是 OLED 可以正常工作、正常显示。
7、SPI2 跟 DRV8301 电机驱动芯片进行通讯,设置一些参数。
8、TIM3 通用定时器,检测拨盘编码器的转动次数啥的,暂时搁置了。
原理图部分:
1、首先是 APM32407 最小系统部分:
最小系统都很常见了,可以看到这里还有很多 IO 我都是打了X,因为芯片本身外设多,引脚也多,还有很多 IO 都没用上,是完全没有发挥出芯片本身的性能的。
2、测试点的引出:
可以直接从命名看出具体引出了哪些东西。
3、电源部分:
这里使用了两个 1117,之前跟网友交流了一下,使用一个 1117 供电是不能满足的,于是使用了两个,然后一个 XL4015 用于 24V 转 5V,这是一个可以过几A电流的芯片。
4、霍尔输入接口:
这里做了两个上拉,一个 5V,一个 3.3V 因为不知道电机输出的霍尔信号需要多少伏电压,就两个都画了并且接了一个 0欧电阻,通过是否焊接0欧电阻选择使用多少V上拉,最后实测是需要 5V 上拉,3.3V 上拉输出的波形是不正常的。
5、温度检测电路:
这里用了一个热敏电阻进行分压,然后通过运放放大然后送入MCU的ADC接口,至于为什么选择 GS8552,因为以前比较少用运放,就参考了别人的方案。
6、串口与USB:
这里使用了 CH340 做串口转换芯片,然后一组差分输入的 USB,USB走线在画板时因为是差分信号以及速度较快于是走了等长线。
7、DRV8301 + 驱动部分:
参考 DRV8301 的数据手册布局就行,数据手册中连layout也有参考,这里我使用的下端电阻采样,电阻值是0.005欧姆,只采样 U、V相电流,通过基尔霍夫电流定律计算出 W 相电流,DRV8301放大倍数40倍。
值得一提的代码:
软件部分大都比较常见,在官方的Demo里基本都有,USB 模拟串口这里给一个链接可自行移植,下面主要展示一下 3路多重ADC注入采样 + 4路ADC1规则通道采样的代码实现,可见下图:
首先是 GPIO 初始化
void ADC_Regular_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能 GPIO 时钟
//PC4, PC5, PB0, PB1
RCM_AHB1PeriphClockCmd(RCM_AHB1Periph_GPIOC, ENABLE);
RCM_AHB1PeriphClockCmd(RCM_AHB1Periph_GPIOB, ENABLE);
// 配置 IO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //不上拉不下拉
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void ADC_Inject_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能 GPIO 时钟
//PA0, PA1, PA2
RCM_AHB1PeriphClockCmd(RCM_AHB1Periph_GPIOA, ENABLE);
// 配置 IO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //不上拉不下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
然后是 ADC 配置多重转换模式并使用高级定时器通道4 (TIM1_CH4) 进行硬件触发转换:
void ADC_Regular_Data_Updata(void)
{
ADC_Regular_Def.ADC_Temperature = ADC_VREF_Voltage*(0x0FFF&ADC_ConvertedTemp[0])/ADC_Resolution_MAX;
ADC_Regular_Def.ADC_U_Voltage = ADC_VREF_Voltage*(0x0FFF&ADC_ConvertedTemp[1])/ADC_Resolution_MAX;
ADC_Regular_Def.ADC_V_Voltage = ADC_VREF_Voltage*(0x0FFF&ADC_ConvertedTemp[2])/ADC_Resolution_MAX;
ADC_Regular_Def.ADC_W_Voltage = ADC_VREF_Voltage*(0x0FFF&ADC_ConvertedTemp[3])/ADC_Resolution_MAX;
}
void ADC_Inject_Regular_Config(uint32_t ADC_ExternalTrigInjecConvEdge)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
ADC_DeInit();
DMA_DeInit(DMA2_Stream0);
ADC_Cmd(ADC1, DISABLE);
ADC_Cmd(ADC2, DISABLE);
ADC_Cmd(ADC3, DISABLE);
delay_ms(1);
//使能 ADC1、ADC2、ADC3 以及 DMA 时钟
// 开启ADC时钟
RCM_APB2PeriphClockCmd(RCM_APB2Periph_ADC1, ENABLE);
RCM_APB2PeriphClockCmd(RCM_APB2Periph_ADC2, ENABLE);
RCM_APB2PeriphClockCmd(RCM_APB2Periph_ADC3, ENABLE);
// 开启DMA时钟
RCM_AHB1PeriphClockCmd(RCM_AHB1Periph_DMA2, ENABLE);
// ------------------DMA Init 结构体参数 初始化--------------------------
// ADC1使用DMA2,数据流0,通道0,这个是手册固定死的
// 外设基址为:ADC 数据寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = DMA_ADC_DR_ADDR;
// 存储器地址,实际上就是一个内部SRAM的变量
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADC_ConvertedTemp;
// 数据传输方向为外设到存储器
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
// 缓冲区大小为,指一次传输的数据量
DMA_InitStructure.DMA_BufferSize = 4;
// 外设寄存器只有一个,地址不用递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 存储器地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// // 外设数据大小为半字,即两个字节
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
// 存储器数据大小也为半字,跟外设数据大小相同
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;
// 循环传输模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
// 禁止DMA FIFO ,使用直连模式
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
// FIFO 大小,FIFO模式禁止时,这个不用配置
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
// 选择 DMA 通道,通道存在于流中
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
//初始化DMA流,流相当于一个大的管道,管道里面有很多通道
DMA_Init(DMA2_Stream0, &DMA_InitStructure);
//配置DMA中断
DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
// 使能DMA流
DMA_Cmd(DMA2_Stream0, ENABLE);
// -------------------ADC Common 结构体 参数 初始化------------------------
// 仅注入同时ADC模式
ADC_CommonInitStructure.ADC_Mode = ADC_TripleMode_InjecSimult;
// 时钟为fpclk 4分频
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
// 禁止DMA直接访问模式
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
// 采样时间间隔
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
// -------------------ADC Init 结构体 参数 初始化--------------------------
// ADC 分辨率
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
// 禁止扫描模式,多通道采集才需要
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
// 连续转换
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
//禁止外部边沿触发
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//外部触发通道
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
//数据右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//转换通道 4个
ADC_InitStructure.ADC_NbrOfConversion = 4;
//---------------------------------------------------------------------------
ADC_Init(ADC1, &ADC_InitStructure);
//序列转换完成 EOS
ADC_EOCOnEachRegularChannelCmd(ADC1, ENABLE);
// 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
/*PC4-V_Temperature-CH14、PC5-V_U-CH15、PB0-V_V-CH8、PB1-V_W-CH9*/
ADC_RegularChannelConfig(ADC1, V_Temperature_Channel, 1, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC1, V_U_Channel, 2, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC1, V_V_Channel, 3, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC1, V_W_Channel, 4, ADC_SampleTime_56Cycles);
/*扫描模式是否只扫描规则通道*/
//---------------------------------------------------------------------------
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_Init(ADC2, &ADC_InitStructure);
ADC_Init(ADC3, &ADC_InitStructure);
// 使能DMA请求 after last transfer
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
// 使能ADC DMA
ADC_DMACmd(ADC1, ENABLE);
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
/*!看门狗 ADC1规则组过温 与 ADC2注入组过压!*/
/*规则组看门狗检测过温*/
// ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);//开启了DMA,DMA读取DR会清除EOC,故不使用
ADC_ITConfig(ADC1, ADC_IT_OVR, ENABLE);
// ADC_ITConfig(ADC1, ADC_IT_AWD, ENABLE);
ADC_ITConfig(ADC1, ADC_IT_JEOC, ENABLE);//三重模式中,使能一个接口的 JEOC 中断,则当三重转换全完成则产生 JEOC 中断
// /*ADC2 或 ADC3 注入组看门狗检测过压*/
// ADC_ITConfig(ADC2, ADC_IT_AWD, ENABLE);
/*3个 ADC 注入通道数量*/
ADC_InjectedSequencerLengthConfig(ADC1, 1);
ADC_InjectedSequencerLengthConfig(ADC2, 1);
ADC_InjectedSequencerLengthConfig(ADC3, 1);
/*ADC1-PA2-IA_SO1、ADC2-PA1-IB_SO2、ADC3-PA0-PVDD*/
ADC_InjectedChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC2, ADC_Channel_1, 1, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC3, ADC_Channel_0, 1, ADC_SampleTime_15Cycles);
/*TIM1_CH4 触发,仅检测上升沿*/
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_CC4);
ADC_ExternalTrigInjectedConvEdgeConfig(ADC1, ADC_ExternalTrigInjecConvEdge);
/*关闭自动注入、断续模式*/
ADC_AutoInjectedConvCmd(ADC1, DISABLE);
ADC_InjectedDiscModeCmd(ADC1, DISABLE);
ADC_AutoInjectedConvCmd(ADC2, DISABLE);
ADC_InjectedDiscModeCmd(ADC2, DISABLE);
ADC_AutoInjectedConvCmd(ADC3, DISABLE);
ADC_InjectedDiscModeCmd(ADC3, DISABLE);
NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能ADC
ADC_Cmd(ADC1, ENABLE);
ADC_Cmd(ADC2, ENABLE);
ADC_Cmd(ADC3, ENABLE);
}
FOC 算法部分就不在这里详细说明了,网上资源挺多的,想了解也可以去看看书,比如《现代永磁同步电机控制原理及MATLAB仿真模型》、查查论文网上一些硕士毕业论文,想直接跟着实操也可以去淘宝买课。
实物与波形展示:
接下来主要是看看实物长啥样,实际运行效果如何, FOC 的 SVPWM波形、电流检测波形、电流环跟踪波形、速度环跟随波形,其实这些效果都还一般,还需要进一步调参。
实物图 SVPWM波形 V、W 相检测波形 电流环跟踪图 速度环效果(下降沿为失速) 总结:
目前是已经完成了电流闭环、速度闭环,但尚达不到理想水平,只能说初步有了双闭环。后续还需要再进一步深入,提供理论或改进逐步解决目前遇到的一些情况。
|