3D 장면에서 컴퓨터까지

이미지 샘플링, 이산화

- 이미지를 픽셀별로 분리. 해상도가 320 x 240 일 시 가로 320픽셀, 세로 240 픽셀

이미지 히스토그램

- 이미지 픽셀별 강도값을 히스토그램으로 표현

- 히스토그램 평활화 : 히스토그램을 평활하게 분포시켜 강도 범위를 펼쳐 이미지 선명도를 높임

https://gaussian37.github.io/vision-concept-histogram_equalization/

 

 

이진화

- 경계값(Thresold)에 따라 픽셀 값을 0, 1로

https://lucathree.github.io/python/day49-1/

스무딩(블러링, 저주파 통과 필터 효과)

- 영상 노이즈 제거. 특정 픽셀이 지나치게 크거나 작은경우 제거하거나 이웃과 비슷하게 -> 이미지가 선명해짐

가우시안 스무딩 https://iskim3068.tistory.com/41

이미지의 기하학적변환

크기변환 scalining

직선이동

 

회전 행렬

 

 

 

에지 검출

- 이미지 경계, 물체 정보 취득에 사용

- 이미지 강도 변화율로 검출 -> 1차 미분의 극점 에지 존재

 

이미지 그라디언트, 자코비안, 헤시안

- 그라디언트 : 다변수 스칼라 함수 1차 미분 -> 이미지 에지검출에 사용

- 자코비안 : 다변수 벡터 함수에 대한 1차 미분 ->  그라디언트나 마찬가지로 1차 미분이므로 지역적 변화 파악에사용

- 헤시안 : 다변수 벡터 함수에 대한 이차미분 -> 곡률 특성 나타냄. 최적화 문제에 사용

- 라플라시안 : 다변수 스칼라 함수의 2차 미분 -> 모든 방향으로 변화가 심할때 최대값가짐 ->코너 검출

ref  :https://darkpgmr.tistory.com/132

 

 

마스크를 이용한 에지검출

- 소벨 마스크를 이용. 

- 소벨 마스크 : 픽셀간 1차 미분 근사화한 마스크, 수직 수평 경계에 민감

 

 

모폴로지(형태학적) 연산

- 픽셀간 통합 분리

- 침식, 팽창, 열림, 닫힘 4가지 프로세스, 노이즈 제거 지문인식 등 사용

- 침식 erode :  이미지 줄임 -> 노이즈 제거

- 팽창 dilation : 경계가 부드러워짐, 구멍 매꿈

- 열림 연산 : 침식 후 팽창 적용 -> 작은 객체 제거에 적합

- 닫힘 연산 : 팽창 후 침식 적용  -> 전체적인 윤곽 파악에 좋음

ref : https://opencv-python.readthedocs.io/en/latest/doc/12.imageMorphological/imageMorphological.html



침식



팽창

 

 

 

이미지 변환 종류

- 평행이동, 회전, 크기조절, 원근변환 등

종류

1. ultrasonic sensor

2. PSD Position Sensitive Device

3. Encoder

4. Hall effect sensor

5. Capacitance sensor

6. Eddy current sensor

7. IMU

8. UWB Communication

 

 

 

 

 

 

 

1. 초음파 센서

1. 트리거 펄스 입력 : DSP IO포트로부터 트리거 신호를 트리거 펄스 인풋에 10us 보내주면 초음파(8사이클) 보낸다.

2. 초음파 에코 단자(에코펄스 출력)에서 로에서 하이로 바뀐다.

 - 이 신호의 라이징 에지를 잡고, 초음파가 물체에 갔다 돌아오는 시간을 재야하므로 타이머 시작.

3. 초음파가 돌아오면 에코 단자의 신호가 다시 Low로 바뀌고 타이머 정지

-> 초음파가 돌아올때까지의 시간과 음포 속도로 거리 측정. 거리 = 시간 x 음속 / 2

인식 범위


https://www.microsonic.de/kr/support/ultrasonic-technology/detection-zones.htm
SRF04

http://www.datasheetcafe.com/srf04-datasheet-pdf/

 

 

2. PSD 센서

- 저가, 신뢰성 있음.

- 대상물이 위치에 따라 센서와 거리, 빛이 반사되는 각도가 다름

- 측정 거리 L은 L_o : L_b = f : x 즉, L_o =  L_b * f/ x

-  송신부 : 적외선 발광 다이오드

- 수광부 : 렌즈의 배열로 있음

- 특징(GPY0A21YK)

  측정 거리 : 10 ~ 80cm

  패키징 크기 : 29.5 x 13 x 13.5

  공급전압 : 4.5 ~ 5.5v

  소비전류 : 30mA






https://m.blog.naver.com/iotsensor/221907920725

 

 

3. 엔코더

- 증분 엔코더 : 회전식 펄스 카운팅

- A상은 펄스 속도, B상은 방향 확인을 위함

- 앱솔루트 엔코더 : 회전각에 따라 다른 출력신호

 * 그레이 코드 표기 : 1비트씩 바뀐다. 경계점에서 완전히 다른 코드로 바뀌는 문제 개선



https://www.freego2.de/index.php/technical-info/encoders

4. 홀 이펙트 센서

- 홀전압은 자기장과 입력 절뉴에 비례

- 회전체의 각도 방위 측정에 활용

 

5. IMU(가속도계 + 각속도계)

- 이동관성(가속도) + 회전관성(각속도) + 지자기계 측정

- 센서내 마이크로프로세서로 계산. 데이터를 I2C, SPI로 가져와 사용.

 

 

센서의 신호처리 - 필터 종류.

- Low Pass Filter : 저역통과(저주파) 필터. 고주파 노이즈 제거(ex. 가속도 센서)

- High Pass Filter : 고역통과필터. 저주파 노이즈 제거하는 경우(ex. 센서 적분 데이터)

- Band Pass Filter : 특정 주파수 대역만 통과.

- Band Stop Filter : 대역 제거 필터. 특정 대역 신호만 제거.

 

 

1차 저주파 통과 필터 구현 1st Low Pass Filter

