STM HAL ADC

 オンボードのADCの使い方

File-NewからSTM32Projectを選択と新規に作るところから。適当な名前でプロジェクトを生成。

ピンの設定からはじめる。今回はADCを使うので、CategoriesーAnalogの下のADC1を選択し、IN0にチェックを入れる。


F103では16chのADCを最大で利用する事ができるが、その1番目を使う事になる。
このIN0でPA0のピンが緑色になり選択される。データシートでもこのADCのIN0がPA0にアサインされている事が確認できる。

USBのシリアルからPCにAD変換の結果を出力したいので、以前にやっているが、
CategoriesーConectivityのUSBにチェックを入れて□Device FSにチェックをいれる。続いて、
CategoriesーMiddlewareのUSB_DEVICEを選択して、Class For FS IPをCommunication Device Classを選択する。

USBは48MHzで動作するので、クロックの設定が必須になるので外部クロックの水晶振動子に接続する設定をする。


CategoriesーRCCを選択して、High Speed ClockをCrystal/Ceramic Resonatorを選択する。

クロックの設定をする。

PLLMulを9倍にして、USB Prescalerを/1.5にする。これで48MHzになる。
ADC Prescalerは/6を選択。
これで設定が完了したので、Ctrl+Sを押して保存し、コードを自動生成させる。

コードの記載

まず、ADCの値を格納する変数を定義する。
プライベート変数を定義する所に、

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;

/* USER CODE BEGIN PV */
uint16_t adc_value;
float adc_voltage;
/* USER CODE END PV */

とADCの生値を格納する変数と、それを電圧に直した値を格納する変数の2つを定義する。
ちなみに、自動生成されたADCのハンドルがhadc1となっている事がここで確認できる。
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
    HAL_ADC_Start(&hadc1);
    if( HAL_ADC_PollForConversion(&hadc1, 1000) == HAL_OK )
    {
    adc_value = HAL_ADC_GetValue(&hadc1);
    adc_voltage = 3.3 * adc_value / 4095;
    }
    HAL_ADC_Stop(&hadc1);
  }
  /* USER CODE END 3 */
を追記する。
使っている関数の説明をざっとすると、
HAL_ADC_Start:&hadc1のハンドル名で指定したADCをスタートさせる関数
HAL_ADC_PollForConversion:&hadc1のハンドル名で指定したADCの変換が完了したかをポーリング(検出)する関数。第二引数は待つ時間で1000msec待たせている。
変換が完了した時点で”HAL_OK"を返してくる。
HAL_ADC_GetValue:&hadc1のハンドル名のAD値を取得する
HAL_ADC_Stop:&hadc1のハンドル名ADCを停止させる。

4095は2の12乗が4096で0を含めると4095が最大数なので、4046-1階調。3.3Vは内部基準電圧。

ADCされた結果を表示するために、USB経由でシリアル出力する。
すでにやっているので、それを参考に。

/* USER CODE BEGIN Includes */
#include "usbd_cdc_if.h"
/* USER CODE END Includes */
とインクルードする。

/* USER CODE BEGIN PV */
uint16_t adc_value;
float adc_voltage;
uint8_t cdc_TX_Buffer[64];
/* USER CODE END PV */
ADCの結果を格納するバッファを用意
あとは、フロート型を取り扱うので、Project Explorerのフォルダを右クリックしてProperties、>C/C++Buildを展開して、Settingを選択。Toolsettingのタブの下にあるMCU Settingsをクリックする。□Use float with printf from newlib-nano(-u _pringf_float)にチェック

while の部分に手を加えて
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_ADC_Start(&hadc1);
    if( HAL_ADC_PollForConversion(&hadc1, 1000) == HAL_OK )
    {
    adc_value = HAL_ADC_GetValue(&hadc1);
    adc_voltage = 3.3 * adc_value / 4095;
    }
    HAL_ADC_Stop(&hadc1);
    
  sprintf(cdcBuffer, "%f\n\r",adc_voltage);
  while(CDC_Transmit_FS((uint8_t*)cdcBuffer, strlen(cdcBuffer)) == USBD_OK) {}
  HAL_Delay(1000);
  }

テスト回路の構築:

今回は、テストにサーミスタを使った温度計測をする。
そのためにサーミスタを用意した。使ったものは、
10kΩのNTCサーミスタ。NTCは、Negative Temperature Coefficientの略で、温度が上昇すると抵抗値が下がるタイプ。

ブレッドボードにさせるように、リード線とジャンパーピンに半田付けしたものを用意した。

一般的なサーミスタの接続は、上の図のようになる。サーミスタに参照抵抗10kΩをGNDとの間に直列につなぎ、その間の電位をADCで読む。読み込んだ値をB係数を用いた温度換算式で計算して温度を割り出す。

 ここの参照抵抗は、温度計測を行う場合は、トリミングして目的値の誤差が小さく、温度特性の小さなものを選定する。材質がTaNとか温度係数が小さい材料で、かついったん作った抵抗を測定してから、レーザ等で加工して絶対値をあわせてからモールドする等手間をかけた抵抗で精度が高いものを利用しないと温度が正確には測定できない。

 普通のカーボン抵抗だと1000ppm/度ぐらいの温度特性を持っているし、絶対値の誤差も5%とか大きい。金属抵抗はそれでも小さい方で100ppmとか、誤差1%とかもある。温度特性が小さいものというと
とかで、±25ppm程度で絶対値の誤差が±0.05%とかきわめて小さいものを選ぶ。まあとは言え簡単な実験だったらそこいらにあるものでOK。


上の回路図の通りに回路を組む。ブレッドボードは上のようになる。スイッチは前回のがぞのままあるだけ。サーミスタの片方を+側に、適当は列に反対側をさす。抵抗の片方をGNDに接続して、もう片方をサーミスタの+じゃない方と同じ列にさす。そしてPA0と抵抗、サーミスタ両方がささっている列にジャンパーワイヤーで接続する形にした。

温度への換算:

To(℃)での抵抗値がRo(Ω)のサーミスタで、温度Tの時の抵抗値Rは、
と示される。この時のB定数の温度の単位は(K)。
先ほどのサーミスタの接続図のように接続しているとすると、ADCに入る箇所の電圧をVm(measureのm)とサーミスタ抵抗Rthと参照抵抗RrfでVrfを分圧するので
Vm=Vrf・(Rth/(Rrf+Rth))
この式をRthで整理して解くと
Rth=Vth/(Vrf-Vm)・Rrf
このRthを上の式のRに入れてTについて解く。展開する際に、まずTの逆数1/Tで解いてあげて最後にTに戻す。出てくる温度の単位が(K)なので、℃に直す場合は273.15を引く。
ってか感じで、ADCで計測された電圧から温度へ変換する事ができる。
関数化しておく。
 Private user code の箇所に以下ような関数を定義しておく。

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
float V2Temp(float Vm) {
    const float R10k=10000.0;//resistor connected in series
    const float R0=10000.0;//thermistor resistor@25 degree
    const float B=3380.0;//thermistor constant
    const float Vth_ref=3.300;//voltage to measure thermistor
    float Rth;
    Rth=R10k*Vm/(Vth_ref-Vm); //resistance of thermistor
    float invT=1/B*log(Rth/R0)+1/(273.15+25.0);
    float Ta=1/invT;
    Ta=Ta-273.15;
  // Return temperature
  return Ta;
}
/* USER CODE END 0 */

と関数を定義しておく。

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_ADC_Start(&hadc1);
    if( HAL_ADC_PollForConversion(&hadc1, 1000) == HAL_OK )
    {
    adc_value = HAL_ADC_GetValue(&hadc1);
    adc_voltage = 3.3 * adc_value / 4095;
    }
    adc_array[1]=adc_array[0] *0.8+0.2*adc_voltage;
    adc_array[0]=adc_array[1];
    HAL_ADC_Stop(&hadc1);

  sprintf(cdc_TX_Buffer, "%f\n\r",V2Temp(adc_array[1]));
  while(CDC_Transmit_FS((uint8_t*)cdc_TX_Buffer, strlen((char*)cdc_TX_Buffer)) == USBD_OK) {}
  HAL_Delay(250);
  }
というようにCDCを通じてシリアルに出力してみる。簡単な温度計測であればこれでそこそこセンサーとしては使える。









コメント

このブログの人気の投稿

Attiny85とAQM0802A(LCD)のI2C接続

Attiny85 FuseRest

HS101 STM32の自作お手軽オシロスコープ