DA(ディジタルアナログ)コンバータとAD(アナログディジタル)コンバータ

20140530 coskx
T.SATOH

1.はじめに

プログラム上の変数値(正整数)に比例した電圧をMCU外部ピンに出力する機能ユニットはディジタルアナログコンバータ(DAC)と呼ばれる。例えば音声データを一定時間間隔(44kHz)でDACから出力して,増幅してスピーカにつなぐと,音声を生成することができる。
逆にセンサの出力電圧をMCU外部ピンに与えて,プログラムの変数の値に変換する機能ユニットは,アナログディジタルコンバータ(ADC)と呼ばれる。マイクロフォン,ポテンショメータ,角度センサ,ジャイロセンサなどの電圧出力を取り込むときに使われる。
STM32F303VCT6には2つの12bit-DACが用意されており,VSSA(GND=0V)とVREF+ = VDDA (VDD=3V)の間の電圧を生成する。このDACは内部的には(DAC1_OUT1, DAC1_OUT2と表記されており,これはPA4,PA5,のピンが割り当てられているが,ここではDAC1_OUT1のPA4を使うことにする。
STM32F303VCT6には4つの12bit-ADCが用意されており,これらはそれぞれ10チャンネルほどの入力を持っている。ここではADC2_IN5,から入力する。ADC2_IN5,はPC4ピンが割り当てられている。
ここでは最初にDACを取り上げ,次にADCを取り上げる。DACの動作チェックはテスタでもできる。
ADCの動作チェックは,DAC出力を取り込むことで行う。
また,複数のチャンネルのADCを使う場合は,DMAを使って,変換されたデータを配列変数に読み出すよ うにすると,その配列変数には最新のデータが入っていることになり,都合が良い。ここではADC2_IN5とADC2_IN11から入力する。ADC2_IN5は PC4ピンが,ADC2_IN11はPC5ピンが割り当てられている。(この方式は1つのチャンネルしか使わない場合も有効である。)

2.DAC


2.1 DACを使うための準備
(1)PA4ピンの設定
1)GPIOのPort Aを使うため,これにクロックを供給する。RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
2)PA4をDAC出力に使うので,モードをアナログモード(GPIO_Mode_AN)に設定,出力段はプルアップ・プルダウンしない。
(2)DACの設定
1)DACを使用するため,これにクロックを供給する。RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
2)DACの設定では,トリガなし,低インピーダンス化のためのバッファ有効を指示する。


2.2 プログラム

2.1の様に作成したプログラムは次のようになる。
DAC_OUT1 (12-bit D/A converter)のデモ

出力指令時にトリガなしでDAC出力される
DAC_OUT1 は PA4 に割り当てる(その他の選択肢はない)
STM32f0DiscoveryではVDDAにはVDD(3V)が接続されており,
VSSAにはGNDが接続されているので,DAC出力範囲は0Vから3Vとなっている。
実行時VDDAは2.93Vが観測されている。
実行するとPA4に0.05V,1.46V,2.88Vが観測される。
#include <stm32f30x.h>
#include <stdint.h>
#include "stm32f30x_conf.h"
#include "stm32f3_discovery.h"
#include <stdio.h>

void init_DAC1(void);
void outputDAC1(uint16_t outdata);

int main(void)
{
    init_DAC1();
    while (1)
    {
        volatile int i=0;
        for (i=0; i<10000000; i++);
        outputDAC1(0);
        for (i=0; i<10000000; i++);
        outputDAC1(0x800);
        for (i=0; i<10000000; i++);
        outputDAC1(0xfff);
    }
}

//DACを初期化する
//STM32F303VCT6ではDACは12bitは2CHあるがその内の1つ
void init_DAC1(void)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

    DAC_InitTypeDef DAC_InitStructure;
    DAC_StructInit(&DAC_InitStructure);
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
    DAC_Init(DAC_Channel_1, &DAC_InitStructure);
    DAC_Cmd(DAC_Channel_1, ENABLE);

}

