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)를 통해 윈도우 화면 미러링 방법

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

안드로이드(android) 전체 화면 시계 앱(clock app) 예제 코드

mkfs.fat Device or resource busy 에러 해결법