[Embedded Software] 경희대 임베디드 소프트웨어 과제7 - Inter-Task Communication in uC/OS-II

경희대학교 컴퓨터공학과 조진성 교수님의 ‘임베디드 소프트웨어’ 강의의 과제입니다.


개발 환경



uC/OS-II


C 언어 기반으로 작성된 마이크로프로세서를 위한 저비용 우선순위 기반 선점형 실시간 운영체제 커널 위키백과


uC/OS-II File Structure


Task state Diagram in uC/OS-II



실습 내용


온도 센서로부터 1초에 한번씩 데이터를 읽는 Temperature Task, Temperature Task로부터 온도를 전달받는 FndTask, 온도를 FND에 출력하는 FndDisplay Task를 두 가지 ITC 방법으로 구현

두 가지 ITC 방법 : Message Passing, Shared Memory

// lab8.c 코드
#include "includes.h"

#define F_CPU	16000000UL	// CPU frequency = 16 Mhz
#include <avr/io.h>	
#include <util/delay.h>

#define  TASK_STK_SIZE  OS_TASK_DEF_STK_SIZE            
#define  N_TASKS        3

#define	MBOX	0
#define	GVAR	1
#define	METHOD	MBOX

#define	DEBUG	1

OS_STK       TaskStk[N_TASKS][TASK_STK_SIZE];

// METHOD가 MBOX일 경우, Mbox를 통해 값을 교환하고
// MBOX가 아닐 경우, semaphore를 이용해 값을 교환한다.
#if METHOD == MBOX
OS_EVENT	  *Mbox;
#else
OS_EVENT	  *Sem;
volatile INT8U   TemperatureValue;
#endif

OS_EVENT	  *Mutex;
volatile INT8U	FndNum;

void  TemperatureTask(void *data);
void  FndTask(void *data);
void  FndDisplayTask(void *data);


int main (void)
{
  OSInit();

  OS_ENTER_CRITICAL();
  TCCR0=0x07;  
  TIMSK=_BV(TOIE0);                  
  TCNT0=256-(CPU_CLOCK_HZ/OS_TICKS_PER_SEC/ 1024 );   
  OS_EXIT_CRITICAL();

  OSTaskCreate(TemperatureTask, (void *)0, (void *)&TaskStk[0][TASK_STK_SIZE - 1], 1);
  OSTaskCreate(FndTask, (void *)0, (void *)&TaskStk[1][TASK_STK_SIZE - 1], 2);  
  OSTaskCreate(FndDisplayTask, (void *)0, (void *)&TaskStk[2][TASK_STK_SIZE - 1], 3);  
  
  OSStart();                         
  
  return 0;
}

void InitI2C()
{
    PORTD = 3;				// For Pull-up override value
    SFIOR &= ~(1 << PUD);		// PUD
    TWSR = 0;				// TWPS0 = 0, TWPS1 = 0
    TWBR = 32;				// for 100  K Hz bus clock
    TWCR = _BV(TWEA) | _BV(TWEN);	// TWEA = Ack pulse is generated
					// TWEN = TWI 동작을 가능하게 한다
}

int ReadTemperature(void)
{
	int value;

	OS_ENTER_CRITICAL();

	TWCR = _BV(TWSTA) | _BV(TWINT) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	TWDR = 0x98 + 1; //TEMP_I2C_ADDR + 1
	TWCR = _BV(TWINT) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
	while(!(TWCR & _BV(TWINT)));

	//온도센서는 16bit 기준으로 값을 가져오므로
	//8비트씩 2번을 받아야 한다.
	value = TWDR << 8;
	TWCR = _BV(TWINT) | _BV(TWEN); 
	while(!(TWCR & _BV(TWINT)));

	value |= TWDR;
	TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTO);

	value >>= 8;
	
	TIMSK = (value >= 33) ? TIMSK | _BV(TOIE2): TIMSK & ~_BV(TOIE2);

	OS_EXIT_CRITICAL();
	
	return value;
}

