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

[Harman 세미콘 아카데미] 27일차 - ATmega128(LCD_8bit, LCD_4bit, LCD_I2C)

by Graffitio 2023. 7. 25.
[Harman 세미콘 아카데미] 27일차 - ATmega128(LCD_8bit, LCD_4bit, LCD_I2C)
728x90
반응형
[LCD]

 

PIN Description

 

 

Instruction Table

 

#define 으로 정의해놓고 사용하면 편하다.

0x01 Clear All Display 0x02 Cursor Position -> Return home
  Entry_Mode_Set_Options   Cursor_Display_Shift_Options
0x04 커서 좌측 이동, 화면 이동 없음 0x10 커서 선택, 커서 좌측 이동
0x05 커서 좌측 이동, 화면 이동 0x14 커서 선택, 커서 우측 이동
0x06 커서 우측 이동, 화면 이동 없음 0x18 화면 선택, 화면 좌측 이동
0x07 커서 우측 이동, 화면 이동 0x1C 화면 선택, 화면 우측 이동
  Display_Option   Function_ Set_Options
0x08 화면 OFF, 커서 OFF, 커서 점멸 OFF 0x20 4bit, 화면 1행, 5x8 Font
0x09 화면 OFF, 커서 OFF, 커서 점멸 ON 0x24 4bit, 화면 1행, 5x11 Font
0x0A 화면 OFF, 커서 ON, 커서 점멸 OFF 0x28 4bit, 화면 2행, 5x8 Font
0x0B 화면 OFF, 커서 ON, 커서 점멸 ON 0x2C 4bit, 화면 2행, 5x11 Font
0x0C 화면 ON, 커서 OFF, 커서 점멸 OFF 0x30 8bit, 화면 1행, 5x8 Font
0x0D 화면 ON, 커서 OFF, 커서 점멸 ON 0x34 8bit, 화면 1행, 5x11 Font
0x0E 화면 ON, 커서 ON, 커서 점멸 OFF 0x38 8bit, 화면 2행, 5x8 Font
0x0F 화면 ON, 커서 ON, 커서 점멸 ON 0x3C 8bit, 화면 2행, 5x11 Font

 

 

초기 작업

8 Bit Interface
4 Bit Interface

 

Read / Write mode

   : 공통적으로 En이 0→1 이 된 후에 데이터 전송이 일어난다.

 

 

 

LCD-1602A Data Sheet

 

LCD-1602A_CA.pdf
0.28MB

 


 

[LCD_8bit]

 

LCD 출력 기본

 

<LCD.h>

#ifndef LCD_H_
#define LCD_H_

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

#define LCD_DATA_DDR	DDRC
#define LCD_DATA_PORT	PORTC // 데이터 포트
#define LCD_DATA_PIN	PINC 
#define LCD_RS_DDR		DDRB // 제어 핀 연결
#define LCD_RW_DDR		DDRB
#define LCD_E_DDR		DDRB
#define LCD_RS_PORT		PORTB
#define LCD_RW_PORT		PORTB
#define LCD_E_PORT		PORTB
#define LCD_RS			5
#define LCD_RW			6
#define LCD_E			7


// COMAND
#define COMMAND_DISPLAY_CLEAR	0x01
#define COMMAND_DISPLAY_ON		0x0C // 화면 ON, 커서 OFF, 커서 점멸 OFF
#define COMMAND_DISPLAY_OFF		0x08 // 화면 OFF, 커서 OFF, 커서 점멸 OFF
#define COMMAND_ENTRY_MODE		0x06 // 커서 우측 이동, 화면 이동 없음
#define COMMAND_8_BIT_MODE		0x38 // 8비트, 화면 2행, 5x8 Font
#define COMMAND_4_BIT_MODE		0x28 // 4비트, 화면 1행, 5x11 Font


// 함수 원형 선언
void LCD_Data(uint8_t data);
void LCD_Data4bit(uint8_t data);
void LCD_EnablePin();
void LCD_WritePin();
void LCD_ReadPin();
void LCD_WriteCommand(uint8_t commandData);
void LCD_WriteData(uint8_t charData);
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 /* LCD_H_ */

 

 

