1. 소개 (Introduction)
이 글은는 AVR128DA48 Curiosity Nano 마이크로컨트롤러(24MHz 시스템 클럭)를 사용하여 PCF8574 I/O 확장 칩을 통해 I2C로 1602 LCD를 제어하는 최적화된 방법을 설명합니다 (This report explains an optimized method for controlling a 1602 LCD using the AVR128DA48 Curiosity Nano microcontroller (24MHz system clock) via a PCF8574 I/O expander over I2C).
시스템은 첫 번째 줄에 "Count: "와 0~99 카운터를 표시하고, 두 번째 줄에 "end of count!"를 표시한 후 1초마다 화면을 지웁니다 (The system displays "Count: " with a 0–99 counter on the first line, "end of count!" on the second line, and clears the screen every second).
Arduino IDE와 DxCore를 사용하며, C++로 고수준 로직을, 어셈블리로 저수준 I2C 통신과 딜레이를 구현했습니다. 에러 처리, 동적 클럭 조정, 표시 속도 최적화가 포함됩니다 (Using Arduino IDE with DxCore, it implements high-level logic in C++ and low-level I2C communication and delays in assembly, including error handling, dynamic clock adjustment, and display speed optimization).
2. Arduino IDE와 DxCore 설정 (Setting Up Arduino IDE with DxCore)
2.1. 개요 (Overview)
AVR128DA48 Curiosity Nano는 Microchip의 AVR DA 시리즈를 위한 평가 보드입니다 (The AVR128DA48 Curiosity Nano is an evaluation board for Microchip’s AVR DA series). DxCore는 I2C, SPI, UART를 지원하여 Arduino IDE에서 프로그래밍을 가능하게 합니다 (DxCore supports I2C, SPI, UART, enabling programming in Arduino IDE).
2.2. 전제 조건 (Prerequisites)
- 하드웨어: AVR128DA48 Curiosity Nano 보드, USB 케이블 (Standard-A to Micro-B) (Hardware: AVR128DA48 Curiosity Nano board, USB cable (Standard-A to Micro-B)).
- 소프트웨어: Arduino IDE (1.8.19 이상 권장) (Software: Arduino IDE, version 1.8.19 or later recommended).
- 운영 체제: Windows, macOS, Linux (Operating System: Windows, macOS, or Linux).
2.3. 설정 단계 (Setup Steps)
- Arduino IDE 설치 (Install Arduino IDE):
- www.arduino.cc에서 다운로드 및 설치 (Download and install from www.arduino.cc).
- DxCore 보드 매니저 추가 (Add DxCore Board Manager):
- Arduino IDE에서 파일 > 환경 설정으로 이동 (Go to File > Preferences in Arduino IDE).
- 추가 보드 매니저 URL에 입력 (Enter in Additional Boards Manager URLs):
http://drazzy.com/package_drazzy.com_index.json
- 확인 클릭 (Click OK).
- DxCore 설치 (Install DxCore):
- 도구 > 보드 > 보드 매니저로 이동 (Go to Tools > Board > Boards Manager).
DxCore
검색 후 DxCore by Spence Konde 설치 (Search forDxCore
and install DxCore by Spence Konde).
- 보드 선택 (Select Board):
- 도구 > 보드 > DxCore > AVR128DA48 Curiosity Nano 선택 (Select Tools > Board > DxCore > AVR128DA48 Curiosity Nano).
- 도구 > 포트에서 COM 포트 선택 (Select COM port under Tools > Port).
- 프로그래머 설정 (Configure Programmer):
- 도구 > 프로그래머에서 Microchip PKOB Nano UPDI mode 선택 (Select Microchip PKOB Nano UPDI mode under Tools > Programmer).
- 테스트 업로드 (Test Upload):
- Blink 스케치 작성 (Write a Blink sketch):
void setup() { pinMode(2, OUTPUT); // PA2 핀을 출력으로 설정, 보드의 LED (Set PA2 as output, board LED) } void loop() { digitalWrite(2, HIGH); // LED 켜기 (Turn on LED) delay(1000); // 1초 대기 (Wait 1 second) digitalWrite(2, LOW); // LED 끄기 (Turn off LED) delay(1000); // 1초 대기 (Wait 1 second) }
- 업로드 클릭하여 LED 깜빡임 확인 (Click Upload to verify LED blinking).
- Blink 스케치 작성 (Write a Blink sketch):
2.4. 주의사항 (Notes)
클럭 설정: 도구 > Clock에서 24MHz 선택 (Select 24MHz in Tools > Clock).
드라이버: Windows에서 보드 인식 실패 시 MPLAB X IDE 설치 (Install MPLAB X IDE if board is not recognized on Windows).
I2C 핀: 기본 핀은 PA2 (SDA), PA3 (SCL) (Default pins: PA2 (SDA), PA3 (SCL)).
참고 자료: DxCore GitHub, AVR128DA48 Curiosity Nano 가이드 (Resources: DxCore GitHub, AVR128DA48 Curiosity Nano Guide).
3. 시스템 개요 (System Overview)
시스템 구성 요소는 다음과 같습니다 (System components are as follows):
- AVR128DA48: 128KB 플래시, 16KB RAM, TWI 모듈, 24MHz 동작 (128KB flash, 16KB RAM, TWI module, 24MHz operation).
- PCF8574: LCD 인터페이스용 I2C I/O 확장 칩 (쓰기 주소: 0x40) (I2C I/O expander for LCD, write address: 0x40).
- 1602 LCD: 4비트 모드, 5x7 도트 매트릭스의 16x2 문자 LCD (16x2 character LCD, 4-bit mode, 5x7 dot matrix).


프로그램은 첫 번째 줄에 "Count: "와 0~99 카운터를 표시하고, 두 번째 줄에 "end of count!"를 표시한 후 1초마다 화면을 지웁니다 (Program displays "Count: " with 0–99 counter on the first line, "end of count!" on the second line, clearing the screen every second).
4. 구현 세부사항 (Implementation Details)
4.1. 시스템 흐름 (System Flow)
- 초기화: I2C(100kHz SCL)와 LCD(4비트 모드, 2줄, 5x7 매트릭스) 설정 (Initialize I2C (100kHz SCL) and LCD (4-bit mode, 2 lines, 5x7 matrix)).
- 메인 루프: "Count: ", 0~99 카운터(300ms 간격), "end of count!" 표시, 1초 후 화면 지우기 (Display "Count: ", 0–99 counter (300ms intervals), "end of count!", clear screen after 1 second).
- I2C 통신: PCF8574로 LCD 핀 제어, 에러 처리 추가 (Control LCD pins via PCF8574 with error handling).
- 딜레이: F_CPU 기반 0.458µs, 40µs, 1.6ms 딜레이 (F_CPU-based 0.458µs, 40µs, 1.6ms delays).
4.2. I2C 설정 (I2C Configuration)
TWI0 모듈은 동적으로 계산된 바우드 레이트로 100kHz SCL을 설정합니다 (TWI0 module sets 100kHz SCL with dynamically calculated baud rate):
f_SCL = F_CPU / (10 + 2 * MBAUD)
MBAUD = (F_CPU / (2 * 100000)) - 5
4.3. 딜레이 타이밍 (Delay Timing)
F_CPU 기반으로 동적 조정된 딜레이 (Delays dynamically adjusted based on F_CPU):
delay_short
: 약 0.458µs at 24MHz (Approximately 0.458µs at 24MHz).delay_us
: 약 40µs at 24MHz (Approximately 40µs at 24MHz).delay_ms
: 약 1.6ms at 24MHz (Approximately 1.6ms at 24MHz).
5. 코드 구현 (Code Implementation)
5.1. C++ 코드 (LCD_PCF8574_AVR128DA48.ino)
LCD 초기화, 텍스트 표시, 카운터 로직을 처리하며, 에러 처리와 표시 속도 최적화를 포함합니다 (Handles LCD initialization, text display, counter logic with error handling and display speed optimization).
//-----------------------------------------------
// Programming 1602 LCD via PCF8574 I/O Expander (Optimized)
// 1602 LCD를 PCF8574 I/O 확장 칩을 통해 프로그래밍 (최적화됨)
//-----------------------------------------------
#define F_CPU 24000000UL // 시스템 클럭 정의, 24MHz 기본 (Define system clock, 24MHz default)
#include <avr/io.h>
extern "C"
{
bool I2C_init(); // I2C SCL 주파수를 100kHz로 동적 초기화, 성공 여부 반환 (Dynamically initialize I2C SCL to 100kHz, return success status)
bool I2C_start(); // I2C 시작 조건 전송, 성공 여부 반환 (Send I2C START condition, return success status)
bool I2C_write(byte); // SDA 라인을 통해 데이터 바이트 전송, 에러 처리 포함 (Send data byte via SDA, includes error handling)
void I2C_stop(); // I2C 정지 조건 전송 (Send I2C STOP condition)
void delay_short(); // F_CPU 기반 약 0.458us 딜레이 (Approximately 0.458us delay based on F_CPU)
void delay_us(); // F_CPU 기반 약 40us 딜레이 (Approximately 40us delay based on F_CPU)
void delay_ms(); // F_CPU 기반 약 1.6ms 딜레이 (Approximately 1.6ms delay based on F_CPU)
}
//==================================================================
bool setup()
{
if (!I2C_init()) return false; // I2C 초기화, 실패 시 false 반환 (Initialize I2C, return false if failed)
if (!LCD_init()) return false; // 1602 LCD 초기화, 실패 시 false 반환 (Initialize 1602 LCD, return false if failed)
delay(1000); // 1초 대기, LCD 안정화 (1-second delay for LCD stabilization)
return true; // 초기화 성공 (Initialization successful)
}
//==================================================================
void loop()
{
if (!LCD_sendText("Count: ")) return; // 텍스트 표시, 실패 시 종료 (Display text, exit if failed)
for(byte i = 0; i <= 99; i++) // 0부터 99까지 카운트 표시 (Display count from 0 to 99)
{
if (!LCD_sendChar(1, 0x30+(i/10)%10)) return; // 카운트의 상위 숫자(MSD) 표시 (Display MSD of count)
if (!LCD_sendChar(1, 0x30+i%10)) return; // 카운트의 하위 숫자(LSD) 표시 (Display LSD of count)
if (!LCD_sendChar(0, 0x87)) return; // 커서를 1번 줄 7번째 위치로 이동 (Move cursor to line 1, position 7)
delay(300); // 300ms 대기 (300ms delay)
}
if (!LCD_sendChar(0, 0xC0)) return; // 2번째 줄 시작 위치로 이동 (Move to beginning of 2nd line)
if (!LCD_sendText("end of count!")) return; // 텍스트 표시 (Display text)
delay(1000); // 1초 대기 (1-second delay)
if (!LCD_sendChar(0, 0x01)) return; // LCD 화면 지우기 (Clear LCD)
delay(1000); // 1초 대기 (1-second delay)
}
//==================================================================
bool PCF8574_sendByte(byte data)
{
if (!I2C_start()) return false; // I2C 시작 조건 전송, 실패 시 false (Send START condition, return false if failed)
if (!I2C_write(0x40)) return false; // PCF8574의 쓰기 주소 전송 (Send PCF8574 write address)
if (!I2C_write(data)) return false; // 데이터 바이트 전송 (Send data byte)
I2C_stop(); // I2C 정지 조건 전송 (Send STOP condition)
return true; // 전송 성공 (Transmission successful)
}
//==================================================================
bool LCD_init()
{
if (!LCD_sendChar(0, 0x33)) return false; // 4비트 모드로 LCD 초기화 (Initialize LCD for 4-bit mode)
delay_ms(); // 1.6ms 대기, 초기화 안정화 (1.6ms delay for stabilization)
if (!LCD_sendChar(0, 0x32)) return false; // 4비트 모드 설정 완료 (Complete 4-bit mode setup)
delay_ms(); // 1.6ms 대기 (1.6ms delay)
if (!LCD_sendChar(0, 0x28)) return false; // 2줄, 5x7 매트릭스 설정 (Set 2 lines, 5x7 matrix)
delay_ms(); // 1.6ms 대기 (1.6ms delay)
if (!LCD_sendChar(0, 0x0C)) return false; // 디스플레이 ON, 커서 OFF (Display ON, cursor OFF)
if (!LCD_sendChar(0, 0x01)) return false; // LCD 화면 지우기 (Clear LCD)
delay_ms(); // 1.6ms 대기 (1.6ms delay)
if (!LCD_sendChar(0, 0x06)) return false; // 커서를 오른쪽으로 이동 (Shift cursor right)
return true; // 초기화 성공 (Initialization successful)
}
//==================================================================
bool LCD_sendChar(byte mode, byte value)
{
byte highnib = value & 0xF0; // 상위 4비트 추출 (Extract high nibble)
byte lownib = (value << 4) & 0xF0; // 하위 4비트 추출 (Extract low nibble)
if (!send4bits(highnib | mode)) return false; // 상위 4비트 전송 (Send high nibble)
if (!send4bits(lownib | mode)) return false; // 하위 4비트 전송 (Send low nibble)
return true; // 전송 성공 (Transmission successful)
}
//==================================================================
bool send4bits(byte data)
{
if (!PCF8574_sendByte(data)) return false; // 데이터 전송 (Send data)
if (!PCF8574_sendByte(data | 0b00000100)) return false; // EN 핀을 HIGH로 설정 (Set EN pin HIGH)
delay_short(); // 0.458us 대기 (0.458us delay)
if (!PCF8574_sendByte(data & ~0b00000100)) return false; // EN 핀을 LOW로 설정 (Set EN pin LOW)
delay_us(); // 40us 대기 (40us delay)
return true; // 전송 성공 (Transmission successful)
}
//==================================================================
bool LCD_sendText(unsigned char text[])
{
byte i = 0;
while(text[i] != '\0') // 문자열 끝까지 반복 (Loop until end of string)
{
if (!LCD_sendChar(1, text[i])) return false; // 한 문자씩 표시 (Display one character at a time)
i++;
delay(50); // 50ms 대기, 표시 속도 최적화 (50ms delay for optimized display speed)
}
return true; // 전송 성공 (Transmission successful)
}
5.2. 어셈블리 코드 (I2C_AVR128DA48_24MHz.S)
AVR128DA48의 TWI 모듈과 F_CPU 기반 동적 클럭으로 I2C 통신 및 딜레이를 구현합니다 (Implements I2C communication and delays with AVR128DA48’s TWI module and F_CPU-based dynamic clock).
;---------------
; Assembly Code for AVR128DA48 (Dynamic F_CPU)
; AVR128DA48용 어셈블리 코드 (F_CPU 동적 설정)
;---------------
#define __SFR_OFFSET 0x00
#include <avr/io.h>
;------------------------
; 함수 선언: I2C 및 딜레이 루틴 (Function declarations: I2C and delay routines)
.global I2C_init
.global I2C_start
.global I2C_write
.global I2C_stop
.global delay_short
.global delay_us
.global delay_ms
;==============================================================
I2C_init:
LDI R21, 0x00
STS TWI0_MSTATUS, R21 ; TWI 상태 레지스터 초기화, 모든 플래그 지우기 (Clear TWI status register, reset all flags)
; MBAUD = (F_CPU / (2 * 100000)) - 5 for 100kHz SCL
LDI R21, (24000000 / (2 * 100000)) - 5 ; F_CPU=24MHz일 때 MBAUD=115 (MBAUD=115 for F_CPU=24MHz)
STS TWI0_MBAUD, R21 ; Baud 레지스터 동적 설정, 100kHz SCL 보장 (Set baud register dynamically, ensures 100kHz SCL)
LDI R21, (1<<TWI_ENABLE_bm)
STS TWI0_MCTRLA, R21 ; TWI 모듈 활성화, 마스터 모드 설정 (Enable TWI module, set master mode)
LDI R24, 1 ; 성공 상태 반환, R24=1 (Return success status, R24=1)
RET ; 함수 종료 (Return)
;==============================================================
I2C_start:
LDI R21, (1<<TWI_WIF_bm)|(1<<TWI_RIF_bm)|(1<<TWI_ENABLE_bm)|(1<<TWI_SDAHOLD_bm)
STS TWI0_MCTRLB, R21 ; I2C 시작 조건 전송, 쓰기/읽기 인터럽트 플래그 설정 (Issue START condition, set write/read interrupt flags)
wt1:LDS R21, TWI0_MSTATUS
SBRS R21, TWI_WIF_bp ; 쓰기 인터럽트 플래그 확인 (Check write interrupt flag)
RJMP wt1 ; 플래그가 설정될 때까지 대기 (Wait until flag is set)
LDI R24, 1 ; 성공 상태 반환, R24=1 (Return success status, R24=1)
RET ; 함수 종료 (Return)
;==============================================================
I2C_write:
LDS R22, TWI0_MSTATUS ; TWI 상태 레지스터 읽기 (Read TWI status register)
SBRC R22, TWI_BUSERR_bp ; 버스 에러 확인, 에러 시 err로 점프 (Check bus error, jump to err if set)
RJMP err
SBRC R22, TWI_ARBLOST_bp ; Arbitration Lost 에러 확인, 에러 시 err로 점프 (Check arbitration lost error, jump to err if set)
RJMP err
STS TWI0_MDATA, R24 ; R24의 데이터를 데이터 레지스터에 복사 (Copy byte in R24 to data register)
LDI R21, (1<<TWI_WIF_bm)|(1<<TWI_ENABLE_bm)
STS TWI0_MCTRLB, R21 ; 데이터 바이트 전송, 쓰기 동작 시작 (Transmit byte, start write operation)
wt2:LDS R21, TWI0_MSTATUS
SBRS R21, TWI_WIF_bp ; 쓰기 인터럽트 플래그 확인 (Check write interrupt flag)
RJMP wt2 ; 전송 완료까지 대기 (Wait for transmission completion)
LDI R24, 1 ; 성공 상태 반환, R24=1 (Return success status, R24=1)
RET ; 함수 종료 (Return)
err:LDI R24, 0 ; 에러 상태 반환, R24=0 (Return error status, R24=0)
RET ; 함수 종료 (Return)
;==============================================================
I2C_stop:
LDI R21, (1<<TWI_MCMD_STOP_gc)|(1<<TWI_ENABLE_bm)
STS TWI0_MCTRLB, R21 ; I2C 정지 조건 전송, TWI 모듈 유지 (Transmit STOP condition, maintain TWI module)
RET ; 함수 종료 (Return)
;==============================================================
delay_short:
; F_CPU 기반 약 0.458us (11 사이클 at 24MHz) (Approximately 0.458us based on F_CPU, 11 cycles at 24MHz)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
NOP ; 1 사이클 대기 (1 cycle wait)
RET ; 함수 종료, 총 11 사이클 (Return, total 11 cycles)
;==============================================================
delay_us:
; F_CPU / 24000000 * 87 = 약 40us at 24MHz (F_CPU / 24000000 * 87 ≈ 40us at 24MHz)
LDI R20, 87 ; 40us를 위해 반복 횟수 동적 설정 (Set iteration count dynamically for 40us)
l3: RCALL delay_short ; delay_short 호출 (Call delay_short)
DEC R20 ; 카운터 감소 (Decrement counter)
BRNE l3 ; 카운터가 0이 아니면 반복 (Loop if counter not zero)
RET ; 약 40us, 함수 종료 (≈ 40us, return)
;==============================================================
delay_ms:
; F_CPU / 24000000 * 40 * 87 = 약 1.6ms at 24MHz (F_CPU / 24000000 * 40 * 87 ≈ 1.6ms at 24MHz)
LDI R21, 40 ; 1.6ms를 위해 반복 횟수 동적 설정 (Set iteration count dynamically for 1.6ms)
l4: RCALL delay_us ; delay_us 호출 (Call delay_us)
DEC R21 ; 카운터 감소 (Decrement counter)
BRNE l4 ; 카운터가 0이 아니면 반복 (Loop if counter not zero)
RET ; 약 1.6ms, 함수 종료 (≈ 1.6ms, return)
6. 주의사항 (Notes and Considerations)
클럭 설정: F_CPU=24MHz 기본. 다른 주파수 시 동적 계산 적용 (Default F_CPU=24MHz; dynamic calculations apply for other frequencies).
PCF8574 주소: I2C 쓰기 주소 0x40 (기본 0x20, 좌측 시프트). A0-A2 핀 확인 (Write address 0x40 (base 0x20, shifted); verify A0-A2 pins).
TWI 핀: 기본 PA2 (SDA), PA3 (SCL). 다른 핀 사용 시 PORTMUX.TWIROUTEA
설정 (Default PA2 (SDA), PA3 (SCL); configure PORTMUX.TWIROUTEA
).
에러 처리: I2C 에러 발생 시 프로그램이 안전하게 종료 (Program safely exits on I2C errors).
7. 결론 (Conclusion)
이 문서는 AVR128DA48 Curiosity Nano로 1602 LCD를 PCF8574를 통해 제어하는 최적화된 방법을 보여줍니다 (This project demonstrates an optimized method for controlling a 1602 LCD with AVR128DA48 Curiosity Nano via PCF8574). 에러 처리, 동적 클럭 조정, 표시 속도 개선으로 안정성과 효율성을 높였으며, Arduino IDE와 DxCore를 활용해 임베디드 개발에 적합합니다 (Error handling, dynamic clock adjustment, and display speed improvements enhance stability and efficiency, suitable for embedded development with Arduino IDE and DxCore).