ウォシュレットの修理 その2 いや改造か?

修理、改造

 
ウォシュレットの水量調節が出来なくなった件、CRCぶっかけて何とか動くようになったのもつかの間、すぐに止まってしまいました。
外したモータはこんな状態。ひどいものです。

このモータは、国内には個人で手に入れられるようなところは無い様で、中華サイトを見てもロット2000個なんて表示になっていました。

面倒だなと思いながらもサンプルを購入するべく交渉し、とある業者から色よい返事をもらうことが出来ました。ところがその直後にgoogle先生から良い情報が。
実はこのモータ、同じ形状のものが 24byj48 なる型番で売られていることがわかりました。家電や車など結構いろいろなところで使われている様子。コレなら中華サイトで1つから購入できることがわかりました。たとえばこれ。電圧が少々違いますが、抵抗入れれば何とでもなります。ということで購入しました。
トイレの方はしばらく固定水量で我慢する事に。

で、このほどモータが届きましたので、早速取り付けを行いました。ネジ穴位置とか違っておりますが、そこはヤスリで削ったりして適当につくようにします。んで、動かしましたところ、何かがおかしい。モータは確かに回っているのですが、水量表示と実際の水量が何か合わないような、というか時々変な音がします。
おかしいと思って一度モータを取り外して動作を見てみました。すると、どうも期待通りの動きをしていないことがわかりました。このモータは電源投入時に初期化動作として、最大角→最小角→中央という動きをするのですが、最後に戻る中央が中央では無いようです。さらに良くみてみると、もう一度初期化するとちょっと中央位置がずれ、さらにもう一度やるとさらにずれ、といった具合です。
これはまずい。どうもパルスあたりの回転角が壊れたモータと違うようです。
ウォシュレットの制御系には手が出ません。これはどうしたものか….しばし考えた後、「改造だ」という結論に到達しました。こんなちっちゃなモータが壊れたくらいで買い替えたくはありません。

ということで構想です。
目的は水量の調節だけですので完全独立制御で行く事にします。つまりウォシュレットの制御系とはまったく独立して後付けの回路で水量調整部分だけを動かすということです。
細かいことを言えば、ウォシュレットを使った後のノズルの自動洗浄や、ノズル掃除モード時の水量のコントロールが行われなくなりますが、頻度も低くたいしたことはありません。掃除の時には必ず水量を下げておくようにすれば良いのです。(そうしないと噴水になります。)

制御は以下のように考えます。

  • 回転角度範囲はおおよそ270度。取り付け時に調整できること。
  • 270度の範囲を10段階に分割。
  • 水量の増減を押しボタンで調節。
  • ボタン一回押しで約27度回転。
  • ボタンの押しっぱなしで27度ずつ連続回転。
  • 増/減ボタンでそれぞれ時計回り/反時計回り。
  • 0度以下、270度以上は回らない。
  • 現在の角度(つまり水量めやす)を7セグで表示。
  • 電源を切っても角度を記憶。
ノズルについている水量調節機構は0度以下/270度以上に無理にまわすと壊れますので、回転角の制限をすることが重要です。また、掃除のために電源を落としますので、電源が切られたときのモータ位置を記憶しておく必要があります。そうしないと次に電源をいれた時にあと何度どちらへまわして良いのかがわからなくなります。
ということで、制御系を作ります。
例によって設計も何も無く、構想の次は作製です。まあ趣味のレベルですから。
購入した 24byj48 はギアードステッピングモータで、ユニポーラの5線です。データシートを見るとまあ記載がてきとうなこと。とりあえず赤線がコモンの様なのであとの線を適切にたたいてやれば回るでしょう。
ドライバは3軸ステージを作ったとき購入した L293D を使う事にします。データシートはこちら。1Aくらい流せますからこのモータに使うには放熱板も何も要らないでしょう。

マイコンは PIC16F1827 を使います。これも何でもついているのでここのところお気に入りの石です。たくさん買い置きがあります。
7セグはLED入れにさまざまあるのですが、適当な大きさとしてROHMの LA-401D を使います。アノードコモンの平凡な赤LEDです。

ブレッドボードを取り出して、まずはモータドライバ L293D を乗せます。手動ステップスイッチを取り付けます。

出力にクリップを繋いで、

モータに結線します。

ロジックに5V、モータに12Vを入れます。
そして、4つのスイッチを適当にプチプチ押して回転するパルスの順序を調べます。ギアードなのでステッピングモータの回転がさらに減速されてなかなかわかりにくいのですが、がんばって調べます。これはやっていればそのうちにわかります。

