电机应用-直流有刷电机多环控制实现

news2024/12/28 19:46:57

目录

直流有刷电机多环控制实现

硬件设计

直流电机三环(速度环、电流环、位置环)串级PID控制-位置式PID

编程要点

配置ADC可读取电流值

配置基本定时器6产生定时中断读取当前电路中驱动电机的电流值并执行PID运算

配置定时器1输出PWM控制电机

配置定时器3读取编码器的计数值

ADC数据处理

编写位置式PID算法


直流有刷电机多环控制实现

外环的输出会作为内环的输入。外环一般是最终要控制的效果, 

硬件设计

可选:L298N电机驱动板、野火MOS搭建的驱动板。

直流电机三环(速度环、电流环、位置环)串级PID控制-位置式PID

编程要点

配置ADC可读取电流值。

配置基本定时器TIM6产生定时中断执行PID运算。

配置高级定时器TIM1输出PWM控制电机。

配置通用定时器TIM3读取编码器的计数值。

ADC数据处理。

编写位置式PID算法。

编写位置环、速度环、电流环控制函数。

增加上位机曲线观察相关代码。

编写按键控制代码。

配置ADC可读取电流值
#define VBUS_MAX      		14    // 电压最大值
#define VBUS_MIN      		10    // 电压最小值
			
#define VBUS_HEX_MAX  		((VBUS_MAX/37.0+1.24)/VREF*65536)    // 电压最大值(测量电压是电源电压的1/37)
#define VBUS_HEX_MIN  		((VBUS_MIN/37.0+1.24)/VREF*65536)    // 电压最小值(测量电压是电源电压的1/37)
 
__IO uint16_t ADC_ConvertedValue;
DMA_HandleTypeDef DMA_Init_Handle;
ADC_HandleTypeDef ADC_Handle;
 
static uint16_t adc_buff[1024];
static uint16_t vbus_adc_mean = 0;  	// 电源电压 ADC 采样结果平均值
static uint32_t adc_mean_sum = 0;   	// 平均值累加
static uint32_t adc_mean_count = 0; 	// 累加计数
 
/**
  * @brief  电流采集初始化
  * @param  无
  * @retval 无
  */
