电机应用-直流有刷电机多环控制实现
目录
直流有刷电机多环控制实现
硬件设计
直流电机三环(速度环、电流环、位置环)串级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; // 计算周期,单位msset_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_CH1GPIO_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_CH2GPIO_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_CH1GPIO_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_CH2GPIO_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; // 设置比例系数 Ppid->Ki = i; // 设置积分系数 Ipid->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 发送实际值}
}
相关文章:
电机应用-直流有刷电机多环控制实现
目录 直流有刷电机多环控制实现 硬件设计 直流电机三环(速度环、电流环、位置环)串级PID控制-位置式PID 编程要点 配置ADC可读取电流值 配置基本定时器6产生定时中断读取当前电路中驱动电机的电流值并执行PID运算 配置定时器1输出PWM控制电机 配…...
Java常量池理论篇:Class常量池、运行时常量池、String常量池、基本类型常量池,intern方法1.6、1.7的区别
文章目录 Class常量池运行时常量池String常量池基本类型常量池Integer 常量池Long 常量池 加餐部分 Class常量池 每个Class字节码文件中包含类常量池用来存放字面量以及符号引用等信息。 运行时常量池 java文件被编译成class文件之后,也就是会生成我上面所说的 …...
module java.base does not “opens java.io“ to unnamed module
环境 如上图所示, Runtime version的版本是JAVA 17 项目所需要JDK版本为JAVA 8 解决...
鸿蒙原生应用/元服务开发-AGC分发如何配置签名信息
使用制作的私钥(.p12)文件、在AGC申请的证书文件和Profile(.p7b)文件,在DevEco Studio配置工程的签名信息,以构建携带发布签名信息的APP。 1.打开DevEco Studio,菜单选择“File > Project S…...
【HTML5-webscoket实时通信(web)】
websocket是什么? 就是用来创建网络聊天室,实时通信websocket的方法有哪些? https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets如何实现:(以下实现流程) 前端: // 直播中// 聊天web…...
如何在Android平板上远程连接Ubuntu服务器code-server进行代码开发?
文章目录 1.ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址6.结语 1.ubuntu本地安装code-server 准备一台虚拟机,Ubuntu或者centos都可以,这里以VMwhere ubuntu系统为例 下载code serve…...
SAP Smartforms打印报错Error in spool C call : spool overflow
处理方式: SAP打印时提示: Error in spool C call : spool overflow (假脱机请求溢出,通俗一点打印池已满) 解决办法: SE38 首先运行程序RSPO1041 再运行RSPO1043,话不多说上图。...
SQL 中的运算符与别名:使用示例和语法详解
SQL中的IN运算符 IN运算符允许您在WHERE子句中指定多个值,它是多个OR条件的简写。 示例:获取您自己的SQL Server 返回所有来自’Germany’、France’或’UK’的客户: SELECT * FROM Customers WHERE Country IN (Germany, France, UK);语…...
3.2 CPU的自动化
CPU的自动化 改造1-使用2进制导线改造2根据整体流程开始改造指令分析指令MOV_A的开关2进制表格手动时钟gif自动时钟gif 根据之前的CPU内部结构改造,制造一个cpu控制单元 改造一 之前的CPU全由手动开关自己控制,极度繁琐,而开关能跟二进制一一对应, 开:1, 关:0图1是之前的, …...
深入理解@Resource与@Autowired:用法与区别解析
Resource: Resource 是Java EE提供的注解,也可以在Spring中使用。它是按照名称进行注入的,默认通过属性名(通常是类名的小驼峰命名方式)或者name属性来匹配。如果找不到符合名称的bean,则会抛出异常。在使…...
高级驾驶辅助系统 (ADAS)介绍
随着汽车技术持续快速发展,推动更安全、更智能、更高效的驾驶体验一直是汽车创新的前沿。高级驾驶辅助系统( ADAS ) 是这场技术革命的关键参与者,是 指集成到现代车辆中的一组技术和功能,用于增强驾驶员安全、改善驾驶体验并协助完成各种驾驶任务。它使用传感器、摄像头、雷…...
2 使用React构造前端应用
文章目录 简单了解React和Node搭建开发环境React框架JavaScript客户端ChallengeComponent组件的主要结构渲染与应用程序集成 第一次运行前端调试将CORS配置添加到Spring Boot应用使用应用程序部署React应用程序小结 前端代码可从这里下载: 前端示例 后端使用这里介…...
[计算机网络]运输层概述
虽然我自己也不知道写在前面和前言有什么区别..... 这个系列其实是针对<深入浅出计算机网络>的简单总结,加入了一点个人的理解和浅薄见识,如果您有一些更好的意见和见解,欢迎随时协助我改正,感激不尽啦. 最近心态平和了不少, 和过去也完全做了个割舍吧,既然痛苦和压力的…...
【分布式】分布式事务及其解决方案
目录 一、分布式事务二、分布式事务的解决方案1. 全局事务(1)DTP模型(2) 两阶段提交协议(2PC)原理二阶段提交的缺点 (3)三阶段提交协议(3PC)原理 2. 基于可靠…...
【文末送书】机器学习高级实践
2023年初是人工智能爆发的里程碑式的重要阶段,以OpenAI研发的GPT为代表的大模型大行其道,NLP领域的ChatGPT模型火爆一时,引发了全民热议。而最新更新的GPT-4更是实现了大型多模态模型的飞跃式提升,它能够同时接受图像和文本的输入…...
吉他初学者学习网站搭建系列(1)——目录
文章目录 背景文章目录功能网站地址网站展示展望 背景 这个系列是对我最近周末搭建的吉他工具类平台YUERGS的总结。我个人业余爱好是自学吉他,我会在这个平台中动手集成我认为很有帮助的一些工具,来提升我的吉他水平和音乐素养,希望也可以帮…...
qgis添加arcgis的mapserver
左侧浏览器-ArcGIS地图服务器-右键-新建连接 Folder: / 展开-双击图层即可...
关于「光学神经网络」的一切:理论、应用与发展
/目录/ 一、线性运算的光学实现 1.1. 光学矩阵乘法器 1.2. 光的衍射实现线性运行 1.3. 基于Rayleigh-Sommerfeld方程的实现方法 1.4. 基于傅立叶变换的实现 1.5. 通过光干涉实现线性操作 1.6. 光的散射实现线性运行 1.7. 波分复用(WDM)实现线性运…...
【HarmonyOS】ArkTS开发@Styles装饰器和@Extend装饰器详解
文章目录 HarmonyOS详解ArkTS详解ArkTS装饰器深度解析:@Styles、@Extend、和stateStyles@Styles装饰器:优雅的组件样式定义与重用@Extend装饰器:扩展原生组件样式的利器stateStyles:多态样式的应用示例场景:装饰器的联合使用进阶应用:动态样式与交互最佳实践:样式的组织…...
Android修行手册-一个滑动旋转的弧形菜单
Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
区块链技术概述
区块链技术是一种去中心化、分布式账本技术,通过密码学、共识机制和智能合约等核心组件,实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点:数据存储在网络中的多个节点(计算机),而非…...
加密通信 + 行为分析:运营商行业安全防御体系重构
在数字经济蓬勃发展的时代,运营商作为信息通信网络的核心枢纽,承载着海量用户数据与关键业务传输,其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级,传统安全防护体系逐渐暴露出局限性&a…...
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...
Docker 镜像上传到 AWS ECR:从构建到推送的全流程
一、在 EC2 实例中安装 Docker(适用于 Amazon Linux 2) 步骤 1:连接到 EC2 实例 ssh -i your-key.pem ec2-useryour-ec2-public-ip步骤 2:安装 Docker sudo yum update -y sudo amazon-linux-extras enable docker sudo yum in…...