//DACから12bitデータを出力する
//0<=outdata<=0xfff
void outputDAC1(uint16_t outdata)
{
    /* DHR registers offsets */
    #define DHR12R1_OFFSET   ((uint32_t)0x00000008)
    #define DAC1DATA_ADR      ((uint32_t)DAC_BASE + DHR12R1_OFFSET + DAC_Align_12b_R)

    *(__IO uint32_t *) DAC1DATA_ADR = outdata;
}


実測結果はつぎのようになった
DACへの値
予想出力[V]
実際の出力[V]テスタ
実際の出力[V]オシロスコープ
0x000
0.0
0.05
-0.14
0x800
1.5
1.46
1.44
0xfff
3.0
2.88
2.84


2.3 DACのループ

次のようにmain関数でDAC出力を0-0x999までループさせると三角波が出力される。

DACループ出力のmain
int main(void)
{
    init_DAC1();
    while (1)
    {
        volatile int i=0;
        for (i=0; i<0x1000; i++) {
            outputDAC1(i);
        }
    }
}

出力波形





3.ADCで逐次入力

1変換指令で1つのADCを実行し,ADCユニットから読み出せるようにする。

3.1 ADCを使うための準備
(1)GPIO PC4ピンの設定
 1)GPIOのPort Cを使うため,これにクロックを供給する。
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

 2)PC4をADC入力に使うので,モードをアナログモード
 (GPIO_Mode_AN)に設定,出力段はプルアップ・プルダウンしない。

(2)ADC2の設定
 1)ADC2を使用するため,これにクロックを供給する。
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);

 2)ADC2用クロックを設定する。
 3)ADC2のキャリブレーションを行う。
 4)ADC2の共通項目を設定する。
 5)ADC2の分解能を12bitとする。
 6)連続変換を無効設定する。(変換命令ごとに1回だけADCする。)
 7)ADC2のチャンネル5を使う設定
  ADC_RegularChannelConfig(ADC2, ADC_Channel_5 , 1, ADC_SampleTime_7Cycles5);

   第3引数は,複数チャンネル設定の場合,そのチャンネルが何番目の変換かを指示する。
   ここでは1チャンネルだけなので1番目を設定
 8)ADC2有効化。ADC_Cmd(ADC2, ENABLE);


3.2 1データの変換動作

(1)変換指令を出す。ADC_StartConversion(ADC2);
(2)変換終了(EndOfConversion)フラッグがセットされるまで待つ。
  while (ADC_GetFlagStatus(ADC2,ADC_FLAG_EOC)!=SET);
(3)ADC_GetConversionValue(ADC2)でデータが取得される。

3.3 プログラム
3.1,3.2の様に作成したプログラムは次のようになる。
ADC2_IN5 (12-bit A/D converter)のデモ
変換して欲しい時に1回変換命令を出して,結果を受け取る動作をする。

STM32f3DiscoveryはADCを2つ持ち,それぞれ10数チャンネル持っている。そのうちのADC2_IN5を利用したデモである。
ADC2_IN2はPC4から入力されるようになっている
このデモではDAC_OUT1(PA4)の出力をADC2_IN5(PC4)に入力,変換結果をsemihostingで表示する。
STM32f3DiscoveryではVDDAにはVDD(3V)が接続されており,
VSSAにはGNDが接続されているので,DAC出力範囲は0Vから3Vとなっている。
実行時VDDAは2.93Vが観測されている。
実行するとPA4に0.05V,1.47V,2.89Vが観測される。

DACに関しての初期化は2.2のままである。

#include <stm32f30x.h>
#include <stdint.h>
#include "stm32f30x_conf.h"
#include "stm32f3_discovery.h"
#include <stdio.h>

void init_ADC2_5(void);
uint16_t readADC2_5(void);
void init_DAC1(void);
void outputDAC1(uint16_t outdata);