void ADC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
    __GPIOB_CLK_ENABLE();
	__DMA2_CLK_ENABLE();
	__ADC1_CLK_ENABLE();
 
	// PB1--电流
    GPIO_InitStructure.Pin = GPIO_PIN_1;
    GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
 
	// PB0--电压
    GPIO_InitStructure.Pin = GPIO_PIN_0;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	// ADC1使用DMA2,数据流0,通道0,这个是手册固定死的
	DMA_Init_Handle.Instance 					= DMA2_Stream0;
    DMA_Init_Handle.Init.Direction 				= DMA_PERIPH_TO_MEMORY;
    DMA_Init_Handle.Init.PeriphInc 				= DMA_PINC_DISABLE;
    DMA_Init_Handle.Init.MemInc 				= DMA_MINC_ENABLE;
    DMA_Init_Handle.Init.PeriphDataAlignment 	= DMA_PDATAALIGN_HALFWORD;
    DMA_Init_Handle.Init.MemDataAlignment 		= DMA_MDATAALIGN_HALFWORD;
    DMA_Init_Handle.Init.Mode 					= DMA_CIRCULAR;
    DMA_Init_Handle.Init.Priority 				= DMA_PRIORITY_HIGH;
    DMA_Init_Handle.Init.FIFOMode 				= DMA_FIFOMODE_DISABLE;
    DMA_Init_Handle.Init.FIFOThreshold 			= DMA_FIFO_THRESHOLD_HALFFULL;
    DMA_Init_Handle.Init.MemBurst 				= DMA_MBURST_SINGLE;
    DMA_Init_Handle.Init.PeriphBurst 			= DMA_PBURST_SINGLE;
    // 选择 DMA 通道,通道存在于流中
    DMA_Init_Handle.Init.Channel 				= DMA_CHANNEL_0;
    //初始化DMA流,流相当于一个大的管道,管道里面有很多通道
    HAL_DMA_Init(&DMA_Init_Handle);
 
    __HAL_LINKDMA(&ADC_Handle, DMA_Handle, DMA_Init_Handle);
	
	ADC_Handle.Instance 					= ADC1;
    ADC_Handle.Init.ClockPrescaler 			= ADC_CLOCKPRESCALER_PCLK_DIV4;
    ADC_Handle.Init.Resolution 				= ADC_RESOLUTION_12B;
    ADC_Handle.Init.ScanConvMode 			= ENABLE;
    ADC_Handle.Init.ContinuousConvMode 		= ENABLE;
    ADC_Handle.Init.DiscontinuousConvMode 	= DISABLE;
    ADC_Handle.Init.NbrOfDiscConversion   	= 0;
    ADC_Handle.Init.ExternalTrigConvEdge 	= ADC_EXTERNALTRIGCONVEDGE_NONE;
    ADC_Handle.Init.ExternalTrigConv 		= ADC_SOFTWARE_START;
    ADC_Handle.Init.DataAlign 				= ADC_DATAALIGN_LEFT;
    ADC_Handle.Init.NbrOfConversion 		= 2;
    ADC_Handle.Init.DMAContinuousRequests 	= ENABLE;
    ADC_Handle.Init.EOCSelection          	= ADC_EOC_SINGLE_CONV;
    HAL_ADC_Init(&ADC_Handle);
	
	ADC_ChannelConfTypeDef ADC_Config;
 
    ADC_Config.Channel      = ADC_CHANNEL_9;
    ADC_Config.Rank         = 1;
    ADC_Config.SamplingTime = ADC_SAMPLETIME_3CYCLES;
    ADC_Config.Offset       = 0;
    HAL_ADC_ConfigChannel(&ADC_Handle, &ADC_Config);
 
    /** Configure the analog watchdog
    */
    ADC_AnalogWDGConfTypeDef AnalogWDGConfig = {0};
 
    AnalogWDGConfig.WatchdogMode 	= ADC_ANALOGWATCHDOG_SINGLE_REG;
    AnalogWDGConfig.HighThreshold 	= VBUS_HEX_MAX;
    AnalogWDGConfig.LowThreshold 	= VBUS_HEX_MIN;
    AnalogWDGConfig.Channel 		= ADC_CHANNEL_8;
    AnalogWDGConfig.ITMode 			= ENABLE;
 
    if (HAL_ADC_AnalogWDGConfig(&ADC_Handle, &AnalogWDGConfig) != HAL_OK)
    {
        while (1);
    }
 
    /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
    */
    ADC_Config.Channel 		= ADC_CHANNEL_8;
    ADC_Config.Rank 		= 2;
    ADC_Config.SamplingTime = ADC_SAMPLETIME_3CYCLES;
    ADC_Config.Offset       = 0;
 
    if (HAL_ADC_ConfigChannel(&ADC_Handle, &ADC_Config) != HAL_OK)
    {
        while (1);
    }
 
    // 外设中断优先级配置和使能中断配置
    HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 4, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
 
    HAL_NVIC_SetPriority(ADC_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(ADC_IRQn);
 
    HAL_ADC_Start_DMA(&ADC_Handle, (uint32_t *)&adc_buff, 1024);
}

配置基本定时器6产生定时中断读取当前电路中驱动电机的电流值并执行PID运算
TIM_HandleTypeDef TIM_TimeBaseStructure;
 
/**
  * @brief  初始化基本定时器定时,默认50ms产生一次中断
  * @param  无
  * @retval 无
  */
void TIMx_Configuration(void)
{
    HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 1, 3);
    HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
	
	__TIM6_CLK_ENABLE();
	
	TIM_TimeBaseStructure.Instance = TIM6;
    TIM_TimeBaseStructure.Init.Period = 50 * 50 - 1;
    TIM_TimeBaseStructure.Init.Prescaler = 1680 - 1;
    TIM_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
    TIM_TimeBaseStructure.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&TIM_TimeBaseStructure);
 
    // 开启定时器更新中断
    HAL_TIM_Base_Start_IT(&TIM_TimeBaseStructure);
	
    uint32_t temp = (__HAL_TIM_GET_AUTORELOAD(&TIM_TimeBaseStructure) + 1) / 50.0;  // 计算周期,单位ms
    set_computer_value(SEND_PERIOD_CMD, CURVES_CH1, &temp, 1);  					// 给通道 1 发送目标值
}
 
/**
  * @brief  定时器更新事件回调函数
  * @param  无
  * @retval 无
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&TIM_TimeBaseStructure))
    {
        motor_pid_control();	// 每50ms执行一次PID运算
    }
}

配置定时器1输出PWM控制电机
TIM_HandleTypeDef  DCM_TimeBaseStructure;
 
/**
  * @brief  初始化控制通用定时器
  * @param  无
  * @retval 无
  */
