レシピ言語ガイド

はじめに

本書では、KC4-C-100A/KC-4-C-101A(以下「本製品」)のレシピ言語のシステム概要とご利用方法を記載します。

レシピ言語の特徴

レシピ言語は「組み込みプログラムをより簡単に」をコンセプトにしたC言語に近いコード体系を持つ独自言語となります。言語としての一般的な機能に加えて外部機器やクラウドに接続し易いAPIを準備していますので、ブロックプログラミングでは制御できなかった複雑なロジックを組むことが可能となります。

開発環境の構築

レシピ言語でプログラミングを行うためには、レシピツール(KC4-C-Installer)のインストールが必要になります。開発環境のインストールに関しては「はじめよう」を参照してください。
本書では以下の環境がある前提で進めます。

レシピツールとSDK

レシピ言語の開発環境は構造が少し複雑になっています。ここではSDKを扱う上で必要なレシピツールとビルド環境の構造を説明します。

レシピツールのビルド環境

開発環境の構築としてインストールした、WSL2とレシピツールの関係は下図となります。
recipe_overall

[レシピツール]
インストールしたレシピツールはWSL2上のLinuxの中で動作しており、内部ではブロックプログラミング領域とレシピ言語領域に分かれています。それぞれから共通のコンパイラ/リンカを利用してレシピ実行ファイルを作る動きとなります。

[ブロックプログラミング領域]
ブロックプログラミング領域はWindows側からブラウザで操作するインターフェースとなり、ブロックプログラミング自体はWebアプリとして動作している為、内部で作られたレシピ実行ファイルはブラウザを経由してエクスポート(ダウンロード)されます。

[レシピ言語領域]
レシピ言語領域はWindows側からコマンドラインで操作するインターフェースとなります。レシピ言語で作成するソースファイルはWindows側に存在しますが、ビルドをする際にはSDKの中にあるバッチファイル(recipe_build.bat)にて、一度レシピ言語領域にコピーされます。コピーされたファイルを使用してレシピツール内のコンパイラ/リンカにてレシピ実行ファイルが生成された後、該当ファイルをWindows側にコピーを行う動きとなります。

[キッティングツール]
エクスポート/コピーされたレシピ実行ファイルはキッティングツールにて本機器に書込みを行います。ブロックプログラミングから生成したレシピ実行ファイルは「exe-file.zip」という名前となり、レシピ言語から生成したレシピ実行ファイルは「rcp_compress.zip」という名前になります。

SDK

レシピ言語のSDKはアプリケーション層のソースファイルを扱ったものとなります。ダウンロードしたSDKを展開したディレクトリ構成は以下の通りとなります。

sdk_structure

[レシピ言語フォルダ]
レシピ言語でレシピ実行ファイルを作成するメインのフォルダとなります。

ファイル名 説明
uapp_*.krs ユーザアプリケーション(ユーザアプリとも表現)。
レシピ作成はこれらファイルを編集します。
uapp_1~uapp_7のそれぞれが別なタスクとして稼働し、マルチタスクが可能です。
sapp_*.krs サブアプリケーション(サブアプリとも表現)。
uappにAPIを提供するタスクです。
sapp_1~sapp_4は独自のAPI提供の為にカスタマイズ可能です。led,oled,serial,vibrationはAPIを提供しているタスクです。
例)プロトコル機能をsappで組み、API化してuapp側から使用する。
sdev_*.krs デバイスドライバ。
サブアプリの中でも優先度の高い処理を行うタスクになります。
recipe_build.bat レシピ言語のビルドを行うバッチファイル。Linux上のレシピツールへ必要なファイルをコピー後にビルドをして実行ファイルを作成します
makefile コンパイル/リンクを定義するファイルです。
\inc インクルードファイルのディレクトリ。APIや設定値などが定義されているヘッダファイルが用意されています。
*.kra 提供するAPIのヘッダを生成する定義ファイル。recipe_api_build.batを用いてヘッダファイルを作成する元ファイル。

[サンプルコードフォルダ]
サンプルコードフォルダには、各機能のサンプルコードとAPIの使い方を示したドキュメントが配置されています。
sdk_sample

サンプルコードはそのまま使えるように構成されています。このため、フォルダ内にある「uapp_1.krs」を「レシピ言語フォルダ」にコピー/貼付け(上書き)してビルドを行うことで、サンプルコードを試す事が出来ます。
sdk_sample_copy

アプリケーションとタスク

レシピの作成にはファイルのuapp_1.krs~uapp_7.krsを編集していくことになります。これらファイルがアプリケーションやタスクの単位となります。ここではレシピを作成していく上でのアプリケーションやタスクの概念を説明していきます。

