2014/11/24

Arduino - ロータリスイッチの状態をA/D変換で取得する

前置きが長いので,すぐに回路図とスケッチを確認したい場合は,最初の2項目を読み飛ばしてください.

しろくま氏のPWMパワーパックの改良

しろくま氏ジオタウンドットコムで公開している,PWMパワーパックを製作してみることにしました. プログラムをPICに移植して製作しようと思っていたのですが,Arduinoの使用経験がなかったこともあり,私の技術不足で一筋縄では行きそうにありません. そこで,Arduinoの開発環境を用意して,ひとまずArduinoで製作してみることにしました.

しろくま氏が記事を投稿してから3年以上経過しているため,既に多くの方が,しろくま氏のPWMパワーパックを製作したようです. ジオタウンドットコムのコメント欄を見ると,誤動作するという方も散見されます. どうやら,マスコンとして使用している,ロータリスイッチ周辺回路やプログラムに問題が1つあるようです. 今回は,このロータリスイッチ周辺回路とプログラムの改良について考えてみました.

誤動作の原因は回路にあると推測

誤動作の原因や対応策についてコメント欄でさまざまな議論がされていますが,私は回路に原因があると考えました. 記事の添付写真から推測すると,しろくま氏が使用しているロータリスイッチは,秋月電子通商で発売されているノンショーティングタイプの1回路12接点のものです. ノンショーティングタイプのロータリスイッチは,スイッチを回転させて選択接点を切り換える際,一時的に共通接点がどの選択接点にも接触していない状態になります.

ここで,しろくま氏の回路図を確認します. ロータリスイッチ RSW_12の共通接点aは,アナログ入力ピンに接続されています. つまり,ロータリスイッチを回転させた際に,一時的にアナログ入力ピンがどこにも接続されていない状態(ハイインピーダンス)になります. このとき,Arduinoがロータリスイッチの状態を取得できなくなりますので,誤動作が発生します. ただし,これは回路図から読み取った推測です. 実際には,見当違いであったり,複合的な原因である可能性もありますのでご注意ください. ご指摘があれば,コメントをいただければ幸いです.

次項から,改良した回路について紹介します.

回路
回路図

PDFファイルにて公開します. 回路図中の「ユーザ回路」部分を製作し,Arduinoのアナログ入力ピンに接続してください.

ロータリスイッチ SW1の共通接点Aは,グランドに接続されています. 一方,Arduinoのアナログ入力ピンは,常にSW1の選択接点1に接続されています. スイッチを回転させると,合成抵抗の分圧比が変化し,アナログ入力ピンに印加される電圧が変化します. この変化を読み取ることで,SW1の状態を取得できます.

仮に共通接点Aがいずれの選択端子にも接続されていない状態になっても,アナログ入力ピンは抵抗 R1でプルアップされている状態となり,ハイインピーダンスにはなりません.

回路部品

下表は,回路図中の「ユーザ回路」部分の使用部品表です. 参考単価をクリックすると,秋月電子通商のページに飛びます. 「互換品」と記載されているリンクについては,互換性があると考えられる部品のページに飛びます. ただし,私が互換性および動作を確認したわけではありませんので,ご注意ください.

番号 部品名 型番 数量 参考単価
SW1 ロータリスイッチ 1回路12接点
ノンショーティングタイプ
1 150円
(互換品)
R1, R10 炭素皮膜抵抗 各社 1/4W 4.7kΩ 2 1円
(互換品)
R2 炭素皮膜抵抗 各社 1/4W 470Ω 1 1円
(互換品)
R3 炭素皮膜抵抗 各社 1/4W 680Ω 1 1円
(互換品)
R4 炭素皮膜抵抗 各社 1/4W 820Ω 1 1円
(互換品)
R5 炭素皮膜抵抗 各社 1/4W 1kΩ 1 1円
(互換品)
R6 炭素皮膜抵抗 各社 1/4W 1.2kΩ 1 1円
(互換品)
R7 炭素皮膜抵抗 各社 1/4W 1.5kΩ 1 1円
(互換品)
R8 炭素皮膜抵抗 各社 1/4W 2.2kΩ 1 1円
(互換品)
R9 炭素皮膜抵抗 各社 1/4W 3kΩ 1 1円
(互換品)
R11 炭素皮膜抵抗 各社 1/4W 8.2kΩ 1 1円
(互換品)
R12 炭素皮膜抵抗 各社 1/4W 15kΩ 1 1円
(互換品)
その他 リード線など 適量
回路製作例