int main(void)
{
    init_ADC2_5();
    init_DAC1();
    while (1)
    {
        volatile int i=0,k=0;
        const int DACValue[3]={0,0x800,0xfff};
        int ADCValue;
        for (k=0; k<3; k++) {
            printf("*DACValue = %03x\n",DACValue[k]);
            outputDAC1(DACValue[k]);
            for (i=0; i<100; i++);
            ADCValue=readADC2_5();
            printf(" ADCValue = %03x\n",ADCValue);
            ADCValue=readADC2_5();
            printf(" ADCValue = %03x\n",ADCValue);
            ADCValue=readADC2_5();
            printf(" ADCValue = %03x\n",ADCValue);
            ADCValue=readADC2_5();
            printf(" ADCValue = %03x\n",ADCValue);
        }
        for (i=0; i<10000000; i++);
    }
}

//ADC2_IN5を初期化する
void init_ADC2_5(void)
{
    int i;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    //XXX RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
    RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div2);

    /* Calibration procedure */
    ADC_VoltageRegulatorCmd(ADC2, ENABLE);

    /* Insert delay */
    for (i=0;i<100000;i++);

    ADC_SelectCalibrationMode(ADC2, ADC_CalibrationMode_Single);
    ADC_StartCalibration(ADC2);

    while(ADC_GetCalibrationStatus(ADC2) != RESET );
    ADC_GetCalibrationValue(ADC2);

    ADC_CommonInitTypeDef ADC_CommonInitStructure;
    ADC_CommonStructInit(&ADC_CommonInitStructure);
    ADC_CommonInit(ADC2, &ADC_CommonInitStructure);

    ADC_InitTypeDef ADC_InitStructure;
    ADC_StructInit(&ADC_InitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;  //変換命令ごとに1回だけADC
    //XXX  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
    //ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfRegChannel = 1;
    ADC_Init(ADC2, &ADC_InitStructure);
    ADC_RegularChannelConfig(ADC2, ADC_Channel_5 , 1, ADC_SampleTime_7Cycles5);

    /* Enable ADC2 */
    ADC_Cmd(ADC2, ENABLE);

    /* wait for ADRDY */
    while(!ADC_GetFlagStatus(ADC2, ADC_FLAG_RDY));

}

uint16_t readADC2_5(void)
{
    ADC_StartConversion(ADC2);
    while (ADC_GetFlagStatus(ADC2,ADC_FLAG_EOC)!=SET);
    return ADC_GetConversionValue(ADC2);
}

/*
uint16_t readADC2_5(void)
{
    int i=0;
   
ADC_StartConversion(ADC2);
    while (ADC_GetFlagStatus(ADC2,ADC_FLAG_EOC)!=SET) i++;
    printf("i=%d\n",i);
    return ADC_GetConversionValue(ADC2);
}
このようにしてもi=1しか表示されない。非常に高速に変換されている
*/

//DACを初期化する
//STM32F303VCT6ではDACは12bit1CHのみ
void init_DAC1(void)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

    DAC_InitTypeDef DAC_InitStructure;
    DAC_StructInit(&DAC_InitStructure);
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; //抵出力インピーダンス化
    DAC_Init(DAC_Channel_1, &DAC_InitStructure);
    DAC_Cmd(DAC_Channel_1, ENABLE);

}

//DACから12bitデータを出力する
//0<=outdata<=0xfff
void outputDAC1(uint16_t outdata)
{
    /* DHR registers offsets */
    #define DHR12R1_OFFSET   ((uint32_t)0x00000008)
    #define DAC1DATA_ADR      ((uint32_t)DAC_BASE + DHR12R1_OFFSET + DAC_Align_12b_R)

    *(__IO uint32_t *) DAC1DATA_ADR = outdata;
}

実行結果
*DACValue = 000
 ADCValue = 04a
 ADCValue = 04a
 ADCValue = 049
 ADCValue = 04c
*DACValue = 800
 ADCValue = 7c4
 ADCValue = 7c4
 ADCValue = 7bf
 ADCValue = 7c3
*DACValue = fff
 ADCValue = fc3
 ADCValue = fc5
 ADCValue = fc8
 ADCValue = fc5



次のような結果となった。
入力電圧[V]
予想ADC値
実際のADC値
0.05
0x044
0x04b
1.47
0x7d7
0x800
2.89
0xf69
0xfc8



3.4 DAC出力-ADC入力における線形性のチェック

