カテゴリー「STM32-ARM」の記事

NMEA-0183メッセージの解析

GPSモジュールが出力するNMEA-0183メッセージと呼ばれる出力文字列の解析プログラムの実験を行いました。最終的にはSTM32 Primer2に組み込んで使用するのですが、今回はPC上(VC++ 2008)で動作確認を行いました。

NMEA-0183メッセージとは

GPSモジュールが衛星を補足すると、以下のような文字列をシリアルインターフェースから吐き出します(自宅で収集したデーターのため、緯度・経度を示す部分は、xxxx.xxxx, yyyy.yyyyに書き換えています)。

$GPGGA,230400.600,xxxx.xxxx,N,yyyy.yyyy,E,1,4,39.03,-170.6,M,39.4,M,,*4D
$GPGLL,xxxx.xxxx,N,yyyy.yyyy,E,230400.600,A,A*56
$GPGSA,A,3,06,03,31,19,,,,,,,,,46.41,39.03,25.10*35
$GPGSV,3,1,10,16,64,350,,06,56,223,29,31,56,129,26,03,42,229,30*74
$GPGSV,3,2,10,23,34,287,,21,31,086,,19,17,218,26,13,14,320,*7A
$GPGSV,3,3,10,29,08,043,,25,04,317,*7A
$GPRMC,230400.600,A,xxxx.xxxx,N,yyyy.yyyy,E,0.05,169.01,131109,,,A*60
$GPVTG,169.01,T,,M,0.05,N,0.08,K,A*3F

各パラメーターの意味は、このリンクを参照ください。概略を示すと;

  • GPGGAメッセージから、衛星の補足状態、経度・緯度や補足している衛星の数が分かります
  • GPGSVメッセージから、個々の衛星の位置(仰角・方位)、受信信号レベルが分かります
  • 経度・緯度情報は複数のメッセージに挿入されており、GPGLL, GPRMCメッセージでも取得できます

NMEA-0183メッセージの解析

経度・緯度を得るためには、GPGGAの「GPSのクオリティ(Position Fix Indicator)」が1 or 2になった後で、経度・緯度情報を取り出す必要があります。NMEA-0183メッセージ(以下NMEAメッセージ)は、カンマ区切り形式の単純な構造であるため、解析はそれほど難しくないと思うのですが、チェックサムの計算とか色々考慮すべき点もあります。また、経度・緯度情報だけ抜き取るのなら簡単ですが、汎用性を持たせて任意のデーターの取り出しまで求めるとプログラムは結構複雑そうです。

そこで、例によって人様の成果を活用させてもらうべく、フリーのnmeaパーサーを探しました。グーグル様で検索してみるとC言語で使用できるライブラリとしては、[dmh2000] – NMEAP, NMEA Libraryが見つかりました。NMEAPはGPGGA/GPRMCのみを解析しているのに対して、NMEA LibraryはすべてのNMEAメッセージを解析できます。GPGSVメッセージも解析して衛星からの信号強度も表示したいため、NMEA Libraryを使用することにします。

NMEA Libraryの使い方

パッケージに含まれるexampleをベースに以下のサンプルプログラムを書いてみました(ほとんどexampleそのままです、、)。サンプルプログラムはテキスト形式で保存したNMEAメッセージのログファイルを読み込んで、位置情報と信号強度を表示します。

#include "nmea/nmea.h"

#include <string.h>
#include <stdio.h>

#ifdef NMEA_WIN
#   include <io.h>
#endif

void trace(const char *str, int str_size)  // debug用のcallback
{
    printf("Trace: ");
    write(1, str, str_size);
}
void error(const char *str, int str_size)
{
    printf("Error: ");
    write(1, str, str_size);
}

int main(int argc, char *argv[])
{
    nmeaINFO info;             // 取得情報を保持するための構造体
    nmeaPARSER parser;         // パーサーの内部管理情報
    FILE *file;
    char buff[256];            // 受信データーのバッファ
    int size, it = 0, i;

    if (argc < 2)
    {
        printf("need log filename");
        return -1;
    }

    file = fopen(argv[1], "rb");

    if(!file)
    {
        printf("File open error");
        return -1;
    }

    nmea_property()->trace_func = &trace;  // trace callbackの登録
    nmea_property()->error_func = &error;

    nmea_zero_INFO(&info);      // nmeaINFO構造体の初期値設定
    nmea_parser_init(&parser);  // パーサーオブジェクトの初期化

    while(!feof(file))
    {
        size = (int)fread(&buff[0], 1, 128, file);   // NMEAメッセージの取得
        nmea_parse(&parser, buff, size, &info);  // パーサーの起動

        printf(                     // 経度・緯度情報の表示
            "%03d, Lat: %f, Lon: %f, Sig: %d, Fix: %d\n",
            it++, info.lat, info.lon, info.sig, info.fix );
        for (i = 0; i < NMEA_MAXSAT; i++)
        {                           // 受信信号強度の表示
            if (info.satinfo.sat[i].in_use)
                printf(
                    "  sat_id:%02d, sig:%02d\n", 
                    info.satinfo.sat[i].id , info.satinfo.sat[i].sig);
        }
    }

    fseek(file, 0, SEEK_SET);
    nmea_parser_destroy(&parser);  // パーサーオブジェクトの廃棄(メモリーの開放など)
    fclose(file);
    return 0;
}

10, 15行目は、デバック用のcallback関数で、43, 44行目で登録しています。ソースを眺めてみたのですが、どのタイミングで呼び出しているかは分からず。

51, 52行目で、128文字単位にファイルからテキストを読み出してパーサーに渡しています。すなわち、メッセージの切れ目(改行)を意識する必要がないということになります。実際にGPSモジュールをつないで動かした場合、割り込みで文字を受信して一定の文字数がたまったらパーサーを起動するようにすればよく、処理が簡略化できそうです。

解析した情報は、nmeaINFO構造体に格納されます。構造体の定義は以下となっています。

