이벤트의 한계
- 지난 글에서 UART 인터럽트 핸들러로 입력 이벤트를 보냈지만 어떤 문자인지는 보낼수가 없었음
-> 데이터를 메시지로 보내면 된다.
- 비유 : 이벤트(뻴) - 메시지(사서함)
구현 목표
- 인터럽트 핸들러에서 이벤트 외에 데이터를 메시지로 보내고 이벤트 핸들러에서 메시지를 처리하자
메시지 설계하기
- 보통 메시지를 큐를 이용해서 구현 -> 메시지 큐
- 큐 구현 : 배열이나 동적 할당으로 링크드 리스트를 만들어 사용하나 임베디드 시스템에선 동적할당안쓰고 배열사용
- 배열로 큐 구현하려면? 데이터 들어갈 인덱스 변수와 꺼낼 데이터 인덱스 변수 필요(front, rear)
메시지 큐 만들기
kernel/msg.h
- 원형 큐 자료구조 KernelCirQ_t 에서 front는 꺼낼 데이터 인덱스, rear는 데이터 넣을 위치 인덱스, 배열 크기는 512
- 태스크가 3개여서 메시지 큐를 3개 사용. 각 메시지 큐를 지시하도록 KernelMsgQ_t 이넘 선언
#ifndef KERNEL_MSG_H_
#define KERNEL_MSG_H_
#define MSG_Q_SIZE_BYTE 512
typedef enum KernelMsgQ_t
{
KernelMsgQ_Task0,
KernelMsgQ_Task1,
KErnelMsgQ_Task2,
KernelMsgQ_Num
} KernelMsgQ_t;
typedef struct KernelCirQ_t
{
uint32_t front;
uint32_t rear;
uint8_t Queue[MSG_Q_SIZE_BYTE];
} KernelCirQ_t;
void Kernel_msgQ_init(void);
bool Kernel_msgQ_is_empty(KernelMsqQ_t Qname);
bool Kernel_msgQ_is_full(KernelMsgQ_t Qname);
bool Kernel_msgQ_enqueue(KernelMsgQ_t Qname, uint8_t data);
bool Kernel_msgQ_dequeue(KernelMsgQ_t Qname, uint8_t* out_data);
#endif
kernel/msg.c
여기선 그렇게 어려운 내용은 없었다.
메시지 큐 3개 초기화 하는 함수나
비었는지, 꽉찻는지 확인하는 함수
유효성 검사후 데이터 입력하거나 빼는 함수
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include "msg.h"
KernelCirQ_t sMsgQ[KernelMsgQ_Num];
void Kernel_msgQ_init(void)
{
for (uint32_t i = 0; i < KernelMsgQ_Num ; i++)
{
sMsgQ[i].front = 0;
sMsgQ[i].rear = 0;
memclr(sMsgQ[i].Queue, MSG_Q_SIZE_BYTE);
}
}
bool Kernel_msgQ_is_empty(KernelMsgQ_t Qname)
{
if (Qname >= KernelMsgQ_Num)
{
return false;
}
if (sMsgQ[Qname].front == sMsgq[Qname].rear)
{
return true;
}
return false;
}
bool Kernel_msgQ_is_full(KernelMsgQ_t Qname)
{
if (Qname >= KernelMsgQ_Num)
{
return false;
}
if (((sMsgQ[Qname].rear + 1) % MSG_Q_SIZE_BYTE) == sMsgQ[Qname].front)
{
return true;
}
return false;
}
bool Kernel_msgQ_enqueue(KernelMsgQ_t Qname, uint8_t data)
{
if (Qname >= KernelMsgQ_Num)
{
return false;
}
if (Kernel_msgQ_is_full(Qname))
{
return false;
}
sMsgQ[Qname].rear++;
sMsgQ[Qname].rear %= MSG_Q_SIZE_BYTE;
uint32_t idx = sMsgQ[Qname].rear;
sMsgQ[Qname].Queue[idx] = data;
return true;
}
bool Kernel_msgQ_dequeue(KernelMsgQ_t Qname, uint8_t* out_data)
{
if (Qname >= KernelMsgQ_Num)
{
return false;
}
if (Kernel_msgQ_is_empty(Qname))
{
return false;
}
sMsgQ[Qname].front++;
sMsgQ[Qname].front %= MSG_Q_SIZE_BYTE;
uint32_t idx = sMsgQ[Qname].front;
*out_data = sMsgQ[Qname].Queue[idx];
return true;
}
메시지 송수신 API 구현하기
Kernel_send_msg
메시지큐와 데이터 포인터, 길이를 인자로 전달받아
길이만큼 루프돌리며 1바이트씩 enqueue 수행
메시지큐가 꽉차 false 리턴받으면 deque시켜 비워준다.
kernel_recv_msg
메시지큐 이름과 저장할 위치 포인터, 길이 전달받아
메시지 디큐 함수로 1바이트씩 꺼내서 저장한다.
메시지큐가 비어있어 false를 반환받거나 정상적으로 수행되면 받은 길이 수를 반환한다.
#include <stdint.h>
#include <stdbool.h>
#include "memio.h"
#include "Kernel.h"
#include "event.h"
void Kernel_start(void)
{
Kernel_task_start();
}
void Kernel_yield(void)
{
Kernel_task_scheduler();
}
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);
}
}
}
KernelEventFlag_t 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;
}
bool Kernel_send_msg(Kernel_MsgQ_t Qname, void* data, uint32_t count)
{
uint8_t* d = (uint8_t*)data;
for (uint32_t i = 0; i < count ; i++)
{
if (false == Kernel_msgQ_enqueue(Qname, *d))
{
for (uint32_t j = 0; j < i; j++)
{
uint8_t rollback;
Kernel_msgQ_dequeue(Qname, &rollback);
}
return false;
}
d++;
}
return true;
}
uint32_t Kernel_recv_msg(KernelMsgQ_t Qname, void* out_data, uint32_t count)
{
uint8_t* d = (uint8_t*)out_data;
for (uint32_t i = 0; i < count ; i++)
{
if (false == Kernel_msgQ_dequeue(Qname, d))
{
return i;
}
d++;
}
return count;
}
지금까지 한게
메시지 큐 설계, 기능 구현
메시지 송수신 커널 API
이제 할건
유아트 입력 인터럽트 발생시 이벤트 송신과 함께 메시지 보내도록 구현하는데
잠깐 이벤트랑 보면
이벤트는 uint32_t sEventFlag를 이용해서 송수신을 했다면
메시지는 메시지 큐를 구현해서 송수신하는게 차이인거같다.
아무튼 유아트 입력 인터럽트시 메시지도 같이 보내도록 수정하면
* 주의 : 지난 이벤트 장에서 유아트 인이랑 CMDIN 이벤트를 같이 보내도록 했는데 그러면 유아트 입력떄마다 task1에서 리시빙 처리한다. 유아트 인 이벤트만 보내도록 뺴야한다.
#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"
#include "Kernel.h"
#include "msg.h"
extern volatile PL011_t* Uart;
static void interrupt_handler(void);
void Hal_uart_init(void)
{
//enable UART
Uart->uartcr.bits.UARTEN=0;
Uart->uartcr.bits.TXE=1;
Uart->uartcr.bits.RXE=1;
Uart->uartcr.bits.UARTEN=1;
Uart->uartimsc.bits.RXIM=1;
Hal_interrupt_enable(UART_INTERRUPT0);
Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}
void Hal_uart_put_char(uint8_t ch)
{
while(Uart->uartfr.bits.TXFF);
Uart->uartdr.all = (ch & 0xFF);
}
uint8_t Hal_uart_get_char(void)
{
uint32_t data;
while(Uart->uartfr.bits.RXFE);
data = Uart->uartdr.all;
//check for and error flag
if (data & 0xFFFFFF00)
{
//clear the error
Uart->uartrsr.all = 0xFF;
return 0;
}
return (uint8_t)(data & 0xff);
}
static void interrupt_handler(void)
{
uint8_t ch = Hal_uart_get_char();
Hal_uart_put_char(ch);
Kernel_send_events(KernelEventFlag_UartIn);
Kernel_send_msg(KernelMsqQ_Task0, &ch, 1);
if (ch == 'x')
{
Kernel_send_events(KernelEventFlag_CmdOut);
}
}
빌드하는 중간에
오타나 수정하다보면
memclr를 정의하지 않아서 링킹이 안된다.
lib/stdlib.c , .h에 그냥 구현해주자.
#ifndef LIB_STDLIB_H_
#define LIB_STDLIB_h_
void delay(uint32_t ms);
void memclr(void* dst, uint32_t count);
#endif
#include <stdint.h>
#include <stdbool.h>
#include "HalTimer.h"
void delay(uint32_t ms)
{
uint32_t goal = Hal_timer_get_1ms_counter() + ms;
while(goal != Hal_timer_get_1ms_counter());
}
void memclr(void* dst, uint32_t count)
{
uint8_t *d = (uint8_t)dst;
while(count--)
{
*d++ = 0;
}
}
다 하고
태스크0이 CmdIn 이벤트와함께 메시지를 보내는데
책에서 안전하게 보내는법이라 나와있는데로 구현하면 좀 이상하게 동작한다.
어쩔때는 잘동작하고 지금은 이상한데 뭐가 문젠지는 잘모르겠지만
원래대로 돌려서 쓰니 이젠 또 잘된다.
#include <stdint.h>
#include <stdbool.h>
#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"
#include "task.h"
#include "Kernel.h"
#include "msg.h"
#include "stdio.h"
#include "stdlib.h"
static void Hw_init(void);
static void Kernel_init(void);
static void Printf_test(void);
static void Timer_test(void);
void User_task0(void);
void User_task1(void);
void User_task2(void);
void main(void)
{
Hw_init();
uint32_t i = 100;
while(i--)
{
Hal_uart_put_char('N');
}
Hal_uart_put_char('\n');
putstr("Hello World!\n");
Printf_test();
Timer_test();
Kernel_init();
while(true);
}
static void Hw_init(void)
{
Hal_interrupt_init();
Hal_uart_init();
Hal_timer_init();
}
static void Printf_test(void)
{
char* str = "printf pointer test";
char* nullptr = 0;
uint32_t i = 5;
uint32_t* sysctrl0 = (uint32_t*)0x10001000;
debug_printf("%s\b", "Hello printf");
debug_printf("output string pointer: %s\n", str);
debug_printf("%s is null pointer, %u number\n", nullptr, 10);
debug_printf("%u=5\n", i);
debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
debug_printf("print zero %u\n", 0);
debug_printf("SYSCTRL0 %x\n", *sysctrl0);
}
static void Timer_test(void)
{
for(uint32_t i = 0; i < 2 ; i++)
{
debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
delay(1000);
}
}
static void Kernel_init(void)
{
uint32_t taskId;
Kernel_task_init();
Kernel_event_flag_init();
taskId = Kernel_task_create(User_task0);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task0 creation fail\n");
}
taskId = Kernel_task_create(User_task1);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task1 creation fail\n");
}
taskId = Kernel_task_create(User_task2);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task2 creation fail\n");
}
Kernel_start();
}
void User_task0(void)
{
uint32_t local = 0;
debug_printf("User Task #0 SP = 0x%x\n", &local);
uint8_t cmdBuf[16];
uint32_t cmdBufIdx = 0;
uint8_t uartch = 0;
while(true)
{
KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn | KernelEventFlag_CmdOut);
switch(handle_event)
{
case KernelEventFlag_UartIn:
Kernel_recv_msg(KernelMsgQ_Task0, &uartch, 1);
if (uartch == '\r')
{
cmdBuf[cmdBufIdx] = '\0';
Kernel_send_msg(KernelMsgQ_Task1, &cmdBufIdx, 1);
Kernel_send_msg(KernelMsgQ_Task1, cmdBuf, cmdBufIdx);
Kernel_send_events(KernelEventFlag_CmdIn);
/*
while(true)
{
Kernel_send_events(KernelEventFlag_CmdIn);
if (false == Kernel_send_msg(KernelMsgQ_Task1, &cmdBufIdx, 1))
{
Kernel_yield();
}
else if (false == Kernel_send_msg(KernelMsgQ_Task1, cmdBuf, cmdBufIdx))
{
uint8_t rollback;
Kernel_recv_msg(KernelMsgQ_Task1, &rollback, 1);
Kernel_yield();
}
}
*/
cmdBufIdx = 0;
}
else
{
cmdBuf[cmdBufIdx] = uartch;
cmdBufIdx++;
cmdBufIdx %= 16;
}
break;
case KernelEventFlag_CmdOut:
debug_printf("\nCmdOut Event by Task0\n");
break;
}
Kernel_yield();
}
}
void User_task1(void)
{
uint32_t local = 0;
debug_printf("User Task #1 SP = 0x%x\n", &local);
uint8_t cmdlen = 0;
uint8_t cmd[16] = {0};
while(true)
{
KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_CmdIn);
switch(handle_event)
{
case KernelEventFlag_CmdIn:
memclr(cmd, 16);
Kernel_recv_msg(KernelMsgQ_Task1, &cmdlen, 1);
Kernel_recv_msg(KernelMsgQ_Task1, cmd, cmdlen);
debug_printf("\nRecv Cmd : %s\n", cmd);
break;
}
Kernel_yield();
}
}
void User_task2(void)
{
uint32_t local = 0;
debug_printf("User Task #2 SP = 0x%x\n", &local);
while(true)
{
Kernel_yield();
}
}
처음에는 원래 코드도 안돌아갔는데
어째저째 고치다보니 지금 잘 동작하니
여기서 너무 시간 지체해서 넘어가야겠다.
'컴퓨터과학 > os' 카테고리의 다른 글
OS30 - 1. 시작 : 바이너리 에디터로 부팅 이미지 만들기 (0) | 2024.04.04 |
---|---|
navilos - 19. 동기화 (0) | 2022.08.25 |
navilos - 17. 이벤트 2 : 사용자 정의 이벤트, 여러 이벤트 동시 처리 (0) | 2022.08.25 |
navilos - 16. 이벤트 1 : 플래그, 인터럽트와 연결해 이벤트 처리 (0) | 2022.08.25 |
navilos - 15. 컨텍스트 스위칭 2 : yield로 비선점형 스케줄링 구현 (0) | 2022.08.24 |