기술 가이드

24LC256 EEPROM 페이지 쓰기: 64바이트 경계·주소 범위·ACK 폴링

24LC256의 페이지 경계와 TX 버퍼 한도를 함께 계산하고, 196,864개 쓰기 계획을 Python으로 검증합니다. ACK 폴링과 WP·읽기 비교 절차도 정리합니다.

목차

센서 교정값이나 장치 설정을 EEPROM에 저장할 때, 시작 주소에 관계없이 64바이트씩 나누면 앞쪽 설정값을 덮어쓸 수 있습니다. 핵심은 이번 물리 페이지에 남은 공간까지 계산하는 것입니다. 이 글은 24LC256의 쓰기 프레임을 만드는 순수 Python 예제로 경계 조건을 확인합니다. 실제 I²C 통신과 전원 중단 시험은 별도로 해야 합니다.

64바이트 이하인데도 주소가 되돌아가는 이유

Microchip 공식 데이터시트 DS20001203Y의 6.2절은 페이지 경계를 넘는 쓰기 데이터가 다음 페이지로 넘어가지 않고 현재 페이지 앞쪽으로 돌아간다고 설명합니다. 24LC256은 32,768바이트 용량과 64바이트 페이지를 사용합니다. 유효 주소는 0x0000~0x7FFF입니다.

0x003E에서 네 바이트를 한 번에 쓰면 기대 주소는 003E, 003F, 0040, 0041이지만 페이지 안에서는 003E, 003F, 0000, 0001로 돌아갑니다. 전송 데이터가 64바이트보다 짧아도 같은 문제가 생깁니다. 따라서 첫 쓰기는 두 바이트로 끝내고, 다음 쓰기를 0x0040에서 시작해야 합니다.

분할 크기는 다음 세 값 중 최솟값입니다.

남은 데이터 길이
64 - (현재 메모리 주소 % 64)
컨트롤러 TX 버퍼 길이 - 메모리 주소 2바이트

TX 버퍼 길이는 이 예제에서 메모리 주소 상·하위 바이트와 데이터의 합계입니다. I²C 장치 주소·START·STOP은 포함하지 않습니다. 사용하는 HAL이 메모리 주소를 별도로 처리한다면 실제 버퍼 계약에 맞춰 바꾸세요. 64바이트 페이지를 한 번에 보내려면 이 정의로 최소 66바이트가 필요합니다.

70바이트를 0x003E부터 쓰는 계획

기본 TX 한도 66바이트에서 결과는 다음과 같습니다. 각 행은 별도의 쓰기 트랜잭션입니다.

시작 주소 메모리 주소 바이트 데이터 수 TX 합계
0x003E 00 3E 2 4
0x0040 00 40 64 66
0x0080 00 80 4 6

장치의 7비트 I²C 주소와 EEPROM 내부의 16비트 메모리 주소는 서로 다른 인수입니다. 아래 함수가 반환하는 frame에는 메모리 주소만 들어 있습니다. I²C 장치 주소는 실제 송신 API에 별도로 전달해야 합니다. 메모리 주소를 상위 바이트부터 보내는 절차는 Microchip I²C 개발 문서의 EEPROM 쓰기 설명에서도 확인할 수 있습니다.

실행 가능한 전체 예제

아래 코드를 eeprom_page_plan.py로 저장하고 Python 3에서 실행하세요. 외부 패키지와 보드가 필요하지 않습니다. assert 검사를 실행해야 하므로 -O 옵션을 사용하지 마세요.

python -B eeprom_page_plan.py
PAGE=64
CAPACITY=32768

def plan_write(address, data, tx_limit=66):
    if not isinstance(address, int) or not 0 <= address < CAPACITY:
        raise ValueError('invalid start address')
    if not isinstance(tx_limit, int) or tx_limit < 3:
        raise ValueError('TX buffer must fit address + data')
    payload=bytes(data)
    if len(payload) > CAPACITY-address:
        raise ValueError('write exceeds EEPROM capacity')
    plan=[]
    offset=0
    while offset < len(payload):
        current=address+offset
        count=min(len(payload)-offset, PAGE-current%PAGE, tx_limit-2)
        frame=current.to_bytes(2, 'big')+payload[offset:offset+count]
        plan.append((current,frame))
        offset+=count
    return plan


def check_plan(address,data,tx_limit):
    items=plan_write(address,data,tx_limit)
    restored=bytearray()
    expected=address
    for current,frame in items:
        block=frame[2:]
        assert current==expected
        assert int.from_bytes(frame[:2],'big')==current
        assert block and len(frame)<=tx_limit
        assert current//PAGE==(current+len(block)-1)//PAGE
        restored.extend(block)
        expected+=len(block)
    assert bytes(restored)==data
    assert expected==address+len(data)


