본문 바로가기
# Semiconductor/- Semicon Academy

[Harman 세미콘 아카데미] 28일차 - ATmege128(UART 통신, Realtime clock)

by Graffitio 2023. 7. 26.
[Harman 세미콘 아카데미] 28일차 - ATmege128(UART 통신, Realtime clock)
728x90
반응형
[UART 통신]

 

Serial 통신의 기초

 

1. 마이크로 프로세서의 정보 교환 방법(외부와 정보 교환)

좌 : 직렬 통신                                                                                                우 : 병렬 통신

    1) 직렬 통신(Serial Communications)

         : 데이터 비트를 1개의 비트 단위로 외부에 송수신하는 방식으로,

           구현하기 쉽고 장거리 전송이 가능하며 기존의 통신선로(전화선 등)를 쉽게 활용할 수 있어

           비용절감이 크다는 장점이 있다.

            ex) 1011 0100 (8bit) 전송

                  Line 하나로 한 bit씩 8번 보내서 총 8bit 전송

                  (2bit씩, 4bit씩, 묶어서 보낼 수도 있다. 설정하기 나름)

 

    2) 병렬 통신(Parallal Communitions)

         : 컴퓨터 내의 장치와 정보교환을 할 때, 통상적으로 고속의 통신 속도를 필요로 하며

           한꺼번에 많은 정보를 처리할 수 있어 일반적으로 쓰인다.
           대량의 정보를 빠른 시간에 한꺼번에 처리함으로써 컴퓨터의 성능을 향상시킬 수 있지만,

           통신 거리의 제한성, 구현상의 기술적 어려움, 고비용 문제 등의 한계가 있다.

 

2. Serial Communication의 구분

    1) 비동기 방식

          비동기 방식은 데이터만 전송하기 때문에 속도는 빠르지만,

          데이터가 어디서 시작되고 어디서 끝나는 지 구분할 수 없기 때문에

          Start Bit, Stop Bit로 시작점과 종료점을 설정해주어야 한다.

          대표적으로 UART 통신(범용 비동기화 송수신, 전이중)이 있다.

    

 

    2) 동기 방식

          데이터 동기화를 위해 별도의 클록 신호를 전송한다.

          데이터를 구분할 필요 없이 클록의 유무만으로 체크하면 되므로, 데이터 송수신 효율이 높다.

          그러나 클록 핀을 추가로 요구하므로, 구조적인 어려움과 고비용 문제가 있다.

          대표적으로 USART 통신(범용 동기/비동기화 송수신, 반이중)이 있다.

 

3. Serial Communications의 기본

 

    1) Baud Rate(보오레이트)와 BPS(bit/sec)

          - Baud : 원래 변조율이나 1초간 통신선의 신호 변경 횟수를 가리키는 말

          - Baud rate와 bps는 2개의 시리얼 디바이스를 접속한 경우, 사실상 똑같다.

          - 9,600 bps = 1초간 9,600 회 선을 통과한 신호가 변화한다는 의미

                               = 초당 9,600 bit가 날라간다 생각하면 편함(약 0.1ms)

 

     2) 비트 단위 데이터 전송

          - 시리얼 통신에서는 1byte를 8개의 bit로 분리해서 1bit씩 전송한다.

          - 수신한 bit는 start bit와 stop bit로 1byte 범위를 식별해 수신한다.

          - Parity bit로 데이터 구조를 확인해 데이터 누락을 체크한다.

 

4. UART Communications Frame Formats

     ① 송신측과 수신측의 Baud Rate(bps)가 일치해야 한다.

     ② 데이터를 보내지 않는 상태에서 데이터 핀은 항상 High로 유지해야 한다.

     ③ Start / Stop bit로 시작점과 종료점을 설정해주어야 한다.

     ④ Data 는 LSB부터 전달된다.

     ⑤ Patity bit : 데이터가 잘 날라가는지 체크하는 기능

                          데이터에서 1의 갯수가 짝수이면 Parity bit = 0

                                                               홀수이면 Parity bit = 1

 

Register

 

      ▶ 데이터 버퍼의 기능을 한다.

      ▶ 데이터 작성해주면, 임시로 UDRn에 저장했다가 Start bit 나가면 데이터 전달

      ▶ 송신할 것은 TXBn, 수신할 것은 RXBn에 저장된다.

 

 

① RXCn (USART Receive Complete, 7번 bit)

     : UDR에 읽지 않은 데이터가 있을 때, 1로 set되고

       UDR이 비어있을 때 0으로 set된다.

 

② TXCn (USART Transmit Complete, 6번 bit)

     : 데이터가 성공적으로 전송되어 UDR이 비어있으면 1으로 set되고

       해당 위치에 1을 넣어 주면, Clear할 수 있다.

 

