Arduinoの良いところは、たくさんのライブラリが公開されており、ハードの面倒な仕様が隠蔽されているところだが、 DAC等を高速に駆動したい場合などはポートを直接いじる必要がある。MCP4922はMicrochipのSPI 12bit DACで、 Settling Timeが4.5µsと比較的高速で、1個数百円程度で入手可能である。ArduinoにはSPIライブラリがあるので それを使用して駆動できるが、この場合はせいぜい数10kHz程度でしか駆動できない。
8(LDAC)、10(SS)、11(MOSI)、12(MISO)、13(SCK)
#include <SPI.h> const int PIN_CS = 8; const int GAIN_1 = 0x1; const int GAIN_2 = 0x0; #define MCP4922_DAC_A 0x00 #define MCP4922_DAC_B 0x80 #define MCP4922_VREF_BUF 0x40 #define MCP4922_GAIN_1X 0x20 #define MCP4922_GAIN_2X 0x00 #define MCP4922_SHDN 0x10 void setup() { pinMode(PIN_CS, OUTPUT); SPI.begin(); SPI.setBitOrder(MSBFIRST) ; SPI.setClockDivider(SPI_CLOCK_DIV2); SPI.setDataMode(SPI_MODE0); } void setOutput(unsigned int val) { byte highByte = ((val >> 8) & 0xf) | MCP4922_GAIN_1X | MCP4922_VREF_BUF | MCP4922_SHDN; digitalWrite(PIN_CS,HIGH); digitalWrite(SS,LOW); SPI.transfer(highByte); SPI.transfer(val & 0xff); digitalWrite(SS,HIGH); digitalWrite(PIN_CS,LOW); // Latch Vout } void loop() { TIMSK0 &= ~_BV(TOIE0); // disable Timer0 overflow interrupt TIMSK2 &= ~_BV(TOIE2); // disable Timer2 overflow interrupt while(1) { setOutput(1024); setOutput(2048); } }
#include <SPI.h> #define LDAC 8 #define MCP4922_DAC_A 0x00 #define MCP4922_DAC_B 0x80 #define MCP4922_VREF_BUF 0x40 #define MCP4922_GAIN_1X 0x20 #define MCP4922_GAIN_2X 0x00 #define MCP4922_SHDN 0x10 void setup() { pinMode(LDAC,OUTPUT) ; SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setClockDivider(SPI_CLOCK_DIV2); SPI.setDataMode(SPI_MODE0); } inline void setDAC(uint16_t i){ PORTB &= ~_BV(2); // CS(10)_Low PORTB |= _BV(0); // LDAC(8) -> high SPDR = (i >> 8) | MCP4922_GAIN_1X | MCP4922_VREF_BUF | MCP4922_SHDN; while(!(SPSR & _BV(SPIF))); SPDR = i & 0xff; while(!(SPSR & _BV(SPIF))); PORTB |= _BV(2); // CS_High PORTB &= ~_BV(0); // LDAC -> low } void loop() { while(1){ setDAC(1024); setDAC(2048); } }
レジスタ直操作でも、SPSRを待機して2バイト転送するのは意外と時間がかかるので、さらに高速にするためには、 待機中に別の演算を行うのが良い。但し、下記の方法の場合、関数を3回呼ばないと始めの値がlatch outされない。 100kHzで出力可能で、125kHzでもなんとか可能であった。この方法ではそれ以上の速度は難しかったが、 ATmega328pとBU9480Fで44.1kHzのSDカードプレーヤを作った人はいる様なので、USARTを使用してXCK等を使う別の方法はあるかもしれない。
#include <TimerOne.h> #include <SPI.h> #include <math.h> #define LDAC 8 // ラッチ動作出力ピン #define MCP4922_DAC_A 0x00 #define MCP4922_DAC_B 0x80 #define MCP4922_VREF_BUF 0x40 #define MCP4922_GAIN_1X 0x20 #define MCP4922_GAIN_2X 0x00 #define MCP4922_SHDN 0x10 #define DAC_FS 10 // [us] = 100kHz uint8_t regH = 0, regL = 0; inline void latchDAC() { // Latch DAC output (It takes three call times to latch out.) PORTB &= ~_BV(0); // LDAC(8) -> Low PORTB |= _BV(0); // LDAC(8) -> High } inline void setDAC(uint16_t i, uint8_t option) { // 1. save to regH/L. // 2. clock out to SPI register. // 3. latch out. latchDAC(); // Start SPI transfer (DACA) PORTB &= ~_BV(2); // CS(10) -> Low // 1. transfer previous high register SPDR = regH; regH = (i >> 8) | option; while(!(SPSR & _BV(SPIF))); // wait SPI transfer // 2. transfer previous low register SPDR = regL; regL = i & 0xff; while(!(SPSR & _BV(SPIF))); // wait SPI transfer PORTB |= _BV(2); // CS_High } #define LENGTH_FLAT 100 volatile uint16_t flat[LENGTH_FLAT]; volatile uint8_t count; volatile uint16_t * p_flat; void updateDAC() { uint16_t t; latchDAC(); PORTB &= ~_BV(2); // CS(10) -> Low SPDR = regH; t = *p_flat; count ++; p_flat ++; regH = (t >> 8) | MCP4922_DAC_A | MCP4922_GAIN_1X | MCP4922_VREF_BUF | MCP4922_SHDN; while(!(SPSR & _BV(SPIF))); // wait SPI transfer SPDR = regL; regL = t & 0xff; if (count >= LENGTH_FLAT) { count = 0; p_flat = flat; } while(!(SPSR & _BV(SPIF))); // wait SPI transfer PORTB |= _BV(2); // CS_High } void setTable(int khz) { float _khz = khz; for(int i = 0;i < LENGTH_FLAT;i ++) { flat[i] = float2dac(sin(khz*2.f*M_PI*(float)i/(float)LENGTH_FLAT)); } } void setup() { setTable(1); pinMode(LDAC,OUTPUT) ; SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setClockDivider(SPI_CLOCK_DIV2); SPI.setDataMode(SPI_MODE0); Serial.begin(9600); Timer1.initialize(); p_flat = flat; } inline uint16_t float2dac(float v) { return (uint16_t)(1000.f*(v*2.+2.08f)); } #define MAX_CMD_STRING 16 void loop() { uint8_t cmdString[MAX_CMD_STRING]; uint8_t cmdC = 0; uint16_t cf_param = 1; TIMSK0 &= ~_BV(TOIE0); // disable Timer0 overflow interrupt TIMSK2 &= ~_BV(TOIE2); // disable Timer2 overflow interrupt // noInterrupts(); Timer1.attachInterrupt(updateDAC, 10); // background // tiny command line interpreter while (1) { if (Serial.available() > 0) { cmdString[cmdC] = Serial.read(); if (!(cmdString[cmdC] == 0x0d||cmdString[cmdC] == 0x0a)) { cmdC ++; if (MAX_CMD_STRING == cmdC) { // reject over maximum length Serial.print("\r\n> "); cmdC = 0; continue; } Serial.print((char)cmdString[cmdC-1]); // echo back only continue; } } } }
ArduFgxでは、もう少し変えて、メインの割り込み部分は下記の様なコードになっている。DACの設定に必要なビットはテーブル作成時に予め立てておく方が早い。
// Three calls are needed to latch out. // 1. save to regH/L. // 2. clock out to SPI register. // 3. latch out with timer output trigger (LDAC). void updateDAC_cont() { uint8_t regL, count_copy; uint16_t smpl; PORTB |= _BV(2); // end SPI transfer : CS(10) -> High PORTB &= ~_BV(2); // start SPI transfer: CS(10) -> Low SPDR = prev_High; // >- SPI write (1) MSB regL = prev_Low; smpl = *p_flat; // load table (do heavy task until the SPI transfer ends) prev_High = smpl >> 8; prev_Low = smpl & 0xff; count_copy = count; count_copy --; if (count_copy == 0) { count = LENGTH_FLAT, p_flat = g_flat; } else { p_flat ++; count = count_copy; } while (!(SPSR & _BV(SPIF))); // <- wait SPI transfer SPDR = regL; // >- SPI write (2) LSB // while (!(SPSR & _BV(SPIF))); // <- wait SPI transfer return; }
Rule of Open-Source Programming #5: A project is never finished. -- Shlomi Fish -- "Rules of Open Source Programming" <Quetzalcoatl_> How do I write a computer vision program in C on a microcontroller? <dyf> Quetzalcoatl_: with a text editor? <Quetzalcoatl_> Hmm.. Never thought of that. But which editor? Is Notepad good enough? <mauke> no, you need at least Wordpad <rindolf> mauke: I suggest MS Word or at least OpenOffice.org <rindolf> mauke: but in order to really be able to write well, you need a desktop publishing program like Scribus or Adobe FrameMaker. * rindolf wonders which compiler will accept PDFs as input. <waiting> rindolf: /usr/bin/pdftotext <rindolf> waiting: and pray. <rindolf> There's an estoric programming language called Piet (I think) that accepts images as input. -- How to write stylistic code -- ##programming, Freenode