私が製作した回路の写真です. 炭素皮膜抵抗R2R12は1/4Wの小型品を使用し,ロータリスイッチの接点間に直接ハンダ付けしました.


回路製作例
スケッチ
A/D変換値からロータリスイッチの状態を取得する方法

まず,ロータリスイッチの各状態における,アナログ入力ピンへの印加電圧を計算してみましょう. 計算結果を下表に示します. 下表から,例えば選択接点2の場合は,アナログ入力ピンへの印加電圧が0.455Vになることがわかります. ArduinoのA/D変換の分解能は10bit(0~1023)ですから,A/D変換値は93になると考えられます. ただし,電源電圧や抵抗値の公差により,各値は多少変動します.

これらの計算結果を基に,A/D変換値と閾値を比較することで,ロータリスイッチの状態を割り出します. 例えば,選択接点が3以下か4以上かを調べるためには,各状態におけるA/D変換値(201と302)の中間値(252)を閾値にします. このような比較処理を繰り返すことで.選択接点が特定できます.

ロータリスイッチの選択接点とアナログ入力ピンへの印加電圧の関係
選択接点 印加電圧 [V] A/D変換値(10bit)
1 0.000 0
2 0.455 93
3 0.983 201
4 1.477 302
5 1.936 396
6 2.351 481
7 2.734 560
8 3.130 641
9 3.491 715
10 3.841 787
11 4.175 855
12 4.459 910
スケッチ例

下記は,スケッチ例 rotary_sw.inoです. A/D変換値からロータリスイッチの状態を取得する,getRotarySwStatus()関数を作成しました. 復帰値1~12でロータリスイッチの状態を,復帰値0で取得エラーを示します.

getRotarySwStatus()関数内の条件分岐処理がif文の入れ子構造になっているのは,ロータリスイッチの状態によって関数内の処理時間が変動しないようにしたかったためです. この関数では,ロータリスイッチがいずれの状態であっても条件分岐処理が3回以内になっています. よりスマートなやり方があるのだとは思いますが….

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
/*
 * ロータリSW実験回路
 * File:   rotary_sw.ino
 * Author: Keitetsu
 */
 
/*
 * ピンアサイン
 */
#define PIN_ROTARYSW 0
 
void setup()
{
    Serial.begin(9600);     // シリアル通信速度を9,600bpsに設定
}
 
void loop()
{
    int sw_inp;
    byte sw_pos;
     
    sw_inp = analogRead(PIN_ROTARYSW);      // A/D変換値を取得
    Serial.print("ADC0: ");
    Serial.println(sw_inp, DEC);            // A/D変換値を送信
     
    sw_pos = getRotarySwStatus(sw_inp);     // ロータリSWの状態を取得
    Serial.print("Status: ");
    Serial.println(sw_pos, DEC);            // ロータリSWの状態を送信
     
    delay(250);                             // 250ミリ秒の待ち時間
}
 
/*
 * ロータリSW状態取得関数
 *
 * 引数:
 * int sw_inp       ロータリSWのアナログ入力
 *
 * 復帰値:
 * byte             ロータリSWの状態
 */
byte getRotarySwStatus(int sw_inp)
{
    if(sw_inp < 349){
        if(sw_inp < 147){
            if(sw_inp < 47){
                return 1;
            }
            else{
                return 2;
            }
        }
        else{
            if(sw_inp < 252){
                return 3;
            }
            else{
                return 4;
            }
        }
    }
    else if(sw_inp < 677){
        if(sw_inp < 520){
            if(sw_inp < 438){
                return 5;
            }
            else{
                return 6;
            }
        }
        else{
            if(sw_inp < 599){
                return 7;
            }
            else{
                return 8;
            }
        }
    }
    else if(sw_inp < 967){
        if(sw_inp < 820){
            if(sw_inp < 750){
                return 9;
            }
            else{
                return 10;
            }
        }
        else{
            if(sw_inp < 883){
                return 11;
            }
            else{
                return 12;
            }
        }
    }
     
    return 0;
}
製品紹介
2014/11/23

