일단 한번 클릭한 것과 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에 올렸다.
커스텀 캐릭터 사용방법은 이전 글에서 소개한적이 있다.
/* 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로 돌아가게끔 했다.
'임베디드 개발 > STM32 (ARM Cortex-M)' 카테고리의 다른 글
STM32 ] 플래시 메모리 지우고 다시 쓰기 + 리틀 엔디안과 빅 엔디안 + ST-link utility 사용법 (0) | 2022.06.28 |
---|---|
STM32 ] 블루투스 모듈 MLT-BT05 사용하기 / 메시지 주고받기 , AT 커맨드 (0) | 2022.06.19 |
STM32 , UART 통신으로 4 digit 7 segment FND 실시간 제어하기 2편 (0) | 2022.06.18 |
STM32 ] Timer 인터럽트를 사용하여 ADC 값 받기 + 그래프 보면서 디버깅하는 팁 (0) | 2022.06.17 |
STM32 ] RTC 와 LCD 모듈을 사용한 알람시계 구현 (1) (8) | 2022.06.11 |