typedef struct _nmeaINFO
{
    int     smask;      /**< Mask specifying types of packages from which data have been obtained */

    nmeaTIME utc;       /**< UTC of position */

    int     sig;        /**< GPS quality indicator (0 = Invalid; 1 = Fix; 2 = Differential, 3 = Sensitive) */
    int     fix;        /**< Operating mode, used for navigation (1 = Fix not available; 2 = 2D; 3 = 3D) */

    double  PDOP;       /**< Position Dilution Of Precision */
    double  HDOP;       /**< Horizontal Dilution Of Precision */
    double  VDOP;       /**< Vertical Dilution Of Precision */

    double  lat;        /**< Latitude in NDEG - +/-[degree][min].[sec/60] */
    double  lon;        /**< Longitude in NDEG - +/-[degree][min].[sec/60] */
    double  elv;        /**< Antenna altitude above/below mean sea level (geoid) in meters */
    double  speed;      /**< Speed over the ground in kilometers/hour */
    double  direction;  /**< Track angle in degrees True */
    double  declination; /**< Magnetic variation degrees (Easterly var. subtracts from true course) */

    nmeaSATINFO satinfo; /**< Satellites information */

} nmeaINFO;


typedef struct _nmeaSATINFO
{
    int     inuse;      /**< Number of satellites in use (not those in view) */
    int     inview;     /**< Total number of satellites in view */
    nmeaSATELLITE sat[NMEA_MAXSAT]; /**< Satellites information */

} nmeaSATINFO;


typedef struct _nmeaSATELLITE
{
    int     id;         /**< Satellite PRN number */
    int     in_use;     /**< Used in position fix */
    int     elv;        /**< Elevation in degrees, 90 maximum */
    int     azimuth;    /**< Azimuth, degrees from true north, 000 to 359 */
    int     sig;        /**< Signal, 00-99 dB */

} nmeaSATELLITE; 

構造体が階層化されており、ちょっとややこしいですが;

  • 経度緯度は、info.lat, info.lonで取得できます(infoはnmeaINFOのインスタンス)
  • 信号強度は階層をたぐって、info.satinfo.sat[i].sigで取得できます

サンプルを動かした結果を以下に示します(経度・緯度はxxxx.xxxx, yyyy.yyyyで、、)。
パーサーには128文字単位でデーターを渡していますが、パーサー内部でメッセージ単位に処理を行う作りになっています。そのため、最初のGPGSAメッセージが読み込まれた時点で(最初のTrace表示)000番目の解析結果が表示され、続けてGPGSAとGPGSVが読み込まれた時点で001番目の解析結果が表示されています。

Trace: $GPGGA,112645.000,xxxx.xxxxx,N,yyyy.yyyy,E,2,05,1.7,465.4,M,39.4,M,3.8,0000*7C
000, Lat: xxxx.xxxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 1
Trace: $GPGSA,A,3,12,24,21,09,18,,,,,,,,2.0,1.7,1.0*31
Trace: $GPGSV,3,1,12,09,80,345,22,27,71,022,17,32,68,311,,18,62,305,16*7E
001, Lat: xxxx.xxxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 3
Trace: $GPGSV,3,2,12,15,42,071,,12,29,161,39,21,28,241,31,26,26,292,*7E
Trace: $GPGSV,3,3,12,22,26,314,17,24,15,198,37,30,09,186,21,05,06,142,*7A
002, Lat: xxxx.xxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 3
Trace: $GPRMC,112645.000,A,xxxx.xxxx,N,yyyy.yyyy,E,0.00,,170110,,,D*7B
Trace: $GPVTG,,T,,M,0.00,N,0.0,K,D*16
Trace: $GPGGA,112646.000,xxxx.xxxx,N,yyyy.yyyy,E,2,05,1.7,465.4,M,39.4,M,4.8,0000*78
003, Lat: xxxx.xxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 3
Trace: $GPGSA,A,3,12,24,21,09,18,,,,,,,,2.0,1.7,1.0*31
Trace: $GPGSV,3,1,12,09,80,347,22,27,70,022,16,32,69,311,,18,62,305,16*7D
004, Lat: xxxx.xxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 3
  sat_id:09, sig:22
  sat_id:18, sig:16
  sat_id:12, sig:39
  sat_id:21, sig:31
  sat_id:24, sig:37
Trace: $GPGSV,3,2,12,15,42,072,,12,29,160,39,21,28,240,31,26,26,293,*7C
Trace: $GPGSV,3,3,12,22,26,314,17,24,14,197,37,30,09,186,21,05,06,142,*74
005, Lat: xxxxx.xxxxx, Lon: yyyyy.yyyyy, Sig: 2, Fix: 3
  sat_id:09, sig:22
  sat_id:18, sig:16
  sat_id:12, sig:39
  sat_id:21, sig:31
  sat_id:24, sig:37

余談

パーサーが動くようになったので、Primer2につないでCircleOS配下でNMEA Libraryを動かそうと作業を始めたのですが、ここで問題発生。Primer2のVBATとGを、GPSモジュールのGとVCCに接続していまい(電源をショートさせてしまい)、GPSモジュールがあえなく昇天してしまいました。SparkFunから直接買ったモジュールで、送料込みで$90以上かかったのに涙です・・・ 電源周りの接続ミスで部品を壊したのは2度目。まったく注意散漫です。

ここまできたので、同じモジュールを再購入して出直しを図ろうかと。
しかし、Primer2を触り始めてから、本体を壊して2個目を買ったりGPSモジュールを2個壊したりとどうも縁起が悪いので、mbedに寄り道してみようかしら、、と思っています。

STM32 Primer2 V1.2

初期のPrimer2は、電源On時に発生する電源電圧の瞬間的な変動でボルテージレギュレーターが(U9, U17)が破損する問題を抱えており私も憂き目に会いました。レギュレーターを交換してだましまだし使っていたのですが、対策を施したV1.2を買ってしまいました(秋月さんで購入)。

基板にV1.2のシルク印刷が入っています。

STM32_Primer2_V12

変更点

stm32circle.comに記載された対策が入っています。

