> 技术文档 > STM32通过读取ABZ编码器同时获取角度、速度和位置_stm32 abz

STM32通过读取ABZ编码器同时获取角度、速度和位置_stm32 abz

        ABZ编码器的原理、信号波形以及如何用cubemx配置在网上有很多介绍,本文以STM32G431为例重点介绍如何利用freeRTOS同时实现角度、速度和位置的读取。

1. cubemx配置编码器

        我使用的ABZ编码器的分辨率是1024,配置四倍频就是4096,所以自动重载设置成4095,这样编码器旋转一周刚好对应0~4095。

以上配置读取的是编码器的AB相,还需要通过外部中断的方式读取零点脉冲即Z相:

生成代码如下:

tim.c:

/* TIM3 init function */void MX_TIM3_Init(void){ /* USER CODE BEGIN TIM3_Init 0 */ /* USER CODE END TIM3_Init 0 */ TIM_Encoder_InitTypeDef sConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM3_Init 1 */ /* USER CODE END TIM3_Init 1 */ htim3.Instance = TIM3; htim3.Init.Prescaler = 0; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 4095; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; sConfig.EncoderMode = TIM_ENCODERMODE_TI12; sConfig.IC1Polarity = TIM_ICPOLARITY_FALLING; sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI; sConfig.IC1Prescaler = TIM_ICPSC_DIV1; sConfig.IC1Filter = 0; sConfig.IC2Polarity = TIM_ICPOLARITY_FALLING; sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI; sConfig.IC2Prescaler = TIM_ICPSC_DIV1; sConfig.IC2Filter = 0; if (HAL_TIM_Encoder_Init(&htim3, &sConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM3_Init 2 */ /* USER CODE END TIM3_Init 2 */}void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle){ GPIO_InitTypeDef GPIO_InitStruct = {0}; if(tim_encoderHandle->Instance==TIM3) { /* USER CODE BEGIN TIM3_MspInit 0 */ /* USER CODE END TIM3_MspInit 0 */ /* TIM3 clock enable */ __HAL_RCC_TIM3_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); /**TIM3 GPIO Configuration PC6 ------> TIM3_CH1 PC7 ------> TIM3_CH2 */ GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF2_TIM3; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); /* USER CODE BEGIN TIM3_MspInit 1 */ /* USER CODE END TIM3_MspInit 1 */ }}void HAL_TIM_Encoder_MspDeInit(TIM_HandleTypeDef* tim_encoderHandle){ if(tim_encoderHandle->Instance==TIM3) { /* USER CODE BEGIN TIM3_MspDeInit 0 */ /* USER CODE END TIM3_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_TIM3_CLK_DISABLE(); /**TIM3 GPIO Configuration PC6 ------> TIM3_CH1 PC7 ------> TIM3_CH2 */ HAL_GPIO_DeInit(GPIOC, GPIO_PIN_6|GPIO_PIN_7); /* USER CODE BEGIN TIM3_MspDeInit 1 */ /* USER CODE END TIM3_MspDeInit 1 */ }}

在适当时机执行HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);即可启动编码器

2. 读取角度

        上面配置完成之后,通过__HAL_TIM_GET_COUNTER(&htim3)获取的就可以被认为是角度,因为编码器经过4倍频的分辨率是0~4095,寄存器重载的值也是4095,只是每次启动的时候零点会变,而FOC的计算需要的是绝对不变的角度,需要在Z相中断回调函数里面把编码器的寄存器置零:

/* USER CODE BEGIN 2 */void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ if (GPIO_Pin == encode_Z_Pin) { // 重置编码器计数器 Set_Encoder_Zero(); // printf(\"Encoder Z interrupt: Counter reset\\n\"); }}void Set_Encoder_Zero(){ __HAL_TIM_SET_COUNTER(&htim3, 0); encoder_angle_valid = 1;}

        每次Z相中断都会校准编码器的零点位置,再次读出来的角度就是绝对角度:

// 计算传感器角度void CalSensorAngle(){ uint16_t encode = Read_Encode(); // 0 ~ 4095 // printf(\"encode: %d\\n\", encode); sensor_angle = ((float)encode / 4096.0f) * 2.0f * PI;}uint16_t Read_Encode(){ return __HAL_TIM_GET_COUNTER(&htim3); // __HAL_TIM_SET_COUNTER(&htim1, 0); // printf(\"encoder: %d\\n\", encoder);}

3. 读取速度和位置

        读取速度和位置目的是让其参与PID的计算,我这里是让PID任务以固定1000Hz的频率计算:

xTaskCreate(pid_task, \"PidTask\", 256, NULL, osPriorityHigh, &pidTaskHandle);void pid_task(void *params){ TickType_t xLastWakeTime; const TickType_t xFrequency = pdMS_TO_TICKS(1); xLastWakeTime = xTaskGetTickCount(); /** * PID初始化代码 */ for (;;) { /** * 计算速度、位置 */ /** * PID代码 */ vTaskDelayUntil(&xLastWakeTime, xFrequency); } }

计算速度和位置的方法如下:

float Cal_Encoder_Speed(){ // 计算编码器差异 int encoder_value = Read_Encode(); int delta_encoder = encoder_value - last_encoder_value; // 处理编码器回绕情况 if (delta_encoder > 2048) { delta_encoder -= 4096; } else if (delta_encoder < -2048) { delta_encoder += 4096; } // 计算速度,单位是弧度每秒 (rad/s) float speed = (float)delta_encoder / 4096.0f * 2.0f * PI * 1000.0f; // 计算位置单位是弧度 (rad) position_encoder += (float)delta_encoder / 4096.0f * 2.0f * PI; last_encoder_value = encoder_value; return speed;}