ブログ/こばさんの wakwak 山歩き

2016-12-10 リチウムイオン電池にも使えるハイサイド動作な過放電防止回路

リチウムイオン電池にも使えるハイサイド動作な過放電防止回路

| リチウムイオン電池にも使えるハイサイド動作な過放電防止回路を含むブックマーク

 最近は さくら IoT で色々と遊んでおります。

 私は Androidシールド でなく、ブレークアウトボードのほうを選択したのですが、Androidシールド と違ってブレークアウトボードのほうには昇降圧な DCDC が搭載されていて、3〜5.5V という幅広い電圧に対応しています。

※ Androidシールド と ブレークアウトボード との違いは こちらを参照

 

 「どうぞ電池を使って遊んでやって下さい」って風ですよね。

 なので、設計者の意図を汲み取って充電池をつないで外に持ち出すわけですが、ブレークアウトボードに載ってる昇降圧は確かに便利ではあるものの、どうも電池の電圧が空っぽに近づいてもカットオフしてくれるわけでもなく、電気を搾り取ろうとしているみたいなんです。


 ということで過放電防止回路の出番。


 過放電防止と言えば 4年ほど前に M51957B/M51958B を使った例で書いてます が、今回はよりシンプルなものを。


http://dl.ftrans.etr.jp/?1ee424bb01e24571ab133f21d85431700771c0ec.png


 秋月の 3.3V 向けリセットIC を投入します。

 リセット電圧は 2.63V と固定ではありますが、リチウムイオン充やニッケル水素の充電池にはちょうどいい電圧です。

(ニッケル水素にとってはやや低めではあるものの、カットオフされると電池の電圧は戻るので、ダメージを与えるほどでもない)


 んで、こいつを 秋月16ホール基板 に実装。


http://dl.ftrans.etr.jp/?fa4908681f0449a18f7031d91f1be329856ed7d7.jpg


http://dl.ftrans.etr.jp/?385b24fabcdd49ceab052d589178deadd7dfda8e.jpg http://dl.ftrans.etr.jp/?d51a9276fa6849aba58357471113d6de582c5dfd.jpg


 これで安心してニッケル水素電池を利用できます〜♪

2016-12-01 さくら IoT Platform と ESP8266 と GPS でロケモニ (後編)

さくら IoT Platform と ESP8266 と GPS とでロケーションモニタリングしてみる(後編)

さくら IoT Platform と ESP8266 と GPS とでロケーションモニタリングしてみる(後編)を含むブックマーク

 いったんは壊してしまった さくら IoT の通信ボード ですが、何とか修復することに成功 しました。

 あと一歩の、ロケーションモニタリング(略してロケモニ)の続きです。


 ESP8266 の light sleep(消費900μ〜1mA)をピン割り込みで起こしたく通信ボードの WAKE_OUT 信号を ESP8266 に入力させてやるわけですが、ESP8266 にプログラムを書き込む時に操作しないといけない IO0 のタクトスイッチに重ねてやりました。

 デバッグ中にスイッチを押してやることで WAKE_OUT の変化の代わりに使えるので便利ですからね。

 light sleep から復帰させるトリガーは

  gpio_pin_wakeup_enable(GPIO_ID_PIN(0), GPIO_PIN_INTR_LOLEVEL);