対策1:レギュレーターU9(2.84V主電源用)の入力側に入っているC23を積層セラコンに置換し、その上に過電圧保護用のトランシル(SM2T3V3A)を乗っけています。

V12_C23

対策2:レギュレーターU17(3.18Vバックライト用)の入力側に入っているC56を積層セラコンに置換しています。

V12_C56

対策は十分か

stm32circle.comには、この対策で十分かはまだ判断できない(but it is too early to know if this will offer complete protection for the regulators.)と、弱気なことが書いてあったりします(外部電源の抜き差しをするような箇所は、本来もっと耐圧の高い部品を使うべきなんでしょうか)。

安全のためにUSBケーブルの挿抜は電源を落とした状態でやらねば。

STM32 ADCの設定

前回記事「MOS FETを使用したハイサイドスイッチ」で、GPSモジュールの電源制御をOn-Off制御信号(サスペンド・レジューム信号)にて行えないかと記載しましたが、実験したところ結果はOKでした。使用したGPSモジュールのOn-Off制御信号は1.2Vロジックのため電圧変換の抵抗が必要ですが(1.8V→1.2V変換の分圧抵抗がモジュール内に入っているため、2.8V→1.2Vとなるように直列の抵抗を1本追加)、MOSFETを使う回路に比べて部品点数が少ないため、On-Off制御信号を使うことにしました。

電源制御は、On-Off pinにパルスを入力することによってサスペンド・レジュームがトグル動作しますが、GPSモジュールへサスペンド要求を出した後、本当に電源が落ちているかを確認したいと思いました。サスペンド動作は、モジュール内部で生成しているVCC(1.8V)が0Vになることで確認ができます。そのため、GPSモジュールのVCCをSTM32のADCでモニターしてサスペンド状態への移行を確認してからGPSアプリを終了することにします。

上記の理由から、STM32のADCを始めて使ってみました。STM32のペリフェラルはやたらと機能が多いと先人の方がブログに書いていますが、確かに「これでもか!」と言わんばかりの機能が入っています。NXP LPC17xxのマニュアルを見たのですが、STM32のADCはLPC17xxより機能が多く複雑です。

これだけの機能を使いこなせれば最強なのでしょうが、先ずは、必要最小限のAD変換機能を動かすための設定方法を記載します。

STM32 ADCの動作モード

動作モードを表にすると以下となります。

STM32ADC_Functions

ちなみに、今回使用した機能は紫の網掛け部分で、以下の内容となります

  • 入力として1chを使用
  • Single shot変換(変換毎にソフト指定のトリガーを出す)
  • サンプル時間は55.5 cycleを指定

AD変換のコード

先ずは、ADC初期設定のコードです。入力ピンに使用するGPIOもあわせて設定します。

/*******************************************************************************
* Function Name  : InitADC2
* Description    : Initialize ADC2 as single shot mode
*                  Set PC.4 (Primer2 Extention Connector Pin 11) as analog input
*
*******************************************************************************/
void InitADC2( void )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef  ADC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);

    GPIO_StructInit (&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    /* ADC1 Configuration ------------------------------------------------------*/
    ADC_InitStructure.ADC_Mode                = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode        = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode  = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv    = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign           = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel        = 1; 
    ADC_Init( ADC2, &ADC_InitStructure );

    /* ADC2 regular channel14 configuration to sample time = 55.5 cycles */ 
    ADC_RegularChannelConfig( ADC2, ADC_Channel_14, 1, ADC_SampleTime_55Cycles5);

    /* Enable ADC2  */
    ADC_Cmd(ADC2, ENABLE);

    /* Enable ADC2 reset calibaration register */   
    ADC_ResetCalibration(ADC2);

    /* Check the end of ADC1 reset calibration register */
    while(ADC_GetResetCalibrationStatus(ADC2));

    /* Start ADC2 calibaration */
    ADC_StartCalibration(ADC2);
    /* Check the end of ADC2 calibration */
    while(ADC_GetCalibrationStatus(ADC2)); 
}

上記以外にADCクロックの分周比を設定する必要がありますが、CircleOSが初期設定しているため割愛。生で使う場合は設定が必要です。

データー取得のコードは以下です。

/*******************************************************************************
* Function Name  : getCxAdc1Value
* Description    : Get ADC converted value of ADC2 ch-14 (CX_ADC1)
*                  Use single conversion mode
* Input          : NONE
* Return         : Converted value
*******************************************************************************/
u16 getCxAdc1Value(void)
{
    // Start ADC2 Software Conversion
    ADC_SoftwareStartConvCmd( ADC2, ENABLE );
    // Wait until conversion completion
    while(ADC_GetFlagStatus(ADC2, ADC_FLAG_EOC) == RESET);
    // Get the conversion value
    return ADC_GetConversionValue(ADC2);
}

思ったこと

ST Libraryの命名規則がしっかりしており(CMSISというやつですね)、名前から設定内容が分かるためコードの見通しはそこそこよいのですが初期化に20行程度必要って面倒ですね、、

初めてWindows APIの本を読んだときに、窓を1つ開くだけで40行近くのコードが必要なのにびっくりして、DOSの方がよっぽどプログラムは楽だと思ったのに似ています。最近のWindowsプログラミングは、C#/VBなんかのRAD(Rapid Application Development)ツールを使えば、窓を開くだけならプログラムコードはまったく不要で(GUIデザイナーで画面イメージを作るだけ)、複雑怪奇なWindows APIを意識することなくコーディングができます(その代わりに膨大なクラスライブラリの機能を把握するのが一苦労ですが)。

上記から、Arduinoやmbedのように、C++のオブジェクトを使ってデバイス初期設定などの下回りを隠蔽するコンセプトは捨てたものではないと思いました。ちなみに、mbedならADCの初期化と値の取得(上記のAD変換コード相当)は、以下のようにたったの2行ですみます。

AnalogIn ain(p20);         // コンストラクターの呼び出し(ADCの初期化)
float vol = ain.read();     // %値(0.0~1.0)として読み取り。ain.read_u16()だと12bitの生データーを取得できる

