사용한 DS3231 모듈

  • 작동 전압 : 2.3~5.5V
  • 통신 : I2C
  • Fast(400kHz) 모드 지원
  • 배터리 용량이 부족할 경우, 시간 흐름이 부정확해짐
  • (사용 모듈의 경우 INT/SQW 핀에 풀업 레지스터 구성되어 있음)

[내부 레지스터 주소 및 데이터]

DS3231이 제공하는 데이터들 (총 19 바이트)

  • 0x00~0x06 : 현재 설정되어 있는 시간
  • 0x07~0x0A : 현재 설정되어 있는 알람1 시간
  • 0x0B~0x0D : 현재 설정되어 있는 알람2 시간
  • 0x0E~0x10 : 제어 레지스터
  • 0x11~0x12 : 온도 (0x11 : 정수, 0x12 : 소수)
  • 0x02 시간(Hours) 레지스터에서 6비트를 HIGH로 설정하면 12시간, LOW라면 24시간 기준으로 시간을 표시
    • 12시간 모드에선 오후 시간일 때(PM) 5비트가 HIGH 상태가 된다
    • 24시간 모드에선 5비트는 20~23시를 표시하는데 사용된다

[Read]

MASTER가 DS3231로부터 데이터를 읽어올 때
레지스터 포인터에 읽어 올 주소를 Write 후 Read 시작
Read 모드 사용시 레지스터 포인터에 대한 주의사항

  • 데이터를 읽어 올 때, SDA 라인의 첫 1바이트는 MSB부터 7 비트는 DS3231(Slave) 주소 + Read(1) 비트로 구성된다
  • Write 모드 사용시에만 레지스터 포인터가 설정되는데 이는 DS3231로부터 데이터를 읽어오는 시작 위치가 된다
  • ex) Write 모드 마지막으로 분이 설정된 경우 레지스터 포인터엔 0x01 주소가 설정되고 DS3231로부터 데이터를 읽어올 때 0x00부터 읽어오는게 아니라 레지스터 포인터에 저장된 0x01부터 설정된 크기만큼의 데이터를 읽어온다
  • 따라서 필요에 따라 레지스터 포인터를 원하는 주소로 재설정한 후 Read 를 시작해야 한다

[Write]

Master로부터 DS3231로의 데이터 전송

  • Write 모드일 때, SDA 라인의 첫번째 바이트는 7 비트 Slave 주소 + Write(0) 비트로 구성
  • 그 이후엔 DS3231내의 데이터를 쓰고자 하는 주소(WORD ADDRESS) ((예)초:0x00, 분:0x01, 시:0x02, ...), 그리고 그 다음엔 해당 주소에 입력할 값이 와야 한다
  • WORD ADDRESS 다음에 바로 이어지는 1 바이트 데이터는 WORD ADDRESS 주소에 입력되고 그 이후에 이어지는 데이터는 순서대로 WORD ADDRESS+1, WORD ADDRESS+2, ..., WORD ADDRESS+X 의 주소에 저장된다
  • ex) 3바이트 크기의 배열 : [0]seconds 주소(0x00), [1]seconds, [2]minutes 의 배열을 전송하는 경우, [0]부터 순서대로 DS3231에 전송되어 0x00 주소에 seconds가 입력되고 그 다음으로 주소가 +1되어 0x01에 minutes가 전송됨
  • Write 모드를 사용할 때 마지막으로 사용된 DS3231 내부 주소(WORD ADDRESS)가 레지스터 포인터에 설정되어 이후 Read 모드 사용시 데이터를 읽어오기 시작하는 시작점으로 사용된다

[CubeMX 설정]

I2C 설정

  • Fast Mode 설정, 7-bit 주소 설정, 인터럽트 활성화

[코드]

- 정의

DS3231 타임 레지스터 구성 및 주소

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/*
DS3231 주소 
실제 통신에선 원 주소인 0x68을 MSB 방향으로 1비트 시프트한 값에 Read or Write 비트가 합해져 1바이트가 된다
*/
#define ds3231_addr 0x68<<1	
#define ds3231_sec_addr 0x00		//DS3231 내 초(sec) 레지스터 주소
#define ds3231_min_addr 0x01	//분(min) 레지스터 주소
#define ds3231_hour_addr 0x02	//시간
#define ds3231_day_addr 0x03	//요일
#define ds3231_date_addr 0x04	//날짜
#define ds3231_month_addr 0x05	//달
#define ds3231_year_addr 0x06	//년도
/*
DS3231 시간 레지스터(0x02)의 6번 비트는 12/24 시간 설정, 
5번 비트는 AM/PM or 24 시간 표시를 사용할 때 2x 시간일 경우 사용됨
*/
#define ds3231_AM 0		//시간 구조체의 시간 부분을 AM으로 설정할 때 사용
#define ds3231_PM 1		//시간 구조체의 시간 부분을 PM으로 설정할 때 사용
#define ds3231_24_hour 2	//DS3231에 쓸 구조체의 시간 부분을 24시간으로 설정할 때 사용
#define ds3231_day 1	//시간 알람 구조체의 날짜/요일 설정 부분을 요일로 설정할 때 사용
#define ds3231_date 0	//시간 알람 구조체의 날짜/요일 설정 부분을 날짜로 설정할 때 사용
#define ds3231_alarm1_addr 0x07	//DS3231 알람1 레지스터 주소
#define ds3231_alarm2_addr 0x0B	//DS3231 알람2 레지스터 주소
#define ds3231_ctrl_addr 0x0E	//DS3231 컨트롤 레지스터 주소
#define ds3231_temp_addr 0x11
/* USER CODE END PD */
  • DS3231 주소 및 DS3231 내의 레지스터 주소들 정의

