STM32基板のRTCを動かす
それにしてもデバッグは難しい。わかってしまえば、何でこんなことに今まで気づかなかったのだろうというお馬鹿な間違いなのだが、気がつくまでの道のりは決して短くない。まあこの謎解きが面白くて電子工作をやっているようなものだが、今度のSTM32のRTC(リアルタイマークロック)プログラムも結構手こずった。「デバッグは外へ、外へ」などと偉そうなことを常日頃言っているくせに、頭に血が昇るとつい我を忘れてしまう。
軽い気持ちで手をつけたがまるで動かない(6/2/09)
このところ1年前の雑誌の付録基板、STM32F103(Cortex-M3) の開発に熱中し、先週、高速化したUSB仮想UARTのモニタープログラムを完成させて意気が上がっている。ウェブでもこの石を使ったCPUボードを時々見かけるようになった。価格もそう高くない(2インチ160x128のカラー液晶付きの評価ボードが¥9000で手に入る)。クロック72Mhzの32ビットプロセッサーだ。ARMコアの中では普及版だそうだが、アマチュアが遊ぶには十分すぎるスペックである。
RTCはUARTモニターの開発のときから次の目標に決めてある。せっかくバッテリーバックアップ回路を追加したのだ。電池と32.767khzのクリスタルの顔を立ててやらねばならない。RTCはファイルシステムには欲しい機能だし(書き込みの記録)、これが動くと俄然マイコンがプロっぽくなる。幸いSTマイクロの提供するライブラリには、RTCのライブラリを使ったデモソースが収録されている。これを使えば簡単に出来るはずだ。
早速Eclipseの新規プロジェクトを作り、そこへソースコード一式を放り込む。このソースはIARなどの開発環境用なので、仮想UARTモニター同様、GNUで必要な、Makefileや、リンカースクリプト、スタートアップルーチンを入れて調整する。Makfileを換えるだけでうまく行くはずだ。必要なライブラリを取捨選択し、ビルドする。殆ど問題なくバイナリーが出来た。
わくわくしながら、フラッシュローダーにかける。おやあ、赤ランプがついて送れない。ええー、hexファイルがないだと? ほんとだ。ファイルはあるけれど中身は0バイト。これは一体どうしたことだ。elfファイルは60KB以上あるのに。コンパイラーもリンカーもエラーは出ていない。こうなると何が悪いのか見当がつかない。
マップファイルは出来ていたので、良くわからないままこれを調べる。あれ、関数のエントリーがみな同じ値だ。これはおかしい。でも、エラーは起きていない。念のため、必要なライブラリの数をMakefileで確かめる。一つ少ない。あれえ、stm32f10x_vecter.cが抜けている。デモソースのreadmeには、これを入れろとは書いていない。
何と言うことだ。こいつがないからに違いない。とにかくこれを含めてビルド。おお、ちゃんとしたhexファイルが出来た。readmeを信じてえらい目にあった(このあと各開発環境のプロジェクトフォルダーにはこのモジュールが入っているのを発見した。GNUのときは注意しないといけない)。それにしても、ビルドやリンクのときのNO ERRORは安心できないことを学ぶ。
お膝元のメーカーのデモソースである。バイナリーが出来たので簡単に動くかと思ったが、これが全く動かない。おかしい原因はおおよそ見当がついている。UARTへの出力関数printfが恐らく暴走の原因だ。これを、これまで使っていたsprintfに置き換え、受信関数も自前のものにして再度、実行。
おお、やっとメッセージが出た。しかし、RTC not configured...のメッセージを出したまま、それっきりになる。ロジックを追うと、RTCのセットアップが終わるのをループで待っていて、ここがreadyになっていないようだ。やれやれ、ハードか。念のため電圧を測る。3.24V。この程度なら問題ないはずだ。オシロで周波数を確認する。立派に32.767khzが出ている。大丈夫だとは思うが、要因を減らしてみよう。
バッテリーバックアップをやめてVccにつなぎなおす。うわあ、RTC configuredのメッセージが出て先へ進んだ。何だ、何だ。電池が原因か。何か部品が足らないのか。
ハードの問題は別として、これでうまく行ったと思ったら、そうは問屋が卸さなかった。時刻の設定をする受信関数が、がんとしてデータを読まない。受信関数は自前だ。自信がないので、プログラムの頭でテストステートメントを挿入してみた。いや、ちゃんと動く。しかし、時刻設定の入力では、受信データのフラグがあがらない。
それにしても、UARTぐらいの機能で、このソースの複雑さは何だ。読みにくいことおびただしい、と悪態をつきながらデバッグする。また最適化が原因かと思ってループに、NOP_Processなどを入れるが変わらない。UARTなら、このあいだのGNUサンプルソースにもあった。こちらは至極簡単なソースだ。こいつを持ってきて動かすが、これも駄目。万策がつきる。
原因はUARTではなかった。あっけない幕切れ(6/5/09)
UART受信が出来ない。本質と関係のないところでつまずいている。RTC不調も電池でない可能性が強い。電源を入れたままリセットをすると、RTCの初期化が途中で止まる。
UARTの方は、その後ステータスレジスターを問題のところで出力させ不正なビットが立っていないことを確認した。どうもUARTがハングの原因ではなさそうだ。LEDとUART出力を処理の始めから、少しづつ挿入し、ハングアップするステートメントを探していった。
その結果、RTCの割込みをenableするところでハングすることがわかる。RTC割込みがおかしい。試しに割込みルーチンにLEDを点灯するステートメントを入れるが、点かない。そうだこれに違いない。
デモソースのバグは考えられないので、これは明らかに、Dfuのためにずらしたプログラムのエントリーアドレスが、ベクターテーブルに反映されていないことを物語る。しかし、ソースの何処を探しても、テーブルのアドレスを設定するところはない。ベクターテーブルは、リンカーの最初のプログラムのスタートアドレスから相対的に展開しているからだ(これが大きな勘違い)。そうすると、リンカースクリプトか、スタートアップルーチンか。しかしここは良く分からないところで手が出せない。
思い余って、最近お世話になっている「ねむい」さんが、RTCを動かしたと言うので、質問しようかとページを開いたら、ソースを公開されている。質問する前に調べてみようとありがたくソースを頂く。(会社ではダウンロードできなかった)
おお、このソースは情報の宝庫だ。ユーザープログラムで割込みを止めるステートメントもある。で、ベクターテーブルを設定するところは、どこだ。見つかった。あ、あ、あ、なーんだ。明示的に設定するのだ。
void NVIC_Configuration(void)
{ NVIC_InitTypeDef NVIC_InitStructure;
/* Set the Vector Table base address at 0x08003000 for Dfu */
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x3000);
早速、このステートメントを追加し、コンパイルしなおす。苦もなく動き始めた。全く問題ない。1秒ごとにLEDが点滅し、UARTに時刻が表示される。RTCの出来上がりである。これで、この週始めから悩んでいた問題がすべて解決した。いやあ、知らないと言うことは恐ろしい。NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); のステートメントでもあれば気がついていたかも知れないが、それにしても「ねむい」さんに感謝である。
しかし、何故、割込みが原因だということに気がつかなかったのだろう。UARTの不具合と頭から思い込み、こればかりを追求していた。RTC割り込みは1秒である。ちょうど時刻設定の入力と重なる頃だ。一瞬入力が出来たときもあり、少し冷静に考えれば、このとき不具合がUARTでないことにもっと早く気がつくべきだった。
電池が入っているときに動かないのは、時刻設定をしないで単に時刻表示に行くからで電池が原因でハングアップしていたわけではない。しかも、付録の雑誌の7月号には、このベクターのオフセットをしないとデモなどは動かないよ、とわざわざ解説がステートメント付きで載っていて、これを読んでいたはずだ。この前の仮想UARTのhw_config.cにもちゃんとベクターのシフトがコーディングされている。今になって考えるとヒントはそこらじゅうにころがっていたのだ。お恥ずかしい限りである。
| 固定リンク
「ARM」カテゴリの記事
- 心電計プロジェクト:スケールが出ると心電計らしくなる(2015.01.08)
- 心電計プロジェクト:TFT液晶に念願の心電波形が出た(2014.12.18)
- 心電計プロジェクト:STM32F103の心電波形表示で悪戦苦闘(2014.12.03)
- 心電計プロジェクト:CooCoxでARMの表示系ソフトを開発する(2014.10.16)
- 心電計プロジェクト:表示部のARM基板の開発環境を一新する(2014.09.19)
コメント
がた老です。
状況が良くわかりました。原因と書かれたもので、勘違いしていたようです。失礼しました。
objdumpですが、試しにGoogleで「GNU objdump」で検索しますと、日本語ページだけで7650件ヒットします。アセンブルリストを出すオプションは -Sということもすぐわかります。始めはわからなくても、情報を浴びるようにしていると何となくわかってくるものです。最初からあきらめないで色々挑戦してみてください。
投稿: がた老 | 2009年9月27日 (日) 00時24分
>Callするたびに数多くのレジスターがsaveされますから、それを数回経由(save/restoreで2倍)すれば、Callするだけで簡単に100ステップ以上になります。従ってクロックが72Mhzでも1Mhzくらいの繰り返しになるのは何の不思議でもありません。
ということで納得したということです。
ありがとうございました。
投稿: へっぽこ回路師 | 2009年9月26日 (土) 22時53分
がた老です。
原因がわかったということですが、どうも状況が良く見えませんね。何をなさりたかったのでしょう。
アセンブラーリストの出し方は、開発環境、コンパイラーによって全部違うので、ここでは書ききれません。GNU系だと、objdump > 出力ファイル名 ですが、お調べください。情報はウェブに沢山出ていますよ。
投稿: がた老 | 2009年9月26日 (土) 22時36分
ありがとうございます。
原因がわかりました^^
すみませんまた質問です。
アセンブラーリストが見つかりません教えてください。
投稿: へっぽこ回路師 | 2009年9月26日 (土) 18時28分
ああ、そういうことでしたか。それは正常だと思います。そちらのLEDのON/OFFは関数でおやりになっていますから、結構沢山のプログラムを呼び出しているはずです。アセンブラーリストを出してみればわかりますが、Callするたびに数多くのレジスターがsaveされますから、それを数回経由(save/restoreで2倍)すれば、Callするだけで簡単に100ステップ以上になります。従ってクロックが72Mhzでも1Mhzくらいの繰り返しになるのは何の不思議でもありません。アセンブラーでI/Oポートを直接叩くのと、C言語ではこれくらいのオーバーヘッドがあるのが普通です。ARMの命令も、すべてが1クロック、1命令では動かないはずで、CPUクロックが8MHzでないことは明らかです。
投稿: がた老 | 2009年9月25日 (金) 23時28分
お返事ありがとうございます。
LEDとARMの間の信号線をオシロで測りました。
ちょうど1MHzくらいの矩形波がでていました。
システムクロックが72MHzならもっと高速なオンオフを繰り返すはずですよね?
投稿: へっぽこ回路師 | 2009年9月25日 (金) 22時19分
がた老です。いやあ、困ったな。ARMはまだ初心者レベルなのです。気がついたところだけ。
・オシロはどこで測られましたか。72Mhzというのは内部クロックなので、外部にクロックは出ていないはずです。オシロで見えるのはベースの8MHzのところだけではないでしょうか。
・Main.cのLEDの点滅の間に待ち時間がありません。このままだと一瞬しかLEDは点かないので点いたようには見えないでしょう。点けた後少なくとも数十msのwaitをかけ、点けたままにしておかないと人間には点いたように見えません。
投稿: がた老 | 2009年9月25日 (金) 18時52分
ありがとうございます。
既存のソースを流用するなど試行錯誤してみましたが、どれを試してもオシロスコープで見る限り、8MHzで動作しているようです。
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
のRCC_PLLMul_9を小さくすると速度は遅くなるのですが、RCC_PLLMul_9での8MHz動作が最高速度のようです。
オシロスコープも10MHz程度までしか測れないものなので見間違えはないと思います。
とりあえず今は以下のソースでやっているのですが、どこが間違っているのでしょうか?
度々すみません、お願いしますm(_ _)m
LEDのオンオフを繰り返すプログラム
main.c
#include "stm32f10x_lib.h"
/* Private variables ---------------------------------------------------------*/
GPIO_InitTypeDef GPIO_InitStructure;
ErrorStatus HSEStartUpStatus;
volatile unsigned long _1ms_counter;
/* Private function prototypes -----------------------------------------------*/
void RCC_Configuration(void);
void NVIC_Configuration(void);
void port_init(void);
int main(void){
RCC_Configuration(); // System Clocks Configuration
NVIC_Configuration();
port_init();
//sys_tick_init();
while(1){
GPIO_SetBits(GPIOC, GPIO_Pin_12);//LED_off
GPIO_ResetBits(GPIOC, GPIO_Pin_12);//LED_on
}
}
void RCC_Configuration(void)
{
/* RCC system reset(for debug purpose) */
RCC_DeInit();
/* Enable HSE */
RCC_HSEConfig(RCC_HSE_ON);
/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(HSEStartUpStatus == SUCCESS)
{
/* Enable Prefetch Buffer */
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
/* Flash 2 wait state */
FLASH_SetLatency(FLASH_Latency_2);
/* HCLK = SYSCLK */
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/* PCLK2 = HCLK */
RCC_PCLK2Config(RCC_HCLK_Div1);
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config(RCC_HCLK_Div2);
/* PLLCLK = 8MHz * 9 = 72 MHz */
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
/* Enable PLL */
RCC_PLLCmd(ENABLE);
/* Wait till PLL is ready */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
while(RCC_GetSYSCLKSource() != 0x08)
{
}
}
}
void NVIC_Configuration(void){
#ifdef VECT_TAB_RAM
/* Set the Vector Table base location at 0x20000000 */
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else /* VECT_TAB_FLASH */
/* Set the Vector Table base location at 0x08000000 */
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
#endif
}
//------------------------------------------------------------------------------
void port_init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
// Configure PC.12 as output push-pull (LED)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
投稿: へっぽこ回路師 | 2009年9月25日 (金) 14時55分
はあ、私もSTM32は素人同然で正確に答えられませんが、このあたりは既存のソースを流用するのが一番早道だと思います。
この記事の2つ前で私が公開したダブルバッファーのUARTも殆どは既存のソースのままで、お申し越しの部分は、main.cの冒頭の初期化処理 Set_system()の中の
/* PLLCLK = 8MHz * 9 = 72 MHz */
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
ところだと思います(コメントから(笑))。私のターゲットボードは例の雑誌付録基板(2008/5DWM誌)でクリスタルは8MHzですので、間違いないでしょう。
STマイクロや、ウェブでサンプルコードを沢山拾ってきてまずそのまま動かすことから少しづつ始めてはいかがでしょう。8bitチップと違って32bitプロセッサーはリアルタイムOSを前提にしているので、セットアップは格段に複雑になっています。スクラッチから作るのは余りお勧めしませんね。時間の無駄です。
投稿: がた老 | 2009年9月23日 (水) 21時47分
はじめまして。
同じくSTM32F103(Cortex-M3)をEclipseで開発している者です。
いきなり質問をして申し訳ないのですが、
72MHzのシステムクロックで動作をするにはどうすればいいでしょうか?
外部クロック(クリスタル)が8MHzの下記のものを使っています。
http://www.olimex.com/dev/stm32-h103.html
恐らく内部PLLを使って8MHz⇒72MHzをすると思うのですが、
なんせ、このARMに関しての情報が少なすぎてお手上げ状態です。
初歩的な質問ですみません。
どうか、お願いしますm(_ _)m
投稿: へっぽこ回路師 | 2009年9月22日 (火) 18時00分