3.3のmain関数を次のようにして,DACへの値kとADCからの値ADCValueの関係をグラフにした。
DAC出力0付近と0xfff付近で線形性が損なわれている。

差し替えたmain
int main(void)
{
    init_ADC2_5();
    init_DAC1();
    while (1)
    {
        volatile int i=0,k=0;
        int ADCValue;
        for (k=0; k<0x1000; k+=10) {
            outputDAC1(k);
            printf("%5d",k);
            for (i=0; i<100; i++);
            ADCValue=readADC2_5();
            printf("\t%5d\n",ADCValue);
        }
        for (i=0; i<10000000; i++);
    }
}







4.2つのADCでの逐次入力

1変換指令で2つのアナログ入力のADCを実行し,ADCユニットから読み出せるようにする。
ADC2のチャンネル5,11を使うことにする。(DM00058181.pdfで使用できるピンを探した結果)

使用できるADCのチャンネルと入力ピン(*はstm32f3discoveryが使用している。)
        ADC1    ADC2    ADC3    ADC4
  IN1   PA0*    PA4     PB1     PE14*
  IN2   PA1     PA5*    PE9*    PE15*
  IN3   PA2     PA6*    PE13*   PB12
  IN4   PA3     PA7*    ---     PB14
  IN5   PF4     PC4     PB13    PB15
  IN11          PC5


4.1 ADCを使うための準備
(1)GPIO PC4,PC4ピンの設定
 1)GPIOのPort Cを使うため,これにクロックを供給する。
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

 2)PC4,PC5をADC入力に使うので,モードをアナログモード
 (GPIO_Mode_AN)に設定,出力段はプルアップ・プルダウンしない。

(2)ADC2の設定
 1)ADC2を使用するため,これにクロックを供給する。
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);

 2)ADC用クロックを設定する。
 3)ADC2のキャリブレーションを行う。
 4)ADC2の共通項目を設定する。
 5)ADC2の分解能を12bitとする。
 6)連続変換を無効設定する。(変換命令ごとに1回だけ,指定した2つの連続ADCする。)
 7)ADC2のチャンネル5,11を使う設定
  ADC_RegularChannelConfig(ADC2, ADC_Channel_5 , 1, ADC_SampleTime_7Cycles5);

  ADC_RegularChannelConfig(ADC2, ADC_Channel_11 , 2, ADC_SampleTime_7Cycles5);
   第3引数は,複数チャンネル設定の場合,そのチャンネルが何番目の変換かを指示する。
 8)ADC2有効化。ADC_Cmd(ADC2, ENABLE);


4.2 2入力データの変換動作

(1)変換指令を出す。ADC_StartConversion(ADC2);
(2)変換終了(EndOfSequence)フラッグがセットされるまで待つ。
  while (ADC_GetFlagStatus(ADC2,ADC_FLAG_EOS)!=SET);
(3)ADC_GetConversionValue(ADC2)を2回読み込むことで2つのデータが取得される。

4.3 プログラム
3.3の様に作成したプログラムは次のようになる。
ADC2_IN5,ADC2_IN11 (12-bit A/D converter)のデモ
STM32f3DiscoveryはADCチャンネルを多く持っている。そのうちのADC2_IN5,ADC2_IN11を利用したデモである
ADC2_IN5はPC4から入力されるようになっている
ADC2_IN11はPC5から入力されるようになっている
このデモではDAC1_OUT1(PA4)の出力をADC2_IN5(PC4)に入力,変換結果をsemihostingで表示する。
STM32f3DiscoveryではVDDAにはVDD(3V)が接続されており,
VSSAにはGNDが接続されているので,DAC出力範囲は0Vから3Vとなっている。
実行時VDDAは2.93Vが観測されている。
実行するとPA4に0.05V,1.46V,2.88Vが観測される。

DACに関しての初期化は2.2のままである。

#include <stm32f30x.h>
#include <stdint.h>
#include "stm32f30x_conf.h"
#include "stm32f3_discovery.h"
#include <stdio.h>