③ UDREn (USART Data Register Empty, 5번 bit)

     : 새로운 데이터를 수신할 준비가 되어 있는지 여부

       UDR(송신 버퍼)가 비어 있으면(준비가 되어 있으면) 1로 set

       Initial value가 1이니 주의할 것.

 

④ FEn (Frame Error, 4번 bit)

     : 수신 버퍼에 있는 다음 문자의 첫 번째 stop bit가 0일 때 1로 set된다.

       이 bit는 수신 버퍼(UDRn)를 읽을 때까지 유효하다.

       Stop bit가 1일때 0이다.(사용 전 항상 0으로 set할 것)

 

⑤ DORn (Data OverRun, 3번 bit)

     : 아직 송신이 덜 됐는데, 다른 데이터가 들어오면 플래그 세움(1로 set) 

 

⑥ UPEn (Parity Error, 2번 bit)

     : 수신 버퍼에 패리티 에러가 있더면, 1로 set

 

⑦ UPEn (Parity Error, 2번 bit)

     : 이 비트가 1로 set되면, USART의 전송 속도가 2배로 증가한다.

 

⑧ MPCM0 (Multi-Processor Communication Mode, 1번 bit)

    : 이 비트를 설정하면, 멀티 프로세서 통신 모드가 활성화된다.

      이 모드는 특수한 통신 환경에서 사용되며,

      일반적인 상황에서는 비활성화 상태로 둘 것을 권장한다.

 

RXCIE (RX Complete Interrupt Enable, 7번 bit)

     : 수신 완료 인터럽트 허용

       수신 데이터가 준비되면, RXCn(UCSRnA 레지스터의 수신 완료 비트)가 1로 set되고

       이에 따라 인터럽트가 발생한다.

 

TXCIE (TX Complete Interrupt Enable, 6번 bit)

     : 송신 완료 인터럽트 허용

       데이터 전송이 완료되면, TXCn(UCSRnA 레지스터의 전송 완료 비트)가 1로 set되고

       이에 따라 인터럽트가 발생한다.

 

UDRIE (UUSART Data Register Empty Interrupt Enable, 5번 bit)

     : 송신 데이터가 준비 완료되면 인터럽트 허용

       데이터 레지스터가 비어있는 상태(데이터를 전송할 준비가 된 상태)일 때,

       인터럽트가 활성화된다.

 

RXEN (Receiver Enable, 4번 bit)

     : 1로 set하면 RX PORT를 허용한다는 의미.

       USART 모듈은 외부로부터 데이터를 수신할 수 있다.

 

TXEN (Transmitter Enable, 3번 bit)

     : 1로 set하면 TX PORT를 허용한다는 의미.

       이 상태에서 USART 모듈은 데이터를 외부로 전송할 수 있다.

 

UCSZ2 (Character Size, 2번 bit)

     : 몇 bit로 송신할 것이냐?

       전송하려는 데이터의 비트 수를 설정

       UCSZ2와 UCSZ1, UCSZ0 bit를 조합하여 5, 6, 7, 8, 9 bit의 문자 크기 선택 가능

 

RXB8 (Receive Data Bit 8, 2번 bit)

     : 9 bit 데이터 형식을 사용할 때, 9번째 bit의 값을 타낸다.

       8bit 데이터 형식을 사용하는 경우에는 이 bit가 무시된다.

 

TXB8 (Transmit Data Bit 8, 1번 bit)

    : 이 bit는 9bit 데이터 형식을 사용할 때 9번째 비트의 값을 설정하는데

      사용된다. 8bit 데이터 형식을 사용하는 경우에는 이 bit가 무시된다.

 

 

UMSELn (USART Mode Select)

     : 1이면 동기, 0이면 비동기

 

② TXCIE (TX Complete Interrupt Enable, 6번 bit)

     : 송신 완료 인터럽트 허용

       데이터 전송이 완료되면, TXCn(UCSRnA 레지스터의 전송 완료 비트)가 1로 set되고

       이에 따라 인터럽트가 발생한다.

 

UPMn1, UPMn0 (Parity Mode)

     : 패러티 모드를 설정하는데 사용한다.

 

USBSn (Stop Bit Select)

     : Stop bit의 갯수를 선택하는데 사용된다.

        0으로 set하면 1개의 stop bit, 1로 set하면 2개의 stop bit가 사용된다.

 

UCSZn1, UCSZn0 (Character Size)

     : 문자의 크기를 지정하는데 사용된다.

       UCSZn2와 함께 조합하여 5, 6, 7, 8, 또는 9비트의 문자 크기를 선택 가능

 

UCPOLn (Clock Polarity)

     : 동기 모드에서 클럭 극성을 선택하는 데 사용된다.

       클럭 극성에 따라서 동기 통신의 동작 방식이 달라진다.

 

 

: USART Baud Rate Register 

   통신 속도를 설정하는 레지스터이다.

   두 레지스터를 조합하여 Baud rate(bps) 설정 가능하다.