<LCD.c>

#include "LCD.h"



void LCD_Data(uint8_t data)
{
	LCD_DATA_PORT = data; // 데이터 핀에 데이터 출력
}

void LCD_Data4bit(uint8_t data)
{
	LCD_DATA_PORT = (LCD_DATA_PORT & 0x0f) | (data & 0xf0); // 상위 4bit 출력
	// 이전 상위 비트 다 날라감 | data의 상위비트 살림
	LCD_EnablePin();
	LCD_DATA_PORT = (LCD_DATA_PORT & 0x0f) | ((data & 0x0f)<<4); // 하위 4bit 출력
	LCD_EnablePin();
}

void LCD_EnablePin()
{
	LCD_E_PORT &= ~(1<<LCD_E); // E 핀을 Low 설정
	LCD_E_PORT |= (1<<LCD_E); // E 핀을 High 설정
	// Low -> High 되어야 Data들이 전송된다.
	LCD_E_PORT &= ~(1<<LCD_E); // 다시 Low 설정
	_delay_us(1800); // 이 시간 조절해서, 문자 간 출력 딜레이 설정 가능
}


void LCD_WritePin()
{
	LCD_RW_PORT &= ~(1<<LCD_RW); // RW 핀을 Low로 설정하여 쓰기 모드
}


void LCD_ReadPin()
{
	LCD_RW_PORT |= (1<<LCD_RW); // RW 핀을 high로 설정하여 읽기 모드
}


void LCD_WriteCommand(uint8_t commandData)
{
	LCD_RS_PORT &= ~(1<<LCD_RS); // RS 핀을 Low로 설정하여 명령어 모드
	LCD_WritePin(); // 데이터 쓰기 모드로 설정
	LCD_Data(commandData); // 명령어 데이터를 데이터 핀에 출력
	LCD_EnablePin(); // LCD 동작 신호 전송
}


void LCD_WriteData(uint8_t charData)
{
	LCD_RS_PORT |= (1<<LCD_RS); // RS 핀을 High로 설정하여 문자 데이터 모드
	LCD_WritePin();
	LCD_Data(charData);
	LCD_EnablePin();
}


void LCD_gotoXY(uint8_t row, uint8_t col)
{
	col %= 16; // 열 인덱스를 0부터 15로 제한
	row %= 2; // 행 인덱스를 0부터 1로 제한
	
	uint8_t address = (0x40 * row) + col; // 주소 계산
	uint8_t command = 0x80 + address; // command 값 계산
	LCD_WriteCommand(command); // 주소 설정 command를 LCD에 전달
}


void LCD_WriteString(char *string)
{
	for (uint8_t i = 0 ; string[i]; i++)
	{
		LCD_WriteData(string[i]); // 문자열의 각 문자를 LCD에 출력
	}
}

void LCD_WritestringXY(uint8_t row, uint8_t col, char *string)
{
	LCD_gotoXY(row, col); // 지정된 위치로 커서 이동
	LCD_WriteString(string); // 문자열을 해당 위치로부터 출력
}


void LCD_Init()
{
	LCD_DATA_DDR = 0xff;
	LCD_RS_DDR |= (1<<LCD_RS);
	LCD_RW_DDR |= (1<<LCD_RW);
	LCD_E_DDR |= (1<<LCD_E);
	_delay_ms(20); // 초기화하고 15ms 이상 딜레이
	
	LCD_WriteCommand(COMMAND_8_BIT_MODE);
	_delay_ms(5); // 4.3ms 이상 딜레이
	LCD_WriteCommand(COMMAND_8_BIT_MODE);
	_delay_ms(5);
	LCD_WriteCommand(COMMAND_8_BIT_MODE);
	LCD_WriteCommand(COMMAND_8_BIT_MODE); // 플로우 차트 따라간 것일 뿐
	
	LCD_WriteCommand(COMMAND_DISPLAY_OFF);
	LCD_WriteCommand(COMMAND_DISPLAY_CLEAR);
	LCD_WriteCommand(COMMAND_DISPLAY_ON);
	LCD_WriteCommand(COMMAND_ENTRY_MODE); // 입력 모드 설정
	
}

 

