FOC的PWM的重新配置以及svpwm的移植

就在前几天,我刚完成了对FOC中最重要的电流环代码的编写,再移植了svpwm的相关代码之后,FOC有感部分的程序就算是大功告成了,但就在这看似简单的一环上,我遇到了一个我之前一直没注意到以及不重视的问题---------------这个问题就是,我的ADC采样是在PWM波形的任意位置采样的,这将导致我的电流值大幅度波动,电机控制不稳!

为什么我ADC采样的位置不确定呢?原因就在于我的ADC是软件触发,也就是当我开启ADC转换之后,DMA会不断地把ADC的数据搬运到我的目标数组里,而它这个搬运是随机的,也就是说这个搬运很有可能在mos管刚打开的瞬间就开启采样,自然受到的噪声就大,电流值波动也大,这就是罪魁祸首。

那么为什么我还在标题中强调PWM的重新配置?因为如果想要用svpwm算法,PWM就必须配置为中央对齐模式,才能最大程度的减缓因为mos管不断开关而带来的巨大损耗。这点其实我发布的第一篇文章有讲过,不知道为啥的自己倒回去看,很快的。

说了这么多,大家可能还不能直观的知道这样的配置究竟有什么好处,那么接下来我给出两张我在两种不同配置下调试无刷电机的波形图:

这两张图就很直观了,注意一下,我一开始设置的目标电流是0A,前面一个图是没配置之前的,后面一个图是配置好以后的,在同等的图形比例下,配置好的波形明显稳定多了,都是0.0005以内波动的,而上面那张图波动就很大,信号毛刺很多,尽管我两种配置都加了一阶低通滤波器,但一张图不管我怎么调参,他的毛刺一直都很多,很不稳定,第二张就不一样,还没开始调参就已经很稳定了。所以接下来,我要好好讲一讲怎么配置。

1.   首先先打开CUBEMX,找到你输出PWM波形的定时器,一般这个定时器都还有一个通道没有用到,把这个没用到的通道配置为PWM 开头的,NO OUTPUT也就是不对外输出。然后把这个通道输出的PWM作为ADC采样的触发源,这个等一下再细说。接着在COUNTER SETTINGS里的COUNTING MODE把原来的默认模式改成中央对齐模式1,紧接着要把ARR值改为你原来ARR值的一半,因为中央对齐模式的一个PWM周期是原来一个PWM周期的两倍。这就意味着你如果要保持原来PWM的频率,ARR的值必须设置为原来的两倍!因为中心对齐模式是,CNT从0计数记到ARR的值,然后再从ARR的值直接递减到0,这样才算一个PWM的一个完整的周期,所以现在的周期是原来的两倍。还有,PSC的值我设置的是0,是因为PSC不分频的话,最后FOC中PWM设置占空比时分辨率也会更高。       

紧接着,把这个空出来的通道稍微调整一下,把Pulse也就是初始的CCR的值设置为8000,也就距离ARR的8400还差400,这个第二点里细说。

2.    接下里配置ADC,首先,这里我ADC与之前最大的不同就是我用的不是普通的规则组而是注入组injected,主要是因为注入组才可以选择把TIM1的通道四(也就是我上面刚配置好的通道)作为触发源,并且注入组也能更优先进入中断。首先先把之前的连续转换模式关闭,因为我们的连续转换是由TIM1通道四驱动的,扫描模式必须开启,因为如果要对电流进行采样,至少也需要有两个IO口来采集两相电流才能解算出iq,你要是没开启的话,配置到后面系统也会报错的。

配置好上面的之后,我再来讲讲规则组和通道组,首先你只需要把原来的规则组删减成只剩一组就行了,反正也用不到。而且CUBEMX的规则组还不能删减到0,反正他的触发源是软件触发,只要我们没有软件开启规则组,规则组ADC就不会转换。接下来就是在injected这里,先把要转换的通道改为2,然后外部触发源选择TIM1的输出比较通道四事件,触发模式选择下降沿触发,这样就是当通道四CNT>CCR值时,PWM波形由高电平变成低电平,也就是在这个下降沿瞬间,出发了ADC注入组的转换,注入组相对于规则组还有一个好处就是不需要用到DMA了,再也不用怕数据被覆盖了,因为注入组奢侈到有四个单独的通道对应四个不同的寄存器,所以可以把之前配置的DMA给关掉。

然后现在我来解释一下为什么一开始设置通道四的CCR设置的这么高,并且ADC采样周期由原来的480cycles变成了28cycles,因为我们现在是在下降沿瞬间经行ADC采样,拿个图举例子:

