Tiva C EK-TM4C123GXLを使ってみるの11回目です。前回は、UART通信でのデバッグでしたが、今回は、ADコンバータを使ってみます。
以下のメーカのサイトを見ながらプログラムを作っていきます。
Getting Started with the TIVA™ C Series TM4C123G LaunchPad
更に、このサイトで下の方の、Workshop MaterialのThe workbookを見ながら作ります。
直接のPDFファイルへのリンクは、The workbook PDF です。
このPDFファイルのLab5のプログラムそのままです。詳細は、このPDFファイルのLab5のあたりを参照して下さい。ソースは以下からダウンロード出来ます。
lab5そのままのプロジェクト一式
出力用UARTを追加したプロジェクト一式
まずは、簡単にですが一般的なADコンバータについてです。
AD コンバータとは、アナログ信号をデジタル信号に変換する電子回路(機能)のことです。計測機器、音声、映像機器、医療機器等、アナログデータをデジタルで扱うには、必須となっていると思います。
このADコンバータで、重要な要素が、分解能とサンプリング速度です。分解能とは、ビットで表現され、アナログ(入力電圧)を何分割出来るかのことです。例えば、10ビットなら1024分割となります。サンプリング速度とは、単位はサンプル/秒(SPS)で1秒間に何回変換が可能かを表現します。例えば、1SPS は1秒間に1回、1MSPS (Mはミリオン)は1秒回に100万回ということになります。
次に今回のTiva CのADコンバータについてです。Tiva C(MCU TM4C123GH6PM)では、ADC0とADC1の2つのモジュールがあります。
分解能はいずれも12ビットで、サンプリング速度は、いずれも1MSPS (Mはミリオン)で上の例と同じで、1秒回に最大100万回の変換が可能という性能です。より詳細は、上のPDFファイルのLab5を参照して下さい。
それでは、サンプルプログラムそのままですが、以下のソースを試してみました。処理は、内蔵の温度センサの値をADCで取得して摂氏の温度と華氏の温度を計算しています。コメントは、私が書きました。参考程度に見て下さい。
一応、毎回書こうと思いますが、TivaWare付属のサンプルプログラム等を見ていると、ROM_ で始まっている関数をよく見かけますが、ROM_ の方は、ROM内蔵版です。こちらを使うとプログラム全体の大きさが小さくなりますが、ここでは特にサイズを気にする程でもないので、普通にリンクして使う、ROM_ が付いていない通常版の関数を使います。
#include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "driverlib/debug.h" #include "driverlib/sysctl.h" #include "driverlib/adc.h" int main(void) { uint32_t ui32ADC0Value[4]; //アナログデータの値 volatile uint32_t ui32TempAvg; //4つの値の平均を計算 volatile uint32_t ui32TempValueC; //温度(摂氏) volatile uint32_t ui32TempValueF; //温度(華氏) //クロックを40MHzに設定 SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ); //ADC0にクロックを供給 SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); //ADC0の構成の定義(シーケンスNoを1、プロセッサをトリガ、最優先順位) ADCSequenceConfigure(ADC0_BASE, 1, ADC_TRIGGER_PROCESSOR, 0); //内蔵温度計からのADCシーケンスを4ステップ定義 ADCSequenceStepConfigure(ADC0_BASE, 1, 0, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 1, 1, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 1, 2, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE,1,3,ADC_CTL_TS|ADC_CTL_IE|ADC_CTL_END); //シーケンス最後の設定も追加 //ADC0のシーケンス有効 ADCSequenceEnable(ADC0_BASE, 1); while(1) { //このADCシーケンスの割り込みフラグをクリア ADCIntClear(ADC0_BASE, 1); //ここからトリガにしてこのADCシーケンスを開始 ADCProcessorTrigger(ADC0_BASE, 1); //完了待ち while(!ADCIntStatus(ADC0_BASE, 1, false)) { } //データを取得して、4つの値の平均を計算 ADCSequenceDataGet(ADC0_BASE, 1, ui32ADC0Value); ui32TempAvg = (ui32ADC0Value[0] + ui32ADC0Value[1] + ui32ADC0Value[2] + ui32ADC0Value[3] + 2)/4; //温度値に変換 ui32TempValueC = (1475 - ((2475 * ui32TempAvg)) / 4096)/10; ui32TempValueF = ((ui32TempValueC * 9) + 160) / 5; } }
処理としては、ADC0を使って内蔵の温度センサから温度を計算しています。割り込みは使わないで、while()ループで毎回取得しています。ADCの使い方としては、1番というシーケンスを定義して、そのシーケンスで温度センサから4回取得するように設定しています。その4回の平均を計算しています。温度への変換ですが、データシートには詳細が書いてあるだろうということで、特に触れません。ご了承下さい。ただ、計算結果は整数でここでは浮動小数点は使っていないということだけ認識しておきます。
このまま実行すると、計算結果の出力がないので、とりあえずデバッガでブレークして確認します。デバッガでも分かるのですが、ここで前回のUARTデバッグを使ってみます。以下のようなソースになります。
#include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "driverlib/debug.h" #include "driverlib/sysctl.h" #include "driverlib/adc.h" //必要なファイルを追加 #include "driverlib/gpio.h" #include "driverlib/uart.h" #include "driverlib/pin_map.h" #include "uartstdio.h" //***************************************************************************** // // Configure the UART and its pins. This must be called before UARTprintf(). // (※サンプルのhello.cのConfigureUART()関数をそのまま利用しています。) // //***************************************************************************** void ConfigureUART(void) { // // Enable the GPIO Peripheral used by the UART. // SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); // // Enable UART0 // SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0); // // Configure GPIO Pins for UART mode. // GPIOPinConfigure(GPIO_PA0_U0RX); GPIOPinConfigure(GPIO_PA1_U0TX); GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1); // // Use the internal 16MHz oscillator as the UART clock source. // UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC); // // Initialize the UART for console I/O. // UARTStdioConfig(0, 115200, 16000000); } //***************************************************************************** // // ADCのメイン関数 // //***************************************************************************** int main(void) { uint32_t ui32ADC0Value[4]; //アナログデータの値 volatile uint32_t ui32TempAvg; //4つの値の平均を計算 volatile uint32_t ui32TempValueC; //温度(摂氏) volatile uint32_t ui32TempValueF; //温度(華氏) //クロックを40MHzに設定 SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ); //ADC0にクロックを供給 SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); //ADC0の構成の定義(シーケンスNoを1、プロセッサをトリガ、最優先順位) ADCSequenceConfigure(ADC0_BASE, 1, ADC_TRIGGER_PROCESSOR, 0); //内蔵温度計からのADCシーケンスを4ステップ定義 ADCSequenceStepConfigure(ADC0_BASE, 1, 0, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 1, 1, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE, 1, 2, ADC_CTL_TS); ADCSequenceStepConfigure(ADC0_BASE,1,3,ADC_CTL_TS|ADC_CTL_IE|ADC_CTL_END); //シーケンス最後の設定も追加 //ADC0のシーケンス有効 ADCSequenceEnable(ADC0_BASE, 1); //UARTの初期化 ConfigureUART(); while(1) { //このADCシーケンスの割り込みフラグをクリア ADCIntClear(ADC0_BASE, 1); //ここからトリガにしてこのADCシーケンスを開始 ADCProcessorTrigger(ADC0_BASE, 1); //完了待ち while(!ADCIntStatus(ADC0_BASE, 1, false)) { } //データを取得して、4つの値の平均を計算 ADCSequenceDataGet(ADC0_BASE, 1, ui32ADC0Value); ui32TempAvg = (ui32ADC0Value[0] + ui32ADC0Value[1] + ui32ADC0Value[2] + ui32ADC0Value[3] + 2)/4; //温度値に変換 ui32TempValueC = (1475 - ((2475 * ui32TempAvg)) / 4096)/10; ui32TempValueF = ((ui32TempValueC * 9) + 160) / 5; //UART出力 UARTprintf("TempValueC = %d\n", ui32TempValueC); } }
UART出力には、日本で馴染みのある摂氏だけ出力しています。これを実行してTeraTermを接続した画面が以下です。それらしい結果が確認出来ました。今回はここまでです。