<main.c>

#include "LCD.h"

int main(void)
{
	LCD_Init();
	
	LCD_gotoXY(0,0); // 첫째 줄에 출력해라.
	LCD_WriteString("Hello LCD");
	LCD_gotoXY(1,0); // 둘째 줄에 출력해라.
	LCD_WriteString("Good AVR");
   
    while (1) 
    {
    }
}

 

하나씩 출력 + 반복

 

// c파일에서 조정 //

void LCD_EnablePin()
{
	LCD_E_PORT &= ~(1<<LCD_E);
	LCD_E_PORT |= (1<<LCD_E);
	LCD_E_PORT &= ~(1<<LCD_E);
	_delay_ms(100); // 이 시간 조절해서, 문자 간 출력 딜레이 설정 가능
}



// main.c 에서 조정 //

int main(void)
{
	LCD_Init();
	

	
	while (1)
	{
		LCD_gotoXY(0,0);
		LCD_WriteString("Hello LCD!");
		LCD_gotoXY(1,0);
		LCD_WriteString("Good AVR!");
		_delay_ms(500);
		
		LCD_WriteCommand(COMMAND_DISPLAY_OFF);
		LCD_WriteCommand(COMMAND_DISPLAY_CLEAR);
		LCD_WriteCommand(COMMAND_DISPLAY_ON);
		LCD_WriteCommand(COMMAND_ENTRY_MODE);
		
	}
}
입력받은 값 출력

 

int main()
{
	char buff[30];
	LCD_Init();
	sprintf(buff, "Hello AVR"); // stdio.h 헤더파일 추가해줄 것
	LCD_WritestringXY(0, 0, buff);
	int count = 0;
	while (1)
	{
		sprintf(buff, "count : %d", count++);
		LCD_WritestringXY(1, 0, buff);
		_delay_ms(30);
	}
}

 

※ sprintf 함수

    sprintf ( str , "문자열" ); 

       ▶ str 문자열에는 "문자열" 이 저장된다.

    sprintf ( str , "%d" , 78);

       ▶ str 문자열에는 문자열 "78"이 저장된다,

     즉, 출력값을 문자열에 저장하여 출력하는 함수이다.

 

 

[LCD_4Bit]

 

<LCD_4bit.h>

#ifndef LCD_4BIT_H_
#define LCD_4BIT_H_

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


#define LCD_DATA_DDR	DDRC
#define LCD_DATA_PORT	PORTC // 데이터 포트
#define LCD_DATA_PIN	PINC
#define LCD_RS_DDR		DDRB // 제어 핀 연결
#define LCD_RW_DDR		DDRB
#define LCD_E_DDR		DDRB
#define LCD_RS_PORT		PORTB
#define LCD_RW_PORT		PORTB
#define LCD_E_PORT		PORTB
#define LCD_RS			5
#define LCD_RW			6
#define LCD_E			7


// COMAND
#define COMMAND_DISPLAY_CLEAR	0x01
#define COMMAND_DISPLAY_ON		0x0C // 화면 ON, 커서 OFF, 커서 점멸 OFF
#define COMMAND_DISPLAY_OFF		0x08 // 화면 OFF, 커서 OFF, 커서 점멸 OFF
#define COMMAND_ENTRY_MODE		0x06 // 커서 우측 이동, 화면 이동 없음
#define COMMAND_8_BIT_MODE		0x38 // 8비트, 화면 2행, 5x8 Font
#define COMMAND_4_BIT_MODE		0x28 // 4비트, 화면 1행, 5x11 Font


// 함수 원형 선언
void LCD_Data(uint8_t data);
void LCD_Data4bit(uint8_t data);
void LCD_EnablePin();
void LCD_WritePin();
void LCD_ReadPin();
void LCD_WriteCommand(uint8_t commandData);
void LCD_WriteData(uint8_t charData);
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();
void LCD_Data4bit_Init(uint8_t data);


