#include <Arduino_FreeRTOS.h>
#include <event_groups.h>
// Digital IO ピンの定義
#define CAR_BLUE 7
#define CAR_YELLOW 8
#define CAR_RED 9
#define PED_BLUE 10 //Pedestrian、歩行者
#define PED_RED 11
#define ALL 100 //@どのような意味か?
#define COUNT_MAX 10000 // 10秒、タイマータスクの最大カウンタ値
#define BLINK_COUNT 9 // 歩行者用信号の点滅回数
// 各種待ち時間(*500msec:タイマータスク周期)
#define C_INIT_TO_BLUE_WAIT_TIME 2
#define C_BLUE_TO_YELLOW_WAIT_TIME 3
#define C_YELLOW_TO_RED_WAIT_TIME 2
#define P_BLINK_PERIOD 1
#define P_RED_TO_BLUE_TIME 3
#define P_BLINK_TO_RED_WAIT_TIME 3
// 周期時間定義(msec)※Timerタスク周期は他タスクの倍以上とすること(他タスクでTimerを監視しているため)
#define MAIN_TASK_PERIOD 100
#define PED_TASK_PERIOD 100
#define SERIAL_TASK_PERIOD 100
#define TIMER_TASK_PERIOD 500
// 車道・歩行者信号同期用WaitFlag
#define P_INIT_WAIT_FLAG 1 //進数で 0001 として表
#define P_BLUE_WAIT_FLAG (1 << 1) //ビットシフト演算 (1 << 1) は 1 を1ビット左にシフトし、2進数で 0010 となります。
#define P_RED_WAIT_FLAG (1 << 2)
#define C_BLUE_WAIT_FLAG (1 << 3)
// 信号状態定義(後日精査)
enum CAR_STATE { //列挙型の定数
C_INIT,
C_INIT_TO_BLUE,
C_RED,
C_BLUE,
C_BLUE_TO_YELLOW,
C_YELLOW,
C_YELLOW_TO_RED,
C_IGNORE //C_IGNORE が設定されることにより、信号機制御システムは新たな状態変更の要求を無視し、現在の状態を維持することが指示されます。
};
enum PED_STATE {
P_INIT,
P_RED,
P_RED_TO_BLUE,
P_BLUE,
P_BLINK,
P_BLINK_TO_RED
};
// タスクハンドラー定義
TaskHandle_t Main_Task_Handler;
TaskHandle_t Serial_Task_Handler;
TaskHandle_t Timer_Task_Handler;
TaskHandle_t Ped_Task_Handler;
// イベントグループ(フラグ)定義
EventGroupHandle_t eventF = NULL;
// 状態変数定義
static CAR_STATE car_state; //列挙型で定義した定数を入れるために、変数car_stateを宣言
static PED_STATE ped_state;
// 要求変数定義
CAR_STATE car_req_state;
PED_STATE ped_req_state;
/*タイマータスクは、システムの基本的なタイムカウントを管理し、一定の周期でタイムカウンター curr_time を更新します。
このカウンターは、他のタスクが時間依存の動作を行う際の基準として使用されます。
更新は周期的に行われ、カウンターが一定の最大値(COUNT_MAX)に達した後、自動的に0にリセットされるように設計されています。
*/
int curr_time; // タイマータスクの現在カウント
//関数のプロトタイプ宣言
void MainTask(void *p);
void SerialTask(void *pValue);
void TimerTask(void *pValue);
void PedTask(void *pValue);
void task_timer_set(int *timer, int times);
void C_LED_Ctrl(char x);
void require_set(char read);
void setup() {
xTaskCreate( //新しいタスクをシステムに追加し、それをスケジューラの管理下に置く
MainTask, //タスク関数
"MainTask", //タスク名
128, //スタックサイズ、特定の関数やタスクのために予約されるメモリの量
(void *)"Main Task is running !", //引数
2, //優先度
&Main_Task_Handler //タスクハンドル
);
xTaskCreate(SerialTask, "SerialTask", 128, (void *)"Serial Task is running !", 1, &Serial_Task_Handler);
xTaskCreate(TimerTask, "TimerTask", 128, (void *)"Timer Task is running !", 1, &Timer_Task_Handler);
xTaskCreate(PedTask, "PedTask", 128, (void *)"Pedestrian Task is running !", 1, &Ped_Task_Handler);
pinMode(CAR_BLUE, OUTPUT);
pinMode(CAR_YELLOW, OUTPUT);
pinMode(CAR_RED, OUTPUT);
pinMode(PED_BLUE, OUTPUT);
pinMode(PED_RED, OUTPUT);
/*xEventGroupCreate() 関数
新しいイベントグループを作成し、その操作が成功するとイベントグループを識別するためのハンドルを返します。
このハンドルは、イベントグループに対する後続の操作(イベントの設定、クリア、待機など)に使用されます。
*/
eventF = xEventGroupCreate();
/*Arduinoのシリアルライブラリに含まれるメソッド
シリアルポートを初期化し、データの送受信を始める準備を整える。
115200 は関数に渡される引数で、シリアル通信のボーレートを指定しています。
ボーレートは、シリアル通信で1秒間に送信可能なビット数を表し、この数値が高いほど高速にデータを送受信できます。
*/
Serial.begin(115200);
Serial.println("start"); //Arduinoのシリアルライブラリに属する関数。この関数は指定されたデータをシリアルポートに送信する。
car_state = C_INIT;
ped_state = P_INIT;
/*タスクスケジューラーを起動するための関数呼び出しです。
RTOS内で定義されたタスクを動作させるためには、このスケジューラーがアクティブでなければなりません。
スケジューラーはタスクの管理と実行の順序を制御し、システムの全体的なマルチタスキング機能を担います。
*/
vTaskStartScheduler();
}
void loop() {
// put your main code here, to run repeatedly:
}
void MainTask(void *pValue) {
static int car_wait_timer;
/*TickType_t: これはFreeRTOSで定義されているデータ型で、OSのタイムティックをカウントするための型です。
具体的には、システムが起動してからのタイムティック数を保持するのに使用され、通常はuint32_tまたはそれに類似する整数型にtypedefされています。
xLastWakeTime: この変数は、タスクが最後に実行を開始した時点のタイムティック値を保持します。
タスクの周期的な実行を管理する際に重要な役割を果たします。
*/
TickType_t xLastWakeTime;
/*TickType_t はFreeRTOSで定義されたデータ型で、タイムティックのカウントに使われる整数型です。
xDelayMain: この変数名は、メインタスクの実行周期をタイムティックで表した値を保持します。
pdMS_TO_TICKS(): このマクロ関数は引数としてミリ秒単位の時間を取り、それをシステムのタイムティック単位に変換します。
FreeRTOSではシステムのタイムティックレートが定義されており、このレートに基づいてミリ秒をタイムティックに変換する計算を行います。
*/
const TickType_t xDelayMain = pdMS_TO_TICKS(MAIN_TASK_PERIOD); //24行目 #define MAIN_TASK_PERIOD 100
//FreeRTOSの関数 xTaskGetTickCount() を使用して、システムが起動してからのタイムティックの総数を取得し、それを xLastWakeTime という変数に格納しています。
xLastWakeTime = xTaskGetTickCount();
car_wait_timer = 0;
while(1) {
switch(car_state) { //初回はvoid setup()にてcar_state = C_INIT;
case C_INIT:
if (car_req_state == C_BLUE) { //70行目 CAR_STATE car_req_state; 初回は0?
car_state = C_INIT_TO_BLUE;
}
break;
case C_INIT_TO_BLUE:
task_timer_set(&car_wait_timer, C_INIT_TO_BLUE_WAIT_TIME);
if (curr_time != car_wait_timer)
break;
// 指定時間経過後にだけ実行
xEventGroupSetBits(eventF, P_INIT_WAIT_FLAG); //車信号が青になる瞬間、歩行者待機フラグを立てる
car_state = C_BLUE;
C_LED_Ctrl('B');
car_wait_timer = 0;
break;
case C_BLUE:
if (car_req_state == C_YELLOW) {
car_state = C_BLUE_TO_YELLOW;
}
break;
case C_BLUE_TO_YELLOW:
task_timer_set(&car_wait_timer, C_BLUE_TO_YELLOW_WAIT_TIME);
if (curr_time != car_wait_timer)
break;
// 指定時間経過後にだけ実行
car_state = C_YELLOW_TO_RED;
car_wait_timer = 0;
C_LED_Ctrl('Y');
break;
case C_YELLOW_TO_RED:
task_timer_set(&car_wait_timer, C_YELLOW_TO_RED_WAIT_TIME);
if (curr_time != car_wait_timer)
break;
// 指定時間経過後にだけ実行
car_state = C_RED;
C_LED_Ctrl('R');
car_wait_timer = 0;
xEventGroupSetBits(eventF, P_BLUE_WAIT_FLAG);
break;
case C_RED:
if (car_req_state != C_BLUE)
break;
xEventGroupSetBits(eventF, P_RED_WAIT_FLAG);
xEventGroupWaitBits(eventF, C_BLUE_WAIT_FLAG, pdTRUE, pdFALSE, portMAX_DELAY);
car_state = C_BLUE;
C_LED_Ctrl('B');
car_wait_timer = 0;
break;
default:
break;
}
//C_IGNORE が設定されることにより、信号機制御システムは新たな状態変更の要求を無視し、現在の状態を維持することが指示されます。
car_req_state = C_IGNORE;
/*
vTaskDelayUntil(): この関数は、タスクを指定されたタイムティック数だけ遅延させ、最後に起動した時刻からの相対的な時間で次の起動をスケジュールします。
&xLastWakeTime: この引数は、TickType_t 型の変数 xLastWakeTime のアドレスを渡します。
xLastWakeTime はタスクが最後に起動した時点のタイムティックを保持しており、この値は関数内で更新されます。
xDelayMain: この引数は、タスクが次に実行されるまでの遅延期間をタイムティック単位で指定します。
この値は pdMS_TO_TICKS() 関数を使ってミリ秒からタイムティックに変換されたものです。
【引数1】TickType_t * const pxPreviousWakeTime:
この引数は、タスクが最後に実行された起動時刻を指すポインタです。この引数にはタスクが起動するたびに更新される変数のアドレスを渡します。
関数が呼ばれると、この変数は次の呼び出しの基準となる新しい起動時刻に更新されます。
この値を基に、次の実行がいつ行われるべきかが計算されます。
【引数2】const TickType_t xTimeIncrement:
この引数は、タスクが次に実行されるまでの遅延をタイムティック単位で指定します。
この値は、タスクがどのくらいの頻度で実行されるべきかを定義するもので、周期的なタスク実行には特に重要です。
*/
vTaskDelayUntil(&xLastWakeTime, xDelayMain); //24行目 #define MAIN_TASK_PERIOD 100の周期で繰り返している
}
}
void PedTask(void *pValue) {
static int ped_wait_timer;
static int blink = 0;
static int blink_count = 0;
TickType_t xLastWakeTime;
const TickType_t xDelayPed = pdMS_TO_TICKS(PED_TASK_PERIOD);
xLastWakeTime = xTaskGetTickCount();
ped_wait_timer = 0;
while(1) {
switch(ped_state) { //初回は 119行目ped_state = P_INIT;
case P_INIT:
xEventGroupWaitBits(
eventF, //待ち操作を行うイベントグループのハンドルです。イベントグループの識別子として機能します。
P_INIT_WAIT_FLAG, //関数が待つイベントビットを指定します。P_INIT_WAIT_FLAG がこの例で指定されており、このビットがセットされるのを関数は待ちます。
pdTRUE, //関数がリターンする時に待っていたビットをクリアするかどうかを決定します。pdTRUE が指定されている場合、関数がリターンする際にビットが自動的にクリアされます。
pdFALSE, //指定されたすべてのビットがセットされるのを待つか、いずれかのビットがセットされるのを待つかを決定します。pdFALSE が指定されているため、指定されたビットのどれか一つがセットされれば関数はリターンします。
portMAX_DELAY //この引数は、関数がビットのセットをどのくらいの時間待つかを指定します。portMAX_DELAY は無限に待つことを意味し、指定されたイベントが発生するまでタスクはブロックされ続けます。
);
ped_state = P_RED;
digitalWrite(PED_RED, HIGH);
digitalWrite(PED_BLUE, LOW);
ped_wait_timer = 0;
break;
case P_RED:
xEventGroupWaitBits(eventF, P_BLUE_WAIT_FLAG, pdTRUE, pdFALSE, portMAX_DELAY);
ped_state = P_RED_TO_BLUE;
break;
case P_RED_TO_BLUE:
task_timer_set(&ped_wait_timer, P_RED_TO_BLUE_TIME);
if (curr_time != ped_wait_timer)
break;
// 指定時間経過後にだけ実行
digitalWrite(PED_RED, LOW);
digitalWrite(PED_BLUE, HIGH);
ped_wait_timer = 0;
ped_state = P_BLUE;
break;
case P_BLUE:
xEventGroupWaitBits(eventF, P_RED_WAIT_FLAG, pdTRUE, pdFALSE, portMAX_DELAY);
ped_state = P_BLINK;
break;
case P_BLINK:
//ここで点滅制御
task_timer_set(&ped_wait_timer, P_BLINK_PERIOD);
if (curr_time != ped_wait_timer) {
break;
}
ped_wait_timer = 0;
blink ^= 1;
digitalWrite(PED_RED, LOW);
if (blink == 0)
digitalWrite(PED_BLUE, LOW);
else
digitalWrite(PED_BLUE, HIGH);
blink_count++;
if (blink_count > BLINK_COUNT) {
digitalWrite(PED_RED, HIGH);
digitalWrite(PED_BLUE, LOW);
ped_state = P_BLINK_TO_RED;
blink = blink_count = 0;
}
break;
case P_BLINK_TO_RED:
task_timer_set(&ped_wait_timer, P_BLINK_TO_RED_WAIT_TIME);
if (curr_time != ped_wait_timer)
break;
ped_wait_timer = 0;
ped_state = P_RED;
xEventGroupSetBits(eventF, C_BLUE_WAIT_FLAG);
break;
default:
break;
}
vTaskDelayUntil(&xLastWakeTime, xDelayPed);
}
}
void SerialTask(void *pValue) {
TickType_t xLastWakeTime;
const TickType_t xDelaySerial = pdMS_TO_TICKS(SERIAL_TASK_PERIOD);
char read; //シリアルから読み取ったデータを格納するための変数を宣言しています
xLastWakeTime = xTaskGetTickCount();
while(1) {
if (Serial.available()) { //シリアルバッファにデータがあるかどうかを確認します。
read = Serial.read(); //シリアルバッファから1バイトのデータを読み取り、read 変数に格納します。
require_set(read); //
}
vTaskDelayUntil(&xLastWakeTime, xDelaySerial);
}
}
/*
TimerTask 関数は、プログラム内で時間の進行を管理し、タイムカウントを一定間隔で更新する役割を果たしています。
このタスクは、他のタスクが時間に基づいて行動を調整するための基礎を提供します。
*/
void TimerTask(void *pValue) {
TickType_t xLastWakeTime;
const TickType_t xDelayTimer = pdMS_TO_TICKS(TIMER_TASK_PERIOD); //27行目 #define TIMER_TASK_PERIOD 500
curr_time = 0;
xLastWakeTime = xTaskGetTickCount();
while(1) {
//剰余を取ることでカウンターが最大値に達したら0にリセットされるようにします。
//これによりカウンターのオーバーフローを防ぎ、時間の追跡が継続的に行われます。
curr_time = (curr_time + 1) % COUNT_MAX;
vTaskDelayUntil(&xLastWakeTime, xDelayTimer);
}
}
void task_timer_set(int *timer, int times)
{
if (*timer == 0)
*timer = (curr_time + times) % COUNT_MAX; // 500msec * times 現在のタイム (curr_time) に所定の遅延時間 (times) を加算
}
void C_LED_Ctrl(char x)
{
switch(x) {
case 'R':
digitalWrite(CAR_RED, HIGH);
digitalWrite(CAR_BLUE, LOW);
digitalWrite(CAR_YELLOW, LOW);
break;
case 'B':
digitalWrite(CAR_RED, LOW);
digitalWrite(CAR_BLUE, HIGH);
digitalWrite(CAR_YELLOW, LOW);
break;
case 'Y':
digitalWrite(CAR_RED, LOW);
digitalWrite(CAR_BLUE, LOW);
digitalWrite(CAR_YELLOW, HIGH);
break;
deault:
break;
}
}
void require_set(char x)
{
switch(x) {
case 'B':
car_req_state = C_BLUE;
break;
case 'P':
car_req_state = C_YELLOW;
break;
deault:
break;
}
}