9600bps를 쓰고 싶다면, UBRR = 103

 

UART 예제 1

 

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>


void UART0_Init() // UART 통신을 쓰려면, 이거 세팅해줘야 한다.
// PE0, PE1을 쓸 것이다.
// 프로그램 다운로드하는 것과 맞물려 있다.
// UART 통신을 사용하려면, 스위치를 오른쪽으로 토글해줘야 한다. 꼭!
// 1. 프로그램 다운로드 해 -> 해당 창 close
// 2. 스위치 오른쪽으로 토글
// 3. comportmaster 파일 열고 
// 4. 프로그램 수정하려면, comportmaster 파일 close하고 스위치 왼쪽으로 토글하고 1번
{
	UBRR0H = 0x00;
	UBRR0L = 207; // 9600bps, 2배속모드를 쓸 것이다.
	
	UCSR0A = (1<<U2X0); // 2배속 모드 셋
	
	// 비동기 모드, 8bit 데이터, 패리티비트 없음, 1비트 stop bit
    // 어짜피 다 0이므로 따로 세팅 안 해줘도 된다.
	//UCSR0C |= (0<<UMSEL0); // 비동기 모드
	//UCSR0C |= (0<<UPM01) | (0<<UPM00); // 패리티 비트 없음
	//UCSR0C |= (0<<USBS0); // 1비트 stop mode
	
	UCSR0B |= (1<<RXEN0); // 수신 가능(RX PIN 허용)
	UCSR0B |= (1<<TXEN0); // 송신 가능(TX PIN 허용)
}


void UART0_Transmit(char data)
{
	while (!(UCSR0A & (1<<UDRE0))); // 송신 가능 대기, UDR(송신버퍼)이 비어있는지 체크
	// 비어있으면 UDRE0가 1로 세팅됨.
	// while 문 거짓되면, 빠져 나가서 다음 라인으로
	UDR0 = data; // 데이터 버퍼 레지스터에 데이터 전송
}


unsigned UART0_Receive(void) // 리턴해주는 값은 없고, 받기만 하므로
{
	while (!(UCSR0A & (1<<RXC0))); // 데이터 수신 대기 // 수신
	return UDR0; // URD0에 있는 데이터를 리턴만 해주면 된다.
}


int main(void)
{
    UART0_Init(); // 0번 UART 초기화
	
    while (1) 
    {
		UART0_Transmit(UART0_Receive()); // 받은 놈을 보낼 것이다 -> 무한루프
    }
}

 

UART 예제 2

 

<UART_2.h>

#ifndef UART_2_H_
#define UART_2_H_


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>


void UART0_Init();
void UART0_Transmit(char data);
unsigned UART0_Receive(void);


#endif /* UART_2_H_ */

 

<UART_2.c>

#include "UART_2.h"


void UART0_Init() // UART 통신을 쓰려면, 이거 세팅해줘야 한다.
// PE0, PE1을 쓸 것이다.
// 프로그램 다운로드하는 것과 맞물려 있다.
// UART 통신을 사용하려면, 스위치를 오른쪽으로 토글해줘야 한다. 꼭!
// 1. 프로그램 다운로드 해 -> 해당 창 close
// 2. 스위치 오른쪽으로 토글
// 3. compo 어쩌고 파일 열고
// 4. 프로그램 수정하려면, compo 어쩌고 파일 close하고 스위치 왼쪽으로 토글하고 1번
{
	UBRR0H = 0x00;
	UBRR0L = 0xcf; // 9600bps, 2배속모드를 쓸 것이다.
	
	UCSR0A = (1<<U2X0); // 2배속 모드 셋
	
	// 비동기 모드, 8bit 데이터, 패리티비트 없음, 1비트 stop bit
	UCSR0B |= (1<<RXEN0); // 수신 가능(RX PIN 허용)
	UCSR0B |= (1<<TXEN0); // 송신 가능(TX PIN 허용)
	
	UCSR0B |= (1<<RXCIE0); // 수신 interrupt Enable
}


void UART0_Transmit(char data)
{
	while (!(UCSR0A & (1<<UDRE0))); // 송신 가능 대기, UDR(송신버퍼)이 비어있는지 체크
	// 비어있으면 UDRE0가 1로 세팅됨.
	// while 문 거짓되면, 빠져 나가서 다음 라인으로
	UDR0 = data; // 데이터 버퍼 레지스터에 데이터 전송
}


unsigned UART0_Receive(void) // 리턴해주는 값은 없고, 받기만 하므로
{
	while (!(UCSR0A & (1<<RXC0))); // 데이터 수신 대기 // 수신
	return UDR0; // URD0에 있는 데이터를 리턴만 해주면 된다.
}

 

<main.c>

#define F_CPU 16000000UL
#include "UART_2.h"