#endif /* LCD_4BIT_H_ */

 

<LCD_4bit.c>

#include "LCD_4bit.h"



void LCD_Data(uint8_t data)
{
	LCD_DATA_PORT = data; // 데이터 핀에 데이터 출력
}

void LCD_Data4bit(uint8_t data)
{
	LCD_DATA_PORT = (LCD_DATA_PORT & 0x0f) | (data & 0xf0); // 상위 4bit 출력
	// 이전 상위 비트 다 날라감 | data의 상위비트 살림
	LCD_EnablePin();
	LCD_DATA_PORT = (LCD_DATA_PORT & 0x0f) | ((data & 0x0f)<<4); // 하위 4bit 출력
	LCD_EnablePin();
}


void LCD_Data4bit_Init(uint8_t data)
{
	LCD_RW_PORT &= ~(1<<LCD_RW);
	LCD_EnablePin();
}

void LCD_EnablePin()
{
	LCD_E_PORT &= ~(1<<LCD_E); // E 핀을 Low 설정
	LCD_E_PORT |= (1<<LCD_E); // E 핀을 High 설정
	// Low -> High 되어야 Data들이 전송된다.
	LCD_E_PORT &= ~(1<<LCD_E); // 다시 Low 설정
	_delay_us(1800); // 이 시간 조절해서, 문자 간 출력 딜레이 설정 가능
}


void LCD_WritePin()
{
	LCD_RW_PORT &= ~(1<<LCD_RW); // RW 핀을 Low로 설정하여 쓰기 모드
}


void LCD_WriteCommand(uint8_t commandData)
{
	LCD_RS_PORT &= ~(1<<LCD_RS); // RS 핀을 Low로 설정하여 명령어 모드
	LCD_WritePin(); // 데이터 쓰기 모드로 설정
	LCD_Data4bit(commandData); // 명령어 데이터를 데이터 핀에 출력_4bit
	// LCD_EnablePin(); // LCD 동작 신호 전송
}


void LCD_WriteData(uint8_t charData)
{
	LCD_RS_PORT |= (1<<LCD_RS); // RS 핀을 High로 설정하여 문자 데이터 모드
	LCD_WritePin();
	LCD_Data4bit(charData); //_4bit
	// LCD_EnablePin();
}


void LCD_gotoXY(uint8_t row, uint8_t col)
{
	col %= 16; // 열 인덱스를 0부터 15로 제한
	row %= 2; // 행 인덱스를 0부터 1로 제한
	
	uint8_t address = (0x40 * row) + col; // 주소 계산
	uint8_t command = 0x80 + address; // command 값 계산
	LCD_WriteCommand(command); // 주소 설정 command를 LCD에 전달
}


void LCD_WriteString(char *string)
{
	for (uint8_t i = 0 ; string[i]; i++)
	{
		LCD_WriteData(string[i]); // 문자열의 각 문자를 LCD에 출력
	}
}

void LCD_WritestringXY(uint8_t row, uint8_t col, char *string)
{
	LCD_gotoXY(row, col); // 지정된 위치로 커서 이동
	LCD_WriteString(string); // 문자열을 해당 위치로부터 출력
}


void LCD_Init()
{
	LCD_DATA_DDR = 0xff;
	LCD_RS_DDR |= (1<<LCD_RS);
	LCD_RW_DDR |= (1<<LCD_RW);
	LCD_E_DDR |= (1<<LCD_E);
	_delay_ms(20); // 초기화하고 15ms 이상 딜레이
	
	LCD_WriteCommand(0x03);
	_delay_ms(5); // 4.3ms 이상 딜레이
	LCD_WriteCommand(0x03);
	_delay_ms(5);
	LCD_WriteCommand(0x03);
	LCD_WriteCommand(0x02); // data sheet의 플로우 차트 따라간 것일 뿐
	LCD_WriteCommand(COMMAND_4_BIT_MODE);
	
	LCD_WriteCommand(COMMAND_DISPLAY_OFF);
	LCD_WriteCommand(COMMAND_DISPLAY_CLEAR);
	LCD_WriteCommand(COMMAND_DISPLAY_ON);
	LCD_WriteCommand(COMMAND_ENTRY_MODE); // 입력 모드 설정
}

 