STM32の複雑なモード設定をオブジェクト化すると、メンバー変数(モードを保持するプロパティー値)が増えそうではありますが、CMSISよりコードの見通しがよくなると思います。ただ、オブジェクト化するということは、ハードの設定や動作を一定の枠に押し込むことになり、ハードの能力を使い切る必要に迫られれる組み込みプログラミングには適さない部分もあると思います。あと、オブジェクト指向だとコードサイズが大きくなってしまうでしょうか。

結局、Cでゴリゴリ書くのが今は王道なのかしら。

MOS FETを使用したハイサイドスイッチ

GPSモジュールをSTM32 Primer2に組み込んだ際に、CircleOSからGPSアプリを起動した時にGPSモジュールの電源をONにし、アプリ終了時に電源OFFを行いたいと考えています。GPSプロジェクトは遅々として進んでいませんが、今回は電源ON/OFF回路の実験で分かったことを記載します。

MOS FETを使ったハイサイドスイッチ回路

当初、stm32circle.comのプロジェクトに登録されているLocus GPSの回路を拝借しようと考えました。以下のような回路となっており、Pch-MOS FETを使用してGPSモジュールのVCC側でON/OFF制御を行う、いわゆるハイサイドスイッチになります。Primer2のVCC(本体の電源ON/OFFと連動)は2.8Vとなっており、GPSを駆動するためには電圧が足りないため、FETにはVBAT(バッテリーの出力電圧)を印加します。バッテリーの出力は当然常時ONのため、この意味でもGPS電源のON/OFF制御が必須となります。

この回路では、FETのGateをVBATにプルアップしているため、MCUリセット時のGPIOピン初期状態(入力モード&Hi-Z)ではFETがOFFとなります。そのため、CircleOSが起動した時点(GPSアプリはまだ起動していない)では、GPS電源OFFを保障できます。

HighSideSW-PFET2

ただし、この回路には次の問題がありました。MCU の電源が入っていない状態(VCC = 0V)では、GPIOがHi-ZではなくGNDと導通した状態になるようで、FETが以下のようにON状態になりました。FETは手持ちの2SJ128 を使用し、VBATは3.3V電源で実験しています。

①R1 = 10KΩにおいてMCUのVCCを0Vにした場合
・Gate電圧: 1.46V
・Drain電圧: 1.81V → 半分ON状態

②R1 = 47KΩに変更すると以下となりました
・Gate電圧: 0.85V  → GPIO~GND間がつながった状態になる
・Drain電圧: 3.23V → 常時ON状態

これでは、Primer2を電源OFFにするとGPSモジュールが通電状態となってしまうためスイッチの意味がありません。上記の実験はMCUとしてArduino Nanoを使用していますが、STM32を接続した場合でも、MCUの電源OFF時に(Gate電圧が低下して)FETがON動作する同様の事象が発生します。

Pch-MOS FETの場合、Gate電圧が0でON状態となるロジック的には負論理動作ですが、問題を回避するためには、正論理のスイッチ(Gate電圧0ではOFFで、電圧HiでONとなる)が必要です。Nch-MOS FETを使用すると正論理の回路が組めて調子がよさそうですが、NMOSはGND側にスイッチ回路を挿入するローサイドスイッチになります。回路図は割愛しますが、GPSモジュールのVCCにはVBATを直接接続し、NMOS FETを使ってGNDピンとG間でON/OFFの制御を行うイメージです。

電球やモーターの制御ならローサイドスイッチでもよいのですが、電子回路がつまったGPSモジュールのVCC側に電圧をかけてGNDピン側をオープンにしておく使い方は気持ちが悪いため(悪影響があるかは分からず)、やはりハイサイドスイッチで行きたいと思うのです。

NPNトランジスターとの2段接続

解決策をググッて調べたところ、NPNトランジスターとの2段接続で正論理が組めそうです。以下の回路で実験したところ問題が解決しました。NPNトランジスタは手持ちの2SC1815です。

HighSideSW-NPN_PFET2

この回路ならNPNトランジスタのベースがプルダウンされているため、MCU電源OFF時もスイッチOFF状態を維持できます。Arduino Nanoを使って動作試験を行った際の写真を以下に示します。

PowerCTL_test

Arduinoでドライブする場合、R1を割愛しても動作は可能でした。ただ、R1はベース電流の制限抵抗となるため割愛しない方がよいと思われます。R1がない状態で、GPIOピンのHighに代わって電源の3.3Vを接続すると電源が停止しました。(B-E間に大電流が流れてシャットダウンしたか?)

R3の値は、トランジスタのCE間電流(スイッチ回路の消費電流になります)が小さくなるように大き目の値としました。FETのスイッチング特性を加味して値を決めたわけではありません。このあたりはアナログの世界になって奥が深いと思いますが、理解が追いつかない部分です、、、

トランジスタのベース電流(こちらも動作時の消費電力になる)を小さくするために、1段目の素子としてNch-MOS FETを使う実験も行ってみました。FETは2SK241を使用。こちらはGate電圧0でもFETが常時ONになってしまいNGでした。2SK241は高周波スイッチング用のため、DC領域の使用には適さないのでしょうか?

2011/10/29更新:
かつぽんさんから上記NGの原因についてコメントをいただきました。2SK241はそもそもスイッチング用でないため、ゲート電位0でもドレイン電流が流れてしまい、二段目のPch-MOS FETが常時ONになることが原因でした。

今後の予定

STM32 Primer2の拡張基板は面積の制約が大きいため、2段構成の回路(部品点数にして5点)を載せる場所が厳しそうです。実は前回の記事で使用したLS20031を使用したGPSレシーバーモジュールは、裏面のチップ抵抗を剥がしてしまいあえなく起動不可となってしまいました。どうも、部品の扱いが雑で破損が多いです(反省)。前回のブログ記事にも書きましたが、GPSモジュール上面にアンテナがあるパッチアンテナ型だとケース内にアンテナが隠れてしまう問題もあったため、アンテナをケース外に出せるチップアンテナ型のモジュールを新たに購入しました。選択したモジュールはSparkFunさんのGPS Micro-Miniです。写真の左が新たに購入したGPS Micro-Mini、右がLS20031です。

