ADCの使い方  ----10ch電圧計----

2012.3.9  訂正   ADC clockがHSIだけだというウソを訂正しました.
2012.1.21 訂正   ADC clockが1〜4MHzに制限されているのでそれに準拠し、訂正しました.

STM8Sは10bit ADCを1つ装備しています.
入力ピンは10本あって、切り替えてあたかも10ch ADCがあるかのように使えます.

ここでは、10ch電圧計を作って、ADCの使い方を学びましょう.


どんなものを作るか?
0.1Secごとに、AIN0〜AIN9の10個のピンの電圧を測定し、UART経由で表示します.

Terminal softの表示例
------------------------------------------------------------
          AIN0 AIN1 AIN2 AIN3 AIN4 AIN5 AIN6 AIN7 AIN8 AIN9
------------------------------------------------------------
ADC(0-9)=   0  112  225  340  454  567  680  794  908 1023  ←10bit ADCの生の値
mV(0-9) =   0  360  725 1095 1463 1827 2191 2558 2926 3296  ←mVに換算した値
ADC(0-9)=   0  110  225  340  452  565  679  792  908 1023
mV(0-9) =   0  354  725 1095 1456 1820 2188 2552 2926 3296
ADC(0-9)=   0  112  225  340  454  569  680  795  909 1023
mV(0-9) =   0  360  725 1095 1463 1833 2191 2562 2929 3296
ADC(0-9)=   0  112  225  340  453  565  680  792  907 1023
mV(0-9) =   0  360  725 1095 1459 1820 2191 2552 2922 3296


あらかじめAIN0〜AIN9には抵抗分割した電圧を与えてあります.
各抵抗は1kΩでして、等電圧に分圧されているので理論的には下記のmVになっていると考えられます.
上の表示例と比較すると、おおむね1%程度の精度で測定できています.



基板写真
茶色の基板が抵抗を載せた基板.
わたしはチップ抵抗を使ったのでやけにコンパクトに収まっています.
右下のコネクタはLCDを取り付けたときのコネクタの名残なので無視してください.




ハードウエアの製作

@USBシリアル変換回路
まず、UARTをUSBに変換する回路を、STM8S-DISCOVERYにどうやって取り付けるかを説明します.
秋月のUSBシリアル変換基板をSTM8S-DISCOVERYに取り付けます.
回路図は下記です.配線は上記回路図の4本です.
●STM8SのPD5とPD6には、UARTの送信信号と受信信号が出てきますので、それをUSBシリア ル基板に配線しています.
STM8S-DISCOVERYでは、電源はFLASH焼き回路から5V or 3.3Vが供給されますが、これを切断し、USBシリアル変換基板から3.3Vが供給されるようにします.
ただし5Vじゃダメってことではありません.3.3Vにしたのはなんとなくです.
そのために、ジャンパーをつぎの1)2)のように設定 してください.
  1) USBシリアル変換基板のジャンパーピンは、下図の赤色の位置にします.
  2) STM8S-DISCOVERYのJP1のジャンパーピンは、外しておきます.

USBシリアル変換基板の取付完了状態はこのようになります.
右の黄色の囲いがUSBシリアル変換回路です.同基板に刺さっているMini-USBがPCのUSBへ行きます.
中央の水色の囲いがSTM8Sマイコンです.USBシリアル変換回路と配線されています.
左の赤い囲いは、ブロック図には描きませんでしたがSTM8Sを焼くために必須な回路です.同基板に刺さっているUSBケーブルは、STM8Sを焼くためのケーブルです.やはりPCに接続します.
この回路は、PCのUSBポートを都合2ヶ占有することになります.

プログラムを焼いてしまえば、焼き用のUSBケーブルは不要になるわけで、下図のようにUSBを外してしまいたくなるのが人情です.
ところが、USBケーブルを外しただけでは、STM8Sにリセットがかかってしまって動かなくなってしまいます.
そこで、下の写真の丸の中にあるSB1とSB2をハンダごてでOPENにしてやればリセット線が切断されるので、STM8Sが動きます.
この方法は憶えておきましょう.


