2017年9月30日土曜日
ARMv6-Mと戯れる 第1号 ~ARMv6-Mと戯れる準備をしよう~
まえがき
大抵の場合「ARMマイコン!ARMマイコン!」と言っているその中身は、ARM社が提供しているプロセッサに加えて、チップベンダー各社が周辺回路を加えてパッケージングされたものだったりします。 「マイコンを使えます」という人でも、自分が使っているマイコンがどういったプロセッサを使用しているのか詳細を答えられる人は稀で、せいぜい「Cortex-M0+です」とかその程度のものでしょう。
4年前の2013年、LPC810でも動作するUOS-LPC800を設計し、その過程でARMv6-Mのレジスタセットについて学習しました。 この学習過程を振り返った上で、再度見直して楽しんでみようというのが本シリーズです。
ブート!
学習過程を振り返るというお題があるので、学習を始める過程も挙げておきます。 まずは題材となるマイクロコントローラのデータシートを見ます。
NXP社のウェブよりLPC81X_LPC83X: Low-Cost Microcontrollers (MCUs) based on ARM® Cortex®-M0+ Coresには、ARM Cortex-M0+と書かれていますね。でも、この段階では「あぁ、ARM Cortex-M0+っていうのを使っているんだ。」程度にしかわかりません。
次に「このARM Cortex-M0+って何だ?」というのは、ARM社の情報を見る事になります。 https://developer.arm.com/products/processors/cortex-m/cortex-m0-plusには、ARM Cortex-M0+という絵の中に「CPU ARMv6-M」とあり、「あぁ、ARMv6-Mと呼ばれるCPUを使っているんだなぁ」と先ほどのARM Cortex-M0+から一段掘り下げた情報が得られます。
で、ハイライトを見ると、ISA Supportの欄に「Thumb/Thumb-2 subset.」と書かれています。この「ISA」というのは、Instruction Set Architectureの略で、命令セットアーキテクチャは「Thumb/Thumb-2のサブセットだよ」と言っています。
ここまでで、「ARM Cortex-M0+は、ARMv6-Mと呼ばれるCPUを使っていて、命令セットアーキテクチャはThumb/Thumb-2のサブセットである。」という事がわかりました。
さて、プロセッサと戯れるためには、ここで止まってはいけません。 更にhttps://developer.arm.com/products/architectureから、M-Profile Architecturesの情報https://developer.arm.com/products/architecture/m-profileに辿り着きます。
概要ページにはARMv6-Mアーキテクチャの概要も書かれており、「T32命令セットをサポート」と書かれています。 新しいキーワードT32命令セットが出てきましたね。
Instruction Setsのページhttps://developer.arm.com/products/architecture/instruction-setsを見ると、A64、A32、T32の各命令セットについてリンクが張られています。
https://developer.arm.com/products/architecture/instruction-sets/a32-and-t32-instruction-setsには「T32命令セットはARMv8アーキテクチャ以前にThumbとして知られていたもの」と書かれています。つまり、先に出てきたThumbと呼ばれる命令セットはT32命令セットである事がわかりました。
今回のまとめ
「ARM Cortex-M0+は、ARMv6-Mと呼ばれるCPUを使っていて、命令セットアーキテクチャはThumb/Thumb-2のサブセットである。T32命令セットはThumbとして知られている。」という事がわかりました。
次回は、ドキュメントのページhttps://developer.arm.com/products/architecture/m-profile/docsに辿り着いて色々と見てみましょう。
http://docs-api-peg.northeurope.cloudapp.azure.com/assets/ddi0419/c/DDI0419C_arm_architecture_v6m_reference_manual.pdfがアーキテクチャのリファレンスマニュアルです。
2015年8月10日月曜日
Micro MML Player LPC812のMaker Faire Tokyo 2015出展報告
ちょっと愉快にMMLを再生しながら、音符まで表示しちゃうギミックを追加。
展示をご覧頂いた方からは「小さい!」とか「可愛い!」とか「音が聞こえない!」とか「何の曲を演奏しているのかわからん!」とか、大好評な御意見を様々に頂きました。
LPC812は、ROMが16KB、RAMが4KBと32ビットプロセッサとしては小さな部類に入りますが、今回はこのプロセッサにリアルタイム・オペレーティング・システム(UOS-LPC800)と、MMLパーサー(A tiny MML parser)を搭載。MMLのパースと音符の表示系の処理をそれぞれタスクに分離する事で、重い描画系の処理と時間軸がズレては困る音声系の処理の並列動作を実現しています。
本当はシェル(Natural Tiny Shell : NT-Shell)も載せたかったのですが、これは入りきらずに断念しました。え?手段と目的が滅茶苦茶になってるって?そうです。手段と目的がごった煮になってま・・・。
ブースの左側は今回メインのMicro MML Player LPC812を展示しました。
準備段階では「音が煩過ぎたらどうしよう」とか考えていたのですが、「何言ってんだい」という感じ。
ブースでは全く音が聞こえませんでした。「あぁ、音も出てるんですね!」という声が続発!ついでに「あぁ、音符も表示してるんですね!」って。結局何を展示しているのかわからん!という話だったようです。なるほど。
ブース右側には今回のシステムで動作しているオペレーティング・システムを展示しました。
これがまた目的と・・・手段を・・・ぐるぐる鍋でかき回して・・・。
こーんな感じの展示で、次回の展示募集でこの内容は通らないだろうな・・・と思いました。
来年は違う路線で行こう!
最後に今回のファームウェアのコールグラフ!
こんな小さなマイコンで複数のタスクが動作するなんて素敵だなぁ!
だから・・・そうじゃなくて・・・。
2015年5月31日日曜日
LPC810でも動作するリアルタイム・オペレーティング・システムのUOS-LPC800にタスク間通信機能を追加しました!
あらまし
約二年も前の話ですが、「割と適当に動作するOS「誰得OS」のCortex-M0+版であるUOS-LPC800を作りました」で 、LPC810でも動作するリアルタイム・オペレーティング・システムを作りました。LPC800シリーズの厳しい制約(ROM: 4KB、RAM: 1KB)の中でOSを動作させてみたいという欲求と、実際にこのような制約の中で何が出来るのか、単純に技術的な興味があったからです。当初のバージョンにおいて、タスク側から操作可能なAPIはuos_task_yieldとuos_task_sleepのみで、OSと呼ぶならば欲しいであろうタスク間通信すら追加しませんでした。 サンプル・プログラムは、片方のタスクでLEDを点滅させ、もう片方のタスクでシリアル通信を扱うだけのものです。まさに「誰得OS」の名前に相応しいアプリケーション。
なかなか思い切った割り切りだと当時は考えましたが、先日から実アプリケーションにUOS-LPC800を適用してみようと考え始めた時点で、タスク間通信の必要性を改めて感じる事になりました。各タスクがそれぞれの仕事をこなしつつ、他のタスクと協調して動作させようと考えた場合、タスク間通信は必須とも言える機能です。 やっぱり欲しいよね・・・。
設計
タスク間通信を追加する前のUOS-LPC800におけるタスクの状態遷移は以下です。タスクは、基本的にRunningとReadyを行ったり来たりしており、uos_task_sleepによってスリープ状態に遷移するようにしました。非常にシンプルな作りです。今回、タスク間通信を追加するにあたって、方針を「出来るだけ現状の設計には手を加えないで追加する」とし、スリープ処理に手を加えない形でタスク間通信を追加する事にしました。
今回のタスク間通信(メッセージ・パッシング)のモデルは以下のようなものです。 Task Aからuos_task_event_sendに対して「送信先タスクID番号」と「32ビットの値」を渡すと、あーら不思議、uos_task_event_recvを使って待ち状態に入っているTask Bに値が渡って実行状態に遷移するというものです。モデルは至って単純。
今回、上記の単純なモデルを実現するために、カーネル内部にブロック状態を管理するタスク・コンテキスト・ブロックのキューを新設し、送信待ちタスクと受信待ちタスクをブロック状態のキューに入れてから処理する事にしました。状態遷移は以下のようになります。
本当は、スリープ状態もブロック状態の一種として扱え、相当な理由が無い限り分離する意味はなさそうなのですが、今回は既存機能に手を加えない事にしたのでそのまま。もしかしたら次のアップデートではブロック状態をBlockedに変更して、現在のBlockとSleepと統合するかもしれません。
サンプル・アプリケーション
サンプル・アプリケーションは、従来通り二つのタスクを用意しました。タスク間通信を使用する場合は、task_ttyからtask_ledに向かってタスク間通信機能を使ってLEDの点灯指令を発行します。外から見た動作は従来のサンプルと何ら変わらないのですが、タスク間通信で指令するようになったところが従来と異なります。ちなみに、タスク間通信を使う場合、直接タスク間通信の関数を呼び出しても良いのですが、受信側タスクにサービス要求用のAPIを作って、送信側のタスクがそのサービス要求用のAPIを呼び出す方が筋が良い設計です。
巷に溢れる数多くのリアルタイム・オペレーティング・システムを使った実装例には、受信側タスクの内部事情を送信側タスクが知らなければ実装出来ないような記述が多く見られます。これではタスク間通信によって得られるはずの抽象化度を上げる事によるメリットの多くを享受出来ません。受信側タスクの事情を変更した場合(例えば、送信側に期待する送信内容を変更したとか)に、送信側タスクの実装を修正しなければならないとしたら、規模の大きな設計ではたちまちバグになります。
ダウンロード
- ソースコードはhttp://cubeatsystems.com/uos-lpc800/index.htmlからダウンロード出来ます。
- ライセンスはThe BSD 2-Clause Licenseです。
- LPC800 Mini BoardとLPCXpresso LPC812で動作します。
- 「使っているよ!」とお声がけ頂くと、作者がとても喜びます。
2015年3月7日土曜日
LPC810でスーパーマリオを演奏するためにMMLの繰り返し処理を追加する 〜設計アプローチ編〜
先日A tiny MML parserを使ったスーパーマリオ演奏アプリケーションを公開しました。
折角なので眠っているLPC810への移植を試みたところ、4KBのフラッシュ・ロムに収まらない事がわかりました。実装したスーパーマリオのMMLを眺めると繰り返し処理をべた書きしています。MMLの処理を削る事は有り得ないので、安直に曲データを圧縮する事を考えました。A tiny MML parserに繰り返し処理を追加する事で曲データを圧縮を可能にし、LPC810でもスーパーマリオを演奏出来るようにしようと考えたわけです。
そんなにスーパーマリオに拘っているわけではないのですが、8ビットのマイコンにやらせていた事を32ビットのマイコンに移植して「残念!」みたいな結果になるのが嫌だったわけです。それがたとえフラッシュ・ロムの容量が原因だったとしても!(いや、それは無理・・・なんですけど)
繰り返し処理の仕様を考える
ひとくちにMMLと言っても様々な仕様、様々な実装が存在します。
A tiny MML parserに追加する繰り返し処理の仕様を以下のように定めました。
- []の記号を用いて繰り返し処理の区間を定義するものとする。
- 繰り返し処理の区間はネストさせる事ができるものとする。
- 繰り返し処理の区間は二回繰り返し処理されるものとする。
乱暴なプログラマの場合、いきなり実装に入ってしまうかもしれません。
ちょっと考えて「出来るんじゃね?」と思う訳ですが、この記事では「簡単そうに見える処理が実際もそうなのか?」という視点でも見てみたいと思います。
最初にテストデータを定義する
A tiny MML parserに繰り返し処理を追加するにあたって、最初にテストデータを定義しました。このテストデータを新しいバージョンのA tiny MML parserに与えて、期待する結果が得られれば新たに加えられた処理は正しいという理路整然としたアプローチです。
- [CDE]
- [CDE[FGA]]
- [[CDE]FGA]
- [[]]
- [C[D]E]
- [CD[EF]GA]
- [[[[[[[[[[[[[[[[[[[[
- ]]]]]]]]]]]]]]]]]]]]
- [ ]]
- [[ ]
- ][
安直な設計でどうなるのか考えてみる
それではここで、めちゃくちゃ安直に設計無しで実装に取りかかった場合、どのようになるのかシミュレーションしてみましょう。
新人A君の場合
新人A君は、学生時代にマイコンでプログラミングを楽しんでいた意欲的な新人です。
与えられた仕様に対して直感で「これは位置を記憶する変数とループ状態を保持するフラグがあれば解決出来る!」と意気込み、早速プログラミングに取りかかる事にしました。
新人A君は、とりあえず実装しながら動作を確認し、ちょこちょこ修正していくアプローチで、学生時代には沢山の作品(製品ではない事に注意)を作ってきました。今回の新人A君の設計は仕様を満たせるでしょうか?
新人A君は、ざざざーとプログラムを意気揚々と書き上げました。
彼の最初のバージョンは、以下のテストデータを正しく処理しました。
- [CDE]
- [CDE[FGA]]
- [[CDE]FGA]
- [[]]
- [C[D]E]
- [CD[EF]GA]
- [CDE]
- [CDE[FGA]]
- [[CDE]FGA]
- [[]]
- [C[D]E]
- [CD[EF]GA]
- 簡単なスタックモジュールを設計する
- 代表的なパターンを定義する (これはテストデータの一部でもある)
- 定義したパターンを満たす一般ルールを見つける
- 一般ルールを他のパターンに適用して破綻がないか確認する
予想以上にROM 4KBが厳しい制約!
2015年3月1日日曜日
LPC810のState Configurable Timer(SCT)を使ってPWMを構成する
概要
A tiny MML parserのアプリケーションを考える過程で、LPC810が手元で眠っている事に気付きました。8ピンのARM Cortex-M0+でA tiny MML parserなんて素敵です。さっそく調べてみたところ、先日のArduinoと同様に動作させる事を考えた場合には、State Configurable Timer(SCT)を使ってPWMを構成すれば良い事がわかりました。
あちこちで大不評のSCTですが、実は簡単に使えるのではないかと思っていました。
が、実際にやってみてやっぱり大撃沈。レジスタマップと機能説明を読んでも、どんな風に設定すれば自分が欲しい動作になるのか、全然意味がわかりません。
そんな中、LEDをステートマシンで点滅させるエクセサイズを見つけたので、頭からやってみる事にしました。これを応用すれば出力をトグルさせるだけのPWMにも適用できるというわけです。
リソース
エクセサイズのリソース:http://www.nxp-lpc.com/updated_materials/sct/lpc81x/LPC81x_SCT_code_examples.zipエクセサイズのプレゼン:https://onedrive.live.com/view.aspx?resid=7AE40E5A5F1EC907!24470&ithint=file%2cpptx&app=PowerPoint&authkey=!ACDxmgRtdAZZdXE (スライド30ページから)
エクセサイズのリソース
ダウンロードしたエクセサイズのリソースは、説明に従って実際にプロジェクトを仕上げるために、作業開始時点の内容になっています。プロジェクトをセットアップし、スライド30ページを開きます。Red State Machine file generator
「Choose Target」ボタンを押してターゲットを選択します。
上記で準備は完了。
エクセサイズのスタートです。
ステップ1
- U_ALWAYS状態を削除する
- State Tableタブで状態を構成する
次に状態を管理するState Tableタブに二つの状態を入力します。
このState Tableに状態名や次の状態名を入力すると、画面中央にある状態編集用GUIにも自動的に反映されます。
上記編集直後の画面は以下。
ちょっと配置が汚いので状態を示すボックスをマウスでドラッグして移動させて下さい。
矢印が双方向に見えるかもしれませんが、そんな事はありません。
きちんと二本に見えるように配置して下さい。
ステップ2
次にステート・マシンに対する入力、delayとmatch0を定義します。ステップ3
動作の定義を追加します。EVENT 0
始めにAction Listタブの右上にあるプラスボタンを押して新しい動作を追加し、名前をEVENT 0に変更します。EVENT 0に対する動作を定義します。
追加は、Operationと書かれたテーブルの上にカーソルを移動させて右クリックして行ないます。
Setオペレーションに対して「Output pin 0」を選択します。
Callオペレーションに対して「Limit unified counter」を選択します。
EVENT 1
同様にEVENT 1を定義します。名前をEVENT 1に変更して、以下の動作を追加します。
EVENT 1は、EVENT 0に対して逆の動作Clear Output pin 0を選択します。
EVENT 0の時と同様にCall Limit unified counterも追加します。
ステップ4
動作を定義したら、状態に関連づけます。State Tableタブに戻ってActionカラムをクリックすると、先ほど定義したイベントを選択できます。
出来上がると以下のようになります。
念のため、ここでSignalsタブの内容も確認しておきましょう。
この値は、最初にState TableにSignal名を入れた時点で自動的に追加されています。
この時点で、画面中央のステートマシン編集画面は以下のようになっています。
ステップ5
Outputs for State Machineタブを開いてプリロード値を設定します。Generate Codeボタンを押してコードを生成します。
ステップ6
ワークスペースを切り替え、SCT初期化コードを有効にする。エクセサイズのコードで予め実装されたコメントアウト済みの箇所のコメントアウトを外します。
次に、sct_user.hを開いて下さい。
吐き出されたコードは「#include "board.h"」なる定義が実装されています。
ここを「#include "LPC8xx.h"」に書き換えます。このエクセサイズに内包されたCMSISの中にこの定義ファイルがあります。
何故このような作業が必要になるかというと、ツールが自動生成したsct_fsm.cからsct_fsm.hが参照されており、このsct_fsmの内部実装でLPC_SCTという定義を使っているからです。
ステップ7
最終調整です。まず、SCTに制御させるピンを決定します。
今回はLPC800 MiniボードのLEDを点滅させる事にしましょう。
このボードは、PIO0_2にLEDが接続されています。
SWMのピン割当レジスタを設定します。
SWM PINASSIGN6レジスタのビット31:24がCTOUT_0の選択レジスタになっています。
PIO0_0が0x00、PIO0_17が0x11と順に値を加える事で出力を選択します。
今回はPIO0_2ですから、0x02を設定すれば良いことになります。
これで準備完了です。
その他の注意点
CMSISのクロック選択
CMSISのクロック選択は、system_LPC8xx.cの実装側に含まれます。動作させるボードに応じて設定する必要があります。
この選択を間違えると「何で動かないんだ?」と混乱します。
対象プロセッサ
対象プロセッサを正しく選択して下さい。この設定は、プロジェクトのプロパティに含まれます。
この選択によって、ツールチェインに与えられるリンカスクリプトが変わります。