iFBalance-코딩으로 뽀샤버리기

2강. Color LED 제어하기 - 2부 ( Free RTOS 기반 )

Posted by INDIFROG on 2021-03-02
Words 1.2k and Reading Time 7 Minutes
Viewed Times

2강. Color LED 제어하기 - 2부 ( Free RTOS 기반 )

IFZero보드는 ESP32 MCU 중에서 4M Byte 플래쉬 메모리 등이 모듈화된 ESP32 PICO V3 이라고 하는 가장 최신의 MCU를 사용하고 있습니다. 이전 ESP32 PICO D4 MCU의 일부 보안 이슈를 해결한 버전입니다.

VSCODE_LIB

ESP32는 Core가 2개이며 Core0은 통신 코어라고 불리기도 하고 WiFi나 BT관련 통신 프로토콜을 제어하는 역할을 하며 Core1은 애플리케이션 코어라고 하며 유저 코드를 실행하는 역할을 담당합니다.

기본적으로 ESP32는 FreeRTOS로 동작하는 MCU입니다.

VSCODE_LIB

따라서 내부 스케줄러 덕분에 Task를 병렬로 실행 즉, 멀티 태스킹으로 동작합니다. 스케줄러가 Task들을 빠르게 전환하기 때문에 여러 응용 프로그램들이 동시에 실행되는 것처럼 보이는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
#include "Arduino.h"

TaskHandle_t loopTaskHandle = NULL;

#if CONFIG_AUTOSTART_ARDUINO

bool loopTaskWDTEnabled;

void loopTask(void *pvParameters)
{
setup();
for(;;) {
if(loopTaskWDTEnabled){
esp_task_wdt_reset();
}
loop();
}
}

extern "C" void app_main()
{
loopTaskWDTEnabled = false;
initArduino();
xTaskCreateUniversal(loopTask, "loopTask", 8192,
NULL, 1, &loopTaskHandle, CONFIG_ARDUINO_RUNNING_CORE);
}

#endif

위 내용은 ESP32의 main.cpp 코드입니다.

main 함수는 기본적으로 xTaskCreateUniversal 함수 호출하여 Core1에서 loopTask 함수를 실행하도록 되어 있습니다. 실제로 ESP32 xTaskCreateUniversal 함수 내부에서는 CPU Core1에서 실행되도록 지정하기 위해 xTaskCreate 함수가 아닌 xTaskCreatePinnedToCore 함수를 호출합니다.

xTaskCreate 함수를 호출하게 되면 무조건 Core0에서 태스크가 실행이 되기 때문입니다.

따라서 초심자들이나 일부 경험이 있는 개발자들도 Task로 코드를 분리 실행하면서 간간히 알수 없는 버그에 시달리게 되는데 많은 원인들 중에서 가장 중요한 것 중에 하나가 바로 xTaskCreate 함수를 사용하여 태스크를 구동하는 문제일 수 있습니다.

서로 다른 Core의 Task에서 실행되는 코드가 하나의 물리적인 port에 동시에 엑세스하는 문제이거나 인터럽트 핸들러 처리의 충돌로 인한 문제일 수 있기 때문입니다.

따라서 실제 아두이노 코드에서는 CPU Core1에서 실행되도록 xTaskCreatePinnedToCore 함수를 사용하여 Task를 실행해야 합니다.

위 loopTask 함수 내부를 자세히 살펴 보면 이곳에서 Arduino의 기본 함수인 setup()함수와 loop() 함수가 호출되는 구조임을 알 수 있습니다.

Free RTOS 기반 COLOR LED 제어 소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69


/******************************************************************************
iFLED.h - iFLine, iFBalance Arduino Library
hkkim@koreaits.com ( KITS Lab )
original creation date: April 3, 2020

Development environment specifics:
Arduino IDE 1.8.x
iFZero, iFLine, iFBalance
******************************************************************************/

#ifndef iFLED_v1_h
#define iFLED_v1_h
#define iFLED_LIBRARY_VERSION 1.1.1

#include <Arduino.h>
#include <NeoPixelBus.h>

typedef NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod> NeoPixelBusType;

#define COLOR_BLACK 1
#define COLOR_BLUE 2
#define COLOR_RED 3
#define COLOR_GREEN 4
#define COLOR_ORANGE 5
#define COLOR_WHITE 6

