旋钮键盘项目---foc讲解(开环)

这里就不过多的讲解什么原理,公式的变换了,感兴趣的可以看灯哥开源,讲解的非常好的。当然,更细致的讲解,也可以看b站其他教学。

我这里主要讲解我对于开环部分的理解,以及stm32代码的实现逻辑。可以看作是讲解灯哥的代码流程,毕竟是一致的。

本次stm32使用hal库开发。

一、为什么会有克拉克变换、帕克变换

一个电机,我们如果想要它能够在损失最小的情况下运动,那么就是我们希望力永远垂直鱼当前指向的方向。(这里,我们把电机看作一个指针,电机转动,其实就是指针转动。想要指针损失最小转动,自然力就是垂直与指针,否则就会有损耗,高中物理嘛)。那么问题来,我们知道需要力,我们该如何设定电机输入的uwv电压,来设定这个力呢?

我们可以将力分解,分解为一个固定坐标系下的两个方向的力。一般是x,y轴的方向。为什么呢?当然是好算,很多的表达式都可以不要再去计算偏移角度了。当然,这里就出现了一个问题,这个力是不断变化的,我们怎么分解?你这个力现在指向左侧,一会又指向左下角了,怎么做分解呢?所有这个当前的角度,我们是必须知道的。有了角度才好分解。ok,这个角度,我们可以使用传感器来获取。这个变换也就是帕克逆变换!

好了,我们现在就当我们得到水平固定坐标系下的两个分解,那么如何得到uwv呢?这个很简单了,我们直接将u或者其他,无所谓哪一个当作x轴,这样就不需要算它了。x轴分解得到多大,就是它的值。借用灯哥的图,这里的la=lα,非常简便,减少了计算。(所以其实不是一定要这样的,你可以根据自己喜爱去偏转,只是麻烦而已,而且计算会多一个正余弦计算)。好了,我们再将lβ分解。角度都知道,自然也就得到了lb,lc。这就是克拉克逆变换。

虽然说是逆变换,实际上就是输入输出地位的变换而已。自然的,我们反过来再顺一遍,就知道我们输入uwv,这个力是哪个方向的力了。

二、代码

我的stm32是当时不知道什么时候收藏的一个stm32f407zgt6板子。

主要也是灯哥的代码思路。

首先,我们转动起来,需要去配置一个定时器来输出三路pwm。

cpp 复制代码
void PWM_gpio_init()
{
		__HAL_RCC_TIM1_CLK_ENABLE();
		__HAL_RCC_GPIOA_CLK_ENABLE();
		GPIO_InitTypeDef GPIO_InitStruct = {0};
		GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; // TIM1_CH1~CH3
		GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;        // 复用推挽
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
		GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;    // TIM1 对应的 AF
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void PWM_init(uint16_t arr, uint16_t psc)
{
		PWM_gpio_init();
		TIM_OC_InitTypeDef sConfigOC = {0};
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
	
	 /* 基本定时器配置 */
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 0;                   // 不分频
    htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;
    htim1.Init.Period = MAX_PWM-1;                   // PWM频率 = 168MHz / (8399+1) = 20kHz
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim1.Init.RepetitionCounter = 0;
    htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_PWM_Init(&htim1);
		
		sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 1400;                        // 初始占空比0
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
		
		HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3);
		
		HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
}

这样我们就得到了三个pwm输出。

剩下的foc开环部分用到的代码,讲解一下这个流程。其实就是void velocityOpenloop(float target_velocity)这个函数。核心作用是在开环条件下,根据目标转速更新电机相角并输出对应的 PWM 电压。开环的情况,一般就是我们不算角度,那么角度怎么来呢?

1.我们可以通过定时器来,比如定时1ms,加上我们需要的速度,那么时间乘以角速度,就是角度了。当然了,这种是不准确的。

2.我们可以通过读取当前获取的时间,也就是记录上一次进入函数的时间,和本次进入的时间,这两者之差就是运行时间。当然,首先你要有这一个时间获取的方法。我这里是hal库,如果是标准库,或者寄存器,或者其他开发板,就得自己想办法了。

