Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- stop watch
- LED
- vivado
- DHT11
- ring counter
- dataflow modeling
- FND
- uart 통신
- verilog
- Algorithm
- gpio
- ATMEGA128A
- soc 설계
- half adder
- atmega 128a
- test bench
- D Flip Flop
- hc-sr04
- structural modeling
- pwm
- behavioral modeling
- KEYPAD
- Recursion
- prescaling
- Pspice
- Edge Detector
- BASYS3
- java
- Linked List
- i2c 통신
Archives
- Today
- Total
거북이처럼 천천히
LCD Display (HD44780U) - (3, I2C 통신) 본문
Embedded Programming (AVR)/Atmega 128A (실습)
LCD Display (HD44780U) - (3, I2C 통신)
유로 청년 2024. 9. 12. 19:271. I2C 통신을 이용하여 HD44780U LCD 모듈과 통신하기
- 지난 게시글에서는 4bit, 8bit 모드로 HD44780U LCD 모듈과 직접 연결하여 통신을 하였다.
- 4bit, 8bit 모드 와이어를 직접 연결하기 때문에 상대적으로 I2C 통신과 속도 측면에서 비교하였을 때, 빠르다는 장점을 가지지만, 와이어를 직접 연결하는 만큼 핀에 대한 자원 낭비가 심하다는 단점을 갖고 있다.
- 그에 반면에 I2C 통신은 SCL, SDA 와이어만으로 LCD 모듈과 통신이 가능하기 때문에 속도가 상대적으로 느리지만, 핀에 대한 자원을 효율적으로 사용할 수 있다는 장점을 갖는다.
- 이번에는 HD44780U LCD 모듈을 8574칩을 매개체로 I2C 통신을 구현해보도록 하겠다.
- I2C 통신에 대한 자세한 내용이 궁금하다면 아래 게시글을 참고하길 바란다.
Verilog RTL 설계(8월 22일 - 1, I2C 통신을 통한 LCD 컨트롤 - (1)) (tistory.com)
2. I2C 통신에 필요한 함수 및 헤더 파일 정의
2.1. I2C 통신에 필요한 헤더 파일
#ifndef I2C_H_
#define I2C_H_
// Include private header file
#include "base.h"
// Define Function prototype
void I2C_init();
void I2C_start();
void I2C_end();
void I2C_send_data(uint8_t data);
void I2C_send_address_and_data(uint8_t address, uint8_t data);
// Define PORT
#define I2C_DDR DDRD
#define I2C_SCL_PIN 0
#define I2C_SDA_PIN 1
#endif /* I2C_H_ */
- LCD 디스플레이 모듈과 I2C 통신을 하기 위해 SCL과 SDA 핀을 PORT D에 연결하였다.
2.2. I2C 통신의 함수 정의
#include "I2C.h"
void I2C_init() {
// SCL, SDA Pin 모두 Output 설정
I2C_DDR |= (1<<0 | 1<<1);
// SCL의 주파수는 100kHz로 설정
// 16MHz SP을 Prescaling 하여 100kHz 주파수를 생성
// System Clock과 SCL Frequency와의 관계식은 데이터 시트에서 확인
TWBR = 72;
}
void I2C_start() {
// TWINT : TWCR Register에서 TWINT bit을 clear
// TWSTA : I2C Start 신호를 활성화
// TWEN : I2C Interface 활성화
TWCR = (1<<TWINT | 1<<TWSTA | 1<<TWEN);
// TWCR Register에서 TWINT 비트 값이 Flag를 올릴 때까지 기다린다.
while(!(TWCR & (1<<TWINT)));
}
void I2C_end() {
// TWINT : TWCR Register에서 TWINT bit을 clear
// TWEN : I2C Interface 활성화
// TWSTO : I2C Stop 신호를 활성화
TWCR = (1<<TWINT | 1<<TWEN | 1<<TWSTO);
}
void I2C_send_data(uint8_t data) {
// TWDR Register에 보내고자 하는 8bit 데이터 전달
TWDR = data;
// TWINT : TWCR Register에서 TWINT bit을 clear
// TWEN : I2C Interface 활성화
TWCR = (1<<TWINT | 1<<TWEN);
// TWCR Register에서 TWINT 비트 값이 Flag를 올릴 때까지 기다린다.
while(!(TWCR & (1<<TWINT)));
}
void I2C_send_address_and_data(uint8_t address, uint8_t data) {
I2C_start();
I2C_send_data(address);
I2C_send_data(data);
I2C_end();
}
< Source, I2C_send_address_and_data 함수 >
void I2C_send_address_and_data(uint8_t address, uint8_t data) {
I2C_start();
I2C_send_data(address);
I2C_send_data(data);
I2C_end();
}
- I2C 통신 과정을 보면 데이터를 MCU에서 LCD 모듈로 전송 한뒤, LCD 모듈측에서 이에 대한 Response signal을 송신하는지 여부를 확인할 필요가 있다. 이를 ACK 단계라고 표현한다.
- 하지만, LCD 모듈과 와이어로 직접 연결하고, 와이어의 길이가 짧기 때문에 데이터가 무조건 전달된다는 가정하에서 설계하였기 때문에 ACK 확인 과정은 생략하였다.
< Source, I2C_init 함수 >
void I2C_init() {
// SCL, SDA Pin 모두 Output 설정
I2C_DDR |= (1<<0 | 1<<1);
// SCL의 주파수는 100kHz로 설정
// 16MHz SP을 Prescaling 하여 100kHz 주파수를 생성
// System Clock과 SCL Frequency와의 관계식은 데이터 시트에서 확인
TWBR = 72;
}
- Q) TWBR 레지스터에 72이라는 값을 대입하는가?
- A) I2C 통신을 하기 위해서는 SCL 단선을 통해 100kHz 주파수를 갖는 Clock 신호를 전달할 필요가 있다. 100kHz 주파수를 갖는 Clock 신호를 만들어 내기 위해서 ATmega128A의 CP를 분주화 작업을 수행하게 된다. 그런데, ATmeaga128A의 Data Sheet에 따르면 100kHz 주파수를 갖는 SCL을 만들기 위해서는 아래와 같은 식을 사용하라고 정의하였다.
- 따라서 위 식을 이용하여 16MHz CP을 분주화하여 100kHZ 주파수를 갖는 SCL 신호를 만들기 위해서는 TWBR 레지스터에 72 값을 대입해야 한다.
< Source, I2C_start 함수 >
void I2C_start() {
// TWINT : TWCR Register에서 TWINT bit을 clear
// TWSTA : I2C Start 신호를 활성화
// TWEN : I2C Interface 활성화
TWCR = (1<<TWINT | 1<<TWSTA | 1<<TWEN);
// TWCR Register에서 TWINT 비트 값이 Flag를 올릴 때까지 기다린다.
while(!(TWCR & (1<<TWINT)));
}
- Q) I2C 통신을 시작하기 위해서 어쩌서 TWCR 레지스터에 위와 같은 값을 대입하였는가?
- A) 이는 ATmega128A의 Data Sheet 에서 찾을 수 있다.
- TWCR 레지스터에 대입하는 각각의 비트들은 다음과 같은 역활을 수행한다.
- TWINT (TWI Interrupt Flag):
- TWI 동작이 완료되면 하드웨어에 의해 1로 설정됩니다.
- 인터럽트를 발생시키는 역할을 합니다 (인터럽트가 활성화된 경우).
- 소프트웨어에서 이 비트를 1로 쓰면 플래그가 클리어됩니다.
- 이 비트가 1이면 TWI가 현재 작업 중이 아님을 나타냅니다.
- TWSTA (TWI Start Condition Bit):
- 이 비트를 1로 설정하면 TWI 마스터 모드에서 START 조건을 생성합니다.
- START 조건은 통신을 시작하기 위해 사용됩니다.
- 하드웨어는 START 조건을 전송한 후 자동으로 이 비트를 클리어합니다.
- TWEN (TWI Enable Bit):
- 이 비트를 1로 설정하면 TWI 인터페이스를 활성화합니다.
- TWI 핀(SCL과 SDA)을 제어하고 TWI 하드웨어 인터페이스를 활성화합니다.
- 이 비트가 0이면 TWI는 비활성화되고 모든 TWI 전송이 종료됩니다.
< Source, I2C_send_data 함수 >
void I2C_send_data(uint8_t data) {
// TWDR Register에 보내고자 하는 8bit 데이터 전달
TWDR = data;
// TWINT : TWCR Register에서 TWINT bit을 clear
// TWEN : I2C Interface 활성화
TWCR = (1<<TWINT | 1<<TWEN);
// TWCR Register에서 TWINT 비트 값이 Flag를 올릴 때까지 기다린다.
while(!(TWCR & (1<<TWINT)));
}
- parameter로 전달 받은 data을 I2C통신으로 MCU에서 LCD 디스플레이 모듈로 전달한다.
< Source, I2C_end 함수 >
void I2C_end() {
// TWINT : TWCR Register에서 TWINT bit을 clear
// TWEN : I2C Interface 활성화
// TWSTO : I2C Stop 신호를 활성화
TWCR = (1<<TWINT | 1<<TWEN | 1<<TWSTO);
}
- TWINT (TWI Interrupt Flag):
- TWI 동작이 완료되면 하드웨어에 의해 1로 설정됩니다.
- 인터럽트를 발생시키는 역할을 합니다 (인터럽트가 활성화된 경우).
- 소프트웨어에서 이 비트를 1로 쓰면 플래그가 클리어됩니다.
- 이 비트가 1이면 TWI가 현재 작업 중이 아님을 나타냅니다.
- TWEN (TWI Enable Bit):
- 이 비트를 1로 설정하면 TWI 인터페이스를 활성화합니다.
- TWI 핀(SCL과 SDA)을 제어하고 TWI 하드웨어 인터페이스를 활성화합니다.
- 이 비트가 0이면 TWI는 비활성화되고 모든 TWI 전송이 종료됩니다.
- TWSTO (TWI Stop Condition Bit):
- 이 비트를 1로 설정하면 TWI 마스터 모드에서 STOP 조건을 생성합니다.
- STOP 조건은 통신을 종료하기 위해 사용됩니다.
- 하드웨어는 STOP 조건을 전송한 후 자동으로 이 비트를 클리어합니다.
- STOP 조건이 실행되면 TWINT 플래그는 설정되지 않습니다.
주어진 I2C_end() 함수에서 이 비트들은 다음과 같이 사용됩니다:
- (1<<TWINT)로 TWINT 플래그를 클리어합니다.
- (1<<TWEN)으로 TWI 인터페이스를 활성 상태로 유지합니다.
- (1<<TWSTO)로 STOP 조건을 생성합니다.
이 설정으로 I2C 통신을 종료하고 버스를 해제합니다. STOP 조건이 생성된 후에는 TWINT 플래그가 설정되지 않으므로, 이 함수는 TWINT 플래그를 기다리지 않고 즉시 반환됩니다.
2.3. LCD Control 헤더 파일 선언
#ifndef I2C_LDC_H_
#define I2C_LDC_H_
// Include private library
#include "base.h"
#include "I2C.h"
// Define Control Signal
#define LCD_RS 0
#define LCD_RW 1
#define LCD_E 2
#define LCD_BACKLIGHT 3
#define ADDRESS 0x27<<1 // {address, 0}
// Define function prototype
void LCD_init();
void LCD_send_data(uint8_t data);
void LCD_enable();
void LCD_gotoXY(uint8_t x, uint8_t y);
void LCD_write_command(uint8_t command);
void LCD_write_data(uint8_t data);
void LCD_send_string(char* string);
#endif /* I2C_LDC_H_ */
2.4. LCD Control 함수들 선언
#include "I2C_LDC.h"
// Declare Global variable
uint8_t LCD_8bit_data; // {4bit 데이터, BT, Enable, RW, RS}
void LCD_init() {
I2C_init();
_delay_ms(30);
LCD_write_command(0x33);
LCD_write_command(0x32);
LCD_write_command(0x2c);
LCD_write_command(0x0c);
LCD_write_command(0x01);
LCD_write_command(0x06);
// Send LCD Data for Turn on LCD
LCD_8bit_data |= (1<<LCD_BACKLIGHT);
I2C_send_address_and_data(ADDRESS, LCD_8bit_data);
}
void LCD_send_data(uint8_t data) {
LCD_8bit_data = (LCD_8bit_data & 0x0f) | (data & 0xf0);
LCD_enable();
LCD_8bit_data = (LCD_8bit_data & 0x0f) | ((data & 0x0f) << 4);
LCD_enable();
}
void LCD_enable() {
// Enable 신호 값 : Low -> High -> Low
LCD_8bit_data &= ~(1<<LCD_E);
I2C_send_address_and_data(ADDRESS, LCD_8bit_data);
LCD_8bit_data |= (1<<LCD_E);
I2C_send_address_and_data(ADDRESS, LCD_8bit_data);
LCD_8bit_data &= ~(1<<LCD_E);
I2C_send_address_and_data(ADDRESS, LCD_8bit_data);
// Enable 신호가 안정화 되기까지 1600us 대기
_delay_us(1600);
}
void LCD_gotoXY(uint8_t y, uint8_t x) {
// x값과 y값을 LCD 화면으로 제한
x %= 16; y %= 2;
// compute specific address
uint8_t address = 0x40 * y + x;
// combine command and computed address
uint8_t command = 0x80 + address;
// Send command
LCD_write_command(command);
}
void LCD_write_command(uint8_t command) {
// Change Command Register
LCD_8bit_data &= ~(1<<LCD_RS);
// Change Write mode
LCD_8bit_data &= ~(1<<LCD_RW);
// Send command
LCD_send_data(command);
}
void LCD_write_data(uint8_t data) {
// Change Command Register
LCD_8bit_data |= (1<<LCD_RS);
// Change Write mode
LCD_8bit_data &= ~(1<<LCD_RW);
// Send command
LCD_send_data(data);
}
void LCD_send_string(char* string) {
uint8_t str_len = strlen(string);
for(uint8_t i=0; i<str_len; i++)
LCD_write_data(string[i]);
}
2.5. Main 함수
#include "I2C_LDC.h"
int main(void)
{
LCD_init();
while (1)
{
LCD_gotoXY(0, 0);
LCD_send_string("Hello World!");
LCD_gotoXY(1, 0);
LCD_send_string("I am I2C !");
}
}
3. 구현
'Embedded Programming (AVR) > Atmega 128A (실습)' 카테고리의 다른 글
LCD Display (HD44780U) - (2, 4bit mode) (0) | 2024.09.11 |
---|---|
LCD Display (HD44780U) - (1, 8bit mode) (0) | 2024.09.11 |
8bit Timer / counter - Phase Correct Fast PWM Mode (0) | 2024.06.19 |
Structure (1) | 2024.06.11 |
다양한 파형 생성 실험 (8bit Timer/Counter) (0) | 2024.06.09 |