16F88 XC8開発例 - 可変抵抗でLEDの輝度を制御する

概要

PIC16F88とMPLAB XC8 C Compilerを使用した開発例として,可変抵抗でLEDの輝度を制御するプログラムを紹介します. この開発例では,PIC16F88のA/D変換モジュールとCCPモジュールのPWMモードを使用しています. 開発環境は下記のとおりです.

PIC PIC16F88-I/P
MPLAB X IDE MPLAB X IDE v2.15
MPLAB XC8 MPLAB XC8 C Compiler v1.32
PICkit 2 MPLAB X IDEを使用して書込み
回路
回路図

PICkit 2を使用してICSP (In Circuit Serial Programming)と電源供給を行うことを前提とした回路です. 今回は,これをブレッドボード上に組んで動作を確認しました.

電源はPICkit 2から5.0Vを供給しています. 内蔵クロックを使用するため,外部発振子は不要です. AN0に可変抵抗を接続し,RB3に電流制限用抵抗 330Ωを経由してLEDを接続しています.


回路図
回路部品

下表は使用部品表です.

番号 部品名 型番 数量 参考単価
U1 PICマイコン Microchip PIC16F88-I/P 1 200円
LED1 LED 各社 各色 1 10円
VR1 可変抵抗 各社 30kΩ B 1 40円
R1 炭素皮膜抵抗 各社 1/4W 10kΩ 1 1円
R2 炭素皮膜抵抗 各社 1/4W 330Ω 1 1円
その他 リード線など 適量
プログラム
main.c

下記はソースファイル「main.c」です.参考になれば幸いです.

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
#include <xc.h>
 
#define _XTAL_FREQ 8000000  // 8MHz
 
#define PR2_DATA 0xFF
#define T2_DIV_BY_1 0b00000000
#define CCP_PWM 0b00001100
 
// 16F88
// CONFIG1
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off)
#pragma config CCPMX = RB3      // CCP1 Pin Selection bit (CCP1 function on RB3)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)
 
// CONFIG2
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal External Switchover bit (Internal External Switchover mode disabled)
 
// プロトタイプ宣言
void setADCChannel(unsigned char channel);
unsigned int readADCValue(void);
void initPWM1(unsigned char pr2, unsigned char t2ckps);
void setPWM1Duty(unsigned int duty);
 
void main(void)
{
    unsigned int duty1;
 
    OSCCON = 0b01110000;    // 内蔵クロックの周波数を8MHzに設定
 
    PORTA = 0x00;           // PORTAを初期化
    PORTB = 0x00;           // PORTBを初期化
 
    TRISA = 0b00000001;     // PORTAの入出力設定
    TRISB = 0b00000000;     // PORTBの入出力設定
 
    ANSEL = 0b00000001;     // AN0を有効化
    ADCON0 = 0b01000001;    // Fosc/16, ADON
    ADCON1 = 0b11000000;    // 右詰め, AVdd, AVss
 
    duty1 = 0;
 
    initPWM1(PR2_DATA, T2_DIV_BY_1);    // CCP1をPWMモードに設定
    // PWM周波数は約7.8kHz(計算式は下記)
    // PWM Frequency = 1 / ((PR2_DATA + 1) * 4 * (1 / _XTAL_FREQ))
    setPWM1Duty(duty1);                 // デューティ値を初期化(10bit, 0-1023)
 
    while(1){
        setADCChannel(0);               // A/D変換対象チャンネルを設定
        __delay_us(50);                 // アクィジション時間(50マイクロ秒)
        duty1 = readADCValue();         // A/D変換値を取得
 
        setPWM1Duty(duty1);             // A/D変換値をデューティ値に設定
    }
}
 
/*
 * A/D変換対象チャンネル設定関数
 *
 * 引数:
 * unsigned char channel    A/D変換対象チャンネル
 *
 * 復帰値:
 * なし
 */
void setADCChannel(unsigned char channel)
{
    ADCON0 &= 0b11000111;               // CHS<2:0>を初期化
    ADCON0 |= (channel << 3);
}
 