という風に、どのピンのナニで起こすかという風に指定できます。

 引数の2つ目は GPIO_PIN_INTR_LOLEVEL または GPIO_PIN_INTR_HILEVEL を指定でき、前者はピンが LOW になったら、後者はピンが HIGH になったら、という風ぽいのですが、現実にはもうすこし条件があるようなのです。

 手探りで分かったことですが、GPIO_PIN_INTR_LOLEVEL の場合のとき

  • light sleep 開始時に HIGH になっていないといけない
  • light sleep 期間中の HIGH→LOW な変化でなく LOW→HIGH な変化をトリガーに light sleep から抜ける

 つまり HIGH → sleep開始 → HIGH → LOW → HIGH というシーケンスを与えないと復帰しないぽいのです。


 さくら IoT の通信ボードに生えてる WAKE_OUT ピンは普段は LOW で、データが届くと(マイコンに読んでほしいときに) HIGH になり、受信キューが空っぽになるまで HIGH を維持しつづけ、空っぽになると LOW になります。

 NPN トランジスタを使って信号線は反転させて使うつもりなので、マイコンからみたら、普段は HIGH、データが届くと LOW という風な負論理として扱えるのですけど、LOW に下がったうえで HIGH に戻してやらないと ESP8266 は light sleep から復帰してくれません。


 HIGH→LOW を受けて HIGH→LOW→HIGH を出す回路、いわゆるワンショットタイマーを仕込んでやらないといけません。


 ということで今日の回路図

 LDOを破壊した私 にしか用をなさない 1.8V な三端子の部分は削除して一般的な回路図に戻しました。


http://dl.ftrans.etr.jp/?ec93887c863947238d37d8aeebb2ebd15057bc14.png


 通信ボード と ESP8266 との間にある 2SC1815 の付近が変更した部分です。

 厳密には、コレクタを ESP8266 の IO0 に直結させていたのを止めてコンデンサと抵抗を並列させたものを挟みました。


 通信ボードの WAKE_OUT が LOW→HIGH になったら 2SC1815 が ON になり、コレクタが GND に落とされます。

 コンデンサが挟まってますが、コレクタが GND に落ちた瞬間のコンデンサは 0Ω と等価で、ESP8266 の IO0 は 0V になります。

 コンデンサへの充電が進行するにつれてその等価抵抗は 0→∞Ω に変化していていき、伴って ESP8266 の IO0 の電圧は上がっていきます。


 方形波じゃないですが、これで HIGH→LOW→HIGH の変化に偽装することができました。

 コンデンサにパラってある 100kΩ は WAKE_OUT が LOW に戻って 2SC1815 が OFF になった時、次回に備えてコンデンサの電荷を抜くためのものです。

 ESP8266 側のプルアップ 10kΩ に対して 100kΩ を選択してますので、プルアップ抵抗を 20kΩ にした人は 200kΩ くらいに変更してください。(おおよそ10倍が適値)

(比が小さすぎると ESP8266 の HIGH/LOW の判定閾値に影響)

http://dl.ftrans.etr.jp/?315c29d2d9cd4dedb177bf8ebec0b43c803a48f9.png


 いつも世話になってる CRローパス・フィルタ数計算ツール で表示させたシミュレーション値ですが、左のような波形になってるはずです。

(オシロを出して測るのが面倒だった)


 放電は10倍の100kΩを介して行われるので、充電の10倍かかります。

 この間は light sleep からの復帰が行えない時間になるのですが、この時間分だけ待機してから light sleep に再入するという風にソフト側で考慮できるので大した問題じゃありません。


 プログラムにも僅かに手を加えました。


#include <time.h>
#include <ESP8266WiFi.h>
#include <SakuraIO.h>

#include "TextLCD.h"
#include "nmea.h"

extern "C" {
#include "user_interface.h"
#include "gpio.h"
}

SakuraIO_I2C  sakuraio;
TextLCD       lcd;
NMEA          nmea;

const char CHAR_ANTENNA_BAR = 0x07;
const int REPORT_INTERVAL  = 60;        // 60秒おきに定期送信