- 구조체 

/*
시간 레지스터에서 AM/PM or 24시간 표기 설정에 사용
*/
typedef struct
{
	//12시간 표기를 사용할 경우엔 ds3231_AM or define ds3231_PM, 24시간 표기일 경우엔 ds3231_24_hour 입력
	uint8_t am_pm_24;	//AM or PM or 24시간 표기 사용 설정
	uint8_t hour;	//12, 24시간 표기에 상관없이 입력할 시간(Hour) 설정
}am_pm_24;

/*
DS3231로부터 시간을 읽어오거나 시간 데이터를 쓸 때 사용할 시간 구조체
*/
typedef struct
{
	uint8_t	sec;
	uint8_t min;
	am_pm_24 hour_select;
	uint8_t day;
	uint8_t date;
	uint8_t month;
	uint8_t year;
}ds3231_time;

/*
알람에서 요일 or 날짜를 쓸건지 구분 지을 구조체
*/
typedef struct
{
	uint8_t day_or_date;	//ds3231_day or ds3231_day 를 입력해 날짜 or 요일 설정
	uint8_t value;	//날짜 or 요일값 설정
}day_date;

/*
알람1을 읽어오거나 전송할 때 사용할 구조체
*/
typedef struct
{
	uint8_t sec;
	uint8_t min;
	am_pm_24 hour_select;
	day_date day_date_select;
}ds3231_Alarm1;

- 10진수, 2진수 변환 함수

/* USER CODE BEGIN PFP */

int decTobcd(uint8_t dec)
{
	return ((dec/10)*16)+(dec%10);
}
int bcdTodec(uint8_t bcd)
{
	return (bcd/16*10)+(bcd%16);
}
  • DS3231은 2진수를 사용하므로 10진수 사용을 위해선 DS3231로부터 읽어들인 데이터는 10진수로 변환해야 하고 Master 장치로부터 DS3231로 나가는 데이터는 2진화 해야 한다

- 시간 출력 함수, Read 함수 

/*
DS3231로부터 읽어 온 시간 데이터를 출력하는 함수
시간 영역에 저장된 값(시간 구조체->hour_select->am_pm_24)을 판단해
AM, PM, 24시간 표기로 구분한 뒤 출력
*/
void ds3231_print_time(ds3231_time *current_time)
{
	printf("year : %d   month : %d   date : %d   day : %d   ",current_time->year,current_time->month
			,current_time->date,current_time->day);
	switch (current_time->hour_select.am_pm_24)
	{
		//12시간 표기의 경우 0~4번 비트까지 유효한 시간 데이터, 5~6번 비트는 시간 표기 설정 비트
		case ds3231_AM :
			printf("AM : %d   ",(current_time->hour_select.hour)&0x1F);	
			break;
		case ds3231_PM :
			printf("PM : %d   ",(current_time->hour_select.hour)&0x1F);
			break;
		case ds3231_24_hour :
			printf("24 : %d   ",current_time->hour_select.hour);
			break;
		default :
			break;
	}
	printf("min : %d   sec : %d\r\n",current_time->min,current_time->sec);
}

/*
전달받은 시간 구조체 주소에 DS3231로부터 읽어 온 시간 데이터를 저장하는 함수
*/
void ds3231_read_time(ds3231_time *current_time)
{
	uint8_t ds3231_read_time_buff[7];	
	//DS3231로부터 읽어 올 데이터를 저장할 데이터 버퍼 (초,분,시간,요일,날짜,달,년도-총 7개)
    
	/*
	특정 메모리 주소에 액세스해서 원하는 갯수만큼 데이터를 읽어와서 데이터 버퍼에 저장
	*/
	HAL_I2C_Mem_Read_IT(&hi2c1,ds3231_addr,ds3231_sec_addr,I2C_MEMADD_SIZE_8BIT,ds3231_read_time_buff,7);
	//사용할 I2C, DS3231 주소, 접근할 DS3231 레지스터 주소, 메모리 블럭 크기, 읽어 온 데이터를 저장할 버퍼,
	//읽어 올 데이터 갯수
    
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
		//수신 완료 때까지 대기
	}

	/*
	DS3231로부터 읽어 온 데이터를 10진수로 변환한 뒤 전달받은 구조체에 입력
	*/
	current_time->sec=bcdTodec(ds3231_read_time_buff[0]);
	current_time->min=bcdTodec(ds3231_read_time_buff[1]);

	/*
	시간 레지스터(0x02)로부터 읽어 온 데이터의 6번 비트(0x40), 5번 비트(0x20)을 확인해 
	High(1)로 설정되있을 경우 구조체 변수에 AM, PM 설정을 한다 (24시간 표기일 경우 6번 비트 Low(0))
	*/
	if((ds3231_read_time_buff[2]&0x40)!=0)	//BIT6!=0 이므로 12시간 표기
	{
		if((ds3231_read_time_buff[2]&0x20)==0)	//BIT5==0 이므로 AM
		{
			current_time->hour_select.am_pm_24=ds3231_AM;
		}
		else	//BIT5!=0 이므로 PM
		{
			current_time->hour_select.am_pm_24=ds3231_PM;
		}
	}
	else
	{
		//24시간 표시 설정일 경우 (6번 비트 0, 5번 비트 2x 시간일 때만 1)
	}
	current_time->hour_select.hour=bcdTodec(ds3231_read_time_buff[2]);
	current_time->day=bcdTodec(ds3231_read_time_buff[3]);
	current_time->date=bcdTodec(ds3231_read_time_buff[4]);
	current_time->month=bcdTodec(ds3231_read_time_buff[5]);
	current_time->year=bcdTodec(ds3231_read_time_buff[6]);

	ds3231_print_time(current_time);	//터미널에 출력할 함수 호출
}
  • 사용자가 지정한 메모리 주소(레지스터 주소)로부터 데이터를 읽어오는 함수 사용 (HAL_I2C_Mem_Read_IT)
  • 레지스터 주소를 지정해서 데이터를 읽어오기 때문에 DS3231에 설정된 레지스터 포인터 위치를 신경 쓸 필요가 없다
  • 시간 레지스터(0x02) 자체에 AM/PM or 24시간 표기 설정을 저장할 수 있으므로 필요에 따라 6, 5번 비트를 설정해준다 (비트6 HIGH-12시간 표기, LOW-24시간 표기 /  비트5 LOW-AM, HIGH-PM (24시간 표기 사용할 경우 따로 설정할 필요는 없다. 시간이 2x시간 이상일 때 사용됨))

