Debouncing of buttons using DMA on STM32

버튼 입력을 처리하는데 채터링은 항상 골치 아픈 문제다. 하드웨어적으로 로우패스 필터 역할을 하는 회로가 버튼 입력에 붙으면 좋겠지만, 그렇지 못한 상황에서는 소프트웨어적으로 해결해야 한다.

일반적인 경우, 일정한 주기의 (1ms) 인터럽트에서 키 입력에 해당하는 GPIO 비트의 변화를 감시하고, 몇 번의 인터럽트동안 비트의 변화가 없는 경우 키의 상태를 반영하면 채터링을 제거할 수 있다.

단순히 EXTI 로 처리하게 된다면 순간적인 노이즈나 버튼을 살짝 건드리는 경우에도 동작하게 되어 문제가 된다. 이를 해결하려면 타이머까지 동원하여 복잡하게 될 것이다.

이리저리 고민하다 최대한 프로그래밍을 하지않고 해결할 방법을 찾아보았다. 앞서 말했듯이 일정한 기간동안 비트의 변화가 없다는 것을 DMA로 해결해보려고 하였다. 기본적인 아이디어는 다음과 같다.

  1. 타이머 하나를 1ms 마다 update 이벤트가 생기도록 설정한다.
  2. 1ms 마다 DMA로 GPIO 포트 (16 bits, half word) 를 읽는다.
  3. 이 동작을 5번 하게 되었을 때 (5ms) DMA 완료가 된다. (0, 1, 2, 3, 4ms)
  4. 5개 비트들 중 해당하는 비트를 논리 연산을 통해 일정하게 유지되는지 확인
  5. 만약 유지가 되었다면, 키 입력 변수에 3번의 결과를 저장
  6. DMA는 circular 모드로 세팅하여 5ms마다 1번 반복

1ms 타이머 인터럽트에서 처리해야 할 것을 5ms마다 DMA가 완료될 때로 미루고, 소프트웨어 처리를 최대한 줄일 수 있다.

uint16_t portb_[5];
bool key_ = false;
void DMA_PORTB_COMPLETED(DMA_HandleTypeDef *hdma)
{
uint16_t mand = 0xFFFF, mor = 0;
for (int i = 0; i < DIMOF(portb_); ++i) {
mand &= portb_[i];
mor |= portb_[i];
}
if (0 == ((mand ^ mor) & ENC_BTN_Pin)) {
key_ = (mand & ENC_BTN_Pin);
}
}

HAL_TIM_Base_Start_DMA는 ARR 레지스트를 사용하도록 하드코딩되어 있기 때문에, HAL 코드를 응용하여 타이머와 DMA가 동작하도록 하였다.

htim4.hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = DMA_PORTB_COMPLETED;
HAL_DMA_Start_IT(htim4.hdma[TIM_DMA_ID_UPDATE], (uint32_t)&GPIOB->IDR, (uint32_t)portb_, DIMOF(portb_));
__HAL_TIM_ENABLE_DMA(&htim4, TIM_DMA_UPDATE);
__HAL_TIM_ENABLE(&htim4);

키 상태를 안정적으로 입력 받을 수 있게 되었으므로, 이전 키 값과 비교하여 키의 변화를 리포팅 하거나 큐 처리하여 사용하자.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다