无刷直流电机
直流无刷电机简介
直流无刷电机(BLDC)是指无电刷和换向器的电机,又称无换向器电机。
有刷直流电机与无刷电机的最大结构区别:无刷没有电刷以及换向器;转子与定子反过来了!
电机类型 特点 直流有刷电机 使用寿命较短、噪音大、转速不高、但是价格便宜、控制、启动简单 直流无刷电机 使用寿命长、噪音低、转速快、但是价格贵、控制较复杂
无刷电机的分类
不同点 BLDC PMSM 反电动势 具有梯形反电动势 具有正弦波反电动势 运行电流 梯形波电流 正弦波电流 绕组方式 定子绕组为集中绕组,永磁转子形成方波磁场 定子绕组为短距分布绕组,永磁转子形成正弦磁场 控制方式 控制方法相对简单(六步) 控制算法复杂(FOC) 运行方式 绕组两两导通 绕组三相全部导通
无刷电机的主要参数
极对数:转子磁铁NS级的对数,此参数和电机旋转速度有关。电子速度 = 电机实际速度 * 极对数。
KV值:值越大电机转速越大。电机转速 = KV值 * 工作电压。
额定转速:额定的电流下的空载转速,通常单位用RPM表示。例如正点原子的就是3000RPM。
转矩:电机中转子产生的可以带动机械负载的驱动力矩。通常单位为:N-M。
应用场景
无刷电机的应用场景很广泛,如电动车、无人机、风扇、鼓风机、抽油烟机等。
无刷电机驱动原理
一般无刷电机使用三相,UVW相线。
在正点原子中,无刷电机有八根线(3+5)。
3根电机线分别是U相线-黄色、V相线-绿色、W相线-蓝色。
5根霍尔传感器线分别是霍尔电源线正极-红色、霍尔电源线负极-黑色、霍尔U相输出-黄色、霍尔V相输出-绿色、霍尔U相输出-蓝色。
霍尔传感器的三相电平输出主要是用于指示无刷电机转子的位置。
三组漆包线绕组的一端连接在一起,另外一端引出即为UVW相线,所以任意两根相线通电都可以导通这两个线圈。
由于BLDC的运行方式为绕组两两导通,所以三相线圈的额导通组合只有6种通电情况,根据合理的顺序依次切换通电顺序即可让转子跟着磁场转起来。
因此想要控制BLDC旋转,根本的问题就是产生这6拍的工作方式的电压信号(BLDC的六步控制),假设BLDC的额定电压为24V。
拿第一步举例,给U接24V,V悬空,W接GND,此时电机的转轴对应第一步的转子位置。
1→6:逆时针旋转。
6→1:顺时针旋转。
如何实现简便的控制U、V、W三相极性的切换?使用三相逆变电路来实现(3个半桥)。
同步进电机类似,实际上步进电机也算是无刷电机,所以无刷电机也会存在步进电机存在的问题(丢步),原因也是定子产生的磁场没有来得及改变定子的位置,一般也可以增大驱动电流来缓解。
三相逆变电路
三相逆变电路是由三个半桥构成的电路(A+和A-为一个半桥、B+和B-为一个半桥、C+和C-为一个半桥)。
这三个半桥各自控制对应的A、B、C三相绕组。
当控制A的上桥臂A+导通时,此时A相绕组接到电源正,当控制B的下桥臂B-导通时,此时B相绕组接到电源负,所以此时电流由A流向B。
所以想要控制绕组的极性,只需要控制绕组对应半桥的“上桥臂导通”或“下桥臂导通”就可以实现控制该相连接至“正极”或“负极”了。
Tips:不可以同侧半桥上下桥臂同时导通,否则会短路,烧毁电机。
在三相逆变电路直接把电源加载到线圈上,会直接使电机很快飙到很高的速度,不利于控制,所以一般工作时都是将高低电平用PWM来代替,这样可以方便的控制线圈电流,从而控制转子扭矩及转速。
PWM控制直流无刷电机的常用方法(五种):
PWM-ON
ON-PWM
H_ON-L_PWM
H_PWM-L_ON
H_PWM-L_PWM
不同的控制方式在性能上有不同的效果,针对实际的应用场合可以多尝试多种调制方式,然后选择最优调制方式。
正点原子例程种,驱动硬件方式为H_PWM-L_ON。
H_PWM-L_ON:
上桥臂使用PWM调制,下桥臂使用高低电平直接控制。
所以电机转速,取决于上桥臂PWM的占空比。
如何确定当前转子的位置在哪?有感驱动(通过霍尔传感器读取)和无感驱动(通过反电动势读取)。
根据霍尔器件可检测磁场的变化的特性再搭配一定的电路将磁场方向变化信号转化成不同的高低电平信号输出。
需要三个霍尔传感器,每相邻两个传感器电角度相差120°。
电度角 = 机械角度 * 极对数。(机械角度指的是电机转子的旋转角度,电度角指的是磁场的旋转角度)
Tips:一般厂家都会给出一个霍尔传感器和绕组得电情况的对应关系表,不一定和上表完全对应一致,但是原理分析都是一致的。
有感驱动整体框图:
所以,
通过三相逆变电路切换极性,通过控制上桥臂PWM占空比来进行调速。
通过位置传感器检测当前转子位置,控制线圈按照真值表顺序通电。
正点原子无刷电机驱动板
正点原子无刷电机参数:
正点原子无刷电机驱动板介绍:
无刷电机驱动板同直流驱动板类似,相对多了一组驱动桥用于控制无刷电机UVW三相的通电,无刷电机驱动板有3路的电流及反电动势采集电路、还有驱动板电压及温度采集电路。
1、控制信号和采集信号接口CN3。直连正点开发板的直流无刷驱动器接口,实现开发板对驱动板的控制以及信号的采集。
2、编码器和霍尔传感器接口CN2。包括3相编码器接口、3相霍尔传感器接口以及编码器供电接口。用于连接电机的编码器和霍尔传感器,从而实现电机转速的测量以及电机转子位置的检测等。
3、编码器供电选择接口。驱动板板载5V和11V电源,用户可根据电机编码器供电电压选择合适的电源,默认5V。
4、制动电阻接口。用于外界制动电阻,结合制动电路实现制动控制。
5、Z相编码器和制动控制信号选择接口。有时可能只用到编码器的AB相,那Z相就可以用作制动控制,通过制动控制电路实现电机制动。
6、霍尔检测和过零检测选择接口。为了提供驱动板对更多无刷电机的适配性,正点无刷驱动板不仅板载了编码器接口和霍尔传感器接口,还板载了过零检测电路。当我们要驱动一个即没有编码器又没有霍尔传感器的直流无刷电机时,这个过零检测电路就起作用了。默认使用霍尔检测的方式。
7、电机和电源接口。电机接口用来连接直流无刷电机,包括 UVW 3相线。电源接口则是用来连接外部电源,范围DC12~60V。
三相电流采集:
1、获得采样电压I_V = 0.02 * 实际电流I。
2、采样电压放大,放大倍数 = 12 /(1+1)= 6倍。
3、加上参考电压1.25V抬升电压。
4、得到输出电压:AMP_IU =(6 * 0.02欧 * I)+ 1.25V。
电源电压采集:
1、分压,采集点电压 = POWER * 1 /(12 + 12 +1)
2、电压跟随电流VBUS = POWER * 1 /(12 + 12 + 1)
3、采集输出电压计算实际电压POWER。POWER = VBUS * 25。
温度采集:
1、分压,分压后的电压 = 3.3V * 4700 /(Rt + 4700)。
2、利用电压跟随电路,让分压后的电压 = VTEMP,即VTEMP = 3.3 * 4700 /(Rt + 4700)。
3、采集VTEMP计算此时热敏电阻的阻值Rt。
4、根据Rt算出实际温度值。实际温度T1 = 1 /(In(Rt / R0)/ B + 1 / T2)。
无刷基础驱动实验
硬件连接:
TIM1_CH1:PA8 对应无刷电机UH相 TIM1_CHN1:PB13 对应无刷电机UL相 TIM1_CH2:PA9 对应无刷电机VH相 TIM1_CHN2:PB14 对应无刷电机VL相 TIM1_CH3:PA10 对应无刷电机WH相 TIM1_CHN3:PB15 对应无刷电机WL相
霍尔U相 PH10 霍尔V相 PH11 霍尔W相 PH12
输出使能SHDN PF10
无刷电机驱动配置流程:
初始化TIM及IO 初始化相关IO,以及初始化定时器,6路通道(3路上桥臂以及3路下桥臂),开启中断 霍尔状态读取 编写霍尔传感器状态读取函数 6步组合 上下桥臂的导通情况,共6种,也称为6步换向 设置无刷参数 设置旋转方向以及脉冲占空比,并编写无刷启停函数 中断服务函数 在中断里读取霍尔状态,根据方向以及霍尔状态依次导通上下桥臂
定时器配置:
/* 高级定时器PWM */ TIM_HandleTypeDef g_atimx_handle; /* 定时器x句柄 */ TIM_OC_InitTypeDef g_atimx_oc_chy_handle; /* 定时器输出句柄 */ extern _bldc_obj g_bldc_motor1; /******************************************************************************************/ /** * @brief 高级定时器TIMX PWM输出初始化函数 * @note * 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此 * 高级定时器时钟 = 168Mhz * 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us. * Ft=定时器工作频率,单位:Mhz * * @param arr: 自动重装值 * @param psc: 时钟预分频数 * @retval 无 */ void atim_timx_oc_chy_init(uint16_t arr, uint16_t psc) { ATIM_TIMX_PWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */ g_atimx_handle.Instance = ATIM_TIMX_PWM; /* 定时器x */ g_atimx_handle.Init.Prescaler = psc; /* 定时器分频 */ g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */ g_atimx_handle.Init.Period = arr; /* 自动重装载值 */ g_atimx_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 分频因子 */ g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; /*使能TIMx_ARR进行缓冲*/ g_atimx_handle.Init.RepetitionCounter = 0; /* 开始时不计数*/ HAL_TIM_PWM_Init(&g_atimx_handle); /* 初始化PWM */ g_atimx_oc_chy_handle.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1 */ g_atimx_oc_chy_handle.Pulse = 0; g_atimx_oc_chy_handle.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */ g_atimx_oc_chy_handle.OCNPolarity = TIM_OCNPOLARITY_HIGH; g_atimx_oc_chy_handle.OCFastMode = TIM_OCFAST_DISABLE; g_atimx_oc_chy_handle.OCIdleState = TIM_OCIDLESTATE_RESET; g_atimx_oc_chy_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET; HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH1); /* 配置TIMx通道y */ HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH2); /* 配置TIMx通道y */ HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &g_atimx_oc_chy_handle, ATIM_TIMX_PWM_CH3); /* 配置TIMx通道y */ /* 开启定时器通道输出PWM */ HAL_TIM_PWM_Start(&g_atimx_handle, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&g_atimx_handle, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&g_atimx_handle, TIM_CHANNEL_3); } /** * @brief 定时器底层驱动,时钟使能,引脚配置 此函数会被HAL_TIM_PWM_Init()调用 * @param htim:定时器句柄 * @retval 无 */ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) { if (htim->Instance == ATIM_TIMX_PWM) { GPIO_InitTypeDef gpio_init_struct; ATIM_TIMX_PWM_CHY_CLK_ENABLE(); /* 定时器时钟使能 */ /* 上桥臂的IO时钟使能 */ ATIM_TIMX_PWM_CH1_GPIO_CLK_ENABLE(); ATIM_TIMX_PWM_CH2_GPIO_CLK_ENABLE(); ATIM_TIMX_PWM_CH3_GPIO_CLK_ENABLE(); /* 下桥臂的IO时钟使能 */ M1_LOW_SIDE_U_GPIO_CLK_ENABLE(); M1_LOW_SIDE_V_GPIO_CLK_ENABLE(); M1_LOW_SIDE_W_GPIO_CLK_ENABLE(); /* 下桥臂的IO初始化 */ gpio_init_struct.Pin = M1_LOW_SIDE_U_PIN; gpio_init_struct.Pull = GPIO_NOPULL; gpio_init_struct.Speed = GPIO_SPEED_HIGH; gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出模式 */ HAL_GPIO_Init(M1_LOW_SIDE_U_PORT, &gpio_init_struct); gpio_init_struct.Pin = M1_LOW_SIDE_V_PIN; HAL_GPIO_Init(M1_LOW_SIDE_V_PORT, &gpio_init_struct); gpio_init_struct.Pin = M1_LOW_SIDE_W_PIN; HAL_GPIO_Init(M1_LOW_SIDE_W_PORT, &gpio_init_struct); /* 上桥臂即定时器IO初始化 */ gpio_init_struct.Pin = ATIM_TIMX_PWM_CH1_GPIO_PIN; /* 通道y的GPIO口 */ gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */ gpio_init_struct.Pull = GPIO_NOPULL; /* 上拉 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */ gpio_init_struct.Alternate = ATIM_TIMX_PWM_CHY_GPIO_AF; /* 端口复用 */ HAL_GPIO_Init(ATIM_TIMX_PWM_CH1_GPIO_PORT, &gpio_init_struct); gpio_init_struct.Pin = ATIM_TIMX_PWM_CH2_GPIO_PIN; /* 通道y的CPIO口 */ HAL_GPIO_Init(ATIM_TIMX_PWM_CH2_GPIO_PORT, &gpio_init_struct); gpio_init_struct.Pin = ATIM_TIMX_PWM_CH3_GPIO_PIN; /* 通道y的CPIO口 */ HAL_GPIO_Init(ATIM_TIMX_PWM_CH3_GPIO_PORT, &gpio_init_struct); HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 2, 2); HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn); } } /** * @brief 定时器中断服务函数 * @param 无 * @retval 无 */ void ATIM_TIMX_PWM_IRQHandler(void) { HAL_TIM_IRQHandler(&g_atimx_handle); }
霍尔接口初始化以及获取状态:
/** * @brief 霍尔传感器接口初始化 * @param 无 * @retval 无 */ void hall_gpio_init(void) { GPIO_InitTypeDef gpio_init_struct; HALL1_U_GPIO_CLK_ENABLE(); HALL1_V_GPIO_CLK_ENABLE(); HALL1_W_GPIO_CLK_ENABLE(); /* 霍尔通道 1 引脚初始化 */ gpio_init_struct.Pin = HALL1_TIM_CH1_PIN; gpio_init_struct.Mode = GPIO_MODE_INPUT; gpio_init_struct.Pull = GPIO_PULLUP; HAL_GPIO_Init(HALL1_TIM_CH1_GPIO, &gpio_init_struct); /* 霍尔通道 2 引脚初始化 */ gpio_init_struct.Pin = HALL1_TIM_CH2_PIN; HAL_GPIO_Init(HALL1_TIM_CH2_GPIO, &gpio_init_struct); /* 霍尔通道 3 引脚初始化 */ gpio_init_struct.Pin = HALL1_TIM_CH3_PIN; HAL_GPIO_Init(HALL1_TIM_CH3_GPIO, &gpio_init_struct); } /** * @brief 获取霍尔传感器引脚状态 * @param motor_id : 电机接口号 * @retval 霍尔传感器引脚状态 */ uint32_t hallsensor_get_state(uint8_t motor_id) { __IO static uint32_t state ; state = 0; if(motor_id == MOTOR_1) { if(HAL_GPIO_ReadPin(HALL1_TIM_CH1_GPIO,HALL1_TIM_CH1_PIN) != GPIO_PIN_RESET) /* 霍尔传感器状态获取 */ { state |= 0x01U; } if(HAL_GPIO_ReadPin(HALL1_TIM_CH2_GPIO,HALL1_TIM_CH2_PIN) != GPIO_PIN_RESET) /* 霍尔传感器状态获取 */ { state |= 0x02U; } if(HAL_GPIO_ReadPin(HALL1_TIM_CH3_GPIO,HALL1_TIM_CH3_PIN) != GPIO_PIN_RESET) /* 霍尔传感器状态获取 */ { state |= 0x04U; } } return state; }
开启关闭电机运转:
/** * @brief 关闭电机运转 * @param 无 * @retval 无 */ void stop_motor1(void) { /* 关闭半桥芯片输出 */ SHUTDOWN_OFF; /* 关闭PWM输出 */ HAL_TIM_PWM_Stop(&g_atimx_handle,TIM_CHANNEL_1); HAL_TIM_PWM_Stop(&g_atimx_handle,TIM_CHANNEL_2); HAL_TIM_PWM_Stop(&g_atimx_handle,TIM_CHANNEL_3); /* 上下桥臂全部关断 */ g_atimx_handle.Instance->CCR2 = 0; g_atimx_handle.Instance->CCR1 = 0; g_atimx_handle.Instance->CCR3 = 0; HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET); HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET); HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET); } /** * @brief 开启电机运转 * @param 无 * @retval 无 */ void start_motor1(void) { SHUTDOWN_EN; /* 使能PWM输出 */ HAL_TIM_PWM_Start(&g_atimx_handle,TIM_CHANNEL_1); HAL_TIM_PWM_Start(&g_atimx_handle,TIM_CHANNEL_2); HAL_TIM_PWM_Start(&g_atimx_handle,TIM_CHANNEL_3); }
6步换向:
/* 六步换向函数指针数组 */ pctr pfunclist_m1[6] = { &m1_uhwl, &m1_vhul, &m1_vhwl, &m1_whvl, &m1_uhvl, &m1_whul }; /** * @brief U相上桥臂导通,V相下桥臂导通 * @param 无 * @retval 无 */ void m1_uhvl(void) { g_atimx_handle.Instance->CCR1 = g_bldc_motor1.pwm_duty; /* U相上桥臂PWM */ g_atimx_handle.Instance->CCR2 = 0; g_atimx_handle.Instance->CCR3 = 0; HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_SET); /* V相下桥臂导通 */ HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET); /* U相下桥臂关闭 */ HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET); /* W相下桥臂关闭 */ } /** * @brief U相上桥臂导通,W相下桥臂导通 * @param 无 * @retval 无 */ void m1_uhwl(void) { g_atimx_handle.Instance->CCR1 = g_bldc_motor1.pwm_duty; g_atimx_handle.Instance->CCR2 = 0; g_atimx_handle.Instance->CCR3 = 0; HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_SET); HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET); HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET); } /** * @brief V相上桥臂导通,W相下桥臂导通 * @param 无 * @retval 无 */ void m1_vhwl(void) { g_atimx_handle.Instance->CCR1=0; g_atimx_handle.Instance->CCR2 = g_bldc_motor1.pwm_duty; g_atimx_handle.Instance->CCR3=0; HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_SET); HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET); HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET); } /** * @brief V相上桥臂导通,U相下桥臂导通 * @param 无 * @retval 无 */ void m1_vhul(void) { g_atimx_handle.Instance->CCR1 = 0; g_atimx_handle.Instance->CCR2 = g_bldc_motor1.pwm_duty; g_atimx_handle.Instance->CCR3 = 0; HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_SET); HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET); HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET); } /** * @brief W相上桥臂导通,U相下桥臂导通 * @param 无 * @retval 无 */ void m1_whul(void) { g_atimx_handle.Instance->CCR1 = 0; g_atimx_handle.Instance->CCR2 = 0; g_atimx_handle.Instance->CCR3 = g_bldc_motor1.pwm_duty; HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_SET); HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_RESET); HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET); } /** * @brief W相上桥臂导通,V相下桥臂导通 * @param 无 * @retval 无 */ void m1_whvl(void) { g_atimx_handle.Instance->CCR1 = 0; g_atimx_handle.Instance->CCR2 = 0; g_atimx_handle.Instance->CCR3 = g_bldc_motor1.pwm_duty; HAL_GPIO_WritePin(M1_LOW_SIDE_V_PORT,M1_LOW_SIDE_V_PIN,GPIO_PIN_SET); HAL_GPIO_WritePin(M1_LOW_SIDE_U_PORT,M1_LOW_SIDE_U_PIN,GPIO_PIN_RESET); HAL_GPIO_WritePin(M1_LOW_SIDE_W_PORT,M1_LOW_SIDE_W_PIN,GPIO_PIN_RESET); }
总体初始化:
/** * @brief 无刷电机初始化,包括定时器,霍尔接口以及SD引脚初始化 * @param arr: 自动重装值 * @param psc: 时钟预分频数 * @retval 无 */ void bldc_init(uint16_t arr, uint16_t psc) { GPIO_InitTypeDef gpio_init_struct; SHUTDOWN_PIN_GPIO_CLK_ENABLE(); gpio_init_struct.Pin = SHUTDOWN_PIN; gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; gpio_init_struct.Pull = GPIO_NOPULL; gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(SHUTDOWN_PIN_GPIO, &gpio_init_struct); hall_gpio_init(); /* 霍尔接口初始化 */ atim_timx_oc_chy_init(arr, psc); /* 定时器初始化 */ } /** * @brief BLDC控制函数 * @param dir :电机方向, Duty:PWM占空比 * @retval 无 */ void bldc_ctrl(uint8_t motor_id,int32_t dir,float duty) { if(motor_id == MOTOR_1) { g_bldc_motor1.dir = dir; /* 方向 */ g_bldc_motor1.pwm_duty = duty; /* 占空比 */ } }
定时器中断回调(和真值表挂钩):
/** * @brief 定时器中断回调 * @param htim:定时器句柄 * @retval 无 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == ATIM_TIMX_PWM) /* 55us */ { #ifdef H_PWM_L_ON if (g_bldc_motor1.run_flag == RUN) { if (g_bldc_motor1.dir == CW) /* 正转 */ { /* 顺序6,2,3,1,5,4 */ g_bldc_motor1.step_sta = hallsensor_get_state(MOTOR_1); } else /* 反转 */ { /* 顺序5,1,3,2,6,4 。使用7减完后可与数组pfunclist_m1对应上顺序 实际霍尔值为:2,6,4,5,1,3*/ g_bldc_motor1.step_sta = 7 - hallsensor_get_state(MOTOR_1); } if ((g_bldc_motor1.step_sta <= 6) && (g_bldc_motor1.step_sta >= 1)) /* 判断霍尔组合值是否正常 */ { pfunclist_m1[g_bldc_motor1.step_sta - 1](); /* 通过数组成员查找对应的函数指针 */ } else /* 霍尔传感器错误、接触不良、断开等情况 */ { stop_motor1(); g_bldc_motor1.run_flag = STOP; } } #endif } }
操作:
key = key_scan(0); if(key == KEY0_PRES) /* 按下KEY0设置比较值+500 */ { pwm_duty_temp += 500; if(pwm_duty_temp >= MAX_PWM_DUTY/2) /* 限速 */ pwm_duty_temp = MAX_PWM_DUTY/2; if(pwm_duty_temp > 0) /* 通过判断正负号设置旋转方向 */ { g_bldc_motor1.pwm_duty = pwm_duty_temp; g_bldc_motor1.dir = CW; } else { g_bldc_motor1.pwm_duty = -pwm_duty_temp; g_bldc_motor1.dir = CCW; } g_bldc_motor1.run_flag = RUN; /* 开启运行 */ start_motor1(); /* 开启运行 */ } else if(key == KEY1_PRES) /* 按下KEY1设置比较值-500 */ { pwm_duty_temp -= 500; if(pwm_duty_temp <= -MAX_PWM_DUTY/2) pwm_duty_temp = -MAX_PWM_DUTY/2; if(pwm_duty_temp < 0) /* 通过判断正负号设置旋转方向 */ { g_bldc_motor1.pwm_duty = -pwm_duty_temp; g_bldc_motor1.dir = CCW; } else { g_bldc_motor1.pwm_duty = pwm_duty_temp; g_bldc_motor1.dir = CW; } g_bldc_motor1.run_flag = RUN; /* 开启运行 */ start_motor1(); /* 运行电机 */ } else if(key == KEY2_PRES) /* 按下KEY2关闭电机 */ { stop_motor1(); /* 停机 */ g_bldc_motor1.run_flag = STOP; /* 标记停机 */ pwm_duty_temp = 0; /* 数据清0 */ g_bldc_motor1.pwm_duty = 0; }
无刷电机电源电压-温度-三相电流采集
三相电流采集:
1、电机停止状态下,通过ADC1_CH8(PB0)检测AMP_IU电压对应的ADC值,计算电压值AMP_IU电压 = ADC值 * 3.3 / 4096,此时电压值即偏置电压。
2、电机旋转状态下,继续检测AMP_IU电压对应的ADC值,计算电压值AMP_IU电压 = ADC值 * 3.3 / 4096。
3、采样电压放大,放大倍数 = 12 /(1+1)= 6倍。
4、得到输出电压:AMP_IU =(6 * 0.02欧 * I)+ 1.25V。
5、采集输出电压计算实际电流I。I =(旋转时电压 - 偏置电压)/ 0.12A。
电源电压采集:
1、使用ADC1_CH9(PB1)采集VBUS点的电压,即VBUS = ADC1_CH9(PB1) * 3.3 / 4096。
2、电压跟随电路VBUS = power * 1 /(12 + 12 + 1)
3、采集输出电压计算实际电压POWER = VBUS * 25
最终得到:POWER = 25 * ADC1_CH9(PB1) * 3.3 / 4096。
温度采集(型号:NCP18XH103F03RB):
1、使用ADC1_CH0(PA0)采集VTEMP点的电压,即VTEMP = ADC1_CH0(PA0)* 3.3 / 4096。
2、电压跟随电路,VTEMP = 3.3 * 4700 /(Rt + 4700)。
3、采集VTEMP计算此时热敏电阻的阻值Rt。Rt =(3.3 * 4700)/ VTEMP - 4700。
4、根据Rt算出实际温度值。实际温度T1 = 1 /(In(Rt / R0)/ B + 1 / T2)。
这里的T1和T2指的是K度,即开尔文温度。K度 = 273.15(绝对温度) + 摄氏度。
T2 = (273.15 + 25)
T1:实际温度
Rt:热敏电阻在T1温度下的阻值
R0:热敏电阻在T2常温下的标称阻值(10k,看数据手册得知)
B:热敏电阻的重要恒定参数(3380,看数据手册得知)
模拟量采集配置流程:
电机基本驱动 实现电机的基础驱动函数,启停、6步换向组合等 初始化ADC和DMA 使用DMA进行ADC的数据传输,减少CPU的使用率 采集函数 编写数据采集函数 中断服务函数 采集电机未开始转时的基准电压,再将旋转后得到的电压进行相减得到实际电压
硬件连接:
TIM1_CH1:PA8 对应无刷电机UH相 TIM1_CHN1:PB13 对应无刷电机UL相 TIM1_CH2:PA9 对应无刷电机VH相 TIM1_CHN2:PB14 对应无刷电机VL相 TIM1_CH3:PA10 对应无刷电机WH相 TIM1_CHN3:PB15 对应无刷电机WL相
霍尔U相 PH10 霍尔V相 PH11 霍尔W相 PH12
输出使能SHDN PF10
ADC1_CH9:PB1 电源电压 ADC1_CH0:PA0 温度 ADC1_CH8:PB0 U相电流 ADC1_CH6:PA6 V相电流 ADC1_CH3:PA3 W相电流
ADC和DMA初始化:
/* 多通道ADC采集 DMA读取 */
ADC_HandleTypeDef g_adc_nch_dma_handle; /* 与DMA关联的ADC句柄 */
DMA_HandleTypeDef g_dma_nch_adc_handle; /* 与ADC关联的DMA句柄 */
uint16_t g_adc_value[ADC_CH_NUM * ADC_COLL] = {0}; /* 存储ADC原始值 */
float g_adc_u_value[ADC_CH_NUM] = {0}; /* 存储ADC转换后的电压值 */
/***************************************多通道ADC采集(DMA读取)程序*****************************************/
/**
* @brief adc 初始化函数
* @note 配置ADC转换通道
* @param 无
* @retval 无
*/
void adc_init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
g_adc_nch_dma_handle.Instance = ADC_ADCX;
g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */
g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 12位模式 */
g_adc_nch_dma_handle.Init.ScanConvMode = ENABLE; /* 扫描模式 多通道使用 */
g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE; /* 连续转换模式,转换完成之后接着继续转换 */
g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式 */
g_adc_nch_dma_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发 */
g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发 */
g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 右对齐 */
g_adc_nch_dma_handle.Init.NbrOfConversion = ADC_CH_NUM; /* 使用转换通道数,需根据实际转换通道去设置 */
g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE; /* 开启DMA连续转换 */
g_adc_nch_dma_handle.Init.EOCSelection = ADC_EOC_SEQ_CONV;
HAL_ADC_Init(&g_adc_nch_dma_handle);
/* 配置使用的ADC通道,采样序列里的第几个转换,增加或者减少通道需要修改这部分 */
sConfig.Channel = ADC_ADCX_CH0; /* 电源电压采集 */
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);
sConfig.Channel = ADC_ADCX_CH1; /* 温度采集 */
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);
sConfig.Channel = ADC_ADCX_CH2; /* U相电压采集 */
sConfig.Rank = 3;
HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);
sConfig.Channel = ADC_ADCX_CH3; /* V相电压采集 */
sConfig.Rank = 4;
HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);
sConfig.Channel = ADC_ADCX_CH4; /* W相电压采集 */
sConfig.Rank = 5;
HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);
}
/**
* @brief ADC DMA读取 初始化函数
* @note 本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置
* @param par : 外设地址
* @param mar : 存储器地址
* @retval 无
*/
void adc_nch_dma_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADCx时钟 */
ADC_ADCX_CH0_GPIO_CLK_ENABLE(); /* 开启GPIO时钟 */
ADC_ADCX_CH1_GPIO_CLK_ENABLE();
ADC_ADCX_CH2_GPIO_CLK_ENABLE();
ADC_ADCX_CH3_GPIO_CLK_ENABLE();
ADC_ADCX_CH4_GPIO_CLK_ENABLE();
/* AD采集引脚模式设置,模拟输入 */
GPIO_InitStruct.Pin = ADC_ADCX_CH0_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ADC_ADCX_CH0_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_ADCX_CH1_GPIO_PIN;
HAL_GPIO_Init(ADC_ADCX_CH1_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_ADCX_CH2_GPIO_PIN;
HAL_GPIO_Init(ADC_ADCX_CH2_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_ADCX_CH3_GPIO_PIN;
HAL_GPIO_Init(ADC_ADCX_CH3_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_ADCX_CH4_GPIO_PIN;
HAL_GPIO_Init(ADC_ADCX_CH4_GPIO_PORT, &GPIO_InitStruct);
adc_init();
if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)DMA2) /* 大于DMA1_Channel7, 则为DMA2的通道了 */
{
__HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */
}
else
{
__HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */
}
/* DMA配置 */
g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx; /* 设置DMA通道 */
g_dma_nch_adc_handle.Init.Channel = DMA_CHANNEL_0;
g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; /* DIR = 1 , 外设到存储器模式 */
g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */
g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */
g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* 外设数据长度:16位 */
g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; /* 存储器数据长度:16位 */
g_dma_nch_adc_handle.Init.Mode = DMA_CIRCULAR; /* 外设流控模式 */
g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */
HAL_DMA_Init(&g_dma_nch_adc_handle);
__HAL_LINKDMA(&g_adc_nch_dma_handle,DMA_Handle,g_dma_nch_adc_handle);
HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 2, 1);
HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn);
HAL_ADC_Start_DMA(&g_adc_nch_dma_handle,(uint32_t *)g_adc_value, ADC_CH_NUM * ADC_COLL);
}
/**
* @brief ADC DMA采集中断服务函数
* @param 无
* @retval 无
*/
void ADC_ADCX_DMASx_IRQHandler(void)
{
HAL_DMA_IRQHandler(&g_dma_nch_adc_handle);
}
uint16_t g_adc_val[ADC_CH_NUM]; /* ADC平均值存放数组 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if (hadc->Instance == ADC1) /* 大约2.6ms采集完成进入中断 */
{
HAL_ADC_Stop_DMA(&g_adc_nch_dma_handle); /* 关闭DMA转换 */
calc_adc_val(g_adc_val); /* ADC数值转换 */
HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, (uint32_t *)&g_adc_value, (uint32_t)(ADC_SUM)); /* 再启动DMA转换*/
}
}
基础定时器初始化:
TIM_HandleTypeDef timx_handler; /* 定时器参数句柄 */ /** * @brief 基本定时器TIMX定时中断初始化函数 * @note * 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候 * 基本定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz * 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us. * Ft=定时器工作频率,单位:Mhz * * @param arr: 自动重装值。 * @param psc: 时钟预分频数 * @retval 无 */ void btim_timx_int_init(uint16_t arr, uint16_t psc) { timx_handler.Instance = BTIM_TIMX_INT; /* 基本定时器X */ timx_handler.Init.Prescaler = psc; /* 设置预分频器 */ timx_handler.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数器 */ timx_handler.Init.Period = arr; /* 自动装载值 */ timx_handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频因子 */ HAL_TIM_Base_Init(&timx_handler); HAL_TIM_Base_Start_IT(&timx_handler); /* 使能基本定时器x和及其更新中断:TIM_IT_UPDATE */ __HAL_TIM_CLEAR_IT(&timx_handler, TIM_IT_UPDATE); /* 清除更新中断标志位 */ } /** * @brief 定时器底册驱动,开启时钟,设置中断优先级 此函数会被HAL_TIM_Base_Init()函数调用 * @param 无 * @retval 无 */ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) { if (htim->Instance == BTIM_TIMX_INT) { BTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIM时钟*/ HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 1, 3); /* 抢占1,子优先级3,组2 */ HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn); /* 开启ITM3中断*/ } } /** * @brief 基本定时器TIMX中断服务函数 * @param 无 * @retval 无 */ void BTIM_TIMX_INT_IRQHandler(void) { HAL_TIM_IRQHandler(&timx_handler); /* 定时器回调函数 */ }
高级定时器中断回调中电流采集:
/* 三相电流采集 */ for (i = 0; i < 3; i++) { adc_val_m1[i] = g_adc_val[i + 2]; adc_amp[i] = adc_val_m1[i] - adc_amp_offset[i][ADC_AMP_OFFSET_TIMES]; /* 运动状态ADC值 - 停机状态ADC值 = 实际作用ADC值 */ if (adc_amp[i] >= 0) /* 去除反电动势引起的负电流数据 */ { adc_amp_un[i] = adc_amp[i]; } } /* 运算母线电流(母线电流为任意两个有开关动作的相电流之和) */ if (g_bldc_motor1.step_sta == 0x05) { adc_amp_bus = (adc_amp_un[0] + adc_amp_un[1]) * ADC2CURT; /* UV */ } else if (g_bldc_motor1.step_sta == 0x01) { adc_amp_bus = (adc_amp_un[0] + adc_amp_un[2]) * ADC2CURT; /* UW */ } else if (g_bldc_motor1.step_sta == 0x03) { adc_amp_bus = (adc_amp_un[1] + adc_amp_un[2]) * ADC2CURT; /* VW */ } else if (g_bldc_motor1.step_sta == 0x02) { adc_amp_bus = (adc_amp_un[0] + adc_amp_un[1]) * ADC2CURT; /* UV */ } else if (g_bldc_motor1.step_sta == 0x06) { adc_amp_bus = (adc_amp_un[0] + adc_amp_un[2]) * ADC2CURT; /* WU */ } else if (g_bldc_motor1.step_sta == 0x04) { adc_amp_bus = (adc_amp_un[2] + adc_amp_un[1]) * ADC2CURT; /* WV */ }
基本定时器中断回调采集未启动电机时基准电压:
if (htim->Instance == TIM6) { /* 计算未开始启动时的基准电压 */ times_count++; if (g_bldc_motor1.run_flag == STOP) { uint8_t i; uint32_t avg[3] = {0, 0, 0}; adc_amp_offset[0][adc_amp_offset_p] = g_adc_val[2]; /* 获取电机停机状态下的三相电流 U */ adc_amp_offset[1][adc_amp_offset_p] = g_adc_val[3]; /* V */ adc_amp_offset[2][adc_amp_offset_p] = g_adc_val[4]; /* W */ adc_amp_offset_p ++; NUM_CLEAR(adc_amp_offset_p, ADC_AMP_OFFSET_TIMES); /* 如果溢出,从头开始计数 */ for (i = 0; i < ADC_AMP_OFFSET_TIMES; i++) { avg[0] += adc_amp_offset[0][i]; /* 各相数值累加 */ avg[1] += adc_amp_offset[1][i]; avg[2] += adc_amp_offset[2][i]; } for (i = 0; i < 3; i++) { avg[i] /= ADC_AMP_OFFSET_TIMES; /* 取平均 */ adc_amp_offset[i][ADC_AMP_OFFSET_TIMES] = avg[i]; /* 赋值 */ } } }
温度获取:
/* Rt = Rp *exp(B*(1/T1-1/T2)) Rt 是热敏电阻在T1温度下的阻值; Rp是热敏电阻在T2常温下的标称阻值; exp是e的n次方,e是自然常数,就是自然对数的底数,近似等于 2.7182818; B值是热敏电阻的重要参数,教程中用到的热敏电阻B值为3380; 这里T1和T2指的是开尔文温度,T2是常温25℃,即(273.15+25)K T1就是所求的温度 */ const float Rp = 10000.0f; /* 10K */ const float T2 = (273.15f + 25.0f); /* T2 */ const float Bx = 3380.0f; /* B */ const float Ka = 273.15f; /** * @brief 计算温度值 * @param para: 温度采集对应ADC通道的值(已滤波) * @note 计算温度分为两步: 1.根据ADC采集到的值计算当前对应的Rt 2.根据Rt计算对应的温度值 * @retval 温度值 */ float get_temp(uint16_t para) { float Rt; float temp; Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f) - 4700.0f; /* like this R=5000, T2=273.15+25,B=3470, RT=5000*EXP(3470*(1/T1-1/(273.15+25)) */ temp = Rt / Rp; temp = log(temp); /* ln(Rt/Rp) */ temp /= Bx; /* ln(Rt/Rp)/B */ temp += (1.0f / T2); temp = 1.0f / (temp); temp -= Ka; return temp; }
采集各参数:
/* 电源电压 */ sprintf(buf,"Power:%.3fV ",g_adc_val[0]*ADC2VBUS); /* 温度 */ sprintf(buf,"Temp:%.1fC ",get_temp(g_adc_val[1])); /* 三相电流及母线电流 */ current[0] = adc_amp_un[0]* ADC2CURT; /* 计算出三相电流值,U */ current[1] = adc_amp_un[1]* ADC2CURT; /* 计算出三相电流值,V */ current[2] = adc_amp_un[2]* ADC2CURT; /* 计算出三相电流值,W */ /*一阶数字滤波 滤波系数0.1 用于显示*/ FirstOrderRC_LPF(current_lpf[0],current[0],0.1f); /* U相电流 */ FirstOrderRC_LPF(current_lpf[1],current[1],0.1f); /* V相电流 */ FirstOrderRC_LPF(current_lpf[2],current[2],0.1f); /* W相电流 */ FirstOrderRC_LPF(current_lpf[3],adc_amp_bus,0.1f); /* 母线电流 */ if(g_bldc_motor1.run_flag == STOP) /* 停机的电流显示 */ { current_lpf[0] = 0; current_lpf[1] = 0; current_lpf[2] = 0; current_lpf[3] = 0; } /* LCD显示提示信息 */ sprintf(buf,"Amp U:%.3fmA ",(float)current_lpf[0]); lcd_show_string(10,230,200,16,16,buf,g_point_color); sprintf(buf,"Amp V:%.3fmA ",(float)current_lpf[1]); lcd_show_string(10,250,200,16,16,buf,g_point_color); sprintf(buf,"Amp W:%.3fmA ",(float)current_lpf[2]); lcd_show_string(10,270,200,16,16,buf,g_point_color); sprintf(buf,"Amp Bus:%.3fmA ",(float)current_lpf[3]); lcd_show_string(10,290,200,16,16,buf,g_point_color);
无刷电机有感方波闭环控制
无刷测速原理
当转子磁极只有一对极时,转子旋转一圈三个霍尔信号都会输出相应波形。
这些波形的特点:高低电平的持续时间是一样的,均为180°电角度。
对于正点原子BLDC的霍尔传感器,N极靠近输出1,S极靠近输出0。
1、当转子只有一对极时,转子旋转一圈,霍尔输出一个完整脉冲(其中高低电平持续时间均为180°电角度)。
2、计算高电平的持续时间,即 t = C / Ft(Ft是霍尔脉冲的频率,C是计数次数)。
3、因为高低电平的持续时间是一样的,均为180°电角度。所以旋转一圈,需要的总时间为T = 2t = 2 * C / Ft。单位是s/圈。
4、RPM的单位是圈/分,所以为 Ft / (2 *C) * 60。
5、当转子为2对极时,转子旋转一圈,霍尔输出两个完整脉冲(其中高低电平持续时间均为360°电角度)。
6、所以速度公式为:Ft / (4 * C)* 60。
速度环控制流程
速度闭环的配置步骤
电机基本驱动 | 实现电机的基础驱动函数,启停、6步换向组合等 |
PID算法 | 实现PID算法(增量式/位置式) |
速度控制 | 编写速度控制代码 |
上位机通信 | 编写上位机通信代码,可在上位机上实时显示当前速度与目标速度的波形变化 |
中断服务函数 | 在中断里计算电机速度,调用PID算法实现PID控制 |
首先是初始化高级定时器TIM1,不分频,168000/18的计数。即55us进一次中断,频率为18000。
按照前面公式,除了计数次数,其余可整合一起:
g_bldc_motor1.count_j++; /* 计算速度专用计数值,55us单脉冲高电平时计数 */
#define SPEED_COEFF (uint32_t)((18000/4)*60) /* 旋转一圈变化4个信号,2对级永磁体特性,NSNS共4级数*/ temp_speed = (SPEED_COEFF/g_bldc_motor1.count_j);
/** * @brief 获取霍尔传感器引脚状态 * @param motor_id : 无刷接口编号 * @retval 霍尔传感器引脚状态 */ uint32_t hallsensor_get_state(uint8_t motor_id) { __IO static uint32_t state ; state = 0; if(motor_id == MOTOR_1) { if(HAL_GPIO_ReadPin(HALL1_TIM_CH1_GPIO,HALL1_TIM_CH1_PIN) != GPIO_PIN_RESET) /* 霍尔传感器状态获取 */ { state |= 0x01U; } if(HAL_GPIO_ReadPin(HALL1_TIM_CH2_GPIO,HALL1_TIM_CH2_PIN) != GPIO_PIN_RESET) /* 霍尔传感器状态获取 */ { state |= 0x02U; } if(HAL_GPIO_ReadPin(HALL1_TIM_CH3_GPIO,HALL1_TIM_CH3_PIN) != GPIO_PIN_RESET) /* 霍尔传感器状态获取 */ { state |= 0x04U; g_bldc_motor1.hall_single_sta = 1; /* 单个霍尔状态,计算速度用到 */ } else g_bldc_motor1.hall_single_sta = 0; } return state; } /** * @brief 检测输入信号是否发生变化 * @param val :输入信号 * @note 测量速度使用,获取输入信号状态翻转情况,计算速度 * @retval 0:计算高电平时间,1:计算低电平时间,2:信号未改变 */ uint8_t uemf_edge(uint8_t val) { /* 主要是检测val信号从0 - 1 在从 1 - 0的过程,即高电平所持续的过程 */ static uint8_t oldval=0; if(oldval != val) { oldval = val; if(val == 0) return 0; else return 1; } return 2; }
计算速度只需知道一个霍尔状态的高电电平。然后可以用uemf_edge函数获取g_bldc_motor1.hall_single_sta检测单个霍尔信号的变化,存入g_bldc_motor1.hall_sta_edge。
计算速度:
/******************************* 速度计算 *******************************/ g_bldc_motor1.count_j++; /* 计算速度专用计数值 */ g_bldc_motor1.hall_sta_edge = uemf_edge(g_bldc_motor1.hall_single_sta);/* 检测单个霍尔信号的变化 */ if(g_bldc_motor1.hall_sta_edge == 0) /* 统计单个霍尔信号的高电平时间,当只有一对级的时候,旋转一圈为一个完整脉冲。一高一低相加即旋转一圈所花的时间*/ { /*计算速度*/ if(g_bldc_motor1.dir == CW) temp_speed = (SPEED_COEFF/g_bldc_motor1.count_j); else temp_speed = -(SPEED_COEFF/g_bldc_motor1.count_j); FirstOrderRC_LPF(g_bldc_motor1.speed,temp_speed,0.2379f); /* 一阶滤波 */ g_bldc_motor1.no_single = 0; g_bldc_motor1.count_j = 0; } if(g_bldc_motor1.hall_sta_edge == 1) /* 当采集到下降沿时数据清0 */ { g_bldc_motor1.no_single = 0; g_bldc_motor1.count_j = 0; } if(g_bldc_motor1.hall_sta_edge == 2) /* 霍尔值一直不变代表未换向 */ { g_bldc_motor1.no_single++; /* 不换相时间累计 超时则判定速度为0 */ if(g_bldc_motor1.no_single > 15000) { g_bldc_motor1.no_single = 0; g_bldc_motor1.speed = 0; /* 超时换向 判定为停止 速度为0 */ } }
PID控制:
/******************************* PID控制 *******************************/ temp_pwm1 = increment_pid_ctrl(&g_speed_pid, g_bldc_motor1.speed); /* PID控制算法,输出期望值 */ FirstOrderRC_LPF(motor_pwm_s,temp_pwm1,0.085); /* 一阶滤波 */ if(motor_pwm_s < 0) /* 判断正负值 */ { g_bldc_motor1.pwm_duty = -motor_pwm_s; } else { g_bldc_motor1.pwm_duty = motor_pwm_s; }
位置记录:
const uint8_t hall_cw_table[12] = {0x62,0x23,0x31,0x15,0x54,0x46,0x63,0x21,0x35,0x14,0x56,0x42}; const uint8_t hall_ccw_table[12] = {0x45,0x51,0x13,0x32,0x26,0x64,0x41,0x53,0x12,0x36,0x24,0x65}; /** * @brief 方向检测函数 * @param obj : 电机控制句柄 * @retval res : 旋转方向 */ uint8_t check_hall_dir(_bldc_obj * obj) { uint8_t temp,res = HALL_ERROR; if((obj->step_last <= 6)&&(obj->step_sta <= 6)) { temp = ((obj->step_last & 0x0F) << 4)|(obj->step_sta & 0x0F); if((temp == hall_ccw_table[0])||(temp == hall_ccw_table[1])||\ (temp == hall_ccw_table[2])||(temp == hall_ccw_table[3])||\ (temp == hall_ccw_table[4])||(temp == hall_ccw_table[5])) { res = CCW; } else if((temp == hall_cw_table[0])||(temp == hall_cw_table[1])||\ (temp == hall_cw_table[2])||(temp == hall_cw_table[3])||\ (temp == hall_cw_table[4])||(temp == hall_cw_table[5])) { res = CW; } } return res; } /******************************* 位置记录 *******************************/ if(g_bldc_motor1.step_last != g_bldc_motor1.step_sta) { bldc_dir = check_hall_dir(&g_bldc_motor1); if(bldc_dir == CCW) { g_bldc_motor1.pos -= 1; } else if(bldc_dir == CW) { g_bldc_motor1.pos += 1; } g_bldc_motor1.step_last = g_bldc_motor1.step_sta; }
无刷电机有感方波速度环-电流环 双环控制
略,查阅代码为主。