ステップ手順がわかったら、スイッチを取り外し、その部分にPICからの出力を繋ぎます。ここからは人力ではなくマイコンの力を借りてパルスを入れていきます。

当たり前ですが、マイコンにはプログラムを書いてやらないと何にもしてくれません。
ということで、プログラムです。
私の(当然の)やり方としてプログラムも設計書のようなものは無く、フルスクラッチでいきなり組んでいきます。
まずはとにかくモータを回す→正転逆転させる→スピード調節→ドライバenable制御組み込み→回転単位の設定→ボタン入力部プログラム、といった手順で動かしながらデバッグしていきます。
7セグ表示部分と共にとりあえず動くようになった仮版プログラムはこのカラムの一番下に解説つきで記載します。

動作を確認出来るようにモータドライバへの出力部分にはLEDをつけます。

7セグを乗せ、電流制限抵抗を並べます。

全体の構成はこんな感じになりました。

動作の動画はこちら。
ボタンを押す毎に一定角度回転し、7セグ表示が増減します。回転制限の端(0/9表示)まで動くとそれ以上はボタンを押しても回転しません。

次にプログラムです。
MPLAB IDE / XC8 コンパイラの組み合わせです。こんな環境が無料なんて夢のような話です。
解説は適宜。

赤文字が(コメントも含めて)ソースコードです。
いろいろな設定は面倒ですが、google先生を通じて諸先輩の教えを請う、データシートを読み込む、XCコンパイラのヘルプを読む、といった方法で少しずつ知識を増やしていく以外ありません。がんばりましょう。

// UniPolar Stepping Motor Driver for 16F1827
// compiler: MPLAB XC8 Compiler
// 2013/05/18


// Configration
// RA0-4,6,7: 7segments LED control
// RB0: power reset button input (interrupt)
// RB1: power up button input (interrupt)
// RB2: power down  button input (interrupt)
// RB3: enable
// RB4,5,6,7: OUTPUT to L293D


// Driving method: 1 phase

ここまでとりあえず覚書。ポートAは7セグに使います。ポートAの5番(RA5)は入力専用なのでLED制御用には使えません。少なくともミドルクラスまでのPIC全般がこの構成になっています。
RB0には「どの位置にあっても一発で最弱に戻る」ボタンを付けようかと思っているところ。まだプログラムしていません。使い勝手をみて考えようかと。
RB1/2は水量の増減ボタンに使います。
RB3は出力で、モータドライバである L293D のenableポートを制御します。enableをHにしていずれかのコイルへ通電するとモータが励磁(ロック)されます。enableをLにするとステップのシーケンスを維持したまま励磁を解除できます。
RB4-7の4本がそれぞれのコイルをONするための信号を出力します。今回は1相駆動、つまり一度にひとつのコイルにしか電流を流しませんので、4つは排他的にONさせます。


#include <xc.h>

これはお約束のヘッダへのリンク。

ここからがPICの基本設定です。動作中に変更しない超基本的なことを設定します。
// CONFIG1

動作クロックのソースを決めます。外部のセラミックオシレータやRC発振回路を使うことも出来ますが、ここは素直に内蔵のクロック回路を使います。ここでは宣言のみ。あとで周波数を設定します。
周波数は動作中にも変更できますのでここには書かないのです。
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)

ウォッチドッグタイマを使わない宣言。ウォッチドッグタイマは「一定時間おきにキャンセルボタンを押さないと爆弾が爆発しちゃう!」といったもので、メインルーチンがフリーズしたりループに落ちて爆発キャンセルボタンを押せなくなったときに爆発して強制リセットをかけるためのものです。
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)

電源投入時、電圧不安定のときに誤動作を避けるための待ち時間です。
#pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)

外部からのリセットを受け付けるピンを有効にするか、それとも通常の入力ピンとして使うかの設定です。ここは入力ピンとして設定します。といっても入力には使いませんが。これが例のRA5です。
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)

プログラム領域にプロテクトをかける設定です。かけません。
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)

同データ領域。かけません。
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)

電圧が下がってきて雲行きが怪しくなってきたときに変な動きをする前にリセットをかけるというものです。電池動作をさせませんのであんまり関係ないのですが、とりあえず入れとけとしています。
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)