// 출력 스트림 설정 <stdio.h>에 있는 표준 입출력
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);
// 이 매크로는 스트림을 설정하는 데 사용되며, 주로 사용자 지정 입출력 함수를 지원하기 위해 사용된다.
// 이러한 스트림은 키보드로부터 입력을 받거나 화면에 출력하는 등의 기본적인 입출력 작업에 사용된다.
// FDEV_SETUP_STREAM 매크로는 사용자가 표준 입출력 함수와 같은 기능을 갖는 사용자 정의 입출력 함수를 작성할 때 유용하다.
// 이걸 stdout과 연결함으로써 printf 함수가 사용자 정의 출력 함수를 호출하게 된다.

char rxBuff[100] = {0,}; // 수신 버퍼
uint8_t rxFlag = 0; // 수신 완료 플래그 설정

ISR(USART0_RX_vect) // 수신 interrupt 핸들러
{
	static uint8_t rxHead = 0; // 수신 데이터 인덱스
	uint8_t rxData = UDR0; // 인터럽트 발생하면 수신된 데이터를 rxData에 넣어라.
	
	if (rxData == '\n' || rxData == '\r') // 수신 데이터기 개행이나 리턴이면
	{
		rxBuff[rxHead] = '\0'; // 수신된 문자열 만지막에 NULL 문자 추가
		rxHead = 0; // 인덱스 초기화
		rxFlag = 1; // 문자열 수신 완료되었다고 플래그 세움
	}
	else // 개행이나 리턴이 아니면
	{
		rxBuff[rxHead] = rxData; // 그렇지 않으면, 버퍼에 데이터 계속 추가
		rxHead++; // 인덱스 증가
	}
}


int main(void)
{
	UART0_Init();
	uint8_t rxData; // 수신 데이터
	stdout = &OUTPUT; // 출력 스트림 지정
	// stdout : 출력 스트림을 지정해주는 포인터
	sei(); // 인터럽트 쓸 거니까 전역 인터럽트 레지스터 허용
    
    while (1) 
    {
		if (rxFlag == 1) // 문자열 수신이 완료되면,
		{
			rxFlag = 0;
			printf(rxBuff); // rxBuff를 출력해라
		}
    }
}

 

[Real Time Clock]

 

DS1302

 

     ▶ 모든 입출력은 CE : (0 → 1)이 되는 것과 동시에 시작된다.

     ▶ 공통적으로 Read/Write 선택하고 주소(A0 ~ A4)를 보내준 뒤, 데이터 Read/Write가 실행된다.

     ▶ Read mode일 때는 clk가 Falling edge일 때 데이터를 수신하고

     ▶ Write mode일 때는 clk가 Rising edge일 때 데이터를 송신한다.

     ▶ LSB부터 송/수신하기 때문에, 주의해서 코딩할 것.

 

주소값 예시, 각 비트에 저장되는 데이터 종류와 범위

 

RTC 예시

 

<헤더 파일 모음>

   - DS1302.h

   - I2C.h

   - I2C_LCD.h

   - UART_2.h

더보기

DS1302.h

#ifndef DS1302_H_
#define DS1302_H_

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

#define DS1302_CLK_DDR		DDRG
#define DS1302_CLK_PORT		PORTG
#define DS1302_DATA_DDR		DDRG
#define DS1302_DATA_PORT	PORTG
#define DS1302_DATA_PIN		PING
#define DS1302_RST_DDR		DDRG  // 칩 셀렉트 핀을 reset 핀이라고도 한다.
#define DS1302_RST_PORT		PORTG
#define DS1302_CLK			2
#define DS1302_DATA			3
#define DS1302_RST			4

// Data sheet 상 address //
#define ADDR_SEC			0x80
#define ADDR_MIN			0x82
#define ADDR_HOUR			0x84
#define ADDR_DATE			0x86
#define ADDR_MONTH			0x88
#define ADDR_DAYOFWEEK		0x8A // 월요일~일요일
#define ADDR_YEAR			0x8C


typedef struct _ds1302
{
	uint8_t sec;
	uint8_t min;
	uint8_t hour;
	uint8_t date;
	uint8_t month;
	uint8_t dayOfweek;
	uint8_t year;	
}DS1302;



void DS1302_Init(); // 초기화
void DS1302_Selected(); // CE = high
void DS1302_Deselected(); // CE = Low
void DS1302_Clock(); // Clock
void DS1302_DataBitSet(); // Bit 설정(Data PIN High)
void DS13602_DataBitReset(); // Bit 설정(Data PIN Low)
void DS1302_Change_ReadMode(); // 읽기 모드로 설정
void DS1302_change_WriteMode(); // 쓰기 모드로 설정
uint8_t Decimal_to_BCD(uint8_t decimal);
uint8_t BCD_to_Decimal(uint8_t bcd);
void DS1302_TxData(uint8_t txData); // RTC에 data send
void DS1302_WriteData(uint8_t address, uint8_t data); // 특정 주소에 data write
void DS1302_SetTimeDate(DS1302 timeDate); // Date, Time 직접 설정
uint8_t DS1302_RxData(); // data receive
uint8_t DS1302_ReadData(uint8_t address); // 특정 주소에서 data read
void DS1302_GetTime(DS1302 *timeDate); // RTC의 시간 get
void DS1302_GetDate(DS1302 *timeDate); // RTC의 날짜 get