- Read 예시

DS3231로부터 데이터를 읽어올 때 (0x00(초)~0x04(날짜))
Start + DS3231 주소(7) + Read(1) + ACK bit(1) + Seconds(8) + ACK(1) + Minutes(8)
Minutes (8) + ACK bit (1) + Hours (8) + ACK bit (1)
Temp_MSB(8) + Temp_LSB(8) + NACK bit + Stop

  • SCL 라인이 HIGH일 때, SDA 라인이 HIGH에서 LOW로 이동하는 것을 스타트 상태로 규정
  • I2C 통신의 첫 데이터는 Slave 장치 주소가 온다
  • DS3231의 경우 1101000(0x68)의 주소를 갖는데 MSB부터 채워져야 하므로 0xD0 or 0x68<<1을 사용해야 한다
  • 7-bit 주소를 사용한다면 MSB부터 주소가 채워지고 마지막 0 bit 자리에 Read(1) 또는 Write(0) 비트가 채워진다
  • 전송이 중단되기 전까지는 SCL 라인의 9번째 비트에 상응하는 SDA 라인의 9번째 비트(Acknowledge bit)가 LOW를 유지
  • 마지막 Temp(LSB) 데이터 전송 후, SCL 라인의 9번째 신호에서 SDA 라인이 HIGH를 유지하며 Not Acknowledge 상태를 나타내고 Master가 전송을 중단하기 위해 STOP 상태 발생 시킴

해당 데이터값 (날짜/요일/시간/분/초)

- Write 함수

/*
전달받은 구조체 주소를 통해 DS3231에 전송할 시간 데이터 버퍼를 채움
*/
void ds3231_write_time(ds3231_time *ds3231_write_time_struct)
{
	uint8_t write_buf[7];	//전송에 사용할 데이터 버퍼 배열 선언
	
	//입력한 10진수 시간 데이터를 2진수화
	write_buf[0]=decTobcd(ds3231_write_time_struct->sec);
	write_buf[1]=decTobcd(ds3231_write_time_struct->min);
	write_buf[2]=decTobcd(ds3231_write_time_struct->hour_select.hour);
	write_buf[3]=decTobcd(ds3231_write_time_struct->day);
	write_buf[4]=decTobcd(ds3231_write_time_struct->date);
	write_buf[5]=decTobcd(ds3231_write_time_struct->month);
	write_buf[6]=decTobcd(ds3231_write_time_struct->year);

	/*
	24시간 표기가 아닌 12시간 표기(AM/PM)을 사용하는 경우엔 
	DS3231 시간(Hour) 레지스터(0x02)에 씌여질 데이터에 추가로 6, 5번 비트를 설정해줘야 한다
	*/
	switch(ds3231_write_time_struct->hour_select.am_pm_24)
	{
		case ds3231_AM :	//AM인 경우 6번 비트(12/24)만 High로 설정해주면 된다
			write_buf[2]|=0x40;
			break;
		case ds3231_PM :	//PM인 경우 6, 5번 비트를 High로 설정해줘야 한다
			write_buf[2]|=0x60;
			break;
		case ds3231_24_hour :
			break;
		default :
			break;
	}

	/*
	DS3231 초(Sec) 레지스터(0x00)부터 7개의 8비트 데이터 배열을 입력
	각 타임 레지스터는 8비트의 크기를 가지므로 0x00부터 0x06까지 순차적으로 8비트 데이터 7개가 입력된다
	*/
	HAL_I2C_Mem_Write_IT(&hi2c1,ds3231_addr,ds3231_sec_addr,I2C_MEMADD_SIZE_8BIT,
			write_buf,7);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
		//입력 완료까지 대기
	}
}

int main(void)
{
	...
  ds3231_time ds_time_default;	//구조체 변수 선언

  //구조체 변수 데이터 입력
  ds_time_default.sec=0;
  ds_time_default.min=43;
  ds_time_default.hour_select.am_pm_24=ds3231_PM;
  ds_time_default.hour_select.hour=7;
  ds_time_default.day=6;
  ds_time_default.date=21;
  ds_time_default.month=12;
  ds_time_default.year=19;

  ds3231_write_time(&ds_time_default);	//원하는 시간으로 설정한 구조체 변수 DS3231에 전송
}
  • 사용자가 지정한 시간 위치를 사용자가 입력한 데이터로 변경
  • 0x00 번지에 액세스해 8비트 크기의 배열 7개를 입력한다 (레지스터 주소는 8비트 입력 후 자동으로 1씩 증가한다)

