今回はPICマイコンでアナログ電圧を出力する方法を紹介してみます。
デバッグ等、非常に便利に使える機能です。
DAC機能とは?
まずDAC機能についてざっくり説明してみます。
DAC機能は、Digital Analog Converter の略です。
直訳、デジタルをアナログに変換、です。
機能は言葉の通り、マイコン内部のソフトウェアの数値を、ポートからアナログ電圧情報として出力できるというものです。
マイコンにはADC機能というものもありますが、これはアナログ電圧をマイコン内部で使用できるデジタル値に変換する機能です。
DAC機能はADC機能の逆方向の機能になります。
今回作成したプログラム
まず最初にプログラム全体を示します。
プログラムの動作内容は下記です。
DACを使用し0~5Vの出力を周期的に繰り返す
(+定期的な処理のデバッグ出力)
一応、最小限の設定でPWM出力するプログラムになっていると、、、思います。
少々長いですが、ほとんどコメントです。
/* PIC16F1827 Configuration Bit Settings */
/* 'C' source line config statements */
/* CONFIG1 */
#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)
/* CONFIG2 */
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON // PLL Enable (4x PLL enabled)
#pragma config STVREN = OFF // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = ON // Low-Voltage Programming Enable (Low-voltage programming enabled)
/* #pragma config statements should precede project file includes. */
/* Use project enums instead of #define for ON and OFF. */
/*==========================================================================================================================================*/
/*
* File: main.c
* Author: ICE_MEGANE
*
* Created on 2020/03/5, 22:26
*/
#include <xc.h>
/* 共通マクロ設定 */
#define CLEAR 0U
#define SET 1U
/* 共通型名称 */
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
/* 関数プロトタイプ宣言 */
static void func_main_s_variable_init( void );
static void func_main_s_register_setup( void );
static void func_main_s_program_start( void );
static void func_main_s_loop( void );
static void func_main_s_timer4_match_int_task( void );
/* 変数宣言 */
static u8 u8_main_s_loop_go;
static u8 u8_main_s_20ms_task_flag;
static u8 u8_main_s_dacout_step;
/**************************************************************/
/* Function: */
/* main task */
/* */
/**************************************************************/
void main(void)
{
func_main_s_register_setup(); /* レジスタ初期化 */
func_main_s_variable_init(); /* 変数初期化 */
func_main_s_program_start(); /* プログラム開始設定 */
while(1)
{ /* 20ms 周期タスク */
if( u8_main_s_loop_go == SET )
{
func_main_s_loop();
u8_main_s_loop_go = CLEAR;
}
}
}
/**************************************************************/
/* Function: */
/* 割り込み処理 */
/* 割り込み発生時にここに来る */
/**************************************************************/
void __interrupt() isr( void )
{
if( (PIR3 & 0x02U) != 0 )
{ /* TMR4 一致割り込み発生 */
func_main_s_timer4_match_int_task(); /* TMR4 一致割り込み処理 */
PIR3 &= (u8)~0x02U; /* 割り込みフラグクリア */
}
}
/*******************/
/* static function */
/*******************/
/**************************************************************/
/* Function: */
/* メインループ処理 */
/* */
/**************************************************************/
static void func_main_s_loop( void )
{
u8 u8_loop_cnt;
/* テスト出力 */
/* 20ms感覚で出力反転 */
if( u8_main_s_20ms_task_flag == CLEAR )
{
u8_main_s_20ms_task_flag = SET;
PORTA |= 0x01U; /* RA0 : HI */
}
else
{
u8_main_s_20ms_task_flag = CLEAR;
PORTA &= 0x00U; /* RA0 : LOW */
}
/* DAC出力設定 */
DACCON1 = u8_main_s_dacout_step;
u8_main_s_dacout_step++;
if( u8_main_s_dacout_step >= (u8)32 )
{ /* のこぎり波で設定 */
u8_main_s_dacout_step = (u8)0;
}
}
/**************************************************************/
/* Function: */
/* 変数初期設定 */
/* */
/**************************************************************/
static void func_main_s_variable_init( void )
{
u8_main_s_loop_go = CLEAR;
u8_main_s_20ms_task_flag = CLEAR;
u8_main_s_dacout_step = CLEAR;
}
/**************************************************************/
/* Function: */
/* レジスタ初期設定 */
/* */
/**************************************************************/
static void func_main_s_register_setup( void )
{
/* クロック周波数設定 */
/*=================================================================*/
/* +------------------------- 7 : SPLLEN */ /* PLL 無効化 ※#pragmaセクションで設定したので、実際は常時許可状態(datasheet P.67) */
/* | +----------------------- 6 : IRCF<3> */ /* 1110 : 32MHz HFINTOSC に設定 */
/* | | +--------------------- 5 : IRCF<2> */
/* | | | +------------------- 4 : IRCF<1> */
/* | | | | */
/* | | | | +------------- 3 : IRCF<0> */
/* | | | | | +----------- 2 : - */
/* | | | | | | +--------- 1 : SCS<1> */ /* 00 : FOSC ※#pragmaセクションで設定している */
/* | | | | | | | + ------ 0 : SCS<0> */
OSCCON = 0x70U; /* 0 1 1 1 - 0 # 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* クロック周波数微調整 */
/*=================================================================*/
/* +------------------------- 7 : - */
/* | +----------------------- 6 : - */
/* | | +--------------------- 5 : TUN5 */
/* | | | +------------------- 4 : TUN4 */
/* | | | | */
/* | | | | +------------- 3 : TUN3 */
/* | | | | | +----------- 2 : TUN2 */
/* | | | | | | +--------- 1 : TUN1 */
/* | | | | | | | + ------ 0 : TUN0 */
OSCTUNE = 0x00U; /* 0 0 0 0 - 0 0 1 1 */
/*-----------------------------------------------------------------*/
/* memo */
/* 特に調整するつもりはないので初期設定 */
/*=================================================================*/
/* Option register setup */
/* 今回は#pragmaセクションで設定したので、設定しなくてもOK。 */
/* 割り込み設定 1 */
/*=================================================================*/
/* +------------------------- 7 : GIE */ /* 全体割り込み 許可 */
/* | +----------------------- 6 : PEIE */ /* ペリフェラル割り込み 許可 */
/* | | +--------------------- 5 : TMR0IE */ /* タイマ0割り込み 禁止 */
/* | | | +------------------- 4 : INTE */ /* 外部割込み 禁止 */
/* | | | | */
/* | | | | +------------- 3 : IOCIE */ /* 外部ピン変化割り込み 禁止 */
/* | | | | | +----------- 2 : TMR0IF */ /* タイマ0 オーバーフロー割り込み禁止 */
/* | | | | | | +--------- 1 : INTF */ /* 外部割込みフラグ クリア */
/* | | | | | | | + ------ 0 : IOCIF */ /* 外部ピン変化割り込みフラグ クリア */
INTCON = 0x40U; /* 0 1 0 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* 初期化時点では全体割り込みは無効化しておく */
/*=================================================================*/
/* 割り込み設定 2 */
/*=================================================================*/
/* +------------------------- 7 : - */
/* | +----------------------- 6 : - */
/* | | +--------------------- 5 : CCP4IE */ /* CCP4割り込み 禁止 */
/* | | | +------------------- 4 : CCP3IE */ /* CCP3割り込み 禁止 */
/* | | | | */
/* | | | | +------------- 3 : TMR6IE */ /* タイマ6 一致割り込み 禁止 */
/* | | | | | +----------- 2 : - */
/* | | | | | | +--------- 1 : TMR4IE */ /* タイマ4 一致割り込み 許可 */
/* | | | | | | | + ------ 0 : - */
PIE3 = 0x02U; /* 0 0 0 0 - 0 0 1 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* 割り込み設定 3 */
/*=================================================================*/
/* +------------------------- 7 : - */
/* | +----------------------- 6 : - */
/* | | +--------------------- 5 : CCP4IF */ /* CCP4割り込みフラグ クリア */
/* | | | +------------------- 4 : CCP3IF */ /* CCP3割り込みフラグ クリア */
/* | | | | */
/* | | | | +------------- 3 : TMR6IF */ /* タイマ6 一致割り込みフラグ クリア */
/* | | | | | +----------- 2 : - */
/* | | | | | | +--------- 1 : TMR4IF */ /* タイマ4 一致割り込みフラグ クリア */
/* | | | | | | | + ------ 0 : - */
PIR3 = 0x00U; /* 0 0 0 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* ポート入出力方向設定 */
/*=================================================================*/
/* +------------------------- 7 : TRISA7 */
/* | +----------------------- 6 : TRISA6 */
/* | | +--------------------- 5 : TRISA5 */
/* | | | +------------------- 4 : TRISA4 */
/* | | | | */
/* | | | | +------------- 3 : TRISA3 */
/* | | | | | +----------- 2 : TRISA2 */
/* | | | | | | +--------- 1 : TRISA1 */
/* | | | | | | | + ------ 0 : TRISA0 */
TRISA = 0x20U; /* 0 0 1 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* 0:出力、1:入力 */
/* RA4は入力専用ポートなので注意 */
/*=================================================================*/
/* ポート出力データ設定 */
/*=================================================================*/
/* +------------------------- 7 : RA7 */
/* | +----------------------- 6 : RA6 */
/* | | +--------------------- 5 : RA5 */
/* | | | +------------------- 4 : RA4 */
/* | | | | */
/* | | | | +------------- 3 : RA3 */
/* | | | | | +----------- 2 : RA2 */
/* | | | | | | +--------- 1 : RA1 */
/* | | | | | | | + ------ 0 : RA0 */
PORTA = 0x00U; /* 0 0 0 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* タイマ4の設定 */
/* ※一致割り込みを使用するだけなら、CCPの設定はいらない */
/* タイマレジスタ初期化 */
/*=================================================================*/
/* +------------------------- 7 : */
/* | +----------------------- 6 : */
/* | | +--------------------- 5 : */
/* | | | +------------------- 4 : */
/* | | | | */
/* | | | | +------------- 3 : */
/* | | | | | +----------- 2 : */
/* | | | | | | +--------- 1 : */
/* | | | | | | | + ------ 0 : */
TMR4 = 0xFFU; /* 0 0 0 0 - 0 0 1 1 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* タイマ4初期設定 */
/* 今回は100msの定期割り込みを発生させたい */
/*====================================================================*/
/* +------------------------- 7 : - */
/* | +----------------------- 6 : TxOUTPS<3> */ /* タイマ4 ポストスケーラ:1001 -> 1/10 */
/* | | +--------------------- 5 : TxOUTPS<2> */
/* | | | +------------------- 4 : TxOUTPS<1> */
/* | | | | */
/* | | | | +------------- 3 : TxOUTPS<0> */
/* | | | | | +----------- 2 : TMRxON */ /* タイマ4動作 停止 */
/* | | | | | | +--------- 1 : TxCKPS<1> */ /* タイマ4 プリスケーラ:11 -> 1/64 */
/* | | | | | | | + ------ 0 : TxCKPS<0> */
T4CON = 0x4BU; /* 0 1 0 0 - 1 0 1 1 */
/*--------------------------------------------------------------------*/
/* memo */
/* クロック:32MHz */
/* ペリフェラルクロック:Fosc/4 = 8MHz */
/* 割り込みポストスケーラ:1:10 -> 800kHz */
/* プリスケーラ:1/64 -> 12,500Hz */
/* 一致カウント数設定:250 -> 12,500/250 = 50Hz -> 20ms */
/*====================================================================*/
/* タイマ4 一致割り込み発生数値 */
/*=================================================================*/
/* +------------------------- 7 : */
/* | +----------------------- 6 : */
/* | | +--------------------- 5 : */
/* | | | +------------------- 4 : */
/* | | | | */
/* | | | | +------------- 3 : */
/* | | | | | +----------- 2 : */
/* | | | | | | +--------- 1 : */
/* | | | | | | | + ------ 0 : */
PR4 = 0x7DU; /* 1 1 1 1 - 1 0 1 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* 125:10ms相当に設定 */
/*=================================================================*/
/* DAC設定1 */
/*=================================================================*/
/* +------------------------- 7 : DACEN */ /* DAC機能 有効 */
/* | +----------------------- 6 : DACLPS */ /* DAC省電力 無効 */
/* | | +--------------------- 5 : DACOE */ /* DAC出力 有効 */
/* | | | +------------------- 4 : - */
/* | | | | */
/* | | | | +------------- 3 : DACPSS1 */ /* DAC HI側基準電圧:VCC 5V */
/* | | | | | +----------- 2 : DACPSS0 */
/* | | | | | | +--------- 1 : - */
/* | | | | | | | + ------ 0 : DACNSS */ /* DAC LO側基準電圧:GND */
DACCON0 = 0xA0U; /* 1 0 1 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* DAC設定2 */
/*=================================================================*/
/* +------------------------- 7 : - */
/* | +----------------------- 6 : - */
/* | | +--------------------- 5 : - */
/* | | | +------------------- 4 : DACR4 */ /* 0/32 -> 0V設定 */
/* | | | | */
/* | | | | +------------- 3 : DACR3 */
/* | | | | | +----------- 2 : DACR2 */
/* | | | | | | +--------- 1 : DACR1 */
/* | | | | | | | + ------ 0 : DACR0 */
DACCON1 = 0x00U; /* 1 0 0 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* テンプレート */
/*=========================================================*/
/* +------------------------- 7 : */
/* | +----------------------- 6 : */
/* | | +--------------------- 5 : */
/* | | | +------------------- 4 : */
/* | | | | */
/* | | | | +------------- 3 : */
/* | | | | | +----------- 2 : */
/* | | | | | | +--------- 1 : */
/* | | | | | | | + ------ 0 : */
/* 0 0 0 0 - 0 0 1 1 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
}
/**************************************************************/
/* Function: */
/* プログラム開始直前の設定 */
/* */
/**************************************************************/
static void func_main_s_program_start( void )
{
T4CON |= 0x04U; /* タイマ4 動作開始 */
INTCON |= 0x80U; /* 全体割り込み許可 */
}
/**************************************************************/
/* Function: */
/* タイマ4 一致割り込み処理 */
/* */
/**************************************************************/
static void func_main_s_timer4_match_int_task( void )
{
u8_main_s_loop_go = SET;
}
プログラムの動作結果
今回のプログラムの測定位置は下記になります。


黄色は今回作成した10msのタイマ割り込み毎の出力、
水色はDACの出力になります。
ソフト内で設定した数値をもとに、アナログ電圧として0~5Vが出力されていることが確認できます。
(レギュレータ出力が完全でない&若干出力が鈍るので0.3Vぐらいマイナスになってます)
今回はバッファ回路などを接続せずポートのDAC出力をそのままプローブ測定したため、若干出力がなまっている、振動しているよ鵜にも見えます。
マイコンのDACなので、そこまで出力能力はないです。
使用時はちゃんとバッファをつなぎましょう。
詳細設定など
細かい設定について説明していきます。
クロック周りの設定
クロック周りの設定は今回は割愛します。
PWM出力をテストした記事で説明しているため、下記参照。
ブロック図を見てざっくり機能を把握する
最初にブロック図の確認をして機能を確認します。
今回のマイコンについているDACは、一般的なラダー抵抗タイプです。

このブロック図からは下記の仕様が読み取れます。
・アナログ出力は32段階で表現できる
・出力電圧の基準点は、VDDや専用ピンVREF+の入力電圧に設定できる
・DACの出力は、出力部のFETとDACのラダー上部のFETの2つで切ることができる。
・消費電力を抑えるためにラダー抵抗の上下の電源経路を切断し、待機電流を減らすことができる。
DACLPSはぱっと見何をするためのビットなのか若干わかりにくいですが、DACENが0でDAC機能が無効となっている際、さらに消費電力を抑える際に使える模様・・・・
以降、どう意図で各ビットを設定したか、書いてみたいと思います。
DAC基準電圧 / 機能の有効無効選択 / 出力有効無効

今回は下記のように設定しました。
DACEN:1
-> DAC機能を使うため、有効化。(上下の電源供給を有効化)
DACLPS:0
-> DAC機能を使っている間は、0でも1でも影響なし。スリープするわけでもないので常時電源は生かしておく。
DACOE:0
->アナログ電圧を出すときに有効化する。いったん出力停止。
DACPSS<1:0>:00
->DACのラダー抵抗上部の電圧(最高出力電圧)は、VDD(電源入力の5V)を使用する。
DACNSS:0
->DACのラダー抵抗下部の電圧(最低出力電圧)は、VSS(GND)を使用する。
出力電圧設定

DACR<4:0>:0-0000
->いったん出力電圧は0Vに設定しておきます。
DAC出力電圧をこのレジスタで設定することになりますが、ビット設定と出力電圧の関係は下記のような式で表されます。

今回はVSRC+ = 5V、VSRC- = 0Vとしたため、出力電圧の刻み幅は0~5Vの間を32分割した156mVになります。
分子のDACRの設定値により何割の出力を出すかを決めることができます。
分解能32段階(5bit)って少なすぎんか・・・?
と思うかもしれませんが、VSRC+やVSRC-の入力をVREFにして外部で調整したりすれば、より細かい電圧での出力が可能になります。
今回は0~5Vをフルレンジとしているため、電圧の刻み幅は、訳0.15Vとなります、
(ちょっとした工作なら十分かと思うが、デバッグ用には厳しいかも?)
おまけ:ラダー抵抗のリファレンス電圧選択機能を試してみる
DACの使い方については以上で終わりですが、もう少し高度なこともできるようなブロック図になっていますので、試してみたいと思います。
おまけ:内部リファレンス電圧の設定
DACを単純に使用するのであれば以上で設定は完了ですが、関連の設定レジスタがもう1つだけあります。

FVRレジスタは、マイコン内部に搭載された基準電圧モジュールを制御できます。
マイコンでは基本的に電源電圧を基準として全体の制御を統率することが多いですが、内部に設けられたレギュレータを開始、マイコンの電源電圧の変動によらない固定電圧を使用するこ都ができます。
マイコンの電源が5Vなので、それより小さい電圧を生成する三端子レギュレータが中に入っている・・・みたいなイメージです。
FVR関連のブロック図は下記です。
(DACとはまた別のモジュールのため、ブロック図が別にあります。)

このブロック図から、1.024Vの基準電圧を、1、2,4倍して利用できることが読み取れます。
また、ないぶでは2つに分岐しており、ゲインを2つ設定してBUFFER1とBUFFER2とで、異なる電圧を使えることがわかります。
これは結構便利そう。
いまいち用途が思いつかんけど。

それぞれのビットの設定内容を説明してみます。
あとで2つ目のサンプルで設定して遊んでみますが、いったん内容だけさらっと説明してみます。
FVREN
->基準電圧生成機能の有効/無効を切り替える。
FVRRDY:
->基準電圧を利用できるかのフラグビット。読み取り専用。PIC16F1827だと1固定だそうな。。。
CDAFVR<1:0>:
->DACと内蔵コンパレータの基準電圧を選択する。出力無効か、1.024V、2.048V、4.096Vの3種類から選べる。
ADFVR<1:0>:
->ADCの基準電圧を選択する。出力無効か、1.024V、2.048V、4.096Vの3種類から選べる。
設定は以上です。
この基準電圧をどこでどのように使うか・・・ですが、もう一度DACのブロック図の上部を見てみると、下記のような記載があります。

ラダー抵抗の基準電圧をVDDではなく1.024V、2.048V、4.096Vから選んで使いたい場合は、DACPSSビットとFVRユニットのCDAFVR1を設定すればよい・・・というわけです。
おまけ2:基準電圧をFVR BUFFER2にして切り替えたらどんな感じで出力できるのか?
基準電圧をFVR BUFFER2にすると、VDDよりも小さい電圧を基準に32分割するため、より細かい出力を行うことができるとわかりました。
残念ながら負側の電圧源は、マイコン内部ではVSSしか参照できないので、徐々にオフセットを加えながら32分割して、常時高い分解能で電圧を出力・・・・・
なんてことはできません。
全体の電圧マップとしては下記のようになります。
下側だけならちょっと分解能を上げることができる、そんなところでしょう。
個人的に動いている最中に切り替えたらどんな挙動になるのか気になったので、下記のようなソースをつくって試してみました。
下側から徐々に電圧を上げていく処理です。