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


    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 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 在main.c里添加头文件 #include
      在这里插入图片描述
    • 之前忘记说了,这个printf重映射要在keil里也设置一下,不然一使用printf单片机就会卡死。抱歉抱歉(2023.3.30)
      在这里插入图片描述

    (3)测试

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

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

    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);
    
    • 1
    • 2
    • 3

    (3)测试定时中断

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

    在这里插入图片描述

    (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;    // 返回传感器状态
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    在这里插入图片描述

    • 把电机霍尔接口接到板子上,用手转动电机,能看到串口打印出此时电机对应的霍尔编码值。
      在这里插入图片描述
      在这里插入图片描述
    • 这个时候霍尔的状态值读回来了,也就是什么时候换相可以知道了,下一步就是驱动全桥电路,用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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    (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
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    (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;
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 更改pwm_pulse占空比值。然后就可以上电测试。!注意,上电前,一定确保你的这个换相逻辑和你的板子是对应起来的,别一上电上下桥同时导通,烧毁一切。

    二、FOC

  • 相关阅读:
    当我们的执行 java -jar xxx.jar 的时候底层到底做了什么?
    69.C++多继承与纯虚函数
    C++ std::find()实例讲解
    Kotlin基本语法
    05节-51单片机-模块化编程
    毅速丨为什么不锈钢材料在金属3D打印中应用广泛
    微信小程序精准扶贫数据收集小程序平台设计与实现
    免root修改手机imei的技术原理是什么?如何实现的?hook吗
    Mysql配置参数
    分享6款文字语音生成驱动虚拟数字人说话的开源项目
  • 原文地址:https://blog.csdn.net/weixin_44029896/article/details/128006602