개요
본 문서는 Microchip AVR32DA, AVR64DA, AVR128DA 시리즈에 내장된 NVMCTRL(Nonvolatile Memory Controller)의 구조와 동작을 분석하고, 이를 기반으로 모든 호환 디바이스에서 공통적으로 사용할 수 있는 범용 드라이버의 설계 및 구현 과정을 다룬다. 본 드라이버는 플래시, EEPROM, 사용자 행, 퓨즈 영역 등을 관리하며, 셀프 프로그래밍 및 부트로더 지원, 쓰기 보호, 표준 ISR 기반의 인터럽트 처리 기능을 제공한다. 또한 페이지 단위 플래시 지우기 및 쓰기, EEPROM의 바이트 단위 접근, 사용자 행 관리, 상태 및 오류 검출 기능을 포함하여, Microchip Studio와 AVR-GCC 환경에서 안정적이고 유지보수성이 높은 코드 개발을 가능하게 한다.
사양
AVR32DA/64DA/128DA 시리즈의 메모리 사양은 다음 표와 같습니다:
Devices | Flash Memory | SRAM | EEPROM | User Row |
AVR32DA28(S), AVR32DA32(S) | 32 KB | 4 KB | 512 B | 32 B |
AVR64DA28(S), AVR64DA32(S), AVR64DA48(S), AVR64DA64(S) | 64 KB | 8 KB | 512 B | 32 B |
AVR128DA28(S), AVR128DA32(S), AVR128DA48(S), AVR128DA64(S) | 128 KB | 16 KB | 512 B | 32 B |
- 지원 메모리:
- 플래시: 32KB (AVR32DA), 64KB (AVR64DA), 128KB (AVR128DA), 페이지 단위(512바이트) 지우기, 바이트/워드 단위 쓰기.
- SRAM: 4KB (AVR32DA), 8KB (AVR64DA), 16KB (AVR128DA).
- EEPROM: 512바이트, 바이트 단위(1/2/4/8/16/32) 지우기/쓰기.
- 시그니처 행: 디바이스 ID, 시리얼 번호, 보정 데이터 (읽기 전용).
- 사용자 행: 32바이트, 칩 지우기 후 유지, 소프트웨어 읽기/쓰기 및 UPDI 쓰기 가능.
- 퓨즈: 디바이스 설정 값, 칩 지우기 후 유지, UPDI로만 쓰기 가능.
- 플래시 섹션:
- 부트(BOOT): 부트로더 코드 저장, 전체 플래시 쓰기 가능.
- 애플리케이션 코드(APPCODE): 애플리케이션 코드 저장, APPDATA에만 쓰기 가능.
- 애플리케이션 데이터(APPDATA): 데이터 저장, 쓰기 불가.
- 기능:
- 셀프 프로그래밍 및 부트로더 지원.
- 쓰기 보호: BOOTRP(부트 읽기 보호), APPCODEWP(APPCODE 쓰기 보호), APPDATAWP(APPDATA 쓰기 보호).
- 인터럽트: EEPROM 준비(EEREADY), 표준 ISR로 처리.
- 페이지 지우기: 플래시 1~32페이지,EEPROM 1~32바이트.
- 칩 지우기: 플래시와 EEPROM 전체 지우기(UPDI 전용, EESAVE 퓨즈로 EEPROM 제외 가능).
- 클럭 의존성: Peripheral Clock (CLK_PER, 최대 24MHz).
- 리셋 상태: NVMCTRL 비활성화, 메모리 접근 불가.
- 전력 최적화: 슬립 모드에서 작업 완료 시 자동 비활성화.
- CCP 보호: CTRLA(SPM 키), CTRLB(IOREG 키) 레지스터.
레지스터 설정 상세
NVMCTRL은 NVMCTRL 레지스터를 통해 제어됩니다. 데이터시트의 Register Summary에 따라 주요 레지스터와 비트 필드는 다음과 같습니다.
- NVMCTRL.CTRLA (0x00):
- 비트 [6:0]: CMD[6:0] (명령: NOCMD, NOOP, FLWR, FLPER, FLMPER2~32, EEWR, EEERWR, EEBER, EEMBER2~32, CHER, EECHER).
- 리셋: 0x00, R/W, CCP 보호 (SPM 키).
- NVMCTRL.CTRLB (0x01):
- 비트 [7]: FLMAPLOCK (플래시 매핑 잠금).
- 비트 [5:4]: FLMAP[1:0] (데이터 공간에 매핑할 플래시 섹션: 0~32KB, 32~64KB 등).
- 비트 [2]: APPDATAWP (APPDATA 쓰기 보호).
- 비트 [1]: BOOTRP (부트 읽기 보호).
- 비트 [0]: APPCODEWP (APPCODE 쓰기 보호).
- 리셋: 0x30, R/W, CCP 보호 (IOREG 키).
- NVMCTRL.STATUS (0x02):
- 비트 [6:4]: ERROR[2:0] (오류: NONE, INVALIDCMD, WRITEPROTECT, CMDCOLLISION).
- 비트 [1]: EEBUSY (EEPROM 작업 중).
- 비트 [0]: FBUSY (플래시 작업 중).
- 리셋: 0x00, R/W.
- NVMCTRL.INTCTRL (0x03):
- 비트 [0]: EEREADY (EEPROM 준비 인터럽트 활성화).
- 리셋: 0x00, R/W.
- NVMCTRL.INTFLAGS (0x04):
- 비트 [0]: EEREADY (EEPROM 준비 플래그, 1 작성으로 클리어).
- 리셋: 0x00, R/W.
- NVMCTRL.DATA (0x06):
- 비트 [15:0]: DATA[15:0] (마지막 읽기 데이터, EEPROM은 하위 8비트만 사용).
- 리셋: 0x00, R/W.
- NVMCTRL.ADDR (0x08):
- 비트 [23:0]: ADDR[23:0] (마지막 접근 주소).
- 리셋: 0x00, R/W.
NVMCTRL 설정 절차
NVMCTRL 설정은 다음 순서로 진행합니다:
- 클럭 설정: CLKCTRL로 CLK_PER 활성화 (예: 24MHz OSCHF).
- 상태 확인: STATUS 레지스터의 EEBUSY/FBUSY로 이전 작업 완료 확인.
- CCP 잠금 해제: CPU.CCP에 SPM 또는 IOREG 키 작성.
- 명령 설정: CTRLA에 명령(FLWR, FLPER, EEWR 등) 작성.
- 메모리 접근: LPM/SPM(코드 공간) 또는 LD/ST(데이터 공간)으로 읽기/쓰기.
- 인터럽트 설정 (옵션): INTCTRL로 EEREADY 활성화, 표준 ISR로 처리.
- 명령 종료: CTRLA에 NOOP 또는 NOCMD 작성.
- 에러 처리: STATUS 레지스터로 오류 확인 및 클리어.
- 플래시 매핑 (옵션): CTRLB의 FLMAP으로 데이터 공간 매핑 설정.
- 테스트: 플래시/EEPROM 읽기/쓰기/지우기, 인터럽트 동작 확인.
NVMCTRL 설정 고려사항
- 메모리 크기 확인: 플래시 주소 범위는 디바이스에 따라 다름 (32KB, 64KB, 128KB). 주소 유효성 검증 필수.
- 보호 메커니즘: BOOTRP, APPCODEWP, APPDATAWP로 메모리 보호 설정. 리셋으로만 해제 가능.
- 클럭 의존성: 플래시/EEPROM 작업 시간은 CLK_PER에 의존. 24MHz 기준 최대 성능.
- 인터럽트: EEREADY 인터럽트로 EEPROM 작업 완료 확인. 표준 ISR로 안정성 및 가독성 향상.
- CCP 보호: _PROTECTED_WRITE로 CTRLA/CTRLB 작성.
- 에러 처리: STATUS의 ERROR 필드로 INVALIDCMD, WRITEPROTECT, CMDCOLLISION 확인.
- 플래시 매핑: 32KB 블록 단위로 데이터 공간 매핑(FLMAP). AVR32DA는 단일 32KB 블록만 지원.
- 호환성: <avr/io.h>로 모델별 NVMCTRL 정의 지원.
- 전력 최적화: 슬립 모드에서 작업 완료 시 NVMCTRL 자동 비활성화.
드라이버 구현 내용
드라이버는 AVR32DA/64DA/128DA 시리즈 호환으로 설계되었으며, NVMCTRL 기능을 추상화합니다. 주요 구현은 다음과 같습니다:
- 플래시 작업: 페이지 단위 지우기(FLPER, FLMPER2~32), 바이트/워드 단위 쓰기(FLWR). 디바이스별 플래시 크기(32KB, 64KB, 128KB) 확인.
- EEPROM 작업: 바이트 단위 지우기/쓰기(EEBER, EEWR, EEERWR), 다중 바이트 지우기(EEMBER2~32), 512바이트 제한 준수.
- 사용자 행 관리: 32바이트 읽기/쓰기, 칩 지우기 후 유지.
- 인터럽트 지원: EEREADY 인터럽트, 표준 ISR로 처리.
- 에러 처리: STATUS 레지스터로 오류 확인 및 클리어.
- nvmctrl_init: NVMCTRL 초기화 및 상태 확인.
- nvmctrl_flash_erase/write: 플래시 페이지 지우기/쓰기, 주소 유효성 검증.
- nvmctrl_eeprom_erase/write: EEPROM 바이트 지우기/쓰기, 512바이트 범위 제한.
- nvmctrl_user_row_read/write: 사용자 행 읽기/쓰기, 32바이트 제한 준수.
- nvmctrl_check_status: 상태 및 오류 확인.
- 클럭 설정: main.c에서 24MHz OSCHF, Auto-tune 활성화.
- 호환성: <avr/io.h>로 모델별 정의 지원, 디바이스별 플래시 크기 매크로 추가.
사용 방법
NVMCTRL 드라이버를 사용하는 상세한 절차는 다음과 같습니다. 각 단계는 초보자도 쉽게 따라 할 수 있도록 설명하며, 실제 프로젝트 설정 및 디버깅 방법을 포함합니다.
- 프로젝트 설정 및 헤더 파일 포함:
- Microchip Studio 또는 AVR-GCC 프로젝트를 생성합니다.
- nvmctrl_driver.h, <avr/io.h>, <avr/interrupt.h>, <stdio.h>를 포함합니다.
- UART 디버깅을 위해 uart_driver.h를 추가합니다(별도 구현 필요).
- 프로젝트 설정에서 타겟 디바이스를 명시합니다(예: AVR32DA28, AVR64DA64, AVR128DA64).
- 예: #include "nvmctrl_driver.h"
- 클럭 설정:
- 시스템 클럭을 24MHz OSCHF로 설정하여 NVMCTRL 작업의 안정성과 최대 성능을 보장합니다.
- main.c에서 clock_init_24mhz 함수를 호출합니다.
- Auto-tune 활성화로 클럭 안정성을 확보합니다.
- 예:
clock_init_24mhz(); // 24MHz 클럭 초기화
- NVMCTRL 인스턴스 생성 및 초기화:
- NVMCTRL_Instance 구조체를 선언하고, nvmctrl_init 함수로 초기화합니다.
- NVMCTRL_t 레지스터 포인터(&NVMCTRL)를 전달하여 하드웨어 레지스터와 연결합니다.
- 초기화 후 busy 플래그가 0으로 설정되고, CTRLA 레지스터가 NOCMD로 초기화됩니다.
- 예:
NVMCTRL_Instance nvmctrl_instance; nvmctrl_init(&nvmctrl_instance, &NVMCTRL);
- 플래시 작업:
- 지우기: nvmctrl_flash_erase를 호출하여 지정된 주소에서 페이지 단위로 플래시를 지웁니다.
- 주소는 디바이스별 플래시 크기(32KB, 64KB, 128KB) 내에서 유효해야 합니다.
- 페이지 수는 1, 2, 4, 8, 16, 32 중 선택합니다.
- 예: nvmctrl_flash_erase(&nvmctrl_instance, 0x8000, 1); // 512바이트 페이지 지우기
- 쓰기: nvmctrl_flash_write를 호출하여 데이터를 플래시 메모리에 기록합니다.
- 주소와 데이터 길이가 플래시 크기 내에 있는지 확인합니다.
- 예:
uint8_t data[] = "Test Flash Data"; nvmctrl_flash_write(&nvmctrl_instance, 0x8000, data, sizeof(data));
- 주의: 플래시 쓰기 전 반드시 해당 페이지를 지워야 합니다. 쓰기 보호(APPCODEWP, APPDATAWP) 설정 확인 필요.
- 지우기: nvmctrl_flash_erase를 호출하여 지정된 주소에서 페이지 단위로 플래시를 지웁니다.
- EEPROM 작업:
- 지우기: nvmctrl_eeprom_erase를 호출하여 EEPROM의 지정된 주소를 지웁니다.
- 주소는 0x1400에서 시작하며, 최대 512바이트 범위 내여야 합니다.
- 바이트 수는 1, 2, 4, 8, 16, 32 중 선택합니다.
- 예: nvmctrl_eeprom_erase(&nvmctrl_instance, 0x1400, 1); // 1바이트 지우기
- 쓰기: nvmctrl_eeprom_write를 호출하여 데이터를 EEPROM에 기록합니다.
- 주소와 데이터 길이가 512바이트 내에 있는지 확인합니다.
- 예:
uint8_t eeprom_data[] = "Test EEPROM"; nvmctrl_eeprom_write(&nvmctrl_instance, 0x1400, eeprom_data, sizeof(eeprom_data));
- 주의: EEPROM 작업은 인터럽트 기반으로 비동기 처리 가능. 작업 완료는 busy 플래그 또는 인터럽트로 확인.
- 지우기: nvmctrl_eeprom_erase를 호출하여 EEPROM의 지정된 주소를 지웁니다.
- 사용자 행 작업:
- 읽기: nvmctrl_user_row_read를 호출하여 사용자 행(32바이트)에서 데이터를 읽습니다.
- 주소는 0x1000에서 시작하며, 최대 32바이트 범위 내여야 합니다.
- 예:
uint8_t buffer[32]; uint16_t read_count = nvmctrl_user_row_read(&nvmctrl_instance, 0, buffer, 32);
- 쓰기: nvmctrl_user_row_write를 호출하여 사용자 행에 데이터를 기록합니다.
- 주소와 데이터 길이가 32바이트 내에 있는지 확인합니다.
- 예:
uint8_t user_row_data[] = "User Row Data"; nvmctrl_user_row_write(&nvmctrl_instance, 0, user_row_data, sizeof(user_row_data));
- 주의: 사용자 행은 칩 지우기 후에도 유지되며, UPDI 또는 소프트웨어로 접근 가능.
- 읽기: nvmctrl_user_row_read를 호출하여 사용자 행(32바이트)에서 데이터를 읽습니다.
- 인터럽트 활성화:
- nvmctrl_eeprom_interrupt_init를 호출하여 EEPROM 작업 완료 인터럽트(EEREADY)를 활성화합니다.
- 표준 ISR(ISR(NVMCTRL_EEREADY_vect))가 작업 완료 시 busy 플래그를 클리어합니다.
- 전역 인터럽트 활성화(sei())가 필요합니다.
- 예:
nvmctrl_eeprom_interrupt_init(&nvmctrl_instance); sei(); // 전역 인터럽트 활성화
- 상태 및 오류 확인:
- nvmctrl_check_status를 호출하여 NVMCTRL 상태와 오류를 확인합니다.
- 오류 플래그: INVALIDCMD(잘못된 명령), WRITEPROTECT(쓰기 보호), CMDCOLLISION(명령 충돌).
- 예:
uint8_t errors = nvmctrl_check_status(&nvmctrl_instance); if (errors) { printf("Error: %d\n", errors); }
- 디버깅 및 테스트:
- UART 설정: UART를 초기화하여 상태 메시지 및 오류를 출력합니다(예: uart_init, printf 사용).
- 테스트 시나리오:
- 플래시: 특정 주소(예: 0x8000)에 데이터 쓰기/읽기 테스트. 디바이스별 플래시 크기 확인.
- EEPROM: 0x1400에 데이터 쓰기/지우기, 인터럽트 기반 작업 완료 확인.
- 사용자 행: 32바이트 데이터 쓰기/읽기, 칩 지우기 후 유지 확인.
- 도구: Microchip Studio의 시뮬레이터 또는 실제 하드웨어(예: AVR128DA64 개발 보드) 사용.
- 로그 출력: UART를 통해 작업 성공/실패 및 오류 메시지 출력.
- 예:
printf("Flash Size: %lu KB, EEPROM: %d B, User Row: %d B\r\n", FLASH_SIZE / 1024, EEPROM_SIZE, USER_ROW_SIZE);
- 프로젝트 빌드 및 배포:
- Microchip Studio에서 프로젝트를 빌드하고, 디바이스별 설정(예: -mmcu=avr128da64)을 추가합니다.
- AVR-GCC 사용 시, 컴파일 명령어 예:
avr-gcc -mmcu=avr128da64 -O1 -o main.elf main.c nvmctrl_driver.c uart_driver.c avr-objcopy -O ihex main.elf main.hex
- 프로그래밍 도구(예: Atmel-ICE, UPDI)를 사용하여 HEX 파일을 타겟 디바이스에 업로드합니다.
- 예: avrdude -c atmelice_updi -p avr128da64 -U flash:w:main.hex
- 주의사항:
- 주소 범위: 플래시(32KB/64KB/128KB), EEPROM(512B), 사용자 행(32B) 크기 제한을 준수합니다.
- 쓰기 보호: BOOTRP, APPCODEWP, APPDATAWP 설정 시 동작 제한 확인.
- 인터럽트: 전역 인터럽트(sei())가 활성화되어야 인터럽트 핸들러 동작.
- 디바이스 호환성: 타겟 디바이스가 nvmctrl_driver.h의 매크로에 정의된 모델인지 확인.
- 디버깅: UART 출력 또는 Microchip Studio 디버거를 사용하여 작업 상태 점검.
코드 구현
- NVMCTRL 드라이버 코드: nvmctrl_driver.h, nvmctrl_driver.c, main.c
- 드라이버 예제 코드: nvmctrl_flash_example.c, nvmctrl_eeprom_example.c
nvmctrl_driver.h
/**
* @file nvmctrl_driver.h
* @brief AVR32DA/64DA/128DA NVMCTRL 드라이버 헤더 파일
* @details 모든 AVR32DA/64DA/128DA 시리즈 호환. 플래시, EEPROM, 사용자 행 관리,
* 표준 ISR 기반 인터럽트 지원, 상태 확인, 에러 처리.
* @author linuxgo
* @date 2025-09-05
*/
#ifndef NVMCTRL_DRIVER_H
#define NVMCTRL_DRIVER_H
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
/**
* @brief 디바이스별 플래시 크기 정의 (바이트 단위)
* @note AVR32DA: 32KB, AVR64DA: 64KB, AVR128DA: 128KB
*/
#if defined(__AVR_AVR32DA28__) || defined(__AVR_AVR32DA32__)
#define FLASH_SIZE 32768UL // 32KB
#elif defined(__AVR_AVR64DA28__) || defined(__AVR_AVR64DA32__) || defined(__AVR_AVR64DA48__) || defined(__AVR_AVR64DA64__)
#define FLASH_SIZE 65536UL // 64KB
#elif defined(__AVR_AVR128DA28__) || defined(__AVR_AVR128DA32__) || defined(__AVR_AVR128DA48__) || defined(__AVR_AVR128DA64__)
#define FLASH_SIZE 131072UL // 128KB
#else
#error "Unsupported AVR device"
#endif
#define EEPROM_SIZE 512 // EEPROM 크기: 512바이트
#define USER_ROW_SIZE 32 // 사용자 행 크기: 32바이트
/**
* @brief NVMCTRL 명령 정의
*/
#define NVMCTRL_CMD_NOCMD 0x00 // 명령 없음
#define NVMCTRL_CMD_NOOP 0x01 // 작업 없음
#define NVMCTRL_CMD_FLWR 0x02 // 플래시 쓰기
#define NVMCTRL_CMD_FLPER 0x08 // 플래시 페이지 지우기
#define NVMCTRL_CMD_FLMPER2 0x09 // 플래시 2페이지 지우기
#define NVMCTRL_CMD_FLMPER4 0x0A // 플래시 4페이지 지우기
#define NVMCTRL_CMD_FLMPER8 0x0B // 플래시 8페이지 지우기
#define NVMCTRL_CMD_FLMPER16 0x0C // 플래시 16페이지 지우기
#define NVMCTRL_CMD_FLMPER32 0x0D // 플래시 32페이지 지우기
#define NVMCTRL_CMD_EEWR 0x12 // EEPROM 쓰기
#define NVMCTRL_CMD_EEERWR 0x13 // EEPROM 지우기+쓰기
#define NVMCTRL_CMD_EEBER 0x18 // EEPROM 바이트 지우기
#define NVMCTRL_CMD_EEMBER2 0x19 // EEPROM 2바이트 지우기
#define NVMCTRL_CMD_EEMBER4 0x1A // EEPROM 4바이트 지우기
#define NVMCTRL_CMD_EEMBER8 0x1B // EEPROM 8바이트 지우기
#define NVMCTRL_CMD_EEMBER16 0x1C // EEPROM 16바이트 지우기
#define NVMCTRL_CMD_EEMBER32 0x1D // EEPROM 32바이트 지우기
/**
* @brief NVMCTRL 에러 플래그 정의
*/
#define NVMCTRL_ERROR_NONE 0x0 // 오류 없음
#define NVMCTRL_ERROR_INVALIDCMD 0x1 // 지원되지 않는 명령
#define NVMCTRL_ERROR_WRITEPROTECT 0x2 // 쓰기 보호 위반
#define NVMCTRL_ERROR_CMDCOLLISION 0x3 // 명령 충돌
/**
* @brief NVMCTRL 인스턴스 구조체
* @note NVMCTRL 레지스터와 작업 상태를 관리
*/
typedef struct {
NVMCTRL_t *nvmctrl; // NVMCTRL 레지스터 포인터
volatile uint8_t busy; // 작업 진행 상태 플래그 (0: 유휴, 1: 작업 중)
} NVMCTRL_Instance;
/**
* @brief NVMCTRL 초기화
* @param instance NVMCTRL 인스턴스 포인터
* @param nvmctrl NVMCTRL 레지스터 포인터 (예: &NVMCTRL)
*/
void nvmctrl_init(NVMCTRL_Instance *instance, NVMCTRL_t *nvmctrl);
/**
* @brief 플래시 페이지 지우기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 페이지 시작 주소 (플래시 범위 내)
* @param pages 지우기 페이지 수 (1, 2, 4, 8, 16, 32)
* @return 성공 여부 (0: 성공, 1: 오류)
*/
uint8_t nvmctrl_flash_erase(NVMCTRL_Instance *instance, uint32_t address, uint8_t pages);
/**
* @brief 플래시 쓰기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 쓰기 시작 주소 (플래시 범위 내)
* @param data 데이터 버퍼
* @param length 데이터 길이 (바이트 단위)
* @return 성공 여부 (0: 성공, 1: 오류)
*/
uint8_t nvmctrl_flash_write(NVMCTRL_Instance *instance, uint32_t address, uint8_t *data, uint16_t length);
/**
* @brief EEPROM 바이트 지우기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 지우기 시작 주소 (EEPROM 범위 내, 0x1400~0x15FF)
* @param bytes 지우기 바이트 수 (1, 2, 4, 8, 16, 32)
* @return 성공 여부 (0: 성공, 1: 오류)
*/
uint8_t nvmctrl_eeprom_erase(NVMCTRL_Instance *instance, uint16_t address, uint8_t bytes);
/**
* @brief EEPROM 쓰기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 쓰기 시작 주소 (EEPROM 범위 내, 0x1400~0x15FF)
* @param data 데이터 버퍼
* @param length 데이터 길이 (바이트 단위)
* @return 성공 여부 (0: 성공, 1: 오류)
*/
uint8_t nvmctrl_eeprom_write(NVMCTRL_Instance *instance, uint16_t address, uint8_t *data, uint16_t length);
/**
* @brief 사용자 행 읽기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 읽기 시작 주소 (사용자 행 범위 내, 0x1000~0x101F)
* @param data 데이터 버퍼
* @param length 읽기 길이 (바이트 단위)
* @return 읽은 바이트 수
*/
uint16_t nvmctrl_user_row_read(NVMCTRL_Instance *instance, uint16_t address, uint8_t *data, uint16_t length);
/**
* @brief 사용자 행 쓰기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 쓰기 시작 주소 (사용자 행 범위 내, 0x1000~0x101F)
* @param data 데이터 버퍼
* @param length 데이터 길이 (바이트 단위)
* @return 성공 여부 (0: 성공, 1: 오류)
*/
uint8_t nvmctrl_user_row_write(NVMCTRL_Instance *instance, uint16_t address, uint8_t *data, uint16_t length);
/**
* @brief EEPROM 인터럽트 초기화
* @param instance NVMCTRL 인스턴스 포인터
*/
void nvmctrl_eeprom_interrupt_init(NVMCTRL_Instance *instance);
/**
* @brief 상태 및 오류 확인
* @param instance NVMCTRL 인스턴스 포인터
* @return 오류 플래그 (NVMCTRL_ERROR_XXX)
*/
uint8_t nvmctrl_check_status(NVMCTRL_Instance *instance);
#endif // NVMCTRL_DRIVER_H
nvmctrl_driver.c
/**
* @file nvmctrl_driver.c
* @brief AVR32DA/64DA/128DA NVMCTRL 드라이버 구현
* @details 플래시, EEPROM, 사용자 행 관리, 표준 ISR 기반 인터럽트 지원, 상태 확인, 에러 처리.
* AVR32DA(32KB), AVR64DA(64KB), AVR128DA(128KB) 플래시 크기 지원.
* @author linuxgo
* @date 2025-09-05
*/
#include "nvmctrl_driver.h"
/**
* @brief NVMCTRL 인스턴스 포인터 (인터럽트 핸들러에서 사용)
* @note 단일 NVMCTRL 모듈만 존재하므로 전역 포인터로 관리
*/
static NVMCTRL_Instance *nvmctrl_instance = NULL;
/**
* @brief NVMCTRL 초기화
* @param instance NVMCTRL 인스턴스 포인터
* @param nvmctrl NVMCTRL 레지스터 포인터 (예: &NVMCTRL)
* @note busy 플래그를 0으로 설정하고, CTRLA를 NOCMD로 초기화
*/
void nvmctrl_init(NVMCTRL_Instance *instance, NVMCTRL_t *nvmctrl) {
instance->nvmctrl = nvmctrl; // 레지스터 포인터 설정
instance->busy = 0; // 작업 상태 초기화
nvmctrl_instance = instance; // 인터럽트 핸들러용 전역 포인터 설정
instance->nvmctrl->CTRLA = NVMCTRL_CMD_NOCMD; // 명령 레지스터 초기화
}
/**
* @brief 플래시 페이지 지우기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 페이지 시작 주소 (플래시 범위 내)
* @param pages 지우기 페이지 수 (1, 2, 4, 8, 16, 32)
* @return 성공 여부 (0: 성공, 1: 오류)
* @note 주소가 FLASH_SIZE(32KB/64KB/128KB)를 초과하거나 작업 중이면 실패
*/
uint8_t nvmctrl_flash_erase(NVMCTRL_Instance *instance, uint32_t address, uint8_t pages) {
// 작업 중이거나 플래시/EEPROM이 바쁜지 확인
if (instance->busy || (instance->nvmctrl->STATUS & (NVMCTRL_FBUSY_bm | NVMCTRL_EEBUSY_bm))) {
return 1; // 작업 중 오류
}
// 주소 범위 확인 (디바이스별 플래시 크기)
if (address >= FLASH_SIZE || (address + (pages * 512UL)) > FLASH_SIZE) {
return 1; // 플래시 크기 초과 오류
}
instance->busy = 1; // 작업 시작 플래그 설정
// 페이지 수에 따른 명령 선택
uint8_t cmd;
switch (pages) {
case 1: cmd = NVMCTRL_CMD_FLPER; break;
case 2: cmd = NVMCTRL_CMD_FLMPER2; break;
case 4: cmd = NVMCTRL_CMD_FLMPER4; break;
case 8: cmd = NVMCTRL_CMD_FLMPER8; break;
case 16: cmd = NVMCTRL_CMD_FLMPER16; break;
case 32: cmd = NVMCTRL_CMD_FLMPER32; break;
default: instance->busy = 0; return 1; // 잘못된 페이지 수 오류
}
// CCP 잠금 해제 및 명령 설정
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = cmd; // 지우기 명령 설정
// 페이지 지우기 (더미 쓰기로 트리거, __zero_reg__ 사용)
asm volatile("std Z+0, %0" :: "r"(__zero_reg__), "z"(address) : ); // 주소에 0 쓰기
while (instance->nvmctrl->STATUS & NVMCTRL_FBUSY_bm); // 플래시 작업 완료 대기
// 명령 종료
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = NVMCTRL_CMD_NOCMD; // 명령 레지스터 초기화
instance->busy = 0; // 작업 완료 플래그 클리어
// 오류 확인
return (instance->nvmctrl->STATUS & NVMCTRL_ERROR_gm) ? 1 : 0;
}
/**
* @brief 플래시 쓰기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 쓰기 시작 주소 (플래시 범위 내)
* @param data 데이터 버퍼
* @param length 데이터 길이 (바이트 단위)
* @return 성공 여부 (0: 성공, 1: 오류)
* @note 주소가 FLASH_SIZE를 초과하거나 작업 중이면 실패
*/
uint8_t nvmctrl_flash_write(NVMCTRL_Instance *instance, uint32_t address, uint8_t *data, uint16_t length) {
// 작업 중이거나 플래시/EEPROM이 바쁜지 확인
if (instance->busy || (instance->nvmctrl->STATUS & (NVMCTRL_FBUSY_bm | NVMCTRL_EEBUSY_bm))) {
return 1; // 작업 중 오류
}
// 주소 범위 확인
if (address >= FLASH_SIZE || (address + length) > FLASH_SIZE) {
return 1; // 플래시 크기 초과 오류
}
instance->busy = 1; // 작업 시작 플래그 설정
// 플래시 쓰기 활성화
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = NVMCTRL_CMD_FLWR; // 쓰기 명령 설정
// 데이터 쓰기 (Z 레지스터 사용)
for (uint16_t i = 0; i < length; i++) {
uint32_t addr = address + i; // 동적 주소 계산
asm volatile(
"movw Z, %A0\n\t" // Z 레지스터에 주소 로드
"std Z+0, %1\n\t" // Z 레지스터를 통해 데이터 쓰기
:: "r"(addr), "r"(data[i]) : "r30", "r31" // Z 레지스터 (r30:r31) 사용
);
while (instance->nvmctrl->STATUS & NVMCTRL_FBUSY_bm); // 플래시 작업 완료 대기
}
// 명령 종료
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = NVMCTRL_CMD_NOCMD; // 명령 레지스터 초기화
instance->busy = 0; // 작업 완료 플래그 클리어
// 오류 확인
return (instance->nvmctrl->STATUS & NVMCTRL_ERROR_gm) ? 1 : 0;
}
/**
* @brief EEPROM 바이트 지우기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 지우기 시작 주소 (EEPROM 범위 내, 0x1400~0x15FF)
* @param bytes 지우기 바이트 수 (1, 2, 4, 8, 16, 32)
* @return 성공 여부 (0: 성공, 1: 오류)
* @note 주소가 EEPROM_SIZE(512B)를 초과하거나 작업 중이면 실패
*/
uint8_t nvmctrl_eeprom_erase(NVMCTRL_Instance *instance, uint16_t address, uint8_t bytes) {
// 작업 중이거나 플래시/EEPROM이 바쁜지 확인
if (instance->busy || (instance->nvmctrl->STATUS & (NVMCTRL_FBUSY_bm | NVMCTRL_EEBUSY_bm))) {
return 1; // 작업 중 오류
}
// 주소 범위 확인 (EEPROM: 0x1400~0x15FF)
if (address < 0x1400 || address >= (0x1400 + EEPROM_SIZE) || (address + bytes) > (0x1400 + EEPROM_SIZE)) {
return 1; // EEPROM 크기 초과 오류
}
instance->busy = 1; // 작업 시작 플래그 설정
// 바이트 수에 따른 명령 선택
uint8_t cmd;
switch (bytes) {
case 1: cmd = NVMCTRL_CMD_EEBER; break;
case 2: cmd = NVMCTRL_CMD_EEMBER2; break;
case 4: cmd = NVMCTRL_CMD_EEMBER4; break;
case 8: cmd = NVMCTRL_CMD_EEMBER8; break;
case 16: cmd = NVMCTRL_CMD_EEMBER16; break;
case 32: cmd = NVMCTRL_CMD_EEMBER32; break;
default: instance->busy = 0; return 1; // 잘못된 바이트 수 오류
}
// CCP 잠금 해제 및 명령 설정
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = cmd; // 지우기 명령 설정
// EEPROM 지우기 (더미 쓰기로 트리거, __zero_reg__ 사용)
asm volatile("sts %0, %1" :: "i"(address), "r"(__zero_reg__) : ); // 주소에 0 쓰기
while (instance->nvmctrl->STATUS & NVMCTRL_EEBUSY_bm); // EEPROM 작업 완료 대기
// 명령 종료
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = NVMCTRL_CMD_NOCMD; // 명령 레지스터 초기화
instance->busy = 0; // 작업 완료 플래그 클리어
// 오류 확인
return (instance->nvmctrl->STATUS & NVMCTRL_ERROR_gm) ? 1 : 0;
}
/**
* @brief EEPROM 쓰기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 쓰기 시작 주소 (EEPROM 범위 내, 0x1400~0x15FF)
* @param data 데이터 버퍼
* @param length 데이터 길이 (바이트 단위)
* @return 성공 여부 (0: 성공, 1: 오류)
* @note 주소가 EEPROM_SIZE(512B)를 초과하거나 작업 중이면 실패
*/
uint8_t nvmctrl_eeprom_write(NVMCTRL_Instance *instance, uint16_t address, uint8_t *data, uint16_t length) {
// 작업 중이거나 플래시/EEPROM이 바쁜지 확인
if (instance->busy || (instance->nvmctrl->STATUS & (NVMCTRL_FBUSY_bm | NVMCTRL_EEBUSY_bm))) {
return 1; // 작업 중 오류
}
// 주소 범위 확인 (EEPROM: 0x1400~0x15FF)
if (address < 0x1400 || address >= (0x1400 + EEPROM_SIZE) || (address + length) > (0x1400 + EEPROM_SIZE)) {
return 1; // EEPROM 크기 초과 오류
}
instance->busy = 1; // 작업 시작 플래그 설정
// EEPROM 쓰기 활성화
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = NVMCTRL_CMD_EEWR; // 쓰기 명령 설정
// 데이터 쓰기 (Z 레지스터 사용)
for (uint16_t i = 0; i < length; i++) {
uint32_t addr = address + i; // 동적 주소 계산
asm volatile(
"movw Z, %A0\n\t" // Z 레지스터에 주소 로드
"std Z+0, %1\n\t" // Z 레지스터를 통해 데이터 쓰기
:: "r"(addr), "r"(data[i]) : "r30", "r31" // Z 레지스터 (r30:r31) 사용
);
while (instance->nvmctrl->STATUS & NVMCTRL_EEBUSY_bm); // EEPROM 작업 완료 대기
}
// 명령 종료
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = NVMCTRL_CMD_NOCMD; // 명령 레지스터 초기화
instance->busy = 0; // 작업 완료 플래그 클리어
// 오류 확인
return (instance->nvmctrl->STATUS & NVMCTRL_ERROR_gm) ? 1 : 0;
}
/**
* @brief 사용자 행 읽기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 읽기 시작 주소 (사용자 행 범위 내, 0x1000~0x101F)
* @param data 데이터 버퍼
* @param length 읽기 길이 (바이트 단위)
* @return 읽은 바이트 수
* @note 주소가 USER_ROW_SIZE(32B)를 초과하면 0 반환
*/
uint16_t nvmctrl_user_row_read(NVMCTRL_Instance *instance, uint16_t address, uint8_t *data, uint16_t length) {
// 주소 범위 확인 (사용자 행: 0x1000~0x101F)
if (address >= USER_ROW_SIZE || (address + length) > USER_ROW_SIZE) {
return 0; // 사용자 행 크기 초과 오류
}
uint16_t count = 0;
for (uint16_t i = 0; i < length; i++) {
if (address + i >= USER_ROW_SIZE) break; // 범위 초과 시 중단
asm volatile("lds %0, %1" : "=r"(data[i]) : "i"(0x1000 + address + i)); // 사용자 행 읽기
count++; // 읽은 바이트 수 증가
}
return count; // 실제 읽은 바이트 수 반환
}
/**
* @brief 사용자 행 쓰기
* @param instance NVMCTRL 인스턴스 포인터
* @param address 쓰기 시작 주소 (사용자 행 범위 내, 0x1000~0x101F)
* @param data 데이터 버퍼
* @param length 데이터 길이 (바이트 단위)
* @return 성공 여부 (0: 성공, 1: 오류)
* @note 주소가 USER_ROW_SIZE(32B)를 초과하거나 작업 중이면 실패
*/
uint8_t nvmctrl_user_row_write(NVMCTRL_Instance *instance, uint16_t address, uint8_t *data, uint16_t length) {
// 작업 중이거나 플래시/EEPROM이 바쁜지 확인
if (instance->busy || (instance->nvmctrl->STATUS & (NVMCTRL_FBUSY_bm | NVMCTRL_EEBUSY_bm))) {
return 1; // 작업 중 오류
}
// 주소 범위 확인 (사용자 행: 0x1000~0x101F)
if (address >= USER_ROW_SIZE || (address + length) > USER_ROW_SIZE) {
return 1; // 사용자 행 크기 초과 오류
}
instance->busy = 1; // 작업 시작 플래그 설정
// 사용자 행 쓰기 (플래시와 동일)
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = NVMCTRL_CMD_FLWR; // 쓰기 명령 설정
// 데이터 쓰기 (Z 레지스터 사용)
for (uint16_t i = 0; i < length; i++) {
uint32_t addr = 0x1000 + address + i; // 사용자 행 주소 계산
asm volatile(
"movw Z, %A0\n\t" // Z 레지스터에 주소 로드
"std Z+0, %1\n\t" // Z 레지스터를 통해 데이터 쓰기
:: "r"(addr), "r"(data[i]) : "r30", "r31" // Z 레지스터 (r30:r31) 사용
);
while (instance->nvmctrl->STATUS & NVMCTRL_FBUSY_bm); // 플래시 작업 완료 대기
}
// 명령 종료
_PROTECTED_WRITE(CPU_CCP, CCP_SPMKEY); // SPM 키로 보호 레지스터 잠금 해제
instance->nvmctrl->CTRLA = NVMCTRL_CMD_NOCMD; // 명령 레지스터 초기화
instance->busy = 0; // 작업 완료 플래그 클리어
// 오류 확인
return (instance->nvmctrl->STATUS & NVMCTRL_ERROR_gm) ? 1 : 0;
}
/**
* @brief EEPROM 인터럽트 초기화
* @param instance NVMCTRL 인스턴스 포인터
* @note EEREADY 인터럽트를 활성화하여 EEPROM 작업 완료 시 ISR 호출
*/
void nvmctrl_eeprom_interrupt_init(NVMCTRL_Instance *instance) {
instance->nvmctrl->INTCTRL |= NVMCTRL_EEREADY_bm; // EEREADY 인터럽트 활성화
}
/**
* @brief 상태 및 오류 확인
* @param instance NVMCTRL 인스턴스 포인터
* @return 오류 플래그 (NVMCTRL_ERROR_XXX)
* @note 오류 발생 시 STATUS 레지스터의 ERROR 필드를 클리어
*/
uint8_t nvmctrl_check_status(NVMCTRL_Instance *instance) {
// STATUS 레지스터에서 오류 플래그 추출
uint8_t errors = (instance->nvmctrl->STATUS & NVMCTRL_ERROR_gm) >> NVMCTRL_ERROR_gp;
if (errors) {
instance->nvmctrl->STATUS |= NVMCTRL_ERROR_gm; // 오류 필드 전체 클리어
}
return errors; // 오류 코드 반환
}
/**
* @brief EEPROM 준비 인터럽트 핸들러 (표준 ISR)
* @note EEPROM 작업 완료 시 busy 플래그 클리어 및 인터럽트 플래그 클리어
*/
ISR(NVMCTRL_EEREADY_vect) {
if (nvmctrl_instance != NULL) {
nvmctrl_instance->busy = 0; // 작업 완료, busy 플래그 클리어
nvmctrl_instance->nvmctrl->INTFLAGS |= NVMCTRL_EEREADY_bm; // EEREADY 인터럽트 플래그 클리어
}
}
main.c
/**
* @file main.c
* @brief AVR32DA/64DA/128DA NVMCTRL 드라이버 테스트 프로그램
* @details 24MHz 클럭, 플래시/EEPROM/사용자 행 읽기/쓰기/지우기 테스트,
* 표준 ISR 기반 EEPROM 작업.
* @author linuxgo
* @date 2025-09-05
*/
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "nvmctrl_driver.h"
#include "uart_driver.h"
/**
* @brief 24MHz OSCHF 클럭 초기화
* @note Auto-tune 활성화로 클럭 안정성 보장
*/
void clock_init_24mhz(void) {
_PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp)); // OSCHF 활성화
_PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm); // 24MHz, Auto-tune 설정
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 분주 비활성화
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00); // 메인 클럭 소스 선택
while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0); // 클럭 안정화 대기
}
/**
* @brief 메인 함수
* @note 클럭, UART, NVMCTRL 초기화 후 플래시/EEPROM/사용자 행 테스트 수행
*/
int main(void) {
clock_init_24mhz(); // 24MHz 클럭 설정
sei(); // 전역 인터럽트 활성화
// NVMCTRL 초기화
NVMCTRL_Instance nvmctrl_instance;
nvmctrl_init(&nvmctrl_instance, &NVMCTRL); // NVMCTRL 인스턴스 초기화
// UART 초기화 (디버깅용)
UART_Instance usart0_instance;
uart_init(&usart0_instance, &USART0, UART_BAUD_9600, UART_DATA_8BIT, UART_STOP_1BIT, UART_PARITY_NONE, UART_MODE_ASYNC, UART_PORTMUX_DEFAULT);
uart_enable_tx(&usart0_instance); // 송신 활성화
uart_enable_rx(&usart0_instance); // 수신 활성화
uart_setup_stdio(&usart0_instance); // printf 설정
// 디바이스 메모리 정보 출력
printf("NVMCTRL Driver Test (Standard ISR): Flash, EEPROM, User Row\r\n");
printf("Flash Size: %lu KB, EEPROM: %d B, User Row: %d B\r\n", FLASH_SIZE / 1024, EEPROM_SIZE, USER_ROW_SIZE);
_delay_ms(1000); // 초기화 후 대기
// 플래시 테스트
uint8_t flash_data[] = "Test Flash Data"; // 테스트 데이터
if (nvmctrl_flash_erase(&nvmctrl_instance, 0x8000, 1) == 0) { // 0x8000에서 1페이지 지우기
printf("Flash erased successfully\r\n");
} else {
printf("Flash erase failed\r\n");
}
if (nvmctrl_flash_write(&nvmctrl_instance, 0x8000, flash_data, sizeof(flash_data)) == 0) { // 0x8000에 데이터 쓰기
printf("Flash written successfully\r\n");
} else {
printf("Flash write failed\r\n");
}
// EEPROM 테스트 (인터럽트 기반)
nvmctrl_eeprom_interrupt_init(&nvmctrl_instance); // EEPROM 인터럽트 활성화
uint8_t eeprom_data[] = "Test EEPROM"; // 테스트 데이터
if (nvmctrl_eeprom_erase(&nvmctrl_instance, 0x1400, 1) == 0) { // 0x1400에서 1바이트 지우기
printf("EEPROM erased successfully\r\n");
} else {
printf("EEPROM erase failed\r\n");
}
if (nvmctrl_eeprom_write(&nvmctrl_instance, 0x1400, eeprom_data, sizeof(eeprom_data)) == 0) { // 0x1400에 데이터 쓰기
printf("EEPROM written successfully\r\n");
} else {
printf("EEPROM write failed\r\n");
}
// 사용자 행 테스트
uint8_t user_row_data[] = "User Row Data"; // 테스트 데이터
uint8_t user_row_buffer[USER_ROW_SIZE]; // 읽기용 버퍼
if (nvmctrl_user_row_write(&nvmctrl_instance, 0, user_row_data, sizeof(user_row_data)) == 0) { // 사용자 행 쓰기
printf("User Row written successfully\r\n");
} else {
printf("User Row write failed\r\n");
}
uint16_t read_count = nvmctrl_user_row_read(&nvmctrl_instance, 0, user_row_buffer, sizeof(user_row_data)); // 사용자 행 읽기
printf("User Row read %d bytes: %s\r\n", read_count, user_row_buffer);
// 주기적 상태 점검 루프
while (1) {
uint8_t errors = nvmctrl_check_status(&nvmctrl_instance); // 오류 확인
if (errors) {
printf("NVMCTRL Errors: %s\r\n",
errors == NVMCTRL_ERROR_INVALIDCMD ? "Invalid Command" :
errors == NVMCTRL_ERROR_WRITEPROTECT ? "Write Protect" :
errors == NVMCTRL_ERROR_CMDCOLLISION ? "Command Collision" : "Unknown");
}
_delay_ms(1000); // 1초 대기
}
return 0;
}
nvmctrl_flash_example.c
/**
* @file nvmctrl_flash_example.c
* @brief AVR32DA/64DA/128DA NVMCTRL 드라이버 플래시 작업 예제
* @details 플래시 페이지 지우기 및 쓰기 테스트.
* @author linuxgo
* @date 2025-09-05
*/
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "nvmctrl_driver.h"
#include "uart_driver.h"
/**
* @brief 24MHz OSCHF 클럭 초기화
* @note Auto-tune 활성화로 클럭 안정성 보장
*/
void clock_init_24mhz(void) {
_PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp)); // OSCHF 활성화
_PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm); // 24MHz, Auto-tune 설정
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 분주 비활성화
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00); // 메인 클럭 소스 선택
while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0); // 클럭 안정화 대기
}
/**
* @brief 메인 함수
* @note 플래시 지우기/쓰기 테스트 수행
*/
int main(void) {
clock_init_24mhz(); // 24MHz 클럭 설정
// UART 초기화 (디버깅용)
UART_Instance usart0_instance;
uart_init(&usart0_instance, &USART0, UART_BAUD_9600, UART_DATA_8BIT, UART_STOP_1BIT, UART_PARITY_NONE, UART_MODE_ASYNC, UART_PORTMUX_DEFAULT);
uart_enable_tx(&usart0_instance); // 송신 활성화
uart_enable_rx(&usart0_instance); // 수신 활성화
uart_setup_stdio(&usart0_instance); // printf 설정
// NVMCTRL 초기화
NVMCTRL_Instance nvmctrl_instance;
nvmctrl_init(&nvmctrl_instance, &NVMCTRL); // NVMCTRL 인스턴스 초기화
// 플래시 테스트 시작
printf("NVMCTRL Flash Test (Flash Size: %lu KB)\r\n", FLASH_SIZE / 1024);
_delay_ms(1000); // 초기화 후 대기
uint8_t flash_data[] = "Flash Example Data"; // 테스트 데이터
if (nvmctrl_flash_erase(&nvmctrl_instance, 0x8000, 1) == 0) { // 0x8000에서 1페이지 지우기
printf("Flash erased successfully\r\n");
} else {
printf("Flash erase failed\r\n");
}
if (nvmctrl_flash_write(&nvmctrl_instance, 0x8000, flash_data, sizeof(flash_data)) == 0) { // 0x8000에 데이터 쓰기
printf("Flash written successfully: %s\r\n", flash_data);
} else {
printf("Flash write failed\r\n");
}
// 주기적 상태 점검 루프
while (1) {
uint8_t errors = nvmctrl_check_status(&nvmctrl_instance); // 오류 확인
if (errors) {
printf("Errors: %s\r\n",
errors == NVMCTRL_ERROR_INVALIDCMD ? "Invalid Command" :
errors == NVMCTRL_ERROR_WRITEPROTECT ? "Write Protect" :
errors == NVMCTRL_ERROR_CMDCOLLISION ? "Command Collision" : "Unknown");
}
_delay_ms(1000); // 1초 대기
}
return 0;
}
nvmctrl_eeprom_example.c
/**
* @file nvmctrl_eeprom_example.c
* @brief AVR32DA/64DA/128DA NVMCTRL 드라이버 EEPROM 작업 예제
* @details 표준 ISR 기반 EEPROM 지우기 및 쓰기 테스트.
* @author linuxgo
* @date 2025-09-05
*/
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "nvmctrl_driver.h"
#include "uart_driver.h"
/**
* @brief 24MHz OSCHF 클럭 초기화
* @note Auto-tune 활성화로 클럭 안정성 보장
*/
void clock_init_24mhz(void) {
_PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp)); // OSCHF 활성화
_PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm); // 24MHz, Auto-tune 설정
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 분주 비활성화
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00); // 메인 클럭 소스 선택
while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0); // 클럭 안정화 대기
}
/**
* @brief 메인 함수
* @note EEPROM 지우기/쓰기 테스트 수행, 인터럽트 기반 작업
*/
int main(void) {
clock_init_24mhz(); // 24MHz 클럭 설정
sei(); // 전역 인터럽트 활성화
// UART 초기화 (디버깅용)
UART_Instance usart0_instance;
uart_init(&usart0_instance, &USART0, UART_BAUD_9600, UART_DATA_8BIT, UART_STOP_1BIT, UART_PARITY_NONE, UART_MODE_ASYNC, UART_PORTMUX_DEFAULT);
uart_enable_tx(&usart0_instance); // 송신 활성화
uart_enable_rx(&usart0_instance); // 수신 활성화
uart_setup_stdio(&usart0_instance); // printf 설정
// NVMCTRL 초기화
NVMCTRL_Instance nvmctrl_instance;
nvmctrl_init(&nvmctrl_instance, &NVMCTRL); // NVMCTRL 인스턴스 초기화
nvmctrl_eeprom_interrupt_init(&nvmctrl_instance); // EEPROM 인터럽트 활성화
// EEPROM 테스트 시작
printf("NVMCTRL EEPROM Test (EEPROM Size: %d B, Standard ISR)\r\n", EEPROM_SIZE);
_delay_ms(1000); // 초기화 후 대기
uint8_t eeprom_data[] = "EEPROM Test Data"; // 테스트 데이터
if (nvmctrl_eeprom_erase(&nvmctrl_instance, 0x1400, 1) == 0) { // 0x1400에서 1바이트 지우기
printf("EEPROM erased successfully\r\n");
} else {
printf("EEPROM erase failed\r\n");
}
if (nvmctrl_eeprom_write(&nvmctrl_instance, 0x1400, eeprom_data, sizeof(eeprom_data)) == 0) { // 0x1400에 데이터 쓰기
printf("EEPROM written successfully: %s\r\n", eeprom_data);
} else {
printf("EEPROM write failed\r\n");
}
// 주기적 상태 점검 루프
while (1) {
uint8_t errors = nvmctrl_check_status(&nvmctrl_instance); // 오류 확인
if (errors) {
printf("Errors: %s\r\n",
errors == NVMCTRL_ERROR_INVALIDCMD ? "Invalid Command" :
errors == NVMCTRL_ERROR_WRITEPROTECT ? "Write Protect" :
errors == NVMCTRL_ERROR_CMDCOLLISION ? "Command Collision" : "Unknown");
}
_delay_ms(1000); // 1초 대기
}
return 0;
}
추가 팁
- 쓰기 보호: BOOTRP, APPCODEWP, APPDATAWP 설정 시 리셋으로만 해제 가능. 애플리케이션 설계 시 주의.
- 인터럽트 최적화: 표준 ISR은 추가 오버헤드가 있지만, 코드 가독성과 디버깅 용이성 제공. 성능 최적화가 필요하면 ISR_NAKED로 전환 가능.
- 에러 처리: STATUS 레지스터의 ERROR 필드를 주기적으로 확인하여 안정성 확보.
- 클럭 안정성: 24MHz OSCHF 사용 시 Auto-tune 활성화로 정확한 타이밍 보장.
- 메모리 매핑: FLMAP으로 32KB 블록 매핑 시, AVR32DA는 단일 블록만 지원하므로 주의.
- 전력 절감: 슬립 모드에서 NVMCTRL 자동 비활성화 활용.
- 테스트 환경: Microchip Studio 시뮬레이터 또는 실제 하드웨어(예: AVR128DA64 개발 보드)에서 디버깅. UART로 상태 출력 추천.
- 디바이스별 테스트: AVR32DA(32KB), AVR64DA(64KB), AVR128DA(128KB)에서 플래시 주소 범위 테스트 권장.
결론
본 문서를 통해 AVR32DA/64DA/128DA 시리즈 전 제품에서 공통적으로 활용 가능한 NVMCTRL 범용 드라이버를 설계 및 구현하였다. 제안된 드라이버는 플래시·EEPROM·사용자 행을 포함한 다양한 메모리 자원의 관리와 인터럽트 기반 처리 기능을 제공함으로써, 개발자의 코드 재사용성과 시스템 안정성을 크게 향상시킨다. 또한 주소 범위 검증, 쓰기 보호 설정, 오류 처리 메커니즘을 내장하여 실사용 환경에서의 신뢰성을 확보하였다. 본 드라이버는 부트로더, 데이터 로깅, 설정 값 저장 등 다양한 임베디드 애플리케이션에 즉시 적용 가능하며, 향후 AVR DA 계열 마이크로컨트롤러 기반 시스템의 개발 효율성을 높이는 기반 도구로 활용될 수 있다.
'MCU > AVR' 카테고리의 다른 글
Microchip studio 및 mplab 에서 발생하는 'vsnprintf_P' 경고, 이유와 해결책 (0) | 2025.09.13 |
---|---|
AVR128DA64/48/32/28 Sleep Controller 드라이버 설계 및 구현 (0) | 2025.09.05 |
AVR128DA64/48/32/28 클럭 드라이버 설계 및 구현 (0) | 2025.09.05 |
AVR128DA64/48/32/28 RTC 드라이버 설계 및 구현 (0) | 2025.09.05 |
AVR128DA64/48/32/28 TCD 드라이버 설계 및 구현 (0) | 2025.09.05 |
AVR128DA64/48/32/28 TCB 드라이버 설계 및 구현 (0) | 2025.09.04 |
AVR128DA64/48/32/28 AC 드라이버 설계 및 구현 (0) | 2025.09.04 |
AVR128DA64/48/32/28 DAC 드라이버 설계 및 구현 (0) | 2025.09.04 |