/*
 * A/D変換値取得関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * unsigned int             A/D変換値(10bit, 0-1023)
 */
unsigned int readADCValue(void)
{
    GO_nDONE = 0b1;
    while(GO_nDONE);
    return ((ADRESH << 8) + ADRESL);
}
 
/*
 * CCP1モジュール PWM初期設定関数
 *
 * 引数:
 * unsigned char pr2        PR2設定値
 * unsigned char t2ckps     プリスケーラ設定値
 *
 * 復帰値:
 * なし
 */
void initPWM1(unsigned char pr2, unsigned char t2ckps)
{
    PR2 = pr2;
    CCP1CON = CCP_PWM;
    setPWM1Duty(0);
    T2CON = t2ckps;
    T2CON |= 0x04;
}
 
/*
 * CCP1モジュール PWMデューティ値設定関数
 *
 * 引数:
 * unsigned char pr2        PR2設定値
 * unsigned char t2ckps     プリスケーラ設定値
 *
 * 復帰値:
 * なし
 */
void setPWM1Duty(unsigned int duty)
{
    CCPR1L = duty >> 2;
    CCP1CON &= 0b11001111;
    CCP1CON |= 0b00110000 & (duty << 4);
}
2014/11/18

Arduino - ソースコードを複数ファイルに分割して記述する(2)

.inoファイルは外部エディタで編集しづらい

前回の投稿で,スケッチを複数のファイル(タブ)に分割して記述する方法を紹介しました. 前回紹介した方法でも,コンパイルに問題はありません. しかし,できれば.inoファイルは作成せず,.cppと.hファイルのみでコーディングしたいと考えました. Arduino IDEのエディタは使いづらいのでサクラエディタを使用しているのですが,デフォルト設定のサクラエディタでは,.inoファイルは.cppファイルのように色分け表示や自動インデントが行われないからです. そこで,.cppと.hファイルのみでスケッチを構成する方法について調べてみました.

スケッチには最低1つの.inoファイルが必要

Intel Galileo Arduino IDEのユーザですが,調査された方を発見しました.

上記Webページによると,各スケッチには,スケッチフォルダ名と同名の.inoファイルが必要であるとのことです. ただし,.inoファイルには何も記述されていなくても良いようです(ブランクのダミーファイル.) 上記Webページを例に,適当なスケッチをダミーの.inoファイル,.cppファイルおよび.hファイルの組み合わせに変更してみました.

.cppと.hファイルのみで記述する方法
概要

.inoファイルのみで記述されたサンプルスケッチを,ダミーの.inoファイル,.cppファイルおよび.hファイルの組み合わせによる記述に変更します. ソースコードは.cppファイルと.hファイルに記述するため,外部エディタを使用しても色分け表示や自動インデントの機能が使用できます. また,別の開発環境やマイコンへの移植性の改善も期待できます.

サンプルスケッチ

LED点滅回路用のサンプルスケッチ,led_test2.inoです.

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
/*
 * LED点滅回路
 * File:   led_test2.ino
 * Author: Keitetsu
 */
 
#include "led_test2.h"
 
#define PIN_LED 2
 
typedef enum status_led{
    OFF = 0,
    ON
} STATUS_LED;
 
/* プロトタイプ宣言 */
STATUS_LED led_toggle(STATUS_LED led);
 
/*
 * セットアップ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void setup()
{
    pinMode(PIN_LED, OUTPUT);
}
 
/*
 * ループ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void loop()
{
    static STATUS_LED led;
 
    led = led_toggle(led);
    delay(500);
}
 
/*
 * LED点滅関数
 *
 * 引数:
 * STATUS_LED led   LED点滅関数実行前のLEDの状態
 *
 * 復帰値:
 * STATUS_LED       LED点滅関数実行後のLEDの状態
 */
STATUS_LED led_toggle(STATUS_LED led)
{
    if(led == OFF){
        digitalWrite(PIN_LED, HIGH);
        led = ON;
    }
    else{
        digitalWrite(PIN_LED, LOW);
        led = OFF;
    }
 
    return led;
}
サンプルスケッチの記述変更例

6つのファイルに分割して記述しました. まずはダミーファイル,led_test3.inoです.

1
2
3
4
5
/*
 * ダミーファイル
 * File:   led_test3.ino
 * Author: Keitetsu
 */