- 예시

int main(void)
{
	...
  ds3231_time ds_time_default;	//구조체 변수 선언
  ds3231_read_time(&ds_time_default);	
  //현재 DS3231에 저장되어있는 시간 데이터를 읽어와 선언한 구조체 변수에 저장

  //DS3231에 새롭게 입력할 시간 데이터들을 설정 (위에서 선언한 변수 사용)
  ds_time_default.sec=0;
  ds_time_default.min=43;
  ds_time_default.hour_select.am_pm_24=ds3231_PM;
  ds_time_default.hour_select.hour=7;
  ds_time_default.day=6;
  ds_time_default.date=21;
  ds_time_default.month=12;
  ds_time_default.year=19;

  ds3231_write_time(&ds_time_default);	//구조체를 이용해 DS3231에 시간 데이터 입력
  ds3231_read_time(&ds_time_default);	
  //시간이 변경됐는지 확인하기 위해 타임 레지스터에 저장되어있는 시간 데이터 읽어옴
	...
}

첫줄 : 기존에 저장되어있던 시간 / 두번째 줄 : 변경 직후. 이후부턴 새로 설정된 시간에 기반해 작동

[알람]

알람 레지스터 테이블

  • DS3231엔 두 개의 알람이 존재한다
  • 알람1 - 초, 분, 시간, 요일 or 날짜. 총 4개의 데이터
  • 알람2 - 분, 시간, 요일 or 날짜. 총 3개의 데이터
  • 설정에 따라 타임 키핑 레지스터(DS3231에서 진행 중인 시간)값이 알람 시간과 일치하면 INT/SQW 핀이 High 에서 Low로 전환되면서 인터럽트가 발생한다
  • INT/SQW핀을 사용하려면 INT/SQW핀에 외부 풀업 레지스터를 연결해야 한다

인터럽트 감지에 사용될 GPIO 설정 (PINB4, 인터럽트 모드(Falling edge 감지), 풀업/풀다운 X)

- 알람 1

알람1 레지스터 테이블
알람1 마스크 비트 설정에 따른 알람 매칭

  • 알람1은 DS3231내 0x07 부터 0x0A까지 총 4바이트의 주소를 사용한다
  • 0x09에 저장되는 시간(Hour) 데이터의 6번 및 5번 비트 설정에 따라 12시간/24시간 표기 및 AM, PM이 설정된다
  • 0x0A에 저장되는 데이터의 6번 비트 설정에 따라 날짜를 기준으로 알람을 설정할지 아니면 요일을 기준으로 설정할지가 결정된다
  • 각 알람1 레지스터의 7번 비트 설정에 따라 (테이블 순서대로)
  • 1. 매 초마다 알람
  • 2. 타임 키핑 레지스터에 저장되어있는 시간과 알람1에 저장된 초가 일치할 때 알람
  • 3. 타임 키핑 레지스터에 저장되어있는 시간과 알람1에 저장된 분, 초가 일치할 때 알람
  • 4. 타임 키핑 레지스터에 저장되어있는 시간과 알람1에 저장된 시간, 분, 초가 일치할 때 알람
  • 5. 타임 키핑 레지스터에 저장되어있는 시간과 알람1에 저장된 날짜, 시간, 분, 초가 일치할 때 알람
  • 6. 타임 키핑 레지스터에 저장되어있는 시간과 알람1에 저장된 요일, 시간, 분, 초가 일치할 때 알람

- 알람1 설정 함수

void ds3231_set_alarm1(ds3231_Alarm1 *alarm1_data)
{
	uint8_t alarm1_buff[4];	//DS3231에 전송할 알람1 데이터 버퍼

	alarm1_buff[0]=decTobcd(alarm1_data->sec);	//초
	alarm1_buff[1]=decTobcd(alarm1_data->min);	//분
	alarm1_buff[2]=decTobcd(alarm1_data->hour_select.hour);	//시간
	alarm1_buff[3]=decTobcd(alarm1_data->day_date_select.value);	//날짜 or 요일값

	/*
	12/24 시간 및 AM, PM 설정
	*/
	switch(alarm1_data->hour_select.am_pm_24)
	{
		case ds3231_AM :	//전달된 구조체의 알람 시간이 AM일 경우
			alarm1_buff[2]|=0x40;	//BIT6 Logic High=12시간 표기, BIT5 Logic Low=AM
			break;
		case ds3231_PM :	//전달된 구조체의 알람 시간이 PM일 경우
			alarm1_buff[2]|=0x60;	//BIT6 Logic High=12시간 표기, BIT5 Logic High=PM
			break;
		case ds3231_24_hour :	//전달된 구조체의 알람 시간이 24시간 표기일 경우 (별도 설정 필요 X)
			break;
		default :
			break;
	}

	/*
	알람 기준을 요일로 할지 아니면 날짜로 할지를 설정
	*/
	switch(alarm1_data->day_date_select.day_or_date)
	{
		case ds3231_date :	//날짜 설정, BIT6 - Low
			break;
		case ds3231_day :
			alarm1_buff[3]|=0x40;	//요일 설정, BIT6 - High
			break;
		default :
			break;
	}

	HAL_I2C_Mem_Write_IT(&hi2c1,(uint16_t)ds3231_addr,(uint16_t)ds3231_alarm1_addr,I2C_MEMADD_SIZE_8BIT,
			(uint8_t*)alarm1_buff,4);
	//ds3231_alarm1_addr (0x07)에 액세스해 각 1바이트 크기를 가진 알람1 시간 데이터 4개를 전송
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
		//전송 완료까지 대기
	}
}

