PIC18F45K22CPUボード用ヘッダファイル(mono_con.h)
ものづくりコンテストではポート定義のヘッダファイルに限り持ち込みが認められていて、下記のようなヘッダファイルを用意しておくとプログラム作成の時間短縮に役立ちます。
/*
* File: mono_con.h
* Author: jsd
*
* Created on 2018/04/17, 14:14
*/
#ifndef MONO_CON_H
#define MONO_CON_H
#ifdef __cplusplus
extern "C" {
#endif
// LED G, B, R
#define LEDG LATDbits.LATD1
#define LEDB LATDbits.LATD5
#define LEDR LATEbits.LATE2
// BUZZER
#define BZ LATEbits.LATE0
// Latch
#define LT LATDbits.LATD2
// 7seg
#define SEG7L LATBbits.LATB0
#define SEG7R LATBbits.LATB1
#define SEG7(d) (LATC=(d))
#define SEG7_a LATCbits.LATC0
#define SEG7_b LATCbits.LATC1
#define SEG7_c LATCbits.LATC2
#define SEG7_d LATCbits.LATC3
#define SEG7_e LATCbits.LATC4
#define SEG7_f LATCbits.LATC5
#define SEG7_g LATCbits.LATC6
#define SEG7_dp LATCbits.LATC7
// Stepping motor
#define STM(d) (LATC=(d)&0x0f)
// DC motor
#define DCM(d) (LATC=(((d)<<6)&0xc0)
// SW status
#define ENCA (PORTAbits.RA3)
#define ENCB (PORTAbits.RA4)
#define SW2 (PORTAbits.RA5==0)
#define SW1 (PORTAbits.RA6==0)
#ifdef __cplusplus
}
#endif
#endif /* MONO_CON_H */
割り込みの使い方サンプルプログラム(プロジェクトkadaiのmain.c)
下のプログラムは割り込みを使うためのサンプルソースです。
プログラムの最初に ヘッダファイルmono_con.hを読み込むための#include文を追加します。
#include "mcc_generated_files/mcc.h"
#include "mono_con.h"
10mS 毎に呼び出される割り込み関数inttmr0()でスイッチの読み込みなどの処理をおこないます。 関数名inttmr0()をmain関数の最初で登録して割り込みを有効にすることで割り込み関数が使えるようになります。 割り込み関数を使う上での注意事項 関数内で宣言するローカル変数は割り込み関数を抜けると記憶されないので変数はグローバル(外部)変数か、関数内で宣言する場合はstatic宣言を使います。 割り込み関数の中で無限ループや長い時間待ちを使ってはいけません。出来るだけ早く終わるように処理を記述します。
/* 10ms毎に起動するタイマ割り込み関数
* mainの最初で登録することで周期的に呼ばれる
* 入力処理と制御をここに記述
* ※割り込み関数使用上の注意
* 1.関数は出来るだけ早く抜けること(無限ループ厳禁)
* 2.変数はグローバル変数(関数の外に書く)にするかstatic宣言をつけること
*/
int tmr1s,tmr10ms; // これはグローバル変数
uint16_t tmr0_s,tmr0_e; // デバッグ用の変数
void inttmr0(void)
{
static int i;
tmr0_s = TMR0_ReadTimer(); // デバッグ用、課題処理には不要
// 課題1回答例 ここから
if( SW2 && SW1 ){
DCM(2);
printf("dcm(2) PORTA=%02x,LATC=%02x\n",PORTA,LATC);
LEDG = 1;
}else if( !SW2 && SW1 ){
DCM(1);
printf("dcm(1) PORTA=%02x,LATC=%02x\n",PORTA,LATC);
LEDB = 1;
}else{
DCM(0);
LEDG = 0;
LEDB = 0;
}
// ラッチの立ち上がりでモータドライバに信号が伝わる
LT = 0;
__delay_us(10); // パルスを観測し易くするために10us待つ
LT = 1;
// 課題1回答例 ここまで
// tmr10msをインクリメントし100になったらtmr1sをカウントアップ
if( 100 <= ++tmr10ms ){
// この部分は1秒ごとに実行
tmr1s++;
tmr10ms = 0;
}
tmr0_e = TMR0_ReadTimer(); // デバッグ用:課題処理には不要
}
tmr01msは100us毎に呼び出される割り込み関数です ブザーを大きな音で鳴らすためには1KHz以上の周波数でON/OFFを繰り返す 必要があるので10KHzの周期にしています。 tmr1_s, tmr1_eには割り込み開始時と終了時のタイマカウンタの値が 入り、printfで表示するとどのくらいの余裕があるかを確認できます。
/* 0.1ms毎に起動するタイマ割り込み関数
* 出力処理をここに記述
* ※関数使用上の注意はtmr0と同じ
*/
int tmr01ms; // これはグローバル変数
uint16_t tmr1_s,tmr1_e; // デバッグ用の変数
void inttmr1(void)
{
static int t; // スタティック変数
tmr1_s = TMR1_ReadTimer(); // デバッグ用、課題処理には不要
// 0.1ms * 9000 = 0.9秒毎に '*' を出力
// mainのprintfを邪魔する様子を観察するために書いてある
if( (tmr10ms==0)&&(45 <= ++tmr01ms) ){
tmr01ms = 0;
printf("*");
}
tmr1_e = TMR1_ReadTimer(); // デバッグ用、課題処理には不要
}
main関数では割り込み関数を登録してタイマー割り込みを開始する処理だけを記述して 課題の処理は全て割り込み関数でおこないます。
void main(void)
{
// Initialize the device
SYSTEM_Initialize();
/* 次のコメントを除いた4行は新しく追加する必要があります。 登録関数とタイマースタート関数の名前は生成されたソースで確認することが出来ます。*/
// タイマー0割り込み関数を登録 (tmr0.c の関数を呼ぶ)
TMR0_SetInterruptHandler(inttmr0);
// タイマー1割り込み関数を登録 (tmr1.c の関数を呼ぶ)
TMR1_SetInterruptHandler(inttmr1);
// タイマー0をスタート (tmr0.c の関数を呼ぶ)
TMR0_StartTimer();
// タイマー1をスタート (tmr1.c の関数を呼ぶ)
TMR1_StartTimer();
/* 割り込みを有効にするために次の2行のコメントを外して有効にします。*/
// If using interrupts in PIC18 High/Low Priority Mode you need to enable the Global High and Low Interrupts
// If using interrupts in PIC Mid-Range Compatibility Mode you need to enable the Global and Peripheral Interrupts
// Use the following macros to:
// Enable the Global Interrupts:この下の行をコメントアウトし有効にする
INTERRUPT_GlobalInterruptEnable();
// Disable the Global Interrupts
//INTERRUPT_GlobalInterruptDisable();
// Enable the Peripheral Interrupts:この下の行をコメントアウトし、有効にする
INTERRUPT_PeripheralInterruptEnable();
// Disable the Peripheral Interrupts
//INTERRUPT_PeripheralInterruptDisable();
/* ここにはデバッグ用のprintf文を書いていますが 課題の処理ではメイン関数の無限ループは空のままで構いません。*/
// メインループはデバッグ用のprintfだけを書く
// 課題の処理には必要無し
while (1)
{
// 1秒毎にインクリメントされるtmr1sの値を表示
// tmr0_s : tmr0割り込み開始時のタイマー値
// tmr0_e : tmr0割り込み終了時のタイマー値
// ※0になると次の割り込みが始まる
// tmr1_s : tmr1割り込み開始時のタイマー値
// tmr2_e : tmr1割り込み開始時のタイマー値
// ※0になると次の割り込み開始
if( tmr10ms == 0 ){
printf("tmr1s=%d, tmr0_s=%u, tmr0_e=%u, tmr1_s=%u, tmr1_e=%u\n",
tmr1s, 0xffff-tmr0_s, 0xffff-tmr0_e, 0xffff-tmr1_s, 0xffff-tmr1_e);
}
}
}
課題1回答例 (MCCが生成したコメントを除く)
ポイント 条件文で SW1 == 0 と書く代わりに !SW1 と記述している 教科書的には等号・不等号をきちんと書いた方が良いが、 プログラムを書いている時に等号==を代入の記号=と間違えて記述する場合があり、 気が付きにくいバグとなるので後者の記述にした方が有利である。
#include "mcc_generated_files/mcc.h"
#include "mono_con.h"
/* 10ms毎に起動するタイマ割り込み関数
*/
void inttmr0(void)
{
if( !SW2 && SW1 ){ // SW2:OFF SW1:ON
DCM(2);
}else if( SW2 && SW1 ){ // SW2:ON SW1:ON
DCM(1);
}else{
DCM(0);
}
// ラッチの立ち上がりでモータドライバに信号が伝わる
LT = 0;
__delay_us(10); // パルスを観測し易くするために10us待つ
LT = 1;
}
/* 10ms毎に起動するタイマ割り込み関数
* 出力処理をここに記述
* 課題1では必要ないが、プロジェクトの初期化部分を共通化するために空の関数を記述
*/
void inttmr1(void)
{
}
void main(void)
{
// Initialize the device
SYSTEM_Initialize();
// タイマー0割り込み関数を登録 (tmr0.c の関数を呼ぶ)
TMR0_SetInterruptHandler(inttmr0);
// タイマー1割り込み関数を登録 (tmr1.c の関数を呼ぶ)
TMR1_SetInterruptHandler(inttmr1);
// タイマー0をスタート (tmr0.c の関数を呼ぶ)
TMR0_StartTimer();
// タイマー1をスタート (tmr1.c の関数を呼ぶ)
TMR1_StartTimer();
// Enable the Global Interrupts:この下の行をコメントアウトし有効にする
INTERRUPT_GlobalInterruptEnable();
// Enable the Peripheral Interrupts:この下の行をコメントアウトし、有効にする
INTERRUPT_PeripheralInterruptEnable();
while (1)
{
}
}
課題2回答例
main関数は課題1と同じなので省略しています ポイント シーケンシャルな処理をするためにステートマシンを使っています。 ステートマシンとはswitch文と状態変数を使って割り込みが呼ばれるたびに 状態変数で処理内容を選択する書き方です。 慣れると複雑なシーケンスでも簡単に記述できます。 スイッチがONした瞬間をとらえるためにSWの状態を記憶しておいて 次のタイミングでスイッチがONでと記憶した状態がOFFの時にスイッチON時の処理をしています。
#include "mcc_generated_files/mcc.h"
#include "mono_con.h"
/* 10ms毎に起動するタイマ割り込み関数
*/
void inttmr0(void)
{
static int sw1; // SW1の状態を記憶する変数 ※static宣言が必要
static int state=0; // ステートマシン用変数
switch(state){
case 0: // 消灯
LEDR = LEDG = LEDB = 0; // 全部消灯
if( SW2 )state = 1; // トグルSWがONになったら 1 へ
break;
case 1: // 黄色
LEDR = LEDG = 1; // 緑と赤を点灯(黄色)
if( !SW2 ) state = 0; // トグルSWがOFFになったら 0 へ
if( SW1 && !sw1 ){ // タクトSWの立ち上がりで2へ
LEDR = LEDG = 0;
state = 2;
}
break;
case 2: // 緑点灯
LEDG = 1;
if( !SW2 ) state = 0;
if( SW1 && !sw1 ){
LEDG = 0;
state = 3;
}
break;
case 3: // 青点灯
LEDB = 1;
if( !SW2 ) state = 0;
if( SW1 && !sw1 ){
LEDB = 0;
state = 4;
}
break;
case 4: // 赤点灯
LEDR = 1;
if( !SW2 ) state = 0;
if( SW1 && !sw1 ){
LEDR = 0;
state = 2;
}
break;
default:
state = 0;
break;
}
sw1 = SW1;
// printf文は無くても良いし、あっても課題の動作には影響が無い
printf("state=%d,SW1=%d,sw1=%d\n",state,SW1,sw1);
}
/* 10ms毎に起動するタイマ割り込み関数:関数名は任意
* mainの最初で登録することで周期的に呼ばれる
* 出力処理をここに記述
* ※関数使用上の注意はtmr0と同じ
*/
void inttmr1(void)
{
}
課題3回答例
ポイント エンコーダ信号もスイッチ信号と同じく変化したタイミングでだけ処理をおこなうのがポイントです。 使っているエンコーダはA相の信号がHighからLowになった時でないと方向判別が出来ない仕様です。 割り込みを使った7SEGの時分割表示はパターンを覚えればとても簡単です。
#include "mcc_generated_files/mcc.h"
#include "mono_con.h"
/* 10ms毎に起動するタイマ割り込み関数
*/
int seg7R = 16, seg7L = 16;
int ct = 0; // `エンコーダカウント
void inttmr0(void)
{
static int encA; // SW1の状態を記憶する変数 ※static宣言が必要
// ロータリーエンコーダの信号でct をカウントアップ・ダウン
if( !ENCA && encA ){ // ENCAがHighからLowになった時
if( ENCB ){
ct++;
if( 20 < ct ) ct = 20;
}else{
ct--;
if( ct < -5 ) ct = -5;
}
}
encA = ENCA; // 次のために記憶
if( !SW2 ) ct = 0;
// 7SEGに表示
if( 0 <= ct ){
seg7L = (ct/10) % 10; // seg7L に ctの上位桁を入れる
seg7R = ct % 10; // seg7R にctの下位桁を入れる
}else{
seg7L = 17;
seg7R = -ct % 10; // seg7R にctの下位桁を入れる
}
}
/* 10ms毎に起動するタイマ割り込み関数:関数名は任意
* mainの最初で登録することで周期的に呼ばれる
* 出力処理をここに記述
* ※関数使用上の注意はtmr0と同じ
*/
/*****************************************************************
7SEG LED の表示データ
****************************************************************/
const unsigned char SEG7D[] = { // 7セグメントLEDの表示データ
0b00111111, // 0 = 0
0b00000110, // 1 = 1
0b01011011, // 2 = 2
0b01001111, // 3 = 3
0b01100110, // 4 = 4
0b01101101, // 5 = 5
0b01111101, // 6 = 6
0b00100111, // 7 = 7
0b01111111, // 8 = 8
0b01101111, // 9 = 9
0b01110111, // 10 = A
0b01111100, // 11 = b
0b00111001, // 12 = C
0b01011110, // 13 = d
0b01111001, // 14 = E
0b01110001, // 15 = F
0b00000000, // 16 = 消灯
0b01000000, // 17 = -(マイナス)
0b01010100, // 18 = OFF(冖)
0b00001000, // 19 = ON (_)
};
void inttmr1(void)
{
static int selseg, t;
// 7SEG 表示処理
if( selseg ){ // 右の7SEG点灯
SEG7R = 1;
SEG7L = 0;
SEG7(SEG7D[seg7R]);
}else{ // 左の7SEG点灯
SEG7R = 0;
SEG7L = 1;
SEG7(SEG7D[seg7L]);
}
selseg = !selseg; // フラグを反転する
// ブザーを鳴らす
if( ct == -5 ){
if( 2 <= ++t ){
t = 0;
BZ = !BZ;
}
}
}
課題4回答例
ポイント エンコーダのカウントと7SEG表示は課題3と同じでカウンタの範囲を±3に制限するだけです。 割り込みを使うとステッピングモータ駆動も簡単に追加することが出来ます。 ステッピングモータ駆動信号と7SEG表示信号が重なっているため、 割り込み関数の中で最初に駆動信号をラッチしてから表示信号をセットするように注意しなければなりません。 順序が逆の場合は7SEGの表示がでたらめになります。 その理由を考えてみるのはハードを理解するうえで良い勉強になるでしょう。
/* 10ms毎に起動するタイマ割り込み関数
*/
int seg7R = 16, seg7L = 16;
int ct = 0;
int stmct = 0; // ステッピングモータ回転指令カウンタ
void inttmr0(void)
{
static int sw1,encA; // SW1の状態を記憶する変数 ※static宣言が必要
static int stmpos = 0, tgtpos = 0;
static int state = 0;
// ロータリーエンコーダの信号でct をカウントアップ・ダウン
if( !ENCA && encA ){ // ENCAがHighからLowになった時
if( ENCB ){
ct++;
if( 3 < ct ) ct = 3;
}else{
ct--;
if( ct < -3 ) ct = -3;
}
}
encA = ENCA; // 次のために記憶
if( !SW2 ) ct = 0;
// ステッピングモーター制御
if( SW1 && !sw1 ) tgtpos = ct*120; // 目標位置
sw1 = SW1;
switch(state){
case 0: // 目標位置との差分に指令値をセット
if( tgtpos != stmpos ){
stmct = tgtpos - stmpos;
stmpos = tgtpos;
state = 1;
}
break;
case 1: // ステッピングモーターが止まるまで待つ
if( stmct == 0 ) state = 0;
break;
default:
state = 0;
break;
}
if( tgtpos != stmpos ){
}
// 7SEGに表示
if( 0 <= ct ){
seg7L = (ct/10) % 10; // seg7L に ctの上位桁を入れる
seg7R = ct % 10; // seg7R にctの下位桁を入れる
}else{
seg7L = 17;
seg7R = -ct % 10; // seg7R にctの下位桁を入れる
}
//printf("stmct=%d\n",stmct);
}
/* 10ms毎に起動するタイマ割り込み関数:関数名は任意
* mainの最初で登録することで周期的に呼ばれる
* 出力処理をここに記述
* ※関数使用上の注意はtmr0と同じ
*/
/*****************************************************************
7SEG LED の表示データ
****************************************************************/
const unsigned char SEG7D[] = { // 7セグメントLEDの表示データ
0b00111111, // 0 = 0
0b00000110, // 1 = 1
0b01011011, // 2 = 2
0b01001111, // 3 = 3
0b01100110, // 4 = 4
0b01101101, // 5 = 5
0b01111101, // 6 = 6
0b00100111, // 7 = 7
0b01111111, // 8 = 8
0b01101111, // 9 = 9
0b01110111, // 10 = A
0b01111100, // 11 = b
0b00111001, // 12 = C
0b01011110, // 13 = d
0b01111001, // 14 = E
0b01110001, // 15 = F
0b00000000, // 16 = 消灯
0b01000000, // 17 = -(マイナス)
0b01010100, // 18 = OFF(冖)
0b00001000, // 19 = ON (_)
};
/* ステッピングモーター相励磁データ
* STMD[stmidx]の形でstmidxを0,1,2,3,0,1,2,3,...とすると
* ここに設定したパターンが順番に呼び出される
* それをモータードライバーに接続されたポートに出力してモーターを回す
*/
const unsigned char STMD[] = {
// oooo にモーターが回転する相励磁パターンを入れる
0b0001, // 0 A 励磁 (A相だけ1にする)
0b0010, // 1 B 励磁 (B相だけ1)
0b0100, // 2 /A 励磁(/A相だけ1)
0b1000, // 3 /B 励磁(/B相だけ1)
};
void inttmr1(void)
{
static int selseg, t, stmidx;
// ステッピングモーター駆動
// 7SEGの信号と共通(時分割)のため先にステッピングモーター信号をセット
SEG7R = SEG7L = 0; // ちらつき防止のため7SEGGをOFFにする
if( 50 <= ++t ){ // ステッピングモータの速度調整
t = 0;
if( 0 < stmct ){
if( --stmidx < 0 ) stmidx = 3; // 時計回り
stmct--;
}else if( stmct < 0 ){
if( 3 < ++stmidx ) stmidx = 0; // 反時計回り
stmct++;
}
STM(STMD[stmidx]); // 相励磁信号セット
LT = 0; // 相励磁信号をラッチ
LT = 1;
}
#if 1
// 7SEG 表示処理
if( selseg ){ // 右の7SEG点灯
SEG7R = 1;
SEG7L = 0;
SEG7(SEG7D[seg7R]);
}else{ // 左の7SEG点灯
SEG7R = 0;
SEG7L = 1;
SEG7(SEG7D[seg7L]);
}
selseg = !selseg; // フラグを反転する
#endif
}