ユーザアプリとサブアプリ

SDK内のファイルは大きく分けて2つのアプリケーションに分かれます。
task_uappsapp

[ユーザーアプリケーション]
作りたいレシピのメインの処理を記述します。ユーザアプリはサブアプリやドライバ/システムから提供されるAPIを組み合わせて、作りたいレシピのロジックを構成します。ここでのアプリケーションの概念にはシステム的な縛りは無く、uapp_1~uapp_7の複数タスクを組み合わせて一つのアプリケーションとして構成することや、それぞれの機能ごとに7つのアプリケーションと考える事も出来ます。
[サブアプリケーション]
サブアプリはある程度まとまった処理を集めて構成しておき、その機能をAPIとして公開する場合に利用します。既にあるシリアルやLEDなどはハードウェア処理やプロトコルなどをまとめて、ユーザアプリにAPIを提供しているサブアプリとなります。
[サブデバイスドライバ]
サブデバイスドライバはサブアプリの中でも優先度の高い処理を行うタスクを定義しています。ここではsdev_sensorがあり高速でセンサーの取得処理を定義しています。

ここでのアプリケーションはコードを開示していますので、自由に変更することが出来ます。

タスクとイベント

[タスク]
レシピ言語のタスクとはReal Time OSのタスクと同義で、並列実行の単位です。レシピは複数のタスクの組み合わせから成り立っており、タスクはユーザーアプリだけではなく、APIを提供しているサブアプリもタスクとして動作しています。
レシピ言語でのタスクは、1タスク/1ファイルで構成されています。ユーザアプリとしてuapp_1.krs~uaap_7.krsの7つのファイルがありますので、ユーザアプリとしては7つのタスクが利用することできます。
task_explain1

[イベント]
タスク間の連携を行うためにイベントの仕組みがあります。イベントは送信したいタスクの番号にイベント番号を通知出来る機能に加えて、26byte分の引数をつけることが出来ます。サブアプリから提供しているAPIもイベントの仕組みを利用しており、ユーザアプリからコールしたAPIはイベントに変換されてサブアプリへ通知して処理されます。

task_explain2

イベントドリブン

レシピ言語のタスクはイベントドリブン方式で動作します。このため組み込み機器のプログラムであるmain()関数でループするようなシーケンス処理の書き方ではなく、イベント毎の処理にイベント分岐から飛ばしていく書き方になります。
task_eventdriven
イベントドリブンでは上図の様に処理毎にタイミングが違う場合などにコードが書きやすくなるメリットがあります。

タスクに通知されてくるイベントには他のタスクから通知されるイベントの他に、システムからのイベント通知があります。

[システムからのイベント]
SDKで提供されるユーザーアプリケーションファイルの雛形には、最初から以下のイベント通知の処理分岐が記載されています。

イベント 説明
タスク起動 最初の起動時に一度だけ呼ばれるイベント。
初期化処理に利用します。
強制停止/解除 ボタン4秒以上押下の電源OFF時に発行されるイベント
機能制限/解除 内部温度の上昇による機能制限時に発行されるイベント

実際のコードは以下のように配置されています。

#ifdef C_SYNTAX
void main_loop(){
    WAIT_REQUEST_EVENT_QUEUE
#endif // C_SYNTAX
    rcplib_TASK_InitArg();

    uint16 event_id;
    event_id = rcplib_SV_Read(ENGINE_SVITEM_ID_TASKEVENTITEM_EVENT_ID);

    // 初期化
    if(event_id == UNSYNC_EVENT_INIT) {
        event_init();
    }
    // 強制停止通知
    else if(event_id == EAPI_EVENT_TASK_ENTER_FORCESTOP) {
        KCS_NotifyReadyShutdown();
    }
    // 強制停止解除通知
    else if(event_id == EAPI_EVENT_TASK_RESUME_FORCESTOP) {
    }
    // 機能制限通知
    else if(event_id == EAPI_EVENT_TASK_ENTER_LIMIT) {
    }
    // 機能制限解除通知
    else if(event_id == EAPI_EVENT_TASK_RESUME_LIMIT) {
    }
#ifdef C_SYNTAX
}
#endif // C_SYNTAX

ソフトウェア構成

レシピ言語が動作するソフトウェア構成を説明します。

ソフトウェア構成図

soft_structure

[レシピエンジン]

レシピ言語で構成するユーザーアプリやサブアプリは「レシピエンジン」上で動作します。レシピエンジンはレシピ言語を動かすバーチャルマシンの働きをしており、これによりレシピ言語はチップセットやOSに依存しない体系となります。このためレシピ言語のビルドはレシピエンジン上で動かすための中間コードを生成を行うものとなり、レシピエンジンにてその中間コードのままで動作をする仕組みとなります。