- 아날로그 필터와 디지털 필터. 디지털 필터(코딩해서 신호 처리)를 주로 구현

 

 

* 실험해보기 : IMU 센서 데이터를 저주파 통과 필터로 필터링

 

 

임베디드 공부하면서 하드웨어, 부트로더, 커널 이미지, 디바이스 드라이버 구현 등 정리

 

 

임베디드 정리.pdf
0.73MB

 

드디여 마지막 장인 동기화까지 왔다.

 

비선점형 멀티태스킹을 수행하는 나빌로스에서 커널 API Kernel_yield를 구현해서 

각 태스크가 다음 태스크에게 컨텍스트를 넘겨주었는데 싱글 코어는 괜찬지만 멀티 코어에서는 문제가 된다.

 

 

 

운영체제에서의 동기화와 아토믹 오퍼레이션

동기화 : 한 작업을 아토믹 오퍼레이션(atomic operation ?)으로 만드는 작업

아토믹 오퍼레이션 : 해당 작업이 끝날때까지 컨텍스트 스위칭이 발생하지 않음

-> 멀티코어 환경에선 아토믹 오퍼레이션이 수행될때 컨텍스트 스위칭 발생 안함.

= 다른작업이 못끼어든다

= 다른 코어도 방해하지 못함

=> 원자가 더이상 쪼개질수없는 단위처럼, 더이상 쪼갤수 없는 동작이란 의미로 이에 해당되는 작업이 크리티컬 섹션임

 

동기화

- 작업, 태스크가 크리티컬 섹션(다른 작업, 코어가 끼어들지 못함)인 경우, 아토믹 오퍼레이션으로 만듬

- 구현 알고리즘 : 세마포어, 뮤텍스, 스핀락

 

 

세마포어

아래는 세마포어 알고리즘의 슈도코드, 60년대 초에 나온 오래된 알고리즘

Test() : 크리티컬 섹션에 진입 가능한지 시험함 = 세마포어를 잠글수 있는지 확인

Release() : 크리티컬 섹션을 나갈 때 호출. 세마포어를 풀어줌

 

=> 크리티컬 섹션에 들어가면 잠금. 

=> 잠긴 동안 컨텍스트 스위칭이나 다른 코어 작업 불가

=> 크리티컬 섹션에서 나오면 잠금 해제

Test(S)
{
	while S <= 0; //대기
    S--;
}

Release(S)
{
	S++;
}

 

세마포어 구현하기

kernel/synch.h

#ifndef KERNEL_SYNCH_H_
#define KERNEL_SYNCH_H_

#include <stdint.h>

void	Kernel_sem_init(int32_t max);
bool	Kernel_sem_test(void);
void	Kernel_sem_release(void);

#endif

 

kernel/synch.c

sem test는 못잠글때 ( S<=0) false 반환 

sem release는 세마포어 변수 증가

 

sem init은 세마포어 초기화 함수 max 파라미터로 세마포어 최대값 받음. 8 못넘김

max가 1이면 크리티컬 섹션에 컨텍스트 한개만 진입 가능(바이너리 세마포어)

2면 2개, 3은 3까지 최대가 8이므로 8개 컨텍스트 진입 가능

0보다 작으면 8개 진입 가능

 

#define DEF_SEM_MAX 8

#include "synch.h"

static int32_t sSemMax;
static int32_t sSem;

void Kernel_sem_init(int32_t max)
{
	sSemMax = (max <= 0) ? DEF_SEM_MAX : max;
	sSemMax = (max >= DEF_SEM_MAX) ? DEF_SEM_MAX : max;

	sSem = sSemMax;
}

bool Kernel_sem_test(void)
{
	if (sSem <= 0)
	{
		return false;
	}
	
	sSem--;

	return true;
}

void Kernel_sem_release(void)
{
	sSem++;

	if(sSem >= sSemMax)
	{
		sSem = sSemMax;
	}
}

 

세마포어용 커널 API 추가

kernel.h

#ifndef KERNEL_KERNEL_H_
#define KERNEL_KERNEL_H

#include "task.h"
#include "event.h"

void Kernel_start(void);
void Kernel_yield(void);

void Kernel_send_events(uint32_t event_list);
KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list);

void Kernel_lock_sem(void);
void Kernel_unlock_sem(void);

#endif

kernel.c

Kernel lock sem은 무한루프로 대기하는걸 Kernel_yield로 다음 태스크에게 전환하는걸로 구현

->  크리티컬 섹션 잠금을 가진 태스크의 컨텍스트까지가서 넘어가서야 세마포어 잠금을 풀수있도록 하기위함

 

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

#include "memio.h"

#include "Kernel.h"
#include "event.h"
#include "msg.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(KernelMsgQ_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;
}

void Kernel_lock_sem(void)
{
	while(false == Kernel_sem_test())
	{
		Kernel_yield();
	}
}

void Kernel_unlock_sem(void)
{
	Kernel_sem_release();
}

 

 

리얼뷰 피비는 싱글코어 에뮬레이터이며, 비선점형 스케줄러라 직접 Kernel_yield 호출 필요 동작가능한 코드 구현하면

 

Uart.c x키 눌럿을떄 CmdOut 이벤트 발생하도록 수정시키고

#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();

	if (ch != 'x')
	{
		Hal_uart_put_char(ch);
		Kernel_send_msg(KernelMsgQ_Task0, &ch, 1);
		Kernel_send_events(KernelEventFlag_UartIn);
	}
	else
	{
		Kernel_send_events(KernelEventFlag_CmdOut);
	}
}

 

Test_critical_section()

크리티컬 섹션을 만들기위해 여러 코어가 같이 쓰는 공유 자원 필요

첫 인자  uint32_t p는 공유 자원 값을 바꾸기위한 값

다음은 호출한 태스크 번호

어느태스크가 어떤 값을 보냈는지 출력

공유 변수 값을 바꾼후 다음 테스크로 스캐줄링

 

#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 "synch.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);

static void Test_critical_section(uint32_t p, uint32_t taskId);

void User_task0(void);
void User_task1(void);
void User_task2(void);

