ガレスタさんのDIY日記

電子回路、Web、組み込み、自作エフェクターを語るblog

C言語可変引数マクロについて

マイコンのDEBUG関数関連見てて

#if defined(DEBUG_ENABLE)
#if defined(DEBUG_SEMIHOSTING)
#define DEBUGINIT()
#define DEBUGOUT(...) printf(__VA_ARGS__)
#define DEBUGSTR(str) printf(str)
#define DEBUGIN() (int) EOF

#else
#define DEBUGINIT() Board_Debug_Init()
#define DEBUGOUT(...) printf(__VA_ARGS__)
#define DEBUGSTR(str) Board_UARTPutSTR(str)
#define DEBUGIN() Board_UARTGetChar()
#endif /* defined(DEBUG_SEMIHOSTING) */

的な”・・・”を多用したやつ見かけて調べてみた。

可変引数と呼ばれているみたいだ。

gccでは __VA_ARGS__ という記述でできるみたい。

printfなんかは引数が変化するのでラッピングするのは結構難しいけどこれ使うと結構いい感じにできるっぽい

STM32でADCをやってみる2(DMAを使ったレギュラ変換)

前回は単一チャンネルの変換を行ったが今回はDMAを使ったものをやって行こうと思う。

とりあえずADCおさらい

ADCは変換終了フラグ(EOC)を見てDRレジスタを見に行くことで変換データを得ることができる。

f:id:gsmcustomeffects:20170409035725p:plain

上記の図のようにシーケンスの終了でもフラグが立つ(EOS)
二つのフラグを見てソフトウエア的に行うこともできるがタイミングがずれるとDRのデータが上書きされてしまう。

それを防ぐ仕組みにオーバーランというものある。
これはデータを保護できるがそのあとの変換値は破棄されてしまうので注意が必要だ

f:id:gsmcustomeffects:20170409040231p:plain

そのため複数チャンネルを変換する場合EOCフラグをトリガとしてDMAでレジスタ→メモリを実現したほうがソフト的な介在がなくなるので楽に処理できる。

マニュアル曰く(RM0316 345ページ)

変換されたチャネルの値は特定のデータレジスタに格納されるので、複数のチャネルの変換には
DMA の使用が便利です。これによって、ADCx_DR レジスタにすでに格納されているデータの損失を防ぐことができます。
DMA モードが有効なとき(ADCx_CFGR レジスタの DMAEN ビットがシングル ADC モードで 1 にセットされている場合、またはデュアル ADC モードで MDMAが0b00 以外に設定されている場合)、
各チャネルの変換後、DMA リクエストが生成されます。これにより、変換データを ADCx_DR レジスタからソフトウェアで選択した場所へ転送することができます。
これにもかかわらず、DMA が DMA 転送リクエストを時間内に処理できなかったためにオーバーランが発生した場合(OVR=1)、ADC は DMA リクエストの生成を停止し、新しい変換に対応するデータ
は DMA によって転送されません。
これは、RAM に転送されるすべてのデータを有効とみなすことができることを意味します。

要するにCH変換ごとにDMAリクエストが生成されるということ。
オーバーランが発生するとDMAがとまる。
OVRMODを適切に処理するか頻繁に止まるようならDMAを時間内に処理できるように変換自体の速度を下げておくなど工夫が必要だ。

DMAのモードには

  • DMA ワンショットモード(DMACFG=0)
  • DMA サーキュラモード(DMACFG=1)

の2つがあり

ワンショットは一回きりの転送になります

f:id:gsmcustomeffects:20170409042453p:plain

一方サーキュラは連続的にADCのCH変換が終わるたびにDMAリクエストが生成するので連続的なストリームが構成できる。

f:id:gsmcustomeffects:20170409042740p:plain

さっそくやっていく

CubeMXでADCを設定していく。

f:id:gsmcustomeffects:20170409043833p:plain

ADCの詳細設定

f:id:gsmcustomeffects:20170409043955p:plain

DMAの設定

f:id:gsmcustomeffects:20170409044035p:plain

ここでアドレスのインクリメントにチェックを入れておくといい感じに配列に格納できる。

ハードウエア

前回と同じでこれにPA_5、PA_4を追加して2CHを読んでいる点だ