[デバイスドライバとシステムライブラリ]

デバイスドライバとシステムライブラリはネイティブコンパイルされたコードとしてReal Time OS上で動作し、システム全体の制御を取り扱うと共に、レシピエンジン上のユーザアプリやサブアプリに向けてAPIを提供しています。SDKではここのコードは提供されておらず変更することは出来ません。

API

レシピ言語では効率よくプログラムを作成出来るように様々なAPIが準備されています。デバイスドライバやシステムライブラリで提供されている機能はAPIとして準備されており、ユーザアプリやサブアプリから利用することが出来ます。
soft_api_kind

API構成図

様々な種類があるAPIですが、以下の構成図のように整理されます。
soft_api_structure
サブアプリもレシピエンジン上で動作しているものですが、ユーザアプリに向けてAPIを提供することが出来ます。

API一覧

提供しているAPIの一覧を示します。
APIは追加の可能性がありますので、最新の詳細はAPIリファレンスを参照してください。※下記 2022/11月時点のAPI

サブアプリケーション・サブデバイスドライバ API

soft_api_subapp

デバイスドライバ API

soft_api_driver

システムライブラリ API

soft_api_sys1
soft_api_sys2
soft_api_sys3
soft_api_sys4


タスクとタスク間通信

タスク機能とタスク間通信を理解することで、よりレシピ言語を活用したプログラムを作成することが出来ます。ここではタスクとタスク間通信について解説します。

タスクの機能

レシピ言語のタスクはReal Time OSのタスクと同義で、並列実行の単位となります。またレシピエンジン上のアプリケーションの単位となります。

[タスク機能の特徴]

task_overview

タスクの優先度

タスクは並列処理が行われますが、優先度により処理される順番や頻度が変わります。優先度は以下の設計となり優先度値が大きいほど優先度が高くなります。

task_priority

※優先度はタスクのオプション設定で変更が出来ます。アプリケーションの優先度の変更はタスクの実行順に影響が出ますので、変更時には注意してください。

タスクの優先度と実行

タスクはマルチタスクで動作を行うことから、優先度によってそれぞれのタスクの実行の振る舞いが変わってきます。タスクはイベントを受信した時に動作を開始するため、優先度によって実行タスクが入れ替わり、優先度の低いタスクが中断します。動きは下記の模式図を参照してください。

task_execute

※ユーザーアプリケーションはそれぞれが並列処理が出来るようにデフォルトの優先度を同じにしています。

タスクのオプション

タスクはそれぞれ独立して稼働することから、該当タスクごとにメモリの容量やプライオリティを設定する必要があります。これはソースコード上の「options」の中で定義を行います。

//////////////////////////////////////////////////////////////////
// event driven task process //
//////////////////////////////////////////////////////////////////
options {
   StackSize = xx;     スタックサイズを定義
   LocalSize = xx;     関数内のローカル変数ワークサイズを定義
   JSONtmpSize = xx;   JSON変数ワークサイズを定義
   LISTtmpSize = xx;   LIST変数ワークサイズを定義
   TaskPriority = xx;  タスクプライオリティを定義
   EventQueueSize = xx; イベント処理キュー数を定義
   BufferFIFOSize = xx; データバッファのサイズを定義
   BufferFormatInfoSize = xx; データバッファのフォーマットID機能のサイズを定義
   DeviceDriver = xx;  デバイスドライバタスクとして実行定義
}
options 範囲 デフォルト値
StackSize 0~65535 1500 [words] ※words=4bytes
LocalSize 0~65535 タスク内全ローカル変数サイズ x 3[bytes]
JSONtmpSize 0~65535 0[bytes]
LISTtmpSize 0~65535 0[bytes]
TaskPriority 1~6 2
EventQueueSize 0~255 5[個]
BufferFIFOSize 0~65535 0 [bytes]
BufferFormatInfoSize 0~65535 0 [bytes]
DeviceDriver 0/1 0(ドライバー属性無効)

※option設定を記載しなければ、デフォルト値が入ります。

[StackSize]
該当タスクのスタックサイズを定義します。
スタックサイズの決定においては、ビルドを実行したときのログ(ビルドログ)に目安となる情報が含まれます。

例えば以下のようなビルドログが出力されるようなuapp_1のアプリケーションでは「minimum number of stacks = 654」のように最低限のスタックサイズは654[words]と算出されています。
参考として、この場合は300[words]程度のマージンを持って1000[words]以上を設定することでタスクを正常に動作させることができます。