static uint32_t shared_value;

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:
				Test_critical_section(5, 0);
				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();
	}
}

static void Test_critical_section(uint32_t p, uint32_t taskId)
{
	debug_printf("User Task #%u Send=%u\n", taskId, p);
	shared_value = p;
	Kernel_yield();
	delay(1000);
	debug_printf("User Task #%u Shared Value = %u\n", taskId, shared_value);
}

 

테스트 크리티컬 섹션 함수은

유아트 컨트롤러에서 x 입력시 cmdout 이벤트를 발생시켜

태스크 1이 받아 호출된다.

여기서 인자를 5, 0(태스크 번호)를 주어

공유된 변수 값을 덮어쓰고 5가 출력되어 정상적으로 동작했다.

 

 

 

하지만 동시에

태스크 2에서 공유 변수값을 3으로 바꾸도록 하면

void User_task2(void)
{
	uint32_t local = 0;

	debug_printf("User Task #2 SP = 0x%x\n", &local);

	while(true)
	{
		Test_critical_section(3, 2);
		Kernel_yield();
	}
}

 

 

태스크2의 차례때 테스트 크리티칼 섹션 함수가 계속 수행되어

3을 보내고 3을 받는 식이 되지만

중간에 태스크 0이 끼어들어

정작 태스크0이 보낸건 5지만, 커널 양보 호출후 공유된 변수 값을 출력할때는 3이 나오고만다.

이런식으로 같은 자원을 여러 태스크끼리 공유할때 문제가 발생

 

 

거의 다왔는데 메시지 부터 꼬인걸 도저히 해결이 안된다.

일단 세마포어와 뮤텍스가 뭐하는건지 봣으니 여기까지 그냥 마무리

 

 

 

이벤트의 한계

- 지난 글에서 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();
	}
}

 

 

 

처음에는 원래 코드도 안돌아갔는데

어째저째 고치다보니 지금 잘 동작하니 

여기서 너무 시간 지체해서 넘어가야겠다.

 

 

모터 정역 제어

- 정역 제어 = 정방향(CW clock wise) + 역방향 제어(CCW counter CW)

- 전원 방향에 따라 정방향 역방향으로 회전한다 

  but) MCU IO 포트는 출력 전류가 약해 전류를 충분히 공급하지 못한다.

 => 전류 증폭이 필요. 트랜지스터?

 

DC 모터 구동

- TR 구동 : 이미터 부하, 컬랙터 부하 -> 컬랙터 부하를 주로 사용

 

간단한 모터 제어 회로

- NPN TR과 이미터 방향에 모터보호저항 단다.(과전류 보호)

갑자기 끄면 큰 역기전력이 발생-> TR 파괴

 

 

 

역기전력으로부터 모터 보호

- 환류 다이오드 사용

- 역기전력이 생겼을때 빼주기 : 저항이 작아진 소자 반대방향

환류 다이오드(free wheeling diode) 필요

but 여전히 한쪽방향만 돈다

 

 

 

모터 양방향 제어를 위한 H 브리지 제어 회로

- TR 하나로는 전류가 부족하다

-> TR을 4개 해서 전류도 증폭하고 양방향 제어가능한 회로

 

 

트랜지스터를 이용한 AND 게이트

 

 

 

L298

DUAL FULL-BRIDGE DRIVER : L298 하나로 모터 2개 제어가능

 

 

 

 

enable 핀을 통해 출력

-> enable off 시 마찰력에 의해 천천히 모터 멈춤

급정거 원할시 H 브리지에 1 1 인가

 

L298N 듀얼 풀 브리지 드라이버 모듈

 

 

 

L297N를 이용한 양방향 DC 모터 제어

 

 

L298N 드라이버 블록 다이어그램 이해하기

1. 방향 제어

 

2. Enable 신호 H일때 동작, L 되면 모터는 관성으로 정지

 

3. 속도 제어

 

 

시뮬레이션 예제

/*
	project1 : 가변 저항을 이용한 DC 모터 속도 제어
	가변저항 - A0
	L298 IN1 - ard 8
	L298 IN2 - ard 7
	L298 ENA - ard 6 980hz
*/


const uint8_t PIN_IN1 = 8;
const uint8_t PIN_IN2 = 7;
const uint8_t PIN_ENA = 6;
const uint8_t analog_pin = A0;


int analog_value;
int pwm_value;

void setup () {
	Serial.begin(9600);
	pinMode(PIN_IN1, OUTPUT);
	pinMode(PIN_IN2, OUTPUT);
	pinMode(PIN_ENA, OUTPUT);

}

void loop() {
	analog_value = analogRead(analog_pin);
	pwm_value = map(analog_value, 0, 1024, 0, 255);

	digitalWrite(PIN_IN1, HIGH);
	digitalWrite(PIN_IN2, LOW);
	analogWrite(PIN_ENA, pwm_value);
	Serial.print("pwm value : ");
	Serial.println(pwm_value);
}

 

 

 

 

 

 

 

 

 

맨 처음에 event.h를 만들때

예약된 자리가 여러개 있었는데,

원래 책에서는 처음 event.h 만들면서 UartIN 플래그 이름만 붙여있었지만

나는 이 헤더파일을 깃헙 완성본에서 복붙하다보니까 이미 사용자 정의 이벤트 이름들이 다 붙어있다.

두번째 이벤트가 CmdIn인데 이걸로 이벤트 처리를 하도록 메인함수를 수정하자.

 

 

boot/Main.c

태스크 0은 유아트 입력 이벤트 처리 시에 커널 API로 커맨드 입력 이벤트를 보내는걸 추가하고

태스크 1은 커맨드 입력 이벤트를 읽어들이면 태스크 1이 이벤트 핸들링했다고 출력해주는 내용이다.

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

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.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 < 5 ; 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);

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

void User_task1(void)
{
	uint32_t local = 0;

	debug_printf("User 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();
		delay(100);
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #2 SP = 0x%x\n", &local);
		Kernel_yield();
		delay(100);
	}
}

 

 

 

태스크들이 동작중에 유아트 입력 인터럽트가 들어오면

