【FOC无刷电机控制】六步换向、FOC,STM32cubemx从零开始搭建BLDC六步换相代码、FOC代码(基于霍尔传感器)

这篇博客详细记录了使用STM32通过Cubemx配置从零开始搭建无刷电机控制系统的过程,包括RCC时钟配置、串口调试、霍尔传感器读取、PWM控制以及六步换相的实现。作者强调了从基础外设逐步调试的重要性,并分享了开环和闭环控制的代码实现。最后,博主提到了未来将要探讨的FOC(磁场定向控制)技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

O、前言

  • 用作备忘录,也希望能帮助正在入门摸索的朋友少走弯路,从外设开始,到开环,到闭环。
  • 参考文章代码:正点原子、野火、硬石,三家文档几乎一样。(个人感觉原子文档写的好)

1 个人经验

  • 刚开始学无刷电机控制时是直接去看的FOC,网上理论一大堆,看了几天,理论大概明白了,想去实践编程,发现都是大多都是电机库,或者一些别人的完整代码,没有步骤教学。经过一顿摸索,我的结论是把理论化为单片机代码实际去控制电机的过程,某种程度上比学习理论更困难。
  • 我个人做一些单片机小项目的习惯是从头开始做。从一个空白工程开始,一个外设一个外设的调,调通一个测试一个,要用的所有外设调完再去加入控制代码,由开环到闭环,一步一步的来。直接用别人写好的一套代码总感觉心里没底。
  • 对于无刷电机控制,我的步骤是这样的:1调霍尔传感器,2调PWM,3调开环控制,4调闭环

2 软硬件介绍

  • 软件:STM32cubemx+keil5
  • 硬件:网上买的一块无刷电机驱动板,芯片是STM32G070。要注意的是我的电机是BLDC,2对极,间隔60度安装的霍尔传感器。所以我现在实现的都是 基于霍尔传感器的开闭环控制。暂时没整过基于编码器的、基于无感的。

一、六步换相

  • 六步换向用到的单片机外设:(根据个人板子引脚要做一些修改)
    • TIM3:选择霍尔传感器模式,用于获取3个霍尔值。
    • TIM1:通道123,普通PWM模式,用于驱动半桥电路的3个上半桥。(因为我这边用的是HPWM-LON的控制。)
    • 普通IO:3个,推挽输出,用于驱动半桥电路的3个下半桥。
    • USART2:用于调试用。
    • RTC:用于闭环控制。(这个用RTC中断可能不太合适,但是暂时这样…)
  • 代码整体的调用流程
    • 开环:电机转动换相时,触发霍尔中断,在霍尔中断回调函数里读取当前的相位值,然后根据相位值进行换相。
    • 闭环:在开环的基础上,再开一个定时器,在里边做PID运算,更改占空比设定值。
  • 六步换向-开环控制代码:https://github.com/wyfroom/BLDC_LiuBu_KaiHuan_hall
  • 六步换向-闭环控制代码

1 新建cubemx工程

在这里插入图片描述

2 工程基础配置

(1)RCC时钟配置

  • 选择时钟源,我这块板子只有外部8M晶振。
    在这里插入图片描述
  • 手动输入最大时钟频率,然后回车。我这块板子是64M。
    在这里插入图片描述

(2)SYS 调试接口

  • 我的下载器是SWD两根线的,所以我选这个。
    在这里插入图片描述

(3)工程设置,生成MDK工程

在这里插入图片描述
在这里插入图片描述

  • 点击生成代码
    在这里插入图片描述

3 串口

  • 这快板子没显示屏,调试中串口还是很有必要。
  • 我这块板子是串口2,看好引脚,串口自动配置的引脚不一定是板子上的。比如我这块板子,就不是这两引脚。

(1)cubemx配置

在这里插入图片描述

(2)printf重映射

  • 添加如下代码到工程的 usart.c 文件中的 /* USER CODE BEGIN 0 / 和 / USER CODE END 0 */之间。
#include <stdio.h>
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
    //具体哪个串口可以更改huart1为其它串口
    HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1 , 0xffff);
    return ch;
}
  • 在main.c里添加头文件 #include <stdio.h>
    在这里插入图片描述
  • 之前忘记说了,这个printf重映射要在keil里也设置一下,不然一使用printf单片机就会卡死。抱歉抱歉(2023.3.30)
    在这里插入图片描述

(3)测试

  • 在main的while里加入如下代码
printf("hello\r\n");
HAL_Delay(1000);

在这里插入图片描述
在这里插入图片描述

4 霍尔传感器

(1)Cubemx配置

  • 32定时器有一种霍尔模式,专门为无刷电机霍尔控制整的叭。
    在这里插入图片描述
  • 打开定时器中断
    在这里插入图片描述
  • 更改引脚名称(可选),为了编程方便
    在这里插入图片描述

(2)初始化启动

在main中加入下面启动代码。

__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_TRIGGER);  //触发:有某个信号触发。 
__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE);   //更新:有某个寄存器被更新。
HAL_TIMEx_HallSensor_Start_IT(&htim3);

