代码拉取完成,页面将自动刷新
同步操作将从 HumphreyLin/FOC教程 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
环境: 使用HAL库编程, Documents:文档 FOC-407-Demo:自制电路板V1.0.0 双路无刷驱动代码 ODrive:ODrive硬件平台,单路驱动代码 芯片资源使用: PWM: 使用中央对其模式2,先向上计数再向下计数,只在向上计数时产生溢出中断。 使用PWM模式1,向上计数 CNT<CCR为有效电平,有效电平为高。 ODrive:使能通道4的比较中断,通道4的CCR为通道1 2 3中CCR最大值+1us。1us是为了让AD采样更稳定 FOC-407-Demo:使用溢出中断,再中断执行函数里选择上溢中断 其中FOC-407-Demo代码中使用主从定时器来将两个电机的电流环计算时间分开,互不干扰。 ABZ编码器: 采用ABZ正交增量编码器 每次上电需要做电气角度校准 利用Z轴每圈做一次清零 绝对式编码器: 只在第一次上电和拆卸电机的时候做校准 芯片选择TLE5012B1000 电流采样: 电流采样使用单次扫描采样,DMA+中断 在定时器1通道4中断中触发ADC采样 控制流程: 定时器中断,进行一次ADC采样. ADC采样完成触发DMA中断,在DMA中完成FOC电流环。 FOC控制流程:(FOC.c) 1.获取BC两相电流,求出A相电流 2.获取当前电气角度。 3.通过Clarke变换和Park变换求出实际电流 IQ和ID 4.通过与目标IQ ID对比和PID计算,得出要输出的UQ UD 5.将UQ UD做Park反变化得出Uα和Uβ 6.将U阿尔法和Uβ送入SVPWM生成模块。 SVPWM控制流程:(Svpwm.c.c) 1.获取Uα和Uβ 2.通过U阿尔法和Uβ计算当前所在扇区 4.使用7段式PWM计算每个矢量的作用时常 5.通过矢量作用时长计算出定时器的高电平时间 6.通过定时器每个高电平时间计算出每个通道的CCR值 7.挑选出CCR最大值送给通道4,准备下一次定时器中断 定时器比较中断: 打开ADC进行一次AD采样 DMA中断: 读取AD数据进行FOC控制 编码器Z轴中断: 校准角度值 文件介绍: 所有文件均放置在User目录下 APP:应用程序总入口,实现初始化流程管理,循环执行管理。 Function:放置功能程序,目前没有实现,如放置T型加减速。 MCUDriver :放置芯片外设代码,如SPI,GPIO,TIM,ADC Framework:放置代码库,将驱动代码和硬件平台剥离出来。 PeripheralsDriver:放置驱动程序,将硬件平台代码和代码库结合起来,实现具体功能,即(MCUDriver + Framework) RTT :使用Segger RTT打印调试,也可以稍作修改改为串口打印。 已知BUG 1.双路FOC代码中(FOC-407-Demo)CUBE生成的代码初始化顺序会导致ADC2无法进入DMA中断,因此外设初始化顺序要修改成我代码中的那样。 2.无法通过SPI与DRV8301通讯,看了数据手册也没有调试通过,哪位老哥有经验望分享。 3.没有做刹车处理 声明: 1.受硬件平台影响,代码可能不能直接运行,但可以参考。 2.先调试SVPWM再调试电流采样,再闭环,SVPWM即可实现电机旋转。 3.闭环先调电流环再调速度环 4.电流环先调试ID再调试IQ 6.有疑问的地方欢迎骚扰,有错误的地方欢迎批评。 联系: QQ:965552797@qq.com 代码编写风格: 我们实现一个功能其实是分为三个步骤 1.配置MCU引脚 2.配置传感器逻辑功能,即让传感器运行起来,或是通讯协议,或是电机控制,或是时间控制。 3.根据传感器的功能做一些小的逻辑应用,或是LED闪烁,或是电机转速控制。 其实我们发现1和3是受硬件平台和我们要实现的功能影响,需要不断修改,但步骤2是不变的,针对一个传感器来说无论你使用什么硬件平台步骤2是不需要变化的,举个例子来说TLE5012B编码器的SPi通讯逻辑是不变的。 因此我们将步骤2抽象出来,针对TLE5012B我们用结构体的方式表示这类传感器,我们假设其有SPI传输函数,SPI读取函数,SPI_CS引脚控制函数,微秒延时函数,有了这些函数之后我们就能使用这个传感器了。 但是现在这些函数都是虚拟的,我们使用这些假函数先把传感器的逻辑写出来,然后我们在去MCU那里把真正的SPI通讯实现了,然后把这些真正的函数地址传给TLE5012B结构体即可。 在单路FOC代码中会给人一种感觉,这样编写比较麻烦,累赘。但是在双路FOC中这样编写的优点就体现出来了。 LEDControl举例说明 我们使用板子习惯会先点亮一个LED,但我们会发现每次开发板点亮一个LED都要重新编写函数,如果在加一些闪烁效果就更恶心,为了不影响主循环的实时性我们甚至要开一个定时器中断, 真是苦不堪言,在本次代码中我们声明一个LED结构体: struct SLEDControl_Struct { uint8_t state;//LED运行状态 0:LED常灭 1:LED常亮 2:闪烁 uint8_t onoff;//当前LED状态 float cycle;//闪烁周期(单位ms) uint8_t onLeave;//点亮电平 uint32_t startTime; void(*SetLEDLeave)(uint8_t leave);//设置LED引脚电平函数 }; 针对LED,我们要知道点亮电平,引脚电平控制,因此我们便在结构体中声明这两个函数,利用虚拟函数完成逻辑功能如: void SetLEDON(PLEDControl_Struct gLED) { gLED->state = LEDState_ON; gLED->SetLEDLeave(gLED->onLeave); } 然后我们再去实现这个引脚控制函数 void SetLedLeave(uint8_t leave) { HAL_GPIO_WritePin(SYS_LED_GPIO_Port, SYS_LED_Pin, leave); } 最后通过接口把这个SetLedLeave函数地址传给结构体 LED_EXPORT(gSysLed,1,SetLedLeave); #define LED_EXPORT(x,xOnLeave,xSetLEDLeave) \ LEDControl_Struct x = { \ .state = LEDState_OFF, \ .onoff = 0, \ .cycle = 0.0, \ .onLeave = xOnLeave, \ .startTime = 0, \ .SetLEDLeave = xSetLEDLeave, \ };
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。