- DS3231에 저장된 알람1 데이터를 읽어오는 함수와 알람1 구조체 출력 함수

/*
전달받은 구조체에 저장된 알람1 시간을 출력하는 함수
*/
void ds3231_print_alarm1(ds3231_Alarm1 *current_alarm1)
{
	switch (current_alarm1->day_date_select.day_or_date)	//0x0A - Alarm1 DAY/DATE Register
	{
		case ds3231_date :	//BIT6 Low - Date
			printf("Date : %d   ",(current_alarm1->day_date_select.value)&0x3F);
			break;
		case ds3231_day :	//BIT6 High - Day
			printf("Day : %d   ",(current_alarm1->day_date_select.value)&0x0F);
			break;
		default :
			break;
	}

	switch (current_alarm1->hour_select.am_pm_24)	//0x09 - Alarm1 Hour Register
	{
		case ds3231_AM :	//BIT6 - High, BIT5 - Low
			printf("AM : %d   ",current_alarm1->hour_select.hour);
			break;
		case ds3231_PM :	//BIT6 - High, BIT5 - High
			printf("PM : %d   ",current_alarm1->hour_select.hour);
			break;
		case ds3231_24_hour :	//BIT6 - Low, BIT5 - 2x 시간일 때 High
			printf("24 : %d   ",current_alarm1->hour_select.hour);
			break;
		default :
			break;
	}
	printf("min : %d   sec : %d\r\n",current_alarm1->min,current_alarm1->sec);
}

/*
DS3231로부터 읽어 온 알람1 시간을 전달받은 구조체에 입력하는 함수
*/
void ds3231_read_alarm1(ds3231_Alarm1 *current_alarm1)
{
	uint8_t read_alarm1_buff[4];	//DS3231로부터 읽어 온 알람1 시간을 저장할 데이터 버퍼
    
	HAL_I2C_Mem_Read_IT(&hi2c1,ds3231_addr,ds3231_alarm1_addr,I2C_MEMADD_SIZE_8BIT,read_alarm1_buff,4);
	//ds3231_alarm1_addr (0x07)에 액세스해 0x0A까지 1바이트 크기를 가진 4개의 알람1 데이터를 읽어 옴
    
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
		//수신 완료까지 대기
	}
    
	/*
	수신한 데이터를 10진수로 변환 후 전달받은 구조체에 입력
	*/
	current_alarm1->sec=bcdTodec(read_alarm1_buff[0]);
	current_alarm1->min=bcdTodec(read_alarm1_buff[1]);

	if((read_alarm1_buff[2]&0x40)!=0)	//12시간 표기를 사용할 경우
	{
		if((read_alarm1_buff[2]&0x20)==0)	//알람 설정 시간이 AM일 경우
		{
			current_alarm1->hour_select.am_pm_24=ds3231_AM;
			current_alarm1->hour_select.hour=bcdTodec(read_alarm1_buff[2]&0x1F);
			//12시간 표기를 사용할 경우, 시간 레지스터에서 유효한 시간 데이터는 BIT 0 ~ BIT 4 까지
		}
		else	//알람 설정 시간이 PM일 경우
		{
			current_alarm1->hour_select.am_pm_24=ds3231_PM;
			current_alarm1->hour_select.hour=bcdTodec(read_alarm1_buff[2]&0x1F);
		}
	}
	else	//24시간 표기
	{
		current_alarm1->hour_select.hour=bcdTodec(read_alarm1_buff[2]);	
	}

	if((read_alarm1_buff[3]&0x40)!=0)	//Day일 경우 (BIT 6 - High)
	{
		current_alarm1->day_date_select.day_or_date=ds3231_day;
		current_alarm1->day_date_select.value=bcdTodec(read_alarm1_buff[3]&0x0F);
		//Day 사용할 경우, 유효한 날짜 데이터는 BIT 0 ~ BIT 3까지
	}
	else	//Date일 경우 (BIT 6 - Low)
	{
		current_alarm1->day_date_select.day_or_date=ds3231_date;
		current_alarm1->day_date_select.value=bcdTodec(read_alarm1_buff[3]&0x3F);
		//Date 사용할 경우, 유효한 날짜 데이터는 BIT 0 ~ BIT 5까지
	}

	ds3231_print_alarm1(current_alarm1);	//읽어 온 알람1 데이터를 출력하기 위한 함수 호출
}

- 알람1 활성화 함수