def run_tests():
    count=0
    for offset in range(64):
        for length in range(257):
            data=bytes(i%256 for i in range(length))
            for tx_limit in (3,16,32,66):
                check_plan(offset,data,tx_limit)
                count+=1
    for address in range(CAPACITY):
        for length in (0,1,min(65,CAPACITY-address),CAPACITY-address if address>=CAPACITY-65 else 2):
            if length<=CAPACITY-address:
                check_plan(address,bytes(i%256 for i in range(length)),66)
                count+=1
    invalid=[(-1,b'x',66),(CAPACITY,b'',66),(CAPACITY-1,b'xx',66),(0,b'x',2),(0,b'x',0)]
    for args in invalid:
        try: plan_write(*args)
        except ValueError: pass
        else: raise AssertionError('invalid input accepted')
    for current,frame in plan_write(0x003E,bytes(range(70))):
        print(f'0x{current:04X}: data={len(frame)-2}, tx={len(frame)}')
    bad=[(0x003E&~63)|((0x003E+i)&63) for i in range(4)]
    assert bad==[0x003E,0x003F,0x0000,0x0001]
    print(f'PASS: {count} valid plans, {len(invalid)} rejected inputs, page-wrap counterexample')

if __name__=='__main__':
    run_tests()

검증 실행 결과:

0x003E: data=2, tx=4
0x0040: data=64, tx=66
0x0080: data=4, tx=6
PASS: 196864 valid plans, 5 rejected inputs, page-wrap counterexample

페이지 안 시작 오프셋 64개, 길이 0~256, TX 한도 3·16·32·66을 조합하고, 모든 절대 시작 주소에서도 짧은 쓰기와 끝 주소 조건을 확인했습니다. 검사는 데이터 재조립 결과, 주소 연속성, 주소 바이트 순서, 페이지 비횡단, TX 한도를 함께 비교합니다. 잘못된 시작 주소·용량 초과·너무 작은 TX 한도는 예외로 거릅니다. 빈 데이터는 유효한 시작 주소에서 빈 계획을 반환하며 버스 송신이 필요하지 않습니다. 이는 호스트에서 실행한 분할 알고리즘 검증입니다.

프레임 송신 다음에는 쓰기 완료를 기다리기

실제 펌웨어에서는 frame 하나를 송신하고 STOP을 만든 뒤, 쓰기 방향 장치 주소에 대한 ACK 폴링을 수행하고 다음 프레임으로 진행합니다. 데이터시트 7.0절과 AC 특성 표의 쓰기 주기를 확인하세요. 내부 쓰기 중에는 NACK가 나올 수 있으며 명시된 최대 쓰기 주기는 5ms입니다. 호스트 타임아웃에는 버스 전송·스케줄링 여유도 반영해야 합니다.

ACK 폴링은 주소 단계 NACK만 준비 전 상태로 해석하는 별도 동작으로 구현하세요. 중재 손실·버스 오류·송신 실패를 모두 준비 전 상태로 바꾸거나 시간 제한 없이 반복하면 고장을 숨깁니다. 드라이버의 준비 확인 함수가 실제로 주소 프로브를 보내는지도 확인해야 합니다. 데이터 한 바이트를 다시 보내는 동작으로 대신하면 메모리 내용에 영향을 줄 수 있습니다.

ACK만으로 데이터 저장 성공을 확정하지 마세요. 데이터시트 6.3절의 WP 핀이 쓰기 금지 상태이면 ACK가 있어도 메모리가 갱신되지 않습니다. 쓰기 완료 후 동일 주소를 읽어 원본과 비교하는 단계를 두세요.

실제 보드에서 확인할 항목

첫 시험은 보존해야 할 설정과 분리한 시험 영역에서 하세요. 0x003E 경계를 가로지르는 데이터와 페이지 양쪽의 구분값을 읽어, 대상 범위 밖 바이트가 바뀌지 않았는지 비교합니다. 로직 애널라이저에서는 트랜잭션별 주소·데이터 길이·STOP과 다음 송신 전 준비 확인을 관찰합니다. WP를 활성화한 시험에서는 읽기 비교가 불일치를 잡는지도 확인합니다.

설정 전체를 여러 페이지에 나눠 저장하는 작업은 전원 중단에 대해 원자적이지 않습니다. 제품 설계에서는 이중 슬롯, 버전·길이·검사값과 마지막 유효 표시 같은 상위 저장 형식을 따로 마련하고, 페이지별 쓰기 수명과 갱신 빈도를 검토하세요. 이 예제는 그 저장 형식이나 실제 컨트롤러의 버스 동작까지 검증하지 않습니다.

공식 참고 자료

다른 EEPROM에는 페이지 크기와 주소 형식이 그대로 적용되지 않을 수 있습니다. 실제 부품 데이터시트와 송신 API를 기준으로 맞추세요.

관련 서비스

관련 글

댓글

첫 댓글을 남겨 보세요.

관리자 확인 후 게시됩니다.

처리 중입니다...

잠시만 기다려주세요.