得到角度之后,我们就知道我们当前的所在的角度。然后输入电压值,电压的大小代表着力矩的大小,同样的,这个电压不能太大。如果太大,可能会烧电机,或者其他问题。根据当前的所在角度,所给力,我们就可以由上边的克拉克,帕克等变换,得到uwv,这时候在给pwm配置,就完成了一次运动。我们将**velocityOpenloop放到main函数的while里就可以了。**当然,也可以再开一个定时器,把这个放到定时器的回调函数里,这里的时间就是固定的了。可以参考定时器调用开环函数

cpp 复制代码
float _normalizeAngle(float angle){
  float a = fmod(angle, 2*PI);   //取余运算可以用于归一化,列出特殊值例子算便知
  return a >= 0 ? a : (a + 2*PI);  
}

void setPhaseVoltage(float Uq,float Ud, float angle_el) {
  angle_el = _normalizeAngle(angle_el + zero_electric_angle);
  // 帕克逆变换
  Ualpha =  -Uq*sin(angle_el); 
  Ubeta =   Uq*cos(angle_el); 

  // 克拉克逆变换
  Ua = Ualpha + voltage_power_supply/2;
  Ub = (sqrt(3)*Ubeta-Ualpha)/2 + voltage_power_supply/2;
  Uc = (-Ualpha-sqrt(3)*Ubeta)/2 + voltage_power_supply/2;
  setPwm(Ua,Ub,Uc);
}

void setPwm(float Ua, float Ub, float Uc) {

  // 计算占空比
  // 限制占空比从0到1
  dc_a = _constrain(Ua / voltage_power_supply, 0.0f , 1.0f );
  dc_b = _constrain(Ub / voltage_power_supply, 0.0f , 1.0f );
  dc_c = _constrain(Uc / voltage_power_supply, 0.0f , 1.0f );

  //写入PWM到PWM 0 1 2 通道

	__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,dc_a*MAX_PWM);
	__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_2,dc_b*MAX_PWM);
	__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_3,dc_c*MAX_PWM);
}
static inline uint32_t micros(void) {
    return DWT->CYCCNT / (SystemCoreClock / 1000000);
}
void velocityOpenloop(float target_velocity){
	unsigned long now_us = micros();
	 float Ts = (now_us - open_loop_timestamp) * 1e-6f;
	if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
 shaft_angle = _normalizeAngle(shaft_angle + target_velocity*Ts);

  // 最大只能设置为Uq = voltage_power_supply/2,否则ua,ub,uc会超出供电电压限幅
  float Uq = voltage_power_supply/8;
  setPhaseVoltage(Uq,  0, _electricalAngle(shaft_angle, 7));
	open_loop_timestamp = now_us;  //用于计算下一个时间间隔
}
float _electricalAngle(float shaft_angle, int pole_pairs) {
  return (shaft_angle * pole_pairs);
}
void DWT_Init(void) {
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 使能 DWT
    DWT->CYCCNT = 0;                                // 清零计数
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;            // 启动
}

讲解的还是过于粗糙了,如果错误,欢迎指正。

智能旋钮(一)---foc开环控制

相关推荐
qq_526099132 小时前
工控机的用途与介绍:工业自动化的重要引擎
嵌入式硬件·自动化·电脑
意法半导体STM324 小时前
STM32N6引入NPU,为边缘AI插上“隐形的翅膀”
单片机·ai·npu·st·stm32n6·边缘人工智能
范纹杉想快点毕业10 小时前
嵌入式 C 语言编程规范个人学习笔记,参考华为《C 语言编程规范》
linux·服务器·数据库·笔记·单片机·嵌入式硬件·fpga开发
Wallace Zhang11 小时前
STM32 - Embedded IDE - GCC - 解决LWRB库在GCC编译器会编译失败,在ARMCC编译器时却正常编译
ide·stm32·嵌入式硬件
3D打印-HUSTAIBO19 小时前
【电气】NPN与PNP
单片机·嵌入式硬件
ksk自在无敌1 天前
ESP8266的AP模式与STA编写,
stm32·单片机·嵌入式硬件
晶振厂家-晶发电子1 天前
怎么判断晶振的好坏,有什么简单的办法
单片机·嵌入式硬件
jllllyuz1 天前
SysTick定时器的工作原理是什么
stm32·单片机·嵌入式硬件
RIKI_11 天前
【浅学】tflite-micro + ESP32S3 + VScode + ESP-IDF 基于例程快速实现自己的图像分类模型训练部署全流程
单片机·分类