본문 바로가기
임베디드 개발/STM32 (ARM Cortex-M)

STM32 ] RTC 와 LCD 모듈을 사용한 알람시계 구현 (2) - 더블클릭 기능 추가

by eteo 2022. 6. 19.

 

 

일단 한번 클릭한 것과 2초 이상 클릭한 것과 구별되어 더블클릭이 아주 잘 인식된다. 더블클릭한 경우 벨소리 선택모드로 넘어가게 끔 하고 벨소리 선택모드에서 한번 클릭하면 다시 노멀모드로 돌아오게끔 했다.

 

 

핵심코드

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == GPIO_PIN_13){

		if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)){
			user_pulled_flag=0;
			user_pressed_flag=1;
			old_tick=HAL_GetTick();
			current_tick=HAL_GetTick();

			if(mode==NORMAL){
				interval_chk[0]= HAL_GetTick();
				interval=interval_chk[0]-interval_chk[1];
			}

		}else {
			user_pulled_flag=1;
			user_pressed_flag=0;

			if(mode==NORMAL) {
				pulled_chk++;

				interval_chk[1] = HAL_GetTick();

				if(interval > 0 && interval < 300 && pulled_chk > 1){
					double_clicked=1;
				}
			}
		}
	}
}

 

두번째 클릭의 falling edge 지점에서 첫번째 클릭과의 간격을 체크해 더블클릭인지 아닌지 확인한다.

 

 

pulled_chk 라는 변수는 선언 동시 -1로 초기화하고 다른 모드에서 노말 모드로 들어올 때 또 다시 -1로 초기화한다. 이렇게 한 이유는 다른모드에서 노말 모드로 들어왔을 때의 클릭을 첫번째 클릭으로 인정하지 않게 하기 위함이다.

 

 

짧은 후기 : 지금까지 다른 모드에서 노말 모드로 들어올 때는 '한번 클릭' 이고 노말 모드에서 다른 모드로 갈 때는 '더블 클릭' 또는 '롱 클릭'이라서 푸시버튼의 채터링 현상을 방지하는 코드를 따로 추가하지 않아도 잘 작동했는데 만약 노말 모드에서도 '한번 클릭'을 사용하고자 한다면 채터링을 해결하기 위한 디바운싱(Debouncing) 코드를 추가해야 할 것 같다.

 

 

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "i2c.h"
#include "rtc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdio.h>
#include "lcm1602.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef enum{
	NONE,
	UP,
	DOWN,
	RIGHT,
	LEFT,
	UNKNOWN
}_Direction;
typedef enum{
	AMPM=0,
	HOUR_T,
	HOUR_O,
	MINUTE_T,
	MINUTE_O,
	SECOND_T,
	SECOND_O,
}_SetMode;
typedef enum{
	LOVEDIVE=0,
	FEARLESS,
	ELEVEN
}_Belltype;
typedef enum{
	SETTING,
	NORMAL,
	ALARM,
	BELL
}_MODE;
/* 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 */
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;


RTC_TimeTypeDef aTime;
RTC_DateTypeDef aDate;

_SetMode setmode = AMPM;
_Belltype belltype = LOVEDIVE;
char tmpTime[100]= {0,};
char tmp_bell_name[20]={0,};
char bell_name[3][20]={
		{">> LOVE DIVE   "},
		{">> FEARLESS    "},
		{">> ELEVEN      "},
};
char ampm[2][3] = {"AM", "PM"};
uint32_t adc_value=0;
_Direction direction;

_Direction button;
_Direction button_before=NONE;
uint8_t user_pressed_flag=0;
uint8_t user_pulled_flag=0;

_MODE mode=SETTING;

uint32_t old_tick=0;
uint32_t current_tick=0;

uint32_t old_alarm_tick=0;
uint32_t current_alarm_tick=0;

uint8_t alarm_on=0;

uint8_t double_clicked=0;

uint32_t interval_chk[2]={0,};
uint32_t interval=0;

