2013年12月31日火曜日

Ni-MH充電器の製作(7)


毎回更新が遅れまして申訳ありません。

今回は、出来上がったプログラムが、仕様通りに動作するかテストを行います。
また、コントローラ部基板のパターン検討まで行います。

※ 部品の実装までは、年内に完了しませんでした。重ね々申訳ありません。


7 テスト・デバッグ

7.1 テスト仕様

出来上がったプログラムが、仕様通りに動作するかテストを行い、不具合箇所を修正します。

テストで重要な要素は、実装した機能全てを、漏れなく評価することです。
そのための基準として、前回 6.1項で示したステータス遷移表を使います。

当該図表を、改めて以下に示します。

トリガー
STARTボタン押下
充電完了
電池取り外し
ステータス
「設定中」「充電中」遷移
「充電中」「設定中」遷移「設定中」遷移「設定中」遷移

I/O要素
電流設定キー
電圧設定キー
STARTキー
電流設定LED
電圧設定LED
充電中LED
ステータス
「設定中」設定値をサイクリックに変更設定値をサイクリックに変更(上記通りにより略)設定値の変更に従い、該当LED点灯設定値の変更に従い、該当LED点灯消灯
「充電中」キー無効キー無効(上記通りにより略)該当LED点灯維持該当LED点灯維持一定間隔で点滅

充電実行においては、「電池の種類(充電電流)」・「電池の本数」の全ての組み合わせについて、終止電圧まで充電し、正しく終了するか確認します。
組み合わせを表にしたものを、以下に示します。

本数 \ 充電電流75mA130mA200mA240mA
1本1.38V※
2本2.76V※
4本5.52V※

※(充電電流)で充電を続け、(1.38V × 本数)に達したら、「設定中」ステータスに戻ること。

お詫びと訂正
これまで、充電終止電圧の値が記述ごとに異なっておりました。
仕様上の終止電圧「1.4V」から、今後、誤差や個体差の安全をみた値「1.38V」に定めます。

上記より、「設定中」ステータスにおいては「ステータス遷移表」を使い、「充電中」ステータスについては、上記組み合わせ表を使ってテストを行いました。


7.2 テスト冶具

テストにおいて、いきなり電池を繋ぐのは危険です。
まずは、ダミー抵抗を接続し、その抵抗値を手動で変化させて、充電中~完了状態を再現することにします。
この機能を実現するために、可変抵抗を使ったテスト用冶具を作成しました。

可変抵抗は、スピーカ用のアッテネータ(8Ω二連 = 16Ω)を流用し、以下写真の通り、クリヤケースに実装してみました。


※ 他の端子や穴は、将来、実験用電源を追加実装することを予定して設けたものです。

次に、各条件で必要な抵抗値を算出します。
仮に充電開始電圧を 0.9V とすると、各条件での抵抗の下限値は、

・0.9V × 本数 / 充電電流 ・・・ ①

抵抗の上限値は、終止電圧を用いて、

・1.38V × 本数 / 充電電流 ・・・ ②

①②より、各条件で必要な抵抗値の範囲を以下に示します。

本数 \ 充電電流75mA130mA200mA240mA
1本12Ω ~ 18Ω7Ω ~ 11Ω5Ω ~ 7Ω4Ω ~ 6Ω
2本24Ω ~ 37Ω14Ω ~ 21Ω9Ω ~ 14Ω8Ω ~ 12Ω
4本48Ω ~ 74Ω28Ω ~ 42Ω18Ω ~ 28Ω15Ω ~ 23Ω

※ 抵抗値は、計算結果を四捨五入で整数にしています。
※ アッテネータの抵抗値(16Ω)を超えるケースでは、直列に固定抵抗を繋いで対処します。


7.3 テスト結果

誤動作やフリーズなどの重欠点はありませんでしたが、以下に示す問題があり、それぞれ対処方針を決めました。

① 充電終了後、抵抗値を下限に戻し、充電開始すると、すぐに充電終了する。

抵抗値を下限にもどさなければ( = 満充電状態のまま)、すぐに充電終了するのが正常であり、問題ではありませんが、下限に戻す( = 電池を入れ替える)ならば、本来、充電中に遷移すべきです。

これは、DC-DCコンバータのコイル及び出力コンデンサに電荷が蓄積されたままであることが原因です。
これらの電荷が放電するためには時間がかかりますが、実使用では、電池を入れ替える時間があるので、現状のまま様子見としました。

※ 理論上の時間算出まで、できませんでした。でき次第、追記します。


② 終止電圧に達しない状態で、充電終了する場合がある。

これは、DC-DCコンバータ出力の、リップルとノイズが原因と思われます。
すなわち、ADCによる電圧検出のタイミングと、リップルとノイズのピークが一致すると、その分検出電圧が高く出ますので、直ちに充電終了してしまうものです。

これは、いくつか対処方法があります。