인터럽트 핸들러가 처리 후. 이벤트를 보낸다.

 

태스크 0번 동작 때 유아트 입력 이벤트를 확인하고 이벤트 핸들링 한 후에 커맨드 입력 이벤트를 보낸다.

 

태스크 1번 동작 차례가되면 커맨드 입력 이벤트가 보내졌지 확인해 처리하면서

아래의 결과가 나온다.

 

 

여러 이벤트 플래그 동시 보내고 처리

 

이전 글에서 kernel/Kernel.c에 Kernel_send 웨이팅 이벤트 함수는

uint32_t 이벤트리스트를 인자로 전달하는데,

그냥 이벤트 플래그가 아니라 uint32_t인 이유는 or연산으로 여러 이벤트를 같이 보내도록 하기 위해서였다고 했다.

 

UART 입력 인터럽트 발생 시 유아트 입력과 커맨드 입력 이벤트

+ 입력된 값이 'x'인 경우 CmdOut 이벤트를 보내도록 인터럽트 핸들러를 수정하면

 

hal/rvpb/Uart.c

#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

#include "Kernel.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|KernelEventFlag_CmdIn);

	if (ch == 'x')
	{
		Kernel_send_events(KernelEventFlag_CmdOut);
	}
}

 

 

 

태스크 0은 유아트 입력, cmd out 이벤트 처리

태스크 1은 그대로 커맨드 입력 이벤츠 처리 하도록 된 메인함수 코드

 

boot/Main.c

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

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.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 < 5 ; 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);

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn | KernelEventFlag_CmdOut);
		switch(handle_event)
		{
			case KernelEventFlag_UartIn:
				debug_printf("\nEvent handled by Task#0\n");
				Kernel_send_events(KernelEventFlag_CmdIn);
				break;
			case KernelEventFlag_CmdOut:
				debug_printf("\nCmdOut Event by Task0\n");
				break;
		}
		Kernel_yield();
		delay(100);
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	debug_printf("User 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();
		delay(100);
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #2 SP = 0x%x\n", &local);
		Kernel_yield();
		delay(100);
	}
}

 

 

키를 입력하면 기존 대로 동작하다가

x를 입력시에는

기존 대로

event handled task 0

event handled task 1 

출력 후

 

sEventFlag에 남아있던 CmdOut 플래그를 태스크 0번때 읽어들여

cmdout event by task0이 출력된다.

 

 

 

이벤트

- 인터럽트와 태스크간 연결 매체

- 버튼 클릭 -> 전기 신호 흐름 -> 컨트롤러가 인식 -> 소프트웨어 인터럽트 처리(IRQ, FIQ 발생) -> IRQ FIQ 핸들러가 인터럽트 확인 -> 해당 인터럽트 핸들러가 처리

- GUI의 경우 : 마우스 클릭(인터럽트 발생) -> 닷넷 or x윈도우가 이벤트로 만들어 핸들러 함수에 전달

 * RTOS의 경우 커널이 인터럽트를 이벤트로 바꿔 전달 이벤트 핸들러가 처리

 

 

이벤트 플래그

- 임의로 정한 갑하는 값

- 아래 이벤트 플래그를 16진수로 표현 시 0x8000 0002 -> 31번, 2번 이벤트 발생

 

이벤트 처리 구현

kernel/event.h

 

이벤트 플래그 선언. 유아트 입력, 커맨드 입력, 출력, 잠금해제 이벤트 플래그 존재하고

나머진 미정

#ifndef KERNEL_EVENT_H_
#define KERNEL_EVENT_H_

#include <stdbool.h>

typedef enum KernelEventFlag_t
{
    KernelEventFlag_UartIn      = 0x00000001,
    KernelEventFlag_CmdIn       = 0x00000002,
    KernelEventFlag_CmdOut      = 0x00000004,
    KernelEventFlag_Unlock      = 0x00000008,
    KernelEventFlag_Reserved04  = 0x00000010,
    KernelEventFlag_Reserved05  = 0x00000020,
    KernelEventFlag_Reserved06  = 0x00000040,
    KernelEventFlag_Reserved07  = 0x00000080,
    KernelEventFlag_Reserved08  = 0x00000100,
    KernelEventFlag_Reserved09  = 0x00000200,
    KernelEventFlag_Reserved10  = 0x00000400,
    KernelEventFlag_Reserved11  = 0x00000800,
    KernelEventFlag_Reserved12  = 0x00001000,
    KernelEventFlag_Reserved13  = 0x00002000,
    KernelEventFlag_Reserved14  = 0x00004000,
    KernelEventFlag_Reserved15  = 0x00008000,
    KernelEventFlag_Reserved16  = 0x00010000,
    KernelEventFlag_Reserved17  = 0x00020000,
    KernelEventFlag_Reserved18  = 0x00040000,
    KernelEventFlag_Reserved19  = 0x00080000,
    KernelEventFlag_Reserved20  = 0x00100000,
    KernelEventFlag_Reserved21  = 0x00200000,
    KernelEventFlag_Reserved22  = 0x00400000,
    KernelEventFlag_Reserved23  = 0x00800000,
    KernelEventFlag_Reserved24  = 0x01000000,
    KernelEventFlag_Reserved25  = 0x02000000,
    KernelEventFlag_Reserved26  = 0x04000000,
    KernelEventFlag_Reserved27  = 0x08000000,
    KernelEventFlag_Reserved28  = 0x10000000,
    KernelEventFlag_Reserved29  = 0x20000000,
    KernelEventFlag_Reserved30  = 0x40000000,
	KernelEventFlag_Reserved31  = 0x80000000,

    KernelEventFlag_Empty       = 0x00000000,
} KernelEventFlag_t;

void Kernel_event_flag_init(void);
void Kernel_event_flag_set(KernelEventFlag_t event);
void Kernel_event_flag_clear(KernelEventFlag_t event);
bool Kernel_event_flag_check(KernelEventFlag_t event);

#endif /* KERNEL_EVENT_H_ */

 

 

kernel/event.c

 

