Language/AVR

ATmega 128 : CLCD, I2C

짱도르딘 2024. 9. 10. 17:05
728x90

ATmega128을 활용하여 LCD를 제어해보려한다.

 

기본적인 명령어는 아래의 표와 같다.

 


LCD를 제어하기위한 헤더파일의 코드는 아래와 같다.

 

헤더파일의 코드 내에는 주로 LCD를 다룰 함수들과 define으로 이루어져있다.

#ifndef LCD_H_
#define LCD_H_

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

#define LCD_DATA_DDR	DDRF
#define LCD_DATA_PORT	PORTF
#define LCD_DATA_PIN	PINF
#define LCD_RS_DDR		DDRE
#define LCD_RW_DDR		DDRE
#define LCD_E_DDR		DDRE
#define LCD_RS_PORT		PORTE
#define LCD_RW_PORT		PORTE
#define LCD_E_PORT		PORTE
#define LCD_RS			5
#define LCD_RW			6
#define LCD_E			7

#define COMMAND_DISPLAY_CLEAR	0x01
#define COMMAND_DISPLAY_ON		0x0C
#define COMMAND_DISPLAY_OFF		0x08
#define COMMAND_ENTRY_MODE		0x06
#define COMMAND_8_BIT_MODE		0x38
#define COMMAND_4_BIT_MODE		0x28

void LCD_Data(uint8_t data);
// void LCD_Data4bit(uint8_t data);
void LCD_WritePin();
void LCD_ReadPin();
void LCD_EnablePin();
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 LCDInit();

#endif /* LCD_H_ */

 

LCD 제어관련 소스코드는 아래와 같다.

 

소스코드에서는 헤더파일에서 다룬 함수들의 동작을 구체적으로 다룬다.

#include "lcd.h"

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

// void LCD_Data4bit(uint8_t data);
void LCD_WritePin()
{	// LOW
	LCD_RW_PORT &= ~(1<<LCD_RW); // RW핀을 LOW -> 쓰기모드로 진입
}
void LCD_ReadPin()
{
	LCD_RW_PORT |= (1<<LCD_RW); // RW핀을 High -> 읽기 모드
}
void LCD_EnablePin()
{
	LCD_E_PORT &= ~(1<<LCD_E); // E핀을 Low로 설정
	LCD_E_PORT |= (1<<LCD_E); // E핀을 High로 설정 -> 동작 신호를 전송 
	LCD_E_PORT &= ~(1<<LCD_E); // E핀을 Low로 설정
	_delay_us(1600);
}
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(); // LCD 동작신호 전송
}
void LCD_GotoXY(uint8_t row, uint8_t col)
{
	col %= 16;
	row %= 2;
	uint8_t address = (0x40 * row) + col; // 주소계산
	uint8_t command = 0x80 + address; // command 값 계산(주소 설정)
	LCD_WriteCommand(command); // 주소 command 전송
}
void LCD_WriteString(char *string)
{
	for (uint8_t i = 0; string[i]; i++)
	{
		LCD_WriteData(string[i]); // string Display
	}
}
void LCD_WriteStringXY(uint8_t row, uint8_t col, char *string)
{
	LCD_GotoXY(row, col);
	LCD_WriteString(string);
}
void LCDInit()
{
	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);
	LCD_WriteCommand(COMMAND_8_BIT_MODE);
	_delay_ms(5);
	LCD_WriteCommand(COMMAND_8_BIT_MODE);
	_delay_ms(1);
	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문의 코드는 아래와 같다.

 

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

#include "lcd.h"

int main(void)
{
	LCDInit();
	
	LCD_GotoXY(0, 0);
	LCD_WriteString("Hello AVR");
	LCD_GotoXY(1, 0);
	LCD_WriteString("Good ATmega128A");
	
    while (1) 
    {
		
    }
}

 

코드를 프로그래밍하면 LCD에는 다음과 같은 문구가 출력되는 것을 확인할 수 있다.

 

 


이제는 8bit가 아닌 4bit mode로 LCD를 제어해보려고 한다.

 

헤더파일에서 바뀐점은 'void LCD_Data4bit(uint8_t data);' 주석처리가 해제됐다는 점이다.

헤더파일의 코드는 아래와 같다.

#ifndef LCD_H_
#define LCD_H_

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

#define LCD_DATA_DDR	DDRF
#define LCD_DATA_PORT	PORTF
#define LCD_DATA_PIN	PINF
#define LCD_RS_DDR		DDRE
#define LCD_RW_DDR		DDRE
#define LCD_E_DDR		DDRE
#define LCD_RS_PORT		PORTE
#define LCD_RW_PORT		PORTE
#define LCD_E_PORT		PORTE
#define LCD_RS			5
#define LCD_RW			6
#define LCD_E			7