AADCへ抵抗とりつけ
抵抗を取り付ける場所はCN3です.
CN3にはAIN0〜AIN9が集まっています.
抵抗は100Ω〜3300ΩぐらいならOKです.
10Ωなどはやめましょう.小さすぎて、無駄な電気を食ってしまいます.
10kΩなどはやめましょう.大きすぎてADCの入力インピーダンスに引きずられそうな予感がします.


BOPTION BYTEの書き換え
STM8Sのピンレイアウトを見ると、AIN0,AIN1,AIN3,AIN4,AIN5には、別の機能も割り当てられています.
不幸にして、別の機能がセレクトされていると、ADCとして動作してくれません.
AINと別の機能とを切り替えるのがOPTION BYTEという機能です.
OPTION BYTEを書き換えると、機能をセレクトできます.
買ってきたばかりのSTM8S-DISCOVERYはADCがセレクトされています.
OPTION BYTEの下記の箇所をケアしてください.option byteの書き換え方法はこちらのページです.

AFR6はAIN4とAIN5の機能セレクトです.

AFR5はAIN0とAIN1とAIN2とAIN3の機能セレクトです.

結果としてAFR6とAFR5がこうなっていればOKです.買ってきたばかりのSTM8S-DISCOVERYはこうなっています.




ソースコードの解説

サンプルプログラムはこちらからダウンロードできます.

以下は、main.cの中身の説明です.

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
文字列のC標準関数をいろいろ使いますので、インクルードします.

#include "stm8s.h"
STMicroのライブラリを使いますのでインクルードします.

#include "usrlib-uart.h"
ヒラサカ自作のUARTを使うための関数をインクルードします.

訂正 2012.1.21
#define fMASTER    16000000   // 16MHz
#define TIM3_FREQ  10        // 10Hz
#define TIM3_PRSC  TIM3_PRESCALER_512
#define TIM3_ARR   ((u16)(fMASTER/512/TIM3_FREQ)-1)   // 16MHz/512/10-1=3124
0.1Secのタイマ割り込みで、ADCを動かします.つまりサンプル周波数10Hzというわけです.
そのためにTIM3から0.1Sec毎に割り込みをかけてもらいます.
TIM3のプリスケーラTIM3_PSRCとカウンタmax値TIM3_ARRをdefine文で定義しました.
やりたい分周比は、4MHz/128/3125 = 10 という計算です.

char UARTstrTX[100];
UARTへ送信するための文字列バッファです.