f:id:gsmcustomeffects:20170408045403j:plain

コード

参考までに動いたコードを示しておく
ADC終了時に呼ばれるコールバック内にprintfを入れてUART出力している。


動作例

Teratermではこんな感じに2CH読めている。

f:id:gsmcustomeffects:20170409050119p:plain

Expressionではこんな感じ

f:id:gsmcustomeffects:20170409050225p:plain

まとめ

3日間ぐらいかけてADCやってみたんだけど何だかんだDMAを使ったのが一番簡単だねぇ
DMAといったら結構難しいイメージあったけどCubeMXのおかげでほとんど設定してくれるのでかなり楽ですね。

3日間一緒に検討してくれたリアルテック先生には感謝です。

realteck-blog.netlify.com

2017/5/18追記

お恥ずかしいお話なのですがオカダといろいろやってて複数のADCを使うとMainにブレーク立てても帰ってこないと2日間ぐらい悩みました。

f:id:gsmcustomeffects:20170518003058p:plain

んで上記の図のようにこいつのADCには低速チャネルもあるんです。
12ビット精度で変換する場合最低でも14サイクルかかります
f:id:gsmcustomeffects:20170518003238p:plain

高速の場合Cubeで19.5サイクルにすればよいのですが低速の部分も19.5にしてました・・・・・ほんと雑魚wwww

なので皆さんはこんなバカなことで時間とかしちゃだめですよ!

直すとちゃんと動くf:id:gsmcustomeffects:20170518003433p:plain




STM32でADCをやってみる1(レギュラ変換)

今回は一番簡単なやつやってみます。

やったことを雑に書くとこんな感じ

  • レギュラ変換
  • ADC2の1CHだけを使った連続変換
  • PB4ピンにポテンショメータをつなぎそれを読む
  • 変換値をUARTでTeratermに表示する。

CubeMXでの設定

今回は一個しか読まないのでこのようにPA4にアサインする
一応UARTも使ってるのでそれも設定する

f:id:gsmcustomeffects:20170408045027p:plain

各種設定はこんな感じ

f:id:gsmcustomeffects:20170408045044p:plain

要所要所説明挟むとこんなもん

ADC_Setting

設定 機能 説明
mode independent mode 独立モードで動く、その他速度を上げるDualModeがある
Clock Prescaler 非同期、1~4分周 読んで字のごとくクロック分周器
Resolution 6~12bit ADCの分解能
Data Alignment 右詰め、左詰め データの詰め方
Scan Coversion Enable,Disable 一回のシーケンスですべてのCHを変換するかどうか
Continuos Conversion Mode Enable,Disable 連続変換
Dis Continuos Conversion Mode Enable,Disable 不連続変換
End Of Conversion Selection シングルごとかシーケンスごとか両方か 変換終了フラグの有無 データシートEOCフラグの部分読んだほうが図もあってわかりやすい
Low Power Auto Wait Enable,Disable 自動遅延変換モードの有無。ADC オーバーランが発生するリスクのある低周波数のクロックで動作しているアプリケーションのパフォーマンスを最適化する場合に有効

ADC_Regular_Conversion_Mode

設定 機能 説明
Enable Regular Conversion Enable,Disable レギュラー変換の有効化
Number of conversion 変換数 チャンネル数分設定できる
External Trigger Conversion Source ソフトおよびタイマートリガ 変換トリガソースの設定。 タイマーとかに連動できる
External Trigger Conversion Edge 立ち上がりか立下りかソフトか 外部割り込みの設定

Rankの設定

RankはCH分だけ個別設定ができる

設定 機能 説明
Channel チャンネル数分設定できる 設定したいCHを入れる
SamplingTime 1.5~601.5サイクル サンプリングタイム
OffsetNumber 1~4 オフセットするCHの選択
Offset 1~4056 変換値のオフセット

ハード構成

一応紹介しておく
POTの2番ピンをA3ピンにつないでいるだけだが

f:id:gsmcustomeffects:20170408045403j:plain

コードを書いていく

参考までにコードを貼っておく

STM32でCMSIS DSPライブラリを使ってみる