#define NUM_OF_LEDS 4
#define MAX_SATURATION 128


typedef enum {
ALL_OFF,
RAINBOW,
BLINK,
RANDOM,
CUSTOM,
} iFLED_MODE;

class iFLED {

private:
static NeoPixelBusType& _strip;
static NeoPixelBusType& _stripB;

static bool _isBlink;
static uint8_t _mode;
static uint32_t _counter;

static TaskHandle_t taskHandle;
public:
static SemaphoreHandle_t xMutex;

public:
iFLED();

static void init();
static void taskColorLED(void *pvParameter);

static void setColor(uint8_t pos, uint8_t color );

static void setAllBlack();
static void setMode(uint8_t mode);


};

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/******************************************************************************
iFLED.cpp - iFLine, iFBalance Arduino Library
hkkim@koreaits.com ( KITS Lab )
original creation date: April 3, 2020

Development environment specifics:
Arduino IDE 1.8.x
iFZero, iFLine, iFBalance
******************************************************************************/

#include <Arduino.h>
#include "iFLED.h"

NeoPixelBusType strip(NUM_OF_LEDS, GPIO_NUM_2 );
NeoPixelBusType& iFLED::_strip = strip;//

bool iFLED::_isBlink = 0;
uint8_t iFLED::_mode = ALL_OFF;
uint32_t iFLED::_counter = 0;

TaskHandle_t iFLED::taskHandle = NULL;
SemaphoreHandle_t iFLED::xMutex = NULL;
portTickType delayTasks = 1000;

RgbColor RED(MAX_SATURATION, 0, 0);
RgbColor GREEN(0, MAX_SATURATION, 0);
RgbColor BLUE(0, 0, MAX_SATURATION);
RgbColor WHITE(MAX_SATURATION);
RgbColor BLACK(0);

iFLED::iFLED(){

}

void iFLED::setAllBlack()
{
for (int i = 0; i < NUM_OF_LEDS; i++)
_strip.SetPixelColor(i, BLACK);
_strip.Show();
}


void iFLED::taskColorLED(void *pvParameter) {

while(1){
if (xSemaphoreTake( xMutex, portMAX_DELAY ) == pdTRUE){

if (_mode == RAINBOW ) {
strip.SetPixelColor(_counter % 4, RED);
strip.SetPixelColor((_counter + 1) % 4, BLUE);
strip.SetPixelColor((_counter + 2) % 4, GREEN);
strip.SetPixelColor((_counter + 3) % 4, WHITE);
strip.Show();
++_counter;
}
else if (_mode == BLINK) {
for (float i = 0; i < 1; i += 0.01f) {
strip.SetPixelColor(_counter % 4, RED.LinearBlend(RED, BLACK, i));
strip.SetPixelColor((_counter + 1) % 4, BLUE.LinearBlend(BLUE, BLACK, i));
strip.SetPixelColor((_counter + 2) % 4, GREEN.LinearBlend(GREEN, BLACK, i));
strip.SetPixelColor((_counter + 3) % 4, WHITE.LinearBlend(WHITE, BLACK, i));
strip.Show();
delay(10);
}
++_counter;
delay(500);
}
else if (_mode == RANDOM) {
RgbColor arr[] = {RED, GREEN, BLUE, BLACK};
int r = rand();
uint8_t i = r & 0xFF;
uint8_t j = (r >> 8) & 0xFF;
float k = (r % 100) * 0.01f;
strip.SetPixelColor(i & 0x3, RED.LinearBlend(arr[(j & 0x3)], arr[(j & 0xC)], k));
strip.SetPixelColor(i & 0xC, BLUE.LinearBlend(arr[(j & 0xC)], arr[(j & 0x30)], k));
strip.SetPixelColor(i & 0x30, GREEN.LinearBlend(arr[(j & 0x30)], arr[(j & 0xC0)], k));
strip.SetPixelColor(i & 0xC0, WHITE.LinearBlend(arr[(j & 0xC0)], arr[(j & 0x3)], k));
strip.Show();
delay(50);
setAllBlack();
delay(10);
}
else {
setAllBlack();
}
}
xSemaphoreGive( xMutex );

vTaskDelay( 1000 );
}
}



