質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

Q&A

4回答

782閲覧

OSレスでの組み込み製品開発における、キャラクタLCD画面状態遷移処理方法

dakao

総合スコア6

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

0グッド

0クリップ

投稿2020/04/14 15:01

初めまして。

OSレスでの組み込みシステムについて質問させていただきます。

私は、「設定した時間に音を鳴らす」という単純な機能を持つ製品を開発しています。
設定変更用の入力ボタン(上下左右+選択)と、16×2行のキャラクタLCDが備えられています。

下記製品のような操作部をイメージしてください。
LCD Keypad Shield For Arduino
イメージ説明

ユーザーの入出力制御は、状態変数を用意し、状態に応じた処理を行っています。
基本的には、LCDの表示内容を変えるだけですが、
ユーザーが設定内容を変えた場合は、変更内容をEEPROMに保存する処理もあります。
なお、メインループの中でAD変換→冷却FAN制御→スピーカー制御→入出力制御のように順番に処理しています。

そこで、問題なのですが、

static uint16_t seq=0; switch(seq) { case 0: lcd_puts(" MAIN MENU "); if(sw == SW_UP) { seq=1; } break; case 1: lcd_puts(" MENU 1 "); if(sw == SW_DOWN) { seq=0; } if(sw == SW_UP) { seq=2; } if(sw == SW_SELECT) { eeprom_write("TEST"); seq=0; } break;

このようなswitch文がcase500ぐらいまであります。
新しい機能を追加し、設定項目が増える度に、switch文が長くなり焦っています。

正直なところ、私は素人から毛を抜いた程度なので、このようなプログラムで良いのか判断できません。

OSレスの家電製品のプログラムを参考にしたいと思ったのですが、探しても見つかりませんでした。

なにか参考になるプログラムや参考書等ご存じでしたら、ご教示願います。
また、組み込みプログラムについてお詳しい方のアドバイスお待ちしております。

どうぞよろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

具体的に解決したい問題はなんですか? (teratailのヘルプを見ると、「具体的な問題を提示し、それに回答する」というのがここの基本スタンスのようなので)

OSレスの家電製品のプログラムを参考にしたいと思ったのですが、探しても見つかりませんでした。

まぁ、製品のプログラムを公開する、なんてことはまずあり得ないですね。

さて。
常識的に言って、その操作系で

このようなswitch文がcase500ぐらいまであります。

というのは明らかに設計がおかしいです。フラットに500の選択項目が並んでいる、というのは...ありえないとはいいませんが、ふつーの神経なら、それをグルーピングしてメニューを階層化します。全項目は500のままかも知れませんが、一時に気にしなければいけない範囲はそれでも随分限定出来るでしょう。

また、項目が少ないならそのプログラムでもいいかも知れませんが、500の項目それぞれに

C

1 if(sw == SW_DOWN) { seq=0; } 2 if(sw == SW_UP) { seq=2; } 3 if(sw == SW_SELECT) {

なんていう処理を持たせるのですか? UPやDOWNが押されたときの遷移先、その項目の種類(設定値を変更する/なにかの機能を実行する)、変更する設定値、実行する機能(関数)へのポインタなんかを構造体にして、それをテーブル(配列)にして「メニューの情報」を分離して、実行部はそれを解釈するだけ、みたいな構成に出来れば随分管理はやりやすくなるのでは? 質問のプログラムに例を取るなら、

C

1enum MENU { //メニューのIDを定義する 2 //一部だけ値を指定するのはMISRA等では怒られるがサンプルということで 3 NONE = -1, 4 MAIN = 0, 5 MENU1, 6 MENU2, 7 MENU_MAX 8}; 9struct MenuItem { 10 enum MENU id; //メニュー項目のID 11 char* Title; //タイトル表示文字列 12 enum MENU moveUp; //UPキーの遷移先 13 enum MENU moveDn; //DOWNキーの遷移先 14 char* eepDat; //SELECT時EEPROM書き込みデータ 15 enum MENU moveSel; //SELECTキーの遷移先 16}; 17const struct MenuItem menuItems[] = { 18 // id項目は不要とも言えるけど...あると意外と便利なことも 19 // id, Title, , up, down, EEP, sel 20 {MAIN, " MAIN MENU ", MENU1, NONE, NULL, NONE}, 21 {MENU1, " MENU 1 ", MENU2, MAIN, "TEST", MAIN}, 22 {MENU2, " MENU 2 ", MAIN, MENU1, "EXEC", MAIN}, 23}; 24 25static uint16_t seq = 0; 26void doMenu(int sw) { 27 int index = -1; 28 int mv_tmp= NONE; 29 char* eep_tmp = NULL; 30 for (int i = 0; i < MENU_MAX; i++){ 31 if(menuItems[i].id==seq){ 32 index = i; 33 break; 34 } 35 } 36 if (index < 0) return; 37 lcd_puts(menuItems[index].Title); 38 switch(sw ){ 39 case SW_UP: 40 mv_tmp = menuItem[index].moveUp; 41 break; 42 case SW_DOWN: 43 mv_tmp = menuItem[index].moveDn; 44 break; 45 case SW_SELECT: 46 eep_tmp = menuItem[index].eepDat; 47 mv_tmp = menuItem[index].moveSel; 48 break; 49 } 50 if(eep_tmp!=NULL){ 51 eeprom_write(eep_tmp); 52 } 53 if (mv_tmp >= 0) { 54 seq = mv_tmp; 55 } 56}

なんて感じにまとめられればmenuItemsのデータの追加だけで要素は増やせるわけで。(条件によってはもっと簡素になるかも知れないけど、もっとメニュー動作の「例外」があって対応しなきゃいけなくなるのが現実か)

投稿2020/04/14 23:53

thkana

総合スコア7652

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

thkana

2020/04/15 00:07

あと、「この状態のときはこのメニューはスキップ」なんてのをどう処理するか、なんてのがひとつのヤマかな。
dakao

2020/04/15 07:40

具体的なコードまで示していただきありがとうございます。 検討してみます。
guest

0

switch文でシーケンスを制御するのは、保守のことを考えるといつか破綻すると思います。
ステートマシン(状態遷移テーブル)で設計することをお勧めします。

参考になりそうなのは、こことか、
https://katono123.hatenablog.com/entry/20120325/1332650559

デザインパターンの一つである、Stateパターンで調べてみるといいかも。

投稿2020/04/14 17:34

TaroToyotomi

総合スコア1430

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dakao

2020/04/15 07:42

Stateパターンについて勉強してみたいと思います。
guest

0

・メニューに階層無し
・メニュー上下でのループなし
・SELECT後はMAIN MENUに戻る
と仮定して、こんな感じですかね?
メニュー表示処理とメニュー選択時の処理を分けておけば
選択時の処理関数と、メニュー内容の構造体への追加だけで済みます。

C

1void menu1(); 2void menu2(); 3struct 4{ 5 const char* menuText; // LCD表示 6 void (*menuFunc)(); // SELECT時処理関数 7} menu[] = { 8 { " MAIN MENU ", NULL }, 9 { " MENU 1 ", menu1 }, 10 { " MENU 2 ", menu2 }, 11}; 12 13static uint16_t seq = 0; 14 15void loop() 16{ 17 sw =;//sw取得 18 19 lcd_puts(menu[seq].menuText); 20 switch (sw) 21 { 22 case SW_DOWN: 23 seq = seq ? seq - 1 : 0; 24 break; 25 case SW_UP: 26 seq = (seq < sizeof(menu)/ sizeof(menu[0]) - 1) ? seq + 1 : seq; 27 break; 28 case SW_SELECT: 29 if (menu[seq].menuFunc) 30 { 31 menu[seq].menuFunc(); 32 } 33 seq = 0; 34 break; 35 } 36} 37 38void menu1() 39{ 40 eeprom_write("TEST"); 41} 42void menu2() 43{ 44 eeprom_write("TEST2"); 45} 46

投稿2020/04/14 15:58

編集2020/04/14 16:06
SHOMI

総合スコア4079

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SHOMI

2020/04/14 18:58 編集

メニューについて書きましたが、500通り近くあるということは困っているのはメニューではなく、 状態の組み合わせでフラグやパターンが増えることでしょうか。 であればTaroToyotomiさんの回答にもあるようにステートマシンについて調べてみてください。
dakao

2020/04/15 07:41

回答ありがとうございます。 調べてみます。
guest

0

大量のcaseをど~するか、に限ると、
範囲ごと、あるいは機能ごとに関数にまとめてしまえがいいかと。

C

1int SWITCH_A(int stat) 2{ 3 switch(stat){ 4 case 0: break; // 処理済み 5 6 case STAT_1: 7 なんやかや 8 break; 9 case STAT_2: 10 なんやかや 11 break; 12 case いろいろ: 13 .... 14 15 16 default: 17 return stat; 18 } 19 return 0; 20}

というのを大量に作っておけば、

stat=SWICH_A(stat); stat=SWICH_B(stat); stat=SWICH_C(stat); stat=SWICH_D(stat); stat=SWICH_E(stat);

ということができます

それと、提示のコードで気になるのは、caseの数値は直値を書くのは避けましょう
必ず、define あるいはenum でラベルを定義するようにします

投稿2020/04/14 23:50

y_waiwai

総合スコア87784

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

thkana

2020/04/15 00:00

メニューを一項目増やす度にあっちの関数こっちの関数をいじらなきゃいけない、というのは悪手だと思う...
y_waiwai

2020/04/15 00:06

あくまで一つのcaseは特定の関数でしか処理しないってはなしなんで、そこらはやりようでどーとにもあるかと。 まあ、caseを何百も作るという自体が悪手なわけで、それをいきなり仕切り直しでステートマシンに実装し直しってのはちと非現実的なんで、まずは関数で細分化しよう、というおはなしでした。
thkana

2020/04/15 00:08

そうですね...とにかく500は勘弁。
dakao

2020/04/15 07:44

回答ありがとうございます。 このやり方なら自分でも簡単にできそうです。
y_waiwai

2020/04/15 07:56

thkanaさんも懸念されてますが、ひとつのcaseは1箇所で処理する、というのを徹底させるようにしましょう。 これとこれは同じ処理だから、と複数の関数で分散させるようなことをするとすぐに破綻しますよ。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問