GPS_Mod

もともとGPS Micro-Miniも選択肢として考えていたのですが、値段が高いため躊躇。結局買う羽目に、、 GPS Micro-Miniなら幅が狭いため、ON/OFF回路の部品も載りそうです。ただ、Tr/FETはSOT等の小型部品が必要かも。LS20031が死んでしまったため、ちゃんと比較ができませんが、ケースなどの遮蔽物がない状態での感度はGPS Micro-MiniよりLS20031の方がよい感じがします。なので、LS20031も捨てたものではなかったのですが、、

GPSモジュールのON/OFF制御ですが、GPS Micro-Miniのデーターシートを見ると、ハイバネーションモードの制御ピンがあります(100nsのパルス入力でハイバネーションと復帰をトグルできる)。また、ジャンパーを切り替えることで、電源投入時はハイバネーション状態で起動するモードもあります。この機能を使えばハイバネーションによってON/OFF制御の代わりができそうです。そのため、次はハイバネーション制御のお試しを行う予定です。

2010/2/10更新
回路図の修正(FETのソース・ドレインが逆になっていたため修正)

GPSモジュールをテストする

STM32 Primer2への組み込み用として購入したGPSモジュールの試験を行いました。Primer2側の準備ができていないため、今回はPCと接続して動作確認を行いました。

使用したGPSモジュール

秋月電子さん、スイッチサイエンスさんでも色々なGPSモジュールを販売していますが、SparkFunさんから”32 Channel LS20031 GPS 5Hz Receiver”を購入しました。この製品を選んだポイントは以下です:

  • 動作電圧範囲が3.0~4.2Vとなっており、Li-ionバッテリー駆動に適している
  • 特殊コネクターを使用しておらず配線が行いやすい(モジュール裏面のパッドにケーブルを直接半田付けできる)
  • 消費電力が41mAと小さい部類に入る

簡易動作試験

先ずはGPSモジュールのシリアル端子をUSB-シリアル変換アダプターにつないで、PCで動作を確認しました。

GPS_test

PCでの動作確認には、SparkFunのサイトからダウンロードできる、Mini GPSを使用しました。屋外で動作させた際の画面キャプチャーを以下に示します(経度・緯度は自宅位置がばれるのでモザイクしてあります、、)。

MiniGPS_mod 

このソフト、なかなか画面がかっこよいです。補足したGPS衛星の位置関係をレーダーのように表示してくれます。上記の画面では、10機の衛星を補足していますが、内8機から有効なデーターが得られているということでしょうか。一度衛星を補足しPosition Fix状態になると、最後の状態を保持するようで(マイクロバッテリーでバックアップ)、Hot Startをかけても数秒でPosition Fixしてくれました。

室内ですと格段に感度が落ち、窓際でも1分程度かかってやっと衛星3個を補足してPosition Fix状態になるかどうかというレベルでした。

誤差は、Google Mapに測位した位置を表示させると5m前後という感じでで、なかなか立派な精度です(Data sheetでは誤差3m)。

Primer2への組み込み

LS2031 GPSモジュールはケースがアンテナの役割を果たすタイプのモジュールです。外付けアンテナが不要というのも購入時のポイントだったのですが、よく考えてみると、GPSモジュールをPrimer2の拡張エリアに組み込むと、GPSモジュールの位置はメイン基盤の裏側になります。そのため、メイン基盤が障害物になって電波が弱まりそうです、、早く気づけよこんな初歩的なこと!!

下の写真のようにPrimer2に仮組込みをして、MiniGPSにて受信感度を測ってみました。

GPSinPrimer2

外が小雨になったため、自宅マンションのベランダからの測位ですが、以下の結果となりました:

  • 写真の状態(モジュールが露出)から、Primer2の裏面カバーをつけても感度は変わらず → プラスチックカバー一枚なら電波をほとんど遮らない
  • Primer2をひっくり返して液晶画面が上面になるようにすると(GPSモジュールがメイン基盤の裏側に隠れる)、MiniGPSのCNR値が5~10程度落ちる。補足している衛星の数は減りませんでしたが、全体的に感度が低下します
  • 本体上向きの状態でPrimer2を起動しても感度はほとんど変わらず → MCU動作ノイズ等の影響はあまりない模様

上記より使えなくはなさそうですが、アンテナを外に出せるタイプのモジュールの方がよさそうです。。。
とりあえずこいつを使って、行けるところまで行きましょうか。

STM32 Primer2 レギュレーター死亡(その後)

長らくブログの更新をお休みしてしまいました。理由は、本業のトラブル対応で死にそうな日々だったのと、ネタ切れ状態の2つでした。。。

今回は、「STM32 Primer2 U17レギュレーター死亡」の後日談でつなぎをさせていただきます。何とか修理はできたのですが、拡張コネクタのピンを剥がしたりで満身創痍です。
ネタ切れは相変わらずでして、恥を忍んで今回のネタとさせていただきます、、、

レギュレーターの代替品選定

お亡くなりになったU17レギュレーターは、3.1V出力で、LCDバックライトとオーディオチップ(3.1Vと2.8Vの2電源)に電源を供給しています。アマチュアが手軽に利用できる(単品で部品を購入できる)お店では3.1V出力のLDOを発見できなかったため、3.3V出力のLinear Technology LT1129-3.3をマルツさんで購入しました。オーディオチップは3.3Vまで許容しているため耐圧的には問題ありません。

絶縁用のポリイミドテープ購入

故障したU17(L6928D)を引っぺがして、跡地にLT1129を搭載します。既存の配線パターンにLT1129の端子がショートしないようにポリイミドテープ(半田の熱に耐え、かつ絶縁性が高いテープ)を下地に張って、その上で空中配線を行いました。マルツさんで3Mのポリイミドテープを販売しているのですが、8000円近い代物でちょっと手が出ません。色々と探すとAmazonでお手ごろな製品が見つかりました。私が探した際は、中興化成工業さんの製品が一番安かったのですが、今ではもっと安い製品がありました。

