임베디드시스템/AVR
[AVR] DS1302 RTC 값을 I2C LCD에 Display
KHJ_940803
2021. 11. 4. 10:54
목표 : Pc의 comport master를 이용해 시간을 설정하고 설정된 시간과 날짜를 I2C LCD 에 출력한다.
개발 툴 : atmel studio
개발 보드 : ATMEGA128A
PC Uart 통신 툴 : Comport master
개발 기간 : 2021년 8월 22일
소스코드
DS1302.c
/*
* DS1302.c
*
* Created: 2019-07-11 오후 2:09:16
*/
#include "DS1302.h"
struct _ds1302 stTime;
char *dayofweek[7] = {"SUN", "MON", "TUE", "WED",
"THU", "FRI", "SAT"};
void DS1302_Init(void)
{
DS1302_CLK_DDR |= (1<<DS1302_CLK);
DS1302_DAT_DDR |= (1<<DS1302_DAT);
DS1302_RST_DDR |= (1<<DS1302_RST);
}
void DS1302_Selected(void)
{
DS1302_RST_PORT |= (1<<DS1302_RST); // CE High
}
void DS1302_Deselected(void)
{
DS1302_RST_PORT &= ~(1<<DS1302_RST); // CE High
}
void DS1302_Clock(void)
{
DS1302_CLK_PORT &= ~(1<<DS1302_CLK); // clk l->h->l
DS1302_CLK_PORT |= (1<<DS1302_CLK);
DS1302_CLK_PORT &= ~(1<<DS1302_CLK);
}
void DS1302_DataBitSet(void)
{
DS1302_DAT_PORT |= (1<<DS1302_DAT); // data bit high
}
void DS1302_DataBitReset(void)
{
DS1302_DAT_PORT &= ~(1<<DS1302_DAT); // data bit low
}
uint8_t DS1302_Read_DataPin(void)
{
return (DS1302_DAT_PIN & (1<<DS1302_DAT));
}
void DS1302_Change_ReadMode(void)
{
DS1302_DAT_DDR &= ~(1<<DS1302_DAT); // read mode
}
void DS1302_Change_WriteMode(void)
{
DS1302_DAT_DDR |= (1<<DS1302_DAT); // write mode
}
uint8_t decimal_to_bcd(uint8_t decimal)
{
return ( ((decimal/10)<<4) | (decimal%10) );
}
uint8_t bcd_to_decimal(uint8_t bcd)
{
return ( ((bcd>>4) * 10) + (bcd & 0x0f) );
}
void DS1302_TxData(uint8_t txData)
{
DS1302_Change_WriteMode();
for (int i=0; i<8; i++)
{
if (txData & (1<<i))
DS1302_DataBitSet();
else
DS1302_DataBitReset();
DS1302_Clock();
}
}
uint8_t DS1302_RxData(void)
{
uint8_t rxData = 0;
DS1302_Change_ReadMode();
for (int i=0; i<8; i++)
{
rxData |= DS1302_Read_DataPin() ? (1<<i) : 0;
// data = A ? b : c; A가 참이면 b 반환, A가 거짓이면 c 반환
DS1302_Clock();
}
return rxData;
}
void DS1302_WriteData(uint8_t address, uint8_t data)
{
DS1302_Selected();
DS1302_TxData(address);
DS1302_TxData(decimal_to_bcd(data));
DS1302_Deselected();
}
uint8_t DS1302_ReadData(uint8_t address)
{
uint8_t rxData = 0;
DS1302_Selected();
DS1302_TxData(address+1);
rxData = DS1302_RxData();
DS1302_Deselected();
return bcd_to_decimal(rxData);
}
void DS1302_GetTime(DS1302 *timeData)
{
timeData->seconds = DS1302_ReadData(ADDRESS_SECONDS); // sec
timeData->minutes = DS1302_ReadData(ADDRESS_MINUTES);
timeData->hour = DS1302_ReadData(ADDRESS_HOUR);
}
void DS1302_GetDate(DS1302 *dateData)
{
dateData->date = DS1302_ReadData(ADDRESS_DATE);
dateData->month = DS1302_ReadData(ADDRESS_MONTH);
dateData->dayofweek = DS1302_ReadData(ADDRESS_DAYOFWEEK);
dateData->year = DS1302_ReadData(ADDRESS_YEAR);
}
void DS1302_SetTimeDates(DS1302 timeDate)
{
DS1302_WriteData(ADDRESS_SECONDS, timeDate.seconds);
DS1302_WriteData(ADDRESS_MINUTES, timeDate.minutes);
DS1302_WriteData(ADDRESS_HOUR, timeDate.hour);
DS1302_WriteData(ADDRESS_DATE, timeDate.date);
DS1302_WriteData(ADDRESS_MONTH, timeDate.month);
DS1302_WriteData(ADDRESS_DAYOFWEEK, timeDate.dayofweek);
DS1302_WriteData(ADDRESS_YEAR, timeDate.year);
}
DS1302.h
/*
* DS1302.h
*
* Created: 2019-07-11 오후 2:09:32
*/
#ifndef DS1302_H_
#define DS1302_H_
#include <avr/io.h>
#define DS1302_CLK_DDR DDRG
#define DS1302_CLK_PORT PORTG
#define DS1302_DAT_DDR DDRG
#define DS1302_DAT_PORT PORTG
#define DS1302_DAT_PIN PING
#define DS1302_RST_DDR DDRG
#define DS1302_RST_PORT PORTG
#define DS1302_CLK 0
#define DS1302_DAT 1
#define DS1302_RST 2
#define ADDRESS_SECONDS 0x80
#define ADDRESS_MINUTES 0x82
#define ADDRESS_HOUR 0x84
#define ADDRESS_DATE 0x86
#define ADDRESS_MONTH 0x88
#define ADDRESS_DAYOFWEEK 0x8a
#define ADDRESS_YEAR 0x8c
typedef struct _ds1302
{
uint8_t seconds;
uint8_t minutes;
uint8_t hour;
uint8_t date;
uint8_t month;
uint8_t dayofweek; //sun=1, mon=2... sat=7
uint8_t year;
uint8_t ampm; // 0:pm, 1:am
uint8_t hourMode; // 0:24, 1:12
//char *paDayofweek[7];
}DS1302;
extern struct _ds1302 stTime;
extern char *dayofweek[7];
void DS1302_Init(void);
void DS1302_Selected(void);
void DS1302_Deselected(void);
void DS1302_Clock(void);
void DS1302_DataBitSet(void);
void DS1302_DataBitReset(void);
uint8_t DS1302_Read_DataPin(void);
void DS1302_Change_ReadMode(void);
void DS1302_Change_WriteMode(void);
uint8_t decimal_to_bcd(uint8_t decimal);
uint8_t bcd_to_decimal(uint8_t bcd);
void DS1302_TxData(uint8_t txData);
uint8_t DS1302_RxData(void);
void DS1302_WriteData(uint8_t address, uint8_t data);
uint8_t DS1302_ReadData(uint8_t address);
void DS1302_GetTime(DS1302 *timeData);
void DS1302_GetDate(DS1302 *dateData);
void DS1302_SetTimeDates(DS1302 timeDate);
#endif /* DS1302_H_ */
/*
* uart0.h
*
* Created: 2021-08-17 오전 10:26:36
*/
#ifndef UART0_H_
#define UART0_H_
void UART0_init(void);
void UART0_transmit(char data);
unsigned char UART0_receive(void);
void UART0_print_string(char *str);
void UART0_tx_rx_test(void);
uint8_t isRxString();
uint8_t *getRxString();
#endif /* UART0_H_ */
uart0.c
/*
* uart0.c
*
* Created: 2021-08-17 오전 10:26:57
*/
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h> // interrupt 관련 lib가 들어 있다.
#include "uart0.h"
volatile uint8_t rxString[100]={0};
volatile uint8_t rxReadyFlag=0; // \n detect flag
volatile int rxindex=0;
// UART0로 부터 1byte가 들어 오면 이것을 자동 실행
// RX interrupt 서비스 루틴
ISR(USART0_RX_vect)
{
volatile uint8_t data;
data = UDR0; // UART 하드웨어 버퍼 UDR0를 읽어서 data에 저장
if (data == '\n' || data == '\r') // led1on\n
{ //
rxString[rxindex] = '\0';
rxindex=0;
rxReadyFlag=1; // 완전한 msg를 받았다는 신호를 알려준다.
}
else
{
rxString[rxindex++] = data; // rxString[rxindex] = data; rxindex++;
}
}
uint8_t isRxString()
{
return rxReadyFlag;
}
uint8_t *getRxString()
{
rxReadyFlag=0;
return rxString;
}
// uart0를 초기화 하는 함수
#define BAUD 9600
#define BAUD_9600 ( (F_CPU / (BAUD * 8)) -1) // 207
void UART0_init(void)
{
UBRR0H = 0x00;
UBRR0L = 207; // 9600으로 설정
UCSR0A |= ( 1 << U2X0); // 2배속 sampling속도를 8로
UCSR0C |= 0x06; // 비동기, data길이: 8bit/nonpairty/1stopbit
// UART0를 송신.수신이 가능 하도록 enable
// RXEN0 : 수신이 가능 하도록 enable
// TXEN0 : 송신이 가능 하도록
// RXCIE0 : UART0로 부터 1byte가 들어 오면 interrupt가 뜨도록 설정
UCSR0B = ( (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0));
}
// UART0로 1byte를 전송하는 함수
void UART0_transmit(char data)
{
while ( !(UCSR0A & (1 << UDRE0))) // UDRE0의 bit가 1이 되는순간 exit
;
UDR0 = data; // UDR(Uart Data Register)에 값을 넣는다.
}
// UART0로 부터 1byte를 읽어 오는 함수
unsigned char UART0_receive(void)
{
// 76543210
// 1 ===> RXC0 bit (RX complet)
while ( !(UCSR0A & (1 << RXC0))) // RXC0의 bit가 1이 되는순간 exit
; // nop : no operation
return UDR0; // 수신된 1byte를 return
}
/*
* UART0로 string(문자열: 끝은 \0)을 출력 하는 함수
*/
void UART0_print_string(char *str)
{
for (int i=0; str[i] != '\0'; i++)
{
UART0_transmit(str[i]);
}
}
menu.h
/*
* menu.h
*
* Created: 2021-08-20 오후 3:06:34
* Author: KHJ
*/
#ifndef MENU_H_
#define MENU_H_
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include <stdio.h>
#include <avr/interrupt.h> // interrupt 관련 lib가 들어 있다.
#include "uart0.h"
#include "ds1302.h"
void show_menu(void);
void menu_exec();
void updateDateTime();
uint8_t get_number();
void display_clock_lcd();
#endif /* MENU_H_ */
menu.c
/*
* menu.c
*
* Created: 2021-08-20 오후 3:06:51
* Author: KHJ
*/
#include "menu.h"
void display_clock_lcd()
{
static uint8_t prevSec =0; // 1초에 한번씩 출력 하기위한 변수
//satatic으로 선언을 하면 local 변수라도 함수가 끝난 뒤에 값을 유지한다.
char buffer[20];// lcd에 string값을 출력하기 위한 변수
DS1302_GetTime(&stTime);
DS1302_GetDate(&stTime);
if(prevSec!= stTime.seconds) // 초가 바뀌었다.
{
prevSec=stTime.seconds;
//
//sprintf를 이용하여 buffer에 원하는 값을 집어 넣을 수 있다.
//
sprintf(buffer,"date %04d-%02d-%02d", 2000+stTime.year, stTime.month, stTime.date); // '\n' 은 lcd 가 인식 못한다.
I2C_LCD_write_string_XY(0,0,buffer);
sprintf(buffer,"time %02d:%02d:%02d", stTime.hour, stTime.minutes, stTime.seconds);
I2C_LCD_write_string_XY(1,0,buffer);
}
}
void show_menu(void)
{
printf("----- MENU ------\n");
printf("1. show Time\n"); // 시 분 초
printf("2. show Date\n"); // 년 월 일
printf("3. update Date_time\n");
printf("Select : ");
}
void menu_exec()
{
char *rxData;
char yy[4], mm[4], dd[4], hh[4], min[4], ss[4];
struct _ds1302 updateData;
//gettime
//settimeyymmddhhmmss
//led0on
//led0off
//ledallon
//ledalloff
if (isRxString())
{
rxData = getRxString(); // Message를 읽어 온다.
if (strncmp(rxData, "help", sizeof("help")) == 0)
{
printf("\n-- help --\n");
printf("gettime\n");
printf("settimeyymmddhhmmss\n");
}
if (strncmp(rxData, "gettime", sizeof("gettime")) == 0)
{
DS1302_GetTime(&stTime);
DS1302_GetDate(&stTime);
printf("date %04d:%02d:%02d\n", 2000+stTime.year, stTime.month, stTime.date);
printf("time %02d:%02d:%02d\n", stTime.hour, stTime.minutes, stTime.seconds);
}
// 0123456789012345678
// settimeyymmddhhmmss
if (strncmp(rxData, "settime", 7) == 0)
{
strncpy(yy,rxData+7,2); // strncpy(yy,&rxData[7],2); --> yy[2] = 0;
strncpy(mm,rxData+9,2);
strncpy(dd,rxData+11,2);
strncpy(hh,rxData+13,2);
strncpy(min,rxData+15,2);
strncpy(ss,rxData+17,2);
updateData.year =(yy[0]-0b0110000)*10+(yy[1]-0b0110000);
updateData.month =(mm[0]-0b0110000)*10+(mm[1]-0b0110000);
updateData.date =(dd[0]-0b0110000)*10+(dd[1]-0b0110000);
updateData.hour =(hh[0]-0b0110000)*10+(hh[1]-0b0110000);
updateData.minutes =(min[0]-0b0110000)*10+(min[1]-0b0110000);
updateData.seconds =(ss[0]-0b0110000)*10+(ss[1]-0b0110000);
DS1302_SetTimeDates(updateData);
printf("\n\ntime update success!!!!\n");
DS1302_GetTime(&stTime);
DS1302_GetDate(&stTime);
printf("date %04d:%02d:%02d\n", 2000+stTime.year, stTime.month, stTime.date);
printf("time %02d:%02d:%02d\n", stTime.hour, stTime.minutes, stTime.seconds);
}
}
}
void updateDateTime()
{
uint8_t rxdata=0;
struct _ds1302 updateData;
// year:
// month:
// date:
// hour:
// minute:
// sec:
for (int i=0; i < 6; i++)
{
switch (i)
{
case 0: // 년
printf("year(00~99): ");
rxdata = get_number();
updateData.year = rxdata;
printf("set year: %d\n", updateData.year);
break;
case 1: // 월
printf("month: ");
rxdata = get_number();
updateData.month = rxdata;
printf("set month: %d\n", updateData.month);
break;
case 2: // 일
printf("date: ");
rxdata = get_number();
updateData.date = rxdata;
printf("set date: %d\n", updateData.date);
break;
case 3: // 시
printf("hour: ");
rxdata = get_number();
updateData.hour = rxdata;
printf("set hour: %d\n", updateData.hour);
break;
case 4: // 분
printf("minutes: ");
rxdata = get_number();
updateData.minutes = rxdata;
printf("set minutes: %d\n", updateData.minutes);
break;
case 5: // 초
printf("sec: ");
rxdata = get_number();
updateData.seconds = rxdata;
printf("set sec: %d\n", updateData.seconds);
break;
break;
}
}
DS1302_SetTimeDates(updateData);
return 0;
}
uint8_t get_number()
{
uint8_t rxdata=0, *rxstring;
while(isRxString()==0)
;
rxstring = getRxString();
rxdata = atoi(rxstring);
return rxdata;
}
I2C_LCD.h
/*
* I2C_LCD.h
*
* Created: 2020-01-07 오후 8:00:34
*/
#ifndef I2C_LCD_H_
#define I2C_LCD_H_
#define COMMAND_CLEAR_DISPLAY 0X01
#define COMMAND_DISPLAY_ON_OFF_BIT 2
#define COMMAND_CURSOR_ON_OFF_BIT 1
#define COMMAND_BLINK_ON_OFF_BIT 0
#define START 0x08
#define SLA_W (0x27<<1) // I2C LCD 주소 0x27 , <<1 이유는 write모드 유지
#define SLA_R (0x27<<1 | 0x01) // I2C LCD 주소 0x27 , Read모드 유지
void I2C_LCD_init(void);
void I2C_LCD_write_data(uint8_t data);
void I2C_LCD_write_command(uint8_t command);
void I2C_LCD_clear(void);
void I2C_LCD_write_string(char *string);
void I2C_LCD_goto_XY(uint8_t row, uint8_t col);
void I2C_LCD_write_string_XY(uint8_t row, uint8_t col, char *string);
#endif /* I2C_LCD_H_ */
I2C_LCD.c
/*
* I2C_LCD.c
*
* Created: 2020-01-07 오후 7:59:31
*/
// 맵핑 시켜주는 코드이다.
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include "I2C.h"
#include "I2C_LCD.h"
// 콜바이 벨류다.
//lcd에 1바이트를 display
void I2C_LCD_write_data(uint8_t data)
{
char data_u, data_l;
uint8_t data_t[4] = {0,};
data_u = (data&0xf0); // 상위 4bit 데이터
data_l = ((data<<4)&0xf0); // 하위 4bit 데이터
data_t[0] = data_u|0x0D; //en=1, rs=1 |D7|D6|D5|D4|X|E|RW|RS|
data_t[1] = data_u|0x09; //en=0, rs=1
data_t[2] = data_l|0x0D; //en=1, rs=1
data_t[3] = data_l|0x09; //en=0, rs=1
for(char i=0;i<4;i++){
I2C_write_byte(SLA_W, data_t[i]);
}
}
// LCD command 실행한다.
void I2C_LCD_write_command(uint8_t command)
{
char data_u, data_l;
uint8_t data_t[4];
data_u = (command&0xf0); // command의 상위 4bit 저장
data_l = ((command<<4)&0xf0); // command의 하위 4bit 저장
data_t[0] = data_u|0x0C; //en=1, rs=0 |D7|D6|D5|D4|X|E|RW|RS|
data_t[1] = data_u|0x08; //en=0, rs=0
data_t[2] = data_l|0x0C; //en=1, rs=0
data_t[3] = data_l|0x08; //en=0, rs=0
for(char i=0;i<4;i++){
I2C_write_byte(SLA_W, data_t[i]);
}
}
//lcd 화면 전체를 클리어 한다.
void I2C_LCD_clear(void)
{
I2C_LCD_write_command(COMMAND_CLEAR_DISPLAY);
_delay_ms(2);
}
//lcd를 초기화 하는것. 처음 프로그램 실행시 한번만 부르는 함수.
void I2C_LCD_init(void)
{
I2C_init(10000);
_delay_ms(50);
//Initialization of HD44780-based LCD (4-bit HW)
I2C_LCD_write_command(0x33);
I2C_LCD_write_command(0x32);
I2C_LCD_write_command(0x28); //Function Set 4-bit mode
I2C_LCD_write_command(0x0c); //Display On/Off Control
I2C_LCD_write_command(0x06); //Entry mode set
I2C_LCD_write_command(0x01); //Clear Display
//Minimum delay to wait before driving LCD module
_delay_ms(10);
}
//현재 커서 위치에 string 값을 lcd에 출력한다.
void I2C_LCD_write_string(char *string)
{
uint8_t i;
for(i=0; string[i]; i++)
I2C_LCD_write_data(string[i]);
}
//커서를 LCD의 특정 위치로 MOVE 할때.
//EX) LINE 0line의 10 col로 move시 I2C_LCD_goto_XY(0, 10);
void I2C_LCD_goto_XY(uint8_t row, uint8_t col)
{
col %= 16;
row %= 2;
uint8_t address = (0x40 * row) + col;
uint8_t command = 0x80 + address;
I2C_LCD_write_command(command);
}
//해당 되는 위치에가서 해당 값을 쓴다.
void I2C_LCD_write_string_XY(uint8_t row, uint8_t col, char *string)
{
I2C_LCD_goto_XY(row, col);
I2C_LCD_write_string(string);
}
I2C_LCD.c
I2C.h
/*
* I2C.h
*
* Created: 2020-01-07 오후 7:57:06
*/
#ifndef I2C_H_
#define I2C_H_
void I2C_init(unsigned int baud);
void I2C_start(void);
void I2C_transmit(uint8_t data);
void I2C_write_byte(uint8_t address, uint8_t data);
void I2C_stop(void);
uint8_t I2C_receive_ACK(void);
uint8_t I2C_receive_NACK(void);
#endif /* I2C_H_ */
I2C.c
/*
* I2C.c
*
* Created: 2020-01-07 오후 7:58:00
*/
#include <avr/io.h>
#include "I2C.h"
void I2C_init(unsigned int baud){
TWBR = baud;
}
void I2C_start(void)
{
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
while (!(TWCR & (1<<TWINT))); // 시작 완료 대기
}
void I2C_transmit(uint8_t data)
{
TWDR = data;
TWCR = (1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));
}
void I2C_write_byte(uint8_t address, uint8_t data)
{
I2C_start();
I2C_transmit(address); //주소를 보내는 이유는 슬레이브가 여러명 일 수 있기 떄문이다.
I2C_transmit(data);
I2C_stop();
}
void I2C_stop(void)
{
TWCR = (1<<TWINT)|(1<<TWEN)| (1<<TWSTO);
}
uint8_t I2C_receive_ACK(void)
{
TWCR = (1<<TWINT) | (1<<TWEN) |(1<<TWEA);
while( !(TWCR & (1<<TWINT))); // 수신 완료 대기
return TWDR;
}
uint8_t I2C_receive_NACK(void)
{
TWCR = (1<<TWINT) | (1<<TWEN);
while( !(TWCR & (1<<TWINT))); // 수신 완료 대기
return TWDR;
}
main.c
/*
* main.c
*
* Created: 2021-08-22 오전 10:20:19
* Author : KHJ
*/
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include <stdio.h>
#include <avr/interrupt.h> // interrupt 관련 lib가 들어 있다.
#include "uart0.h"
#include "ds1302.h"
void UART0_rx_interrupt_setRC522(void);
// printf를 동작 시키위한 mapping작업
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_transmit, NULL, _FDEV_SETUP_WRITE);
int main(void)
{
long count=0; // lcd 카운터 변수
char buffer[20]; // text lcd에 출력 하기 위함이다.
// printf 초기화
stdout = &OUTPUT; // FILE : 1(stdout) 0: stdin 2:stderr
I2C_LCD_init();
DS1302_Init();
UART0_init();
sei();
#if 1
while(1)
{
menu_exec();
display_clock_lcd();
}
#else // 테스트 코드
I2C_LCD_write_string_XY(0,0,"HI THERE");
while (1)
{
_delay_ms(100);
sprintf(buffer, "%04d",count);
I2C_LCD_write_string_XY(1,0,buffer);
count++;
}
#endif
}
//comport master 에서 settimeyymmddhhmmss로 ds1302 rtc를 설정
void UART0_rx_interrupt_setRC522(void)
{
stdout = &OUTPUT;
DS1302_Init();
UART0_init();
sei();
}
동작 동영상