[임베디드 OS 개발 프로젝트] 11. 이벤트

Sijin·2025년 7월 20일

이벤트

  • 어떤 임베디드 시스템에서 사용자가 버튼을 누른 상황을 가정하자
    • 버튼에 연결된 스위치가 전기신호를 발생시킨다
    • 컨트롤러는 전기 신호를 인식한다
    • 물리적 전기신호를 소프트웨어적 인터럽트인 IRQ나 FIQ를 발생시킨다
    • IRQ나 FIQ 핸들러에서 인터럽트 컨트롤러의 레지스터를 읽고 어떤 인터럽트인지 확인한다
    • 해당 인터럽트의 핸들러가 동작한다
  • 이번 장에서 인터럽트 핸들러를 태스크로 옮겨보자
    • 이때 이벤트가 인터럽트와 태스크 사이의 연결매체 역할을 수행한다

이벤트 플래그

  • 이벤트를 bitmap으로 만들면 각가의 이벤트를 명확히 구분할 수 있다
  • 예를 들어, 0b1001은 0b1000과 0b0001 두 이벤트가 발생해서 pending 상태라는 뜻으로 표현할 수 있다
# kernel/event.h

typedef enum KernelEventFlag_t
{
    KernelEventFlag_UartIn      = 0x00000001,
    KernelEventFlag_Reserved01  = 0x00000002,
    KernelEventFlag_Reserved02  = 0x00000004,
    KernelEventFlag_Reserved03  = 0x00000008,
    KernelEventFlag_Reserved04  = 0x00000010,
    KernelEventFlag_Reserved05  = 0x00000020,
    KernelEventFlag_Reserved06  = 0x00000040,
    ...
    KernelEventFlag_Reserved29  = 0x20000000,
    KernelEventFlag_Reserved30  = 0x40000000,
    KernelEventFlag_Reserved31  = 0x80000000,

    KernelEventFlag_Empty       = 0x00000000,
} KernelEventFlag_t;

void Kernel_event_flag_set(KernelEventFlag_t event);
void Kernel_event_flag_clear(KernelEventFlag_t event);
bool Kernel_event_flag_check(KernelEventFlag_t event);
  • 이벤트 플래그들을 먼저 선언한다
    • 우선 UartIn 이벤트만 선언해두었다
    • 32bit 변수에 32개 이벤트를 예약해두었다
# kernel/event.c

static uint32_t sEventFlag;

void Kernel_event_flag_init(void)
{
    sEventFlag = 0;
}

void Kernel_event_flag_set(KernelEventFlag_t event)
{
    sEventFlag |= (uint32_t)event;
}

void Kernel_event_flag_clear(KernelEventFlag_t event)
{
    sEventFlag &= ~(uint32_t)event;
}

void Kernel_event_flag_check(KernelEventFlag_t event)
{
    if (sEventFlag & (uint32_t)event)
    {
        Kernel_event_flag_clear(event);
        return true;
    }
    return false;
}
  • 이벤트 init/set/clear/check에 대한 함수도 만들었다
    • check 함수에서는 파라미터로 전달된 이벤트가 pending 상태면 clear 후 true를 반환한다
  • 태스크에서 커널 API를 통해 이벤트를 처리할 수 있도록 구성해보자
# kernel/Kernel.c

void Kernel_send_events(uint32_t event_list)
{
    for (uint32_t i = 0; i < 32; i++)
    {
        if ((event_list >> i) & 1)
        {
            KernelEventFlag_t sending_event = KernelEventFlag_Empty;
            sending_event = (KernelEventFlag_t)SET_BIT(sending_event, i);
            Kernel_event_flag_set(sending_event);
        }
    }
}

void Kernel_wait_events(uint32_t waiting_list)
{
    for (uint32_t i = 0; i < 32; i++)
    {
        if ((waiting_list >> i) & 1)
        {
            KernelEventFlag_t waiting_event = KernelEventFlag_Empty;
            waiting_event = (KernelEventFlag_t)SET_BIT(waiting_event, i);

            if (Kernel_event_flag_check(waiting_event))
            {
                return waiting_event;
            }
        }
    }
    return KernelEventFlag_Empty;
}
  • Kernel_send_events는 이벤트를 전달하는 함수이다
  • 파라미터가 uint32_t인 것은, 여러개 이벤트를 한번에 보내고 받기 위함이다
    • Kernel_send_events(event1|event2|event3) 처럼 caller에서 or연산으로 한번에 보낼 수 있다
  • 마찬가지로, 자신이 기다리는 이벤트의 전체 목록을 Kernel_wait_events에 한번에 보내 처리할 수 있다

