デサルフェータ―3号機(スケッチ)
パーツの選択も終えたので制御用のスケッチをプログラミング
主な仕様
- デサルフェータを実行する最低電圧を設けPWMをON/OFFする(75%以上で実行,充電中は当然ながらONになる)
- LEDを点灯点滅させて実行中か未実行か判別できるようにする(実行:2秒点灯-1秒消灯,未実行:1秒点灯ー8秒消灯)
- 異常発生時Attiny13aをリセットする(WDT使用)
- 約3日毎に再起動(リセット)する
機能の実装方法
①PWM出力
ハードウェアのPWM制御を利用して4000HzのDuty4.8%の12μsを出力だが,メインクロック9.6MHzのままで使いたかったので8分周の4700Hz(=9.6MHz/8/256)でDutyを調整することにした
メインとなる部分だし調整しておかないと次に進めないので,先ずはPWM出力のスケッチを組み実動テストを行い設定値を求め,発熱を抑えたためONタイムが8~9μsになる位のDuty値となった
本テストの際PB0でPWM制御(Dutyを変更しても変化しない)が行えないことが判りPB1を利用するよう回路を変更した
②インターバルタイマー
ATtiny13aはタイマー0しか持っていないためPWMで使用するとarduino環境のタイマーに関連する機能(例えばdelay関数)は使えなくなる
LEDの点滅を行いたいのでPWMのオーバーフロー割込みでカウントして秒単位のwait関数を用意した
オーバーフロー割込みだが,ISR(TIMER0_OVF_vect)で割込みしなくてかなり嵌った
ISR(TIM0_OVF_vect)がATtiny13aの正しいオーバーフロー割込みである
③WDT
システム異常が発生(特に熱暴走)することが想定されるため監視は必須
WDTのリセットはタイマー(実装はPWM割込みの積算で定期的にリセット)で行うことを考えていたが必要ない場合にPWM出力を止められなくなるため,WDTの割込みandリセット手順の割込みでリセットをリジェクトする方法で実装した(お勧めできない方法と説明書にはあるようだ)
④スリープ
省電力モードが必要な訳ではなくループさせて待つは寂しいプログラミングなのでスリープさせることにした
原因が判らないのだが,asm volatile ("sleep")ではスリープしないでのでsleep_mode()を使用(コードはさほど増えないので問題にはならなかった)
⑤ADC
バッテリー電圧のチェックにADCを利用
調整しやすいコンパレーターを使ってみる手もあったがまたの機会とした
回路
- FUSEを追加
- L3があるのでL2の位置を変えた
- PB0(PWM0)ではPWMのDutyが制御できないのでPB1(PWM1)に変更
- FETのゲートを不定にさせないためR5を追加
- PB0,PB4を使用して動作させるバッテリ電圧を設定可能にできる(今回は未実装)
スケッチ
// デサルフェータ―3号機
//
// ATMEL ATTINY13 / ARDUINO
//
// +-\/-+
// ADC0 (D 5) PB5 1| |8 Vcc
// ADC3 (D 3) PB3 2| |7 PB2 (D 2) ADC1
// ADC2 (D 4) PB4 3| |6 PB1 (D 1) PWM1
// GND 4| |5 PB0 (D 0) PWM0
// +----+
//
// 1: Reset
// 2: LED
// 3: Serial Out
// 4: GND
// 5: SW (常時ピン)
// 6: PWM
// 7: ADC(バッテリー電圧確認)
// 8: Vcc 5.0V
//
// システムクロック:9.6MHz
// PWM周波数:4.7KHz(8分周)
//
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
//#define DEBUG 1
#ifdef DEBUG
#define BAUD_RATE 38400
#include <BasicSerial3.h>
#endif
#ifdef DEBUG
static void serOut(const char *str) {
while(*str) TxByte(*str++);
}
//番号シリアル出力
static void numberOut(int v) {
char bf[8];
itoa(v, bf, 10); //10は十進数
serOut(bf);
}
static void newLine() {
serOut("\r\n");
}
//確認用シリアル出力
static void voltOut(int v) {
numberOut(v);
serOut("mV\r\n");
}
#endif
//ピンアサイン
#define PIN_PWM PB1 //PWM出力(注意:PB0だと指定通りのバルス幅が出ない)
#define PIN_ADC 1 //PB2(ADC1)電圧チェック
#define PIN_LED PB3 //動作LED
#define PIN_SW PB0 //常時実行スイッチ
#ifdef DEBUG
#define PIN_SER PB4 //SerialOut(Debug)
#endif
//PWM利用のための定義
#define PWM_STOP (B11111000) //PWM停止(分周ビットマスクとしても利用)
#define PWM_START ((0<<CS02)|(1<<CS01)|(0<<CS00)) //8分周
// 1 / (9.6MHz / 8分周 / 256) = 1 / 4687.5 = 0.000213 =(周期)
#define CNT_PSEC (4687) //カウント数/秒
// 12us / 213us * 256 = 14
// 10us / 213us * 256 = 12
// 9us : 10.8 , 8us : 9.6
//#define CNT_DUTY (10) //8.32us = (10) * 213us / 256
#define CNT_DUTY (6) //5.00us = (6) * 213us / 256
//#define CNT_DUTY (5) //4.16us = (5) * 213us / 256
//スリープ定義
#define SLP_MODE SLEEP_MODE_ADC
#define sleep() sleep_mode() //asm volatile ("sleep")
//WDT利用のための定義
#define WDT_reset() asm volatile ("wdr") //カウンタリセット
#define WDT_500MS (B000101)
#define WDT_1S (B000110)
#define WDT_2S (B000111)
#define WDT_4S (B100000)
#define WDT_8S (B100001)
//デサル動作電圧閾値(mV)
#define BTT_CHRG (13500)
#define BTT_100P (12600)
#define BTT_075P (12400)
#define BTT_050P (12200)
#define BTT_025P (12000)
#define BTT_ACTN BTT_075P
//PWM動作状態定義
#define DF_INIT 0 //初期状態
#define DF_STOP 1 //PWM停止中
#define DF_START 2 //PWM動作中
#define RESET_MAX (3600/9*24*3) //約3日間毎にリセット
static unsigned df_mode = DF_INIT;
static unsigned reset_count = 0;
//WDTリセット前に呼び出される割込み
ISR(WDT_vect) {
if(reset_count < RESET_MAX) {
//システムリセットを回避
WDTCR |= _BV(WDE)|_BV(WDTIE);
}
//reset_countはシステムリセットで初期化させる
}
static unsigned temp_count = 0; //秒換算数
static unsigned long sec_count = 0; //累積秒数
//タイマ0:オーバーフロー
ISR(TIM0_OVF_vect) {
if(++temp_count > CNT_PSEC) {
sec_count++;
temp_count = 0;
}
}
//PWMタイマー使用の時間待ち(秒)
static void wait(int t) {
unsigned tc, ttc;
unsigned long sc, ssc;
cli();
ttc = temp_count;
ssc = sec_count + t;
sei();
do {
cli();
tc = temp_count;
sc = sec_count;
sei();
} while(sc <= ssc && (sc != ssc || tc < ttc));
}
//WDT開始(割り込み後システムリセット動作設定)
static void WDT_set(int wt) {
cli(); //割り込み禁止
WDT_reset(); //ウォッチドッグタイマーリセット
WDTCR |= _BV(WDCE)|_BV(WDE)|_BV(WDTIE);
WDTCR |= wt; //タイマ設定
sei(); //割り込み許可
}
//WDT停止
static void WDT_off() {
cli(); //割り込み禁止
WDT_reset(); //ウォッチドッグタイマーリセット
MCUSR &= ~_BV(WDRF); //MCUSRレジスタのウォッチドッグフラグをクリア
WDTCR |= _BV(WDCE)|_BV(WDE); //WDCEとWDEをセット
WDTCR = 0; //ウォッチドッグ停止
sei(); //割り込み許可
}
//電圧(mV)
static int voltRead() {
ADCSRA = (1<<ADEN)|(1<<ADSC)|(0<<ADATE)|(0<<ADIF)|(0<<ADIE)|(0b100);
loop_until_bit_is_set(ADCSRA,ADIF);
long v = (long)ADC;
v += 10; //基準電圧,分圧抵抗調整分
v *= (5000 * 4); //基準電圧5V×3:1の分圧
v /= 1024;
#ifdef DEBUG
voltOut(v);
#endif
return((int)v);
}
void setup() {
WDT_off(); //起動直後WDT停止
WDT_set(WDT_8S); //ウォッチドッグタイマ8秒で開始
#ifdef DEBUG
OSCCAL = 91; //87 - 96 (91, 92)
DDRB = _BV(PIN_SER); //pinMode(PIN_SER, OUTPUT);
#endif
//pinMode(PB2, INPUT); analogReference(DEFAULT);
DIDR0 = _BV(PIN_ADC);
ADMUX = (0<<REFS0)|(0<<ADLAR)|PIN_ADC;
//pinMode(PIN_LED, OUTPUT); pinMode(PIN_PWM, OUTPUT);
DDRB |= _BV(PIN_LED)|_BV(PIN_PWM);
//pinMode(PIN_SW, INPUT_PULLUP);
DDRB &= ~_BV(PIN_SW);
PORTB |= _BV(PIN_SW);
//PWM設定
TCCR0A = (1<<COM0B1)|(1<<WGM01)|(1<<WGM00); //非反転出力, 高速PWM動作
TCCR0B = (0<<WGM02); //高速PWM動作(カウンタTOPは最大値の0xff)
OCR0B = CNT_DUTY; //デューティ比(カウンタTOP前にLOWにするための値)
//タイマー割込み
TIMSK0 |= (1<<TOIE0); //オーバーフロー(PWMのTOPで)割り込みを許可
#ifdef DEBUG
TCCR0B |= PWM_START; //常時PWM開始(8分周)
#endif
//スリープモード設定
set_sleep_mode(SLP_MODE);
}
void loop() {
#ifndef DEBUG
//バッテリー電圧チェック
if((PINB & _BV(PIN_SW)) && voltRead() < BTT_ACTN) {
//動作電圧より低い場合
if(df_mode != DF_STOP) {
TCCR0B &= PWM_STOP; //PWM停止
df_mode = DF_STOP;
}
WDT_off();
WDT_set(WDT_1S);
PORTB |= _BV(PIN_LED); //digitalWrite(PIN_LED, HIGH);
sleep();
WDT_off();
WDT_set(WDT_8S);
PORTB &= ~_BV(PIN_LED); //digitalWrite(PIN_LED, LOW);
sleep();
//9.5秒
} else {
if(df_mode != DF_START) {
TCCR0B |= PWM_START; //PWM開始(8分周)
df_mode = DF_START;
}
for(int n = 0; n < 3; n++) {
PORTB |= _BV(PIN_LED); //digitalWrite(PIN_LED, HIGH);
wait(2);
PORTB &= ~_BV(PIN_LED); //digitalWrite(PIN_LED, LOW);
wait(1);
}
//9秒
}
#else
voltRead();
wait(2);
#endif
reset_count++;
}
なんとか収まった
最大1024バイトのフラッシュメモリのうち、スケッチが866バイト(84%)を使っています。
最大64バイトのRAMのうち、グローバル変数が10バイト(15%)を使っていて、ローカル変数で54バイト使うことができます。
(2018.7.7 変更)
上記スケッチに反映
- 基準電圧と分圧抵抗の誤差による調整
- 常時動作SWを追加
- ONタイムを調整(DUTY変更)
最大1024バイトのフラッシュメモリのうち、スケッチが892バイト(87%)を使っています。
最大64バイトのRAMのうち、グローバル変数が10バイト(15%)を使っていて、ローカル変数で54バイト使うことができます。
Bugs
上記スケッチには認知の不具合があるので記載しておく
- loop()でWDTをOFFにしているためWDTでリセットされないタイミングがある
これはLEDで本機の状態が判るようにするのを優先したためで,もしLEDが点灯か消灯状態になったらハングアップ,暴走,ループなどの異常状態である(WDTが有効ならリセットされるので点滅の繰り返し)
なので,WDTをOFFに(WDTの操作は)しないでLEDを単に点灯もしくは消灯にしてsleep()させれば解決する
→ WDTのタイムアウトを2秒程度にしてLEDの点灯消灯を操作すれば同じように点滅させることができる
この方が良さそうなので良いタイミングで変更しようかと思う(以下のようになる)
void setup() {
WDT_off(); //起動直後WDT停止
WDT_set(WDT_2S); //ウォッチドッグタイマ2秒で開始
・
・
・
}
void loop() {
・
・
・
//バッテリー電圧チェック
if((PINB & _BV(PIN_SW)) && voltRead() < BTT_ACTN) {
//動作電圧より低い場合
if(df_mode != DF_STOP) {
TCCR0B &= PWM_STOP; //PWM停止
df_mode = DF_STOP;
}
sleep(); //最初は2秒以内となるので待ち
PORTB |= _BV(PIN_LED); //digitalWrite(PIN_LED, HIGH);
sleep(); //2秒間点灯
PORTB &= ~_BV(PIN_LED); //digitalWrite(PIN_LED, LOW);
sleep();
sleep();
sleep();
//10秒
} else {
・
・
・
}
- 次回:
- デサルフェータ―3号機(試験)へ