sEventflag  이벤트 플래그 32개 기록해서 보관 + 태스크에 전달하기 위한 변수

flag_clear 특정 플래그 반전 함수

flag_check 해당 이벤트가 1인지 체크. 1이면 호출한 함수에게 대기중임을 알려주고 플래그 클리어

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

#include "stdio.h"
#include "event.h"

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);
}

bool Kernel_event_flag_check(KernelEventFlag_t event)
{
	if (sEventFlag & (uint32_t) event)
	{
		Kernel_event_flag_clear(event);
		return true;
	}
	return false;
}

 

 

커널 API로 이벤트 처리하기위해 kernel/Kernel.c, .h에 함수 추가

#ifndef KERNEL_KERNEL_H_
#define KERNEL_KERNEL_H

#include "task.h"
#include "event.h"

void Kernel_start(void);
void Kernel_yield(void);

void Kernel_send_events(uint32_t event_list);
KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list);

#endif

 

 

kernel/Kernel.c

 

Kernel_send_events

이벤트를 전달하기 위한 함수로 uint32_t로 이벤트들의 목록을 받아

  * 1100 0000 0000 0000, 0010 0000 0000 0000과 같이 이벤트가 하나 혹은 여러개가 발생한 경우를 매개변수로 받는듯

비트 연산자로 해당 플래그가 올라왔는지 확인

플래그가 올라온 이벤트가 있으면 uint32_t 이벤트 목록을 KernelEventFlag_t인 sending_event에다 담아서(SET_BIT) 

Kernel_event_flag_set으로 sEventFlag에 등록

* sEventFlag에 이벤트리스트를 등록함으로서 sending을 하나 어떻게 받아서 처리하는지는 don't care

 

 

Kernel_wait_events 

위와 비슷하게 대기할 이벤트 목록을 받아서

올라온 이벤트 플래그가 있으면 내리고, 내린 대기 이벤트 플래그 반환

waiting list에 올라온 플래그가 없으면 엠프티 반환

 

uint32_t 로 전달하는건 event1 | event2 .. 와같여 어러 이벤트를 or 연산해서 한번에 보내기 위함.

#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;
}

 

 

 

인터럽트로 이벤트 발생시키기

QEMU에는 인터럽트가 별로 없으며, 타이머는 자주 발생해 Uart 만 사용

 

 

Uart 입력 인터럽트 발생 시 유아트 입력 이벤트 발생 시키기

interrupt_handler에서 Kernel_send_events에다가 enum인 UartIn을 전달하여 인터럽트 처리 후 커널에 이벤트 전달

#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

#include "Kernel.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);
}

 

 

 

boot/Main.c에

이벤트 플래그 초기화와

User Task0에 유아트 입력 이벤트 처리 코드 구현

 

Kernel_wait_event(KernelEventFlag_UartIn) 으로

태스크 0 동작때마다 유아트 입력이 들어왔는지 확인. 확인 후

sEventFlag에서 유아트인 플래그는 off되고 handle_event에 일단 유아트 이벤트 플래그 등록

스위칭을 통해 처리할 이벤트 핸들 이벤트가 유아트인 이면 이벤트 처리됨 문자열 출력

 

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

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.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 < 5 ; 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);

	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();
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #1 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #2 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

 

딜레이를 안줬더니 너무 빨리 지나가서

위 코드의 각 태스크에 delay(100) 추가해서 돌리니

잘 나온다. User task #0은 루프 밖이라 한번 출력하고 마니 안나오는게 당연하고

 

 

 

 

스케줄링 = 스케줄러 + 컨텍스트 스위칭

라운드 로빈 스케줄러와 컨텍스트 스위칭 구현했으니 스케줄링 시점 결정이 필요

 

시분할 시스템과 선점형 비선점형 멀티테스킹

- 시분할 시스템 : 정기적인 타이머 인터럽트로 태스크를 일정 시간만 동작하고 다음으로 바꾸도록 스케줄링하는 운영체제

- 선점형 멀티 태스킹 : 태스크가 스케줄링 요청안해도 커널이 강제로 스케줄링

 -> 시분할 시스템은 선점형 멀티태스킹에 가까움.

- 비선점형 멀티태스킹 : 태스크가 스케줄링 요청안하면 커널이 스케줄링 안함

- 선점형/ 비선점형 결정 : 임베디드 시스템 요구 사항에 달림.

* 나빌로스는 비선점형 스케줄링 = 태스크가 커널에 스케줄링 요청 필요

  = >현재 태스크가 다음 태스크에게 자원을 양보(yield) 하도록 구현하자!

 

 

yield(양보) 구현하기

- 태스크보다는 커널 영역 -> kernel/Kernel.c, kernel/Kernel.h 구현

Kernel_yield 호출 즉시 kernel_task_scheduler(task.c) 함수로 스케줄러에서 다음에 동작할 태스크 선정

-> 호출한 태스크 컨텍스트 백업

-> 스케줄러가 선정한 태스크 스택 포인터 복구

-> 태스크 스택 포인터를 이용해 컨택스트 복구

-> Kernel_yield()가 마치면서 다음 태스크 동작

kernel/task.c

 

kernel/Kernel.h

#ifndef KERNEL_KERNEL_H_
#define KERNEL_KERNEL_H

#include "task.h"

void Kernel_yield(void);

#endif

kernel/Kernel.c 

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

#include "Kernel.h"

void Kernel_yield(void)
{
	Kernel_task_scheduler();
}

 

 

이전 코드는 커널 시작 시 동작 태스크가 없어서 동작하지 않음.

 

컨텍스트 스위칭 과정 정리

- 호출 과정 : Restore_context<-Save_context <- kernel_task_context_switching <- kernel_task_scheduler <- kernel_yield

- Saved_context 함수에서 1. 현재 태스크 컨택스트를 스택에 백업  2. TCB에 SP 백업

- Restore_context 함수에서 1. 다음 TCB에서 SP 가져옴 2. SP에서 컨택스트 읽음

<-> 커널 시작시에는 동작 태스크가 없어 저장도 못하고, 복원할거도 없다!