void init_ADC2_5_11(void);
void readADC2_5_11(uint16_t *indata);
void init_DAC1(void);
void outputDAC1(uint16_t outdata);

int main(void)
{
    uint16_t converteddata[2];
    init_ADC2_5_11();
    init_DAC1();
    while (1)
    {
        volatile int i=0,k=0;
        const int DACValue[3]={0,0x800,0xfff};
        for (k=0; k<3; k++) {
            printf("*DACValue = %03x\n",DACValue[k]);
            outputDAC1(DACValue[k]);
            for (i=0; i<100; i++);
            readADC2_5_11(converteddata);
            printf(" ADCValue2_5,11 = %03x %03x\n",converteddata[0],converteddata[1]);
            for (i=0; i<100; i++);
            readADC2_5_11(converteddata);
            printf(" ADCValue2_5,11 = %03x %03x\n",converteddata[0],converteddata[1]);
            for (i=0; i<100; i++);
            readADC2_5_11(converteddata);
            printf(" ADCValue2_5,11 = %03x %03x\n",converteddata[0],converteddata[1]);
        }
        for (i=0; i<10000000; i++);
    }
}

//ADC2_IN5,IN11を初期化する
void init_ADC2_5_11(void)
{
    int i;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
    RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div2);

    /* Calibration procedure */
    ADC_VoltageRegulatorCmd(ADC2, ENABLE);

    /* Insert delay */
    for (i=0;i<100000;i++);

    ADC_SelectCalibrationMode(ADC2, ADC_CalibrationMode_Single);
    ADC_StartCalibration(ADC2);

    while(ADC_GetCalibrationStatus(ADC2) != RESET );
    ADC_GetCalibrationValue(ADC2);

    ADC_CommonInitTypeDef ADC_CommonInitStructure;
    ADC_CommonStructInit(&ADC_CommonInitStructure);
    ADC_CommonInit(ADC2, &ADC_CommonInitStructure);

    ADC_InitTypeDef ADC_InitStructure;
    ADC_StructInit(&ADC_InitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Disable;
    // ↑変換命令ごとに1回だけ2つのチャンネルの連続ADC
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfRegChannel = 2;
    ADC_Init(ADC2, &ADC_InitStructure);
    ADC_RegularChannelConfig(ADC2, ADC_Channel_5 , 1, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC2, ADC_Channel_11 , 2, ADC_SampleTime_7Cycles5);

    /* Enable ADC2 */
    ADC_Cmd(ADC2, ENABLE);

    /* wait for ADRDY */
    while(!ADC_GetFlagStatus(ADC2, ADC_FLAG_RDY));
    ADC_StartConversion(ADC2);
    while (ADC_GetFlagStatus(ADC2,ADC_FLAG_EOS)!=SET); //EOSであることに注意
    ADC_GetConversionValue(ADC2);
    ADC_GetConversionValue(ADC2);
}

void readADC2_5_11(uint16_t *indata)
{
    ADC_StartConversion(ADC2);
    while (ADC_GetFlagStatus(ADC2,ADC_FLAG_EOS)!=SET); //EOSであることに注意
    //End Of Sequence
    //この後2回続けて読み込む
    indata[0]=ADC_GetConversionValue(ADC2);
    indata[1]=ADC_GetConversionValue(ADC2);
}


//DACを初期化する
//STM32F303VCT6ではDACは12bit1CHのみ
void init_DAC1(void)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

    DAC_InitTypeDef DAC_InitStructure;
    DAC_StructInit(&DAC_InitStructure);
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; //抵出力インピーダンス化
    DAC_Init(DAC_Channel_1, &DAC_InitStructure);
    DAC_Cmd(DAC_Channel_1, ENABLE);

}

//DACから12bitデータを出力する
//0<=outdata<=0xfff
void outputDAC1(uint16_t outdata)
{
    /* DHR registers offsets */
    #define DHR12R1_OFFSET   ((uint32_t)0x00000008)
    #define DAC1DATA_ADR      ((uint32_t)DAC_BASE + DHR12R1_OFFSET + DAC_Align_12b_R)

    *(__IO uint32_t *) DAC1DATA_ADR = outdata;
}