[uapp_1.krb]
 heap:ENGINE_TLS_ITEM 1464bytes
 heap:global var 265bytes
 heap:local var 1104bytes
 heap:stack size 6000bytes
 heap:queue size 1600bytes
 heap:json buff 256bytes
 minimum number of stacks = 654
 minimum size of heap memory = 10689bytes

[LocalSize]
該当タスクで使用するローカル変数のサイズを定義します。この値はビルド時に全てのローカル変数の3倍分のサイズを自動計算し定義されますが、任意の値を定義し直す事も可能です。

[JSONtmpSize, LISTtmpSize]
該当タスクで使用するJSON変数とLIST変数のサイズを定義します。ここの値はビルド時に自動計算されません。
該当タスクで使用するJSON変数とLIST変数のそれぞれの合計サイズを定義してください。
JSON変数とLIST変数機能を利用しない場合は定義不要です。(デフォルトの0が適用されます)

例えば以下のようなJSON変数とLIST変数を利用する場合は

json json_a[512];
json json_b[512];
list list_a[128];

option設定には以下のように定義することでJSON変数とLIST変数を正しく利用できます。

options {
   ~省略~
   JSONtmpSize = 1024;
   LISTtmpSize = 128;
   ~省略~
}

[TaskPriority]
該当タスクのプライオリティを定義します。アプリケーションタスクはデフォルト2ですが、優先度高くしたい場合は高くする事が可能です。
1~6を定義することができますが、アプリケーションタスクでは2か3の利用を推奨します。

[EventQueSize]
該当タスクのイベントキューの数を定義します。
デフォルト5ですが、利用用途に応じて十分なサイズを定義してください。

該当タスクが何かしらの処理を実行中であっても、このサイズの分だけ新たなイベント受信を溜めることができます。
メモリの利用量に余裕がある場合は、最大値255を設定することでイベントキューの取りこぼしを回避しやすくなります。

[BufferFIFOSize]
該当タスクで使用するFIFOバッファのサイズを定義します。この値はビルド時に自動計算されません。
該当タスクで使用するFIFOバッファの合計サイズを定義してください。
FIFOバッファ機能を利用しない場合は定義不要です。(デフォルトの0が適用されます)

例えば、該当タスクでサイズ1024[bytes]とサイズ512[bytes]の2つのFIFOバッファを利用する場合、option設定には以下のように定義することでFIFOバッファを正しく利用できます。
詳細はソフトウェア開発ガイドのFIFOバッファの章を参照してください。

options {
   ~省略~
   BufferFIFOSize = 1536;
   ~省略~
}

[BufferFormatInfoSize]
該当タスクで使用するFIFOバッファの「フォーマットID」機能のために割り当てるメモリサイズを定義します。
「フォーマットID」機能を利用しない場合は定義不要です。(デフォルトの0が適用されます)

[DeviceDriver]
該当タスクのドライバー属性設定を定義します。
ドライバー属性が有効のタスクはキッティングモードでも動作できます。

デフォルトは0(ドライバー属性無効)であり、1(有効)を定義することもできますが、アプリケーションタスクでは0(ドライバー属性無効)を使用してください。

タスク間通信(イベント)

タスク間の連携や情報のやり取りを行う為には、タスク間通信としてイベントを使用します。イベントには送信先タスクの実行トリガを与えることに加え、26byte分の引数をつけることが出来ます。これによりイベントを受けたタスクはイベントの番号だけではなくデータの入っているバッファ番号等の情報も取得出来るため、APIコールや返り値にも利用されています。

[タスク間通信の特徴]

event_sndrcv

※rcplib_TASK_SetArg()にてセットした引数はrcplib_TASK_GetArg()にて順番に取り出す仕組みになっています。数と順序に気をつけて使用する必要があります。
※引数には合計で26byteのサイズしか使えませんので、大きなデータの通知はFIFOバッファを利用します。

[イベントの送受信]

タスク間でのイベント送信APIを利用した動きは以下のようになります。

event_sndrcv_api

イベントの同期/非同期

タスク間通信のイベント処理には2種類の動作があります。

非同期処理では相手タスクにイベント送信をした際にイベントの通知のみを行い自タスクのコンテキストは続行される動きとなります。同期処理では相手タスクにイベント送信をした際に自タスクのコンテキストが相手が応答メッセージを受信するまで中断されます。

SDKで提供されるほとんどのAPIは、各機能のタスクに対するイベント送信を利用しています。いくつかのAPIの引数にある「非同期」「同期」はここでのイベント送信時の「非同期」「同期」と同義になります。