#endif /* DS1302_H_ */

 

I2C.h

#ifndef I2C_H_
#define I2C_H_

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

#define I2C_DDR	DDRD
#define I2C_SCL	PORTD0 // clock은 PORTD0
#define I2C_SDA	PORTD1 // 


void I2C_Init();
void I2C_Start();
void I2C_Stop();
void I2C_TxData(uint8_t data);
void I2C_TxByte(uint8_t devAddrRW, uint8_t data);


#endif /* I2C_H_ */

 

I2C_LCD.h

#ifndef I2C_LCD_H_
#define I2C_LCD_H_

//#define F_CPU 16000000UL
//#include <avr/io.h>
//#include <util/delay.h>
#include "I2C.h"

#define LCD_RS	0 // Register Select
// 제어 레지스터 : 0 , 데이터 레지스터 : 1  
#define LCD_RW	1 // Read/Write 설정하는 레지스터 bit / Read : 1, Write : 0
#define LCD_E	2 // 여기서 2는 LCD board상 2번 핀(E핀)을 뜻한다.
#define LCD_BACKLIGHT	3

#define LCD_DEV_ADDR	(0x27<<1) // I2C LCD 주소 0x27, <<1 : Write 모드 유지

#define COMMAND_DISPLAY_CLEAR	0x01
#define COMMAND_DISPLAY_ON		0x0c
#define COMMAND_DISPLAY_OFF		0x08
#define COMMAND_4_BIT_MODE		0x28
#define COMMAND_ENTRY_MODE		0x06

// 함수 원형 선언 //
void LCD_Data4bit(uint8_t data);
void LCD_EnablePin();
void LCD_WriteCommand(uint8_t commandData);
void LCD_WriteData(uint8_t charData);
void LCD_BackLight();
void LCD_gotoXY(uint8_t row, uint8_t col);
void LCD_WriteString(char *string);
void LCD_WriteStringXY(uint8_t row, uint8_t col, char *string);
void LCD_Init();



#endif /* I2C.LCD_H_ */

 

 UART_2.h

#ifndef UART_2_H_
#define UART_2_H_


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>



void UART0_Init();
void UART0_Transmit(char data);
unsigned UART0_Receive(void);




#endif /* UART_2_H_ */

 

<소스 파일 모음>

   - DS1302.c

   - I2C.c

   - I2C_LCD.c

   - UART_2.c

더보기

DS1302.c

#include "DS1302.h"


void DS1302_Init()
{
	/*
	 * DS1302 초기화 함수
	 * 클럭 핀, 데이터 핀 출력 설정
	 * RST 핀 LOW로 설정
	 * 
	*/
	DS1302_CLK_DDR |= (1<<DS1302_CLK); // 2번 CLK 라인에 해당하는 PIN을 출력으로 설정
	DS1302_DATA_DDR |= (1<<DS1302_DATA); // 3번
	DS1302_RST_DDR |= (1<<DS1302_RST); // 4번
	
	DS1302_CLK_PORT &= ~(1<<DS1302_CLK); // 초기값은 Low
	DS1302_DATA_PORT |= (1<<DS1302_DATA); // 초기값 high
	DS1302_RST_PORT &= ~(1<<DS1302_RST); // 셀렉트 핀 초기값 Low 
}


void DS1302_Selected()
{
	DS1302_RST_PORT |= (1<<DS1302_RST); // CE 핀을 High
}


void DS1302_Deselected()
{
	DS1302_RST_PORT &= ~(1<<DS1302_RST); // CE 핀을 Low
}


void DS1302_Clock() // 오실레이터에 달려 있는 클록 기준으로 clk 펄스 발생
{
	DS1302_CLK_PORT |= (1<<DS1302_CLK); // Clock High
	DS1302_CLK_PORT &= ~(1<<DS1302_CLK); // Clock Low
	
}


void DS1302_DataBitSet()
{
	DS1302_DATA_PORT |= (1<<DS1302_DATA); // Data 핀 High
}


void DS13602_DataBitReset()
{
	DS1302_DATA_PORT &= ~(1<<DS1302_DATA); // Data 핀 Low
	
}


void DS1302_Change_ReadMode()
{
	DS1302_DATA_DDR &= ~(1<<DS1302_DATA); // 읽기 모드에서 데이터 핀 출력 설정 변경
}