実行結果 最初はADC2IN11にGNDを,後半は+3Vを接続してテストした
*DACValue = 000
 ADCValue2_5,11 = 04c 000
 ADCValue2_5,11 = 04c 000
 ADCValue2_5,11 = 050 000
*DACValue = 800
 ADCValue2_5,11 = 7e3 000
 ADCValue2_5,11 = 7e5 000
 ADCValue2_5,11 = 7e7 000
*DACValue = fff
 ADCValue2_5,11 = fc9 000
 ADCValue2_5,11 = fc6 000
 ADCValue2_5,11 = fc7 000
*DACValue = 000
 ADCValue2_5,11 = 04b fff
 ADCValue2_5,11 = 04b fff
 ADCValue2_5,11 = 04c fff
*DACValue = 800
 ADCValue2_5,11 = 7e5 ffd
 ADCValue2_5,11 = 7e5 ffd
 ADCValue2_5,11 = 7e7 ffd
*DACValue = fff
 ADCValue2_5,11 = fc8 ffd
 ADCValue2_5,11 = fc6 ffd
 ADCValue2_5,11 = fc7 ffd






5. DMAを用いて複数チャンネルのデータを配列変数に保存


絶えず最新ADC結果データが指定した配列内に入っているようにするのがこのプログラムの目的である。
ここではADCの2つのチャンネルからアナログデータを入力することとし,具体的にはADC2_IN5と ADC2_IN11から入力する。ADC2_IN5はPC4ピンが,ADC2_IN11はPC5ピンが割り当てられている。テストのために,DAC出力を ADC2_IN5の入力とし,ADC2_IN11の入力はVSSA(0V)またはVDDA(3V)とする。
DMAとはDirect Memory AccessのことでCPUがプログラムされた動作でデータを移動するのではなく,DMAユニットがCPU動作の隙間(むりやり隙間を作ることもある)を 使って高速にデータを移動させる機能のことである。ここではADC結果のデータを指定した配列のアドレスに移動するのに使う。また,ADCの変換が終わっ た際に,DMAを起動することにより,CPUには負担なくADC+DMAが行われ,結果として指定した配列には常に最新ADC結果データが保存されている ようになる。

DMAにはDMA1,DMA2がありそれぞれ複数のチャンネルがあるが,DMA起動要因とDMAチャンネルには対応が有り,ADC2で使えるのはDMA2チャンネル1である。
DM00043574.pdf(RM0316) Table 30. STM32F303xB/C and STM32F358xC summary of DMA2 requests for each channel

5.1 ADC,DMAを使うための準備
(1)PC4ピン,PC5ピンの設定
1)GPIOのPort Cを使うため,これにクロックを供給する。RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
2)PC4,PC5をADC入力に使うので,モードをアナログモード(GPIO_Mode_AN)に設定,出力段はプルアップ・プルダウンしない。

(2)ADC2の設定
1)ADC2を使用するため,これにクロックを供給する。RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
2)クロックを設定する。
3)ADC2のキャリブレーションを行う
4)ADCの分解能を12bitとする。
5)連続変換を有効設定する。(変換命令を1回だけ発すると後は変換し続ける。データを読まないと次の変換が始まらないが,DMAが読んでいるので連続変換となる)
6)トリガは使わない。(ADC_ExternalTrigEventEdge_None)
7)ADC2のチャンネル5を使う設定
    ADC_RegularChannelConfig(ADC2, ADC_Channel_5, 1, ADC_SampleTime_7Cycles5);
8)ADC2のチャンネル11を使う設定
    ADC_RegularChannelConfig(ADC2, ADC_Channel_11, 2, ADC_SampleTime_7Cycles5);

9)DMAリクエスト設定を連続モードにする。ADC_DMAConfig(ADC2, ADC_DMAMode_Circular);
10)ADC_DMAを有効にする。ADC_DMACmd(ADC2, ENABLE);
11)ADC1有効化。ADC_Cmd(ADC2, ENABLE);
12)ADC変換スタート。

