マイコンでチャタリング除去

昔書いたソースを眺めていたら、こういう方法でチャタリング除去をしていました。

前提

H8/3694Fのポート5にタクトスイッチが4個ぶら下がっている。マイコン内部でプルアップしている。ローパスフィルタはつけていない。そのためソフトウェアでチャタリング除去する。

方法

ポートを3回見て、3回とも0だったら0とみなす。3回とも1だったら1とみなす。それ以外だったら前回の値と同じとみなす。

真理値表を書くと以下のようになります。
out0は前回みなした値。p2は2回前のポートの値。p1は1回前のポートの値。p0は現在のポートの値。out1は今回みなす値。

真理値表
out0 p2 p1 p0 out1
0 0 0 0 0
0 0 0 1 0
0 0 1 0 0
0 0 1 1 0
0 1 0 0 0
0 1 0 1 0
0 1 1 0 0
0 1 1 1 1
1 0 0 0 0
1 0 0 1 1
1 0 1 0 1
1 0 1 1 1
1 1 0 0 1
1 1 0 1 1
1 1 1 0 1
1 1 1 1 1

ここからout1を得る論理式を導きます。
まず真理値表からカルノー図を書きます。

カルノー
out0 p2\p1 p0 00 01 11 10
00
01
11
10

そしてカルノー図から論理式を書きます。

論理式

out1 = out0・p2 + out0・p1 + out0・p0 + p2・p1・p0

C言語によるソース
#include "3694s.h"

/**
 * チャタリング除去。タイマーで10msごとに呼び出す。
 */
unsigned char polling(void)
{
    static unsigned char out = 0xff, p2 = 0xff, p1 = 0xff;
    unsigned char p0 = IO.PDR5.BYTE;
    out = (out & p2) | (out & p1) | (out & p0) | (p2 & p1 & p0);
    p2 = p1;
    p1 = p0;
    return out;
}
おまけ GCCが吐き出した上記のソースのアセンブラを眺めてみる

out1 = out0(p2+p1+p0) + p2・p1・p0 のように最適化されてますね。

$ h8300-hms-gcc -S -mh -mn -O1 test.c
$ cat test.s
        .file   "test.c"
        .h8300hn
        .section .data
out___0:
        .byte   -1
p2___1:
        .byte   -1
p1___2:
        .byte   -1
        .section .text
        .align 1
        .global _polling
_polling:
        mov.l   er6,@-er7
        mov.w   r7,r6
        mov.l   er4,@-er7
        mov.b   @-40:8,r4l
        mov.b   @p2___1,r0l
        mov.b   @p1___2,r1l
        mov.b   r0l,r2l
        or      r1l,r2l
        or      r4l,r2l
        mov.b   @out___0,r3l
        and     r3l,r2l
        and     r1l,r0l
        and     r4l,r0l
        or      r0l,r2l
        mov.b   r2l,@out___0
        mov.b   r1l,@p2___1
        mov.b   r4l,@p1___2
        mov.b   @out___0,r0l
        extu.w  r0
        mov.l   @er7+,er4
        mov.l   @er7+,er6
        rts
        .end
        .ident  "GCC: (GNU) 3.4.6"
おまけ2 スイッチが押されたときにフラグを立てる(試してません)
#include "3694s.h"

union {
    unsigned char BYTE;
    struct {
        unsigned char B7:1;
        unsigned char B6:1;
        unsigned char B5:1;
        unsigned char B4:1;
        unsigned char B3:1;
        unsigned char B2:1;
        unsigned char B1:1;
        unsigned char B0:1;
    } BIT;
} SW = {0};

void polling(void)
{
    static unsigned char q1 = 0xff, q0 = 0xff, p2 = 0xff, p1 = 0xff;
    unsigned char p0 = IO.PDR5.BYTE;
    q0 = (q0 & p2) | (q0 & p1) | (q0 & p0) | (p2 & p1 & p0);
    SW.BYTE &= q1 & ~q0;
    q1 = q0;
    p2 = p1;
    p1 = p0;
}

2017-01-02追記

ポート見るの,2回でいいような気がしてきました。

真理値表
out0 p1 p0 out1
0 0 0 0
0 0 1 0
0 1 0 0
0 1 1 1
1 0 0 0
1 0 1 1
1 1 0 1
1 1 1 1
カルノー
out0\p1 p0 00 01 11 10
0
1
論理式

out1 = p0・p1 + out0・p0 + out0・p1

ソース
#include "3694s.h"
#include <stdint.h>

/**
 * チャタリング除去。タイマーで10msごとに呼び出す。
 */
uint8_t polling()
{
    static uint8_t out = 0xff;
    static uint8_t p1 = 0xff;
    uint8_t p0 = IO.PDR5.BYTE;
    out = (p0 & p1) | (out & p0) | (out & p1);
    p1 = p0;
    return out;
}

なんならビット演算しなくてもいい気がしてきました。

ソース
#include "3694s.h"
#include <stdint.h>

/**
 * チャタリング除去。タイマーで10msごとに呼び出す。
 */
uint8_t polling()
{
    static uint8_t out = 0xff;
    static uint8_t p1 = 0xff;
    uint8_t p0 = IO.PDR5.BYTE;
    if (p0 == p1) {
      out = p0;
    }
    p1 = p0;
    return out;
}