void TemperatureTask (void *data)
{
  int	value;
  INT8U	err;

  
  data = data;                                   

#if METHOD == MBOX
  Mbox = OSMboxCreate(0);
#else
  Sem = OSSemCreate(0);
#endif

  Mutex = OSMutexCreate(0, &err);

  InitI2C();

  DDRA = 0xFF;
  while (1)  {
    value = ReadTemperature();
		
#if METHOD == MBOX
	OSMboxPost(Mbox, &value);
#else
	OSMutexPend(Mutex, 0, &err);
	TemperatureValue = value;
	OSMutexPost(Mutex);
	OSSemPost(Sem);
#endif

	OSTimeDlyHMSM(0, 0, 1, 0);
  }
}

void FndTask (void *data)
{
	INT8U	err;
	
	data = data;
 
	while (1)  {
#if METHOD == MBOX
		FndNum = *(int *)OSMboxPend(Mbox, 0, &err);
#else
		OSSemPend(Sem, 0, &err);
		OSMutexPend(Mutex, 0, &err);
		FndNum = TemperatureValue;
		OSMutexPost(Mutex);
#endif

#ifdef	DEBUG
		PORTA = FndNum;
#endif
	}
}

void FndDisplayTask (void *data)
{
    unsigned char FND_DATA[ ]= {
		0x3f, // 0
		0x06, // 1 
		0x5b, // 2 
		0x4f, // 3 
		0x66, // 4 
		0x6d, // 5 
		0x7d, // 6 
		0x27, // 7 
		0x7f, // 8 
		0x6f, // 9 
		0x77, // A 
		0x7c, // B 
		0x39, // C 
		0x5e, // D 
		0x79, // E 
		0x71, // F 
		0x80, // . 
		0x40, // - 
		0x08  // _
	};
    unsigned int num0, num1, num2, num3;
	INT8U	err;

    data = data;
	
    DDRC = 0xff;	
    DDRG = 0x0f;
	
    while(1)  {
		OSMutexPend(Mutex, 0, &err);
		num3 = (FndNum / 1000) % 10;
		num2 = (FndNum / 100) % 10;
		num1 = (FndNum / 10) % 10;
		num0 = FndNum % 10;
		OSMutexPost(Mutex);
		PORTC = FND_DATA[num3];
		PORTG = 0x08;
		_delay_ms(2);
		PORTC = FND_DATA[num2];
		PORTG = 0x04;
		_delay_ms(3);
		PORTC = FND_DATA[num1];
		PORTG = 0x02;
		_delay_ms(2);
		PORTC = FND_DATA[num0];
		PORTG = 0x01;
		_delay_ms(3);
    }
}


실습 응용 1


온도가 특정 값 이상이 되면 buzzer를 통해 싸이렌을 울림 추가

// lab8.c
#include "includes.h"

#define F_CPU	16000000UL	// CPU frequency = 16 Mhz
#include <avr/io.h>	
#include <util/delay.h>

#define  TASK_STK_SIZE  OS_TASK_DEF_STK_SIZE            
#define  N_TASKS        3

#define	MBOX	0
#define	GVAR	1
#define	METHOD	MBOX

#define	DEBUG	1

OS_STK       TaskStk[N_TASKS][TASK_STK_SIZE];

#if METHOD == MBOX
OS_EVENT	  *Mbox;
#else
OS_EVENT	  *Sem;
volatile INT8U   TemperatureValue;
#endif

OS_EVENT	  *Mutex;
volatile INT8U	FndNum;

void  TemperatureTask(void *data);
void  FndTask(void *data);
void  FndDisplayTask(void *data);


int main (void)
{
  OSInit();

  OS_ENTER_CRITICAL();
  TCCR0=0x07;  
  TIMSK=_BV(TOIE0);                  
  TCNT0=256-(CPU_CLOCK_HZ/OS_TICKS_PER_SEC/ 1024 );   
  OS_EXIT_CRITICAL();

  OSTaskCreate(TemperatureTask, (void *)0, (void *)&TaskStk[0][TASK_STK_SIZE - 1], 1);
  OSTaskCreate(FndTask, (void *)0, (void *)&TaskStk[1][TASK_STK_SIZE - 1], 2);  
  OSTaskCreate(FndDisplayTask, (void *)0, (void *)&TaskStk[2][TASK_STK_SIZE - 1], 3);  
  
  OSStart();                         
  
  return 0;
}