黄色的波形的突变表示ADC已经完成了转换,当我们配置为中央对齐模式时,三路PWM都是中央对齐的,这也就以为着三路PWM中占空比最高的也就是对应低电平时间最低的,而我们必须在这最低的低电平时间里完成对ADC的采样,因为在三路都是低电平的持续时间里,这时候去对电流进行采样得出的电流值是稳定的,不会受到mos管开关造成的电流不稳定现象。但是这时候我们又要想了,万一占空比有出现100%的,那不就没有低电平了吗?对于这个我想说几乎不可能,占空比百分百就说明电机基本上是满负荷运行的,这样的情况一般都是作死去赌电机或者目标电流值设置过大才会这样的。所以一般还是有低电平时间的,但是有时候可能电机真的占空比会非常高,所以为了应对这种情况,我们应该尽可能缩短ADC采样时间,不然如果在ADC还没转换完全时,PWM突然变成高电平了,这是会导致ADC值波动,从而使结果不准确的。但是采样时间又不能太短,会导致ADC电路中的电容充电时间不够,导致读的电流也不准确。所以,经过我的运算,(28+12.5)*2/21=3.8571us,也就是转换两个ADC通道大概需要这么多时间,而我一个PWM的周期又是100us,所以理论上只要每一相电路的占空比没到95%,我的ADC就有充足的时间经行转换,这也保证了电容的充电时间。

3.     配置好以后,要怎么调用ADC的值并且原来的代码要怎么修改?

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_I2C2_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();
  MX_UART4_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADCEx_InjectedStart(&hadc1);
  HAL_TIM_Base_Start(&htim2);
  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_PWM_Start(&htim1,TIM_CHANNEL_4);
//  HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adc_value,2);
  HAL_Delay(100);
  electricity_init();
  setMotor(motor_PP,motor_DIR);
  HAL_UART_Receive_IT(&huart4, &rx_byte, 1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	receive_data_to_serial_target();
	FOC_setCurrent(serial_targrt);
	  
	float nowtime = micros();
    if(nowtime - lastTime>=1000){	  
	current = getCurrent_fillter();
	HAL_UART_Transmit(&huart4,(uint8_t*)&current,sizeof(float),HAL_MAX_DELAY);
    HAL_UART_Transmit(&huart4,(uint8_t*)tail,4,HAL_MAX_DELAY);
    lastTime = nowtime;
	}
  }
  /* USER CODE END 3 */
}

就是要记得把TIM1的通道四打开,然后记得使能ADC的注入通道,接下来是怎么读取ADC的值,其实这个很简单,我们只要读取相应寄存器的值就好了:

#include "electricity.h"


float adc1_sum=0,adc2_sum=0;
float adc1_init=0,adc2_init=0;
float current_a=0.0f,current_b=0.0f;
#define _1_SQRT3 0.57735026919f
#define _2_SQRT3 1.15470053838f
#define _ADC_CONV 3.3f/4095.0f

void electricity_init(void)
{
	HAL_Delay(1000);
	for(int i=0;i<1000;i++)
	{
		adc_value[0] = hadc1.Instance->JDR1;
		adc_value[1] = hadc1.Instance->JDR2;
		adc1_sum+=(float)adc_value[0]*_ADC_CONV;
		adc2_sum+=(float)adc_value[1]*_ADC_CONV;
		HAL_Delay(1);
	}
	adc1_init=adc1_sum/1000.0f;	
	adc2_init=adc2_sum/1000.0f;
}

float calculate_current(float angle)
{
	adc_value[0] = hadc1.Instance->JDR1;
	adc_value[1] = hadc1.Instance->JDR2;
	current_a=((float)adc_value[0]*_ADC_CONV-adc1_init)/50.0f/0.01f;  //经过ADC转换为电流值公式得到该小数
	current_b=((float)adc_value[1]*_ADC_CONV-adc2_init)/50.0f/0.01f;
	
    float I_alpha=current_a;
    float I_beta = _1_SQRT3 * current_a + _2_SQRT3 * current_b;

    float ct = cos(angle);
    float st = sin(angle);
    //float I_d = I_alpha * ct + I_beta * st;
    float I_q = I_beta * ct - I_alpha * st;
    return I_q;
}


就是从JDR寄存器里直接取值就行。

如果你之前的PWM是普通边沿检测,ADC是软件触发的,照我以上我说的去配置,相信你的电流环的实际电流值会更加稳定,而且这么配置也是为了svpwm做铺垫,也是为了更好的移植svpwm代码,移植好以后,确实能够明显的看出电机转起来更加的丝滑了,因为svpwm在理论上来说是可以更精确的实现力矩的控制以及角度的控制的,但这前提就是你要把PWM配置为中央对齐模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值