#define COMMAND_DISPLAY_CLEAR	0x01
#define COMMAND_DISPLAY_ON		0x0C
#define COMMAND_DISPLAY_OFF		0x08
#define COMMAND_ENTRY_MODE		0x06
#define COMMAND_8_BIT_MODE		0x38
#define COMMAND_4_BIT_MODE		0x28

void LCD_Data(uint8_t data);
void LCD_Data4bit(uint8_t data);
void LCD_WritePin();
void LCD_ReadPin();
void LCD_EnablePin();
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 LCDInit();

#endif /* LCD_H_ */

 

소스코드는 아래의 코드와 같다.

#include "lcd.h"

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

void LCD_Data4bit(uint8_t data)
{
	LCD_DATA_PORT = (LCD_DATA_PORT & 0x0f) | (data & 0xf0); // 상위 4비트 출력
	LCD_EnablePin(); // LCD 동작 신호 전송
	LCD_DATA_PORT = (LCD_DATA_PORT & 0x0f) | ((data & 0x0f) << 4); // 하위 4비트 출력
	LCD_EnablePin(); // LCD 동작 신호 전송
}
void LCD_WritePin()
{	// LOW
	LCD_RW_PORT &= ~(1<<LCD_RW); // RW핀을 LOW -> 쓰기모드로 진입
}
void LCD_ReadPin()
{
	LCD_RW_PORT |= (1<<LCD_RW); // RW핀을 High -> 읽기 모드
}
void LCD_EnablePin()
{
	LCD_E_PORT &= ~(1<<LCD_E); // E핀을 Low로 설정
	LCD_E_PORT |= (1<<LCD_E); // E핀을 High로 설정 -> 동작 신호를 전송 
	LCD_E_PORT &= ~(1<<LCD_E); // E핀을 Low로 설정
	_delay_us(1600);
}
void LCD_WriteCommand(uint8_t commandData)
{
	LCD_RS_PORT &= ~(1<<LCD_RS); // RS핀을 Low로 설정 -> 명령어모드 진입
	LCD_WritePin(); // 데이터 쓰기모드
	LCD_Data4bit(commandData); // 명령어를 데이터핀에 출력
	//LCD_EnablePin(); // LCD 동작신호 전송
}
void LCD_WriteData(uint8_t charData)
{
	LCD_RS_PORT |= (1<<LCD_RS); // RS핀을 High로 설정 -> 문자모드 진입
	LCD_WritePin(); // 데이터 쓰기모드
	LCD_Data4bit(charData); // 명령어를 데이터핀에 출력
	//LCD_EnablePin(); // LCD 동작신호 전송
}
void LCD_GotoXY(uint8_t row, uint8_t col)
{
	col %= 16;
	row %= 2;
	uint8_t address = (0x40 * row) + col; // 주소계산
	uint8_t command = 0x80 + address; // command 값 계산(주소 설정)
	LCD_WriteCommand(command); // 주소 command 전송
}
void LCD_WriteString(char *string)
{
	for (uint8_t i = 0; string[i]; i++)
	{
		LCD_WriteData(string[i]); // string Display
	}
}
void LCD_WriteStringXY(uint8_t row, uint8_t col, char *string)
{
	LCD_GotoXY(row, col);
	LCD_WriteString(string);
}
void LCDInit()
{
	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);
	LCD_WriteCommand(0x03);
	_delay_ms(5);
	LCD_WriteCommand(0x03);
	_delay_ms(5);
	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_DISPLAY_ON);
	LCD_WriteCommand(COMMAND_ENTRY_MODE);
}

 

메인소스의 코드는 아래와 같다.

 

#include <avr/io.h>

#include "lcd.h"

int main(void)
{
	LCDInit();
	LCD_GotoXY(0, 0);
	LCD_WriteString("Hello 4BIT");
	LCD_GotoXY(1, 0);
	LCD_WriteString("4BIT GOOD");
	
    while (1) 
    {
    }
}

 

출력되는 화면은 아래의 사진과 같다.

 

 


I2C

 

이번에는 I2C 통신을 활용하여 LCD를 제어해보려한다.

 

def.h 헤더파일로 기본적인 헤더파일을 선언하였다.

#ifndef DEF_H_
#define DEF_H_

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



#endif /* DEF_H_ */

 

I2C 헤더파일의 코드내용은 아래와 같다.

#ifndef I2C_H_
#define I2C_H_

#include "def.h"

#define I2C_DDR	DDRD
#define I2C_SCL	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 devAddress, uint8_t data);


#endif /* I2C_H_ */

 