void Motor_TIMx_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_OC_InitTypeDef  TIM_OCInitStructure;
 
	__HAL_RCC_GPIOA_CLK_ENABLE();
    __TIM1_CLK_ENABLE();
 
	// PA8--PWM_TIM_CH1
	GPIO_InitStruct.Pin 		= GPIO_PIN_8;
    GPIO_InitStruct.Mode 		= GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed 		= GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate 	= PWM_TIM_GPIO_AF;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
	// PA9--PWM_TIM_CH2
    GPIO_InitStruct.Pin 		= GPIO_PIN_9;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
	// TIM1 66.7us一次周期
    DCM_TimeBaseStructure.Instance = TIM1;
    DCM_TimeBaseStructure.Init.Period = 5600 - 1;
    DCM_TimeBaseStructure.Init.Prescaler = 1 - 1;
    DCM_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
    DCM_TimeBaseStructure.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&DCM_TimeBaseStructure);
 
    /*PWM模式配置*/
    TIM_OCInitStructure.OCMode = TIM_OCMODE_PWM1;
    TIM_OCInitStructure.Pulse = 0;
    TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;
    TIM_OCInitStructure.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    TIM_OCInitStructure.OCIdleState = TIM_OCIDLESTATE_SET;
    TIM_OCInitStructure.OCNIdleState = TIM_OCNIDLESTATE_RESET;
 
    /*配置PWM通道*/
    HAL_TIM_PWM_ConfigChannel(&DCM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_1);
    /*开始输出PWM*/
    HAL_TIM_PWM_Start(&DCM_TimeBaseStructure, TIM_CHANNEL_1);
 
    /*配置PWM通道*/
    HAL_TIM_PWM_ConfigChannel(&DCM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_2);
    /*开始输出PWM*/
    HAL_TIM_PWM_Start(&DCM_TimeBaseStructure, TIM_CHANNEL_2);
}
 
/**
  * @brief  设置TIM通道的占空比
	* @param  channel		通道	(1,2,3,4)
	* @param  compare		占空比
	*	@note 	无
  * @retval 无
  */
void TIM1_SetPWM_pulse(uint32_t channel, int compare)
{
    switch (channel)
    {
        case TIM_CHANNEL_1:
            __HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_1, compare);
            break;
 
        case TIM_CHANNEL_2:
            __HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_2, compare);
            break;
 
        case TIM_CHANNEL_3:
            __HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_3, compare);
            break;
 
        case TIM_CHANNEL_4:
            __HAL_TIM_SET_COMPARE(&DCM_TimeBaseStructure, TIM_CHANNEL_4, compare);
            break;
    }
}

配置定时器3读取编码器的计数值
TIM_HandleTypeDef  DCM_TimeBaseStructure;
 
/**
  * @brief  初始化控制通用定时器
  * @param  无
  * @retval 无
  */
void Motor_TIMx_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_OC_InitTypeDef  TIM_OCInitStructure;
 
	__HAL_RCC_GPIOA_CLK_ENABLE();
    __TIM1_CLK_ENABLE();
 
	// PA8--PWM_TIM_CH1
	GPIO_InitStruct.Pin 		= GPIO_PIN_8;
    GPIO_InitStruct.Mode 		= GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed 		= GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate 	= PWM_TIM_GPIO_AF;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
	// PA9--PWM_TIM_CH2
    GPIO_InitStruct.Pin 		= GPIO_PIN_9;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
	// TIM1 66.7us一次周期
    DCM_TimeBaseStructure.Instance = TIM1;
    DCM_TimeBaseStructure.Init.Period = 5600 - 1;
    DCM_TimeBaseStructure.Init.Prescaler = 1 - 1;
    DCM_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
    DCM_TimeBaseStructure.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&DCM_TimeBaseStructure);
 
    /*PWM模式配置*/
    TIM_OCInitStructure.OCMode = TIM_OCMODE_PWM1;
    TIM_OCInitStructure.Pulse = 0;
    TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;
    TIM_OCInitStructure.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    TIM_OCInitStructure.OCIdleState = TIM_OCIDLESTATE_SET;
    TIM_OCInitStructure.OCNIdleState = TIM_OCNIDLESTATE_RESET;
 
    /*配置PWM通道*/
    HAL_TIM_PWM_ConfigChannel(&DCM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_1);
    /*开始输出PWM*/
    HAL_TIM_PWM_Start(&DCM_TimeBaseStructure, TIM_CHANNEL_1);
 
    /*配置PWM通道*/
    HAL_TIM_PWM_ConfigChannel(&DCM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_2);
    /*开始输出PWM*/
    HAL_TIM_PWM_Start(&DCM_TimeBaseStructure, TIM_CHANNEL_2);
}
 