(3)DMAの設定
1)DMAを使用するため,これにクロックを供給する。RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2 , ENABLE);
2)周辺ユニットのアドレスとしてADCの変換結果が入っているレジスタのアドレスを設定する。
 DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&(ADC2->DR);
3)メモリ上のアドレスとして,ADCデータが入るべき配列のアドレスを設定する。
 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)data;
4)DMAの方向は周辺ユニットからメモリへの方向
 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
5)DMA転送するデータの個数(バイト数ではない)
 DMA_InitStructure.DMA_BufferSize = 2;
6)1つの転送ごとに周辺ユニット側のアドレスをインクリメントさせることができるが,しない設定
 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
7)1つの転送ごとにメモリ側のアドレスをインクリメントさせることができるが,する設定
 する設定なので,配列に順にデータが保存される
 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
8)周辺ユニット側のデータサイズはハーフワード(16bit)
 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
9)メモリ側のデータサイズはハーフワード(16bit)
 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
10)DMA転送要求があれば何度でも実行するモード(最初の1回のDMA転送要求にのみ反応するモードもある)
 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
11)優先度の設定
 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
12)メモリからメモリへの転送はしない設定
 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
13)DMAを有効にする。DMA_Cmd(DMA1_Channel1, ENABLE);


5.2 プログラム

ADC2_IN5,ADC2_IN11 (12-bit A/D converter)+DMAのデモ

ADC2_IN5,ADC2_IN11を利用し,DMAで値を取り出すデモである
ADC2_IN5はPC4から入力されるようになっている
ADC2_IN11はPC5から入力されるようになっている
このデモではDAC_OUT1(PA4)の出力をADC2_IN5(PC4),ADC2_IN11(PC5)に入力,変換結果をsemihostingで表示する。
STM32f3DiscoveryではVDDAにはVDD(3V)が接続されており,
VSSAにはGNDが接続されているので,DAC出力範囲は0Vから3Vとなっている。
実行時VDDAは2.93Vが観測されている。
実行するとPA4に0.05V,1.47V,2.89Vが観測される。
#include <stm32f30x.h>
#include <stdint.h>
#include "stm32f30x_conf.h"
#include "stm32f3_discovery.h"
#include <stdio.h>

void init_DMA_forADC2_511(uint16_t *data);
void init_ADC2_511(void);
void init_DAC1(void);
void outputDAC1(uint16_t outdata);

uint16_t data511[2]={0,0};

int main()
{
    init_DAC1();
    init_DMA_forADC2_511(data511);
    init_ADC2_511();

    while (1)
    {
        volatile int i=0,k=0;
        const int DACValue[3]={0,0x800,0xfff};
        for (k=0; k<3; k++) {
            outputDAC1(DACValue[k]);
            printf("*DACValue = %03x\n",DACValue[k]);
            for (i=0; i<10; i++);
            printf(" ADC1_511 Value = %03x %03x\n",data511[0],data511[1]);
            for (i=0; i<10; i++);
            printf(" ADC1_511 Value = %03x %03x\n",data511[0],data511[1]);
            for (i=0; i<10; i++);
            printf(" ADC1_511 Value = %03x %03x\n",data511[0],data511[1]);
            for (i=0; i<10; i++);
            printf(" ADC1_511 Value = %03x %03x\n",data511[0],data511[1]);
        }
        for (i=0; i<10000000; i++);
    }
}

void init_DMA_forADC2_511(uint16_t *data)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

    /* DMA1 channel1 configuration -------------------------------------------*/

    DMA_InitTypeDef DMA_InitStructure;
    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&(ADC2->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)data;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = 2;  //データ数
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA2_Channel1, &DMA_InitStructure);

    /* Enable DMA2 Channel1 */
    DMA_Cmd(DMA2_Channel1, ENABLE);
}