/*
알람1을 활성화 or 비활성화하는 함수
Control(0x0E), Control/Status(0x0F)에 현재 설정되어있는 값을 읽어 온 뒤
이 값에 활성화 또는 비활성화에 필요한 설정값만을 수정&입력하고 이 값을 DS3231에 재전송
*/
void ds3231_enable_alarm1(int enable)
{
	uint8_t ctrl_stat_buff[2];	//읽어 온 Control(0x0E), Contorl/Status(0x0F) 값을 저장할 데이터 버퍼 배열
    
	HAL_I2C_Mem_Read_IT(&hi2c1,ds3231_addr,ds3231_ctrl_addr,I2C_MEMADD_SIZE_8BIT,ctrl_stat_buff,2);
	//컨트롤 레지스터(0x0E)에 액세스해 컨트롤/스테이터스(0x0F) 레지스터까지 2바이트 데이터를 읽어온다
    
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
		//수신 완료까지 대기
	}

	if(enable==0)	//알람1 비활성화
	{
		ctrl_stat_buff[0]&=~(0x01);	//컨트롤 레지스터 비트0 A1IE 비활성화
	}
	else	//알람1 활성화
	{
		ctrl_stat_buff[0]|=(0x05);	//컨트롤 레지스터 비트3 INTCN, 비트0 A1IE 활성화
		ctrl_stat_buff[1]&=~(0x03);	//컨트롤/스테이터스 레지스터 비트1(A2F), 비트0(A1F) Clear
	}
    
	HAL_I2C_Mem_Write_IT(&hi2c1,ds3231_addr,ds3231_ctrl_addr,I2C_MEMADD_SIZE_8BIT,ctrl_stat_buff,2);
	//컨트롤 레지스터(0x0E)에 액세스해 필요에 따라 수정된 컨트롤, 컨트롤/스테이터스 레지스터 값을 전송
    
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
		//전송 완료까지 대기
	}
}

컨트롤 레지스터 (0x0E)
스테이터스 레지스터 (0x0F)

  • 알람1을 사용하기 위해선 컨트롤 레지스터(0x0E)에 A1IE(Alarm 1 Interrupt Enable) 비트와 INTCN (Interrupt Control) 비트를 활성화(Logic 1) 해야 한다
  • 위 설정 상태에서 타임 키핑 레지스터에 저장된 값과 알람1에 저장된 값이 일치할 때, 스테이터스 레지스터(0x0F)의 A1F (Alarm 1 Flag)비트가 Logic 1로 변경되고 High 상태를 유지하던 INT/SQW 핀이 Low로 전환되면서 인터럽트가 발생한다
  • (A1F=1 일 때, SQW-HIGH / A1F=0 일 때, SQW-LOW)
  • 알람을 재활성화 하기 위해선 A1F 비트를 0으로 재설정해야 한다

- 예시

int main(void)
{
	...
  ds3231_time ds_time_default;	//시간 구조체 변수 선언
  
  //DS3231에 입력할 구조체 변수 시간 설정
  ds_time_default.sec=50;
  ds_time_default.min=43;
  ds_time_default.hour_select.am_pm_24=ds3231_PM;
  ds_time_default.hour_select.hour=7;
  ds_time_default.day=6;
  ds_time_default.date=21;
  ds_time_default.month=12;
  ds_time_default.year=19;
  
  ds3231_write_time(&ds_time_default);	//DS3231에 입력한 구조체 변수 값들을 전송
  
  
  ds3231_Alarm1 alarm1_default;	//알람1 구조체 변수 선언
  
  ds3231_read_alarm1(&alarm1_default);	//DS3231에 현재 설정된 알람1 데이터 읽어오는 함수 호출
  
  //DS3231에 입력할 알람1 구조체 변수 시간 설정
  alarm1_default.sec=0;
  alarm1_default.min=44;
  alarm1_default.hour_select.am_pm_24=ds3231_PM;
  alarm1_default.hour_select.hour=7;
  alarm1_default.day_date_select.value=6;
  alarm1_default.day_date_select.day_or_date=ds3231_day;
  
  ds3231_set_alarm1(&alarm1_default);	//DS3231에 입력한 알람1 구조체 변수 값들 전송
  ds3231_read_alarm1(&alarm1_default);	//DS3231에 설정된 알람1 값 읽어옴

  ds3231_enable_alarm1(1);	//알람1 활성화 (0 입력시 비활성화)
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  ds3231_read_time(&ds_time_default);
	  HAL_Delay(1000);
  }
}

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	printf("external interrupt occurred\r\n");
	if (GPIO_Pin == Alarm_Interrupt_Line_Pin)
	{
		printf("Alarm interrupt occurred\r\n");
		HAL_GPIO_TogglePin(LD1_GPIO_Port,LD1_Pin);
	}
}

알람1으로 설정한 Day 6, PM 7, 44분, 0초에 인터럽트가 발생
INT/SQW 핀이 HIGH 상태를 유지하다 타임 키핑 레지스터에 저장된 시간과 알람1에 설정된 시간이 일치하면서 LOW로 전환되어 인터럽트 발생

  • A1F를 0으로 재설정해주지 않는다면 INT/SQW 핀은 LOW 상태를 계속 유지한다
  • 따라서 알람을 재활성화 하기 위해선 A1F 비트를 0으로 재설정해줘야 한다

- 알람2

알람2 레지스터 테이블
알람2 마스크 비트 테이블

  • 0x0B~0x0D까지 1바이트씩 총 3바이트의 레지스터 주소를 사용
  • 알람1과의 차이점은 초를 설정할 수 없다는 점이다 (분, 시간, 요일 or 날짜. 세 개만 설정 가능)
  • 그외에 컨트롤 레지스터에서 A2IE 비트, 스테이터스 비트에서 A2F 비트를 설정해줘야 한다는 것과
  • 마스크 비트 설정에 따른 알람 차이점이 존재한다 (마스크 비트 테이블 순서대로)
  • 1. 매 분마다 알람 발생 (매 분 00초)
  • 2. 타임 키핑 레지스터에 저장되어있는 시간과 알람2에 저장된 분이 일치할 때 알람
  • 3. 타임 키핑 레지스터에 저장되어있는 시간과 알람2에 저장된 시간, 분이 일치할 때 알람
  • 4. 타임 키핑 레지스터에 저장되어있는 시간과 알람2에 저장된 날짜, 시간, 분이 일치할 때 알람
  • 5. 타임 키핑 레지스터에 저장되어있는 시간과 알람2에 저장된 요일, 시간, 분이 일치할 때 알람