void InitI2C()
{
    PORTD = 3;				// For Pull-up override value
    SFIOR &= ~(1 << PUD);		// PUD
    TWSR = 0;				// TWPS0 = 0, TWPS1 = 0
    TWBR = 32;				// for 100  K Hz bus clock
    TWCR = _BV(TWEA) | _BV(TWEN);	// TWEA = Ack pulse is generated
					// TWEN = TWI 동작을 가능하게 한다
}

int ReadTemperature(void)
{
	int value;

	OS_ENTER_CRITICAL();

	TWCR = _BV(TWSTA) | _BV(TWINT) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	TWDR = 0x98 + 1; //TEMP_I2C_ADDR + 1
	TWCR = _BV(TWINT) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
	while(!(TWCR & _BV(TWINT)));

	//온도센서는 16bit 기준으로 값을 가져오므로
	//8비트씩 2번을 받아야 한다.
	value = TWDR << 8;
	TWCR = _BV(TWINT) | _BV(TWEN); 
	while(!(TWCR & _BV(TWINT)));

	value |= TWDR;
	TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTO);

	value >>= 8;
	
	TIMSK = (value >= 33) ? TIMSK | _BV(TOIE2): TIMSK & ~_BV(TOIE2);

	OS_EXIT_CRITICAL();
	
	return value;
}

void TemperatureTask (void *data)
{
  int	value;
  INT8U	err;

  
  data = data;                                   

#if METHOD == MBOX
  Mbox = OSMboxCreate(0);
#else
  Sem = OSSemCreate(0);
#endif

  Mutex = OSMutexCreate(0, &err);

  InitI2C();

  DDRA = 0xFF;
  while (1)  {
    value = ReadTemperature();
		
#if METHOD == MBOX
	OSMboxPost(Mbox, &value);
#else
	OSMutexPend(Mutex, 0, &err);
	TemperatureValue = value;
	OSMutexPost(Mutex);
	OSSemPost(Sem);
#endif
	
	// Buzzer는 포트 B에 연결되어 있다.
	DDRB = 0x10;
	int i;
	
	// 온도센서를 통해 측정한 값이 29도 이상이면
	// PORTB에 연결된 Buzzer를 On하여 싸이렌이 울린다.
	if(value >= 29){
		for(i = 0; i < 10; i++)
		{
			PORTB = 0x10;
			_delay_ms(1);
			PORTB = 0x00;
			_delay_ms(1);
		}
	}
	OSTimeDlyHMSM(0, 0, 1, 0);
  }
}

void FndTask (void *data)
{
	INT8U	err;
	
	data = data;
 
	while (1)  {
#if METHOD == MBOX
		FndNum = *(int *)OSMboxPend(Mbox, 0, &err);
#else
		OSSemPend(Sem, 0, &err);
		OSMutexPend(Mutex, 0, &err);
		FndNum = TemperatureValue;
		OSMutexPost(Mutex);
#endif

#ifdef	DEBUG
		PORTA = FndNum;
#endif
	}
}

void FndDisplayTask (void *data)
{
    unsigned char FND_DATA[ ]= {
		0x3f, // 0
		0x06, // 1 
		0x5b, // 2 
		0x4f, // 3 
		0x66, // 4 
		0x6d, // 5 
		0x7d, // 6 
		0x27, // 7 
		0x7f, // 8 
		0x6f, // 9 
		0x77, // A 
		0x7c, // B 
		0x39, // C 
		0x5e, // D 
		0x79, // E 
		0x71, // F 
		0x80, // . 
		0x40, // - 
		0x08  // _
	};
    unsigned int num0, num1, num2, num3;
	INT8U	err;

    data = data;
	
    DDRC = 0xff;	
    DDRG = 0x0f;
	
    while(1)  {
		OSMutexPend(Mutex, 0, &err);
		num3 = (FndNum / 1000) % 10;
		num2 = (FndNum / 100) % 10;
		num1 = (FndNum / 10) % 10;
		num0 = FndNum % 10;
		OSMutexPost(Mutex);
		PORTC = FND_DATA[num3];
		PORTG = 0x08;
		_delay_ms(2);
		PORTC = FND_DATA[num2];
		PORTG = 0x04;
		_delay_ms(3);
		PORTC = FND_DATA[num1];
		PORTG = 0x02;
		_delay_ms(2);
		PORTC = FND_DATA[num0];
		PORTG = 0x01;
		_delay_ms(3);
    }
}