非同期処理
非同期処理の模式図を下記に示します。非同期処理ではイベントのみ相手タスクに通知される事から、相手タスクの優先度が同じ場合は相手タスクと並列で実行されます。相手タスクの優先度が高い場合は非同期でイベント送信をしたとしても、相手タスクの実行が優先されて自タスクが待たされる動きになりますのでご注意ください。
event_unsync

同期処理
同期処理の模式図を下記に示します。同期処理ではイベントを送信した際に自タスクのコンテキストは相手の応答が来るまで中断します。相手タスクが応答を返さない場合に中断を継続しないように、イベント発行のAPIにはタイムアウトが設定でき、一定時間応答の無い場合はコンテキストが戻され、中断していたタスク処理が再開します。
event_sync

※タスク間通信の利用方法詳細は「ソフトウェア開発ガイド」-「制御処理」- 「タスク間通信」をご参照ください。

タスク間のデータ共有

タスク間での大きなデータのやり取りはFIFOバッファを利用します。イベントの引数にFIFOバッファのIDを乗せて相手タスクに通知する使い方をします。

[FIFOバッファの特徴]

fifo_overview

FIFOバッファのタスク間での利用例
FIFOバッファを利用してタスク間でデータの共有をするには、片方のタスクでFIFOバッファの割当を行い、そのバッファIDをイベントでデータを共有したいタスクに送付します。受信したタスク側は該当のバッファIDを利用してデータを取得出来ます。
fifo_usage_general

※FIFOバッファの利用方法詳細は「ソフトウェア開発ガイド」-「制御処理」- 「FIFOバッファ」をご参照ください。

タスク間通信全体図

ここまででのタスク、イベント、バッファの全体図は以下の通りです。
task_event_fifo


レシピ言語の言語体系

レシピ言語はC言語に近い言語体系ですが、若干違いがありますので、ここでは言語の基本的な内容を説明します。

変数

[数値変数、配列変数宣言]

書式:型名 変数名;
   型名 変数名[添字];
----------------------------
   int16 val;        //int16型の変数定義
   int32 index[10];  //int32型の10個の配列変数

型名:利用できる型については「変数型」を参照ください。
添字:添字の最大は65536、正数のみ指定可能で計算式は指定できません。

[変数への代入]

書式:変数名 = 値;
   変数名[添字] = 値;
----------------------------
   val = 123;         //変数に数値を代入
   index[0] = 10;     //配列変数に数値を代入
   val = 1 + 2 + 3;   //演算値を代入
   val = index[0];    //変数を代入

[文字変数の宣言]

書式:str 文字変数名[添字];
----------------------------
   str name[16];     //16文字までの文字変数

注意: C言語であるchar型はありません
✕ char moji[10];

[文字変数への代入]

書式:文字変数名 = “文字列”;
   文字変数名 = 文字変数名;
----------------------------
   name = “abcdef”;  //添字の文字数範囲で代入
   mcopy = moji;     //mcopyは文字変数

文字変数にはダブルクオーテーションで囲った文字を直接代入可能 。
注意: str型は配列変数の様な定義をしますが、配列としての操作は不可
✕ mcopy[1] = moji[1];
✕ mcopy[] = moji[];

[ローカル変数宣言と初期値の代入]

書式:型名 変数名 = 値;
   型名 変数名[添字] = {値1, 値2, ...};
----------------------------
   int16 val1 = 123; //変数の宣言と同時に数値を代入
   int16 val2 = 100 + 23; //計算式の形式も表現可能
   int32 index1[4] = {10, 0, 0, 0}; //int32型の4個の配列変数の宣言と同時に数値を代入
   int32 index2[] = {0x01, 0x02, 0x03}; //添字の省略も可、要素数に合わせた配列サイズとなる

注意: グローバル変数の宣言と初期化は不可

注意: 文字列変数の宣言と初期化は不可
✕ char moji[10] = “abcdef”;
✕ char mcopy[10] = moji;

[変数型]

利用できる変数型は以下の通りです。

型名 説明
int8 符号付き8bit整数。範囲は -128 ~ 127。
int16 符号付き16bit整数。範囲は -32768 ~ 32767。
int32 符号付き32bit整数。範囲は -2147483648 ~ 2147483647。
int64 符号付き64bit整数。範囲は -9223372036854775808 ~ 9223372036854775807。
uint8 符号なし8bit整数。範囲は 0 ~ 255。
uint16 符号なし16bit整数。範囲は 0 ~ 65535。
uint32 符号なし32bit整数。範囲は 0 ~ 4294967295。
uint64 符号なし64bit整数。範囲は 0 ~ 18446744073709551615。
float 32bit浮動小数点。範囲は 1.175494e-38 ~ 3.402823e+38。
double 64bit浮動小数点。範囲は 2.225074e-308 ~ 1.797693e+308。
str 文字列。必ず添字の定義が必要です。最大長は 65536[文字]。
json JSON変数。必ず添字の定義が必要です。最大サイズは 65536[byte]。
list LIST変数。必ず添字の定義が必要です。最大サイズは 65536[byte]。