- 알람2 코드 (알람1에서 초를 설정하는 부분이 빠진 것과 알람2 설정에 따른 레지스터 주소 변경 외엔 차이점이 없다)

void ds3231_print_alarm2(ds3231_Alarm2 *current_alarm2)
{
	switch (current_alarm2->day_date_select.day_or_date)
	{
		case ds3231_date :	//bit6 Low
			printf("Date : %d   ",(current_alarm2->day_date_select.value)&0x3F);
			break;
		case ds3231_day :	//bit6 High
			printf("Day : %d   ",(current_alarm2->day_date_select.value)&0x0F);
			break;
		default :
			break;
	}

	switch (current_alarm2->hour_select.am_pm_24)
	{
		case ds3231_AM :
			printf("AM : %d   ",current_alarm2->hour_select.hour);
			break;
		case ds3231_PM :
			printf("PM : %d   ",current_alarm2->hour_select.hour);
			break;
		case ds3231_24_hour :
			printf("24 : %d   ",current_alarm2->hour_select.hour);
			break;
		default :
			break;
	}
	printf("min : %d\r\n",current_alarm2->min);
}

void ds3231_read_alarm2(ds3231_Alarm2 *current_alarm2)
{
	uint8_t read_alarm2_buff[3];
	HAL_I2C_Mem_Read_IT(&hi2c1,ds3231_addr,ds3231_alarm2_addr,I2C_MEMADD_SIZE_8BIT,read_alarm2_buff,3);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{

	}
	current_alarm2->min=bcdTodec(read_alarm2_buff[0]);

	if((read_alarm2_buff[1]&0x40)!=0)
	{
		if((read_alarm2_buff[1]&0x20)==0)
		{
			current_alarm2->hour_select.am_pm_24=ds3231_AM;
			current_alarm2->hour_select.hour=bcdTodec(read_alarm2_buff[1]&0x1F);
		}
		else
		{
			current_alarm2->hour_select.am_pm_24=ds3231_PM;
			current_alarm2->hour_select.hour=bcdTodec(read_alarm2_buff[1]&0x1F);
		}
	}
	else
	{
		current_alarm2->hour_select.hour=bcdTodec(read_alarm2_buff[2]);	//24시간 표기
	}

	if((read_alarm2_buff[2]&0x40)!=0)	//Day or Date
	{
		current_alarm2->day_date_select.day_or_date=ds3231_day;	//Day=BIT6 High
		current_alarm2->day_date_select.value=bcdTodec(read_alarm2_buff[2]&0x0F);
	}
	else
	{
		current_alarm2->day_date_select.day_or_date=ds3231_date;	//Date=BIT6 Low
		current_alarm2->day_date_select.value=bcdTodec(read_alarm2_buff[2]&0x3F);
	}

	ds3231_print_alarm2(current_alarm2);
}

void ds3231_set_alarm2(ds3231_Alarm2 *alarm2_data)
{
	uint8_t alarm2_buff[4];

	alarm2_buff[0]=decTobcd(alarm2_data->min);
	alarm2_buff[1]=decTobcd(alarm2_data->hour_select.hour);
	alarm2_buff[2]=decTobcd(alarm2_data->day_date_select.value);

	switch(alarm2_data->hour_select.am_pm_24)
	{
		case ds3231_AM :
			alarm2_buff[1]|=0x40;	//BIT5 AM/PM, Logic High=PM
			break;
		case ds3231_PM :
			alarm2_buff[1]|=0x60;	//BIT6 12/24, Logic High=12
			break;
		case ds3231_24_hour :
			break;
		default :
			break;
	}

	switch(alarm2_data->day_date_select.day_or_date)
	{
		case ds3231_date :
			break;
		case ds3231_day :
			alarm2_buff[2]|=0x40;	//day-BIT6 High
			break;
		default :
			break;
	}

	HAL_I2C_Mem_Write_IT(&hi2c1,(uint16_t)ds3231_addr,(uint16_t)ds3231_alarm2_addr,I2C_MEMADD_SIZE_8BIT,
			(uint8_t*)alarm2_buff,3);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{

	}
}

void ds3231_enable_alarm2(int enable)
{
	uint8_t ctrl_stat_buff[2];
	HAL_I2C_Mem_Read_IT(&hi2c1,ds3231_addr,ds3231_ctrl_addr,I2C_MEMADD_SIZE_8BIT,ctrl_stat_buff,2);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{

	}

	if(enable==0)
	{
		ctrl_stat_buff[0]&=~(0x02);	//A2IE disable
	}
	else
	{
		ctrl_stat_buff[0]|=(0x06);	//INTCN, A2IE enable
		ctrl_stat_buff[1]&=~(0x03);	//A1F, A2F Clear
	}
	HAL_I2C_Mem_Write_IT(&hi2c1,ds3231_addr,ds3231_ctrl_addr,I2C_MEMADD_SIZE_8BIT,ctrl_stat_buff,2);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{

	}
}

- 알람2 예시