ヘッダファイル,stdafx.hです.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
 * File:   stdafx.h
 * Author: Keitetsu
 */
 
#ifndef __STDAFX_H__
#define __STDAFX_H__
 
#include "Arduino.h"
 
void setup();
void loop();
 
#endif /* __STDAFX_H__ */

setup()関数およびloop()関数を記述した,led_test3.cppです.

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
/*
 * LED点滅回路
 * File:   led_test3.cpp
 * Author: Keitetsu
 */
 
#include "stdafx.h"
#include "led_test3.h"
#include "led_toggle.h"
 
/*
 * セットアップ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void setup()
{
    pinMode(PIN_LED, OUTPUT);
}
 
/*
 * ループ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void loop()
{
    static STATUS_LED led;
 
    led = led_toggle(led);
    delay(500);
}

ヘッダファイル,led_test3.hです.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 * File:   led_test3.h
 * Author: Keitetsu
 */
 
#ifndef __LED_TEST3_H__
#define __LED_TEST3_H__
 
#define PIN_LED 2
 
typedef enum status_led{
    OFF = 0,
    ON
} STATUS_LED;
 
#endif /* __LED_TEST3_H__ */

led_toggle()関数を記述した,led_toggle.cppです.

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
/*
 * File:   led_toggle.cpp
 * Author: Keitetsu
 */
 
#include "stdafx.h"
#include "led_test3.h"
#include "led_toggle.h"
 
/*
 * LED点滅関数
 *
 * 引数:
 * STATUS_LED led LED点滅関数実行前のLEDの状態
 *
 * 復帰値:
 * STATUS_LED  LED点滅関数実行後のLEDの状態
 */
STATUS_LED led_toggle(STATUS_LED led)
{
    if(led == OFF){
        digitalWrite(PIN_LED, HIGH);
        led = ON;
    }
    else{
        digitalWrite(PIN_LED, LOW);
        led = OFF;
    }
 
    return led;
}

ヘッダファイル,led_toggle.hです.

1
2
3
4
5
6
7
8
9
10
11
12
/*
 * File:   led_toggle.h
 * Author: Keitetsu
 */
 
#ifndef __LED_TOGGLE_H__
#define __LED_TOGGLE_H__
 
/* プロトタイプ宣言 */
STATUS_LED led_toggle(STATUS_LED led);
 
#endif /* __LED_TOGGLE_H__ */
製品紹介
2014/11/17

Arduino - Arduino IDEのエディタのタブ幅を変更する

Arduino IDEの設定ファイル

Arduino IDEの環境設定は,「ファイル」-「環境設定」をクリックして表示されるウィンドウで変更できます. しかし,このウィンドウ上で変更できる項目は限られているため,より詳細な環境設定を行うためには,設定ファイルを直接編集しなければいけません. 設定ファイルのファイル名はpreferences.txtで,この設定ファイルのパスは環境設定のウィンドウで確認できます. パスをクリックすると,エクスプローラで設定ファイルが格納されているフォルダが表示されます.


Arduino
エディタのタブ幅を変更する

設定項目については,下記Webサイトにまとめが掲載されています.

デフォルトのタブ幅は2ですが,下記のようにタブ幅を4に変更しました.

25
editor.tabs.size=4
Arduino IDEのエディタは貧弱…

行番号表示もできませんし,インデントにタブ文字が使用できないようです. 私は,外部エディタとしてサクラエディタを使用し,Arduino IDEはコンパイルにのみ使用しています. 今後のバージョンアップで機能増強が図られるのでしょうか….

製品紹介

Arduino - typedef宣言を使用する際の注意

C/C++のようで違う,Arduino

前回の記事でも書きましたが,関数のプロトタイプ宣言をユーザが記述する必要がないなど,Arduino IDEで使用するプログラミング言語はC/C++とは若干異なります.

実際には,スケッチ内のタブのうち.inoファイルと拡張子のないファイルは,コンパイル前に1つの.cppファイルに統合されるようです. 一方,.cファイルと.cppファイルは個別にコンパイルされ,最後にリンクが実行されます.