=> 해결책 : 첫 스케줄링 시 컨텍스트 백업(Saved_context) 없이, 0번 TCB을 복구하자(=TCB 정적 전역변수 초기값 사용)

 

 

 

kernel/task.c

추가 사항

1. static uint32_t sCurrent_tcb_index;

2. kernel_task_int() - sCurrent_tcb_index = 0;

3. Kernel_Task_start() 함수 

 

Kernel_task_start 커널 시작 시 한번만 호출

- 0번 tcb 주소 가져와 - restore_context

-> 기존 태스크 컨택스트는 사라지고 다음 태스크 컨택스트를 가져옴.

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

#include "ARMv7AR.h"
#include "task.h"

//task
static KernelTcb_t	sTask_list[MAX_TASK_NUM];
static uint32_t		sAllocated_tcb_index;
static uint32_t		sCurrent_tcb_index;

//scheduling
static uint32_t		sCurrent_tcb_index;
static KernelTcb_t*	Scheduler_round_robin_algorithm(void);

//context switching
static KernelTcb_t*	sCurrent_tcb;
static KernelTcb_t*	sNext_tcb;

void Kernel_task_init(void)
{
	sAllocated_tcb_index = 0;
	sCurrent_tcb_index = 0;

	for(uint32_t i = 0; i < MAX_TASK_NUM ; i++)
	{
		sTask_list[i].stack_base = (uint8_t*)(TASK_STACK_START + (i * USR_TASK_STACK_SIZE));
		sTask_list[i].sp = (uint32_t)sTask_list[i].stack_base + USR_TASK_STACK_SIZE - 4;

		sTask_list[i].sp -= sizeof(KernelTaskContext_t);
		KernelTaskContext_t* ctx = (KernelTaskContext_t*)sTask_list[i].sp;
		ctx->pc = 0;
		ctx->spsr = ARM_MODE_BIT_SYS;
	}
}

uint32_t Kernel_task_create(KernelTaskFunc_t startFunc)
{
	KernelTcb_t* new_tcb = &sTask_list[sAllocated_tcb_index++];

	if(sAllocated_tcb_index > MAX_TASK_NUM)
	{
		return NOT_ENOUGH_TASK_NUM;
	}

	KernelTaskContext_t* ctx = (KernelTaskContext_t*)new_tcb->sp;
	ctx -> pc = (uint32_t)startFunc;

	return (sAllocated_tcb_index - 1);
}

static KernelTcb_t*	Scheduler_round_robin_algorithm(void)
{
	sCurrent_tcb_index++;
	sCurrent_tcb_index %= sAllocated_tcb_index;

	return &sTask_list[sCurrent_tcb_index];
}

void Kernel_task_scheduler(void)
{
	sCurrent_tcb = &sTask_list[sCurrent_tcb_index];
	sNext_tcb = Scheduler_round_robin_algorithm();

	Kernel_task_context_switching();
}

__attribute__ ((naked)) void Kernel_task_context_switching(void)
{
	__asm__ ("B Save_context");
	__asm__ ("B Restore_context");
}

static __attribute__ ((naked)) void Saved_context(void)
{
	//save current task context into the current task stack
	__asm__ ("PUSH {lr}");
	__asm__ ("PUSH {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
	__asm__ ("MRS r0, cpsr");
	__asm__ ("PUSH {r0}");
	//save current task stack pointer into the current TCB
	__asm__ ("LDR r0, =sCurrent_tcb");
	__asm__ ("LDR r0, [r0]");
	__asm__ ("STMIA r0!, {sp}");
}

static __attribute__ ((naked)) void Restore_context(void)
{
	//restore next task stack pointer from the next TCB
	__asm__ ("LDR	r0, =sNext_tcb");
    __asm__ ("LDR	r0, [r0]");
    __asm__ ("LDMIA	r0!, {sp}");
    // restore next task context from the next task stack
    __asm__ ("POP {r0}");
    __asm__ ("MSR cpsr, r0");
    __asm__ ("POP {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
    __asm__ ("POP {pc}");
}

void Kernel_task_start(void)
{
	sNext_tcb = &sTask_list[sCurrent_tcb_index];
	Restore_context();
}

 

 

메인 함수에서 커널 관련 초기화 함수를 모아서 한번에 쓸수 있도록

task.c/.h의 Kernel_task_start() 함수를

Kernel.c/.hdp 커널 API인  Kernel_start() 구현해서 호출

 

kernel/Kernel.h

#ifndef KERNEL_KERNEL_H_
#define KERNEL_KERNEL_H

#include "task.h"

void Kernel_start(void);
void Kernel_yield(void);

#endif

kernel/Kernel.c

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

#include "Kernel.h"

void Kernel_start(void)
{
	Kernel_task_start();
}

void Kernel_yield(void)
{
	Kernel_task_scheduler();
}

 

Main 함수 수정 : 첫 태스크 컨택스트를 복원해서 실행 추가

 

Kernel_init() 함수 맨 끝에 Kernel_start() 추가

Kernel_init() 정리

1. Kernel_task_int()으로 64개의 태스크 스택 배이스, SP 등록, 컨텍스트 영역 추가

2. Kernel_task_create를 세번 호출

- Kernel_task_create 동작

    1. 호출할 함수 매개변수로 받음

    2. 현재 tcb가져옴 + sAllocated_tcb_index를 1 추가(다음 tcb로 넘어가도록)

    3. tcb의 스택포인터를 가져온다 = 현재 스택 포인태 위에는 컨텍스트가 존재

    4. 컨택스트의 pc에 인자로 전달받은 함수 저장 = 이 함수 실행

    5. 현재 tcb 인덱스 반환

3. Kernel_start() 호출

    1. sCurrent_tcb_index는 0번이므로 맨 처음 만든 tcb 주소 가져옴

    2. 가져온 tcb의 컨택스트를 로드

    3. tcb로 로드한 컨택스트에 pc에 등록된 함수(=태스크 생성시 등록한 함수) 호출

 

주의 : 아직 각 태스크 등록 함수는 Kernel_yield 호출 하지 않았다! = 다음 태스크에게 양보안한다

 

 

boot/Main.c

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

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.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();
}

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)
{
	while(true)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_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)
{
	debug_printf("User Task#0\n");

	while(true);
}

