本書では、KC4-C-100A/KC-4-C-101A(以下「本製品」)のレシピ言語のシステム概要とご利用方法を記載します。
レシピ言語は「組み込みプログラムをより簡単に」をコンセプトにしたC言語に近いコード体系を持つ独自言語となります。言語としての一般的な機能に加えて外部機器やクラウドに接続し易いAPIを準備していますので、ブロックプログラミングでは制御できなかった複雑なロジックを組むことが可能となります。
レシピ言語でプログラミングを行うためには、レシピツール(KC4-C-Installer)のインストールが必要になります。開発環境のインストールに関しては「はじめよう」を参照してください。
本書では以下の環境がある前提で進めます。
レシピ言語の開発環境は構造が少し複雑になっています。ここではSDKを扱う上で必要なレシピツールとビルド環境の構造を説明します。
開発環境の構築としてインストールした、WSL2とレシピツールの関係は下図となります。
[レシピツール]
インストールしたレシピツールはWSL2上のLinuxの中で動作しており、内部ではブロックプログラミング領域とレシピ言語領域に分かれています。それぞれから共通のコンパイラ/リンカを利用してレシピ実行ファイルを作る動きとなります。
[ブロックプログラミング領域]
ブロックプログラミング領域はWindows側からブラウザで操作するインターフェースとなり、ブロックプログラミング自体はWebアプリとして動作している為、内部で作られたレシピ実行ファイルはブラウザを経由してエクスポート(ダウンロード)されます。
[レシピ言語領域]
レシピ言語領域はWindows側からコマンドラインで操作するインターフェースとなります。レシピ言語で作成するソースファイルはWindows側に存在しますが、ビルドをする際にはSDKの中にあるバッチファイル(recipe_build.bat)にて、一度レシピ言語領域にコピーされます。コピーされたファイルを使用してレシピツール内のコンパイラ/リンカにてレシピ実行ファイルが生成された後、該当ファイルをWindows側にコピーを行う動きとなります。
[キッティングツール]
エクスポート/コピーされたレシピ実行ファイルはキッティングツールにて本機器に書込みを行います。ブロックプログラミングから生成したレシピ実行ファイルは「exe-file.zip」という名前となり、レシピ言語から生成したレシピ実行ファイルは「rcp_compress.zip」という名前になります。
レシピ言語のSDKはアプリケーション層のソースファイルを扱ったものとなります。ダウンロードしたSDKを展開したディレクトリ構成は以下の通りとなります。
[レシピ言語フォルダ]
レシピ言語でレシピ実行ファイルを作成するメインのフォルダとなります。
ファイル名 | 説明 |
---|---|
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の使い方を示したドキュメントが配置されています。
サンプルコードはそのまま使えるように構成されています。このため、フォルダ内にある「uapp_1.krs」を「レシピ言語フォルダ」にコピー/貼付け(上書き)してビルドを行うことで、サンプルコードを試す事が出来ます。
レシピの作成にはファイルのuapp_1.krs~uapp_7.krsを編集していくことになります。これらファイルがアプリケーションやタスクの単位となります。ここではレシピを作成していく上でのアプリケーションやタスクの概念を説明していきます。
SDK内のファイルは大きく分けて2つのアプリケーションに分かれます。
[ユーザーアプリケーション]
作りたいレシピのメインの処理を記述します。ユーザアプリはサブアプリやドライバ/システムから提供される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つのタスクが利用することできます。
[イベント]
タスク間の連携を行うためにイベントの仕組みがあります。イベントは送信したいタスクの番号にイベント番号を通知出来る機能に加えて、26byte分の引数をつけることが出来ます。サブアプリから提供しているAPIもイベントの仕組みを利用しており、ユーザアプリからコールしたAPIはイベントに変換されてサブアプリへ通知して処理されます。
レシピ言語のタスクはイベントドリブン方式で動作します。このため組み込み機器のプログラムであるmain()関数でループするようなシーケンス処理の書き方ではなく、イベント毎の処理にイベント分岐から飛ばしていく書き方になります。
イベントドリブンでは上図の様に処理毎にタイミングが違う場合などにコードが書きやすくなるメリットがあります。
タスクに通知されてくるイベントには他のタスクから通知されるイベントの他に、システムからのイベント通知があります。
[システムからのイベント]
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
レシピ言語が動作するソフトウェア構成を説明します。
レシピ言語で構成するユーザーアプリやサブアプリは「レシピエンジン」上で動作します。レシピエンジンはレシピ言語を動かすバーチャルマシンの働きをしており、これによりレシピ言語はチップセットやOSに依存しない体系となります。このためレシピ言語のビルドはレシピエンジン上で動かすための中間コードを生成を行うものとなり、レシピエンジンにてその中間コードのままで動作をする仕組みとなります。
デバイスドライバとシステムライブラリはネイティブコンパイルされたコードとしてReal Time OS上で動作し、システム全体の制御を取り扱うと共に、レシピエンジン上のユーザアプリやサブアプリに向けてAPIを提供しています。SDKではここのコードは提供されておらず変更することは出来ません。
レシピ言語では効率よくプログラムを作成出来るように様々なAPIが準備されています。デバイスドライバやシステムライブラリで提供されている機能はAPIとして準備されており、ユーザアプリやサブアプリから利用することが出来ます。
様々な種類があるAPIですが、以下の構成図のように整理されます。
サブアプリもレシピエンジン上で動作しているものですが、ユーザアプリに向けてAPIを提供することが出来ます。
提供しているAPIの一覧を示します。
APIは追加の可能性がありますので、最新の詳細はAPIリファレンスを参照してください。※下記 2022/11月時点のAPI
タスク機能とタスク間通信を理解することで、よりレシピ言語を活用したプログラムを作成することが出来ます。ここではタスクとタスク間通信について解説します。
レシピ言語のタスクはReal Time OSのタスクと同義で、並列実行の単位となります。またレシピエンジン上のアプリケーションの単位となります。
タスクは並列処理が行われますが、優先度により処理される順番や頻度が変わります。優先度は以下の設計となり優先度値が大きいほど優先度が高くなります。
※優先度はタスクのオプション設定で変更が出来ます。アプリケーションの優先度の変更はタスクの実行順に影響が出ますので、変更時には注意してください。
タスクはマルチタスクで動作を行うことから、優先度によってそれぞれのタスクの実行の振る舞いが変わってきます。タスクはイベントを受信した時に動作を開始するため、優先度によって実行タスクが入れ替わり、優先度の低いタスクが中断します。動きは下記の模式図を参照してください。
※ユーザーアプリケーションはそれぞれが並列処理が出来るようにデフォルトの優先度を同じにしています。
タスクはそれぞれ独立して稼働することから、該当タスクごとにメモリの容量やプライオリティを設定する必要があります。これはソースコード上の「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コールや返り値にも利用されています。
※rcplib_TASK_SetArg()にてセットした引数はrcplib_TASK_GetArg()にて順番に取り出す仕組みになっています。数と順序に気をつけて使用する必要があります。
※引数には合計で26byteのサイズしか使えませんので、大きなデータの通知はFIFOバッファを利用します。
タスク間でのイベント送信APIを利用した動きは以下のようになります。
タスク間通信のイベント処理には2種類の動作があります。
非同期処理では相手タスクにイベント送信をした際にイベントの通知のみを行い自タスクのコンテキストは続行される動きとなります。同期処理では相手タスクにイベント送信をした際に自タスクのコンテキストが相手が応答メッセージを受信するまで中断されます。
SDKで提供されるほとんどのAPIは、各機能のタスクに対するイベント送信を利用しています。いくつかのAPIの引数にある「非同期」「同期」はここでのイベント送信時の「非同期」「同期」と同義になります。
非同期処理
非同期処理の模式図を下記に示します。非同期処理ではイベントのみ相手タスクに通知される事から、相手タスクの優先度が同じ場合は相手タスクと並列で実行されます。相手タスクの優先度が高い場合は非同期でイベント送信をしたとしても、相手タスクの実行が優先されて自タスクが待たされる動きになりますのでご注意ください。
同期処理
同期処理の模式図を下記に示します。同期処理ではイベントを送信した際に自タスクのコンテキストは相手の応答が来るまで中断します。相手タスクが応答を返さない場合に中断を継続しないように、イベント発行のAPIにはタイムアウトが設定でき、一定時間応答の無い場合はコンテキストが戻され、中断していたタスク処理が再開します。
※タスク間通信の利用方法詳細は「ソフトウェア開発ガイド」-「制御処理」- 「タスク間通信」をご参照ください。
タスク間での大きなデータのやり取りはFIFOバッファを利用します。イベントの引数にFIFOバッファのIDを乗せて相手タスクに通知する使い方をします。
FIFOバッファのタスク間での利用例
FIFOバッファを利用してタスク間でデータの共有をするには、片方のタスクでFIFOバッファの割当を行い、そのバッファIDをイベントでデータを共有したいタスクに送付します。受信したタスク側は該当のバッファIDを利用してデータを取得出来ます。
※FIFOバッファの利用方法詳細は「ソフトウェア開発ガイド」-「制御処理」- 「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 | 復帰命令 |
以下の形式で記述する。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(条件式) {
繰り返し処理
}
----------------------------
条件ループ: ※ループの中で条件ケアをしないと無限ループになるので注意
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言語に近い構文を取ることから、テキストエディタでの静的構文解析機能を利用することにより、便利な使い方が出来るように工夫してあります。ここではそれらの機能や注意点を説明します。
冒頭のインクルードファイルに以下の記載があります。
#include "basicInformation.h"
ここでの「basicInformation.h」はレシピ言語のビルドには影響しないヘッダファイルですが、このヘッダファイルの中にはAPIの説明が書かれているため、ソースコード上で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);
}