提问人:Djowwie 提问时间:11/10/2023 最后编辑:Djowwie 更新时间:11/20/2023 访问量:105
带定时器的旋转编码器编程
Programming rotary encoder with timers
问:
我设计了一个带有STM32F103CBT6微控制器和 16 MHz 晶体振荡器的定制 PCB。该板具有一个按钮矩阵,其中包含 16 个按钮、2 个独立按钮和 4 个旋转编码器。
虽然我已经使用轮询方法成功地对按钮矩阵和独立按钮进行了编程,以检查按钮状态,但我现在正在解决对旋转编码器进行编程的挑战。我正在探索两种选择。
第一种选择是将所有编码器引脚设置为gpio_input并监控它们的状态变化,类似于按钮矩阵(例如,当引脚变为低电平时注册输入)。但是,这会在引脚上引入弹跳/跳跃,从而导致不正确或重复的输入。
向我推荐的第二种选择是使用计时器。每个编码器都连接到定时器的通道 1 和通道 2。例如,编码器 1 连接到定时器 1(通道 1 = PA8 和通道 2 = PA9),编码器 2 连接到定时器 2(通道 1 = PA0 和通道 2 = PA1),编码器 3 连接到定时器 3(通道 1 = PA6,通道 2 = PA7),编码器 4 连接到定时器 4(通道 1 = PB6,通道 2 = PB7)。
由于我不太熟悉在STM32上使用定时器,因此我在网上查找了信息,并尝试了各种方法,但收效甚微。目前,我有编码器 1 与定时器 1 一起工作,但输入不正确,提供随机按钮 1 和按钮 2 输入,甚至不是每个脉冲。
目标是为每个编码器的顺时针和逆时针移动生成唯一值,这些值将作为 HID 报告发送到 PC。这意味着我需要 8 个不同的输入值(每个编码器 2 个,x4),例如编码器 1 的顺时针位 0 和逆时针位 1,以及编码器 2 的位 2 和位 3,依此类推。
我目前拥有的计时器配置代码:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM1) {
int16_t currentEncoderValue = TIM1->CNT;
int16_t encoderDiff = currentEncoderValue - prevEncoderValue;
if (encoderDiff > 0) {
// Clockwise rotation detected
buttonReport.buttons |= (1 << 0);
} else if (encoderDiff < 0) {
// Counterclockwise rotation detected
buttonReport.buttons |= (1 << 1);
}
if (encoderDiff != 0) {
buttonReport.report_id = 1;
USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&buttonReport, sizeof(buttonReport));
}
prevEncoderValue = currentEncoderValue;
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_TIM4_Init();
MX_USB_DEVICE_Init();
HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_Base_Start_IT(&htim4);
while (1) {
SCANALL();
HAL_Delay(50);
}
}
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 1;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 1;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_FALLING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 10;
sConfig.IC2Polarity = TIM_ICPOLARITY_FALLING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 10;
if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
}
答:
Cube/HAL 在 Update 事件(即计数器翻转)时调用 HAL_TIM_PeriodElapsedCallback(),因此在您的设置中,当编码器向一个方向转动时,需要 65535 个脉冲才能实现此目的。
通常/推荐的过程是定期对计数器进行采样,例如在 main() 的定时循环中。中断对于编码器来说没有多大用处。
顺便说一句,TIM1 专门为其各种中断源提供了单独的中断向量,请参阅启动文件中的向量表(有关更新,它TIM1_UP_IRQHandler)。
评论
下面的代码是我现在如何编程的。它确实有效,但是需要一些调整,因为我有时仍然会弹跳/跳跃,但不像以前那么多了。
while (1) {
// Poll TIM1 encoder
int16_t currentEncoderValue1 = TIM1->CNT;
int16_t encoderDiff1 = currentEncoderValue1 - prevEncoderValue1;
if (encoderDiff1 > 0) {
// Clockwise rotation detected
buttonReport.buttons |= (1 << 0);
} else if (encoderDiff1 < 0) {
// Counterclockwise rotation detected
buttonReport.buttons |= (1 << 1);
}
if (encoderDiff1 != 0) {
buttonReport.report_id = 1;
USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&buttonReport, sizeof(buttonReport));
}
prevEncoderValue1 = currentEncoderValue1;
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 20;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_FALLING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 5;
sConfig.IC2Polarity = TIM_ICPOLARITY_FALLING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 5;
if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
}
评论
volatile
encoder1_state