STM32 HAL 编码器读脉冲值 编码器模式/中断方法

编码器脉冲值变化如下

这里以AB相双边沿四倍频为例说明

A相上升沿B相高电平  脉冲值加一
 B相低电平  脉冲值减一
A相下降沿B相高电平  脉冲值减一
B相低电平 脉冲值加一

准备

这里使用串口1用于编码器脉冲值的打印,并使用TIM1做100us周期定时器

cubemx配置

T = (PSC+1) * (ARR+1) / 72MHz = 100us

记得开启中断

代码

//uart.h
#ifndef __UART_H
#define __UART_H
#include "stm32f1xx_hal.h"
#include "stdio.h"

int fputc(int ch,FILE *F);

#endif

//uart.c
#include "uart.h"

extern UART_HandleTypeDef huart1;

int fputc(int ch,FILE *F)
{
	HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
	return ch;
}

1.编码器模式

cubemx配置

代码如下

//encoder_tim.h
#ifndef __ENCODER_TIM_H
#define __ENCODER_TIM_H
#include "stm32f1xx_hal.h"

void Encoder_Init(void);
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);

#endif
//encoder_tim.c
#include "encoder_tim.h"
#include "uart.h"

extern TIM_HandleTypeDef htim1;
extern TIM_HandleTypeDef htim2;

//定义脉冲值,用volatile保护
static volatile short en_pulse;

//初始化,中断模式启动定时器,清零计数值和脉冲值
void Encoder_Init(void)
{
	HAL_TIM_Base_Start_IT(&htim1);
	HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
	__HAL_TIM_SetCounter(&htim2,0);
	en_pulse = 0;
}

//100us定时器,如后续需周期读取,可使用分频的方法
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim1)
	{
		//使用short类型,可以得到正负数值,分辨方向
		 en_pulse = (short)__HAL_TIM_GetCounter(&htim2 );
		
		//打印脉冲值
		printf("Encoder pulse : %d\r\n",en_pulse );
		
		//清除标志位,可选
		__HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_UPDATE);
	}		
}

2.外部中断模式

cubemx配置

这里使用PB12/13  对应接线为   PB12-->A相  PB13-->B相

配置双边沿触发,上拉

记得开启中断

代码

//encoder_exti.h
#ifndef __ENOCODER_EXTI_H
#define __ENOCODER_EXTI_H
#include "stm32f1xx_hal.h"

void Encoder_Init(void);
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
int16_t Get_Encoder(void);

#endif
//encoder_exti.c
#include "encoder_exti.h"
#include "uart.h"

extern TIM_HandleTypeDef htim1;
static volatile int16_t en_pulse;

//初始化,中断开启定时器,计数值清零
void Encoder_Init(void)
{
	HAL_TIM_Base_Start_IT(&htim1);
	en_pulse = 0;

}


//使用外部中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	//定义变量表示AB相电平状态
	uint8_t en_a=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12) ;
    uint8_t en_b=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13) ;
	
	
    //A相
	if(GPIO_Pin == GPIO_PIN_12)
	{
		if(en_a == GPIO_PIN_SET)
		{
			if(en_b == GPIO_PIN_RESET)  en_pulse++;
			else                        en_pulse--;
		}
		else
		{
			if(en_b == GPIO_PIN_SET)    en_pulse++;
			else                        en_pulse--;
		}
	}
	
	 //B相
	if(GPIO_Pin == GPIO_PIN_13)
	{
		if(en_a == GPIO_PIN_SET)
		{
			if(en_b == GPIO_PIN_SET)    en_pulse++;
			else                        en_pulse--;
		}
		else
		{
			if(en_b == GPIO_PIN_RESET)  en_pulse++;
			else                        en_pulse--;
		}
	}
	//清除标志位
	__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
}

//返回脉冲值函数,后续需要可以调用
int16_t Get_Encoder(void)
{
	return en_pulse;
}


//100us定时器,如后续需周期读取,可使用分频的方法
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim1)
	{
		
		printf("Encoder pulse : %d\r\n",en_pulse );
		
		//清除标志位,可选
		__HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_UPDATE);
	}		
}

两个程序main.c是一样的,只需要添加头文件和初始化函数即可,这里以编码器模式的为例

//main.c
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "encoder_tim.h"   //头文件
//#include "encoder_exti.h"
#include "uart.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM3_Init();
  MX_TIM1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  
  Encoder_Init();  //初始化函数

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  
  while (1)
  {
	 
	 
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

烧录后,用手转动电机就会有数值变化了,

如果是编码器电机,转动一圈脉冲值变化为  减速比*线数*4(因为这里是四倍频)

如,30减速比500线电机,一圈变化为 30*500*4=600000

写在最后:

外部中断的方法可行,但是肯定还是直接使用编码器模式方便高效,且外部中断还需要考虑中断线的情况,因为STM32 的 EXTI 中断不是按 “GPIO 引脚号” 分配,而是按 “EXTI 线” 分配,且每根 EXTI 线在同一 GPIO 端口下仅对应 1 个引脚。

指的是考虑使用外部中断方法做四轮小车的编码器测脉冲后续使用pid算法,可能就需要好好考虑一下引脚分布了,尤其如果你使用的还是STM32C8T6,那还是打算换一个芯片吧,或者使用串口通信使用两个STM32C8T6.

不过其实也可以做的,因为我当时真的分配出了8条中断线,最后剩下了PC13/14/15引脚,哈哈哈,贴出来给大家当个笑话,别学我。哦,PC13用来点灯了

还有PC13/14/15这三个引脚比较特殊,在手册中这样一段描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值