void main(void) {
メイン関数の始まり

訂正 2012.3.9
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
外部クロックの16MHzを使います.

TIM3_DeInit();
TIM3_TimeBaseInit(TIM3_PRSC,TIM3_ARR); // 0.1sec timer
TIM3_ITConfig(TIM3_IT_UPDATE, ENABLE);
TIM3_Cmd(ENABLE);
タイマ3に0.1Secで割り込みをかけてもらいます.
TIM3_PRSC, TIM3_ARRはdefineしてあります.

ADC1_DeInit();
一応ADCを初期化してから.

訂正 2012.3.9

ADC1_Init( @ADC1_CONVERSIONMODE_CONTINUOUS, AADC1_CHANNEL_9, BADC1_PRESSEL_FCPU_D4, CADC1_EXTTRIG_TIM, DDISABLE, EADC1_ALIGN_RIGHT, FADC1_SCHMITTTRIG_ALL, GDISABLE);
ADCを初期化する引数がたくさんある関数です.
@ADCを連続変換モードにするか、一発変換モードにするかの選択.
_CONTINUOUSは連続変換、_SINGLEは一発変換.
この用途では、_CONTINUOUSにすること.
AADCの入力AINxのどれを変換するかを設定する.
_9だと10個全部._8だとAIN0〜AIN8までの9個を変換._0だとAIN0だけを変換するようになります.
変換しないピンは、普通のデジタルIOピンとして使えると思いますが、未確認です.
このページの末尾でその様子をチェックしていますので、末尾を見てください.
BADCへ供給するclock周波数を選択する.
訂正 2012.3.9
ADCのclockは1〜4MHzという制限がありますので、適切なfADC1になるように設定します.
ここでは16MHz÷4=4MHzにしています.

ちなみに、FCPUという名前でdefineされているのでfCPUを分周するかのごとく誤解してしまいますが、正しくはfMASTERを分周します.これはSTM8Sのライブラリを書いた人の誤解による誤記と思われます.
fMASTERはこのサンプルプログラムでは16MHzですから、ADC clockを8MHz〜888kHzの範囲で選択できます.
ただし、AD変換レートが8MHz〜888kHzになるわけではありません.
なぜなら、1回のAD変換に14clock必要だからです.

    ADC1_PRESSEL_FCPU_D2  Prescaler selection fADC1 = fcpu/2. 
    ADC1_PRESSEL_FCPU_D3  Prescaler selection fADC1 = fcpu/3. 
    ADC1_PRESSEL_FCPU_D4  Prescaler selection fADC1 = fcpu/4. 
    ADC1_PRESSEL_FCPU_D6  Prescaler selection fADC1 = fcpu/6. 
    ADC1_PRESSEL_FCPU_D8  Prescaler selection fADC1 = fcpu/8. 
    ADC1_PRESSEL_FCPU_D10  Prescaler selection fADC1 = fcpu/10. 
    ADC1_PRESSEL_FCPU_D12  Prescaler selection fADC1 = fcpu/12. 
    ADC1_PRESSEL_FCPU_D18  Prescaler selection fADC1 = fcpu/18. 

CDはADC変換のトリガ信号を選択する引数ですが、DがDISABLEになっているので、Cはドントケアでいいでしょう.
EADCは10bitですが、変換結果は16bitで返されます.
なので、左詰にするか右詰にするかをこの引数で選択します.
普通は_RIGHTの右詰でつかいます.
FGはAINxのシュミット特性をオンオフするために使います.
わたしの感触では、シュミット特性がオンでもオフでもAD変換動作には影響ないようです.
しかし問題は、シュミット回路にアナログ信号が入力されると消費電力が増えてしまうことだそうです.
ですから、この機能は、アナログ入力ピンのシュミット特性をオフし、デジタル入力ピンにはシュミット特性をオンしておくべきだという正しい運用をするための引数です.
場面としては、引数AでAINxをADC入力ピンにする/しないを決めますので、そのときに考慮すべきものです.
しかしこのプログラムでは、半端なやりかただとは判りつつも、FがADC1_SCHMITTTRIG_ALL GがDISABLEなので全てのAINxからシュミット特性をオフしてしまっています.
律儀にAINxごとにシュミット特性をオン/オフするには、
ADC1_SchmittTriggerConfig()関数を使います.
めんどくさいのでそこまではやってませんということです.

ADC1_ScanModeCmd(ENABLE);

AINxを一気にスキャンするモードにするおまじないです.
AINxのどこをスキャンするかは、
ADC1_Init()のAADC1_CHANNEL_xで決めます.

ADC1_DataBufferCmd(ENABLE);
AINxを一気にスキャンするモードで、スキャン結果を保存するためのレジスタをオンにするおまじないです.

UART2_DeInit();
UART2_Init((u32)115200, UART2_WORDLENGTH_9D, UART2_STOPBITS_1, UART2_PARITY_ODD, UART2_SYNCMODE_CLOCK_DISABLE, UART2_MODE_TXRX_ENABLE);
UART2_ITConfig(UART2_IT_RXNE_OR, ENABLE);
UART2_Cmd(ENABLE);
UARTを使うための設定です.おなじみなので説明は省略します.

GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_HIGH_FAST); // LED
インジケータとしてLEDをチカチカさせるために、PD0を出力ポートに設定します.

enableInterrupts();
割り込みenableです.

while(1){ } /*Main loop*/
}
永久ループを回してメインルーチンはおしまいです.