(3)测试定时中断

  • 在工程里新建两个文件:hall.c、hall.h。在main里加头文件。
  • 加入下面中断回调函数,先测基本定时器中断,串口助手看现象。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	printf("tim\r\n");
}

在这里插入图片描述

(4)测试霍尔中断

  • hall.c 加入如下代码
uint8_t state = 0;
//换相中断
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim)
{
	state = get_hall_state();
	printf("%d\r\n",state);
}

//获取霍尔传感器值
uint8_t get_hall_state(void)
{
  uint8_t state = 0;
  
#if 1
  /* 读取霍尔传感器 U 的状态 */
  if(HAL_GPIO_ReadPin(hallu_GPIO_Port, hallu_Pin) != GPIO_PIN_RESET)
  {
    state|= 0x01U << 0;
//		printf("u1\r\n");
  }
  
  /* 读取霍尔传感器 V 的状态 */
  if(HAL_GPIO_ReadPin(hallv_GPIO_Port, hallv_Pin) != GPIO_PIN_RESET)
  {
    state |= 0x01U << 1;
//		printf("v1\r\n");
  }
  
  /* 读取霍尔传感器 W 的状态 */
  if(HAL_GPIO_ReadPin(hallw_GPIO_Port, hallw_Pin) != GPIO_PIN_RESET)
  {
    state |= 0x01U << 2;
//		printf("w1\r\n");
  }
#else
  state = (GPIOH->IDR >> 10) & 7;    // 读 3 个霍尔传感器的状态
#endif
	//printf("stateL:%d\n",state);
  return state;    // 返回传感器状态
}

在这里插入图片描述

  • 把电机霍尔接口接到板子上,用手转动电机,能看到串口打印出此时电机对应的霍尔编码值。
    在这里插入图片描述
    在这里插入图片描述
  • 这个时候霍尔的状态值读回来了,也就是什么时候换相可以知道了,下一步就是驱动全桥电路,用3个普通PWM+3个普通IO口。

4 开环控制

(1)普通PWM cubemx配置

在这里插入图片描述

  • 更改引脚名字
    在这里插入图片描述
  • 测试PWM是否正常输出,加入PWM启动代码和初始化占空比。
    用万用表电压档,去测对应引脚电压是否符合占空比值。
  • 在main里加入下面代码:
	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_SetCompare(&htim1,TIM_CHANNEL_3,800);	//U
	__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_2,0);	//V
	__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,0);	//W

在这里插入图片描述

(2)普通GPIO配置

  • cubemx
    在这里插入图片描述
  • 测试,在main中加入下代码
  • 用电压档,测对应引脚是否正常输出电压。
//普通IO初始化,驱动3个下桥臂
	HAL_GPIO_WritePin(PWMUL_GPIO_Port,PWMUL_Pin,GPIO_PIN_RESET);//UL//GPIO_PIN_SET   GPIO_PIN_RESET
	HAL_GPIO_WritePin(PWMVL_GPIO_Port,PWMVL_Pin,GPIO_PIN_RESET);//VL
	HAL_GPIO_WritePin(PWMWL_GPIO_Port,PWMWL_Pin,GPIO_PIN_RESET);//WL

在这里插入图片描述

(3)开环控制

  • 我的电机是2对极,60度霍尔,所以能用下面这个换相表。同类型电机可以用,不同的话就要网上找一下对应的换相表,然后改下对应代码。
    在这里插入图片描述
  • 在hall.c里加入换相代码
