目录

CubeMX基本配置

drv8301控制模式(3pwm?6pwm?死区时间设置?)

SVPWM理论推导

HALL接口设置以及旋转速度获取原理


FOC开环控制主要分为三步:第一,角度自增;第二,Park逆变换;第三,SVPWM计算出下个周期要写入的占空比Ta,Tb,Tc

验证SVPWM模块也非常简单,串口打印出来应该是个比较标准的马鞍波(我是20k频率,可供参考) 

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_引脚

如果角度改变太快会造成波形有不同程度的失真,不过是正常现象,角度变化改小点就可以了

CubeMX基本配置

使用STM32外设情况如下:

TIM1高级定时器CH1-CH3输出三路互补的PWM信号,设置为中央对齐模式,同时CH4配置为PWM generation no output用于触发ADC采样,具体原因可以看这篇博客:foc配置篇——ADC注入组使用定时器触发采样的配置_定时器触发adc采样_jdhfusk的博客-CSDN博客

据我测试Center Align Mode1和Center Align Mode2没啥很大的区别,定时器Update中断触发位置都是在中间的时刻,但最后我采用的是在ADC采集完成的回调函数中进行FOC运算,所以配置成什么模式就更无所谓了。

这里就提一句比较推荐使用高级定时器TIM1和TIM8来做PWM输出,因为只有他俩是挂在168M下的,而且具有互补输出功能,在频率比较高的情况下,占空比不至于过小。

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_单片机_02

 ADC配置如下,随意看看就行,没啥很特别的

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_引脚_03

 初始化代码如下:

void MOTOR1_PWM_Init(void)
{
	//PWM初始化
	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_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);
	HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2);
	HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_3);
    __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1,0);
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2,0);
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3,0);
	//通道4触发ADC采样
	HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_4);
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4,3900);
	//开启ADC注入转换
	HAL_ADCEx_InjectedStart_IT(&hadc1);
}

drv8301控制模式(3pwm?6pwm?死区时间设置?)

一开始采样3PWM控制,但是这个模式不灵活的地方在于,DRV8301是通过DTC引脚到地的电阻值设置死区时间的,而怨种队友一开始坚定认为用了很好的MOS管,就只接了0欧电阻(相当于50ns死区时间)。推荐6PWM模式,配置简单,死区时间设置很方便,而且DRV8301默认就是6PWM控制,省了一步寄存器更改配置

死区时间计算理论上为 MOS管关断时间(看芯片手册) + 单片机IO口翻转时间(对于F4来说10ns左右)当然,理论计算出来后最好还要留一点裕量

我一开始没怎么考虑死区时间问题,于是电机转起来的声音非常惨绝人寰。排查了很久代码的问题,最后锁定了死区时间问题

实际调试时发现,死区时间过小,电机会有尖锐的噪声,死区时间过大,采样电流会隐隐看到有些不连续,严重时就像这样(神似马鞍波)

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_嵌入式硬件_04

 这个时候我们就可以考虑适当调小死区时间,效果好的时候就是这样啦

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_时间设置_05

SVPWM理论推导


这里推荐直接看这篇文章,至于代码网上有很多,但关键在于理解以下几个问题:

为什么要选择中心对其模式?

Uq的实际含义是什么?

以及采集总线电压到底有什么作用?

我一开始配置单片机外设的时候也没考虑过总线电压采集,其实如果电压比较稳定没有波动的话给个定值完全ok

HALL接口设置以及旋转速度获取原理

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_单片机_06

 首先选择HALL接口模式,选择时钟源及合适的分频系数,以得到最大计数器周期,该周期长于传感器上两次变化的间隔时间

Input Filter 其实是为了抗干扰,因为会有电磁干扰进入引脚,就相当于按键的多次计数软件滤波

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_引脚_07

 三个引脚二配置为内部上拉模式,当然在硬件电路上加上拉电阻也可以的。如果不上拉是无法获取到稳定的电平信号的(本人惨痛教训,曾经直接把三根信号线接到示波器上看,只有几十mV的电平,当时还以为是电机坏了)

下面来到本篇重点了,就是到底如何根据霍尔信号推算当前的电角度?

霍尔传感器分为60°安装方式和120°安装方式,但是仔细观察下面这张图就会发现,本质的区别不大。对于HALL接口模式下,三个信号异或,只要有一路信号发生跳变单片机就会进入输入捕获中断,也就是每隔60电角度就可以在触发中断里得到准确的电角度值

对于120°安装时,信号在0-2PI内变换6次,顺序是5-4-6-2-3-1

而60°安装时,信号改变顺序是4-6-7-3-1-0

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_单片机_08

但这里存在一个问题,就是我们需要根据TIM2->CCR1捕获值来计算角速度吗?

由于我把FOC的运算放在ADC注入组的中断里,所有如果把角速度计算放在霍尔中断,但是在adc中断里算电角度会有严重的延迟误差,每次校准时会有一个很明显的突变发生 

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_stm32_09

如果把计算角速度的公式放在adc中断里就能得到一个很平滑的曲线

 

stm32cubemx fmc 驱动 ad7606 stm32驱动drv8301_单片机_10

 话不多说直接上代码

/**
  * 函数功能: 霍尔传感器回调函数
  * 输入参数: @htim,霍尔传感器接口定时器
  * 返 回 值: 无
  * 说    明:霍尔信号发生跳变后进入这个中断,只做角度矫正
  */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	uint8_t hall_read_temp;
	if(htim == &htim2)
	{
		hall_read_temp = HALL_GetPhase();
		if(hall_read_temp==0x05)
    {
      hall_angle = 0.0f+PHASE_SHIFT_ANGLE;
    }
    else if(hall_read_temp==0x04)
    {
      hall_angle = (PI/3.0f)+PHASE_SHIFT_ANGLE;
    }
    else if(hall_read_temp==0x06)
    {
      hall_angle = (PI*2.0f/3.0f)+PHASE_SHIFT_ANGLE;
    }
    else if(hall_read_temp==0x02)
    {
      hall_angle = PI+PHASE_SHIFT_ANGLE;
    }
    else if(hall_read_temp==0x03)
    {
      hall_angle = (PI*4.0f/3.0f)+PHASE_SHIFT_ANGLE;
    }
    else if(hall_read_temp==0x01)
    {
      hall_angle = (PI*5.0f/3.0f)+PHASE_SHIFT_ANGLE;
    }
    if(hall_angle<0.0f)
    {
      hall_angle += 2.0f*PI;
    }
    else if(hall_angle>(2.0f*PI))
    {
      hall_angle -= 2.0f*PI;
    }
    
	}
}


void FOC_ControllerTask(void)
{
		//最先计算霍尔角度,其次电流,然后送入SVPWM计算
		static uint8_t hall_phase,hall_phase_last;
		static uint16_t hall_count;
		hall_phase = HALL_GetPhase();
		if(hall_phase == hall_phase_last)
		{
			hall_count++;
			if(hall_count > 2000)
			{
				hall_count = 0;
				hall_angle_add = 0;
			}
		}
		else
		{
			hall_angle_add = (float)PI/3.0f/hall_count;
			hall_count = 0;
			hall_phase_last = hall_phase;
		}
		hall_angle = _normalizeAngle(hall_angle + hall_angle_add);
}