I2C 소스파일의 코드는 아래와 같다.

#include "I2C.h"

void I2C_Init()
{
	I2C_DDR |= (1<<I2C_SCL) | (1<<I2C_SDA);
	TWBR = 72; // TWBR = 비트 Rate 선정하는 것 (100kHz)
	// TWBR = 32; // TWBR = 비트 Rate 선정하는 것 (200kHz)
	// TWBR = 12; // TWBR = 비트 Rate 선정하는 것 (400kHz)
}
void I2C_Start()
{
	TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
	while(!(TWCR & (1<<TWINT))); // TWINT 시점 결정
}
void I2C_Stop()
{
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
}
void I2C_TxData(uint8_t data) // 1byte 전송
{
	TWDR = data;
	TWCR = (1<<TWINT) | (1<<TWEN);
	while(!(TWCR & (1<<TWINT))); // 전송 완료 대기
}
void I2C_TxByte(uint8_t devAddress, uint8_t data)
{
	I2C_Start();
	I2C_TxData(devAddress);
	I2C_TxData(data);
	I2C_Stop();
}

 

I2C_LCD 헤더파일의 코드는 아래와 같다.

#ifndef I2C_LCD_H_
#define I2C_LCD_H_

#include "def.h"
#include "I2C.h"

#define LCD_RS	0
#define LCD_RW	1
#define LCD_E	2
#define LCD_BACKLIGHT	3
#define LCD_DEV_ADDR	(0x27<<1)

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

void LCD_Data4bit(uint8_t data);
void LCD_EnablePin();
void LCD_WriteCommand(uint8_t commandData);
void LCD_WriteData(uint8_t charData);
void LCD_BACKLIGHTOn();
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 소스파일의 코드는 아래와 같다.

 

#include "I2C_LCD.h"

uint8_t I2C_LCD_Data;

void LCD_Data4bit(uint8_t data)
{
		I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | (data & 0xf0); // 상위 4비트 출력
		LCD_EnablePin(); // LCD 동작 신호 전송
		I2C_LCD_Data = (I2C_LCD_Data & 0x0f) | ((data & 0x0f) << 4); // 하위 4비트 출력
		LCD_EnablePin(); // LCD 동작 신호 전송
}
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); // 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);
	
	_delay_us(1600);
}
void LCD_WriteCommand(uint8_t commandData)
{
	I2C_LCD_Data &= ~(1<<LCD_RS);
	I2C_LCD_Data &= ~(1<<LCD_RW);
	LCD_Data4bit(commandData);
}
void LCD_WriteData(uint8_t charData)
{
	I2C_LCD_Data |= (1<<LCD_RS);
	I2C_LCD_Data &= ~(1<<LCD_RW);
	LCD_Data4bit(charData);
}
void LCD_BACKLIGHTOn()
{
	I2C_LCD_Data |= (1<<LCD_BACKLIGHT);
	I2C_TxByte(LCD_DEV_ADDR, I2C_LCD_Data);
}
void LCD_GotoXY(uint8_t row, uint8_t col)
{
	col %= 16;
	row %= 2;
	uint8_t address = (0x40 * row) + col; // 주소계산
	uint8_t command = 0x80 + address; // command 값 계산(주소 설정)
	LCD_WriteCommand(command); // 주소 command 전송
}
void LCD_WriteString(char *string)
{
	for (uint8_t i = 0; string[i]; i++)
	{
		LCD_WriteData(string[i]); // string Display
	}
}
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(5);
	LCD_WriteCommand(0x03);
	_delay_ms(5);
	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_DISPLAY_ON);
	LCD_WriteCommand(COMMAND_ENTRY_MODE);
	LCD_BACKLIGHTOn();
}

 

LCD 출력을 정할 main의 코드는 아래와 같다.

#include "I2C_LCD.h"

int main(void)
{
	uint16_t count = 0;
	uint8_t buff[30];
	
	LCD_Init();
	LCD_WriteStringXY(0, 0, "Hello ATmega128a");
	
    while (1) 
    {
		sprintf(buff, "count : %-5d", count++);
		LCD_WriteStringXY(1, 0, buff);
		_delay_ms(200);
    }
}

 

동작 영상은 아래와 같다.

 

 

 

 

728x90

'Language > AVR' 카테고리의 다른 글

STM32 : UART  (2) 2024.09.12
STM32 : LED  (0) 2024.09.12
ATmega 128 : 4-Digit FND 동작 구현  (0) 2024.05.30
ATmega 128 : FND 동작 구현  (0) 2024.05.29
ATmega 128 : Led Bar 버튼 인터럽트 동작 구현  (0) 2024.05.29