I2C(TWI) LCD

2023. 3. 3. 17:02AVR ATmega128

1. I2C방식

I2C는 2개의 핀으로 여러개의 디바이스를 연결할 수 있게 해주는 2방향 직렬 통신이다. SCL(Serial Clock), SDA(Serial Data)의 두 핀을 사용하며, 동기 통신(어떠한 신호를 기준으로 데이터를 보내는 통신) 방식이다.

하나의 Master(기준 Clock을 발생하는 기기)가 나머지 Slave와 통신한다. 

SCL은 데이터 동기 속도이므로 Data 송수신 속도이고, SDA는 1비트 단위로 Data를 직렬로 통신한다. 

또, 선 1가닥으로 Read Write를 모두 진행하기 때문에 한번에 하나의 행동만 가능하다. 이러한 방식을 반이중 통신

(Half DUflex)라고 한다.

디바이스들은 각각 고유 주소를 가지고 있는데, 주소는 7비트값이므로, 0~127까지의 값으로 표시될 수 있다. 

또 두 선은 내부가 Open Drain (=Open Collector)상태이기 때문에, I2C통신을 하려면 오른쪽의 풀 업 저항을 달아놔야 한다. 

 

데이터 변경 타이밍 다이어그램

1비트의 데이터 신호를 주고 SCL에 1클록을 주면 해당 데이터를 송신한다. 

Clock이 LOW상태일때 데이터를 변경하고, 다시 HIGH상태일때 데이터를 보낸다. 

정리하면 

1. SDA에서 1비트 전송

2. SCL을 LOW->HIGH->LOW로 변경

3. Data 1비트 송수신 완료

데이터 전송 시작, 멈춤 타이밍 다이어그램

Start : SCL이 HIGH일때, SDA가 FALLING EDGE

Stop : SCL이 HIGH일때, SDA가 RISING EDGE

 

시작 신호가 발생된 직후 Master는 1바이트의 신호를 보내는데, 이 신호는 [ 7bit address | 1bit read write]로 이루어져 있다. 

ACK는 해당 주소의 Slave에서 Master로 제어신호를 정상적으로 신호를 송신했다는 1비트 ACK신호를 보낸다.

R/W는 다음 데이터의 Read, Write 여부를 결정하게 된다. 

정리하면

1. Start신호

2. 기기 주소 7비트+1비트 R/W신호(마스터 신호) + 1비트 ACK신호(슬레이브 신호)

3-1 Write byte 데이터(마스터) + 1비트 ACK신호(슬레이브)

3-2 Read byte 데이터(슬레이브) + 1비트 ACK신호(마스터)

...

4. Stop신호

Stop신호가 나오기 전까지는 계속 데이터만 보내며, Stop신호가 나오기 전까지는 R/W를 변경할 수 없다. 

 

 

 

한번에 표시하면 이 그림처럼 표현될 수 있다. LCD의 경우는 Master에서 Slave로 데이터를 보내기만 하므로, Write로만 동작하게 된다.

I2C의 장점은 적은 선으로 많은 디바이스를 제어할 수 있다.

단점은 중간중간 주소와 ACK신호를 보내기 때문에 다른 직렬 통신에 비해 상대적으로 느리다. 

I2C 통신은 노트북의 트랙패드처럼 속도가 중요하지 않은 곳에 자주 사용된다. 

 

2. I2C 코드

/*
 * I2C.h
 *
 * Created: 2023-03-03 오후 3:06:32
 *  Author: yrt12
 */ 


#ifndef I2C_H_
#define I2C_H_
#include <avr/io.h>

#define I2C_DDR DDRD
#define SCL 0
#define SDA 1



#endif /* I2C_H_ */
/*
 * I2C.c
 *
 * Created: 2023-03-03 오후 3:06:25
 *  Author: yrt12
 */ 

#include "I2C.h"

void I2C_init()
{
	I2C_DDR |= 1<<SCL | 1<<SDA;
	TWBR = 72; //100Khz
	// 	TWBR = 32; //200khz
	// 	TWBR = 12; //400kHz
	TWSR = 0;
}
void I2C_txByte(uint8_t SLA_W, uint8_t data)
{
	//Start 신호 송신
	I2C_start();
	//(주소 + write bit) 의 1Byte 데이터 송신 + ACK받기
	I2C_txDeviceAddress(SLA_W);
	//데이터 송신 + ACK
	I2C_txData(data);
	//Stop신호 송신
	I2C_stop();
}

void I2C_start()
{
	//	Send START condition
	TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
	// 	Wait for TWINT flag set. This indicates that the
	// 	START condition has been transmitt
	while (!(TWCR & (1<<TWINT)));
}

void I2C_txDeviceAddress(uint8_t address)
{
	/*Load SLA_W into TWDR Register.*/
	TWDR = address;
	/*Clear TWINT bit in TWCR to start transmission of address*/
	TWCR = (1<<TWINT) | (1<<TWEN);
	// 	Wait for TWINT flag set. This indicates that the SLA+W has been transmitted, and ACK/NACK has been received.
	while (!(TWCR & (1<<TWINT)));
}

void I2C_txData(uint8_t data)
{
	/*Load DATA into TWDR Register. */
	TWDR = data;
	/*Clear TWINT bit in TWCR to start transmission of data*/
	TWCR = (1<<TWINT) |(1<<TWEN);
	/*Wait for TWINT flag set. This indicates that the DATA has been transmitted, and ACK/NACK has been received.*/
	while (!(TWCR & (1<<TWINT)));
}

