처음 실행시 타임세팅 모드.
업/다운/라이트/레프트 버튼으로 시간 설정. 00분에서 다운버튼 누르면 59분, 59분에서 업버튼 누르면 00분으로 변함. 해당부분 할 드라이버 매크로함수 사용하였음
시간설정 후 유저버튼 한번 누르면 노멀모드 진입하고 시계 돌아감.
노멀 모드에서 유저버튼을 2초이상 누르고 있으면 바로 알람세팅 모드 진입
현재시간에서 커서가 깜빡거리고 버튼으로 알람시간을 설정한 후 유저버튼을 한번 누르면 다시 노멀모드로 돌아온다.
영상은 10초 뒤로 알람맞춘것. 알람설정 시간인 PM 02:25:26 가 되면 "삐삐삐삐" 하면서 알람이 울리는데 소리가 작아서 키워야 들린다. 알람울리는 중간에 mute interval을 주는 건 HAL_Delay를 사용하지 않았기 때문에 알람이 울리는 중간에도 시간은 계속 간다.
LCD모듈의 라이브러리 : https://github.com/afiskon/stm32-i2c-lcd-1602
lcm1602.h 파일과 lcm1602.c 파일로 분리해주고 함수 일부분을 지우고 매개변수, 내용 등을 수정하여 사용하였다. 그리고
void lcd_put_cur(int row, int col)
라는 함수를 추가해 행, 열만 간단히 입력해도 해당 위치로 이동하게끔 했다.
디버그 하기 쉽게 하려고 모든 변수를 전역에 때려박았다. 나중에 수정할 예정이다.
/* 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{
SETTING,
NORMAL,
ALARM
}_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;
char tmpTime[100]= {0,};
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;
/* 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);
/* 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();
uint8_t toggle=0;
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
/* USER CODE END 2 */
원래는 CubeMX에서 RTC의 ALARM A기능을 on 시키고 RTC_AlarmTypeDef 구조체를 쓸 예정이었다. 이 구조체는 멤버변수로 RTC_TimeTypeDef 구조체를 가진다. 아무튼.. 세팅 하고 코드도 추가했는데 HAL_RTC_SetAlarm_IT 를 써도 알람 인터럽트가 트리거 되지 않는다..디버그해보니 아예 알람 레지스터에 세팅이 안되는거 같아서 직접 레지스터에 접근하는 방법을 쓰면 되지 않을까 했는데 굳이 싶어서 그냥 RTC_TimeTypeDef 의 aTime이라는 구조체를 하나 더 만들어서 알람시간을 저장하는 용도로 썼다.
그리고 나중에는 RTC 시간을 백업 레지스터 저장하는 함수의 사용방법을 익혀 한번 사용해보고 싶다.
while문은 크게 SETTING 모드 NORMAL 모드 ALARM 모드로 나뉜다.
중복되는 부분, 함수화할 수 있는 부분이 많은데 일단은 생각나는대로 줄줄 썼다. 나중에 수정할 예정이다.
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 세팅모드
if(mode==SETTING){
toggle^=1;
lcd_put_cur(0,0);
LCD_SendString("Time Setting ");
// ADC
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(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)){
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);
}
}
// 알람모드
}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 AL", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes, aTime.Seconds);
}else{
if(setmode==AMPM){
sprintf(tmpTime," %02d:%02d:%02d AL", aTime.Hours, aTime.Minutes, aTime.Seconds);
}else if(setmode==HOUR_T){
sprintf(tmpTime,"%s %d:%02d:%02d AL", ampm[aTime.TimeFormat], aTime.Hours%10, aTime.Minutes, aTime.Seconds);
}else if(setmode==HOUR_O){
sprintf(tmpTime,"%s %d :%02d:%02d AL", ampm[aTime.TimeFormat], aTime.Hours/10, aTime.Minutes, aTime.Seconds);
}else if(setmode==MINUTE_T){
sprintf(tmpTime,"%s %02d: %d:%02d AL", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes%10, aTime.Seconds);
}else if(setmode==MINUTE_O){
sprintf(tmpTime,"%s %02d:%d :%02d AL", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes/10, aTime.Seconds);
}else if(setmode==SECOND_T){
sprintf(tmpTime,"%s %02d:%02d: %d AL", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes, aTime.Seconds%10);
}else if(setmode==SECOND_O){
sprintf(tmpTime,"%s %02d:%02d:%d AL", ampm[aTime.TimeFormat], aTime.Hours, aTime.Minutes, aTime.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;
mode=NORMAL;
}
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
ADC는 폴링모드를 썼다. 알람 음계를 출력하는데 TIM2를 쓰고 음 사이의 공백은 다른 타이머를 쓰려고 생각하고있다.
LCD 모듈의 버튼은 getButton 함수 내에서도 이전 상태랑 비교해서 아무것도 안누른 상태(NONE)에서 버튼을 누른게 감지된 순간의 것만 인정했고 while 문 내에서도 button_before 변수에 버튼상태를 저장하게끔 하여 이전버튼이랑 겹치지 않을때만 누른 것으로 인정하게끔 했다.
부저에 알람소리를 내게 하는건 PA0 번 핀 TIM2 CH1 을 사용했고 Prescaler는 1000-1, ARR은 180-1해서 주파수는 500Hz로 맞춰놓고, 펄스폭 설정은 ARR의 절반인 90으로했다. 지금은 3초동안 단순히 "삐삐삐삐삐" 하며 소리를 냈다 안냈다 반복하는 거라서 그냥 toggle 변수를 사용해 TIM_PWM_Start와 Stop을 반복하지만 나중에 노래를 출력하게 하려면 다른 타이머를 같이 사용해야 할것 같다.
/* 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 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();
}else {
user_pulled_flag=1;
user_pressed_flag=0;
}
}
}
/* USER CODE END 4 */
커서가 위치할 수 있는 곳은 typedef enum으로 정의해서 총 7곳인데 나중에 늘리려고 생각하고 있다.
유저버튼은 외부인터럽트로 핀설정을 했고 rising edge/falling edge 모두 트리거 되게 한 뒤에 콜백함수 내에서 readpin 함수로 현재 상태를 읽어 이게 rising edge에서 들어온건지 falling edge에서 들어온건지 구분하고 다른 행동을 취하게 끔했다. 버튼이 눌린상태일땐 pressed_flag 버튼에서 손 뗀 상태일땐 pulled_flag가 반전으로 켜지고, 버튼이 눌렸을 때 old_tick과 current_tick 변수를 같은값으로 초기화 한 후 while문 내에서 current_tick을 계속 갱신한다. 이를 통해 한번 클릭과 2초 이상 클릭된 것을 구분한다.. 나중엔 더블클릭도 구별할 수 있게 수정해보려고한다.
'임베디드 개발 > STM32 (ARM Cortex-M)' 카테고리의 다른 글
STM32 , UART 통신으로 4 digit 7 segment FND 실시간 제어하기 2편 (0) | 2022.06.18 |
---|---|
STM32 ] Timer 인터럽트를 사용하여 ADC 값 받기 + 그래프 보면서 디버깅하는 팁 (0) | 2022.06.17 |
STM32 ] RTC , GetTime / GetDate 함수로 시간 값 확인 시 주의사항 (1) | 2022.06.11 |
STM32 , RTC 와 FND 로 시계 만들기 + UART로 시간 제어 (FND 라이브러리 공유) (0) | 2022.06.09 |
STM32 , printf 디버깅에 사용 & 변수 값 그래프로 출력하기 2편 ( SWV / ITM ) (3) | 2022.06.06 |