AVR DDS signal generatorをArduinoに移植する

ちょっとした実験をするのにSignalGeneratorが欲しくなりました。何かお手頃なのはないかと探してみるとAVRマイコンを使った”AVR DDS signal generator”を見つけました。オープンソースで公開されていて、これを使ったキットも販売されているようです。このキットを買うのが手っ取り早いのですが、私の手元には通販で購入したArduino nanoがいくつも転がっています。型番は違うもののAVRマイコン、動作周波数も同じです。なんとかこれで動かせないものか、ということで前述の”AVR DDS signal generator”をArduino向けに移植してみました。

 

環境

  • PlatformIO IDE 1.6.0
  • Arduino nano(ATmega328p)

開発環境はArduino公式のarduino.cc IDEではなく、PlatformIOというものを使用しました。コマンドラインからもビルド&アップロード出来るので重宝しています。

platformio_ide_home.png

 また、オリジナルのファームウェアはSin値テーブル等をセクションでアドレス指定して配置していてArduino移植版も同様です。PlatformIOではplatformio.iniでビルドオプションを使って設定しています。他の環境でビルドする際には同様にセクションの指定が必要です。


build_flags = -Wl,-section-start=.MySection1=0x3A00 -Wl,-section-start=.MySection2=0x3B00 -Wl,-section-start=.MySection3=0x3C00 -Wl,-section-start=.MySection4=0x3D00 -Wl,-section-start=.MySection5=0x3E00 -Wl,-section-start=.MySection6=0x3F00

(2016/11/20追記)

上記セクションの指定なしでビルド&動作するよう修正しました。最新版では本家Arduino.cc IDEでビルドが可能です。
(やっぱり小難しい設定抜きでお手軽にビルド出来てこそのArduinoですよね)

 

SignalGeneratorの仕様

機能

本家の仕様です。Arduino移植版もこれに準じています。

  • 出力波形:正弦波、矩形波、三角波、鋸歯状波、ECG(心電図?)、ノイズ
  • 出力周波数:0~65,535Hz
  • 高速信号(マイコンI/Oから直接出力):1MHz,2MHz,4MHz,8MHz

 

ハードウェア構成

最低限の動作確認が出来る構成です。出力段のオペアンプやコネクタは省略してあります。

  • Arduino nano(ATMega328p)
  • 抵抗器(ラダー抵抗用10k,20k[ohm])
  • キャラクタLCD(16 x 2)
  • プッシュスイッチ

なお、ピンアサインは異なるものの回路構成は本家そのままです。以下のページを参照頂ければと思います。

RIMG1909.JPG

 

ピンアサイン

Arduino nanoを使った場合のピンアサインを記載します。

 

ファームウェアの修正

処理はAVR DDS SignalGenerator v2をそのまま使わせて頂いて、そこにArduino nanoで動作するように微修正を加えています。

また、Arduino移植版のソースコードは以下にアップロードしてあります。参考になればと思います。

 

ピンアサイン変更

元はATmega16向けにコーディングされているので、Arduino nanoに載っているATmega328/128向けに割り当てを変更しました。同時に外部割込み(start/stopボタン)も接続I/Oが変わるため変更しています。

 

デバッグモードの追加

I/Oに余裕が無くラダー抵抗に使うのとUART端子がかぶってしまいUARTが使用出来ません。これだとデバッグ時に不便なので、A7(AnalogInput)端子をVCC(5V)に接続すると通常モード、GNDに接続するとdebugモードとなりUARTが使えるようにしました。ただし、debugモードではラダー抵抗の下位2bitがUARTに割り当てられます。加えてdebugモードではWDTが有効になっているため一定周期で波形が乱れます。

 

信号発生処理

信号発生部の処理は速度優先のためアセンブラで記述されていました。これをATmega328向けにそのままビルドするとエラーが発生してしまいます。

ccjmtne9.s:1601: Error: number must be positive and less than 3

データシートを見てみると、SBIS命令の行がまずいようです。ATmega168/328ではSBIS命令でSPCRレジスタを参照出来ません。ここでSPCRレジスタは処理を抜けるフラグとして使っているだけなので、代わりにGPIOR0レジスタを使用するように変更しました。同様にボタン操作でフラグをセットする部分も修正しています。


void static inline Signal_OUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0)
{
 asm volatile( "eor r18, r18 ;r18<-0" "\n\t"
 "eor r19, r19 ;r19<-0" "\n\t"
 "1:" "\n\t"
 "add r18, %0 ;1 cycle" "\n\t"
 "adc r19, %1 ;1 cycle" "\n\t"
 "adc %A3, %2 ;1 cycle" "\n\t"
 "lpm ;3 cycles" "\n\t"
 "out %4, __tmp_reg__ ;1 cycle" "\n\t"
 "sbis %5, 2 ;1 cycle if no skip" "\n\t"
 "rjmp 1b ;2 cycles. Total 10 cycles" "\n\t"
 :
 :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(R2RPORT)),"I" (_SFR_IO_ADDR(GPIOR0))
 :"r18", "r19"
 );
}

 