PolyimideTape

Amazon恐るべしです。ポリイミドテープはオフィス用品として売っているようですが、そのうち、電子工作パーツもAmazonで買えるようになったりして(Arduinoはすでにスイッチサイエンスさんの出展品が買えます)。

レギュレーターの交換

ここからが難関でした。空中配線になるため、拡張コネクタの2番ピン(GND)にLT1129のグランド端子を半田付けして固定しようとしたのですが、うまく半田付けができず、ピンの間隔を広げて半田付けしやすいようにしようとした際に、1,3番ピンをパターンから剥がしてしまいました。今のところ使う予定がない点が不幸中の幸い、、

無残な写真を以下に示します(バッテリーは大容量品に交換しています)。ねむいさんのぶろぐの修理方法を真似たのですが、腕の差が歴然です。

LODrep

部品交換中はJP3のショートピンを抜いてバッテリーを切り離していたのですが、半田コテが拡張コネクタの電源ピンに触れた際に火花がパチッと。いやな予感が。。。
なんとかLT1129を固定して電源を入れると、起動しません。MCUなどに電源を供給しているメインのレギュレーター(U9)の出力電圧を測ると0Vです。もうひとつのレギュレーターもしっかりと壊してしまいました。

U9は2.8V出力になっており、やっぱりぴったりはまるLDOは見つかりません。LT1129の出力は700mAあり、Primer2全体を給電する余力があるため、U9もひっぺがして、LT11291個で全体を給電することにしました。MEMSなど主要な部品のデーターシートを見ると、3.3Vでの動作は問題なさそうです。

上記の措置で何とか復活したのですがひとつ問題がありました。バッテリーの電圧監視にMCUのADCを使っているのですが、リファレンス電圧が2.8Vから3.3Vに上がってしまうため測定値が狂ってしまいます。対策として、CircleOSのcircle.hで定義しているVDD_VOLTAGE_MVを3300に変更することで問題を回避しました。

おわりに

なんとか動いていますが、かなりのつぎはぎ状態です。そもそもPrimer2を買った目的はGPSモジュールの実験ですが、まだ果たせていません(GPSモジュールは、実は買ってあります)。もう1個新品を買おうかと思っているのですが、現状のレギュレーターだとまた壊しそうです。stm32circle.comのkown issueを見ると、次ロットからレギュレーターの入力保護をつける予定とあるため、次ロットが出回るまで待とうかと思っています。秋月さんやストロベリーリナックスさんで一時期売り切れ状態になっていたため、ひょっとして新ロットになっていないかしら。

STM32 Primer2 U17レギュレーター死亡

あちこちでこの問題が起きていますが、ついにやってしまいました、私も、、、

FreeRTOSのPrimer2用デモ版を入れてLEDチカチカを確認した後、CircleOSに戻してリセットしてもLCDが真っ暗のままで、CircleOS起動時のサウンドも出ないのです。症状としては以下の通りで、Known IssueのU9 & U17 Voltage Regulator Failureそのものです。

  • FreeRTOS demoやCirlceOSを使用しないLEDチカチカプログラムは動作するため、U9の出力は生きています。拡張コネクタのPin-1(VCC2V8)にも2.8Vが出ていますし、U9の発熱もありません
  • 問題のU17は、出力電圧が0Vです(C54の両端で測定)
  • 中央ボタンを押し続けるとVCC2V8が0Vに落ちるため、CircleOSのshutdown機能は動いているようです。ただ、一定時間放置した際の自動shutdownは働いてくれず、CircleOSも完全に起動しているかが怪しい感じ
  • U9のグランド強化対策は実施済みのロットでした

U17が死んだのはほぼ間違いがないのですが、CircleOSが立ち上がっているかを確認してみようと(ひょっとしてFlashROMの書き込みが不完全ということはないか)、自殺行為覚悟でU17の出力に手持ちの3.3V電源をつないでみました。すると、LCDが点灯して、例のメニュー画面がしっかりと表示されています。

出力がショートしている可能性もあるため(何か焦げ臭い臭いもしましたし)、すぐに3.3V電源を切り離すとLCDバックライトがチカチカしながら・かつ薄暗い状態で点灯しています。U17の出力が復活(!)したのですが、内部的にはダメージを受けたようで、U17レギュレーターがかなり発熱しています。

こうなると、ねむさいんのブログにあるように、U17の置換を行わないとダメそうです。あと、出力側のコンデンサC56, C23も積層セラミックコンデンサに交換ですね、、

U17はなんとか引っぺがしました。VCC3V1に3.3Vをつないでやると、LCD表示やサウンド再生もOKです。バックライトとサウンドICの電源は3.3Vでもよさそうなので、3.3VのLDOをうまく乗っけられるかに挑戦です。

STM32 Primer2のSysTickタイマー周期設定

SysTickタイマーはSTM32 MCUが持つ機能で、OSがタスク切り替えを行う契機として使用します。PrimerのCircleOSでは、SysTickタイマー周期毎にタスクを切り替えるのではなく、SysTickHandlerに登録された以下の関数群を毎回実行します。

  • BUTTON_Handler: ボタンの状態取得
  • POWER_Handler: バッテリー状態の取得と表示
  • RTC_DisplayTime: 時刻の取得と表示
  • MENU_Handler: メニュー表示の更新とアプリケーションの起動。
    加えて、SysTick周期100回毎にユーザーアプリケーションの本体部分(Application_Handler)を実行する
  • その他の周期処理

CircleOSにてSysTickタイマーをどのように設定しているかを調べてみました。

CircleOSにおけるSysTickタイマー周期

以前の記事「STM32 Primer2」の末尾に示す通り、タイマー周期はMCUのクロック設定に連動して変化します。例えば24MHz動作の場合、SysTick周期 = 1ms、Application_Handler呼び出し周期 = 100msとなります。

SysTick制御レジスターの動作

SysTick制御レジスタのフィールド定義と動作を以下に示します。「Cortex-M3 テクニカルリファレンスマニュアル(Rev.G)」をベースに、STM32固有の部分(太字・斜体)を追記してみました。