실습 응용 2


실습 응용 1을 OS 없이 구현

// lab8.c
#define F_CPU 16000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile int FndNum;
volatile int TemperatureValue;

void InitI2C()
{
	PORTD = 3;
	SFIOR &= ~(1<<PUD);
	TWSR = 0;
	TWBR = 32;
	TWCR = _BV(TWEA) | _BV(TWEN);
}

int ReadTemperature(void)
{
	int value;

	TWCR = _BV(TWSTA) | _BV(TWINT) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	TWDR = 0x98 + 1; 
	TWCR = _BV(TWINT) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
	while(!(TWCR & _BV(TWINT)));

	value = TWDR << 8;
	TWCR = _BV(TWINT) | _BV(TWEN); 
	while(!(TWCR & _BV(TWINT)));

	value |= TWDR;
	TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTO);

	value >>= 8;
	
	TIMSK = (value >= 33) ? TIMSK | _BV(TOIE2): TIMSK & ~_BV(TOIE2);
	
	return value;
}

void TemperatureTask()
{
	int value;

	InitI2C();
	DDRA = 0xFF;
	value = ReadTemperature();
	TemperatureValue = value;
	DDRB = 0x10;
	int i;
	if(value >= 29) {
		for(i = 0; i<10; i++){
			PORTB = 0x10;
			_delay_ms(1);
			PORTB = 0x00;
			_delay_ms(1);
		}
	}
}

void FndTask()
{
	FndNum = TemperatureValue;
}

// 운영체제는 멀티태스크 환경에서 Waiting 상태의 태스크를 Ready 상태로
// Context Switching하며 스케줄링을 하고 태스크 간
// Critical Section의 동기화를 한다.
// 이러한 운영체제가 없기 때문에 main()에서 태스크의 순서를 맞춰야 하며,
// TemperatureTask -> FndTask + FndDisplayTask 순으로
// 호출하도록 하였다.
int main() {
	int num0, num1, num2, num3;

	static unsigned char NUM[ ]= {
		0x3f, // 0
		0x06, // 1 
		0x5b, // 2 
		0x4f, // 3 
		0x66, // 4 
		0x6d, // 5 
		0x7d, // 6 
		0x27, // 7 
		0x7f, // 8 
		0x6f, // 9 
		0x77, // A 
		0x7c, // B 
		0x39, // C 
		0x5e, // D 
		0x79, // E 
		0x71, // F 
		0x80, // . 
		0x40, // - 
		0x08  // _
	};

	DDRC = 0xff;
	DDRG = 0x0f;
	DDRA = 0xff;
	PORTA = 0x00;
	
	while(1) {
		TemperatureTask();
		FndTask();
		num3 = (FndNum / 1000) % 10;
		num2 = (FndNum / 100) % 10;
		num1 = (FndNum / 10) % 10;
		num0 = FndNum % 10;

		PORTC = NUM[num3];
		PORTG = 0x08;
		_delay_ms(2);

		PORTC = NUM[num2];
		PORTG = 0x04;
		_delay_ms(3);

		PORTC = NUM[num1];
		PORTG = 0x02;
		_delay_ms(2);

		PORTC = NUM[num0];
		PORTG = 0x01;
		_delay_ms(3);
	}
}


실습 응용 3


조도 센서로부터 1초에 한번씩 데이터를 읽는 LighTask 추가, FndDisplayTask는 Temperature sensor 값과 Light sensor 값을 1초마다 번갈아 출력하도록 추가