// 電波強度を測定し、LCDの07Hに強度グラフを登録する
void setAntennaBar()
{
  static short lastQry = -1;
  short qry = sakuraio.getSignalQuarity();
  if(lastQry != qry)
  {
    Serial.print(millis());
    Serial.print(" SignalQuarity ");
    Serial.println(qry);
    lastQry = qry;
    if(qry == 0) {
      char font[8] = {0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00};
      lcd.writeFont(0x07, font);
    } else {
      char font[8] = {0x01, 0x03, 0x03, 0x07, 0x07, 0x0f, 0x0f, 0x1f};
      for(int y = 0; y < 8; y++)  {
        if(qry < 5) font[y] &= 0x1e;
        if(qry < 4) font[y] &= 0x1c;
        if(qry < 3) font[y] &= 0x18;
        if(qry < 2) font[y] &= 0x10;
      }
      lcd.writeFont(CHAR_ANTENNA_BAR, font);
    }
  }
}

// unix時刻を文字列に変換する
String convertUnixtime(uint64_t unixtime64)
{
  long int unixtime = unixtime64 / 1000;
  struct tm *ut = localtime(&unixtime);
  uint16_t msec = unixtime64 % 1000;
  
  char result[20];
  sprintf(result, "%02d:%02d:%02d.%03d", ut->tm_hour, ut->tm_min, ut->tm_sec, msec);
  return(result);
}

// unix時刻を取得しLCDに表示する
void getUnixtime()
{
  lcd.locate(0, 0);
  lcd.printf("%c", CHAR_ANTENNA_BAR);

  uint64_t  unixtime64 = sakuraio.getUnixtime();
  if(unixtime64 > 0)
  {
    unixtime64 += 9 * 60 * 60 * 1000;   // TimeZone +9h
    String unixtime = convertUnixtime(unixtime64);
    
    lcd.locate(1, 0);
    lcd.print(unixtime);
  } else  {
    Serial.print(millis());
    Serial.println(" can't get Unixtime");
    delay(1000);
  }
}

// 位置情報を送信する
void reportLocation(uint8_t channel)
{
  uint8_t value[8];

  lcd.locate(0, 1);
  lcd.print("report location ");
  
// GPS情報
  memcpy(&value[0], &nmea.latitude, 4);
  memcpy(&value[4], &nmea.longitude, 4);
  sakuraio.enqueueTx(channel, value, millis() - nmea.millis_latitude_longitude);

  memcpy(&value[0], &nmea.altitude, 4);
  memcpy(&value[4], &nmea.geoid, 4);
  sakuraio.enqueueTx(channel, value, millis() - nmea.millis_altitude_geoid);

  memcpy(&value[0], &nmea.courseover, 4);
  memcpy(&value[4], &nmea.speed, 4);
  sakuraio.enqueueTx(channel, value, millis() - nmea.millis_courseover);

  sakuraio.send();
}

// 周辺のWiFi情報を送信する
void reportWiFi(uint8_t channel)
{
  uint8_t value[8];

  lcd.locate(0, 1);
  lcd.print("report WiFi APs ");

  WiFi.enableSTA(true);
  int8_t n = WiFi.scanNetworks(false, true);  // number of networks found

  Serial.print(millis());
  Serial.print("  WiFi.Scaned:");
  Serial.println(n);
  for(int i=0; i<n; i++)
  {
    int16_t rssi = WiFi.RSSI(i); 
    Serial.printf("  BSSID/SSID %d %02x:%02x:%02x:%02x:%02x:%02x", rssi, WiFi.BSSID(i)[0],WiFi.BSSID(i)[1],WiFi.BSSID(i)[2],WiFi.BSSID(i)[3],WiFi.BSSID(i)[4],WiFi.BSSID(i)[5]);
    Serial.print(" ");
    Serial.print(WiFi.SSID(i));

    memcpy(&value[0], &rssi, 2);
    memcpy(&value[2], WiFi.BSSID(i), 6);
    sakuraio.enqueueTx(channel, value);

    for(int j=0; j<4; j++)
    {
      memcpy(value, &WiFi.SSID(i)[j * 8], 8);
      sakuraio.enqueueTx(channel, value);
    }
    sakuraio.send();

    for(;;)
    {
      uint8_t queue, immediate;
      if(sakuraio.getTxStatus(&queue, &immediate) == CMD_ERROR_NONE && queue != 0x01 )  break;
      Serial.print(".");
      delay(100);
    }
    Serial.println("");
  }
  WiFi.enableSTA(false);
}