1)SysTick制御レジスタ

Bit フィールド名称 機能
2 CLKCOURCE
(SysTickタイマーのクロックソース)
0: 外部参照クロックを使用
STM32ではAHBクロック(=HCLK)の1/8を供給する(STM32 Ref Manual 4.2章より)
1: コアクロックを使用
STM32ではAHBクロック(HCLK)を使用する
1 TICKINT 1: 0 までカウントダウンすると、SysTick ハンドラを保留する → 割り込みを生成すると理解
0: 0 までカウントダウンしても、SysTick ハンドラを保留しない → 割り込み生成しない
0 ENABLE 1: カウンタはマルチショット方式で動作
0: カウンター無効

2)SysTick リロード値レジスタ

Bit フィールド名称 機能
0-23 RELOAD カウンタが0になったときに、SysTick現在値レジスタにロードする値 → タイマー周期を指定する

SysTick制御レジスターの設定

CircleOSの初期化時に、SysTick_Configuration()関数にて設定を行っています。コードは以下の通りです(CircleOS V3.80のscheduler.cより抜粋)。5行目のSysTick_CLKSourceConfig()はSTM32F10x標準ライブラリ(以下STライブラリ)の関数です。パラメーター名よりCLKCOURCE = 0を設定することが分かります。

14行目のSysTick_Config(SystemFrequency / 500)では、SysTickリロード値レジスタを、SystemFrequency(12MHz) / 500 = 24000に設定しています。

void SysTick_Configuration( void )
    {
    /* Configure SysTick to generate an interrupt @1.5MHz (with HCLK = 36MHz) */
    /* Select AHB clock(HCLK) as SysTick clock source */
    SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK_Div8 );
    //SysTick_SetReload( 3000 );

    /* Enable SysTick Counter */
    //SysTick_CounterCmd( SysTick_CTRL_ENABLE );

    /* Enable SysTick interrupt */
    //SysTick_ITConfig( ENABLE );
    
    if (SysTick_Config(SystemFrequency / 500)) /* SystemFrequency is
                defined in system_stm32f10x.h and equal to HCLK frequency */
        {
        /* Capture error */
        while (1);
        } 
    }

SysTick_Config()は、CMSISファイルcore_cm3.hで定義されたインライン関数でコードは以下です。

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SYSTICK_MAXCOUNT)  return (1);   /* Reload value impossible */

  SysTick->LOAD  =  (ticks & SYSTICK_MAXCOUNT) - 1;   /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for System Interrupts */
  SysTick->VAL   =  (0x00);                    /* Load the SysTick Counter Value */
  SysTick->CTRL = (1 << SYSTICK_CLKSOURCE) | 
                  (1<<SYSTICK_ENABLE) | 
                  (1<<SYSTICK_TICKINT);        /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                  /* Function successful */
}

SysTick_Config()の中で、リロード値レジスタの設定(5行目)に加えて、CLKSOURCEを1に再設定しています(8行目)。また割り込みの有効化をここで行っています(9~10行目)。すなわち、最終的なSysTickタイマーの設定条件は以下となります。

  • SysTickカウンターをカウントダウンするクロックはMCUのコアクッロク(18~72MHz)  (CLKCOURCE = 1)
  • SysTickタイマー割り込み有効
  • カウンターのリロード値は24000

上記設定により、冒頭に示したとおり、クロック周波数24MHz時のSysTickタイマー周期は1msとなります。
   SysTickタイマー周期 = 24000000Hz / 24000 = 1000Hz

SysTick割り込み発生回数をカウントするプログラムを作って動かしてみると、確かにクロック24MHzでは、1秒間に1000回割り込みが発生することが分かります。

PS: SyntaxHighlighter for Windows Live Writerを使ってソースコードのフォーマットを行ってみました。使い方が分からず結構苦戦。このページが昨晩から何回も消えたり・見えたりしていた筈です。

STM32 Primer2のバッテリー交換

前回バッテリーが不調と書きましたが、原因はどうも以下のようです。

Primer2のKnown Issue(既知問題)に「USBケーブルの抜き差しを頻繁に行うとバッテリーが死んでしまう」が上がっています。USBケーブルを挿入するとその都度充電シーケンスが開始されるため、これを短時間に繰り返すとバッテリーが死んでしまうというものです。

実はデバック中に過充電を気にして、充電完了状態になる毎にUSBケーブルを抜き差したため、もろに上記の状態を作ってしまいました。ForumのFAQ(IMPORTANT: How to preserve the STM32 Primer batteries)に「Primerの充電回路は単純であり、バッテリーの保護のために長時間USBケーブルを接続したままにしてはいけない」とあったため、デバック中にUSBケーブルの抜き差しを行っていました。

実は、上記の注意書きは、NiMHバッテリーを使ったPrimer1の話なんですね。単にPrimerとしか書いてないため、Primer2に対する注意書きだと勘違いしていました。よく考えてみると、過充電で発火の危険を伴うLi-Ionバッテリーを使った製品に「簡易充電回路」が認められるわけがなく、Primer2も当然制御ICを使った充電回路を持っています。

バッテリーの交換

ということで、バッテリーを交換しました。バッテリーは860mAhのリチウムイオンポリマー電池をスイッチサイエンスさんから購入。製品添付のバッテリーは容量が400mAhと小さめで、もともと交換を考えていたため、まあよいかというところです。

購入したバッテリーはサイズ的にはぴったりで、元のバッテリーを外して基盤に両面テープで固定しました。

Primer2_Battery_Change

まだ、2回程充電した程度ですが、今のところ問題なく動いています。充電電流は60mA程度で充電回路に負担がかかっているという感じはありません。バッテリー稼働時間はLCDをONにしたままでも余裕で6時間はいけそうでいい感じです。当然充電時間も延びましたが。

2009/10/25追記:
STM32 Primer2の電源回路とバッテリー駆動時間」で行った測定方法を使用して、12時間動作させてもバッテリーは3.77Vでした。まだ数時間は動きそうですので、十分な駆動時間となりました。