<main.c>

#include "LCD_4bit.h"

// LCD 4bit //
// 4bit는 직렬 방식으로 2번 보낸다. // 
int main(void)
{
    char buff[30];
	LCD_Init();
	
	sprintf(buff, "Hello AVR");
	LCD_WritestringXY(0,0,buff);
	int count = 0;
	
	
    while (1) 
    {
		sprintf(buff, "count : %d", count++);
		LCD_WritestringXY(1,0,buff);
		_delay_ms(300);
    }
}

 


 

[LCD_I2C]

 

I2C통신이란?

: Inter Integrated Circuit

  여러 개의 슬레이브와 통신이 가능하며, 여러 개의 마스터끼리도 통신이 가능하다.

  데이터를 주고 받는데 필요한 통신선은 2개이다.

  - SDA(Serial Data Line)

  - SCL(Serial Clock Line)

 

시리얼 통신 방식

 

구분 UART SPI I2C
동기 / 비동기 비동기 동기 동기
전이중/반이중 전이중 전이중 반이중
연결 방식 1:1 1:N 1:N
연결 선 개수 1개 슬레이브 연결 데이터 2 2 1(반이중)
클록 0 1 1
제어 0 1 0
합계 2 4 2
슬레이브 선택 - 하드웨어
(SS라인)
소프트웨어
(주소 지정)

전이중 : 전화기, 반이중 : 무전기를 생각하면 쉽다.

 

I2C 통신의 특징

 

    - 동기식 통신 방법

    - 각 슬레이브 장치는 고유의 주소값(7bit)을 가진다.

    - 버스에 연결된 장치들이 고유의 주소값을 가지고 master/slave 관계를 가진다.

    - 세 가진 전송 속도를 지원

   
표준 모드(Standard Mode) 100Kbps
고속 모드(Fast Mode) 400Kbps
초고속 모드(High Speed Mode) 3.4Mbps

 

I2C 통신 방법

   - 마스터가 통신의 시작 및 통신의 종료를 선언하여 통신이 이루어짐

   - 마스터가 특정 주소를 가진 슬레이브와 연결하여 송수신을 수행

   - 마스터가 클록 신호 SCL을 발생하여 통신

   - 통신을 위하여 기본적으로 SDA와 SCL은 High 상태를 유지하기 위해 PULL-UP저항을 달아줘야 한다.

 

 

마스터에서 슬레이브로 쓰기

    : START → 슬레이브 W 모드 → 마스터에서 데이터 전송 → 슬레이브에서 받고 ACK 신호 출력 → .... → ACK → STOP

 

 마스터가 슬레이브에서 읽기

    : START → 마스터에서 R 모드 → 슬레이브에서 데이터 전송 → 마스터에서 ACK → .... → 마스터에서 NACK → STOP

 

 

START

    : SCL이 High일 때, SDA가 High에서 Low로 떨어지면 Start bit

 

STOP

    : SCL이 High일 때, SDA가 Low에서 High로 올라가면 Stop bit

 

Register

 

1. TWBR (TWI Bit Rate Register)

보통 TWPS = 0으로 놓고 계산한다.

     - Master SCL 주파수를 구할 때 쓰는 bitrate를 저장하는 레지스터

     - TWER이 72인 경우, SCL은 100,000Hz(100KHz)에 해당(분주율 1 가정)

     - TWER 32 = 200KHz, TWER 12 = 400KHz

     - TWPS : TWI Reg 내부에 있는 분주비의 값

 