タイマー処理

Atmega328にあわせてレジスタを修正しました。使用するタイマーは変更していません。

 

キャラクタLCD処理

オリジナルの処理をオミットして、ArduinoのキャラクタLCDライブラリ(LiquidCrystal)を使用するよう変更しました。
(多分オリジナルでも動作したと思いますが、ピン割り当ての変更が楽だったのでライブラリを使うことにしました)


//LiquidCrystal(rs, enable, d4, d5, d6, d7)
LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);

 

テーブル配置アドレスを256バイト長に揃える

(2016/11/21追記)

信号出力処理の関係で、sin波データテーブル等の配置アドレスはFlashメモリ上かつ256バイトアライメントとなっている必要があります。本家ではテーブル毎にセクション名を指定してコンパイラオプションを使ってアドレスを直接指定していました。Arduino移植版AVRDDSも当初は同じ方法を使っていました。


//define signals
const uint8_t sinewave[] __attribute__ ((section (".MySection1")))= //sine 256 values
{
0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae,


#Makefile

#Define sections where to store signal tables
LDFLAGS += -Wl,-section-start=.MySection1=0x3A00
LDFLAGS += -Wl,-section-start=.MySection2=0x3B00
LDFLAGS += -Wl,-section-start=.MySection3=0x3C00
LDFLAGS += -Wl,-section-start=.MySection4=0x3D00
LDFLAGS += -Wl,-section-start=.MySection5=0x3E00
LDFLAGS += -Wl,-section-start=.MySection6=0x3F00

しかし、これでは本家Arduino.cc IDEから気軽にビルドすることが出来ません。そこでコンパイラオプション無しで256バイト長に揃えるためにテーブル定義の箇所を以下のように修正しました。”__attribute__((aligned(256)))”は配置アドレスを256バイト長にすること、”PROGMEM”はFlashメモリ上に配置することを指定しています。


//define signals
static const uint8_t sinewave[] __attribute__((aligned(256))) PROGMEM = //sine 256 values
{
0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae,

mapファイルを確認すると、指定通りにアライメントされていることが分かります。


#ArduinoDDS.map

0x00000000 __vector_default
0x00000000 __vectors
*(.vectors)
*(.progmem.gcc*)
0x00000068 . = ALIGN (0x2)
0x00000068 __trampolines_start = .
*(.trampolines)
.trampolines 0x00000068 0x0 linker stubs
*(.trampolines*)
0x00000068 __trampolines_end = .
*(.progmem*)
*fill* 0x00000068 0x98
.progmem.data.ECG
0x00000100 0x100 .pioenvs\nanoatmega328\src\main.o
.progmem.data.trianglewave
0x00000200 0x100 .pioenvs\nanoatmega328\src\main.o
.progmem.data.rewsawtoothwave
0x00000300 0x100 .pioenvs\nanoatmega328\src\main.o
.progmem.data.sawtoothwave
0x00000400 0x100 .pioenvs\nanoatmega328\src\main.o
.progmem.data.squarewave
0x00000500 0x100 .pioenvs\nanoatmega328\src\main.o
.progmem.data.sinewave
0x00000600 0x100 .pioenvs\nanoatmega328\src\main.o
.progmem.data.RND
0x00000700 0xc .pioenvs\nanoatmega328\src\main.o
0x00000700 RND

これでラダー抵抗とボタン,LCDで回路を組めば、後はArduino.cc IDEでスケッチを読み込んでビルド/アップロードするだけで動作します。

 

動作確認

ブレッドボードで回路を組んで動作確認を行いました。今回はオリジナルにあった出力段のオペアンプはなしでラダー抵抗からの出力をオシロスコープで観測しています(動作させる際にはA7端子をVCCに接続してください)。

RIMG1908.JPG

 ブレッドボード上にラダー抵抗を配置

waveform_arduinodds_sin_100hz.png

 sin波 – 100Hz

waveform_arduinodds_sin_65535hz.png

 sin波 – 65500Hz

waveform_arduinodds_hs_8mhz.png

 高速モード – 8MHz

waveform_arduinodds_triangle_28800hz.png

 三角波 – 28800Hz

waveform_arduinodds_square_28800hz.png

 矩形波 – 28800Hz

 

所感

私の知識不足で変なところにはまったりしましたが、最終的に無事に動いてくれました。65535Hzまでの信号しか出力出来ませんが、ちょっとした実験にはぴったりです。ちょっとSignalGenerator欲しいなぁという方で手元にArduinoが余っている場合はおひとつ如何でしょうか。

 

参考

この記事は以下のサイトを参考に記載させて頂きました。

コメントを残す

メールアドレスが公開されることはありません。

This blog is kept spam free by WP-SpamFree.