int pulled_chk=-1;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
_Direction getButton();
void move_cur_time(RTC_TimeTypeDef *time, _Direction direction);
void move_cur_bell(_Direction direction);
/* 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_I2C1_Init();
  MX_RTC_Init();
  MX_ADC1_Init();
  MX_USART3_UART_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */

  LCM1602_init();

  HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
  HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);

  uint8_t toggle=0;

  char customChar[] = {0x01, 0x03, 0x05, 0x09, 0x09, 0x0B, 0x1B, 0x18};
  LCD_SendCommand(LCD_ADDR, 0x40);
  for(int i=0; i<8; i++) LCD_SendData(customChar[i]);

  /* USER CODE END 2 */

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

	  if(mode==SETTING){
		  toggle^=1;

		  lcd_put_cur(0,0);
		  LCD_SendString("Time Setting   ");

		  HAL_ADC_Start(&hadc1);
		  HAL_ADC_PollForConversion(&hadc1, 10);
		  adc_value = HAL_ADC_GetValue(&hadc1);
		  button = getButton();


		  if(button!=button_before){
			  move_cur_time(&sTime, button);
			  HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
			  button_before=button;
		  }

		  if(toggle){
			  sprintf(tmpTime,"%s %02d:%02d:%02d", ampm[sTime.TimeFormat], sTime.Hours, sTime.Minutes, sTime.Seconds);
		  }else{
			  if(setmode==AMPM){
				  sprintf(tmpTime,"   %02d:%02d:%02d", sTime.Hours, sTime.Minutes, sTime.Seconds);
			  }else if(setmode==HOUR_T){
				  sprintf(tmpTime,"%s  %d:%02d:%02d", ampm[sTime.TimeFormat], sTime.Hours%10, sTime.Minutes, sTime.Seconds);
			  }else if(setmode==HOUR_O){
				  sprintf(tmpTime,"%s %d :%02d:%02d", ampm[sTime.TimeFormat], sTime.Hours/10, sTime.Minutes, sTime.Seconds);
			  }else if(setmode==MINUTE_T){
				  sprintf(tmpTime,"%s %02d: %d:%02d", ampm[sTime.TimeFormat], sTime.Hours, sTime.Minutes%10, sTime.Seconds);
			  }else if(setmode==MINUTE_O){
				  sprintf(tmpTime,"%s %02d:%d :%02d", ampm[sTime.TimeFormat], sTime.Hours, sTime.Minutes/10, sTime.Seconds);
			  }else if(setmode==SECOND_T){
				  sprintf(tmpTime,"%s %02d:%02d: %d", ampm[sTime.TimeFormat], sTime.Hours, sTime.Minutes, sTime.Seconds%10);
			  }else if(setmode==SECOND_O){
				  sprintf(tmpTime,"%s %02d:%02d:%d ", ampm[sTime.TimeFormat], sTime.Hours, sTime.Minutes, sTime.Seconds/10);
			  }
		  }

		  lcd_put_cur(1,0);
		  LCD_SendString(tmpTime);

		  if(user_pressed_flag==1){
			  current_tick=HAL_GetTick();
			  if(current_tick-old_tick > 1){
				  old_tick=current_tick;
				  user_pressed_flag=0;
				  HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
				  mode=NORMAL;
			  }
		  }


	  }else if(mode==NORMAL){

		  HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
		  HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
		  sprintf(tmpTime,"%s %02d:%02d:%02d    ", ampm[sTime.TimeFormat], sTime.Hours, sTime.Minutes, sTime.Seconds);
		  lcd_put_cur(0,0);
		  LCD_SendString("Current Time   ");
		  lcd_put_cur(1,0);
		  LCD_SendString(tmpTime);


		  if(sTime.TimeFormat==aTime.TimeFormat
				  && sTime.Hours==aTime.Hours
				  && sTime.Minutes==aTime.Minutes
				  && sTime.Seconds==aTime.Seconds){
			  alarm_on=1;
		  }


		  if(alarm_on==1){
			  toggle^=1;
			  current_alarm_tick=HAL_GetTick();
			  if(toggle==1){
				  HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);
			  }else{
				  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
			  }
			  if(current_alarm_tick-old_alarm_tick > 3000){
				  old_alarm_tick=current_alarm_tick;
				  alarm_on=0;
				  HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);
			  }
		  }


		  if(user_pressed_flag==1){
			  current_tick=HAL_GetTick();
			  if(current_tick-old_tick > 2000){
				  old_tick=current_tick;
				  user_pressed_flag=0;
				  mode=ALARM;
				  HAL_RTC_GetTime(&hrtc, &aTime, RTC_FORMAT_BIN);
				  HAL_RTC_GetDate(&hrtc, &aDate, RTC_FORMAT_BIN);
			  }

		  }

		  if(double_clicked==1){
			  mode=BELL;
			  double_clicked=0;
		  }



	  }else if(mode==ALARM){

		  toggle^=1;

		  lcd_put_cur(0,0);
		  LCD_SendString("Alarm Setting");

		  HAL_ADC_Start(&hadc1);
		  HAL_ADC_PollForConversion(&hadc1, 10);
		  adc_value = HAL_ADC_GetValue(&hadc1);
		  button = getButton();

		  if(button!=button_before){
			  move_cur_time(&aTime, button);
			  button_before=button;
		  }


		  if(toggle){
			  sprintf(tmpTime,"%s %02d:%02d:%02d", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes, aTime.Seconds);
		  }else{
			  if(setmode==AMPM){
				  sprintf(tmpTime,"   %02d:%02d:%02d", aTime.Hours, aTime.Minutes, aTime.Seconds);
			  }else if(setmode==HOUR_T){
				  sprintf(tmpTime,"%s  %d:%02d:%02d", ampm[aTime.TimeFormat], aTime.Hours%10, aTime.Minutes, aTime.Seconds);
			  }else if(setmode==HOUR_O){
				  sprintf(tmpTime,"%s %d :%02d:%02d", ampm[aTime.TimeFormat], aTime.Hours/10, aTime.Minutes, aTime.Seconds);
			  }else if(setmode==MINUTE_T){
				  sprintf(tmpTime,"%s %02d: %d:%02d", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes%10, aTime.Seconds);
			  }else if(setmode==MINUTE_O){
				  sprintf(tmpTime,"%s %02d:%d :%02d", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes/10, aTime.Seconds);
			  }else if(setmode==SECOND_T){
				  sprintf(tmpTime,"%s %02d:%02d: %d", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes, aTime.Seconds%10);
			  }else if(setmode==SECOND_O){
				  sprintf(tmpTime,"%s %02d:%02d:%d ", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes, aTime.Seconds/10);
			  }
		  }

		  lcd_put_cur(1,0);
		  LCD_SendString(tmpTime);

		  lcd_put_cur(1, 14);
		  LCD_SendData(0);


		  if(user_pressed_flag==1){
			  current_tick=HAL_GetTick();
			  if(current_tick-old_tick > 1){
				  old_tick=current_tick;
				  user_pressed_flag=0;
				  pulled_chk=-1;
				  mode=NORMAL;
			  }
		  }

	  }else if(mode==BELL){
		  lcd_put_cur(0,0);
		  LCD_SendString("Bell Setting");


		  HAL_ADC_Start(&hadc1);
		  HAL_ADC_PollForConversion(&hadc1, 10);
		  adc_value = HAL_ADC_GetValue(&hadc1);
		  button = getButton();

		  if(button!=button_before){
			  move_cur_bell(button);
			  button_before=button;
		  }

		  sprintf(tmp_bell_name, "%s", bell_name[belltype]);

		  lcd_put_cur(1,0);
		  LCD_SendString(tmp_bell_name);

		  if(user_pressed_flag==1){
			  current_tick=HAL_GetTick();
			  if(current_tick-old_tick > 1){
				  old_tick=current_tick;
				  user_pressed_flag=0;
				  interval=0;
				  pulled_chk=-1;
				  mode=NORMAL;
			  }
		  }

	  }



    /* USER CODE END WHILE */

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

 