/**
  * @brief  定时器更新事件回调函数
  * @param  无
  * @retval 无
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&TIM_EncoderHandle))
    {
        /* 判断当前计数器计数方向 */
        if (__HAL_TIM_IS_TIM_COUNTING_DOWN(&TIM_EncoderHandle))
            /* 下溢 */
        {
            Encoder_Overflow_Count--;
        }
        else
            /* 上溢 */
        {
            Encoder_Overflow_Count++;
        }
    }
}

ADC数据处理
static uint16_t flag_num = 0;
/**
  * @brief  常规转换在非阻塞模式下完成回调
  * @param  hadc: ADC  句柄.
  * @retval 无
  */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    uint32_t adc_mean = 0;
 
    HAL_ADC_Stop_DMA(hadc);       // 停止 ADC 采样,处理完一次数据在继续采样
 
    /* 计算电流通道采样的平均值 */
    for (uint32_t count = 0; count < 1024; count += 2)
    {
        adc_mean += (uint32_t)adc_buff[count];
    }
 
    adc_mean_sum += adc_mean / (1024 / 2);    // 保存平均值
    adc_mean_count++;
 
    adc_mean = 0;
 
    /* 计算电压通道采样的平均值 */
    for (uint32_t count = 1; count < 1024; count += 2)
    {
        adc_mean += (uint32_t)adc_buff[count];
    }
 
    vbus_adc_mean = adc_mean / (1024 / 2);    // 保存平均值
 
    HAL_ADC_Start_DMA(&ADC_Handle, (uint32_t *)&adc_buff, 1024); // 开始 ADC 采样
}
 
/**
  * @brief  在非阻塞模式模拟看门狗回调
  * @param  hadc: ADC  句柄.
  * @retval 无
  */
void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef *hadc)
{
    float temp_adc;
 
    flag_num++;     	// 电源电压超过阈值电压
 
    temp_adc = get_vbus_val();
 
    if (temp_adc > VBUS_MIN && temp_adc < VBUS_MAX)
    {
        flag_num = 0;
    }
 
    if (flag_num > 10) 	// 电源电压超过阈值电压10次
    {
        set_motor_disable();
        flag_num = 0;
        printf("电源电压超过限制!请检查原因,复位开发板在试!\r\n");
 
        while (1);
    }
}
 
/**
  * @brief  获取电流值(应定时调用)
  * @param  无
  * @retval 转换得到的电流值
  */
int32_t get_curr_val(void)
{
    static uint8_t flag = 0;
    static uint32_t adc_offset = 0;    				// 偏置电压
    int16_t curr_adc_mean = 0;         				// 电流 ACD 采样结果平均值
 
    curr_adc_mean = adc_mean_sum / adc_mean_count;  // 保存平均值
 
    adc_mean_count = 0;
    adc_mean_sum = 0;
 
    if (flag < 17 && is_motor_en == 0)				//	仅在电机未启动时记录
    {
        adc_offset = curr_adc_mean;    				// 多次记录偏置电压,待系统稳定偏置电压才为有效值
        flag += 1;
    }
 
    if (curr_adc_mean >= adc_offset)
    {
        curr_adc_mean -= adc_offset;                // 减去偏置电压
    }
    else
    {
        curr_adc_mean = 0;
    }
 
	float vdc = (float)curr_adc_mean/(float)65536 * 3.3f;	// 获取电压值
    return (float)vdc / 8.0f / 0.02f * 1000.0f;
}
 
/**
  * @brief  获取电源电压值
  * @param  无
  * @retval 转换得到的电流值
  */
float get_vbus_val(void)
{
    float vdc = (float)vbus_adc_mean/(float)65536 * 3.3f;	// 获取电压值
    return ((float)vdc - (float)1.24) * (float)37.0;		// 电源电压值(测量电压是电源电压的1/37)
}