void DS1302_change_WriteMode()
{
	DS1302_DATA_DDR |= (1<<DS1302_DATA); // 쓰기모드에서 데이터 핀 출력 설정 변경
}


// 읽어와도 BCD값으로 읽어올 것이니까~
//DEC -> BCD, BCD->DEC로 바꿔주는 함수 필요
uint8_t Decimal_to_BCD(uint8_t decimal)
{
	return (((decimal/10) << 4) | (decimal%10));
	// 10진값을 2진값으로 변환
	// 4bit씩 묶어서 1의 자리와 10의 자리로 각각 변환
	// ex) 59 = (50/10)<<9 | 9%10 = 0101 1001
}


uint8_t BCD_to_Decimal(uint8_t bcd)
{
	return (((bcd >> 4) * 10) + (bcd & 0x0f));
	// bcd 값을 4bit씩 묶어서 10의 자리와 1의자리로 각각 변환한 후 더함.
	// 0101 1001 = (0000 0101 * 10) + (0000 1001) = 5*10 + 9 = 59
}


void DS1302_TxData(uint8_t txData)
{
	// 데이터 하위비트부터 상위비트 순으로 보내고 (Lsb부터 날라간다.)
	// 클럭 신호를 발생시켜 데이터를 전송한다.
	DS1302_change_WriteMode(); // 쓰기 모드로 변경
	
	for (int i = 0 ; i < 8 ; i++)
	{
		// 하위 비트 -> 상위 비트로
		// 1비트 출력하고 클럭 올렸다 내리고
		if (txData & (1<<i)) // 0번 bit부터 밀면서 7번까지 사용
		DS1302_DataBitSet(); // 1이면, Data Pin High
		else
		DS13602_DataBitReset(); // 0 또는 그렇지 않으면, Data Pin Low
		
		DS1302_Clock(); // 클럭 한 번 반복
	}
}

void DS1302_WriteData(uint8_t address, uint8_t data)
{
	// 주소와 데이터를 전송하고
	// 다 끝났으면, RST 핀을 Low로 설정
	DS1302_Selected(); // RST 핀 High(CE = high)
	DS1302_TxData(address); // address send
	DS1302_TxData(Decimal_to_BCD(data)); // data를 BCD로 바꿔서 보냄 
	DS1302_Deselected(); // CE = Low
}


void DS1302_SetTimeDate(DS1302 timeDate) // 매개변수는 DS1302 구조체type의 timeDate
{
	DS1302_WriteData(ADDR_SEC, timeDate.sec);
	DS1302_WriteData(ADDR_MIN, timeDate.min);
	DS1302_WriteData(ADDR_HOUR, timeDate.hour);
	DS1302_WriteData(ADDR_DATE, timeDate.date);
	DS1302_WriteData(ADDR_MONTH, timeDate.month);
	DS1302_WriteData(ADDR_DAYOFWEEK, timeDate.dayOfweek);
	DS1302_WriteData(ADDR_YEAR, timeDate.year);
}


uint8_t DS1302_RxData()
{
	// 데이터를 하위비트부터 상위비트 순으로 읽고
	// 클럭 신호를 발생시켜 데이터를 읽음
	uint8_t rxData = 0;
	DS1302_Change_ReadMode(); // Read mode로 바꾸고
	
	for (int i = 0 ; i < 8 ; i++)
	{
		rxData |= (DS1302_DATA_PIN & (1<<DS1302_DATA)) ? (1<<i) : 0;
		if(i != 7) // (0~7까지 총 8번 rising edge)
		DS1302_Clock();
	}
	return rxData;
}


uint8_t DS1302_ReadData(uint8_t address)
{
	// 지정된 주소의 데이터를 읽어옴
	uint8_t rxData = 0; //  수신된 데이터를 저장할 변수
	DS1302_Selected(); // CE = high
	DS1302_TxData(address+1); // 지정된 주소를 전송(Write에서 1을 더하면 Read주소)
	rxData = DS1302_RxData(); // 수신된 데이터를 읽음
	DS1302_Deselected(); // CE = Low
	
	return BCD_to_Decimal(rxData);
}


void DS1302_GetTime(DS1302 *timeDate) // 매개변수는 DS1302 type의 timeDate 포인터 변수 
{
	timeDate->sec = DS1302_ReadData(ADDR_SEC);
	timeDate->min = DS1302_ReadData(ADDR_MIN);
	timeDate->hour = DS1302_ReadData(ADDR_HOUR);
}


void DS1302_GetDate(DS1302 *timeDate)
{
	timeDate->date = DS1302_ReadData(ADDR_DATE);
	timeDate->month = DS1302_ReadData(ADDR_MONTH);
	timeDate->dayOfweek = DS1302_ReadData(ADDR_DAYOFWEEK);
	timeDate->year = DS1302_ReadData(ADDR_YEAR);
}

 