// lab8.c
#include "includes.h"

#define F_CPU	16000000UL	// CPU frequency = 16 Mhz
#include <avr/io.h>	
#include <util/delay.h>

#define  TASK_STK_SIZE  OS_TASK_DEF_STK_SIZE            
#define  N_TASKS        4

#define	MBOX	0
#define	GVAR	1
#define	METHOD	MBOX

#define	DEBUG	1

OS_STK       TaskStk[N_TASKS][TASK_STK_SIZE];

// TemperatureTask와 LightTask에서 사용할
// MobxTemper, MoxLight, SemTemper, SemLight 생성
#if METHOD == MBOX
OS_EVENT	  *MboxTemper;
OS_EVENT	  *MboxLight;
#else
OS_EVENT	  *SemTemper;
OS-EVENT	  *SemLight;
volatile INT8U   TemperatureValue;
volatile INT8U	 LightValue;
#endif

OS_EVENT	  *Mutex;
volatile INT8U	FndNum;

void  TemperatureTask(void *data);
void  FndTask(void *data);
void  FndDisplayTask(void *data);
void  LightTask(void *data);

int main (void)
{
  OSInit();

  OS_ENTER_CRITICAL();
  TCCR0=0x07;  
  TIMSK=_BV(TOIE0);                  
  TCNT0=256-(CPU_CLOCK_HZ/OS_TICKS_PER_SEC/ 1024 );   
  OS_EXIT_CRITICAL();

  OSTaskCreate(TemperatureTask, (void *)0, (void *)&TaskStk[0][TASK_STK_SIZE - 1], 1);
  OSTaskCreate(LightTask, (void *)0, (void *)&TaskStk[1][TASK_STK_SIZE - 1], 2);
  OSTaskCreate(FndTask, (void *)0, (void *)&TaskStk[2][TASK_STK_SIZE - 1], 3);  
  OSTaskCreate(FndDisplayTask, (void *)0, (void *)&TaskStk[3][TASK_STK_SIZE - 1], 4);  
  
  OSStart();                         
  
  return 0;
}

void InitI2C()
{
    PORTD = 3;				// For Pull-up override value
    SFIOR &= ~(1 << PUD);		// PUD
    TWSR = 0;				// TWPS0 = 0, TWPS1 = 0
    TWBR = 32;				// for 100  K Hz bus clock
    TWCR = _BV(TWEA) | _BV(TWEN);	// TWEA = Ack pulse is generated
					// TWEN = TWI 동작을 가능하게 한다
}

int ReadTemperature(void)
{
	int value;

	OS_ENTER_CRITICAL();

	TWCR = _BV(TWSTA) | _BV(TWINT) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	TWDR = 0x98 + 1; //TEMP_I2C_ADDR + 1
	TWCR = _BV(TWINT) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
	while(!(TWCR & _BV(TWINT)));

	//온도센서는 16bit 기준으로 값을 가져오므로
	//8비트씩 2번을 받아야 한다.
	value = TWDR << 8;
	TWCR = _BV(TWINT) | _BV(TWEN); 
	while(!(TWCR & _BV(TWINT)));

	value |= TWDR;
	TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTO);

	value >>= 8;
	
	TIMSK = (value >= 33) ? TIMSK | _BV(TOIE2): TIMSK & ~_BV(TOIE2);

	OS_EXIT_CRITICAL();
	
	return value;
}

void TemperatureTask (void *data)
{
  int	value;
  INT8U	err;

  data = data;                                   

#if METHOD == MBOX
  MboxTemper = OSMboxCreate(0);
#else
  SemTemper = OSSemCreate(0);
#endif

  Mutex = OSMutexCreate(0, &err);

  InitI2C();

  DDRA = 0xFF;
  while (1)  {
    value = ReadTemperature();
		
#if METHOD == MBOX
	OSMboxPost(MboxTemper, &value);
#else
	OSMutexPend(Mutex, 0, &err);
	TemperatureValue = value;
	OSMutexPost(Mutex);
	OSSemPost(SemTemper);
#endif
	DDRB = 0x10;
	int i;
	if(value >= 34){
		for(i = 0; i < 10; i++)
		{
			PORTB = 0x10;
			_delay_ms(1);
			PORTB = 0x00;
			_delay_ms(1);
		}
	}
	OSTimeDlyHMSM(0, 0, 1, 0);
  }
}

