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

news2024/12/26 23:49:45

目录

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

硬件设计

直流电机电流环控制-位置式PID实现

编程要点

配置ADC可读取电流值

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

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

ADC数据处理

编写位置式PID算法

直流电机电流环控制-增量式PID实现

编程要点

配置ADC可读取电流值

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

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

ADC数据处理

编写增量式PID算法


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

利用直流有刷驱动板来完成对电流的采集,最终实现电流环的闭环控制。

在一些场景中想让电机吊起超出电机能力的重物(即超载),但电机的能力有限,电机长期超载工作会严重损坏电机。如果想合理利用电机的性能,就需要控制电流的输出,所以需要电流环的控制。

硬件设计

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

直流电机电流环控制-位置式PID实现

编程要点

配置ADC可读取电流值

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

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

编写位置式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;
    }
}

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;

/**
  * @brief  PID参数初始化
	*	@note 	无
  * @retval 无
  */
void PID_param_init()
{
    /* 初始化参数 */
    pid.target_val 	= 40.0;
    pid.actual_val 	= 0.0;
    pid.err 		= 0.0;
    pid.err_last 	= 0.0;
    pid.integral 	= 0.0;
    pid.Kp 			= 0;
    pid.Ki 			= 3.5;
    pid.Kd 			= 0;

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

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

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

/**
  * @brief  设置比例、积分、微分系数
  * @param  p:比例系数 P
  * @param  i:积分系数 i
  * @param  d:微分系数 d
	*	@note 	无
  * @retval 无
  */
void set_p_i_d(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 PID_realize(float actual_val)
{
    /* 限制电流幅值,野火电机空载时最大电流在100ma左右,如果过大容易积分饱和 */
    if (pid.target_val >= 120)
    {
        pid.target_val = 120.0;
    }
    else if (pid.target_val <= 5)
    {
        pid.target_val = 0.0;
    }

    /*计算目标值与实际值的误差*/
    pid.err = pid.target_val - actual_val;

    /*误差累积*/
    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  定时器每50ms产生一次中断回调函数
  * @param  htim:定时器句柄
  * @retval 无
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&TIM_TimeBaseStructure))
    {
        motor_pid_control();
    }
}

/**
  * @brief  电机位置式 PID 控制实现(定时调用)
  * @param  无
  * @retval 无
  */