uint16_t state=0;
uint16_t pwm_pulse=0
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim)
{
	state = get_hall_state();
//	printf("%d\r\n",state);

	//513264
	switch(state)
	{
		case 1:    /* U+ W- */

			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_3,pwm_pulse);	//U+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_2,0);	//V+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,0);	//W+
			HAL_GPIO_WritePin(PWMUL_GPIO_Port,PWMUL_Pin,GPIO_PIN_RESET);//U-	//GPIO_PIN_SET   GPIO_PIN_RESET
			HAL_GPIO_WritePin(PWMVL_GPIO_Port,PWMVL_Pin,GPIO_PIN_RESET);//V-
			HAL_GPIO_WritePin(PWMWL_GPIO_Port,PWMWL_Pin,GPIO_PIN_SET);//W-
//			printf("%d\r\n",pwm_pulse);
			break;
		
		case 2:     /* V+ U- */

			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_3,0);	//U+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_2,pwm_pulse);	//V+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,0);	//W+
			HAL_GPIO_WritePin(PWMUL_GPIO_Port,PWMUL_Pin,GPIO_PIN_SET);//U-	//GPIO_PIN_SET   GPIO_PIN_RESET
			HAL_GPIO_WritePin(PWMVL_GPIO_Port,PWMVL_Pin,GPIO_PIN_RESET);//V-
			HAL_GPIO_WritePin(PWMWL_GPIO_Port,PWMWL_Pin,GPIO_PIN_RESET);//W-
			break;
		
		case 3:    /* V+ W- */

			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_3,0);	//U+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_2,pwm_pulse);	//V+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,0);	//W+
			HAL_GPIO_WritePin(PWMUL_GPIO_Port,PWMUL_Pin,GPIO_PIN_RESET);//U-	//GPIO_PIN_SET   GPIO_PIN_RESET
			HAL_GPIO_WritePin(PWMVL_GPIO_Port,PWMVL_Pin,GPIO_PIN_RESET);//V-
			HAL_GPIO_WritePin(PWMWL_GPIO_Port,PWMWL_Pin,GPIO_PIN_SET);//W-
			break;

		case 4:     /* W+ V- */

			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_3,0);	//U+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_2,0);	//V+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,pwm_pulse);	//W+
			HAL_GPIO_WritePin(PWMUL_GPIO_Port,PWMUL_Pin,GPIO_PIN_RESET);//U-	//GPIO_PIN_SET   GPIO_PIN_RESET
			HAL_GPIO_WritePin(PWMVL_GPIO_Port,PWMVL_Pin,GPIO_PIN_SET);//V-
			HAL_GPIO_WritePin(PWMWL_GPIO_Port,PWMWL_Pin,GPIO_PIN_RESET);//W-
			break;

		case 5:     /* U+  V -*/

			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_3,pwm_pulse);	//U+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_2,0);	//V+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,0);	//W+
			HAL_GPIO_WritePin(PWMUL_GPIO_Port,PWMUL_Pin,GPIO_PIN_RESET);//U-	//GPIO_PIN_SET   GPIO_PIN_RESET
			HAL_GPIO_WritePin(PWMVL_GPIO_Port,PWMVL_Pin,GPIO_PIN_SET);//V-
			HAL_GPIO_WritePin(PWMWL_GPIO_Port,PWMWL_Pin,GPIO_PIN_RESET);//W-
			break;
		
		case 6:     /* W+ U- */

			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_3,0);	//U+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_2,0);	//V+
			__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,pwm_pulse);	//W+
			HAL_GPIO_WritePin(PWMUL_GPIO_Port,PWMUL_Pin,GPIO_PIN_SET);//U-	//GPIO_PIN_SET   GPIO_PIN_RESET
			HAL_GPIO_WritePin(PWMVL_GPIO_Port,PWMVL_Pin,GPIO_PIN_RESET);//V-
			HAL_GPIO_WritePin(PWMWL_GPIO_Port,PWMWL_Pin,GPIO_PIN_RESET);//W-
			break;

	}
}
  • 更改pwm_pulse占空比值。然后就可以上电测试。!注意,上电前,一定确保你的这个换相逻辑和你的板子是对应起来的,别一上电上下桥同时导通,烧毁一切。

二、FOC

### STM32六步换相实现方法 #### 1. 基本原理 六步换相法是一种用于驱动三相无刷直流电机的方法。该方法通过六个不同的通电顺序来控制定子绕组中的电流方向,从而使得转子按照预定的方向旋转。每一步对应着两个线圈被供电而另一个不供电的情况。 对于基于STM32控制系统来说,可以通过定时器产生的PWM信号来切换功率MOSFET的状态以完成这六个阶段的操作[^1]。 #### 2. 硬件连 硬件部分主要包括MCU(如STM32)、门极驱动IC以及三个半桥结构组成的逆变器电路。其中,STM32负责产生所需的PWM波形并发送给门极驱动IC;后者则用来放大这些弱电信号以便能够有效地开启/关闭外部的大功率器件——即构成逆变器各臂上的MOSFETs。 具体而言,在设计PCB板时需注意电源滤波、去耦电容放置位置等因素,并确保良好的地布局以减少噪声干扰影响系统的稳定性。 #### 3. 软件编程要点 在编写固件之前应该先熟悉所使用的开发环境及其工具链配置过程。着便是初始化外设资源,例如设置GPIO引脚功能为推挽输出模式、使能相应TIMx通道作为互补PWM源等操作。 当一切准备就绪之后便可以着手于核心算法逻辑构建工作了: - **霍尔传感器检测**:读取来自安装于电机内部的霍尔元件状态变化信息,以此判断当前处于哪个扇区之中; - **计算目标角度**:依据期望转速设定值与实际反馈回来的位置偏差量求解出下一时刻应到达的理想方位角; - **更新占空比参数**:根据上述所得结果调整PWM周期内的高电平持续时间长短比例关系,进而改变施加到电动机端口电压幅值大小,最终达成精准调速目的。 ```c // 定义全局变量存储霍尔传感器输入状态 uint8_t hall_state; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ static uint8_t last_hall; if (htim->Instance == TIM2){ // 假设使用TIM2捕捉事件中断服务函数 /* 获取最新一次采样的霍尔编码 */ hall_state = GPIO_ReadInputPin(HALL_PORT, HALL_PIN); /* 判断是否有跳变发生 */ if(hall_state != last_hall){ switch(hall_state){ case STATE_0: // 执行特定序列下的动作... break; ... default : ; } last_hall = hall_state; } } } ```
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值