Li-Ionバッテリーの放電下限電圧

過去のブログ記事で、バッテリーのデーターシート上では放電下限電圧が2.75Vなのになぜ3.5Vでshutdownしてしまうのか、3Vあたりまでバッテリーを使えないのかと書きました。携帯電話のバッテリーが1セル・800mAh程度で今回購入したバッテリーと同程度のスペックですので、携帯電話の動作がどうなっているかを調べてみました。結果は以下のとおりです。

バッテリーアラームが出て操作を受け付けなくなるまで放電した状態で電池を取り出し、電圧を測ると3.5Vでした。Primer2のshutdown電圧と同じです。かみさんの携帯で試しても同様でした(どちらも同じキャリアですが、端末メーカーは異なります)。ということは、Li-Ion電池は実質的に3.5V/cellあたりまでしか使ってはいけないということでしょうか。理由としては、以下が考えられそうです。

このWebページにあるLi-Ion電池の放電特性を見ると、3.5Vあたりまではだらだらと電圧が低下しますが、3.5Vから急激に電圧が低下することがわかります。放電電流が少ないほど3.5V境界での落ち込みが激しくなっています。Primer2も放電電流が少ない使い方になるため、電池を3.0Vあたりまで使うとshutdownタイミングの判断が難しく、下手をすると危険領域の2.75Vまであっという間に放電してしまうリスクがありそうです。加えて、3.5Vまで放電した時点で容量の90%以上を使っており、これ以上放電させても稼働時間延長への寄与は少ないと思われます。

この点を考えて3.5Vで打ち止め(shotdown)しているのだと理解した次第です。

STM32 Primer2のバッテリー不調

Primer2のバッテリー駆動時間が異常に短くなってしまいました。ひょっとしてバッテリーがお亡くなりになったかかも。いけない使い方はしていないつもりなのですが、、

充電回路の動作を確認する意味も込めて、バッテリー充放電時の電圧・電流を計ってみました。

リチウムイオンバッテリーの充電(予備知識)

先ずはリチウムイオンバッテリーの基礎知識をこのサイトでお勉強です。リチウムイオンバッテリーの充電は、以下のフェーズに分かれます。

  1. プリチャージ(バッテリー電圧が低い場合、電流を少し流して電圧が回復するかを確認 →バッテリーの正常性確認)
  2. 定電流充電(定電流で急速充電を行う →90%程度まで充電)
  3. 定電圧充電(セル電圧が4.2Vに到達すると、定電圧で充電電流が一定の値以下になるまで充電)

充電時の電圧変化

電圧変化をテスターの写真を並べて示します。実はこのテスター20年以上前の代物なのですが(よく動いている)、測定値はCircleOSのUTIL_GetBat()関数で得られるバッテリー電圧と合っていますので概ね正確だと思います。今度新しいテスターを買わねば。UTIL_GetBat()関数は、バッテリー電圧をSTM32 MCUのADCで測定します。

1)電圧低下による自動shutdown直後の電圧

Shudonw直前は3.4V台まで電圧が落ちるが、電源が切れると電圧が少し復活する。
01_v_aftershutdown

2) 充電中の電圧

4.19V(=4.2V)となっています。「STM32 Primer2の電源回路とバッテリー駆動時間」で書きましたが、STM32 Primer2はバッテリー電圧3.5Vでshutdownしてしまうためか、定電流充電フェーズがなく、いきなり定電圧充電が始まっています。
02_v_charging

3) 充電が完了すると、電圧が4.1Vになります

03_v_chargecomp

4) バッテリー駆動状態にする

いきなり電圧が3.7V以下になります。電池の内部抵抗の関係?すぐに本体の電源をOFFすると4V台の電圧が見えますが、電源ONのままにするとあっという間に3.5Vに低下していきます。電圧落ちが早すぎるような。
04_v_poweron_aftercharge_2

電流の測定

1) 72MHz動作時の電流

36MHz動作では、40.3mAでした。36MHzで、バックライト輝度を最低まで落とすと34.4mAとなりました。ATMega 328 16MhzのArduinoで25mA程度ですので、LCDがあることを加味するとまぁまぁの低消費電力性能か。
A_72mhz_blmid

2)充電電流

4.2Vの定電圧充電時の電流(放電時に対して電流の流れが逆になるため、マイナス表示になっています)。だんだん、電流が減っていきます。
A_chargestart

3)充電完了時の電流

僅かに放電しています。
A_chargeend

まとめ

バッテリーが不調のため、正しい充放電特性になっていない可能性がありますが、以下のことが言えます

1) STM32 Primer2では放電終止電圧が3.5Vと高めのため、充電はいきなり定電圧充電から始まる。ひょっとしてバッテリーを深く放電させると、定電流充電が走り、ボルテージレギュレーター充電回路への負荷増になるため定電流充電が動かないようにしている?充電回路の抵抗値(Rprog)で充電電流を制御でき、STM32 Primer2では68KΩになっているため、充電制御IC(ST社L6924D)のデーターシートの式を使って計算すると171mAになります。本体を動かしながら充電すると200mA以上の電流供給が必要になりますが、この電流が取れない?こちらはUSBバスパワーからの直送のため容量的には足りる筈です。

2) 定電圧状態の充電電流は徐々に減少して、10mAあたりで充電が終了しました。充電終了電流も充電回路の抵抗で制御ができ、STM32 Primer2では13mAとなります。従って、充電終了の制御もちゃんと行われています

この先どうするか

バッテリーの電圧があっという間に下がってしまうのは、現象としてメモリー効果が起きているみたいですが、リチウムイオン電池ですからそれはないはずです。やっぱり電池が死んだのでしょうか。

この際なので、容量の大きいバッテリーに交換を行います。スイッチサイエンスさんに860mAhのリチウムイオンポリマーバッテリーを注文しました。STM32 Primer2のForumを見ると、バッテリー容量を上げても充電回路は対応できる(充電時間が延びるだけ)とのスレッドがあったので、バッテリー交換で問題が解消するかを確認予定です。

2017年10月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
無料ブログはココログ