// 指令を確認
bool checkOrder()
{
  bool result = false;
  uint8_t available, queued;
  if(sakuraio.getRxQueueLength(&available, &queued) == CMD_ERROR_NONE && queued > 0)
  {
    // データが届いている場合
    Serial.print(millis());
    Serial.print(" Rx Available=");
    Serial.print(available);
    Serial.print(" Queued=");
    Serial.println(queued);
    
    for(uint8_t i = 0; i < queued; i++)
    {
      uint8_t channel;
      uint8_t type;
      uint8_t value[8];
      int64_t offset;
      if(sakuraio.dequeueRx(&channel, &type, value, &offset) == CMD_ERROR_NONE)
      {
        //デバッグ用
        Serial.print("     ch:" + String(channel));
        Serial.print("   type:" + String((char)type));
        Serial.print(" values:");
        for(uint8_t b = 0; b < 8; b++)  Serial.print(value[b]);
        Serial.print(" offset:" + String((int32_t)offset));

        // キャラクタ液晶
        lcd.locate(14, 0);
        lcd.printf("%02x", channel);    // 右上に channel 表示
        lcd.locate(0, 1);               // 下段に受信データを16進で表示
        for(uint8_t b = 0; b < 8; b++)  lcd.printf("%02x", value[b]); 

        // 3秒間 GPS のデータを待ち受ける
        unsigned long startGPS = millis();
        while(millis() - startGPS < 3 * 1000)
          nmea.read(Serial.read());

        reportLocation(channel);
        reportWiFi(channel);
        
        delay(1000);    // ワンショット用コンデンサの電荷抜きを兼ねるので省略不可
        lcd.cls();
        result = true;
      }
    }
  }
  return result;
}

void wakeup() {
  wifi_fpm_close();
}

void sleep() {
  wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); //light sleep mode
  gpio_pin_wakeup_enable(GPIO_ID_PIN(0), GPIO_PIN_INTR_LOLEVEL); //set the interrupt to look for LOW pulses on Pin 0 (the PIR).
  wifi_fpm_open();
  delay(100);
  wifi_fpm_set_wakeup_cb(wakeup); //wakeup callback
  wifi_fpm_do_sleep(0xFFFFFFF); 
  delay(100);
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600, SERIAL_8N1);
  Serial.println("");
  Serial.print(millis());
  Serial.print("  Connecting to SAKURA IoT");

  WiFi.mode(WIFI_STA);
  WiFi.enableSTA(false);
  pinMode(0, INPUT);

  lcd.locate(0, 0);
  lcd.print("Connecting");
  lcd.locate(0, 1);
  lcd.print("to SAKURA IoT");
  delay(1000);

  short retry = 0;
  for(;;){
    if((sakuraio.getConnectionStatus() & 0x80) == 0x80) break;
    Serial.print(".");

    retry ++;
    lcd.locate(13, 0);
    lcd.printf("%03d", retry);
    delay(1000);
  }
  Serial.println("");

  lcd.cls();
}

void loop() {
  setAntennaBar();
  getUnixtime();
  
  if(!checkOrder())
    sleep();
}
  • 指令を処理する checkOrder() に放電時間を稼ぐウェイト(1秒)を追加
  • light sleep に突入させるための sleep() 関数を新設
  • 復帰後の処理を書く wakeup() 関数を新設
  • loop() の中を手直し

 テストでもバッチリ期待通りの動作をしてくれて、ひとまず形になりました。


 秋月の新商品 感震センサー も届いているので、以前にポチったまま部品庫の肥やしになってる 雷センサー と一緒に、次は、災害センサーでも作ってみようかなぁ