Longan nano(Sipeed社製ボード)は、 GigaDevice Semiconductor社のRISC-Vコア搭載32ビットマイコンGD32VF103を搭載しています。低価格なだけあって、マイコンとしては小ピンの部類のパッケージバージョンですが、周辺は充実、なかなか全てを触りきるまでに至りません。本日はかねて懸案のADコンバータを触ってみます。
※「鳥なき里のマイコン屋」投稿順Indexはこちら
もともとの目的を振り返れば、もっと早くにADコンバータを触ってもよかったのですが、面倒くさそうなので後回しにしておりました。このマイコンは、12ビット精度で2Mサンプル毎秒というスペックの逐次比較式のADコンバータを2チャンネル搭載しています。ADコンバータへの入力は外部端子各8チャンネル+内部の温度センサと参照電圧各1チャネルで合計18チャネルあります。ただし、Longan nano搭載のチップの場合、端子が少ないので、外部のアナログ入力端子は8端子となっており、1つの端子がADC0とADC1の両方に接続されているようです。
AD変換そのものは出来たとして、ADCを使って連続して入ってくるアナログ入力信号を処理しようとする場合、決めとかねばならないのが
-
- どのようなタイミングでサンプリングするか
- コンバージョンが終わった結果をソフトウエアにどう渡すか
という2点でしょうか。音声データのサンプリングなどを想像すると一番分かり易いと思うのですが、サンプリングは正確な周期で行わないとなりません。よって、ハードウエアのタイマなどで周期を作り、その周期をADCに与えてサンプリングさせる、という段取りになります。このとき、GD32VF103のADCは2つのユニットで複数の外部入力端子を扱えるようになっているので、いろいろなモードでサンプリングできるようになっています。2つのユニットをマスタとスレーブとして連動させてサンプリングさせたり、それぞれ独立に動作させたり、外部入力端子を順次スキャンしたり、1つの端子を連続して読み取り続けたりなど機能的には充実しています。勿論、タイマも複数あるので、可能な組み合わせは結構あります。
また、AD変換したデータの取り扱いも重要です。それぞれのユニットは変換したデータをレジスタに出力してくれますが、これを次々とメモリに送っていかないとなりません。この作業にCPUを割り当てているとCPUが他の仕事をできないので、DMA(ダイレクト・メモリ・アクセス)コントローラを使って、変換後のデータをメモリ上の配列などに格納してから、割り込み使ってCPUに「届いてますよ」とお知らせする必要もあります。
ちゃんとADコンバータ動かそうとするとタイマとDMAと割り込みコントローラを先にやっとかねばなりません。まあね、やるつもりではいるのだけれど、週1ペースで1個ずつ潰していっても大分先だな~ そこで、
今回はタイマもDMAも割り込みも無しでAD変換を1発
やってみようということにいたしました。アナログ信号といってほとんどDCのような遅い信号であれば、
-
- サンプリングタイミングはソフトで決める
- 変換が終わるころまでゆっくり待つ
- 変換終わったらソフトで読む
という方法で、とりあえずADコンバータを動かしてみることができるでしょう。例によって、このところ使っているデモソフトに行を追加していきます。
まず、クロックの設定。いつものように、ADC0にクロックを与える許可するのですが、今回は、それに加えてもう1行必要です。ADCに与えるクロックにはプリスケーラが入っているので、その設定。今回は、とても遅い信号をゆるゆると処理するだけなので、サンプルプログラムで採用していた÷8の定数をそのままコピペ。
rcu_periph_clock_enable(RCU_ADC0); rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);
gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_4); //DAC0, ADC_IN3
というモードです。一発AD変換にトリガかけたら、指定のCHをサンプリングして、変換し、直ぐに終わった、というその名のとおり最もシンプルなモード。また、測定のチャネルの順番やタイミングを制御するために
-
- Regular channels
- Inserted channels
という2つのグループにわけて制御できるのですが、単発で1CH(PA3端子)のみ変換しておしまい、という今回の使い方に向いているのはInserted channelsに思えたので、そちらを使っています。そんなあれやこれやの設定をするのがこちら。
void adc_config(void) { adc_deinit(ADC0); adc_mode_config(ADC_MODE_FREE); adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT); adc_channel_length_config(ADC0, ADC_INSERTED_CHANNEL, 1); adc_inserted_channel_config(ADC0, 0, ADC_CHANNEL_3, ADC_SAMPLETIME_239POINT5); adc_external_trigger_source_config(ADC0, ADC_INSERTED_CHANNEL, ADC0_1_EXTTRIG_INSERTED_NONE); adc_external_trigger_config(ADC0, ADC_INSERTED_CHANNEL, ENABLE); adc_enable(ADC0); delay_1ms(1); adc_calibration_enable(ADC0); }
適当に最初の方で、上記のコンフィギュレーションを実施した後、アナログ入力信号をサンプリングしたいところで、以下の関数にサンプリングをお願いします。先ほど述べたとおり、INSERTED_CHANNELの中、唯一サンプリングすべき端子としてPA3端子を指定しているので、以下によりPA3だけがAD変換されます。(勿論、複数指定すれば複数処理される筈。)
adc_software_trigger_enable(ADC0, ADC_INSERTED_CHANNEL);
本来なら、変換完了を割り込みとかで受けたいところですが、今回は、ゆるゆると回っているループの先の方で、変換結果をソフトで読み出します。ADC_IDAT0というのは、INSERTED_CHANNELの最初の読み出し結果をレジスタから取り出すためのマクロです。標準のadcのヘッダの中に含まれています。
adcData = ADC_IDATA0(ADC0); printf(" ADC : %04x\n", adcData);
ADCは12ビット設定で、HEX表示にしてありますから、最小値は0、最大値は0fff です。0で0V,0fffで3.3Vの筈。まずは、PA3の先を解放とした場合。フォローティング状態では、なにか中間電位が見えていますが、当然、安定せず、フラフラして見えます。
つづいて、入力信号をGNDに落としてみます。
ちゃんと0000が読み取れました。
つづいて、3.3V電源に接続。
期待どおり0fffが読み取れました。
最後にPA3にとなりのPA4を接続してみます。PA4はDAコンバータの出力端子で、DAコンバータも12ビットなので、直前にDAコンバータが出力した値とほぼ同じ値が観察できる筈。
DAが0affのとき、ADは0b00。+1。DAが0dffのとき、ADは0dfe。-1っと。まあ、いいんでないかい。こんな感じで。
今のところいい加減なもんだし。
まあ、タイマとかDMAとか、割り込みとかやってからまたADに戻ってくることにいたしませう。