I2C.c

#include "I2C.h"


void I2C_Init()
{
	I2C_DDR |= (1<<I2C_SCL) | (1<<I2C_SDA); // 출력 설정
	TWBR = 72; // 100KHz
	// TWBR = 32; // 200KHz
	// TWBR = 12; // 400KHz
}

void I2C_Start()
{
	TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN); // 1을 보내는 것은, S/W적으로 플래그를 Clear해주는 것이다.
	                                            // 1 Set하여 인터럽트 발생시키는 것이 아님.
	while(!(TWCR & (1<<TWINT))); // 하드웨어적으로 TWINT 시점을 결정
	                             // 기다렸다가 조건이 만족되면 while문 수행
}

void I2C_Stop()
{
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO); // Stop 비트 설정
}

void I2C_TxData(uint8_t data) // data 1바이트 전송
{
	TWDR = data;
	TWCR = (1<<TWINT) | (1<<TWEN);
	while (!(TWCR & (1<<TWINT))); // 전송 완료 대기
}

void I2C_TxByte(uint8_t devAddrRW, uint8_t data)
{
	I2C_Start();
	I2C_TxData(devAddrRW); // 읽을 것이냐 쓸 것이냐
	I2C_TxData(data); // 입/출력할 데이터 보냄
	I2C_Stop();
}

 

I2C_LCD.c

#include "I2C_LCD.h"

uint8_t I2C_LCD_Data;

void LCD_Data4bit(uint8_t data)
{
	I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | (data & 0xf0); // 상위 4bit 출력
	// 이전 상위 비트는 다 날라감 | data의 상위 비트는 살림 
	LCD_EnablePin();
	I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | ((data & 0x0f)<<4); // 하위 4bit
	LCD_EnablePin();
	// 상위 4bit 받고 -> Enable해서 출력하고 -> 하위 4bit 받고 상위로 시프트하고 -> Enabla해서 출력하고
	// [7:4] 핀만 사용해서 반반 나눠서 출력하면, 총 8bit를 출력할 수 있다.
}

void LCD_EnablePin()
{
	I2C_LCD_Data &= ~(1<<LCD_E); // E low 설정
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
	
	I2C_LCD_Data |= (1<<LCD_E); // High 설정
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
	
	I2C_LCD_Data &= ~(1<<LCD_E); // E low 설정
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
	
	// 0->1 일때, data 출력 -> 다음 출력을 위해 0으로 다시 설정
	
	_delay_us(1600);
}

void LCD_WriteCommand(uint8_t commandData)
{
	I2C_LCD_Data &= ~(1<<LCD_RS); // Command일 때는 control register(TWCR)을 쓰고
	I2C_LCD_Data &= ~(1<<LCD_RW); // Write 모드
	LCD_Data4bit(commandData);
}

void LCD_WriteData(uint8_t charData)
{
	I2C_LCD_Data |= (1<<LCD_RS); // Data받을 때는 Data Register(TWDR) 쓰고
	I2C_LCD_Data &= ~(1<<LCD_RW); // Write 모드
	LCD_Data4bit(charData);
}


void LCD_BackLight()
{
	I2C_LCD_Data |= (1<<LCD_BACKLIGHT);
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
}

void LCD_gotoXY(uint8_t row, uint8_t col) // LCD 화면의 커서를 지정된 행(row)과 열(col) 위치로 이동시키는 함수
{
	col %= 16; // 0~15 사이의 값 지정 가능
	row %= 2; // 0~1 사이의 값 지정 가능
	uint8_t address = (0x40 * row) + col; // 주어진 row와 col 값을 이용하여 LCD 화면의 주소(address, 커서 위치)룰 계산한다.
	// 첫 번째 행(row 0)의 주소 범위는 0x00 ~ 0x0f(0~15)
	// 두 번째 행(row 1)의 주소 범위는 0x40 ~ 0x4f(64~79)
	// 예시 : row 1, col 3 -> address = 0x43
	uint8_t command = 0x80 + address; // 계산된 주소를 이용하여 이동시키는 명령어 command 생성
	// 0x80을 사용하는 이유는 특정 주소값이 아닌, 첫 번째 행의 시작을 나타내는 상징적인 값으로 사용된다,
	// 이렇게 함으로써 코드의 가독성을 높이고, 행과 열 값을 쉽게 결합하여 원하는 주소 값을 계산할 수 있다.
	LCD_WriteCommand(command);
}

void LCD_WriteString(char *string)
{
	for (uint8_t i = 0 ; string[i] ; i++) // 받은 문자열의 포인터가 보는 곳을 한 비트씩 분해해서 LCD_WriteData 함수에 하나씩 뿌려줌
	{
		LCD_WriteData(string[i]);
	}
}