自分が動いている基準クロック(今回は内部クロックを使うと上のほうで宣言しました)を外部に出力するかどうかの設定です。ほかの石と協調動作したいときに使います。今回は不要ですのでOFFします。 ONにするとピンをひとつそれに割り当てる必要が出ますので、使わないときは無駄です。
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)

外部クロックが安定するまで内部クロックで様子を見る、といった機能を使うかどうかです。
とりあえず使っとけと。
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)

外部クロック(を使っている場合)が死んだときに内部クロックで動作を続けるというサバイバルモードの設定です。今回は内部クロックなので意味はないのですがONとしています。どっちでも良いです。
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
eepromの上書き禁止をするかどうか。今回はeepromをプログラムから書き込みますのでOFFにしておきます。
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)

Phase Lock Loop の略かな。マルチプルオーバークロック動作の設定です。確か外部クロックを使うときじゃないと意味が無かったような。
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)

そのまんまです。スタック異常時にリセットします。
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)

上であった電圧ヤバめな時のりせっとについて、どのくらいの電圧をヤバいとするかの設定です。
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)

プログラムをPICの内部メモリに書き込むときには特定のピンに高い電圧をかける必要がある(昔は必ずそうだった)のですが、それは無理にやらなくても良いので、そのときはこれをONに。
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

PIC動作で必ず必要な初期設定はここまで。

ここからは私が勝手に定義する設定です。プログラム中に使う数字に文字列を割り当てます。
こうしておくとあちこちに出てくる設定の数値をいじるときにこの#defineのところを書き換えれば全部いっぺんに書き換えられることになります。便利。

まずは水量の増減ボタンを1回押したときに回転する角度をステップ数を基準に決めます。この数を増やすと一度に大きな角度回るようになります。

#define Step 150    // Number of steps per button push

モータに送り込むパルスの間隔を決めます。これが短いとモータは早く回り、長いと遅くなります。
#define PulseInterval 5    // Step pulse interval for stepping motor in msec

ここに基準クロックの周波数を書きます。設定ではありません。(せっていはもう少し下)8MHzで動作させますのでこのような記載となります。これで__delayマクロが(正確に)動くようになります。
#define _XTAL_FREQ 8000000  // definition for delay

予めeepromに書いておく数値の列です。データに意味はありません。電源投入時の値がわけわからないと気持ち悪いので。無くても動作には影響はありません。また、ここは8バイト一度に書く必要があります。以下のプログラムでは先頭の1バイト(したの0x00のとこ)だけしか使いませんのであとの7バイトは1~7の数字が書き込まれているだけです。何にも使いません。
__EEPROM_DATA(0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07); // Set initial value to EEPROM

ここから変数宣言です。モータのどのコイルがONしているかを覚えておくための変数です。4通りしかありませんが、きちんとした順番でONしないと回らなかったり反対に回ったりしますので大切です。
char StepPosition=1; // increment or decrement by every rotation pulse
                    // according to the direction

上で書いた「ボタン一押しで回る角度」のどのあたりにいるかを回っている最中に覚えておく変数です。
int NumberOfStep=0; //Counts up to Step

変数はこれだけ。

ここから3行は関数の宣言です。7セグの書き換え、時計回りにまわす、反時計回りにまわす、の3つです。
void Flash7seg(void);
void CW(void);
void CCW(void);