모드에서 벨소리 설정모드를 하나 추가했다. bell_name을 이차원배열 문자열로 저장해 놓고 LCD모듈의 버튼으로 이동되는 커서 위치(belltype)을 인덱스로 사용해 LCD 화면에 출력되게 끔 했다. 

 

 

또한 알람세팅 모드에서 AL이란 글자 대신 음표기호를 도트로 찍어 커스텀 캐릭터로 CGRAM에 올렸다.

커스텀 캐릭터 사용방법은 이전 글에서 소개한적이 있다.

2022.05.31 - [프로젝트 모음] - STM32 프로젝트 , 팩맨 게임 ( ADC in DMA mode 로 조이스틱 이용한 방향제어 , I2C LCD , Timer 인터럽트 및 PWM 사용 )

 

STM32 프로젝트 , 팩맨 게임 ( ADC in DMA mode 로 조이스틱 이용한 방향제어 , I2C LCD , Timer 인터럽트 및

개인 프로젝트 제작기간 : 3일 보드 : STM32F429ZI Tool : STM32CubeIDE I2C LCD 모듈, 조이스틱, 수동부저 사용 https://www.youtube.com/watch?v=-59wAqW_T-A 회로도 핀 연결과 설정은 아래표를 참고해주세요...

eteo.tistory.com

 

 

/* USER CODE BEGIN 4 */

_Direction getButton(){

	if(adc_value > 3700){
		return NONE;
	}else if(adc_value < 20 && button_before==NONE){
		return UP;
	}else if(adc_value > 800 && adc_value < 900 && button_before==NONE){
		return DOWN;
	}else if(adc_value > 1700 && adc_value < 2100 && button_before==NONE){
		return LEFT;
	}else if(adc_value > 2700 && adc_value < 3100 && button_before==NONE){
		return RIGHT;
	}else
		return UNKNOWN;
}

void move_cur_time(RTC_TimeTypeDef *Time, _Direction direction){
	switch(direction){
	case RIGHT:
		setmode++;
		if(setmode > SECOND_O) setmode = SECOND_O;
		break;
	case LEFT:
		if(setmode > AMPM) setmode--;
		break;
	case UP:
		if(setmode==AMPM){
			Time->TimeFormat ^= 1;
		}else if(setmode==HOUR_T){
			Time->Hours+=10;
			if(!IS_RTC_HOUR12(Time->Hours)) Time->Hours = 1;
		}else if(setmode==HOUR_O){
			Time->Hours++;
			if(!IS_RTC_HOUR12(Time->Hours)) Time->Hours = 1;
		}else if(setmode==MINUTE_T){
			Time->Minutes+=10;
			if(!IS_RTC_MINUTES(Time->Minutes)) Time->Minutes = 0;
		}else if(setmode==MINUTE_O){
			Time->Minutes++;
			if(!IS_RTC_MINUTES(Time->Minutes)) Time->Minutes = 0;
		}else if(setmode==SECOND_T){
			Time->Seconds+=10;
			if(!IS_RTC_SECONDS(Time->Seconds)) Time->Seconds = 0;
		}else if(setmode==SECOND_O){
			Time->Seconds++;
			if(!IS_RTC_SECONDS(Time->Seconds)) Time->Seconds = 0;
		}
		break;
	case DOWN:
		if(setmode==AMPM){
			Time->TimeFormat ^= 1;
		}else if(setmode==HOUR_T){
			Time->Hours-=10;
			if(!IS_RTC_HOUR12(Time->Hours)) Time->Hours = 12;
		}else if(setmode==HOUR_O){
			Time->Hours--;
			if(!IS_RTC_HOUR12(Time->Hours)) Time->Hours = 12;
		}else if(setmode==MINUTE_T){
			Time->Minutes-=10;
			if(!IS_RTC_MINUTES(Time->Minutes)) Time->Minutes = 59;
		}else if(setmode==MINUTE_O){
			Time->Minutes--;
			if(!IS_RTC_MINUTES(Time->Minutes)) Time->Minutes = 59;
		}else if(setmode==SECOND_T){
			Time->Seconds-=10;
			if(!IS_RTC_SECONDS(Time->Seconds)) Time->Seconds = 59;
		}else if(setmode==SECOND_O){
			Time->Seconds--;
			if(!IS_RTC_SECONDS(Time->Seconds)) Time->Seconds = 59;
		}
		break;
	case NONE:
		break;
	case UNKNOWN:
		break;
	}
}

void move_cur_bell(_Direction direction){
	switch(direction){
	case RIGHT:
		break;
	case LEFT:
		break;
	case NONE:
		break;
	case UNKNOWN:
		break;
	case UP:
		if(belltype < 2) belltype++;
		else belltype=0;
		break;
	case DOWN:
		if(belltype > 0) belltype--;
		else belltype=2;
		break;
	}
}


void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == GPIO_PIN_13){

		if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)){
			user_pulled_flag=0;
			user_pressed_flag=1;
			old_tick=HAL_GetTick();
			current_tick=HAL_GetTick();

			if(mode==NORMAL){
				interval_chk[0]= HAL_GetTick();
				interval=interval_chk[0]-interval_chk[1];
			}

		}else {
			user_pulled_flag=1;
			user_pressed_flag=0;

			if(mode==NORMAL) {
				pulled_chk++;

				interval_chk[1] = HAL_GetTick();

				if(interval > 0 && interval < 300 && pulled_chk > 1){
					double_clicked=1;
				}
			}
		}
	}
}
/* USER CODE END 4 */

 

참고로 move_cur_bell 함수에선 enum belltype이 0에서 2까지 밖에 없어서 2에서 up버튼이 눌리면 다시 0으로 돌아오고 0에서 down버튼이 눌리면 2로 돌아가게끔 했다.