기술 가이드

SPI NOR 플래시 BUSY 폴링: 상태 레지스터·시간 초과·틱 오버플로 검증

SPI NOR 페이지 프로그램 뒤 05h 상태 읽기로 BUSY를 기다리고, 시간 초과·버스 오류·32비트 틱 오버플로를 C 예제로 검증합니다.

목차

SPI NOR 플래시에 페이지 프로그램 명령을 보낸 직후 다음 쓰기 명령을 보내면 내부 프로그램 동작이 아직 끝나지 않았을 수 있습니다. Microchip의 Read Status Register 1 설명은 05h 명령으로 상태 레지스터 1을 읽고, 쓰기 중에는 BUSY 비트를 폴링하라고 안내합니다. 이 글은 MCU 드라이버에 이 절차를 넣을 때의 종료 조건과 실패 처리를 다룹니다.

BUSY와 WEL은 다른 신호

상태 레지스터 정의에 따르면 비트 0의 BUSY가 1이면 내부 프로그램·소거가 진행 중이고, 0이면 바쁘지 않습니다. 비트 1의 WEL은 쓰기 허용 래치입니다. 따라서 WEL=1만 보고 프로그램 완료를 판단하면 안 됩니다. 쓰기 전에는 06h Write Enable 뒤 WEL을 확인하고, 페이지 프로그램 02h 뒤에는 BUSY가 0으로 돌아오는지 확인합니다. Page Program 공식 설명도 상태 비트 폴링을 완료 확인 방법으로 제시합니다.

단계 MCU가 확인할 것 실패 시 처리
프로그램 전 장치 식별, 보호 상태, WEL 쓰기 중단
02h 명령 전송 CS 경계, 주소·페이지 범위 버스 오류 기록
프로그램 대기 05h로 읽은 BUSY 비트 0 제한 시간 초과 또는 읽기 오류로 중단
완료 후 읽기 검증과 데이터 비교 저장 실패로 처리

32비트 시계 오버플로를 견디는 대기 루프

다음 C11 예제는 SPI 하드웨어 대신 상태 바이트를 돌려주는 가짜 드라이버를 사용합니다. 실제 보드의 read_sr1 함수는 CS를 내리고 05h를 보낸 뒤 상태 바이트 하나를 클록하고 CS를 올리는 한 트랜잭션이어야 합니다. millis는 단조 증가하는 32비트 밀리초 시계, pause는 작업 양보 또는 짧은 지연으로 바꾸세요. 제한 시간은 대상 부품의 데이터시트에서 해당 프로그램·소거 동작에 맞춰 정하고 2^31밀리초보다 작게 유지합니다.

#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

/* The board driver implements one 05h transaction per call: CS low,
* send 05h, clock one status byte, then CS high. */
typedef bool (*read_sr1_fn)(void *ctx, uint8_t *status);
typedef uint32_t (*millis_fn)(void *ctx);
typedef void (*pause_fn)(void *ctx);

enum wait_result { WAIT_READY, WAIT_TIMEOUT, WAIT_IO_ERROR };

static enum wait_result wait_for_program(read_sr1_fn read_sr1,
millis_fn millis, pause_fn pause,
void *ctx, uint32_t timeout_ms)
{
const uint32_t start = millis(ctx);
for (;;) {
uint8_t sr1 = 0;
/* Unsigned subtraction remains correct across one 32-bit wrap,
* provided timeout_ms is less than 2^31 milliseconds. */
if ((uint32_t)(millis(ctx) - start) >= timeout_ms)
return WAIT_TIMEOUT;
if (!read_sr1(ctx, &sr1))
return WAIT_IO_ERROR;
if ((sr1 & 0x01u) == 0u)
return WAIT_READY;
pause(ctx); /* In firmware, yield or wait briefly; do not spin forever. */
}
}

struct fake_flash {
uint32_t tick;
unsigned reads;
unsigned busy_reads;
bool fail_read;
};

static uint32_t fake_millis(void *ctx)
{
return ((struct fake_flash *)ctx)->tick;
}

static void fake_pause(void *ctx)
{
((struct fake_flash *)ctx)->tick++;
}

static bool fake_read_sr1(void *ctx, uint8_t *status)
{
struct fake_flash *flash = ctx;
if (flash->fail_read)
return false;
*status = flash->reads++ < flash->busy_reads ? 0x01u : 0x00u;
return true;
}

int main(void)
{
struct fake_flash done = { .tick = 100u, .busy_reads = 2u };
assert(wait_for_program(fake_read_sr1, fake_millis, fake_pause,
&done, 5u) == WAIT_READY);
assert(done.reads == 3u);

struct fake_flash stuck = { .tick = 100u, .busy_reads = 100u };
assert(wait_for_program(fake_read_sr1, fake_millis, fake_pause,
&stuck, 3u) == WAIT_TIMEOUT);
assert(stuck.reads == 3u);

struct fake_flash wrapped = { .tick = UINT32_MAX - 1u,
.busy_reads = 2u };
assert(wait_for_program(fake_read_sr1, fake_millis, fake_pause,
&wrapped, 5u) == WAIT_READY);
assert(wrapped.tick == 0u);

struct fake_flash io = { .tick = 100u, .fail_read = true };
assert(wait_for_program(fake_read_sr1, fake_millis, fake_pause,
&io, 5u) == WAIT_IO_ERROR);

puts("ready: 3 status reads");
puts("timeout: 3 busy reads");
puts("tick wrap: ready");
puts("transport failure: error");
return 0;
}

gcc -std=c11 -Wall -Wextra -Werror spi_nor_wait.c -o spi_nor_wait로 빌드·실행했습니다. 정상 완료, BUSY 지속에 따른 시간 초과, 틱 오버플로, SPI 읽기 실패를 각각 assert로 검사합니다.

ready: 3 status reads
timeout: 3 busy reads
tick wrap: ready
transport failure: error

실제 펌웨어에 적용할 때

폴링 성공은 내부 동작 종료만 뜻합니다. 페이지 주소가 경계를 넘거나 보호 비트가 설정돼도 기대한 데이터가 기록되지 않을 수 있으므로, 완료 후 다시 읽어 비교하세요. MCU의 SPI DMA·인터럽트 콜백에서 바로 긴 폴링을 반복하지 말고, 작업 문맥에서 시간 제한과 스케줄링 정책을 적용하세요. 전원 중단 가능성이 있는 데이터에는 별도의 저널·이중 슬롯 설계가 필요합니다. 이 예제는 PC의 가짜 상태 값으로 제어 흐름을 검증했으며 실제 NOR 칩의 전기적 타이밍은 시험하지 않았습니다.

관련 글

댓글

첫 댓글을 남겨 보세요.

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

처리 중입니다...

잠시만 기다려주세요.