Post thumbnail

네모난 보드속에 피어난 How is the life? 아두이노 setup과 loop의 동작 원리

· by 박승재

Arduino는 문법이 제한된 C++ 언어를 이용해 프로그래밍하며, 코드의 구성부는 크게 setup 함수와 loop 함수로 나뉘어 있습니다.

setuploop의 동작 원리를 알아보기 위해, Arduino Core 코드를 다운로드 받아 main 함수를 열어보았습니다.

코드 분석은 Atmel SAM3X를 사용하는 Arduino Due를 기준으로 수행했습니다.

cores/arduino/main.cpp:

/*
 * \brief Main entry point of Arduino application
 */
int main(void) {
    // Initialize watchdog
    watchdogSetup();

    init();

    initVariant();

    delay(1);

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();

    for (;;) {
        loop();
        if (serialEventRun)
            serialEventRun();
    }

    return 0;
}

모든 C++ 프로그램은 main에서부터 시작합니다.

아두이노가 실행되면 가장 먼저 watchdogSetup을 호출합니다.

cores/arduino/watchdog.cpp:

extern "C" void _watchdogDefaultSetup(void) {
    WDT_Disable(WDT);
}
void watchdogSetup(void) __attribute__((weak, alias("_watchdogDefaultSetup")));

watchdogSetup_watchdogDefaultSetup으로 매핑되며, WDT_Disable를 호출합니다.

system/CMSIS/Device/ATMEL/sam3xa/include/component/component_wdt.h:

/**
 * \brief Disable watchdog.
 *
 * \note The Watchdog Mode Register (WDT_MR) can be written only once.
 * Only a processor reset resets it.
 */
extern void WDT_Disable(Wdt* pWDT) {
    pWDT->WDT_MR = WDT_MR_WDDIS;
}

WDT_MR는 Watchdog Timer의 Mode Register를 의미합니다.

WDT_MR_WDDIS는 Watchdog Disable을 의미하며, Watchdog Timer Mode Register Watchdog Disable의 약자로 추정됩니다.

그 다음은 init을 호출합니다.

variants/arduino_due_x/variant.cpp:

void init(void) {
    SystemInit();

    // Set Systick to 1ms interval, common to all SAM3 variants
    if (SysTick_Config(SystemCoreClock / 1000)) {
        // Capture error
        while (true)
            ;
    }

    // Initialize C library
    __libc_init_array();

    // Disable pull-up on every pin
    for (unsigned i = 0; i < PINS_COUNT; i++)
        digitalWrite(i, LOW);

    // Enable parallel access on PIO output data registers
    PIOA->PIO_OWER = 0xFFFFFFFF;
    PIOB->PIO_OWER = 0xFFFFFFFF;
    PIOC->PIO_OWER = 0xFFFFFFFF;
    PIOD->PIO_OWER = 0xFFFFFFFF;

    // Initialize Serial port U(S)ART pins
    PIO_Configure(
        g_APinDescription[PINS_UART].pPort,
        g_APinDescription[PINS_UART].ulPinType,
        g_APinDescription[PINS_UART].ulPin,
        g_APinDescription[PINS_UART].ulPinConfiguration);
    digitalWrite(0, HIGH); // Enable pullup for RX0
    PIO_Configure(
        g_APinDescription[PINS_USART0].pPort,
        g_APinDescription[PINS_USART0].ulPinType,
        g_APinDescription[PINS_USART0].ulPin,
        g_APinDescription[PINS_USART0].ulPinConfiguration);
    PIO_Configure(
        g_APinDescription[PINS_USART1].pPort,
        g_APinDescription[PINS_USART1].ulPinType,
        g_APinDescription[PINS_USART1].ulPin,
        g_APinDescription[PINS_USART1].ulPinConfiguration);
    PIO_Configure(
        g_APinDescription[PINS_USART3].pPort,
        g_APinDescription[PINS_USART3].ulPinType,
        g_APinDescription[PINS_USART3].ulPin,
        g_APinDescription[PINS_USART3].ulPinConfiguration);

    // Initialize USB pins
    PIO_Configure(
        g_APinDescription[PINS_USB].pPort,
        g_APinDescription[PINS_USB].ulPinType,
        g_APinDescription[PINS_USB].ulPin,
        g_APinDescription[PINS_USB].ulPinConfiguration);

    // Initialize CAN pins
    PIO_Configure(
        g_APinDescription[PINS_CAN0].pPort,
        g_APinDescription[PINS_CAN0].ulPinType,
        g_APinDescription[PINS_CAN0].ulPin,
        g_APinDescription[PINS_CAN0].ulPinConfiguration);
    PIO_Configure(
        g_APinDescription[PINS_CAN1].pPort,
        g_APinDescription[PINS_CAN1].ulPinType,
        g_APinDescription[PINS_CAN1].ulPin,
        g_APinDescription[PINS_CAN1].ulPinConfiguration);

    // Initialize Analog Controller
    pmc_enable_periph_clk(ID_ADC);
    adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);
    adc_configure_timing(ADC, 0, ADC_SETTLING_TIME_3, 1);
    adc_configure_trigger(ADC, ADC_TRIG_SW, 0); // Disable hardware trigger.
    adc_disable_interrupt(ADC, 0xFFFFFFFF); // Disable all ADC interrupts.
    adc_disable_all_channel(ADC);

    // Initialize analogOutput module
    analogOutputInit();
}

init의 가장 위에는 SystemInit이 있습니다.

system/CMSIS/Device/ATMEL/sam3xa/source/system_sam3xa.c:

/**
 * \brief Setup the microcontroller system.
 * Initialize the System and update the SystemFrequency variable.
 */
void SystemInit(void) {
    /* Set FWS according to SYS_BOARD_MCKR configuration */
    EFC0->EEFC_FMR = EEFC_FMR_FWS(4);
    EFC1->EEFC_FMR = EEFC_FMR_FWS(4);

    /* Initialize main oscillator */
    if (!(PMC->CKGR_MOR & CKGR_MOR_MOSCSEL)) {
        PMC->CKGR_MOR = SYS_CKGR_MOR_KEY_VALUE | SYS_BOARD_OSCOUNT | CKGR_MOR_MOSCRCEN | CKGR_MOR_MOSCXTEN;
        while (!(PMC->PMC_SR & PMC_SR_MOSCXTS)) {
        }
    }

    /* Switch to 3-20MHz Xtal oscillator */
    PMC->CKGR_MOR = SYS_CKGR_MOR_KEY_VALUE | SYS_BOARD_OSCOUNT | CKGR_MOR_MOSCRCEN | CKGR_MOR_MOSCXTEN | CKGR_MOR_MOSCSEL;

    while (!(PMC->PMC_SR & PMC_SR_MOSCSELS)) {
    }
    PMC->PMC_MCKR = (PMC->PMC_MCKR & ~(uint32_t) PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_MAIN_CLK;
    while (!(PMC->PMC_SR & PMC_SR_MCKRDY)) {
    }

    /* Initialize PLLA */
    PMC->CKGR_PLLAR = SYS_BOARD_PLLAR;
    while (!(PMC->PMC_SR & PMC_SR_LOCKA)) {
    }

    /* Switch to main clock */
    PMC->PMC_MCKR = (SYS_BOARD_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_MAIN_CLK;
    while (!(PMC->PMC_SR & PMC_SR_MCKRDY)) {
    }

    /* Switch to PLLA */
    PMC->PMC_MCKR = SYS_BOARD_MCKR;
    while (!(PMC->PMC_SR & PMC_SR_MCKRDY)) {
    }

    SystemCoreClock = CHIP_FREQ_CPU_MAX;
}

SystemInit에서는 마이크로컨트롤러 시스템을 초기화하고, 시스템 주파수를 업데이트합니다.

코드 사이에 while 루프가 있는 이유는, 코드 상에서 값이 변경되더라도 시스템에 영향을 미칠 때까지 시간이 걸리기 때문에 반복문을 돌며 기다리기 위함입니다.

다시 init으로 돌아와 코드를 실행시키다 보면,

for (unsigned i = 0; i < PINS_COUNT; i++)
    digitalWrite(i, LOW);

모든 핀LOW설정하는 것을 확인할 수 있습니다.

그리고 좀 더 아래에서,

digitalWrite(0, HIGH); // Enable pullup for RX0

RX0를 위한 0번 핀만 HIGH재설정합니다.

cores/arduino/wiring_constants.h:

#define HIGH 0x1
#define LOW  0x0

참고로 HIGHLOW의 값은 unsigned int로 각각 1, 0으로 정의되어 있습니다.

adc_* 함수로 아날로그 컨트롤러까지 초기화하고 나면 main 함수로 돌아옵니다.

init 다음의 initVariant는 현재 아무 기능이 없는 함수입니다.

Variant의 사전적 의미는 “변종”이라는 뜻이며, initVariant는 특정 보드에서 특별한 초기화가 추가로 필요할 때 구현해서 사용하기 위해 정의된 함수입니다.

그다음 delay(1) 이후, 사용자가 정의한 setup이 호출됩니다.

delay 함수 같은 경우는 반복문을 이용한 Busy-waiting 방식으로 구현되어 있습니다.

cores/arduino/wiring.c:

void delay(unsigned long ms) {
    if (ms == 0)
        return;
    uint32_t start = GetTickCount();
    do {
        yield();
    } while (GetTickCount() - start < ms);
}

setup이 끝나면 무한루프 for (;;)를 이용해 loop를 호출합니다.

코드를 통해 loop는 일정 시간마다 호출되는 함수가 아니라, 이전 루프가 끝나는 대로 for에 의해 연속적으로 호출되는 함수임을 알 수 있습니다.

loop가 끝날 때마다 serialEventRun이 호출되는데, serialEventRun은 Serial 버퍼에 데이터가 있다면 serialEvent를 호출합니다.

variants/arduino_due_x/variant.cpp:

UARTClass Serial(UART, UART_IRQn, ID_UART, &rx_buffer1, &tx_buffer1);
void serialEvent() __attribute__((weak));
void serialEvent() { }

USARTClass Serial1(USART0, USART0_IRQn, ID_USART0, &rx_buffer2, &tx_buffer2);
void serialEvent1() __attribute__((weak));
void serialEvent1() { }
USARTClass Serial2(USART1, USART1_IRQn, ID_USART1, &rx_buffer3, &tx_buffer3);
void serialEvent2() __attribute__((weak));
void serialEvent2() { }
USARTClass Serial3(USART3, USART3_IRQn, ID_USART3, &rx_buffer4, &tx_buffer4);
void serialEvent3() __attribute__((weak));
void serialEvent3() { }

void serialEventRun(void) {
    if (Serial.available())
        serialEvent();
    if (Serial1.available())
        serialEvent1();
    if (Serial2.available())
        serialEvent2();
    if (Serial3.available())
        serialEvent3();
}

serialEvent는 프로그래머가 시리얼 ISR로 구현할 수 있는 함수입니다.

main 마지막의 return 0는 무한루프로 인해 영원히 실행되지 않는 코드입니다.