인터럽트와 이벤트

  • 이벤트는 인터럽트와 엮어서 사용하는 것이 일반적이다
  • 지금까지 구현으로는 UART 입력 인터럽트가 발생하면 UART 하드웨어에서 입력된 글자를 받아 그대로 UART 출력하는 일을 한다
    • 이 기능을 태스크의 이벤트로 옮겨보자
  • 우선 이벤트를 발생시켜서 이벤트 핸들러가 동작하는지 확인해보자
# hal/rvpb/Uart.c

static void interrupt_handler(void)
{
    uint8_t ch = Hal_uart_get_char();
    Hal_uart_put_char(ch);

    Kernel_send_events(KernelEventFlag_UartIn);
}
  • Uart 이벤트 핸들러에서 UartIn 이벤트를 등록하자
# boot/Main.c

void my_task(void)
{
    uint32_t local = 0;
    debug_printf("task_1 SP=0x%x\n", &local);

    while (true)
    {
        KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn);
        switch (handle_event)
        {
            case KernelEventFlag_UartIn:
                debug_printf("\nEvent handled\n");
                break;
        }
        Kernel_yield();
    }
}

static void Kernel_init(void)
{
    uint32_t taskId;
    Kernel_task_init();
    Kernel_event_flag_init();

    if (Kernel_task_create(my_task) == NOT_ENOUGH_TASK_NUM)
    {
        debug_printf("dummy task creation failed\n");
    }

    Kernel_start();
}
  • Kernel 초기화 부분에서 event를 초기화한다
  • my_task에서 UartIn 이벤트를 기다리고 도착했으면 debug_print로 출력해보자
  • 이렇게 수정한 코드를 실행해보면 위와 같이 확인할 수 있다

사용자 정의 이벤트

  • 이벤트는 인터럽트에서 파생된 경우에만 사용해야 하는 것은 아니다
  • Task에서 Task로 이벤트를 전송할 수도 있다
# kernel/event.h

typedef enum KernelEventFlag_t
{
    KernelEventFlag_UartIn = 0x00000001,
    KernelEventFlag_CmdIn = 0x00000002,
    ...
} KernelEventFlag_t;
  • Task0에서 Task1로 보내는 이벤트를 CmdIn으로 정의하고 동작시켜보자
void task_0(void)
{
    uint32_t local = 0;
    debug_printf("task_1 SP=0x%x\n", &local);

    while (true)
    {
        KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn);
        switch (handle_event)
        {
            case KernelEventFlag_UartIn:
                debug_printf("\nEvent handled by Task0\n");
                Kernel_send_events(KernelEventFlag_CmdIn);
                break;
        }
        Kernel_yield();
    }
}

void task_1(void)
{
    uint32_t local = 0;
    debug_printf("task_1 SP=0x%x\n", &local);

    while (true)
    {
        KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_CmdIn);
        switch (handle_event)
        {
            case KernelEventFlag_CmdIn:
                debug_printf("\nEvent handled by Task1\n");
                break;
        }
        Kernel_yield();
    }
}

static void Kernel_init(void)
{
    uint32_t taskId;
    Kernel_task_init();
    Kernel_event_flag_init();

    if (Kernel_task_create(task_0) == NOT_ENOUGH_TASK_NUM)
    {
        debug_printf("dummy task creation failed\n");
    }

    if (Kernel_task_create(task_1) == NOT_ENOUGH_TASK_NUM)
    {
        debug_printf("dummy task creation failed\n");
    }

    Kernel_start();
}
  • Uart 인터럽트가 발생하면 Uart 인터럽트 핸들러에서 UartIn 이벤트를 보낸다
  • 이후에 스케줄러에 의해 실행된 Task0이 UartIn 이벤트를 확인하고 받아온다
  • Task0의 이벤트 처리 코드가 실행되면서 CmdIn 이벤트를 보낸다
  • 이후 스케줄러에 의해 실행된 Task1이 CmdIn 이벤트를 확인하고 받아온다
  • Task1의 이벤트 처리코드가 실행된다

0개의 댓글