a.DC-DCコンバータ部の性能を上げて、リップルとノイズをさらに減らす。
b.PWMのスイッチングと、ADC読み取りを同期させ、ピークでない位置で読み取る。
c.電圧検出を複数回行い、全て終止電圧を超えていたら充電完了とする。

a と b は模範的ですが、改修規模が大きく、また、性能上も限界であろうとの判断で、安易ではありますが c を採用しました。

上記方針より、10回検出を行うように修正したソースを以下に示します(抜粋)。


//////////////////////////////////////////////////////////////////////////////
// メインループ
//////////////////////////////////////////////////////////////////////////////
int main(void)
{
    uint8_t uOVCount = 0; // OverVoltage Counter

// 途中略

    while(1){

        // --------- Setting Status --------

        if(uSTATUS == STATUS_SETTING){

// 途中略

        // --------- Charge Running Status ---------

        }else if(uSTATUS == STATUS_CHARGING){

// 途中略

            if(uREQUEST == REQUEST_FUNC_C || (uDataB > uVref && ++uOVCount == 10)){

                uOVCount = 0;

                uSTATUS = STATUS_SETTING;
                OCR1A = 0;              // PWM width min
                _CBI(TCCR1A, COM1A1);   // OC1A Non-Invert off
                uREQUEST = REQUEST_IDLE;

            }

// 以下略


③ ②の対策をとっても、終止電圧より僅かに低い状態で、充電終了する場合がある。

1本充電時、1.33V程度で充電終了する状態となっています。
これは、電圧検出抵抗や、ダミー抵抗のばらつきが原因のようです。
対策として、以下箇所の電流設定値を変更すればOKですが、この対応は、次項 基板実装まで終えて、本当の電池を接続して微調整することにします。


// 以下の整数値を微調整する。
const uint16_t arVOLTAGE[] = {
    266,    // 1024 / Vref * 1.38V * 1
    532,    // 1024 / Vref * 1.38V * 2
    1065    // 1024 / Vref * 1.38V * 4
};


8 コントローラ部 基板実装

ようやくプログラムが固まりましたので、マイコン及び周辺回路の基板実装に移ります。
コントローラ部回路の最終版を、以下に示します。


最終版では、DC-DCコンバータ部にあったドライブ用FET(Q11 2SK982)がコントローラ部に移動してQ1となり、電源(+5V)用に三端子レギュレータが追加となりました(忘れてました!すみません)。

また、Q1のソース(PWM-)は、グランドと同電位ですが、この経路にはスイッチング電流が流れるため、ADコンバータが同居するコントローラ部のグランドと共通にはせず、独立端子として引き出しています。


8.1 パターン検討

例によって、ICB-86ユニバーサル基板の使用を前提に検討します。
決定したパターンを以下に示します。



Atmega168は、電源とグランドの足配置に毎回悩みますが、今回止むを得ず、AVCCはジャンパ接続となりました。
また、実機での設定電圧/電流の微調整を考慮して、Atmega168の17~19ピンに、ISPコネクタを追加しました。



今回は、年内に公開することを優先して、ここまでと致します。


次回の予定

コントローラ基板に部品を実装し、動作及び微調整を行う予定です。









2013年10月18日金曜日

Ni-MH充電器の製作(6)


再び体調の思わしくない日々が続き(言い訳です…)、更新が遅れまして申訳ありません。
ようやく、プログラムの本検討にとりかかることができましたので、以下の通り報告します。


6 プログラム設計検討

6.1 基本仕様

6.1.1 内部ステータス

内部ステータスを以下の通り定義します。

・「設定中」(充電中止)
⇒ 充電が行われず、設定の変更ができる状態。

・「充電中」
⇒ 設定した内容に従って、充電を実行している状態。

6.1.2 ステータス遷移

上記で決めたステータスと、関連するトリガイベントの遷移を、以下の通り整理します。

トリガー
STARTボタン押下
充電完了
電池取り外し
ステータス
「設定中」「充電中」遷移
「充電中」「設定中」遷移「設定中」遷移「設定中」遷移


6.1.3 ユーザーI/O

各キー・各LED毎に、以下の通り整理します。
キー・LED共、処理方法の組み合わせで複数の状態を定義して、数を減らすこともできますが、処理が複雑になるのと、ポートに余裕があるため、これまでの回路図通り、単純に機能の数だけスイッチとLEDを設けました。

I/O要素
電流設定キー
電圧設定キー
STARTキー
電流設定LED
電圧設定LED
充電中LED
ステータス
「設定中」設定値をサイクリックに変更※1設定値をサイクリックに変更※2「充電中」遷移※3設定値の変更に従い、該当LED点灯設定値の変更に従い、該当LED点灯消灯
「充電中」キー無効キー無効「設定中」遷移※3該当LED点灯維持該当LED点灯維持一定間隔で点滅

※1 押下に従って、設定値を 75mA(AAA) ⇒ 130mA(AA小容量) ⇒ 200mA(AA標準) ⇒ 240mA(AA pro) ⇒ … と、サイクリックに変更します。
※2 押下に従って、設定値を 1.3V(1本) ⇒ 2.6V(2本) ⇒ 5.2V(4本) ⇒ … と、サイクリックに変更します。
※3 6.1.2項の通り


6.2 個別機能

6.2.1 キー入力処理

6.1.3項の通り、今回は、キーの押下(OFF ⇒ ON)のみ処理します(開放・押し続けは無視)。
キーは機械接点のため、必ずチャタリング(接点あばれ)が発生して誤検出の原因となりますので、一定間隔の割り込みを利用して、スイッチ入力のサンプリングを行い、チャタリング除去を行います。
割り込みは、TIMER0 CTCモードを使って、10msのインターバルでサンプリングします。
キーのOFF ⇒ ON を検出するために、直前の状態を都度記憶しておき、比較します。


6.2.2 電流・電圧検出

回路図のうち、電流・電圧検出部を抽出した物を以下に示し、当該図を参照しながら検討を進めます(計算が長くなりますが、ご容赦願います)。















充電池両端の電圧 Va・Vb は、

Va = Va' * (1 + R15/R16) … ①
Vb = Vb' … ②

ADCの出力データ Da・Db と電圧の関係は、ADCが10ビット(=1024)であることから、

Da = (1024/Vref) * Va' … ③
Db = (1024/Vref) * Vb' … ④

以降、(1024/Vref) を T 、(1 + R15/R16) を R とおき、上記式を変形して、

②④より、
Vb = (1/T) * Db … ⑤

①③より、
Va = Va' * R * Da = (R/T) * Da … ⑥

検出電流 Ibatt は、⑤より、

Vb = Ibatt * R17 = (1/T) * Db … ⑦

検出電圧 Vbatt は、⑤⑥より、

Vbatt = Va - Vb = (R/T) * Da - (1/T) * Db … ⑧

ここで、プログラム中において、浮動小数点演算をなるべく排除することを考えます。
すなわち、検出電流および電圧を、早いうちにADCデータと直接比較できる10ビットデータに変換しておきます。

上記実現のために、ADCデータを左辺、定数項を右辺にまとめると、

⑦より、
Db = T * R17 * Ibatt

⑧より、
R * Da - Db = T * Vbatt

Ibattは、例えば75mAの場合、

(1024 / 5V) * 4.7 *75mA = 72(小数点四捨五入)

をあらかじめ計算しておいて、Da と比較すればよいことになります。
Vbattのほうに、一部小数演算が残りましたが、他は、10ビットデータに揃った状態で取り扱うことができました。


6.2.3 電流・電圧制御

これまでの試作通り、TIMER1のPWM機能を使って、DC-DCコンバータのスイッチング制御を行います。
制御方法は、今回、充電という機能に特化しますので、汎用電源に要求されるような厳密な過渡特性までは検討しませんが(本当はよくわかっていません…)、ゲインだけは適切でないと、安定負荷でも出力が不安定となるので、以下の通り検討しておきます。

最低限のゲイン制御要件として、ADC及びPWMの分解能の観点で検討を進めます。

ADC 1ビットの分解能 Sadc は、1 / T なので、
Sadc = 1 / T = 5V/1024 = 4.9mV

一方、PWMの分解能 Spwm は、8ビット幅で、入力電圧が8.4V、分圧比が 1 / R であることより、
Spwm = 8.4V / R / 256 = 10.5mV

このままでは、4.9mVの変動に対して、10.5mVの補正を行うということになり、制御ゲインが過大となってしまいます。
よって、ゲインを下げるための何らかの方策を考えます。
最も適切なのは、PWMの分解能を上げることです。今回の構成では、もう1ビット分解能を上げることはできます(9ビット=512)が、外部クロックにしたり、回路を変更する必要があるので、これは最後の手段にします。
今回は、簡単に、プログラムにおいて変動幅を許容する方法で対処したいと思います。


6.2.4 充電状態判定

充電完了と、電池を故意に外してしまった場合の状態を判定します。

充電完了は、検出した電圧が、Ni-MH電池の満充電電圧(今回は、1.3V)になったら、PWMをオフして、「設定中」ステータスに戻ります。

電池が外れた状態は、充電電流がゼロになる状態とする方法がありますが、充電開始直後、充電電流が流れ始めない状態と区別できない恐れがあります。
他に、充電を維持しようとして、電圧が上昇してしまった状態を判定する方法がありますが、これは満充電と同一事象のため、結局、充電完了検出で両方を兼ねることができます。

6.3 試作版プログラム

全機能を実装した、試作版ソースリストを以下に示します。


//////////////////////////////////////////////////////////////////////////////
//
// Ni-MH Battery Charger "experiment" version
//
// Programmed by (c)ota957
//
// MPU =  ATmega168P
// lfuse = 0xe2(CKDIV8 disable)
// hfuse = 0xdf(default)
// efuse = 0xf9(default)
//
//////////////////////////////////////////////////////////////////////////////
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define _SBI(p, q) ((p) |= _BV(q))
#define _CBI(p, q) ((p) &= ~_BV(q))

//////////////////////////////////////////////////////////////////////////////
// グローバル変数及び各定義
//////////////////////////////////////////////////////////////////////////////
volatile uint8_t uSW_FUNC_A;
volatile uint8_t uSW_FUNC_B;
volatile uint8_t uSW_FUNC_C;
volatile uint8_t uREQUEST;
volatile uint8_t uSTATUS;
volatile uint8_t uTIMERBLINK;

#define SW_LOW 0
#define SW_HIGH 1
#define SW_ON 2
#define SW_OFF 3

#define REQUEST_IDLE 0
#define REQUEST_FUNC_A 1
#define REQUEST_FUNC_B 2
#define REQUEST_FUNC_C 3

#define STATUS_IDLE 0
#define STATUS_SETTING 1
#define STATUS_CHARGING 2

/*
#define V_REF 5.0

#define R_17 4.7
*/
#define R_15 1000
#define R_16 470

const uint16_t arCURRENT[] = {
    72,     // 1024 / Vref * R17 *  75mA
    125,    // 1024 / Vref * R17 * 130mA
    193,    // 1024 / Vref * R17 * 200mA
    231     // 1024 / Vref * R17 * 240mA
};

const uint16_t arVOLTAGE[] = {
    266,    // 1024 / Vref * 1.3V * 1
    532,    // 1024 / Vref * 1.3V * 2
    1065    // 1024 / Vref * 1.3V * 4
};

//const double arVOLTAGE[] = {(1.3 * 1), (1.3 * 2), (1.3 * 4)};
const uint8_t uINDEX[] = {1, 2, 4, 8};

volatile uint8_t uISET;
volatile uint8_t uVSET;

 
//////////////////////////////////////////////////////////////////////////////
// 各割り込み処理
//////////////////////////////////////////////////////////////////////////////
// ---------------------------------------------------------------------------
// タイマー0 比較一致割り込み
// ---------------------------------------------------------------------------
ISR(TIMER0_COMPA_vect)
{
    // swich sampling
    static uint8_t uPrevFuncA = SW_OFF;
    static uint8_t uPrevFuncB = SW_OFF;
    static uint8_t uPrevFuncC = SW_OFF;

   uint8_t uSwitchFuncA = bit_is_clear(PIND, PIND6) ? SW_ON : SW_OFF;
    uint8_t uSwitchFuncB = bit_is_clear(PIND, PIND7) ? SW_ON : SW_OFF;
    uint8_t uSwitchFuncC = bit_is_clear(PINB, PINB0) ? SW_ON : SW_OFF;

     // set status
    if(uPrevFuncA == SW_OFF && uSwitchFuncA == SW_ON)
        uREQUEST = REQUEST_FUNC_A;
    if(uPrevFuncB == SW_OFF && uSwitchFuncB == SW_ON)
        uREQUEST = REQUEST_FUNC_B;
    if(uPrevFuncC == SW_OFF && uSwitchFuncC == SW_ON)
        uREQUEST = REQUEST_FUNC_C;

     // memory present switch
    uPrevFuncA = uSwitchFuncA;
    uPrevFuncB = uSwitchFuncB;
    uPrevFuncC = uSwitchFuncC;

     // 10ms timer countup
    uTIMERBLINK++;
}
/*
ISR(TIMER1_OVF_vect)
{

 }
*/
//////////////////////////////////////////////////////////////////////////////
// 各関数
//////////////////////////////////////////////////////////////////////////////
// ---------------------------------------------------------------------------
// LED on/off
// ---------------------------------------------------------------------------
void DispBarLED8(uint8_t uData)
{
    (uData & 0b00000001) ? _SBI(PORTC, PC2) : _CBI(PORTC, PC2);
    (uData & 0b00000010) ? _SBI(PORTC, PC3) : _CBI(PORTC, PC3);
    (uData & 0b00000100) ? _SBI(PORTC, PC4) : _CBI(PORTC, PC4);
    (uData & 0b00001000) ? _SBI(PORTC, PC5) : _CBI(PORTC, PC5);
    (uData & 0b00010000) ? _SBI(PORTD, PD5) : _CBI(PORTD, PD5);
    (uData & 0b00100000) ? _SBI(PORTD, PD4) : _CBI(PORTD, PD4);
    (uData & 0b01000000) ? _SBI(PORTD, PD3) : _CBI(PORTD, PD3);
    (uData & 0b10000000) ? _SBI(PORTD, PD2) : _CBI(PORTD, PD2);
}

 void DispLED_A(uint8_t uData)
{
    (uData & 0b00000001) ? _SBI(PORTC, PC5) : _CBI(PORTC, PC5);
    (uData & 0b00000010) ? _SBI(PORTC, PC4) : _CBI(PORTC, PC4);
    (uData & 0b00000100) ? _SBI(PORTC, PC3) : _CBI(PORTC, PC3);
    (uData & 0b00001000) ? _SBI(PORTC, PC2) : _CBI(PORTC, PC2);
}

 void DispLED_B(uint8_t uData)
{
    (uData & 0b00000001) ? _SBI(PORTD, PD2) : _CBI(PORTD, PD2);
    (uData & 0b00000010) ? _SBI(PORTD, PD3) : _CBI(PORTD, PD3);
    (uData & 0b00000100) ? _SBI(PORTD, PD4) : _CBI(PORTD, PD4);
    (uData & 0b00001000) ? _SBI(PORTD, PD5) : _CBI(PORTD, PD5);
}

// ---------------------------------------------------------------------------
// ADC 入力選択
// ---------------------------------------------------------------------------
void ADC_Select(uint8_t uChannel)
{
    uint8_t uTemp = ADMUX;

     ADMUX = (uTemp & 0xf0) | (uChannel & 0x0f);
//  DispBarLED8(ADMUX);
}

// ---------------------------------------------------------------------------
// ADC 変換スタート
// ---------------------------------------------------------------------------
uint16_t ADC_Start(void)
{
    _SBI(ADCSRA, ADIF);
    _SBI(ADCSRA, ADSC);

     loop_until_bit_is_set(ADCSRA, ADIF);

     return ADCW;
}

// ---------------------------------------------------------------------------
// ADC 変換値取得ラッパー
// ---------------------------------------------------------------------------
uint16_t ADC_GetData(uint8_t uChannel)
{
    ADC_Select(uChannel);
    _delay_us(10);

     return ADC_Start();
}

 
//////////////////////////////////////////////////////////////////////////////
// メインループ
//////////////////////////////////////////////////////////////////////////////
int main(void)
{
    // I/O Setting
    _SBI(DDRC, DDC2);
    _SBI(DDRC, DDC3);
    _SBI(DDRC, DDC4);
    _SBI(DDRC, DDC5);
    _SBI(DDRD, DDD2);
    _SBI(DDRD, DDD3);
    _SBI(DDRD, DDD4);
    _SBI(DDRD, DDD5);

    _CBI(DDRD, DDD6);
    _SBI(PORTD, PD6);
    _CBI(DDRD, DDD7);
    _SBI(PORTD, PD7);
    _CBI(DDRB, DDB0);
    _SBI(PORTB, PB0);

     // Timer0 Setting
    _SBI(TCCR0A, WGM01);    // TIMER0 CTCMode
    _SBI(TCCR0B, CS02);     // TIMER0 Prescaler = 1/1024
    _SBI(TCCR0B, CS00);     // ↑
    OCR0A = 79;             // TIMER0 CTC Top(1024 * 79 / 8MHz = 10ms)
    _SBI(TIMSK0, OCIE0A);   // TIMER0 CTC Interrut Enable

     // Timer1 Setting
    _SBI(DDRB, DDB1);       // OC1A(PB1) output
//  _SBI(TCCR1A, COM1A1);   // OC1A Non-Invert on
    _SBI(TCCR1A, WGM10);    // 8Bit Highspeed PWM
    _SBI(TCCR1B, WGM12);    // ↑
    _SBI(TCCR1B, CS10);     // TIMER1 Prescaler = 1/1
//  _SBI(TIMSK1, TOIE1);    // Overflow Interrupt Enable
//  OCR1A = 0;

     // ADC Setting
    _SBI(ADMUX, REFS0);     // Vref = AVCC(5V)
    _SBI(ADCSRA, ADPS2);    // ck = 1/64
    _SBI(ADCSRA, ADPS1);    // ↑
    _SBI(ADCSRA, ADEN);     // ADC Enable

 
     uint16_t uDataI = 0x00;
    uint16_t uDataV = 0x00;

     uint16_t uIref = arCURRENT[0];
    uint16_t uVref = arVOLTAGE[0];

     uSTATUS = STATUS_SETTING;

     sei();

     _delay_ms(10);

     while(1){

         // --------- Setting Status --------

         if(uSTATUS == STATUS_SETTING){

             if(uREQUEST == REQUEST_FUNC_A){

                uISET = uISET < 3 ? uISET + 1 : 0;
                uIref = arCURRENT[uISET];
                uREQUEST = REQUEST_IDLE;

             }else if(uREQUEST == REQUEST_FUNC_B){

                uVSET = uVSET < 2 ? uVSET + 1 : 0;
                uVref = arVOLTAGE[uVSET];
                uREQUEST = REQUEST_IDLE;

             }else if(uREQUEST == REQUEST_FUNC_C){

                uSTATUS = STATUS_CHARGING;
                OCR1A = 0;              // PWM width = min
                _SBI(TCCR1A, COM1A1);   // OC1A Non-Invert on
//              _SBI(TIMSK1, TOIE1);    // Overflow Interrupt Enable
                uTIMERBLINK = 0;
                uREQUEST = REQUEST_IDLE;

         }

             // display status
            DispLED_A(uINDEX[uISET]);
            DispLED_B(uINDEX[uVSET]);

         // --------- Charge Running Status ---------

         }else if(uSTATUS == STATUS_CHARGING){

             if(uREQUEST == REQUEST_FUNC_C){

            uSTATUS = STATUS_SETTING;
                OCR1A = 0;              // PWM width = min
                _CBI(TCCR1A, COM1A1);   // OC1A Non-Invert off
                uREQUEST = REQUEST_IDLE;

             }else{

                 uDataI = ADC_GetData(1); // get current(ADC1)

                 //DispBarLED8(uDataI);  // for debug

                 if(uDataI + 2 < uIref)
                    OCR1A = OCR1A < 0xff ? OCR1A + 1 : 0xff;
                else if(uIc - 2 > uIref)
                    OCR1A = OCR1A > 0x00 ? OCR1A - 1 : 0x00;

                 uDataV = ADC_GetData(0); // get voltage(ADC0)

                 uint16_t uDataB = (uint16_t)(1 + R_15 / R_16) * uDataV - UdataI;

                 if(uDataB > uVref){         // Charge complete or Battery rejected

                     uSTATUS = STATUS_SETTING;
                    OCR1A = 0;              // PWM width = min
                    _CBI(TCCR1A, COM1A1);   // OC1A Non-Invert off
                    uREQUEST = REQUEST_IDLE;

                 }
            }

             // display status
            DispLED_A(uINDEX[uISET]);
            uTIMERBLINK & 0x40 ? DispLED_B(uINDEX[uVSET]) : DispLED_B(uINDEX[uVSET] | 0x08);

         // --------- Other Status (no defined) ---------

         }else{
            ;
        }

         //_delay_ms(10);
    }
}

//////////////////////////////////////////////////////////////////////////////
// End
//////////////////////////////////////////////////////////////////////////////

※本ソースでは、まだ充分なテストを行っておりません。ご了承願います。

次回の予定

デバッグ及びテストを行い、できれば、マイコン部の基板実装まで進みたいと思います。

2013年8月28日水曜日

AVR 新開発環境の構築


本業の事情(?)により、PCを買い換えることになったため、この機会に、AVRの開発環境も更新することにしました。
今回は、Ni-MH充電器の製作を一回お休みして、上記更新結果を報告致します。


1 開発環境の選定

1.1 ライター

これまでは、秋月電子製 AE-UM232R をライティングデバイスとして用い、ライティングソフトは、avrdude-serjtag  を使用してきました。

AE-UM232R は、AVRと接続するだけで書き込み出来るようになり、また、TXD/RXDも接続しておけば、シリアルモニタでデバッグも可能(arduinoライク)です。
現状、使い勝手として申し分ありませんので、そのまま流用します。

AE-UM232R ⇔ AVR の接続は、Kimio Kosaka さまのページ で説明されている通りに結線しています。
私事環境では、これも定番となった、秋月電子製 ATMEGA168/328用マイコンボード に以下の通り実装して使用しています。


実装箇所の回路は、このページをご参照ください。

一方、ライティングソフトは、AE-UM232R に対応したもうひとつの avrdude で、複数種類のAVRを使用するときに便利な、MPU自動検出機能を持つ、YCIT版 avrdude を使用してみます。


1.2 統合開発環境(IDE)

これまでは、Windows XP + RAM 512MB!という貧弱な環境であったため、AVR Studio 4(+AVR Toolchain)を使用してきました。今回は、Windows 7 + RAM 2GB と、能力面でやや改善?されましたので、AVR Studio 5 以降の新世代開発環境の導入を検討します。

現状での最新の開発環境「Atmel Studio 6」は、AVRTinyシリーズがサポートから外れてしまい、また、要求スペックが高く(最低RAM2GB)、今回のPCでも苦しいと予想されましたので、今回は、AVR Studio 5 の最終版 AVR Studio 5.1.208 を導入することにします。

※ AVRTiny サポート外の記述は誤りでした。Atmel社の公式サイトにおいて、サポートされていることを確認致しました。お詫びして訂正致します。


2 開発環境の導入

現在ログインしているユーザーが、管理者権限を有していることを前提とします。
管理者権限があれば、「コントロールパネル」 ⇒ 「ユーザーアカウント」を開くと、カレントユーザーの権限が「Administrator」になっています。



2.1 USBドライバ

AE-UM232R を、PCに接続します。
これで、自動的にドライバがインストールされる旨の情報もありましたが、私事環境では導入できませんでした。
よって、FTDI社のサイトから、ドライバを入手して解凍しておき※、「コントロールパネル」 ⇒ 「デバイスマネージャ」を開き、「USB Serial Port」「USB Serial Converter」夫々のドライバの更新を行いました。

※ 32bit/64bitの区別はありません。また、「VCP」のドライバをダウンロードすれば、「D2XX」のドライバは内含されています。





2.2 YCIT版 avrdude

山形県立産業技術短期大学校(YCIT) 千秋研究室さま のサイト(以下校名・敬称略)から、現状の最新版 avrdude-2013-RC15b.zip を入手します※。

※ 同研究室サイトへのログイン及び投稿が必要です。

任意のフォルダに展開します。bin/setup.bat は、インストールフォルダが c:/bin 固定であるため、実行せずに展開フォルダの場所で使用することにします。

このとき、必須とされている「libusb」ライブラリは、readme.txt [2](2)項において、AE-UM232Rでは不要との由だったので、インストールしませんでした。


2.3 AVR Studio 5.1

直前バージョンの AVRStudio 5 の導入結果は、『昼夜逆転』工作室さま 「AVR Studio 5のインストールと留意点」(以下敬称・サイト名略)に詳細に説明されています。

同記事に比肩する秀逸な説明はとてもできませんので、ここでは、AVRStudio 5.1 を Win7に導入した場合の相違点・注意点に絞って報告します。


2.3.1 パッケージの選択

AVRStudio 5.1 は、.NET Framework 4 および VisualStudio 2010 Shell 上で動作します。
一方、インストールパッケージは、上記ミドルウエアを含むもの(Full版)と含まないもの(Small版)が用意されています。

『昼夜逆転』工作室によれば、Full版を使用すると、同時にインストールされた英語版ミドルウエアを、日本語版に置き換える必要がある由だったので、今回は、先に .NET Framework 4 および VisualStudio 2010 Shell の日本語版をインストールした状態で、Small版をインストールしてみます。

Atmel社公式サイトでは、上記バージョンの公開を終了しています(Atmel Studio 6.1のページに移動)ので、有志のアーカイブサイトから入手しました。


2.3.2 導入手順

インストールは、Windows 7 のセキュリティを考慮して、念のため「管理者として実行」を選択して実行しました。


『昼夜逆転』工作室の指摘にあった、「Small版のインストール時にエラーとなる」現象は、今回発生しませんでした。

しかし、予想と異なり、VisualStudio 2010 Shell がネット上から強制的にダウンロードされ、英語版がインストールされる結果となりました。
対して、.NET Framework 4 は、新たにダウンロード(=インストール)されませんでした。





この後、AVRStudio5.1本体のインストールが実行されます。
特別な操作はありませんでした。



最後に、『昼夜逆転』工作室の説明に従って、VisualStudio 2010 Shell(英語版)をアンインストールしました。


3 開発作業

3.1 IDEの操作

AVRStudio5.1を起動します。



新規プロジェクトを作成します。
このとき、『昼夜逆転』工作室の指摘にあった、「AVRGCCテンプレートが選択できない」現象は、5.1 では発生しませんでした。

「ソリューション」は、Visual Studioの機能の一つで、複数のプロジェクトをまとめて取り扱う単位です。
例えば、プログラム本体と、専用のライブラリを並行して開発する場合などです。
今回はチェックを外して未使用にします。



(プロジェクト名).c ファイルが生成されて表示されます。
必要に応じて、「ソリューションエクスプローラ」画面から、ファイル名を変更します。
また、VisualStudio の機能である「ビルド構成」を、「Release」に設定します。本項目も、『昼夜逆転』工作室に判りやすく説明されています。



動作クロックを宣言します。これも『昼夜逆転』工作室の説明通り、「プロジェクト」-「(プロジェクト名)のプロパティ」を選択して設定します。



3.2 ライターの操作

3.2.1 起動・設定

avrdude-GUI.exe を起動します。
「Programmer」を、使用しているライターに設定します。AE-UM232Rは、そのものズバリの名称が選択できます。



3.2.2 MPU検出・ヒューズビット

YCIT版の特徴のひとつである、MPU自動検出を行います。
「Device Read」ボタンを押下すると、MPU名が検出され、同時にヒューズビットを読み取ります。
このとき、ステータスにエラーが無いことを確認しておきます。


Extend Bit の値は、未使用ビットを「1」としたときの値が表示されます(AVR仕様書通り)。なお、括弧内の値が、同ビットを「0」としたときの値のようです。

ヒューズビットの書き換えは、Low / High / Extend の何れかを編集し、「Set」ボタンを押下します。
(20130926追記)未使用ビットの取り扱いが柔軟になっています。例えば、Extend Bit に「03」を明示的に書き込んでも、AVRには「FB」と反映され、結果は「FB(03)」と表示されます。

「Fuse Calc」ボタンを押下すると、ヒューズビットの計算を行ってくれるサイトが表示されます。
このサイトは、AVRのマニュアルと睨めっこしないで直感的に計算できるので、非常に便利です。

3.2.3 バイナリ書き込み

「flash」欄のコンボボックスから、書き込むバイナリファイルを選択し、「Write」ボタンを押下して書き込みます。
書き込み結果はステータス表示されます。
また、「Verify」ボタンを押下して、ファイルとROMの内容を比較できました。


書き込みが終了すると、MPUが自動リセットされ、速やかに動作に移行しました(arduinoライクで快適)。
avrdude-serjtag で使っていた、-E reset オプションを明示して適用する必要は、ありませんでした。


3.2.4 課題

AE-UM232Rのシリアルポート機能を利用して、「Debugging Tools」機能が使用できるはずですが、現状グレーアウト状態で使用できていません。

引き続き調査して、分かり次第報告します。


次回予定

永らく中断していました、Ni-MH充電器のソフトウエア制御の検討に戻る予定です。

最後に、今回報告の参考とさせて頂きました、

Kimio Kosakaさま
山形県立産業技術短期大学校 千秋研究室さま
『昼夜逆転』工作室さま

に感謝申し上げます。

2013年6月22日土曜日

Ni-MH充電器の製作(5)


前回、DC-DCコンバータ部の回路が決定しましたが、今回は、以下状況の通り、同基板の実装を先に行うこととなりました。


5 DC-DCコンバータ部 基板実装

5.1 バラック制御

まず、バラック状態で、電流検出 ⇒ ADC ⇒ PWM+-の制御がどの位できるのかを調べます。
手始めに、電流検出だけ有効にして、制御可能となるようにプログラムを修正します。
以下の通り、OCR1Aレジスタを増減する単純なものです。

※電圧検出を実装するまでは、充電池を繋ぐことはできないので、代わりにダミー抵抗(今回は手持ちの22Ω)を接続しておきます。

(以下抜粋です)

    uint16_t uDataV = 0;
    uint16_t uDataI = 0;

    uint16_t uRef = 0x004a;
    OCR1A = 0;



    while(1){

        uDataV = ADC_GetData(5); // ADC5
        uDataI = ADC_GetData(4); // ADC4

//      DispBarLED(uDataV);
        DispBarLED(uDataI);


        if(uDataI < uRef)
            OCR1A++;
        else if(uDataI > uRef)
            OCR1A--;

        _delay_ms(10);

    }



電流検出抵抗に仮設定で約85mAを流し、Isense端子の波形を観察したのが、以下の写真です。


負荷が一定抵抗なので、制御は出来ているように見えますが、全般にノイズが乗り、検出電位がスイッチング電流に揺さぶられています。
何れも、バラック接続による回り込みが原因と考えられます。
また、大物部品がバラックのままでは、不注意でショートや破損の恐れも考えられます。
やはり、配線を基板化して、然るべき引き回しにした上で、プログラムの本検討に移行することにします。


5.2 パターン検討

例によって、基板は「ICB-86」使用を前提に検討します。
私のような、ハンダ付けの下手な者にとって、手配線の軽減される同基板は手放せません。

中央部のパターンを活用して、なるべく距離が短くなるように配置を考えます。
特に、グランドの配線に注意します。グランドの入力側にはスイッチング電流が流れるので、電圧/電流検出用のリターンは、グランドの途中から引き出さないように注意します。

上記を留意して決定したパターンを、以下に示します。


スイッチング用FET(右上の放熱器 2SJ477)の足配置を出発点に、向かって右が入力、左が出力に落ち着きました。

また、上記パターンにおいて、ドライブ用FET(Q11 2SK982)が実装されていませんが、これは、同FETをマイコン部に移動し、将来の拡張性(マイコン部出力をオープンドレイン回路にしておくと、拡張性が良い)を考慮したものです。

また、電流検出抵抗(左上 4.7Ω)の直右に、同抵抗と並列にコネクタを追加しています。
これも、将来の拡張性(電流検出抵抗を可変にする、etc)を考慮したものです。


5.3 部品実装及び通電テスト

このパターンに従って、部品を実装した基板を、以下に示します。


この基板に、電源・ダミー抵抗・マイコン部(含むドライブ用FET)を繋いで、通電します。


基板における、Isense端子の波形が、以下の写真です。


ノイズ、グランドの回り込みが無くなり、良好な波形となりました。
スイッチングON時にスパイクが乗っているのが気になりますが、プローブやジャンパー線を動かすと変化するので、今後の実装で改善できると思われまます。
よって、DC-DCコンバータ部 基板実装は終了とします。


次回の予定

遅くなりましたが、プログラムの本検討に入りたいと思います。