做平衡小车目前有两种思路,第一种是使用编码器电机,这样一般是两个闭环控制,直立闭环和速度闭环,另一种是使用步进电机,一般使用步进电机很少进行闭环控制。使用Cube进行配置时,发现几点注意事项,STM32单片机自带编码器接口,可以直接进行使用,十分方便,所以根据硬石科技的资料,编码器模式在STM32HAL库中叫做Encoder 。
首先有一点注意事项,在配置编码器模式的时候一定要对所使用定时器的初始化进行修改:
/* TIM2 init function */
void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_IC_InitTypeDef sConfigIC;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 65535;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
_Error_Handler(FILE, LINE);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(FILE, LINE);
}
if (HAL_TIM_IC_Init(&htim2) != HAL_OK)
{
_Error_Handler(FILE, LINE);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
_Error_Handler(FILE, LINE);
}
//以下是个人修改代码 ,经过试验,没有以下代码的修改,定时器初始化后不能进入编码器模式。
sEncoderConfig.EncoderMode = TIM_ENCODERMODE_TI2;
sEncoderConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sEncoderConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sEncoderConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC1Filter = 0;
sEncoderConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sEncoderConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sEncoderConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC2Filter = 0;
HAL_TIM_Encoder_Init(&htim2, &sEncoderConfig);
// if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
// {
// _Error_Handler(FILE, LINE);
// }
// if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
// {
// _Error_Handler(FILE, LINE);
// }
}
主循环进入前,还要进行将编码器打开
打开代码如下:
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
进行读取的时候还需要设置变量进行读取
uwDirection = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
CaptureNumber=__HAL_TIM_GET_COUNTER(&htim2);
__IO uint16_t time_count=0;
__IO uint32_t CaptureNumber=0;
__IO uint8_t start_flag=0;
uint32_t uwDirection = 0;//读取计数方向
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
PWM_Change_Duty(1800,0);
start_flag = 1;
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
OLED_Clear();
PWM_Change_Duty(3600,0);
uwDirection = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
CaptureNumber=__HAL_TIM_GET_COUNTER(&htim2);//获取计数值
OLED_Clear();
sprintf(str,“1sjs:%d”,CaptureNumber>=count?CaptureNumber-count:CaptureNumber+65535-count);
OLED_ShowString(0,0,str);
sprintf(str,“sjzs:%0.2f”,(float)(CaptureNumber>=count?CaptureNumber-count:CaptureNumber+65535-count)/11/34/2);
OLED_ShowString(0,4,str);
count=CaptureNumber;
delay_ms(1000);
}
上述的公式内容有待完善但是可以正常使用,具体参数还要继续调整
,硬件配置时一定要打开定时器的两路接口,因为这样才能对AB相同时进行捕获
这里面的预分频应该使用0,使用的时候注意一下。
以上代码是重中之重,一定要根据自己手里的电机型号编码器种类进行配置。
以上用于在电机反转的时候进行修正,因为此时的捕获值与实际正好相差 65535 - CaptureNumber。
经过具体试验只有以下程序能够在中断中跑起来:(切记如果太复杂了会导致程序卡死)
切记在中断中再次触发其它中断一定要使第一中断优先级最高,否则无法正常进入中断程序。
以下代码的中断有限最高级是外部中断EXTI9_5其次是定时器2和定时器4,经过试验发现只有这样,编码器才能正常工作。
最关键问题发现,中断无法进入的另一个原因是因为没有初始化完成寄存器,因此在具体使用程序的时候将中断初始化放在最后。
这两个函数用来读取编码器的数值。
这是最终的初始化流程顺序,在程序使用外部中断的时候中断初始化函数一定要放到最后,放到其他初始化前面会导致无法对未初始化的寄存器进行初始化,这条一定要在调用外部中断进行触发的时候切记,十分重要。