void I2C_stop()
{
	/*Transmit STOP condition*/
	TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
}

I2C 전송 코드는 위 코드와 같이 작성한다. 위 코드는 데이터시트의 TWI항목에 있는 샘플 코드를 가져온 것이다.

 

3. LCD

I2C LCD 회로도

I2C LCD는 PCF8574와 연결되어 있는데, LCD에 데이터핀의 상위 4비트에만 핀이 연결되어 있다. 따라서 데이터를 4비트 전송모드로 작동하게된다. 

 

 

LCD I2C 주소

8574의 데이터시트를 보면 주소 설정하는 부분이 있다.

LCD 보드 뒷편에 있는 A2, A1, A0라고 써져있는 부분이 있는데, 이부분이 OPEN상태면 slave address부분의 a2,a1,a0

비트가 1이 된다. 이 부분을 쇼트시키거나 오픈시키면 LCD의 주소를 변경할 수 있다. 

현재는 모두 오픈되어있기 때문에, Slave 주소는 0b0100111 = 0x27이 된다. 

I2C는 주소와 R/W 비트를 1바이트 내에 보내야하기 때문에, 

#define LCD_SLAVE_ADDRESS 0X27 //7비트값
#define LCD_SLA_W (LCD_SLA << 1) //R/W 신호가 들어갈 자리를 만들어주기 위해 왼쪽 쉬프트

주소값을 1만큼 왼쪽으로 쉬프트해준다. 따라서 LCD_SLA_W는 01001110이 되어 0100111의 주소와 0 이라는 Write 비트를 설정해준 것이다.

 

1602 DataSheet

LCD의 데이터시트를 보면 4비트 모드를 사용할때 1Byte를 전송하려면 상위 4비트를 먼저 전송하고 그 다음 하위 4비트를 전송하라고 설명되어 있다. 

따라서 원래 LCD 드라이버의 코드를 수정해야한다. 

4비트 데이터 전송 모드 - 1602 데이터시트

RS/RW를 모두 OFF한 상태에서, 4비트 단위로 LCD로 데이터를 보내 초기화 해준다.

Data를 보낼때, 8비트 단위로 4비트 데이터를 보내야 한다. 

코드로 바꾸면 다음과 같다.

void LCD_init()
{
	lcdControlData = 0;
	I2C_init();
	_delay_ms(15);
	LCD_cmdMode();
	LCD_writeMode();
	LCD_writeNibble(0x30);
	_delay_ms(5);
	LCD_writeNibble(0x30);
	_delay_ms(1);
	LCD_writeNibble(0x30);
	LCD_writeNibble(0x20);
	LCD_writeCmdData(LCD_4BIT_FUCTION_SET);
	LCD_writeCmdData(LCD_DISPLAY_OFF);
	LCD_writeCmdData(LCD_DISPLAY_CLEAR);
	LCD_writeCmdData(LCD_ENTRY_MODE_SET);
	LCD_writeCmdData(LCD_DISPLAY_ON);
	LCD_backLightOn();
}

다음은 LCD에 I2c 데이터를 전송하는 함수이다. 

void LCD_sendToI2C(uint8_t slw_w, uint8_t data)
{
	I2C_txByte(slw_w, data);
}

다음은 LCD의 모드를 설정하는 코드이다. 

 

void LCD_cmdMode()
{
	lcdControlData &= ~(1 << LCD_RS);
	LCD_sendToI2C(LCD_SLA_W, lcdControlData);
}

void LCD_charMode()
{
	lcdControlData |= (1 << LCD_RS);
	LCD_sendToI2C(LCD_SLA_W, lcdControlData);
}
void LCD_writeMode()
{
	lcdControlData &= ~(1 << LCD_RW);
	LCD_sendToI2C(LCD_SLA_W, lcdControlData);
}

void LCD_enableHigh()
{
	lcdControlData |= (1 << LCD_E);
	LCD_sendToI2C(LCD_SLA_W, lcdControlData);
	_delay_ms(1);
}

void LCD_enableLow()
{
	lcdControlData &= ~(1 << LCD_E);
	LCD_sendToI2C(LCD_SLA_W, lcdControlData);
	_delay_ms(1);
}

다음은 LCD에 데이터를 사용하는 코드이다. 

LCD의 데이터 핀이 4핀 모드로 동작하기 때문에 8비트를 보내려면 4비트씩 두번 보내야 한다. 

위에서 데이터를 보내려면 MSB를 먼저 보내야하기 때문에, data를 보내고 왼쪽으로 4 쉬프트 해야한다. 

writeNibble에서는 [data | control] 형식으로 바꿔준다. 

void LCD_writeByte(uint8_t data)
{
	LCD_writeNibble(data); //Nibble = 4비트
	data <<= 4;
	LCD_writeNibble(data);
}

void LCD_writeNibble(uint8_t data)
{
	LCD_enableHigh();
	lcdControlData = (lcdControlData & 0x0f) | (data & 0xf0); //콘트롤비트와 data의 상위 4비트를 합쳐준다. [data 4비트 | 컨트롤비트 4비트]
	LCD_sendToI2C(LCD_SLA_W,lcdControlData);
	LCD_enableLow();
}

나머지 코드는 이전 LCD와 같다. 

'AVR ATmega128' 카테고리의 다른 글

UART 활용  (0) 2023.03.06
UART  (0) 2023.03.06
초음파센서 드라이브 만들기  (0) 2023.03.03
AVR초음파 센서  (0) 2023.03.03
Timer를 이용한 Fan만들기  (0) 2023.03.02