- 작동 전압 : 2.3~5.5V
- 통신 : I2C
- Fast(400kHz) 모드 지원
- 배터리 용량이 부족할 경우, 시간 흐름이 부정확해짐
- (사용 모듈의 경우 INT/SQW 핀에 풀업 레지스터 구성되어 있음)
[내부 레지스터 주소 및 데이터]
- 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]
- 데이터를 읽어 올 때, SDA 라인의 첫 1바이트는 MSB부터 7 비트는 DS3231(Slave) 주소 + Read(1) 비트로 구성된다
- Write 모드 사용시에만 레지스터 포인터가 설정되는데 이는 DS3231로부터 데이터를 읽어오는 시작 위치가 된다
- ex) Write 모드 마지막으로 분이 설정된 경우 레지스터 포인터엔 0x01 주소가 설정되고 DS3231로부터 데이터를 읽어올 때 0x00부터 읽어오는게 아니라 레지스터 포인터에 저장된 0x01부터 설정된 크기만큼의 데이터를 읽어온다
- 따라서 필요에 따라 레지스터 포인터를 원하는 주소로 재설정한 후 Read 를 시작해야 한다
[Write]
- 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 설정]
- Fast Mode 설정, 7-bit 주소 설정, 인터럽트 활성화
[코드]
- 정의
/* 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 예시
- 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핀에 외부 풀업 레지스터를 연결해야 한다
- 알람 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)
{
//전송 완료까지 대기
}
}
- 알람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);
}
}
- A1F를 0으로 재설정해주지 않는다면 INT/SQW 핀은 LOW 상태를 계속 유지한다
- 따라서 알람을 재활성화 하기 위해선 A1F 비트를 0으로 재설정해줘야 한다
- 알람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도 알람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;
}
}
- 온도
- 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);
}
- TrueStudio를 사용하는 경우, printf를 통한 float 변수 출력이 제대로 되지 않는 문제가 있는데 이는 프로젝트 설정을 변경해줌으로써 해결할 수 있다
- https://www.google.co.kr/search?newwindow=1&sxsrf=ACYBGNRQK8mHRqzyYmt66AzcjuymmnkLpg%3A1577109903830&source=hp&ei=j8kAXt2iMKH2hwOYvrfQBA&q=truestudio+float&oq=truestudio+float&gs_l=psy-ab.3..0j0i30l2j0i8i30j0i333j0i5i30l2.203.2123..2284...0.0..0.124.1685.1j15......0....1..gws-wiz.......0i131.oml1Wn25NWA&ved=0ahUKEwidptmc-MvmAhUh-2EKHRjfDUoQ4dUDCAY&uact=5
'STM32' 카테고리의 다른 글
STM32 - UART 기반 PMS7003 먼지 센서 제어 (17) | 2019.12.04 |
---|---|
P-NUCLEO-WB55 보드 무선 통신 사용 설정 (1) | 2019.10.16 |
STM32 - UART 통신 기반 SenseAir S8 LP CO2 감지 센서 (0) | 2019.10.11 |
STM32 - I2C 통신을 이용한 SSD1306 128x64 OLED 제어 (4) | 2019.09.17 |
STM32 - SPI를 이용한 SK6812 RGBW LED 제어 (2) | 2019.07.25 |