编写位置式PID算法
typedef struct
{
    float target_val;	// 目标值
    float actual_val;	// 实际值
    float err;       	// 定义偏差值
    float err_last;  	// 定义上一个偏差值
    float Kp,Ki,Kd;  	// 定义比例、积分、微分系数
    float integral;  	// 定义积分值
}_pid;
_pid pid_location;		// 位置环控制
_pid pid_curr;			// 电流环控制
_pid pid_speed;			// 速度环控制

/**
  * @brief  PID参数初始化
	*	@note 	无
  * @retval 无
  */
void PID_param_init(void)
{
    /* 位置相关初始化参数 */
    pid_location.target_val = 0.0;
    pid_location.actual_val = 0.0;
    pid_location.err 		= 0.0;
    pid_location.err_last 	= 0.0;
    pid_location.integral 	= 0.0;
    pid_location.Kp 		= 0.011;
    pid_location.Ki 		= 0.0018;
    pid_location.Kd 		= 0.0;

    /* 速度相关初始化参数 */
    pid_speed.target_val 	= 100.0;
    pid_speed.actual_val 	= 0.0;
    pid_speed.err 			= 0.0;
    pid_speed.err_last 		= 0.0;
    pid_speed.integral 		= 0.0;
    pid_speed.Kp 			= 2.0;
    pid_speed.Ki 			= 0.02;
    pid_speed.Kd 			= 0.00;

    /* 电流相关初始化参数 */
    pid_curr.target_val 	= 80.0;
    pid_curr.actual_val 	= 0.0;
    pid_curr.err 			= 0.0;
    pid_curr.err_last 		= 0.0;
    pid_curr.integral 		= 0.0;
    pid_curr.Kp 			= 0.0;
    pid_curr.Ki 			= 3.5;
    pid_curr.Kd 			= 0.00;

    float pid_temp[3] = {pid_location.Kp, pid_location.Ki, pid_location.Kd};
    set_computer_value(SEND_P_I_D_CMD, CURVES_CH1, pid_temp, 3);     // 给通道 1 发送 P I D 值

    pid_temp[0] = pid_speed.Kp;
    pid_temp[1] = pid_speed.Ki;
    pid_temp[2] = pid_speed.Kd;
    set_computer_value(SEND_P_I_D_CMD, CURVES_CH2, pid_temp, 3);     // 给通道 2 发送 P I D 值

    pid_temp[0] = pid_curr.Kp;
    pid_temp[1] = pid_curr.Ki;
    pid_temp[2] = pid_curr.Kd;
    set_computer_value(SEND_P_I_D_CMD, CURVES_CH3, pid_temp, 3);     // 给通道 3 发送 P I D 值
}

/**
  * @brief  设置目标值
  * @param  val		目标值
	*	@note 	无
  * @retval 无
  */
void set_pid_target(_pid *pid, float temp_val)
{
    pid->target_val = temp_val;    // 设置当前的目标值
}

/**
  * @brief  获取目标值
  * @param  无
	*	@note 	无
  * @retval 目标值
  */
float get_pid_target(_pid *pid)
{
    return pid->target_val;    // 设置当前的目标值
}

/**
  * @brief  设置比例、积分、微分系数
  * @param  p:比例系数 P
  * @param  i:积分系数 i
  * @param  d:微分系数 d
	*	@note 	无
  * @retval 无
  */
void set_p_i_d(_pid *pid, float p, float i, float d)
{
    pid->Kp = p;    // 设置比例系数 P
    pid->Ki = i;    // 设置积分系数 I
    pid->Kd = d;    // 设置微分系数 D
}

/**
  * @brief  位置环位置式PID算法实现
  * @param  actual_val:实际值
  *	@note 	无
  * @retval 通过PID计算后的输出
  */
float location_pid_realize(_pid *pid, float actual_val)
{
    /*计算目标值与实际值的误差*/
    pid->err = pid->target_val - actual_val;

    /* 限定闭环死区 */
    if ((pid->err >= -40) && (pid->err <= 40))
    {
        pid->err = 0;
        pid->integral = 0;
    }

    /* 积分分离,偏差较大时去掉积分作用 */
    if (pid->err > -1500 && pid->err < 1500)
    {
        pid->integral += pid->err;    // 误差累积

        /* 限定积分范围,防止积分饱和 */
        if (pid->integral > 4000)
        {
            pid->integral = 4000;
        }
        else if (pid->integral < -4000)
        {
            pid->integral = -4000;
        }
    }

    /*PID算法实现*/
    pid->actual_val = pid->Kp * pid->err + pid->Ki * pid->integral + pid->Kd * (pid->err - pid->err_last);

    /*误差传递*/
    pid->err_last = pid->err;

    /*返回当前实际值*/
    return pid->actual_val;
}

