RTC DS1302 linux 예제 C 코드

본 글은 시간 저장을 위해 사용하는 RTC 칩 중 하나인 DS1302의 리눅스 예제 C 코드에 대해 기술한다. 


DS1302의 datasheet를 보면 아래와 같은 구조로 연결이 이루어진다.

아래 그림처럼 데이터를 읽고 쓸 때 CE핀을 high로 올려주고, 읽고 쓰지 않을 때는 low로 내려준다. SCLK과 I/O핀은 아래 그림과 같이 일반적인 i2c나 spi에서 데이터를 읽고 쓸 때 사용한다. 본 글의 예제 코드는 gpio를 컨트롤 하여 아래 그림의 데이터 전송 형식대로 구현 했다.


DS1302 example c code

연결된 핀의 GPIO와 레이스터 값을 선언된 내용과, 핀에 할당된 GPIO를 초기화 코드는 아래와 같다.


#define DS1302_CE_GPIO (1)
#define DS1302_DATA_GPIO (12)
#define DS1302_CLK_GPIO (11)

#define DS1302_SEC_WRITE    0x80
#define DS1302_MIN_WRITE    0x82
#define DS1302_HOUR_WRITE   0x84
#define DS1302_DATE_WRITE   0x86
#define DS1302_MONTH_WRITE  0x88
#define DS1302_YEAR_WRITE   0x8C
#define DS1302_WP_WRITE   0x8E
#define DS1302_TRICKLE_WRITE 0x90

#define DS1302_SEC_READ     0x81
#define DS1302_MIN_READ     0x83
#define DS1302_HOUR_READ    0x85
#define DS1302_DATE_READ    0x87
#define DS1302_MONTH_READ   0x89
#define DS1302_YEAR_READ    0x8D
#define DS1302_WP_READ    0x8F
#define DS1302_TRICKLE_READ 0x91

#define WRITE_ENABLE 0x00
#define WRITE_DISABLE 0x80

#define RTC_CLCK_LEN 0x08 /* Size of clock burst */
#define RTC_ADDR_CTRL 0x07 /* Address of control register */
#define RTC_ADDR_YEAR 0x06 /* Address of year register */
#define RTC_ADDR_DAY 0x05 /* Address of day of week register */
#define RTC_ADDR_MON 0x04 /* Address of month register */
#define RTC_ADDR_DATE 0x03 /* Address of day of month register */
#define RTC_ADDR_HOUR 0x02 /* Address of hour register */
#define RTC_ADDR_MIN 0x01 /* Address of minute register */
#define RTC_ADDR_SEC 0x00 /* Address of second register */


void DS1302_if_init()
{
gpio_export(DS1302_DATA_GPIO);
gpio_set_outdir(DS1302_DATA_GPIO,1);
gpio_set(DS1302_DATA_GPIO,0);
gpio_export(DS1302_CLK_GPIO);
gpio_set_outdir(DS1302_CLK_GPIO,1);
gpio_set(DS1302_CLK_GPIO,0);
gpio_export(DS1302_CE_GPIO);
gpio_set_outdir(DS1302_CE_GPIO,1);
gpio_set(DS1302_CE_GPIO,0);
}

GPIO를 이용한 한 바이트 데이터 쓰기/읽기 코드는 아래와 같다.

unsigned char DS1302_read_byte()
{
int i = 0;
unsigned int in_level = 0;
unsigned char val = 0;
gpio_set_outdir(DS1302_DATA_GPIO,0);
usleep(2);

for (i=0; i<8; i++) 
{
val >>= 1;
in_level = gpio_get(DS1302_DATA_GPIO);
if (in_level == 1) val |= 0x80;
else val &= 0x7F;
gpio_set(DS1302_CLK_GPIO,1);
usleep(2);
gpio_set(DS1302_CLK_GPIO,0);
usleep(2);
}

return val;
}

void DS1302_write_byte(unsigned char data)
{
int i = 0;
unsigned char val = data;

gpio_set_outdir(DS1302_DATA_GPIO,1);
usleep(2);

for (i=0; i<8; i++) 
{
if (val & 1) gpio_set(DS1302_DATA_GPIO,1);
else gpio_set(DS1302_DATA_GPIO,0);
val >>= 1; 
usleep(2);
gpio_set(DS1302_CLK_GPIO,1);
usleep(2);
gpio_set(DS1302_CLK_GPIO,0);
usleep(2); 
}

gpio_set(DS1302_DATA_GPIO,0);
}

데이터를 버스트로 읽고/쓰는 코드는 아래와 같다. 위의 한 바이트 데이터를 읽고/쓰는 코드를 사용하여 만들어 졌고, CE핀 조정도 여기에서 한다.

int DS1302_read_bust(unsigned char *buffer, int len)
{
int i = 0;
if(!buffer) return -1;
gpio_set(DS1302_CE_GPIO,1);
usleep(2);

DS1302_write_byte(0xBF);
for(i=0;i<len;i++)
{
buffer[i] = DS1302_read_byte();
}

gpio_set(DS1302_CE_GPIO,0);

return len;
}