//ADC_IN5,ADC_IN6を初期化する
void init_ADC2_511(void)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOC, &GPIO_InitStructure);//PC4,PC5の設定

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
    RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div2);

    /* Calibration procedure */
    ADC_VoltageRegulatorCmd(ADC2, ENABLE);

    /* Insert delay */
    int i;
    for (i=0;i<100000;i++);

    ADC_SelectCalibrationMode(ADC2, ADC_CalibrationMode_Single);
    ADC_StartCalibration(ADC2);

    while(ADC_GetCalibrationStatus(ADC2) != RESET );
    ADC_GetCalibrationValue(ADC2);

    ADC_CommonInitTypeDef ADC_CommonInitStructure;
    ADC_CommonStructInit(&ADC_CommonInitStructure);
    //ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1; // 12-bit
    ADC_CommonInitStructure.ADC_DMAMode = ADC_DMAMode_Circular; //ADC_DMAMode_Circular;
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = 10;
    ADC_CommonInit(ADC2, &ADC_CommonInitStructure);

    ADC_InitTypeDef ADC_InitStructure;
    ADC_StructInit(&ADC_InitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Enable;
    ADC_InitStructure.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_0;
    ADC_InitStructure.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_OverrunMode = ADC_OverrunMode_Disable;
    ADC_InitStructure.ADC_AutoInjMode = ADC_AutoInjec_Disable;
    ADC_InitStructure.ADC_NbrOfRegChannel = 2;
    ADC_Init(ADC2, &ADC_InitStructure);

    ADC_RegularChannelConfig(ADC2, ADC_Channel_5, 1, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC2, ADC_Channel_11, 2, ADC_SampleTime_7Cycles5);

    /* ADC DMA request*/
    ADC_DMAConfig(ADC2, ADC_DMAMode_Circular);

    /* Enable DMA for ADC2 */
    ADC_DMACmd(ADC2, ENABLE);

    /* Enable ADC2 */
    ADC_Cmd(ADC2, ENABLE);

    /* wait for ADC2 ADRDY */
    while(!ADC_GetFlagStatus(ADC2, ADC_FLAG_RDY));
    ADC_StartConversion(ADC2);
}


//DACを初期化する
//STM32F303VCT6ではDACは12bitは2CHあるがその内の1つ
void init_DAC1(void)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

    DAC_InitTypeDef DAC_InitStructure;
    DAC_StructInit(&DAC_InitStructure);
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
    DAC_Init(DAC_Channel_1, &DAC_InitStructure);
    DAC_Cmd(DAC_Channel_1, ENABLE);

}

//DACから12bitデータを出力する
//0<=outdata<=0xfff
void outputDAC1(uint16_t outdata)
{
    /* DHR registers offsets */
    #define DHR12R1_OFFSET   ((uint32_t)0x00000008)
    #define DAC1DATA_ADR      ((uint32_t)DAC_BASE + DHR12R1_OFFSET + DAC_Align_12b_R)

    *(__IO uint32_t *) DAC1DATA_ADR = outdata;
}

実行結果
ADC2_IN5にはDAC_OUT1を接続
ADC2_IN11には最初はGNDを途中からVDDを接続
*DACValue = 000
 ADC1_511 Value = 04b 000
 ADC1_511 Value = 049 000
 ADC1_511 Value = 04a 000
 ADC1_511 Value = 04a 000
*DACValue = 800
 ADC1_511 Value = 81f 000
 ADC1_511 Value = 81a 000
 ADC1_511 Value = 81b 000
 ADC1_511 Value = 820 000
*DACValue = fff
 ADC1_511 Value = fc7 000
 ADC1_511 Value = fc5 000
 ADC1_511 Value = fc5 000
 ADC1_511 Value = fc3 000
*DACValue = 000
 ADC1_511 Value = 04b fff
 ADC1_511 Value = 048 fff
 ADC1_511 Value = 04b ffd
 ADC1_511 Value = 04b ffd
*DACValue = 800
 ADC1_511 Value = 7f1 fff
 ADC1_511 Value = 7f6 ffc
 ADC1_511 Value = 7f2 ffd
 ADC1_511 Value = 7f1 fff
*DACValue = fff
 ADC1_511 Value = fce ffa
 ADC1_511 Value = fc3 fff
 ADC1_511 Value = fc5 ffd
 ADC1_511 Value = fc3 ffe