void User_task1(void)
{
	debug_printf("User Task#1\n");

	while(true);
}

void User_task2(void)
{
	debug_printf("User Task#2\n");

	while(true);
}

 

 

스택의 스택 포인터를 가져와서 볼수 있도록

지역변수 선언

해당 지역변수의 주소= 스택포인터를 확인할수 있도록 debug_printf 와

다음 태스크에게 넘겨줄수 있도록 kernel_yield() 추가.

 

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

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.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 < 5 ; 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();

	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;

	while(true)
	{
		debug_printf("User Task #0 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #1 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #2 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

 

kernel 폴더와 관련 파일들이 추가됬으니 makefile도 수정하자

 

ARCH=armv7-a
MCPU=cortex-a8

TARGET=rvpb

CC=arm-none-eabi-gcc
AS=arm-none-eabi-as
LD=arm-none-eabi-gcc
OC=arm-none-eabi-objcopy

LINKER_SCRIPT=./navilos.ld
MAP_FILE=build/navilos.map

ASM_SRCS=$(wildcard boot/*.S)
ASM_OBJS=$(patsubst boot/%.S, build/%.os, $(ASM_SRCS))

VPATH = boot			\
		hal/$(TARGET)	\
		kernel			\
		lib

C_SRCS	=	$(notdir $(wildcard boot/*.c))
C_SRCS	+=	$(notdir $(wildcard hal/$(TARGET)/*.c))
C_SRCS	+=	$(notdir $(wildcard lib/*.c))
C_OBJS	=	$(patsubst %.c, build/%.o, $(C_SRCS))

INC_DIRS	=	-I include			\
				-I hal				\
				-I hal/$(TARGET)	\
				-I kernel			\
				-I lib

CFLAGS	= -c -g -std=c11

LDFLAGS	= -nostartfiles -nostdlib -nodefaultlibs -static -lgcc

navilos=build/navilos.axf
navilos_bin=build/navilos.bin

.PHONY: all clean run debug gdb

all:$(navilos)

clean:
	@rm -fr build

run: $(navilos)
	qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -nographic

debug: $(navilos)
	qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -S -gdb tcp::1234,ipv4

gdb:
	gdb-multiarch

$(navilos):$(ASM_OBJS) $(C_OBJS) $(LINKER_SCRIPT)
	$(LD) -n -T $(LINKER_SCRIPT) -o $(navilos) $(ASM_OBJS) $(C_OBJS) -Wl,-Map=$(MAP_FILE) $(LDFLAGS)
	$(OC) -O binary $(navilos) $(navilos_bin)

build/%.os: %.S
	mkdir -p $(shell dirname $@)
	$(CC) -mcpu=$(MCPU) $(INC_DIRS) $(CFLAGS) -o $@ $<

build/%.o: %.c
	mkdir -p $(shell dirname $@)
	$(CC) -mcpu=$(MCPU) $(INC_DIRS) $(CFLAGS) -o $@ $<

 

이전에 문법 에러한것도 다 고치고,

책에 빠진 부분이 있어서 로직이 제대로 돌아가지 않은 부분이있었다.

깃헙 메인함수 참고해서 수정하고 빌드 후 돌리면

 

테스트 마친 후 테스크 0, 1, 2, 3이 서로 돌아가면서

커널 생성 때 등록한 함수 User_task0, 1, 2를 호출하면서

각자의 스택포인터를 출력하는걸 확인할수 있다.

 

 

컨텍스트 스위칭

다른 태스크, 프로그램으로 전환하기 위해 기존 정보 컨택스트를 다른대다 백업하고 

실행할 태스크의 컨택스트를 로드하는 동작

 

과정

1. 현재 태스크 컨텍스트를 스택에 백업

2. 실행할 태스크 컨트롤 블록을 스캐줄러로 전달받음

3. 전달받은 태스크 컨트롤 블록에서 스택 포인터를 읽고

3. 읽은 스택포인터로부터 컨텍스트를 읽어 ARM 코어에 복구

4. 실행할 태스크 직전 실행 위치로 이동

 

kernel/task.c

위 과정을 하도록 Kernel_task_scheduler 함수를 구현했는데

정작 중요한 Kernel_task_context_switching() 내용이 없어 구현해야한다.

 

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

#include "ARMv7AR"
#include "task.h"

//task
static KernelTcb_t	sTask_list[MAX_TASK_NUM];
static uint32_t		sAllocated_tcb_index;

//scheduling
static uint32_t		sCurrent_tcb_index;
static KernelTcb_t*	Scheduler_round_robin_algorithm(void);

//context switching
static KernelTcb_t*	sCurrent_tcb;
static KernelTcb_t*	sNext_tcb;

void Kernel_task_init(void)
{
	sAllocated_tcb_index = 0;

	for(uint32_t i = 0; i < MAX_TASK_NUM ; i++)
	{
		sTask_list[i].stack_base = (uint8_t*)(TASK_STACK_START + (i * USR_TASK_STACK_SIZE));
		sTask_list[i].sp = (uint32_t)sTask_list[i].stack_base + USR_TASK_STACK_SIZE - 4;

		sTask_list[i].sp -= sizeof(KernelTaskContext_t);
		KernelTaskContext_t* ctx = (KernelTaskContext_t*)sTask_list[i].sp;
		ctx->pc = 0;
		ctx->spsr = ARM_MOD_BIT_SYS;
	}
}

uint32_t Kernel_task_create(KernelTaskFunc_t startFunc)
{
	KernelTcb_t* new_tcb = &sTask_list[sAllocated_tcb_index++];

	if(sAllocated_tcb_index > MAX_TASK_NUM)
	{
		return NOT_ENOUGH_TASK_NUM;
	}

	KernelTaskContext_t* ctx = (KernelTaskContext_t*)new_tcb->sp;
	ctx -> pc = (uint32_t)startFunc;

	return (sAllocated_tcb_index - 1);
}

static KernelTcb_t*	Scheduler_round_robin_algorithm(void)
{
	sCurrent_tcb_index++;
	sCurrent_tcb_index %= sAllocated_tcb_index;

	return &sTask_list[sCurrent_tcb_index];
}

void Kernel_task_scheduler(void)
{
	sCurrent_tcb = &sTask_list[sCurrent_tcb_index];
	sNext_tcb = Scheduler_round_robin_algorithm();

	Kernel_task_context_switching();
}

 

 

그런데 컨텍스트 스위칭 함수 구현 부분은

책에 설명이 제대로 되있지 않아 깃헙 참고해서 구현하면...

 

lib/(아키텍처)/switch.h/.c 이름으로 있긴한데

현재 아키텍처 고려해서 하는건 아니기도하고

뒤에 트리를보면 별도 switch 파일을 만드는게 없어서 그냥 task.c에다가 그대로 구현하야겠다.

 

위 코드의 kernel_task_scheduler 아래에 

kernel_task_context_switching 함수를 구현하면서

__attribute__ ((naked))로 어트리뷰트를 네이크드 설정 시 컴파일러가

자동으로 스택 백업, 복구, 리턴 관련 어셈블리어를 생성하지 않는다고 한다.

=> 역 어샘블시

<Save_context>

<Restore_context>

만 나온다.

 

ARM의 인스트럭션 B를 쓴건 LR(링크 레지스터 : 함수 호출전 리턴 주소 보관)을 바꾸지 않기 위함인데

naked로 스택 확보해서 뭐 하지 않았기 때문이라 한다.

void Kernel_task_scheduler(void)
{
	sCurrent_tcb = &sTask_list[sCurrent_tcb_index];
	sNext_tcb = Scheduler_round_robin_algorithm();

	Kernel_task_context_switching();
}

__attribute__ ((naked)) void Kernel_task_context_switching(void)
{
	__asm__ ("B Save_context");
	__asm__ ("B Restore_context");
}

 

컨텍스트  스위칭 과정 보기

 

컨텍스트 스위칭 과정을 정리해서 보면 

task1 이 동작하다가 전환 시 

task1 context를 저장하고

변경된 sp를 task1 tcb에 저장.

그 다음 task2 tcb의 sp를 읽고

해당 sp로부터 task2 context를 복구

task2 작업 스택 원래 sp로 돌아와 수행

 

 

 

 

태스크 초기화 및 생성

 

커널 tcb를 sTask_list[MAX_TASK_NUM] 그러니까 64개 배열 형태의 전역 변수로 만들었으니 

global variable 영역에 있을 것이고 ..

태스크 스택은 1MB 씩 task stack 영역에 있으니까 맞겟지..?

 

태스크 컨텍스트 저장 하기

 

이제 컨텍스트 백업을 위한 코드와 복구를 위한 코드를 구현해야하는데.

 

태스크 컨텍스트의 메모리 주소는 spsr, 레지스터, pc가 작은데서 큰 순이지만

스택은 주소가 큰곳에서 작은곳으로 가서 

가장 마지막에 담아야할 pc를 스택의 가장 먼저

중간인 r0_r12은 pc 다음

가장 마지막에 읽어야할 spsr를 어샘블리어 상에서 가장 먼저 저장시켜야한다.

 

 

아까 쓴 kernel_task_context_switching에 이어서

Saved_context를 구현하면

 

먼저 LR을 스택에 푸시해서 태스크 컨택스트 pc 변수 저장(복귀때 리턴 주소)

그 다음 r0, r1 ... r12를 스택에 저장

프로그램 상태 레지스터 psr을 바로 직접 메모리에 저장불가해 r0에 전달했다가 푸시

 

그 뒤 tcb의 포인터 변수를 로드 LDR r0, =sCurrent_tcb

LDR r0, [r0] 로 r0에 저장된 tcb 포인터의 주소에서 실제 값 tcb 포인터를 가져온 뒤

tcb 포인터 주소를 sp에 저장

 

이 과정을 c언어로 쓰면 => sCurrent_tcp->sp = ARM_코어_SP_레지스터값

 

__attribute__ ((naked)) void Kernel_task_context_switching(void)
{
	__asm__ ("B Save_context");
	__asm__ ("B Restore_context");
}

static __attribute__ ((naked)) void Saved_context(void)
{
	//save current task context into the current task stack
	__asm__ ("PUSH {lr}");
	__asm__ ("PUSH {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
	__asm__ ("MRS r0, cpsr");
	__asm__ ("PUSH {r0}");
	//save current task stack pointer into the current TCB
	__asm__ ("LDR r0, =sCurrent_tcb");
	__asm__ ("LDR r0, [r0]");
	__asm__ ("STMIA r0!, {sp}");
}

 

-------------------------

컨텍스트 복구하기

 

이 글을 일요일에 쓰고, 화목은 조별 과제 준비하느라 어떻게 했는지 잘 생각안나는데

이번에는 컨텍스트 복구 과정을 그림을 따라보면

1. 다음 TCB SP읽어서 현재 ARM SP에 쓰기

2. 현재 ARM SP로부터 태스크 컨택스트 읽기 - cpsr -> r0_r12 -> pc 순

3. 태스크 2의 작업스택으로 돌아와 동작 수행한다

naked를 오랜만에 보는대

스택 복구, 리턴 같은 코드를 생성하지 않도록 컴파일러에 전달하는 명령어였던거같다.

attribute는 컴파일러 확장기능을 쓰도록 하는 명령어이고

 

static __attribute__ ((naked)) void Restore_context(void)
{
	//restore next task stack pointer from the next TCB
	__asm__ ("LDR	r0, =sNext_tcb");
    __asm__ ("LDR	r0, [r0]");
    __asm__ ("LDMIA	r0!, {sp}");
    // restore next task context from the next task stack
    __asm__ ("POP {r0}");
    __asm__ ("MSR cpsr, r0");
    __asm__ ("POP {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
    __asm__ ("POP {pc}");
}

 

+ Recent posts