※jsonとlist変数はoption部でのデータバッファサイズの指定が必要です。
注意: ポインタ変数は使えません。
✕ uint8* a;

[変数スコープ]


定数と記号定数

プログラム中の定数表記は以下の通り

[定数表記]

定数 説明
10進数 0 ~ 9の数値。int64の範囲の指定が可能になります。負数は先頭に「-」を指定可能です。
16進数 先頭に「0x」のプリフィクスを指定し、0 ~ 9、a ~ f (またはA ~ F)で指定します。uint64の範囲の指定が可能です。途中任意に区切り文字「_」を指定可能です。
2進数 先頭に「0b」のプリフィクスを指定し、0 ~ 1で指定します。uint64の範囲の指定が可能です。途中任意に区切り文字「_」を指定可能です。
小数 0 ~ 9、「.」の数値。doubleの範囲の指定が可能です。指数指定は非対応になります。
文字 "(ダブルコーテーション)で囲まれた任意の文字。文字コードはASCIIのみとし、Unicodeは非対応になります。"\n"などのエスケープ文字(\の後に次のパターン:n r \ " ? ' 0 a b t f v)にも対応しています。 "\x"など非対応の表記はビルドエラーとなります。 
10              : 10進数
0x32            : 16進数
0b0001          : 2進数
0b1100_0000     : 2進数、区切り文字付き
3.14            : 小数
"abcd"          : 文字

[記号定数表記]

defineマクロを利用した記号定数表記が可能です。

書式:#define 記号定数名 定数
----------------------------
   #define PI 3.141592
   #define EVENT 0

注意: enum, const, structは使用不可
✕ enum num {1,2,3,4};
✕ const float pi;


演算子

演算子は優先順位に従い評価します。

[演算子]

優先順位 演算子 説明
1 [ ] 配列要素
2 ( ) カッコ
3 ! , ~ , [ ] 単項演算子、アドレス参照
4 * , / , % 乗除算、剰余
5 + , - 加減算
6 < , <= , > , >= 大小比較
7 == , != 等価、非等価比較
8 & , ^ , |  ビットAND、XOR、OR
9 && 論理AND
10 || 論理OR

[型の異なる演算]

演算式中に異なる型が混在する場合は、以下のキャストが行われます。

[文字の演算]

文字比較は以下の演算子のみが評価可能です。

[利用できない演算子]

以下の演算子は利用出来ません。


命令コード

制御命令コードは以下が利用可能です。

コマンド名 説明
if , else , else if 比較命令
while , for , break , continue 繰り返し命令
return 復帰命令

[比較命令(if-else)]

以下の形式で記述する。else if節やelse節は省略可能です。
比較命令はネストして記述可能です。ネスト数に制限はありません。

書式:
   if(第一条件式) {
        第一条件成立時の処理
   } else if (第二条件式) {
        第二条件成立時の処理
   } else {
        条件不成立時の処理
   }
----------------------------
定数比較:
   if ( val == 1 ) {       //数値比較
      val = 0;
   }
   if ( moji == “abcd” ) { //文字比較
      moji = “cdef”;
   }
変数比較:
   if ( val1 == val2 ) {   //数値変数比較
      val = 0;
   }
   if ( moji1 == moji2 ) { //文字変数比較
      moji1 = “cdef”;
   }

※switch-case文は利用できません。if-else文での記述が必要です。
※C言語のようにif文内が1行の時のカッコ省略不可です。

[繰返し命令(while)]

繰返し命令はネストして記述可能。ネスト数に制限はありません。

書式:
    while(条件式) {
     繰り返し処理
    }
----------------------------
条件ループ: ※ループの中で条件ケアをしないと無限ループになるので注意
   i = 0;
   while ( i < 10 ) {
      処理;
      i = i + 1;        //ループ内で条件ケア
   }