void FndTask (void *data)
{
	INT8U	err;
	
	data = data;
 
 	// TemperatureTask의 값을 받은 후,
 	// OSTimeDlyHMSM()을 통해 1초 딜레이를 발생하고
 	// LightTask의 값을 받도록 수행
	while (1)  {
#if METHOD == MBOX
		FndNum = *(int *)OSMboxPend(MboxTemper, 0, &err);
#else
		OSSemPend(SemTemper, 0, &err);
		OSMutexPend(Mutex, 0, &err);
		FndNum = TemperatureValue;
		OSMutexPost(Mutex);
		#endif
		OSTimeDlyHMSM(0, 0, 1, 0);

#if METHOD == MBOX
		FndNum = *(int *)OSMboxPend(MboxLight, 0, &err);
#else
		OSSemPend(SemLight, 0, &err);
		OSMutexPend(Mutex, 0, &err);
		FndNum = LightValue;
		OSMutexPost(Mutex);
#endif
		OSTimeDlyHMSM(0, 0, 1, 0);
#ifdef	DEBUG
		PORTA = FndNum;
#endif
	}
}

void FndDisplayTask (void *data)
{
    unsigned char FND_DATA[ ]= {
		0x3f, // 0
		0x06, // 1 
		0x5b, // 2 
		0x4f, // 3 
		0x66, // 4 
		0x6d, // 5 
		0x7d, // 6 
		0x27, // 7 
		0x7f, // 8 
		0x6f, // 9 
		0x77, // A 
		0x7c, // B 
		0x39, // C 
		0x5e, // D 
		0x79, // E 
		0x71, // F 
		0x80, // . 
		0x40, // - 
		0x08  // _
	};
    unsigned int num0, num1, num2, num3;
	INT8U	err;

    data = data;
	
    DDRC = 0xff;	
    DDRG = 0x0f;
	
    while(1)  {
		OSMutexPend(Mutex, 0, &err);
		num3 = (FndNum / 1000) % 10;
		num2 = (FndNum / 100) % 10;
		num1 = (FndNum / 10) % 10;
		num0 = FndNum % 10;
		OSMutexPost(Mutex);
		PORTC = FND_DATA[num3];
		PORTG = 0x08;
		_delay_ms(2);
		PORTC = FND_DATA[num2];
		PORTG = 0x04;
		_delay_ms(3);
		PORTC = FND_DATA[num1];
		PORTG = 0x02;
		_delay_ms(2);
		PORTC = FND_DATA[num0];
		PORTG = 0x01;
		_delay_ms(3);
    }
}


void init_adc()
{
	DDRF = 0x01;
	ADMUX = 0x00;
	ADCSRA = 0x87;
}


unsigned short read_adc()
{
	unsigned char adc_low, adc_high;
	unsigned short value;
	ADCSRA |= 0x40;
	while((ADCSRA & 0x10) != 0x10)
		;
	adc_low = ADCL;
	adc_high = ADCH;
	value = ((unsigned short)adc_high << 8) | (unsigned short)adc_low;
	return value;
}


void LightTask(void *data)
{
	data = data;
	unsigned short value;
	init_adc();
#if METHOD == MBOX
	MboxLight = OSMboxCreate(0);
#else
	SemLight = OSSemCreate(0);
#endif
	while(1)
	{
		value = read_adc();
		FndNum = value;
#if METHOD == MBOX
		OSMboxPost(MboxLight, &value);
#else
		OSMutexPend(Mutex, 0, &err);
		PrintValue = value;
		OSMutexPost(Mutex);
		OSSemPost(SemLight);
#endif
		OSTimeDlyHMSM(0, 0, 1, 0);
	}
}