これらの処理は,ユーザのコーディングを簡略化することを目的としているのだと思います. しかしながら,ここに罠が隠れており,私はその罠にかかってしまいました.

typedef宣言が無視されてしまう
コンパイルエラーとなるプログラム

一見すると問題のないソースコードのようですが,コンパイルエラーが発生します. メッセージは「led_test2:12: error: 'STATUS_LED' does not name a type」でした.

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
/*
 * LED点滅回路
 * File:   led_test2.ino
 * Author: Keitetsu
 */
 
#define PIN_LED 2
 
typedef enum status_led{
    OFF = 0,
    ON
} STATUS_LED;
 
/*
 * セットアップ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void setup()
{
    pinMode(PIN_LED, OUTPUT);
}
 
/*
 * ループ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void loop()
{
    static STATUS_LED led;
 
    led = led_toggle(led);
    delay(500);
}
 
/*
 * LED点滅関数
 *
 * 引数:
 * STATUS_LED led   LED点滅関数実行前のLEDの状態
 *
 * 復帰値:
 * STATUS_LED       LED点滅関数実行後のLEDの状態
 */
STATUS_LED led_toggle(STATUS_LED led)
{
    if(led == OFF){
        digitalWrite(PIN_LED, HIGH);
        led = ON;
    }
    else{
        digitalWrite(PIN_LED, LOW);
        led = OFF;
    }
 
    return led;
}
問題の原因