void iFLED::init(){
////////////////////////////////////////////////////////////////
//
_strip.Begin();
for (int i = 0; i < NUM_OF_LEDS; i++)
strip.SetPixelColor(i, BLACK);
strip.Show(); // Send the updated pixel colors to the hardware.

xMutex = xSemaphoreCreateMutex();
xTaskCreatePinnedToCore(&taskColorLED,
"taskColorLED",
1024,
(void*)&delayTasks,
uxTaskPriorityGet(NULL),
&taskHandle,
1);
}

void iFLED::setColor(uint8_t pos, uint8_t color ){

switch( color ){
case 1: _strip.SetPixelColor(pos, BLACK); break;
case 2: _strip.SetPixelColor(pos, BLUE); break;
case 3: _strip.SetPixelColor(pos, RED); break;
case 4: _strip.SetPixelColor(pos, GREEN); break;
case 5: _strip.SetPixelColor(pos, WHITE); break;
}
_strip.Show();

}

void iFLED::setMode(uint8_t mode)
{
if (xSemaphoreTake( xMutex, portMAX_DELAY ) == pdTRUE){
switch (mode) {
case RAINBOW:
case BLINK:
_counter = 0;
break;
}
_mode = mode;
}
xSemaphoreGive( xMutex );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/******************************************************************************
main.cpp - iFLine, iFBalance Arduino Library
hkkim@koreaits.com ( KITS Lab )
original creation date: April 3, 2020

Development environment specifics:
Arduino IDE 1.8.x
iFZero, iFLine, iFBalance
******************************************************************************/

#include <Arduino.h>
#include "iFLED.h"

iFLED ifLed;

void setup()
{
Serial.begin(115200);

///////////////////////////////////////////////////////
// Color LED
ifLed.init();
ifLed.setMode( BLINK );

}

void loop() {

}

VSCODE_LIB

위 코드는 COLOR LED를 4가지 구동 방식으로 동작시킵니다. setMode 멤버 함수를 통해 동작 모드를 선택할 수 있습니다.

일반적인 코드와는 다르게 특이하게도 static 맴버 변수 및 멤버 함수를 사용하는 것을 볼 수 있을 겁니다. 그 이유는 Task나 인터럽트 핸들러 등의 함수들은 main()함수가 실행되기 전 초기화되어 전역으로 엑세스가 가능해야 합니다.
따라서 C언어에서 처럼 전역함수로 그냥 선언해서 사용해도 되지만, C++ 형식으로 관리하기 위해 클래스 내부에서 이들 변수 및 함수들을 static으로 선언하는 것입니다.
중요한 점은 static 함수에서 사용되는 모든 멤버 변수들은 역시 static으로 관리해야 합니다.

제가 iF시리즈에서 가능하면 모든 코드를 C++ 클래스로 관리하는 이유는 코드를 간결하게 유지할 수 있을 뿐만 아니라 클래스 객체가 자신의 동작 컨셉에 따라 스스로 동작하도록 하게 하는데 목적이 있습니다.

이 장에서는 하기와 같이 사용되는 Free RTOS 함수들의 사용법을 이해하는 것이 가장 중요합니다.

  • xTaskCreatePinnedToCore
  • uxTaskPriorityGet
  • xSemaphoreCreateMutex
  • xSemaphoreTake
  • xSemaphoreGive

간혹, 왜 복잡한 Free RTOS를 사용하나요? 하는 질문을 받게 됩니다. 좋은 질문이죠. 왜 일까요?
그 이유는 다음과 같은 목적을 이루고자 할 때 입니다.

예를들어 보면, 기존 1강의 코드에서 처럼 loop()함수에서 LED를 위와 같이 여러 가지동작 방식으로 구동하면서 동시에 모터 속도 조절을 원하는 목적으로 동작시키고자 한다면 가능할까요?
물론, 가능할 수 있습니다. 그러나 코드가 복잡해지면서 여러가지 예외처리를 해야 하겠죠.

그러나 Free RTOS를 사용한다면, 클래스 객체 자신에게 주어진 시간에 해당 목적에만 충실하게 동작하도록 설계하면 간단하게 구현할 수 있습니다.

이번 강좌에서는 iFBalance의 Color LED를 Free RTOS에서 동작시키는 방법을 공부했습니다.