ここからようやくメインルーチン。
void main()
{

再び設定の嵐です。ここからの設定が#pragmaと違うのは、#pragmaに記載されている設定が実行中には変更されないのに対し、ここにある設定はプログラム実行中に変更することがあるというところです。

動作クロックを8MHzに設定します。
    OSCCON=0b01110010; // internal clock 8MHz

ポートAでアナログ入力を使いませんの設定。
    ANSELA=0b00000000; // No analog for PORTA

ポートBでも使いません。
    ANSELB=0b00000000; // No analog for PORTB

ポートAのどのピンを入力に、どのピンを出力にするか、です。Aは全部出力です。RA5はハード的に入力のみしかありませんので、0(出力)に設定しても入力のままです。
    TRISA=0b00000000; // Set I/O according to configuration

BはRB0,RB1,RB2を入力にします。上で書いたように(最弱直行、水量増/水量減の3つ。最弱直行はまだプログラムしていません)
    TRISB=0b00000111; // Set I/O according to configuration

ポートA/Bで内部プルアップを使うかどうかです。入力はプルアップしておいてスイッチでGNDに落とすことで入力を行います。抵抗が減るので便利です。
    WPUA=0b00100000;    // Weak pull-up for RA5 enabled
    WPUB=0b00000111;    // Weak pull-up for RB0,1,2 enabled

上の二行で設定したプルアップするポートを実際にプルアップする設定です。面倒ですね。
    OPTION_REG=0b00000000;  // Weak pull-up enabled

ポートA/Bに初期値を書き込みます。入力ポートの状態には影響は与えません。
まあ、不定はいやなのでおまじないみたいなものです。
    PORTA=0b00000000; // reset out put
    PORTB=0b00000000; // reset out put

7セグに電源投入前の状態(eepromに書き込まれている)を表示します。別関数。(下にあります)
    Flash7seg();

こっから先をずーっと繰り返します。
    while(1)
    {

もしRB1のボタンが押されていたら。
        if(RB1==0)  // CW
        {
eepromの状態をみてすでに最弱の位置まで下がっていないことを確認して、(すでに最弱だったら何もせずに if を抜ける)
            if(eeprom_read(0x00)>0) //Check if the power level is in lower limit
            {

eepromに一段下の数値を書き込んで、
                eeprom_write(0x00,eeprom_read(0x00)-1);  // Write new level

モータのenableをHにして、
                RB3=1;  // Motor driver enable

時計回りに単位角度回転させ、
                CW();   // Turn clockwise for defined steps

モータのenableをLにして、
                RB3=0;  // Motor driver disable

7セグの表示をリフレッシュします。
                Flash7seg();    // Change power level indicator
            }
        }

ここからは、逆回転で、やっている論理はRB1を0にしたときと同じ。
        if(RB2==0)  // CCW
        {
            if(eeprom_read(0x00)<9) //Check if the power level is in upper limit
            {
                eeprom_write(0x00,eeprom_read(0x00)+1);  // Write new level
                RB3=1;  // Motor driver enable
                CCW();  // Turn counter-clockwise for defined steps
                RB3=0;  // Motor driver disable
                Flash7seg();    // Change power level indicator
            }
        }
    }
}

時計回りの関数(サブルーチン)
void CW(void)
{

設定された回転角(#defineにステップ数で定義) になるまで、
    while(NumberOfStep<=Step)
            {
以下の1-4を順に繰り返します
                switch(StepPosition)
                {
                 case 1:
                  PORTB = 0b00011000;
                  break;
                 case 2:
                  PORTB = 0b00101000; // CW:00100000 CCW:01000000
                  break;
                 case 3:
                  PORTB = 0b10001000;
                  break;
                 case 4:
                  PORTB = 0b01001000; // CW:01000000 CCW:00100000
                  StepPosition=0;
                  break;
                 }
                NumberOfStep++;
                StepPosition++;

パルスごとに#defineで設定された時間待ちます。これが回転スピードを決めます。
                __delay_ms(PulseInterval);
            }
    NumberOfStep=0; // Reset NumberOfStep
}

反時計回り。時計回りと逆ロジックです。
void CCW(void)
{
    while(NumberOfStep<=Step)
            {
                switch(StepPosition)
                {
                 case 1:
                  PORTB = 0b00011000;
                  break;
                 case 2:
                  PORTB = 0b01001000; // CW:00100000 CCW:01000000
                  break;
                 case 3:
                  PORTB = 0b10001000;
                  break;
                 case 4:
                  PORTB = 0b00101000; // CW:01000000 CCW:00100000
                  StepPosition=0;
                  break;
                 }
                NumberOfStep++;
                StepPosition++;
                __delay_ms(PulseInterval);
            }
    NumberOfStep=0; // Reset NumberOfStep
}

7セグの書き換え
void Flash7seg(void)
{

eepromの0x00を読んで、
    switch(eeprom_read(0x00))
    {

その値によって以下のどれかを実行します。

0を書く
         case 0:
          PORTA = 0b00001000;
          break;

1を書く….以下同じ
         case 1:
          PORTA = 0b00011111;
          break;
         case 2:
          PORTA = 0b01010000;
          break;
         case 3:
          PORTA = 0b00010100;
          break;
          case 4:
          PORTA = 0b00000111;
          break;
          case 5:
          PORTA = 0b10000100;
          break;
          case 6:
          PORTA = 0b10000000;
          break;
          case 7:
          PORTA = 0b00011110;
          break;
          case 8:
          PORTA = 0b00000000;
          break;
          case 9:
          PORTA = 0b00000100;
          break;
    }
}

プログラム解説は以上です。つかれた。

次は基板に実装して箱に入れます。
しばらくCO2レーザはお休みになりそうな感じ。

コメント