int DS1302_write_bust(unsigned char *buffer, int len)
{
int i = 0;
if(!buffer) return -1;
gpio_set(DS1302_CE_GPIO,1);
usleep(2);

DS1302_write_byte(DS1302_WP_WRITE);
DS1302_write_byte(WRITE_ENABLE);

DS1302_write_byte(0xBE);
for(i=0;i<len;i++)
{
DS1302_write_byte(buffer[i]);
}

gpio_set(DS1302_CE_GPIO,0);

return len;
}

위의 코드들을 사용해 실제 시간을 쓰고 읽는 코드는 아래와 같다. 시간은 bcd형태의 데이터로 저장된다.

int DS1302_write_time(time_t time_sec)
{
struct tm ltm;
unsigned char buffer[RTC_CLCK_LEN] = {0,};

if(time_sec == -1) return -1;
if(gmtime_r(&time_sec,&ltm) == NULL) return -1;
buffer[RTC_ADDR_SEC] = bin2bcd(ltm.tm_sec); // 0~59
buffer[RTC_ADDR_MIN] = bin2bcd(ltm.tm_min); // 0~59
buffer[RTC_ADDR_HOUR] = bin2bcd(ltm.tm_hour); // 0~23
buffer[RTC_ADDR_DATE] = bin2bcd(ltm.tm_mday); // 1~31
buffer[RTC_ADDR_MON] = bin2bcd(ltm.tm_mon+1); // 1~12 
buffer[RTC_ADDR_DAY] = 0;
buffer[RTC_ADDR_YEAR] = bin2bcd((ltm.tm_year+1900) % 100); // 0~99
buffer[RTC_ADDR_CTRL] = WRITE_DISABLE;
DS1302_write_bust(buffer, RTC_CLCK_LEN);
return 0;
}


time_t DS1302_read_time()
{
struct tm rtc_tm;
unsigned char buffer[RTC_CLCK_LEN-1] = {0,};
DS1302_read_bust(buffer,RTC_CLCK_LEN-1);
rtc_tm.tm_sec = (((buffer[RTC_ADDR_SEC] & 0x70) >> 4) * 10) + (buffer[RTC_ADDR_SEC]  & 0x0F);
rtc_tm.tm_min = (((buffer[RTC_ADDR_MIN] & 0x70) >> 4) * 10) + (buffer[RTC_ADDR_MIN] & 0x0F);
rtc_tm.tm_hour = (((buffer[RTC_ADDR_HOUR] & 0x30) >> 4) * 10) + (buffer[RTC_ADDR_HOUR] & 0x0F);
rtc_tm.tm_mday = (((buffer[RTC_ADDR_DATE] & 0x30) >> 4) * 10) + (buffer[RTC_ADDR_DATE] & 0x0F);
rtc_tm.tm_mon = (((buffer[RTC_ADDR_MON] & 0x10) >> 4) * 10) + (buffer[RTC_ADDR_MON] & 0x0F) - 1;
rtc_tm.tm_year = ((((buffer[RTC_ADDR_YEAR] & 0xF0) >> 4) * 10) + (buffer[RTC_ADDR_YEAR] & 0x0F) + 2000) - 1900;
rtc_tm.tm_wday = 0;
rtc_tm.tm_yday = 0; 
rtc_tm.tm_isdst = -1;

if ( ((rtc_tm.tm_year + 1900) < 2000) || ((rtc_tm.tm_year + 1900)  > 2099) 
|| ((rtc_tm.tm_mon +1) < 1) || ((rtc_tm.tm_mon +1) > 12)
||(rtc_tm.tm_mday < 1) || (rtc_tm.tm_mday > 31) 
|| (rtc_tm.tm_hour < 0) || (rtc_tm.tm_hour > 23) 
|| (rtc_tm.tm_min < 0) || (rtc_tm.tm_min > 59) 
|| (rtc_tm.tm_sec < 0) || (rtc_tm.tm_sec > 59) )
{
return -1;
}

return mktime(&rtc_tm);
}

bcd 형태로 변환하는 코드는 아래와 같이 간단하게 구현할 수 있다.

int bin2bcd(int bin)
{
int bcd = 0;
int n = 0;
int count = 0;
int dig = 0;
int num = bin;

for(n = 0; n<4;n++)
{
dig = num %10;
num = num/10;
bcd = (dig<<count) | bcd;
count += 4;
}
return bcd;
}


[관련 포스트]

댓글

이 블로그의 인기 게시물

간단한 cfar 알고리즘에 대해

windows에서 간단하게 크롬캐스트(Chromecast)를 통해 윈도우 화면 미러링 방법

쉽게 설명한 파티클 필터(particle filter) 동작 원리와 예제

base64 인코딩 디코딩 예제 c 소스

간단한 칼만 필터(Kalman Filter) 소스 코드와 사용 예제