2. TWCR (TWI Control Register)

 

    1) TWINT(TWI Interrupt Flag, 7번 bit)

          - TWI의 현재 작업이 완료되면 하드웨어적으로 1로 세트되며,

            응용 소프트웨어에서 1을 Write하여 TWINT = 0 으로 클리어한다.

          - SERG 레지스터의 I bit(전역)가 set되고 TWCR 레지스터의 TWIE가 set되어 있을 때

            현재 작업이 완료되고 TWINT bit가 set되면, ISR을 실행한다.,

 

     2) TWEA(TWI Enable Acknowledge bit, 6번 bit)

           - ACK 펄스의 생성을 제어한다. 본 bit가 set되어 있을 때,

            아래와 같은 상황에서 ACK 펄스 생성

            ① 슬레이브 모드에서 자신의 주소를 수신하고 나서

            ② 슬레이브 모드에서 TWAR 레지스터의 TWGCE bit가 세트되어 있고

                슬레이브로 전달되는 일반 호출을 수신한 경우

            ③ 마스터나 슬레이브 모드에서 한 Byte 데이터를 수신한 경우

 

     3) TWSTA(TWI Start Condition bit, 5번 bit)

           - 마스터 모드에서 전송을 시작하기 위해 해당 bit를 set

 

     4) TWSTO(TWI Stop Condition bit, 4번 bit)

           - 마스터 모드에서 전송을 종료하기 위해 해당 bit를 set

 

     5) TWWC(TWI Write Collision bit, 3번 bit)

           - TWINT bit가 0인 경우,(데이터 전송 작업이 완료되지 않은 경우)

             TWDR 레지스터에 데이터를 기록하려고 하면 set된다.

           - TWINT bit가 1인 경우, TWDR 레지스터에 데이터를 기록하면 클리어된다.

 

     6) TWEN(TWI Enable bit, 2번 bit)

           - TWI 동작을 활성화하고 TWI의 인터페이스를 활성화한다.

           - Enable = 1(PORT0, PORT1 사용)

 

3. TWDR (TWI Data Register)

     : 송수신되는 데이터를 저장하는 레지스터

 

4. TWSR (TWI Status Register)

    - TWS7 ~ TWS3 bit는 TWI의 로직과 버스의 상태를 반영한다.

    - TWPS1, TWPS0는 분주비를 나타낸다.

TWPS1 TWPS0 Prescaler Value
0 0 1
0 1 4
1 0 16
1 1 64

 

C code Sample

 

위와 같이 code sample이 존재하므로 참고하면 좋다.

 

I2C 예제

 

<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 // SCL : Clock Line
#define I2C_SDA	PORTD1 // SDA : Data Line

// 함수 원형 선언 //
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.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 시점을 결정
	                             // TWI 통신 동작이 완료될 때까지 대기하는 역할
                                 // 기다렸다가 조건이 거짓이 되면 while문을 빠져나간다.
                                 // 즉, TWCR에서 TWINT bit가 1이 되면, while 문의 조건이 거짓이 되고 빠져나가게 된다.
}

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

void I2C_TxData(uint8_t data) // data 1바이트 전송
{
	TWDR = data; // TWDR Register에 data 값 저장
	TWCR = (1<<TWINT) | (1<<TWEN); // 플래그 세우고 En = 1로 바꿔서 데이터 전송 완료됨을 알림
	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.h>

#ifndef I2C_LCD_H_
#define I2C_LCD_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_ */

 

<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);
    // 1Byte 출력하고 1600us 대기(LCD는 매우 느린 부품이므로 빨리 출력하면 글자 다 깨짐)
}

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() // LCD의 Back_Light를 켜주는 함수
{
	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); // 딜레이들은 Data sheet 내의 4-bit Interface 참조
	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();
}

LCD 내부 주소 참조

 

<main.c>

int main(void)
{
    uint16_t count = 0;
	char buff[30];
	
	LCD_Init();
	LCD_WriteStringXY(0,0,"Hello ATmega128"); // 0행 0열부터 해당 문자열 출력하여라

    while (1) 
    {
		sprintf(buff, "count : %-5d", count++);
		LCD_WriteStringXY(1, 0, buff);
		_delay_ms(500);
    }
}

 

728x90
반응형