/**
  * @brief  速度环位置式PID算法实现
  * @param  actual_val:实际值
  *	@note 	无
  * @retval 通过PID计算后的输出
  */
float speed_pid_realize(_pid *pid, float actual_val)
{
    /*计算目标值与实际值的误差*/
    pid->err = pid->target_val - actual_val;

    if ((pid->err < 0.2f) && (pid->err > -0.2f))
    {
        pid->err = 0.0f;
    }

    pid->integral += pid->err;    // 误差累积

    /*PID算法实现*/
    pid->actual_val = pid->Kp * pid->err + pid->Ki * pid->integral + pid->Kd * (pid->err - pid->err_last);

    /*误差传递*/
    pid->err_last = pid->err;

    /*返回当前实际值*/
    return pid->actual_val;
}

/**
  * @brief  电流环位置式PID算法实现
  * @param  actual_val:实际值
	*	@note 	无
  * @retval 通过PID计算后的输出
  */
float curr_pid_realize(_pid *pid, float actual_val)
{
    /*计算目标值与实际值的误差*/
    pid->err = pid->target_val - actual_val;

    pid->integral += pid->err;    // 误差累积

    /* 限定积分范围,防止积分饱和 */
    if (pid->integral > 2000)
    {
        pid->integral = 2000;
    }
    else if (pid->integral < -2000)
    {
        pid->integral = -2000;
    }

    /*PID算法实现*/
    pid->actual_val = pid->Kp * pid->err + pid->Ki * pid->integral + pid->Kd * (pid->err - pid->err_last);

    /*误差传递*/
    pid->err_last = pid->err;

    /*返回当前实际值*/
    return pid->actual_val;
}

#define TARGET_CURRENT_MAX    200    // 目标电流的最大值 mA
#define TARGET_SPEED_MAX      200    // 目标速度的最大值 r/m

/**
  * @brief  电机位置式 PID 控制实现(定时调用)
  * @param  无
  * @retval 无
  */