この問題の原因は,Arduino IDEがプロトタイプ宣言を自動挿入する位置がプリプロセッサ(#include#defineのこと)の直後であることです. つまり,プロトタイプ宣言はプリプロセッサとtypedef宣言の間,8行目に自動挿入されることになります. したがって,typedef宣言でデータ型を定義する前にそのデータ型を使用したプロトタイプ宣言があることになり,コンパイルエラーとなってしまうのです.

問題の回避方法
方法(1) typedef宣言をヘッダファイルに移行する

Arduino Build Processでも紹介されている問題回避方法です. 自動挿入されるプロトタイプ宣言はプリプロセッサの直後なので,プリプロセッサでインクルードされるヘッダファイルでtypedef宣言を行えば良い,というものです.

方法(1)を適用したプログラムを示します. 下記は既存タブのled_test2.inoです. 新規作成するヘッダファイル,led_test2.hをインクルードしています.

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
/*
 * LED点滅回路
 * File:   led_test2.ino
 * Author: Keitetsu
 */
 
#include "led_test2.h"
 
#define PIN_LED 2
 
/*
 * セットアップ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void setup()
{
    pinMode(PIN_LED, OUTPUT);
}
 
/*
 * ループ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void loop()
{
    static STATUS_LED led;
  
    led = led_toggle(led);
    delay(500);
}
 
/*
 * LED点滅関数
 *
 * 引数:
 * STATUS_LED led   LED点滅関数実行前のLEDの状態
 *
 * 復帰値:
 * STATUS_LED       LED点滅関数実行後のLEDの状態
 */
STATUS_LED led_toggle(STATUS_LED led)
{
    if(led == OFF){
        digitalWrite(PIN_LED, HIGH);
        led = ON;
    }
    else{
        digitalWrite(PIN_LED, LOW);
        led = OFF;
    }
 
    return led;
}

下記は新規タブのled_test2.hです. typedef宣言を記述しています.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
 * File:   led_test2.h
 * Author: Keitetsu
 */
 
#ifndef __LED_TEST2_H__
#define __LED_TEST2_H__
 
typedef enum status_led{
    OFF = 0,
    ON
} STATUS_LED;
 
#endif
方法(2) プロトタイプ宣言を自分で記述する

プロトタイプ宣言が既に存在する場合は,自動挿入が実行されません. したがって,typedef宣言の後方に自分でプロトタイプ宣言を書いておくことでも,この問題を回避できます.

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
/*
 * LED点滅回路
 * File:   led_test2.ino
 * Author: Keitetsu
 */
 
#include "led_test2.h"
 
#define PIN_LED 2
 
typedef enum status_led{
    OFF = 0,
    ON
} STATUS_LED;
 
/* プロトタイプ宣言 */
STATUS_LED led_toggle(STATUS_LED led);
 
/*
 * セットアップ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void setup()
{
    pinMode(PIN_LED, OUTPUT);
}
 
/*
 * ループ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void loop()
{
    static STATUS_LED led;
 
    led = led_toggle(led);
    delay(500);
}
 
/*
 * LED点滅関数
 *
 * 引数:
 * STATUS_LED led   LED点滅関数実行前のLEDの状態
 *
 * 復帰値:
 * STATUS_LED       LED点滅関数実行後のLEDの状態
 */
STATUS_LED led_toggle(STATUS_LED led)
{
    if(led == OFF){
        digitalWrite(PIN_LED, HIGH);
        led = ON;
    }
    else{
        digitalWrite(PIN_LED, LOW);
        led = OFF;
    }
 
    return led;
}
製品紹介
2014/11/16

Arduino - ソースコードを複数ファイルに分割して記述する

Arduino,はじめました

かなり今更ですが,最近になってArduinoを触り始めました. Arduinoと言っても,基板は買わずにATmega328Pを買ってきて,ブートローダと開発環境だけを使用しています. 参考書なども買っていないため,インターネットで調べつつ試行錯誤しています.

最初に躓いたのは,ソースコードを複数ファイルに分割して記述する方法についてです. Arduino Build Processでも説明されていますが,備忘録として記述しておこうと思います.

ソースコードを複数ファイルに分割して記述する
サンプルプログラム

説明に使用するサンプルプログラム,led_test.inoです. PIN_LEDで指定したデジタル入出力ピンのLEDを点滅させるプログラムです. LEDを接続する際は,電流制限用抵抗を忘れずに挿入してください.

今回は,loop()関数内の32~35行目の処理を関数化し,別ファイルに記述します.

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
/*
 * LED点滅回路
 * File:   led_test.ino
 * Author: Keitetsu
 */
 
#define PIN_LED 2
 
/*
 * セットアップ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void setup(){
    pinMode(PIN_LED, OUTPUT);
}
 
/*
 * ループ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void loop(){
    digitalWrite(PIN_LED, HIGH);
    delay(500);
    digitalWrite(PIN_LED, LOW);
    delay(500);
}
新規タブを作成する

Arduinoでは,1つのスケッチで複数ファイルを扱うとき,各々を「タブ」と呼ぶようです. 新規タブは,ウィンドウ右側のアイコンをクリックすると表示されるメニューから作成できます. 私は最初これを知りませんでした….


Arduino

ウィンドウ下部に表示されるテキストボックスで,新規タブ名を指定します. Arduino Build Processによれば,タブとして扱うことができるファイルは,拡張子なし,.c,.cppまたは.hのいずれかのようです. しかし,私の使用しているArduino 1.0.5-r2では,このテキストボックスで拡張子を指定しなかった場合,拡張子は.inoになります. 1つのスケッチで,複数の.inoファイルが扱えるようです.

関数を記述するのであれば,C++言語ライクではなくC言語ライクに書くのであっても,個人的には拡張子は.cppを指定すれば良いと思います. 今回は拡張子を指定しませんでしたので,led_func.inoというファイルがスケッチフォルダに新規作成されました.


Arduino
ソースコードを編集する

上記のサンプルプログラムを分割して記述した例を示します. loop()関数内の32~35行目の処理を,led_func()関数として関数化しています. このように関数化やファイル分割を行うことで,ソースコードの再利用性が向上します.

下記は既存タブのled_test.inoです.

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
/*
 * LED点滅回路
 * File:   led_test.ino
 * Author: Keitetsu
 */
 
#define PIN_LED 2
 
/*
 * セットアップ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void setup(){
    pinMode(PIN_LED, OUTPUT);
}
 
/*
 * ループ関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void loop(){
    led_func();
}

下記は新規タブのled_func.inoです.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 * File:   led_func.ino
 * Author: Keitetsu
 */
 
/*
 * LED点滅関数
 *
 * 引数:
 * なし
 *
 * 復帰値:
 * なし
 */
void led_func(){
    digitalWrite(PIN_LED, HIGH);
    delay(500);
    digitalWrite(PIN_LED, LOW);
    delay(500);
}

なお,関数のプロトタイプ宣言は,Arduinoにおいてはコンパイル時に自動挿入されるため,必要ありません. また,作成したタブが拡張子.hのヘッダファイルでない限り,#includeの記述も必要ありません. しかし,これらにも例外があります. 詳細については,別記事「Arduino - typedef宣言を使用する際の注意」をご覧ください.

追加情報

よりC/C++言語ライクに記述したい場合は,Arduino - ソースコードを複数ファイルに分割して記述する(2)をご覧ください.

製品紹介
2014/11/14

16F88 アセンブリ言語開発例 - LED点滅回路

概要

PIC16F88とMPASM (アセンブリ言語)を使用した開発例として,LED点滅回路のプログラムを紹介します. 開発環境は下記のとおりです.

PIC PIC16F88-I/P
MPLAB X IDE MPLAB X IDE v2.15
MPASM MPASMWIN v5.57
PICkit 2 MPLAB X IDEを使用して書込み
回路
回路図

電源はPICkit 2から5.0Vを供給しています. 内蔵クロック 8MHzで動作させています. RB0には,電流制限用抵抗 330Ωを経由してLEDが接続されています.


回路図
回路部品

下表は使用部品表です.

番号 部品名 型番 数量 参考単価
U1 PICマイコン Microchip PIC16F88-I/P 1 200円
LED1 LED 各社 各色 1 10円
R1 炭素皮膜抵抗 各社 1/4W 10kΩ 1 1円
R2 炭素皮膜抵抗 各社 1/4W 330Ω 1 1円
その他 リード線など 適量
プログラム
main.asm

下記はソースファイル「main.asm」です.参考になれば幸いです.

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
        LIST        P=PIC16F88
        #INCLUDE    "P16F88.INC"
 
; CONFIG1
; __config 0xEF70
 __CONFIG _CONFIG1, _FOSC_INTOSCIO & _WDTE_OFF & _PWRTE_ON & _MCLRE_ON & _BOREN_ON & _LVP_OFF & _CPD_OFF & _WRT_OFF & _CCPMX_RB3 & _CP_OFF
; CONFIG2
; __config 0xFFFC
 __CONFIG _CONFIG2, _FCMEN_OFF & _IESO_OFF
 
CNT1    EQU         20H
CNT2    EQU         21H
CNT3    EQU         22H
 
        ORG         0
INIT
        BSF         STATUS,RP0  ; Bank1に切替え
        MOVLW       70H         ; 内蔵クロックの周波数を8MHzに設定
        MOVWF       OSCCON
        BCF         STATUS,RP0  ; Bank0に切替え
 
        CLRF        PORTA       ; PORTAを初期化
        CLRF        PORTB       ; PORTBを初期化
 
        BSF         STATUS,RP0  ; Bank1に切替え
        CLRF        TRISA       ; PORTAの入出力設定
        CLRF        TRISB       ; PORTBの入出力設定
 
        CLRF        ANSEL       ; A/D変換を無効化
        CLRF        ADCON1
        BCF         STATUS,RP0  ; Bank0に切替え
        CLRF        ADCON0
 
MAIN
        MOVLW       01H
        XORWF       PORTB,F     ; RB0をビット反転
        CALL        TIMER3      ; 500ミリ秒タイマサブルーチンの呼出し
        GOTO        MAIN        ; 無限ループ
 
TIMER1                          ; 0.5ミリ秒タイマサブルーチン
        MOVLW       D'250'
        MOVWF       CNT1
LOOP1
        NOP
        DECFSZ      CNT1,F
        GOTO        LOOP1
        RETURN
 
TIMER2                          ; 100ミリ秒タイマサブルーチン
        MOVLW       D'200'
        MOVWF       CNT2
LOOP2
        CALL        TIMER1
        DECFSZ      CNT2,F
        GOTO        LOOP2
        RETURN
 
TIMER3                          ; 500ミリ秒タイマサブルーチン
        MOVLW       D'5'
        MOVWF       CNT3
LOOP3
        CALL        TIMER2
        DECFSZ      CNT3,F
        GOTO        LOOP3
        RETURN
 
        END
更新履歴
2014/11/15 プログラムを修正(動作にほぼ影響なし)
  • サブルーチンTIMER2内のNOP命令を削除
  • サブルーチンTIMER3内のNOP命令を削除
2014/11/14 公開開始