Technical Information/GR-CITRUS
GR-CITRUS の省電力機能
2019/10/19
GR-CITRUS は単純なループ動作で42mA程度の電流を消費しますので、バッテリーでの長時間動作には不向きです。標準のライブラリにはありませんが、マイコン自体は省電力機能を備えていますので評価してみます。
方針
- Arduino言語を用いる
- 開発環境はIDE for GR を使用
- 定期的に省電力状態から復帰し、所定の動作を終えた直後に再び省電力状態に移行する
動作説明
- RX631で大幅に省電力化できるのはソフトウェアスタンバイモードかディープスタンバイモードですが、後者はより省電力であるもののリセット状態で復帰します。また、GR-CITRUS自体の暗電流が700uA程度(VBUS分圧抵抗350uA+LDO自己消費電流350uA)あり、後者の優位性があまりありません。これらの理由により、今回は使い勝手の良いソフトウェアスタンバイモードを用います。(実測では、CPU自体の消費電流は前者が180uA、後者が80uAといったところです)
- ソフトウェアスタンバイモードで動作できるタイマはRTCのみです。
- RTCでソフトウェアスタンバイモードの復帰条件にできる割込は、周期割込、アラーム割込のいずれかです。以下の3例を作りました。(最後のは検証しきれてませんが多分問題無いです)
- 周期割込を用い、GR-CITRUSのオンボードLEDをフラッシュ (1/256, 1/128, 1/64, 1/32, 1/16, 1/8, 1/4, 1/2, 1, 2 秒のいずれかを指定)
- アラーム割込を用い、GR-CITRUSのオンボードLEDをフラッシュ(秒指定で、1秒以上24時間以下)
- アラーム割込を用い、GR-CITRUSのオンボードLEDをフラッシュ(秒指定で、1秒以上~上限なし)
ソフトウェア
- 周期割込使用( 1/2^8s(=1/256s)~1/2^-1s(=2s) )
// RTCの周期割込を利用したソフトウェアスタンバイモード動作
// 1/2秒周期のRTC周期割込をソフトウェアスタンバイモードの
// 復帰トリガとし、1/2秒毎にLEDをフラッシュさせる。
// ソフトウェアスタンバイ中は消費電流が 1mAを切る省電力状態となる。
#include <RTC.h>
RTC_TIMETYPE t;
int led = PIN_LED0; // 4
void ensleep(void) { // RX631をソフトウェアスタンバイモードにする
SYSTEM.PRCR.WORD = 0xA503u; // Protection off
SYSTEM.SBYCR.BIT.SSBY = 1; // WAIT命令後にソフトウェアスタンバイモードに遷移
while(!SYSTEM.SBYCR.BIT.SSBY) ;
__asm("wait");
SYSTEM.PRCR.WORD = 0xA500u; // Protection on
}
void setup() {
pinMode(led, OUTPUT);
// リセット後の動作:200ms周期で3回LEDを点滅
for (int i=0;i<3;i++){
digitalWrite(led,HIGH);
delay(100);
digitalWrite(led,LOW);
delay(100);
}
rtc_init(); //リアルタイムクロックを有効化
//ダミーの時刻を設定する
t.year = 2000;
t.mon = 1;
t.day = 1;
t.weekday = RTC_WEEK_SATURDAY;
t.hour = 0;
t.min = 0;
t.second = 0;
rtc_set_time(&t); // ここで初めてRTCが動作し始める
// 周期割り込み1/2秒 および周期割り込み許可を有効に
RTC0.RCR1.BYTE = 0xd4;
while (RTC0.RCR1.BYTE != 0xd4) ; // レジスタ設定が反映されるまで待機
IPR(RTC, PRD) = 3; // RTCの周期割込の割込優先度を3に設定
IEN(RTC, PRD) = 1; // RTCの周期割込を許可
IR(RTC, PRD) = 0; // RTCの周期割込の割込フラグをクリア
}
void loop() {
digitalWrite(led,HIGH);
delay(10);
digitalWrite(led,LOW);
ensleep(); // 次のRTC周期割り込みがかかるまでソフトウェアスタンバイモードへ移行
}
- アラーム割込使用(1s~24h)
// RTCのアラーム割込を利用したソフトウェアスタンバイモード動作
// 1秒周期のRTCアラーム割込をソフトウェアスタンバイモードの
// 復帰トリガとし、1秒毎にLEDをフラッシュさせる。
// ソフトウェアスタンバイ中は消費電流が 1mAを切る省電力状態となる。
// RTCを動作させたままアラーム時間を都度変更していくため、周期は正確。
// 設定できる間欠動作の周期は24時間以内で、秒数で設定する。
// 24時間を超える値に設定された場合は、24時間で割った余りに設定する。
//
#include <RTC.h>
#include <time.h>
RTC_TIMETYPE t;
int led = PIN_LED0;
static inline uint8_t HEX2BCD(int s16HEX)
{
return ((s16HEX / 10) << 4) | (s16HEX % 10);
}
/**
* アラーム時間を(時、分、秒)で設定します。
*
* @param[in] hour 時を指定します。
* @param[in] min 分を指定します。
* @param[in] sec 秒を指定します。
*
* @retval 0:アラームの設定に失敗しました。
* @retval 1:アラームの設定に成功しました。
*
* @attention 時刻の値はBCDではありません。
***************************************************************************/
int rtc_set_alarm_time_hms(int hour, int min, int sec)
{
/* Configure the alarm as follows -
Alarm time - 12:00:00
Enable the hour, minutes and seconds alarm */
RTC0.RSECAR.BYTE = HEX2BCD(sec);
RTC0.RMINAR.BYTE = HEX2BCD(min);
RTC0.RHRAR.BYTE = HEX2BCD(hour);
RTC0.RSECAR.BIT.ENB = 1;
RTC0.RMINAR.BIT.ENB = 1;
RTC0.RHRAR.BIT.ENB = 1;
RTC0.RDAYAR.BIT.ENB = 0;
RTC0.RMONAR.BIT.ENB = 0;
RTC0.RYRAREN.BIT.ENB = 0;
RTC0.RWKAR.BIT.ENB = 0;
/* Enable alarm and interrupts*/
RTC0.RCR1.BIT.AIE = 1;
while(!RTC0.RCR1.BIT.AIE);
/* Enable RTC Alarm interrupts */
IPR(RTC, ALM) = 3u;
IEN(RTC, ALM) = 1u;
IR(RTC, ALM) = 0u;
return 1;
}
void ensleep(void) { // RX631をソフトウェアスタンバイモードにする
SYSTEM.PRCR.WORD = 0xA503u; // Protection off
SYSTEM.SBYCR.BIT.SSBY = 1; // WAIT命令後にソフトウェアスタンバイモードに遷移
while(!SYSTEM.SBYCR.BIT.SSBY) ;
__asm("wait");
SYSTEM.PRCR.WORD = 0xA500u; // Protection on
}
void set_wakeup_period(int sec){ // 間欠動作の周期を設定する 24時間以内の秒数で設定
RTC_TIMETYPE t_now;
int sumsec, tmp, next_h, next_m, next_s;
if(sec >86400){ // 間欠動作の周期が24時間を超える場合は、24時間で割った余りに設定
sec = sec % 86400;
}
rtc_get_time(&t_now); // RTCから現在時刻を取得
sumsec = t_now.hour*3600 + t_now.min*60 + t_now.second; //秒数に変換
tmp = sumsec + sec; //次のアラーム時刻の「秒」表現
if( tmp >= 86400 ){ //86400=3600*24 24時間以上であるなら
tmp = tmp - 86400; //86400=3600*24 24時間を減ずる
}
next_h = tmp / 3600;
next_m = (tmp % 3600) / 60;
next_s = (tmp % 3600) % 60;
rtc_set_alarm_time_hms(next_h,next_m,next_s); // 次のアラーム時刻を設定
}
void setup() {
pinMode(led, OUTPUT);
// リセット後の動作:200ms周期で3回LEDを点滅
for (int i=0;i<3;i++){
digitalWrite(led,HIGH);
delay(100);
digitalWrite(led,LOW);
delay(100);
}
rtc_init(); //リアルタイムクロックを有効化
t.year = 2000;
t.mon = 1;
t.day = 1;
t.weekday = RTC_WEEK_SATURDAY;
t.hour = 23; //0
t.min = 59; //0
t.second = 55; //0
rtc_set_time(&t); // ここで初めてRTCが動作し始める
}
void loop() {
digitalWrite(led,HIGH);
delay(10);
digitalWrite(led,LOW);
set_wakeup_period(1); // 1秒後にアラームをセット
ensleep(); // 次のアラーム割り込みがかかるまでソフトウェアスタンバイモードへ移行
}
- アラーム割込使用(1s~上限なし)
// RTCのアラーム割込を利用したソフトウェアスタンバイモード動作
// 1秒周期のRTCアラーム割込をソフトウェアスタンバイモードの
// 復帰トリガとし、1秒毎にLEDをフラッシュさせる。
// ソフトウェアスタンバイ中は消費電流が 1mAを切る省電力状態となる。
// RTCを動作させたままアラーム時間を都度変更していくため、周期は正確。
//
// IDE for GRのデフォルトの開発環境ではRTCのtimezoneはGMTに設定されて
// いるので、 localtime()とgmtime()による取得時間は一致する。したがって、
// mktime()関数によるlocal時間の1970年1月1日0:0:0からの経過秒数(UNIX時間)
// は双方で一致する。
// また、RTC関連のC言語標準ライブラリの設定はうるう秒に対応していない為、
// マイコンのRTCから取得した日時データをC言語標準ライブラリにてUNIX時間に
// 変換し加工したあとでC言語標準ライブラリにてマイコン用のRTCの日時に
// 変換し直しても特に問題は生じない。
//
#include <RTC.h>
#include <time.h>
RTC_TIMETYPE t;
int led = PIN_LED0;
static inline uint8_t HEX2BCD(int s16HEX)
{
return ((s16HEX / 10) << 4) | (s16HEX % 10);
}
/**
* アラーム時間を設定します。
*
* @param[in] year 年を設定します。
* @param[in] mon 月を設定します。
* @param[in] day 日を指定します。
* @param[in] hour 時を指定します。
* @param[in] min 分を指定します。
* @param[in] sec 秒を指定します。
* @param[in] week_flag 曜日を指定します。複数の曜日を指定する場合は論理和で接続します。
*
* @retval 0:アラームの設定に失敗しました。
* @retval 1:アラームの設定に成功しました。
*
* @attention 時刻の値はBCDではありません。
***************************************************************************/
int rtc_set_alarm_time_all(int year, int mon, int day, int hour, int min, int sec, int week_flag)
{
/* Configure the alarm as follows -
Alarm time - 12:00:00
Enable the hour, minutes and seconds alarm */
RTC0.RSECAR.BYTE = HEX2BCD(sec);
RTC0.RMINAR.BYTE = HEX2BCD(min);
RTC0.RHRAR.BYTE = HEX2BCD(hour);
RTC0.RDAYAR.BYTE = HEX2BCD(day);
RTC0.RMONAR.BYTE = HEX2BCD(mon);
RTC0.RYRAR.WORD = HEX2BCD(year % 100);
if(week_flag <= 0x06){
RTC0.RWKAR.BYTE = week_flag;
}
RTC0.RSECAR.BIT.ENB = 1;
RTC0.RMINAR.BIT.ENB = 1;
RTC0.RHRAR.BIT.ENB = 1;
RTC0.RDAYAR.BIT.ENB = 1;
RTC0.RMONAR.BIT.ENB = 1;
RTC0.RYRAREN.BIT.ENB = 1;
if(week_flag <= 0x06){
RTC0.RWKAR.BIT.ENB = 1;
} else {
RTC0.RWKAR.BIT.ENB = 0;
}
/* Enable alarm and interrupts*/
RTC0.RCR1.BIT.AIE = 1;
while(!RTC0.RCR1.BIT.AIE);
/* Enable RTC Alarm interrupts */
IPR(RTC, ALM) = 3u;
IEN(RTC, ALM) = 1u;
IR(RTC, ALM) = 0u;
return 1;
}
void ensleep(void) { // RX631をソフトウェアスタンバイモードにする
SYSTEM.PRCR.WORD = 0xA503u; // Protection off
SYSTEM.SBYCR.BIT.SSBY = 1; // WAIT命令後にソフトウェアスタンバイモードに遷移
while(!SYSTEM.SBYCR.BIT.SSBY) ;
__asm("wait");
SYSTEM.PRCR.WORD = 0xA500u; // Protection on
}
void set_wakeup_period(int sec){
RTC_TIMETYPE t_now;
struct tm tn;
time_t timer;
rtc_get_time(&t_now); // RTCから現在時刻を取得
tn.tm_year = t_now.year-1900;
tn.tm_mon = t_now.mon-1;
tn.tm_mday = t_now.day;
tn.tm_wday = t_now.weekday;
tn.tm_hour = t_now.hour;
tn.tm_min = t_now.min;
tn.tm_sec = t_now.second;
timer = mktime(&tn); // 1970年からの秒数に変換
timer += sec; // 次のアラーム時刻(秒表現)の算出
tn = *localtime(&timer); // 1970年からの秒データを日付に変換
rtc_set_alarm_time_all(tn.tm_year+1900, tn.tm_mon+1, tn.tm_mday, tn.tm_hour, tn.tm_min, tn.tm_sec, 0xff);
}
void setup() {
pinMode(led, OUTPUT);
// リセット後の動作:200ms周期で3回LEDを点滅
for (int i=0;i<3;i++){
digitalWrite(led,HIGH);
delay(100);
digitalWrite(led,LOW);
delay(100);
}
rtc_init(); //リアルタイムクロックを有効化
t.year = 2000;
t.mon = 1;
t.day = 1;
t.weekday = RTC_WEEK_SATURDAY;
t.hour = 0;
t.min = 0;
t.second = 0;
rtc_set_time(&t); // ここで初めてRTCが動作し始める
}
void loop() {
digitalWrite(led,HIGH);
delay(10);
digitalWrite(led,LOW);
set_wakeup_period(1); // 1秒後にアラームをセット
ensleep(); // 次のアラーム割り込みがかかるまでソフトウェアスタンバイモードへ移行
}
性能評価
- ソフトウェアスタンバイ時は0.9mA程度の消費電流となります。Arduino環境のnopループでは42mA程度消費していますので、消費電力は約1/45に低減できることになります。例えばバッテリー駆動で10分に1回センサ処理するような省エネ機能の場合は、45倍程度の動作時間になります。
|