void LCD_WriteStringXY(uint8_t row, uint8_t col, char *string)
{
	LCD_gotoXY(row,col); // 행과 열의 위치를 받음. -> 어디서부터 쓸 것인지를 좌표를 받음
	LCD_WriteString(string); // 문자열 입력을 받음
}

void LCD_Init()
{
	I2C_Init();
	
	_delay_ms(20);
	LCD_WriteCommand(0x03);
	_delay_ms(10);
	LCD_WriteCommand(0x03);
	_delay_ms(1);
	LCD_WriteCommand(0x03);
	
	LCD_WriteCommand(0x02);
	LCD_WriteCommand(COMMAND_4_BIT_MODE);
	LCD_WriteCommand(COMMAND_DISPLAY_OFF);
	LCD_WriteCommand(COMMAND_DISPLAY_CLEAR);
	LCD_WriteCommand(COMMAND_ENTRY_MODE);
	LCD_WriteCommand(COMMAND_DISPLAY_ON);
	LCD_BackLight();
}

 

UART_2.c

#include "UART_2.h"


void UART0_Init() // UART 통신을 쓰려면, 이거 세팅해줘야 한다.
// PE0, PE1을 쓸 것이다.
// 프로그램 다운로드하는 것과 맞물려 있다.
// UART 통신을 사용하려면, 스위치를 오른쪽으로 토글해줘야 한다. 꼭!
// 1. 프로그램 다운로드 해 -> 해당 창 close
// 2. 스위치 오른쪽으로 토글
// 3. compo 어쩌고 파일 열고
// 4. 프로그램 수정하려면, compo 어쩌고 파일 close하고 스위치 왼쪽으로 토글하고 1번
{
	UBRR0H = 0x00;
	UBRR0L = 0xcf; // 9600bps, 2배속모드를 쓸 것이다.
	
	UCSR0A = (1<<U2X0); // 2배속 모드 셋
	
	// 비동기 모드, 8bit 데이터, 패리티비트 없음, 1비트 stop bit
	//UCSR0C |= (0<<UMSEL0); // 비동기 모드
	//UCSR0C |= (0<<UPM01) | (0<<UPM00); // 패리티 비트 없음
	//UCSR0C |= (0<<USBS0); // 1비트 stop mode
	
	UCSR0B |= (1<<RXEN0); // 수신 가능(RX PIN 허용)
	UCSR0B |= (1<<TXEN0); // 송신 가능(TX PIN 허용)
	
	UCSR0B |= (1<<RXCIE0); // 수신 interrupt Enable
}


void UART0_Transmit(char data)
{
	while (!(UCSR0A & (1<<UDRE0))); // 송신 가능 대기, UDR(송신버퍼)이 비어있는지 체크
	// 비어있으면 UDRE0가 1로 세팅됨.
	// while 문 거짓되면, 빠져 나가서 다음 라인으로
	UDR0 = data; // 데이터 버퍼 레지스터에 데이터 전송
	
	
}


unsigned UART0_Receive(void) // 리턴해주는 값은 없고, 받기만 하므로
{
	while (!(UCSR0A & (1<<RXC0))); // 데이터 수신 대기 // 수신
	return UDR0; // URD0에 있는 데이터를 리턴만 해주면 된다.
}

 

<main.c>

#define F_CPU 16000000UL
#include "DS1302.h"
#include "UART_2.h"
#include "I2C_LCD.h"


// 출력 스트림 설정 <stdio.h>에 있는 표준 입출력
// 일종의 버퍼
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);




int main(void)
{
	stdout = &OUTPUT; // 출력 스트림 지정
	UART0_Init();
	DS1302_Init();
	LCD_Init();
	
    // RTC 시계 칩에 날짜와 시간을 설정
	DS1302 myTime;
	myTime.year = 23;
	myTime.month = 7;
	myTime.date = 26;
	myTime.dayOfweek = 4; // 일(1) 월(2) 화(3) 수(7) 목(5) 금(6) 토(7)
	myTime.hour = 14;
	myTime.min = 37;
	myTime.sec = 10;
	
	DS1302_SetTimeDate(myTime);
	
	uint8_t buff[30];
	uint8_t buff_t[30];
	
	
    while (1) 
    {
		DS1302_GetDate(&myTime);
		DS1302_GetTime(&myTime);
		
		printf("20%02d.%02d.%02d  %02d:%02d:%02d \n",
				myTime.year, myTime.month, myTime.date,
				myTime.hour, myTime.min, myTime.sec);
		_delay_ms(500);
		
		sprintf(buff, "DATE:%4d-%02d-%02d", 2000+myTime.year, myTime.month, myTime.date);
		LCD_WriteStringXY(0,0,buff);
		sprintf(buff_t, "TIME : %02d:%02d:%02d", myTime.hour, myTime.min, myTime.sec);
		LCD_WriteStringXY(1,0,buff_t);
		_delay_ms(500);
    }
}

 

728x90
반응형