void motor_pid_control(void)
{
    static uint32_t louter_ring_timer = 0;      // 外环环周期(电流环计算周期为定时器周期T,速度环为2T,位置环为3T)
    int32_t actual_current = get_curr_val();    // 读取当前电流值

    if (actual_current > TARGET_CURRENT_MAX)
    {
        actual_current = TARGET_CURRENT_MAX;
    }

    if (is_motor_en == 1)                  		// 电机在使能状态下才进行控制处理
    {
        static int32_t Capture_Count = 0;    	// 当前时刻总计数值
        static int32_t Last_Count = 0;       	// 上一时刻总计数值
        float cont_val = 0;                  	// 当前控制值

        /* 当前时刻总计数值 = 计数器值 + 计数溢出次数 * ENCODER_TIM_PERIOD  */
        Capture_Count = __HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (Encoder_Overflow_Count * 65535);

        /* 位置环计算 */
        if (louter_ring_timer++ % 3 == 0)
        {
            cont_val = location_pid_realize(&pid_location, Capture_Count);    // 进行 PID 计算

            /* 目标速度上限处理 */
            if (cont_val > TARGET_SPEED_MAX)
            {
                cont_val = TARGET_SPEED_MAX;
            }
            else if (cont_val < -TARGET_SPEED_MAX)
            {
                cont_val = -TARGET_SPEED_MAX;
            }

            set_pid_target(&pid_speed, cont_val);    // 设定速度的目标值

            int32_t temp = cont_val;
            set_computer_value(SEND_TARGET_CMD, CURVES_CH2, &temp, 1);     // 给通道 2 发送目标值
        }

        /* 速度环计算 */
        static int32_t actual_speed = 0;             // 实际测得速度

        if (louter_ring_timer % 2 == 0)
        {
            /* 转轴转速 = 单位时间内的计数值 / 编码器总分辨率 * 时间系数  */
            actual_speed = ((float)(Capture_Count - Last_Count) / 16 * 4 / 30) / (GET_BASIC_TIM_PERIOD() * 2 / 1000.0 / 60.0);

            /* 记录当前总计数值,供下一时刻计算使用 */
            Last_Count = Capture_Count;

            cont_val = speed_pid_realize(&pid_speed, actual_speed);    		// 进行 PID 计算

            if (cont_val > 0)    					// 判断电机方向
            {
                set_motor_direction(MOTOR_FWD);
            }
            else
            {
                cont_val = -cont_val;
                set_motor_direction(MOTOR_REV);
            }

            cont_val = (cont_val > TARGET_CURRENT_MAX) ? TARGET_CURRENT_MAX : cont_val;    // 电流上限处理
            set_pid_target(&pid_curr, cont_val);    						// 设定电流的目标值

            int32_t temp = cont_val;
            set_computer_value(SEND_TARGET_CMD, CURVES_CH3, &temp, 1);     	// 给通道 3 发送目标值
        }

        /* 电流环计算 */
        cont_val = curr_pid_realize(&pid_curr, actual_current);    			// 进行 PID 计算

        if (cont_val < 0)
        {
            cont_val = 0;    	// 下限处理
        }
        else if (cont_val > 5500)
        {
            cont_val = 5500;	// 速度上限处理
        }

        set_motor_speed(cont_val);                                                 // 设置 PWM 占空比

        set_computer_value(SEND_FACT_CMD, CURVES_CH1, &Capture_Count,  1);         // 给通道 1 发送实际值
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1249252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nodejs微信小程序+python+PHP-书吧租阅管理系统的设计与实现-安卓-计算机毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

canvas高级动画001:文字瀑布流

canvas实例应用100 专栏提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。 canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重要的帮助。 文章目录 示例…

什么是强化学习

1 概况 1.1 定义 强化学习&#xff08;Reinforcement Learning, RL&#xff09;是机器学习的一个重要分支&#xff0c;与监督学习和无监督学习并列。它主要涉及智能体&#xff08;agent&#xff09;在环境中通过学习如何做出决策。与监督学习的主动指导和无监督学习的数据探索…

volatile 关键字的作用是什么?它的实现原理是什么?

文章目录 volatile 关键字的作用是什么&#xff1f;它的实现原理是什么&#xff1f; 今天来聊一聊 volatile 这个关键字在java中的作用是什么&#xff0c;经常看到却不知道原理是什么&#xff0c;今天就带大家看看&#xff0c;开干。。。。 volatile 关键字的作用是什么&#x…

鸿蒙4.0开发笔记之DevEco Studio如何使用低代码开发模板进行开发的详细流程(六)

鸿蒙低代码开发 一、什么是低代码二、如何进行鸿蒙低代码开发1、 创建低代码开发工程&#xff08;方式壹&#xff09;2、已有工程则创建Visual文件&#xff08;方拾贰&#xff09; 三、低代码开发界面介绍四、低代码实现页面跳转五、低代码开发建议 一、什么是低代码 所谓低代码…

点大商城V2.5.3分包小程序端+小程序上传提示限制分包制作教程

这几天很多播播资源会员反馈点大商城V2.5.3小程序端上传时提示大小超限&#xff0c;官方默认单个包都不能超过2M&#xff0c;总分包不能超20M。如下图提示超了93KB&#xff0c;如果出现超的不多情况下可采用手动删除一些images目录下不使用的图片&#xff0c;只要删除超过100KB…

导入PIL时报错

在导入PIL时,报以下错误: 查找原因 参考博客 Could not find a version that satisfies the requirement PIL (from versions: ) No matching distributi-CSDN博客,按照wheel后,安装PIL时,报如下的错误。 查找说是python版本与wheel文件版本不同,确认本机python版本 …

仿 美图 / 饿了么,店铺详情页功能

前言 UI有所不同&#xff0c;但功能差不多&#xff0c;商品添加购物车功能 正在写&#xff0c;写完会提交仓库。 效果图一&#xff1a;左右RecyclerView 联动 效果图二&#xff1a;通过点击 向上偏移至最大值 效果图三&#xff1a;通过点击 或 拖动 展开收缩公告 效果图四&…

晨控CK-FR03-EIP读卡器与欧姆龙NX/NJ系列EtherNet/IP通讯手册

晨控CK-FR03-EIP读卡器与欧姆龙NX/NJ系列EtherNet/IP通讯手册 CK-FR03-EIP是一款基于射频识别技术的高频RFID标签读卡器&#xff0c;读卡器工作频率为13.56MHZ&#xff0c;支持对I-CODE 2、I-CODE SLI等符合ISO15693国际标准协议格式标签的读取。 读卡器同时支持标准工业通讯…

如何用低代码的思路设计文字描边渐变组件

前言 文字特效设计一直是困扰 Web 前端 Css 世界多年的问题, 比如如何用纯 Css 实现文字描边, 渐变, 阴影等, 由于受限于浏览器兼容性的问题, 我们不得不使用其他替代方案来实现. 平时工作中我们使用 PS 等设计工具能很容易的实现文字渐变等特效, 但是随着可视化技术的成熟, 我…

《Effective Modern C++》全书内容提炼总结

个人博客地址: https://cxx001.gitee.io 前言 C程序员都应该是对性能执着的人&#xff0c;想要彻底理解C11和C14&#xff0c;不可止步于熟悉它们引入的语言特性&#xff08;例如&#xff0c;auto型别推导、移动语义、lambda表达式&#xff0c;以及并发支持&#xff09;。挑战在…

Python开发运维:Django 4.2.7 使用Celery 5.3.5 完成异步和定时任务

目录 一、实验 1.Django使用Celery完成异步和定时任务 二、实验 1. 如何查看Django版本 一、实验 1.Django使用Celery完成异步和定时任务 (1)安装Django (2)新建Django项目 (3)初始框架 (4)urls.py引用视图views from django.contrib import admin from django.urls imp…

MATLAB实战 | MEX文件

应用接口是MATLAB与其他语言相互调用各自函数的方法&#xff0c;MEX文件使MATLAB程序中可以调用或链接其他语言编写的函数&#xff0c;而MATLAB引擎使其他语言程序中可以调用MATLAB函数。 01、MEX文件 MEX是MATLAB Executable的缩写&#xff0c;是MATLAB中用于调用其他语言编写…

UniWebView 版本3 版本4 版本5介绍

一、介绍 UniWebView是iOS/Android上的web视图组件的包装器&#xff0c;所以运行时拥有与原生web相似性能。是针对Unity所写的插件&#xff0c;节省了项目的开发时间。 官网地址&#xff1a;UniWebView 二、下载&使用 1、下载 &#xff08;1&#xff09;、Unity Asset …

electron windows robotjs 安装教程

Robotjs 安装 前言第一步 : 安装python第二步 : 安装Visual Studio 2022第三步 : 安装robotjs 前言 robotjs可以控制鼠标键盘&#xff0c;获取屏幕内容&#xff0c;配合electron可做很多自动化操作。windows下配置环境有很多坑&#xff0c;很多文章都太旧了。试了很多次发现了…

线程-Thread类及常见方法

目录 一、创建线程 1.继承 Thread 类 2. 实现 Runnable 接口 3.匿名内部类创建 Thread 子类对象 4. 匿名内部类创建 Runnable 子类对象 5. lambda 表达式创建 Runnable 子类对象 二、Thread 类及常见方法 2.1 Thread 的常见构造方法 2.2 Thread 的几个常见属性 2.3 启…

nodejs微信小程序+python+PHP-青云商场管理系统的设计与实现-安卓-计算机毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

82基于matlab GUI的图像处理

基于matlab GUI的图像处理&#xff0c;功能包括图像一般处理&#xff08;灰度图像、二值图&#xff09;&#xff1b;图像几何变换&#xff08;旋转可输入旋转角度、平移、镜像&#xff09;、图像边缘检测&#xff08;拉普拉斯算子、sobel算子、wallis算子、roberts算子&#xf…

nodejs+vue+python+PHP+微信小程序-青云商场管理系统的设计与实现-安卓-计算机毕业设计

研究步骤、措施&#xff1a; &#xff08;1&#xff09;与指导老师确定系统主要功能&#xff1b; &#xff08;2&#xff09;做需求分析及功能模块划分&#xff1b; &#xff08;3&#xff09;指导老师通过后&#xff0c;设计出用例图&#xff0c;E-R图&#xff0c;功能模块图 …

Python基础:JSON保存结构化数据(详解)

1. JSON概念 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;也易于机器解析和生产。   虽然JSON使用JavaScript语法来描述数据对象&#xff0c;但是JSON仍然独立于语言和平台&#xff0c;JSON解…