ループの入れ子:
   while (i < 10) {
      while (k < 20) {
         …

for文の利用も可能。

書式:
    for(初回の実行処理; 条件式; 繰り返し時の実行処理) {
     繰り返し処理
    }
----------------------------
条件ループ: ※for文やループの中で条件ケアをしないと無限ループになるので注意

   for (i = 0; i < 10; i = i + 1) {
      処理;
   }
ループの入れ子:
   for (i = 0; i < 10; i = i + 1) {
      for (k = 0; k < 20; k = k + 1) {
         …

但し、ローカル変数は関数名と変数名を含めた管理であり関数内でのローカル変数名の重複は許容されないため、以下のように一つの関数の中での同じ変数名の定義を含めたfor文の記載はできないことに要注意。

ビルドエラーとなる例: ※for()内のローカル変数定義スコープも関数内のスコープとして扱われるため

   for (int32 i = 0; i < 10; i = i + 1) {
      処理1;
   }
   for (int32 i = 0; i < 20; i = i + 1) {
      処理2;
   }

breakとcontinueの利用も可能。

break文: ※ループ強制終了
   while (条件式) {
      if (条件) {
         処理1 ;
         break;
      }
      処理2 ;
   }
   //break後はここに移動

continue文: ※ループの頭に戻る
   while (条件式) {
      //continue後はここに移動
      if (条件) {
         処理1 ;
         continue;
      }
   }

関数定義

[関数の特徴]

関数はfuncキーワードで定義をします。

書式:
    func 関数名(引数リスト) {
     関数内処理
     return(戻り値);
    }
----------------------------
引数なし:
   func doSome ()
   {
      関数内処理;
      return(0);
   } 
引数あり:
   func TriangleArea ( uint16 base, uint16 height )
   {
      uint16 area;
      area = base * height / 2;
      return(area);
   } 
関数の呼び方:
   ans = TriangleArea (teihen, 10);  //ansに戻り値が入る
   TriangleArea (teihen, 10);        //戻り値は破棄される


引数リスト:

変数定義:

配列変数の引数:
配列変数の引数は「数値配列」と「文字配列」ではメモリの取り方が変わります。

書式:数値配列の引数
   func 関数名(データ型 配列引数名[])
   {
      関数内処理;
      return(戻り値);
   }
----------------------------
apという変数名で呼び元と同じメモリを使用
   func myf(uint8 ap[])
   {
      ap[0] = 3;
      ap[1] = 4;
      return (0)
   }

   uint8 val[5];
   val[0]=1; val[1]=2;
   myf(val);

上記アドレス渡しとなり、関数戻った後はap[0] = 3となる。
書式:文字変数の引数
   func 関数名(str 変数[添字])
   {
      関数内処理;
      return(戻り値);
   }
----------------------------
mpという新しい変数メモリを確保してコピーされる。
新しい変数の定義の為、[添字]が必要となる。
   func myf(str mp[10])
   {
     mp = “cdf”;
     return (0)
   }

   str moji[5];
   moji = “abc”;
   myf(moji);

文字列のコピーとして渡す。関数戻った後のmoji = "abc"となる。


戻り値:

    func func_d() {
       int16 ret;
       ret = 1;
       return(ret);
   }

  var_a = func_d();     戻り値は変数var_aに代入される
  API(func_d());        戻り値は関数APIの引数として渡される
  func_d();             戻り値は破棄される

レシピ言語と他言語との比較

凡例 説明
利用可
条件付きで利用可
利用不可
定義 種類 C言語 レシピ言語
定数 整数 ○ *1
小数
文字
変数 int8, uint8
int16, uint16
int32, uint32
int64, uint64
float
double
文字 ○ *2
文字最大長 制限なし 65535
名称長 制限なし 制限なし
定義数 制限なし 65535
算術演算 四則剰余 ○ *3
括弧
冪乗
冪根
関数呼び出し
論理演算 and
or
xor
not
shift ✕ *4
rotate
関係演算 比較(=, <, >)
論理積(AND)
論理和(OR)
否定(NOT)
配列定義 整数
実数
文字 ○ *5
データ定義 struct
union
enum
#define
#ifdef, #else, #endif
ポインタ ○ *6
JSON
データ制御 if, else, else if
while
for ○ *8
break, continue
sleep
ログ出力
文字列操作
フォーマット変換(atoi, sprintf)
FIFOバッファ操作 ✕ *7
リングバッファ操作 ✕ *7
デバイス制御 時刻取得
LED ✕ *7
GPIO ✕ *7
I2C ✕ *7
Bluetooth Low Energy ✕ *7
CAN ✕ *7
UART ✕ *7
GNSS ✕ *7
省電力 ✕ *7
電源 ✕ *7
関数 関数定義
関数内変数
(ローカル変数)
引数渡し
戻り値渡し
通信プロトコル HTTP ✕ *7
HTTPS ✕ *7
MQTT ✕ *7
MQTTS ✕ *7
TCP ✕ *7
UDP ✕ *7

*1:符号付き32bit値のみ
*2:char[]を文字として利用する想定
*3:小数演算は非サポート
*4:符号なし整数の乗除演算にて代用可
*5:文字変数の配列定義は不可だが、LIST変数にて代用可
*6:数値配列変数のみアドレス参照可
*7:外部ライブラリなどを利用することが前提
*8:for()内のローカル変数定義であっても関数内スコープ扱いとなる


レシピ言語記載について

レシピ言語はC言語に近い構文を取ることから、テキストエディタでの静的構文解析機能を利用することにより、便利な使い方が出来るように工夫してあります。ここではそれらの機能や注意点を説明します。

[API説明のインクルードファイル]

冒頭のインクルードファイルに以下の記載があります。

#include "basicInformation.h"

ここでの「basicInformation.h」はレシピ言語のビルドには影響しないヘッダファイルですが、このヘッダファイルの中にはAPIの説明が書かれているため、ソースコード上でAPIの定義を見る事が出来ます。テキストエディタによってはマウスオーバーで表示することも出来ます。
edit_static_api

[静的構文解析エラー回避]

ソースコード中に「#ifdef C_SYNTAX」があります。

#ifdef C_SYNTAX
#include "basicInformation.h"
#endif // C_SYNTAX

ソースコード中にこの記載は静的構文解析のエラーを回避するためのキーワードとなります。静的構文解析が無いエディターやエラーが気にならない場合は、この記載を削除してもレシピ言語のビルドには影響を与えません。

[イベント分岐のメインループ]

イベント分岐処理の冒頭に「void main_loop() {」とあります。

#ifdef C_SYNTAX
void main_loop(){
    WAIT_REQUEST_EVENT_QUEUE
#endif // C_SYNTAX
    rcplib_TASK_InitArg();

    uint16 event_id;
    event_id = rcplib_SV_Read(ENGINE_SVITEM_ID_TASKEVENTITEM_EVENT_ID);

レシピ言語はイベンドドリブンで動く作りであるため、この「main_loop()」は静的構文解析エラーを回避するための便宜上のキーワードとして存在をします。このため記載を削除してもレシピ言語のビルドには影響を与えませんが、初見でソースコード構造上の動きの把握をし難くなるため、削除時はご注意ください。

[強制停止と機能制限通知]

イベント分岐処理に雛形として強制停止と機能制限通知の処理分岐があります。

    // 強制停止通知
    else if(event_id == EAPI_EVENT_TASK_ENTER_FORCESTOP) {
        KCS_NotifyReadyShutdown();
    }
    // 強制停止解除通知
    else if(event_id == EAPI_EVENT_TASK_RESUME_FORCESTOP) {
    }
    // 機能制限通知
    else if(event_id == EAPI_EVENT_TASK_ENTER_LIMIT) {
    }
    // 機能制限解除通知
    else if(event_id == EAPI_EVENT_TASK_RESUME_LIMIT) {
    }

これはシステムからのイベント通知に対する処理を記載する場所ですが、安定的に機器を稼働し続けるためのイレギュラー時の処理として必要ですが、電源OFFをしない場合や、カメラや通信をし続けるなどの高温になる動作をしない使い方をする場合には、これらの記載を削除する判断も可能です。

[ソースコードの最小構成]

静的構文解析のエラーや可読性、システムからのイベント通知処理を必要としない場合は、以下のように必要最低限のソースコード構成にすることが可能です。

コード例

下記サンプルコードはボタンを押下した際に画面に表示をする最小コードです。

#include "kc4.h"

// イベントID
#define EVENT_INIT 0
#define EVENT_KEY_S 1
// global value //
uint16 event_id; // イベントIDの変数

// event roop //
// タスク送受信時の引数管理情報を初期化
rcplib_TASK_InitArg();
event_id = rcplib_SV_Read(ENGINE_SVITEM_ID_TASKEVENTITEM_EVENT_ID);
// 初期化
if(event_id == EVENT_INIT) {
    event_init();
}
// キーイベント Short
else if(event_id == EVENT_KEY_S) {
    event_key();
}  

// event function //
// 初期化
func event_init() {
    // ボタンイベント登録
    UIC_SenseButtonS(EVENT_KEY_S);  //1秒以内
    return(0);
}

// ボタンイベント
func event_key() {
    OLC_DisplayChar(OLC_CHAR_FORCE,0,0,"Button Push     ");
    OLC_DisplayChar(OLC_CHAR_FORCE,18,0,"                ");
    return(0);
}