int main(void)
{
	...
  ds3231_time ds_time_default;
  ds_time_default.sec=50;
  ds_time_default.min=29;
  ds_time_default.hour_select.am_pm_24=ds3231_AM;
  ds_time_default.hour_select.hour=10;
  ds_time_default.day=6;
  ds_time_default.date=21;
  ds_time_default.month=12;
  ds_time_default.year=19;
  ds3231_write_time(&ds_time_default);

  /*
  알람2 구조체 변수 선언 및 설정 후 DS3231에 전송
  */
  ds3231_Alarm2 alarm2_default;
  alarm2_default.min=30;
  alarm2_default.hour_select.am_pm_24=ds3231_AM;
  alarm2_default.hour_select.hour=10;
  alarm2_default.day_date_select.day_or_date=ds3231_date;
  alarm2_default.day_date_select.value=21;
  ds3231_set_alarm2(&alarm2_default);
  ds3231_read_alarm2(&alarm2_default);
  ds3231_enable_alarm2(1);
  
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  ds3231_read_time(&ds_time_default);
	  HAL_Delay(1000);
  }
}

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	printf("external interrupt occurred\r\n");
	if (GPIO_Pin == Alarm_Interrupt_Line_Pin)
	{
		printf("Alarm interrupt occurred\r\n");
		HAL_GPIO_TogglePin(LD1_GPIO_Port,LD1_Pin);
	}
}

설정된 알람2 값이 타임 키핑 레지스터에 저장된 값과 일치하면서 인터럽트 발생

  • 알람2도 알람1과 같이 인터럽트 발생 후 스테이터스 레지스터의 A2F 비트를 0으로 재설정해줘야 알람이 재활성화된다

- 마스크 비트 단순 예시 (매 초마다 알람 인터럽트가 발생하도록 설정했을 때)

int alarm_every_sec=0;	//알람 인터럽트 발생 후 알람 재활성화 하기 위해 사용할 전역 변수

/*
매 초마다 알람을 발생 시키려면 알람1의 모든 시간 레지스터들의 7번 비트를 1로 설정해줘야 한다
*/
void ds3231_alarm_every_sec()
{
	uint8_t alarm_buff[4]={0x80,0x80,0x80,0x80};	//매 초마다 알람 발생 시키기 위한 마스크 비트 설정
	HAL_I2C_Mem_Write_IT(&hi2c1,ds3231_addr,ds3231_alarm1_addr,I2C_MEMADD_SIZE_8BIT,alarm_buff,4);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{

	}
}

int main(void)
{
	...
    ds3231_enable_alarm1(1);	//매 초마다 알람을 발생 시킬 수 있는 건 알람1 뿐이다 (알람2는 매 분마다)
    ds3231_alarm_every_sec();
    
    while(1)
    {
      if((HAL_I2C_GetState(&hi2c1)==HAL_I2C_STATE_READY)&&(alarm_every_sec==0))
	  {
		  ds3231_read_time(&ds_time_default);
          /*
          알람 발생 이후 A1F=1, INT/SQW핀=LOW 상태를 유지해 INT/SQW핀을 통한 새 인터럽트가 발생하지 
          못하므로 A1F를 0으로 재설정해 INT/SQW핀을 다시 HIGH 상태로 만들어 준다
          */
		  ds3231_enable_alarm1(1); 
		  alarm_every_sec=1;
	  }
    }
}

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	printf("external interrupt occurred\r\n");
	if (GPIO_Pin == Alarm_Interrupt_Line_Pin)
	{
		printf("Alarm interrupt occurred\r\n");
		alarm_every_sec=0;
	}
}

매 초마다 알람 인터럽트가 발생하도록 설정한 결과
알람 인터럽트 발생 후, 스테이터스 레지스터의 A1F 비트 값을 0으로 재설정해 INT/SQW 핀을 다시 HIGH로 만들어 다음 인터럽트가 발생할 수 있게끔 만들어준다

- 온도

온도 레지스터

  • DS3231의 온도 레지스터엔 64초를 주기로 갱신되는 0.25℃ 해상도를 가진 온도값이 저장된다
  • 정수 부분(레지스터 0x11), 소수 부분(0x12) 두 부분으로 나뉘어 온도값이 저장된다
  • 소수 부분은 상위 2비트(비트6, 비트7)만 사용된다
float ds3231_temperature;	//읽어 온 온도값을 저장할 float 전역 변수

void ds3231_read_temperature()
{
	uint8_t ds3231_temp_buff[2];	//상위 8비트, 하위 8비트를 읽어와야 하므로 2개의 배열을 가진 변수 생성
	
	HAL_I2C_Mem_Read_IT(&hi2c1,ds3231_addr,ds3231_temp_addr,I2C_MEMADD_SIZE_8BIT,
			ds3231_temp_buff,2);
	//ds3231_temp_addr(0x11)에 액세스해 정수(0x11), 소수(0x12) 레지스터를 읽어온다
    
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{

	}
	ds3231_temperature=ds3231_temp_buff[0]+((ds3231_temp_buff[1]>>6)*0.25);
	//온도값을 저장하기 위해 선언한 float 전역 변수에 정수 부분과 소수 부분을 합산해 저장한다
	//소수 부분은 상위 2비트에 소수 온도 데이터가 저장되므로 이를 LSB 방향으로 6비트 비트 시프트 한 뒤 계산
	//00=0.00, 01=0.25, 10=0.5, 11=0.75
    
	printf("%.2f\r\n",ds3231_temperature);
}

+ Recent posts