u16 adc[10]; // ADC0-9 values
ADCの10個の値を入れておく変数です.

float mV[10]; // calculated voltage
ADC値からミリボルトに換算した値を入れておく変数です.

void Tim3Update(void) interrupt 15 // 0.1Sec interrupt
{
タイマ3割り込みでここに入ってきます.
割り込みであることを示す interrupt の文字と、TIM3であることを示す 15 が記述されています.
タイマ3は0.1Secで割り込みを発生させるようにメインルーチンで設定してあります.

int i;
GPIO_WriteReverse(GPIOD,GPIO_PIN_0); // LED flash
LEDをチカチカさせます.

ADC1_StartConversion();
この関数一発で、AIN0〜AIN9までを一気にAD変換してくれます.

for(i=0;i<=9;i++)    adc[i]=ADC1_GetBufferValue(i);
レジスタ内に格納されたAIN0〜AIN9のデータを一つ一つ取ってきます.

for(i=0;i<=9;i++)    mV[i]=(float)adc[i]/1024.0*3300.0;
ミリボルトに換算します.
式の意味は、1024は10bitなので1024です.3300は、フルスケールで3.3Vだからです.
STM8SのADCはそういう仕様になっています.

sprintf(UARTstrTX,"ADC(0-9)=");
SerialPutString(UARTstrTX);
for(i=0;i<=9;i++)
{
   sprintf(UARTstrTX,"%4d ",adc[i]);
   SerialPutString(UARTstrTX);
}
SerialPutString("\r\n");
ADCデータを表示します.

sprintf(UARTstrTX,"mV(0-9) =");
SerialPutString(UARTstrTX);
for(i=0;i<=9;i++)
{
   sprintf(UARTstrTX,"%4d ",(int)mV[i]);
   SerialPutString(UARTstrTX);
}
SerialPutString("\r\n");
TIM3_ClearFlag(TIM3_FLAG_UPDATE);
ミリボルトを表示します

 return;
}
割り込みルーチンは終わりです.

以上でひとまずプログラムの説明はおしまいです.






Q:  ADC1_Init()の引数をADC1_CHANNEL_8,_7,_6,_5,_4,_3,_2,_1,_0 と変えるとどうなるか?

A: 同時変換できるch数が減ってゆく.以下はその実例です.この引数の解釈は注意しましょう.

ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_8, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL, DISABLE)

ADC(0-9)=   0  112  224  338  452  565  679  793  907    0

mV(0-9) =   0  360  721 1089 1456 1820 2188 2555 2922    0

ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_7, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL, DISABLE);

ADC(0-9)=   0  112  226  340  453  567  680  793    0    0
mV(0-9) =   0  360  728 1095 1459 1827 2191 2555    0    0

ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_6, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL, DISABLE);

ADC(0-9)=   0  112  226  339  453  565  683    0    0    0
mV(0-9) =   0  360  728 1092 1459 1820 2201    0    0    0

ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_5, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL, DISABLE);

ADC(0-9)=   0  112  226  341  454  566    0    0    0    0
mV(0-9) =   0  360  728 1098 1463 1824    0    0    0    0

ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_4, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL, DISABLE);

ADC(0-9)=   0  112  226  341  454    0    0    0    0    0

mV(0-9) =   0  360  728 1098 1463    0    0    0    0    0

ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_3, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL, DISABLE);

ADC(0-9)=   0  112  224  339    0    0    0    0    0    0
mV(0-9) =   0  360  721 1092    0    0    0    0    0    0

ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_2, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL, DISABLE);

ADC(0-9)=   0  112  225    0    0    0    0    0    0    0
mV(0-9) =   0  360  725    0    0    0    0    0    0    0

ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_1, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL, DISABLE);

ADC(0-9)=   0  112    0    0    0    0    0    0    0    0
mV(0-9) =   0  360    0    0    0    0    0    0    0    0

ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_0, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL, DISABLE);

ADC(0-9)=   1    0    0    0    0    0    0    0    0    0
mV(0-9) =   3    0    0    0    0    0    0    0    0    0

inserted by FC2 system