void motor_pid_control(void)
{
    int32_t actual_current = get_curr_val();    // 读取当前电流值

    if (is_motor_en == 1)     					// 电机在使能状态下才进行控制处理
    {
        float cont_val = 0;                     // 当前控制值

        cont_val = PID_realize(actual_current); // 进行 PID 计算

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

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

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

直流电机电流环控制-增量式PID实现

编程要点

配置ADC可读取电流值

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

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

编写增量式PID算法

编写电流控制函数

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

编写按键控制代码

配置ADC可读取电流值

同上。

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

同上。

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

同上。

ADC数据处理

同上。

编写增量式PID算法
typedef struct
{
	float target_val; 	//目标值
	float actual_val; 	//实际值
	float err;        	//定义当前偏差值
	float err_next;   	//定义下一个偏差值
	float err_last;   	//定义最后一个偏差值
	float Kp, Ki, Kd; 	//定义比例、积分、微分系数
}_pid;
_pid pid;

/**
  * @brief  PID参数初始化
	*	@note 	无
  * @retval 无
  */
void PID_param_init()
{
    /* 初始化参数 */
    pid.target_val 	= 80;
    pid.actual_val 	= 0.0;
    pid.err 		= 0.0;
    pid.err_last	= 0.0;
    pid.err_next	= 0.0;
    pid.Kp 			= 0;
    pid.Ki 			= 2.8;
    pid.Kd 			= 0;

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

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

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

/**
  * @brief  设置比例、积分、微分系数
  * @param  p:比例系数 P
  * @param  i:积分系数 i
  * @param  d:微分系数 d
	*	@note 	无
  * @retval 无
  */
void set_p_i_d(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 PID_realize(float actual_val)
{
    /*计算目标值与实际值的误差*/
    pid.err = pid.target_val - actual_val;
	
    /*PID算法实现*/
    pid.actual_val += pid.Kp * (pid.err - pid.err_next) + pid.Ki * pid.err + pid.Kd * (pid.err - 2 * pid.err_next + pid.err_last);

    /* 限幅输出,防止深度饱和 */
    if (pid.actual_val > 5600)
    {
        pid.actual_val = 5600;
    }
    else if (pid.actual_val <= 0)
    {
        pid.actual_val = 0;
    }

    /*传递误差*/
    pid.err_last = pid.err_next;
    pid.err_next = pid.err;
	
    /*返回当前实际值*/
    return pid.actual_val;
}

/**
  * @brief  定时器每50ms产生一次中断回调函数
  * @param  htim:定时器句柄
  * @retval 无
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&TIM_TimeBaseStructure))
    {
        motor_pid_control();
    }
}

/**
  * @brief  电机增量式 PID 控制实现(定时调用)
  * @param  无
  * @retval 无
  */
void motor_pid_control(void)
{
    int32_t actual_current = get_curr_val();    // 读取当前电流值

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

        cont_val = PID_realize(actual_current); // 进行 PID 计算

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

        cont_val = (cont_val > PWM_MAX_PERIOD_COUNT) ? PWM_MAX_PERIOD_COUNT : cont_val;    	// 速度上限处理
        set_motor_speed(cont_val);                                                 			// 设置 PWM 占空比

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

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

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

相关文章

jquery实现:多个输入框字数统计示例

一、实现效果&#xff1a; 二、代码实现&#xff1a; 每个输入框添加了相同的类名 inputField 并且每个输入框旁边的字数统计使用相同的类名 charCount。 然后使用 jQuery 的类选择器 $(‘.inputField’) 来选中所有具有该类名的输入框&#xff0c;并为它们绑定了 input 事件。…

【学历是敲门砖】如果你想有个好的起点,不妨冲一冲计算机考研,这本书将会助你一臂之力

目录 计算机考研难点 《计算机考研精炼1000题》揭秘问答 1. 为什么是1000题&#xff1f; 2. 有什么优势&#xff1f; 3. 编写团队水平如何&#xff1f; 4. 题目及解析品质如何&#xff1f;可以试读吗&#xff1f; 购买链接 高质量的学习提升圈子 京东热卖下单链接&…

本地MinIO存储服务通过Java程序结合Cpolar内网穿透进行远程访问

[本地MinIO存储服务通过Java程序结合Cpolar内网穿透进行远程访问] 前言 MinIO是一款高性能、分布式的对象存储系统&#xff0c;它可以100%的运行在标准硬件上&#xff0c;即X86等低成本机器也能够很好的运行MinIO。它的优点包括高性能、高可用性、易于部署和管理、支持多租户…

C++中在一个cpp文件中引用另外一个cpp文件的方法

C中在一个cpp文件中引用另外一个cpp文件 可以通过导入cpp文件或者.h文件来实现&#xff0c; 类似python中的import 导入 下面距离说明下 创建1个func1.cpp 内容如下&#xff1a; #include<iostream> using namespace std;int sum (int num1, int num2) {return (num1…

Nginx反向代理实现负载均衡+Keepalive实现高可用

目录 实现负载均衡 实现高可用 实现负载均衡 Nginx的几种负载均衡算法&#xff1a; 1.轮询&#xff08;默认&#xff09; 每个请求按照时间顺序逐一分配到下游的服务节点&#xff0c;如果其中某一节点故障&#xff0c;nginx 会自动剔除故障系统使用户使用不受影响。 2.权重…

珠江啤酒坚持创新,“酿”造电子化采购平台

珠江啤酒 广州珠江啤酒股份有限公司&#xff08;简称“珠江啤酒”&#xff09;是一家以啤酒酿造产业和啤酒文化产业“双主业”协同发展、包装产业配套发展的大型现代化国有控股企业。2010年&#xff0c;在深交所上市&#xff0c;下属企业16家&#xff0c;其中啤酒企业12家&…

UE5 UI教程学习笔记

参考资料&#xff1a;https://item.taobao.com/item.htm?spma21n57.1.0.0.2b4f523cAV5i43&id716635137219&ns1&abbucket15#detail 基础工程&#xff1a;https://download.csdn.net/download/qq_17523181/88559312 1. 介绍 工程素材 2. 创建Widget UE5 UI系统的…

C语言运算符详解

详细介绍了C语言表达式、算术运算符、赋值运算符、关系运算符、条件结构、逻辑运算符、位运算符的语法和使用方法&#xff0c;并讨论了运算符的优先级。 1、表达式与算术运算符 在C语言中&#xff0c;表达式是一个类似数学中的算式&#xff0c;表达式由变量、字面值、常量、运…

前端开发工具集合

文章目录 Visual Studio Code (VS Code)安装及配置一、安装二、常用插件三、相关配置四、统一配置代码 Visual Studio Code (VS Code)安装及配置 一、安装 下载地址&#xff1a;https://code.visualstudio.com/ ?> VS Code 下载慢&#xff0c;解决办法请点击 双击下载文件…

@JsonCreator和@JsonValue

文章目录 1、正常反序列化的过程2、JsonCreator3、JsonValue4、应用&#xff1a;枚举类中校验传参以及优化前后端数据交互5、补充&#xff1a;ConstructorProperties 1、正常反序列化的过程 反序列化时&#xff0c;默认会调用实体类的无参构造来实例化一个对象&#xff0c;然后…

二叉树题目:具有所有最深结点的最小子树

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;具有所有最深结点的最小子树 出处&#xff1a;865. 具有所有最深结点的最小子树 难度 5 级 题目描述 要求 给定…

web前端开发基础------外边距折叠现象

引言 在设置样式时&#xff0c;需要遵循先整体再细节&#xff0c;先通用样式再特殊样式的顺序进行设置 一&#xff0c;什么是外边距折叠现象呢&#xff1f; 外边距折叠 定义&#xff1a; 外边距折叠是指相邻的两个或者多个外边距&#xff08;margin&#xff09;在垂直方向会合并…

分布式锁之基于mysql实现分布式锁(四)

不管是jvm锁还是mysql锁&#xff0c;为了保证线程的并发安全&#xff0c;都提供了悲观独占排他锁。所以独占排他也是分布式锁的基本要求。 可以利用唯一键索引不能重复插入的特点实现。设计表如下&#xff1a; CREATE TABLE tb_lock (id bigint(20) NOT NULL AUTO_INCREMENT,…

最小二乘线性回归

​ 线性回归&#xff08;linear regression&#xff09;&#xff1a;试图学得一个线性模型以尽可能准确地预测实际值的输出。 以一个例子来说明线性回归&#xff0c;假设银行贷款会根据 年龄 和 工资 来评估可放款的额度。即&#xff1a; ​ 数据&#xff1a;工资和年龄&…

企业必看的大数据安全极速传输解决方案

在这个大数据时代&#xff0c;企业在享受大数据带来的便利同时&#xff0c;也面临着巨大的挑战&#xff0c;其中最主要的问题就是数据安全方面和传输方面&#xff0c;为了更好地满足企业大数据传输的需求&#xff0c;小编将深入分析企业对于大数据传输面临的挑战和风险以及大数…

[PTQ]均匀量化和非均匀量化

均匀量化和非均匀量化 基本概念 量化出发点&#xff1a;使用整型数据类型代替浮点数据&#xff0c;从而节省存储空间同时加快推理速度。量化基本形式 均匀量化&#xff1a;浮点线性映射到定点整型上&#xff0c;可以根据scale/offset完成量化/反量化操作。非均匀量化 PowersO…

containerd Snapshots功能解析

containerd Snapshots功能解析 snapshot是containerd的一个核心功能&#xff0c;用于创建和管理容器的文件系统。 本篇containerd版本为v1.7.9。 本文以 ctr i pull命令为例&#xff0c;分析containerd的snapshot “创建” 相关的功能。 ctr命令 ctr image相关命令的实现在cmd…

OpenAI“宫斗”新进展!Sam Altman将重返OpenAI担任首席执行官 董事会成员改动

在经过激烈的五天讨论和辩论之后&#xff0c;高调人工智能初创公司OpenAI宣布&#xff0c;其联合创始人之一Sam Altman将回归担任首席执行官。这一决定是对上周Altman突然被解雇的回应&#xff0c;该决定引起了极大的关注和讨论。 OpenAI表示&#xff0c;他们已经达成了与Altm…

低代码平台推荐:五大低代码厂商谁的模式更“合适”

随着数字化时代的到来&#xff0c;低代码开发平台作为提高数字生产力的工具正受到越来越多企业的关注&#xff0c;市面上的低代码产品和厂商更是“乱花渐欲迷人眼”。 各家产品不仅功能各有不同&#xff0c;甚至商机都有区别的情况&#xff0c;如何做好产品选型已然成了采购企…

2023年国内好用的企业网盘推荐

2023年企业网盘俨然已经成为了各个企业团队的标配了&#xff0c;那么2023年国内有什么好用的企业网盘吗&#xff1f;2023国内哪个企业网盘好用&#xff1f; 国内哪个企业网盘好用&#xff1f; 由于不同行业企业对于企业网盘的具体需求点不同&#xff0c;因此我们无法从功能上评…