今回は高速に三角関数とかを計算できるARM社提供ライブラリであるCMSIS DSPライブラリを使ってみる話です。

API各種はarm_math.hというファイルで用意されています。

今回の実習環境は以下の通りです

  • IDE:AtollicTrueStudio
  • Board:STM32F767 Nucleo144

導入

今回はCortexM7のNucleo144を使います(746か767

CubeMXで適当にプロジェクトを作ってビルドが通るとこまで作っておきます。

その次にmain.cプログラム上部のインクルードの上に

#define ARM_MATH_CM7

の定義を追加します。

これはCortexM7で使うよというのをコンパイラに教えてあげる定義です。

次にヘッダーをインクルードします。

#include "arm_math.h"
#include "arm_const_structs.h"

オブジェクトの追加

次にライブラリのオブジェクトを追加していきます。
オブジェクト自体はCubeF7のフォルダ内にあります。

場所を調べるには

CubeMX上記メニューからhelp->UpdaterSettingでパスをコピーします

f:id:gsmcustomeffects:20170405164420p:plain

ファイルエクスプローラーでそこに移動します。

するとこんな感じのファイル構成になっていると思います

f:id:gsmcustomeffects:20170405164606p:plain

オブジェクトは以下のPATHにあります。

STM32Cube\Repository\STM32Cube_FW_F7_V1.6.0\Drivers\CMSIS\Lib

f:id:gsmcustomeffects:20170405165503p:plain

三つありますがダブルポイント(倍精度)かシングル(単精度)かを選ぶ。
今回はF767なのでdpがついてるlibarm_cortexM7lfdp_math.aを選びます。

それをコピーしてプロジェクト直下にいれます。

f:id:gsmcustomeffects:20170405164940p:plain

Linkerなどの設定

プロジェクトのプロパティーで「C/C++Build」の設定内の
Linker、LibrariesでDSP処理のLinkerとかを設定していく

Librariesに下記を追加。

「:」を頭につける

:libarm_cortexM7lfdp_math.a

Library search pathに下記を追加

"${workspace_loc:/${ProjName}}"

f:id:gsmcustomeffects:20170405165752p:plain

んでいったんビルドする

f:id:gsmcustomeffects:20170405165819p:plain


FPUの設定

以下のように選択する

f:id:gsmcustomeffects:20170405172401p:plain

詳しい説明はここ読んでください
ARM Information Center

コードを書いていく

今回は試しに平方根でもやってみます。
FastMathのとこに入ってます。

CMSIS DSP研究室さんで詳しく解説されています。

DIGITALFILTER.COM

APIとしては

arm_sqrt_f32 (float32_t in,float32_t * pOut )

を用います

f:id:gsmcustomeffects:20170405170921p:plain
Square Root

こんな感じに書いた
f:id:gsmcustomeffects:20170405171946p:plain

動作

f:id:gsmcustomeffects:20170405172020p:plain

入力した値の平方根が帰ってきているのでちゃんと動いているといえる。

ちなみに返り値でStatを見ているがそれの返り値はENUMで定義されてる。

f:id:gsmcustomeffects:20170405172126p:plain

まとめ

  • DSPライブラリを導入できた
  • FastMath使った割に速度については検討してないので今後やって行きたい

STM32でUARTをやってみる6(float型printfをUART経由で出力)

何だかんだこのシリーズも6個目ですねw

前回まででUARTの基本的な使い方はマスターしたと思います。
今回はCubeMXでの出力は詳しくは解説しないので

まだの方は1から読んでみてください

gsmcustomeffects.hatenablog.com

gsmcustomeffects.hatenablog.com

gsmcustomeffects.hatenablog.com

gsmcustomeffects.hatenablog.com

gsmcustomeffects.hatenablog.com

さっそくやってく

今回の環境は以下に示す通り

  • AC6 SystemWorkbenchForSTM32
  • STM32F303K Nucleo
  • Teraterm

とりあえずCubeでUART2を有効にしてボーレートを各自設定しファイルを出力する。
ココまでは今までと同じです。

Syscall.cを持ってくる

syscall.cがCにおけるスタンダードライブラリを読んでいるのでそれをどっかからもってこないとリンクできないわけです。
とりあえずSTが提供するサンプルには入っているのですがCubeMXが出力するsrcフォルダには入っていません。

なのでAc6で適当な空プロジェクトを作る必要があります。
めんどかったらSTが提供するサンプルから引っ張ってくるという方法もあります。

f:id:gsmcustomeffects:20170404195559p:plain

で今回使うボードを選択

f:id:gsmcustomeffects:20170404195630p:plain

CubeHALを選択

f:id:gsmcustomeffects:20170404195658p:plain

そうするとsyscall.cができるのでそれをCubeMXが出力したSrcにコピーしてくる

f:id:gsmcustomeffects:20170404195814p:plain

f:id:gsmcustomeffects:20170404195841p:plain

んで一度コンパイルします。


Main.cでputcharを再定義。

syscall.c内では

__io_putchar(int ch) __attribute__((weak))

のようにWEAK定義されているのでMain内で再定義してこっちを使うよということをコンパイラに教えてあげる作業をします

こういうふうにします。

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
void __io_putchar(uint8_t ch) {
HAL_UART_Transmit(&huart2, &ch, 1, 1);
}

printfというのは内部で書式を整えてputcharを呼び出しているのでそのputcharの内部をHALの一文字送信APIに置き換えるということです。

コンパイルマクロがGNUとかついてるのはARMの純正コンパイラではfputcと定義されてるのでコンパイラごとに読み替えが効くようにしているためです。

次にprintfを呼ぶ前にどこか最初以下の行を挿入します

setbuf(stdout, NULL);

これはprintfの仕様みたいで1024byte入れた後に呼ばれるのでコレがないと変なハンドラに飛びます。
とりあえずバッファをフラッシュするといいみたいです。

ここまででとりあえずint型のprintfは動きます。

だいたい15KBぐらいになります。

Linkerフラグの編集

リンカーオプションにfloatを追加して再ビルドする必要があります。

やることとしてはlinkerのフラグ部分に-u _printf_floatというのを追加するだけです。

f:id:gsmcustomeffects:20170404201644p:plain

それであとはApplyして再ビルドします。

f:id:gsmcustomeffects:20170404202009p:plain

結構サイズがでかくなります。

いうてGCCならKEILと違って32KB以上もビルドできるしFloat扱うようなマイコンはフラッシュ自体大きい物がのってるので特に悩まないかと思います。

動作のようす

こんな感じに書いて

f:id:gsmcustomeffects:20170404202315p:plain

Teratermで表示する

f:id:gsmcustomeffects:20170404202339p:plain

ちなみに桁数ちゃんと合わせないとゴミが入りますw

ちなみにSemihostingというARMの機能を使ってSWD経由でEclipse上のコンソールに出力することもできます。
それについてはユークリッドさんが書いているので引用させていただきます。

yuqlid.hatenablog.com

yuqlid.hatenablog.com

場合分けで使いたい方使えばいいと思います。

単に値見たいだけならExpression機能もありますしね

ARMのソフトウエアトリガ割り込みについて

ARMのソフトウエア駆動割り込みの覚え書きです。

ARMの割り込みはNVIC(統合ネスト型ベクタ割り込みコントローラ)で管理されていますがその中でSTIRというレジスタがあります

f:id:gsmcustomeffects:20170404051828p:plain

図で言うと赤の部分ですね

詳細はこちらになります

f:id:gsmcustomeffects:20170404052003p:plain

Write to the STIR to generate an interrupt from software

とあるように割り込みの番号を入れてやるとソフトウエアから割り込みを生成できるようです。

実際の使い方はこのような感じです。

f:id:gsmcustomeffects:20170404052313p:plain

STIRレジスタIRQ番号を書くだけです

STM32の場合IRQ番号はstm32f767xx.hに定義されています

f:id:gsmcustomeffects:20170404052432p:plain

ためしにピン割り込みハンドラにソフトウエアから移動してみます

f:id:gsmcustomeffects:20170404052528p:plain

図ではわからないですがスイッチを押さなくても割り込みハンドラに遷移しています。
もちろん割り込みも有効なのでスイッチを押すとそのタイミングでも割り込みがかかります。
なのでこの割り込みを使う際は使わないであろうペリフェラルを殺しておいて使うほうがいいでしょう