PWM出力のあたりを少し改良した。
/*
* ATtinyVCO.c
*
* Created: 2015/11/25 14:07:46
* Author: gizmo
*
* PB1: PWM out
* PB2: check pin
*
* 2015.11.27 PWM出力を6bit精度に変更
*
*/
#define F_CPU 9600000ul
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <stdint.h>
#define SAMPLE_CLOCK (8000.0f)
#define POW_2_16 (65536ul)
#define FREQUENCY_MAX (2000.0f)
// Saw dawn wave up table
const PROGMEM uint8_t sawUpTable[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63
};
volatile uint16_t phaseAccumlator;
volatile uint16_t tuningWord;
volatile uint8_t amp;
//=============================================================================
// 波形生成
//
// ----------------------------------------------------------------------------
// setDDSParameter()
// parameter: frequency: 生成する周波数
//
void setDDSParameter(float frequency)
{
tuningWord = (int16_t)(frequency * POW_2_16 / SAMPLE_CLOCK);
}
// ----------------------------------------------------------------------------
// generateWave()
// return: 出力値(0..255)
//
uint8_t generateSawWave()
{
uint8_t index;
phaseAccumlator += tuningWord;
// 右へシフト: 16bit -> 6bit(64個)
index = phaseAccumlator >> 10;
return pgm_read_byte(&sawUpTable[index]);
}
//=============================================================================
// 波形生成
//
// ----------------------------------------------------------------------------
// setPWMDuty()
// parameter: value: 設定するDuty(0 .. OCR0A)
//
void setPWMDuty(uint8_t value)
{
// PWMのデューティー比を設定
OCR0B = value;
}
//=============================================================================
// ADC
//
// ----------------------------------------------------------------------------
// getCV()
// return: CV値(0..4095)
//
uint16_t getCV() {
// ADC_CVの値を取得
return 901; // 440Hz
//return 4095; // 2000Hz
}
// ----------------------------------------------------------------------------
// getGate()
// return: Gate値(0..255)
//
uint8_t getGate() {
// ADC_GATEの値を取得
return 255;
}
//=============================================================================
// ラッチ割込み
//
//ISR()
// generateSawWave()を呼び出し
// amp値を乗算
// PWM DACに出力値を設定
//=============================================================================
// メイン・ルーチン
//
int main()
{
uint16_t cv;
uint8_t v;
//-------------------------------------------------------------------------
// PORT設定
//-------------------------------------------------------------------------
// DDRB = 0;
DDRB |= (1 << DDB1); // PB1(OC0B): PWM out
// Debug用
//
DDRB |= (1 << DDB2); // PB2: output
//-------------------------------------------------------------------------
// PWM設定
//-------------------------------------------------------------------------
// TCCR0A = 0;
// TCCR0B = 0;
//-------------------------------------------------------------------------
// 波形生成モード: WGM0: 1:1:1
// 高速PWM(モード7)
TCCR0A |= (1 << WGM01) | (1 << WGM00);
TCCR0B |= (1 << WGM02);
//-------------------------------------------------------------------------
// コンペア・アウトプットB: COM0B: 1:0
// コンペア・マッチでOC0Bクリア、TOPでOC0Bセット
TCCR0A |= (1 << COM0B1) | (0 << COM0B0);
//-------------------------------------------------------------------------
// クロック設定: CS0: 0:0:1
// 分周なし
TCCR0B |= (0 << CS02) | (0 << CS01) | (1 << CS00);
// TCCR0A = 0b00100011;
// TCCR0B = 0B00001001;
//-------------------------------------------------------------------------
// 分解能 6bit(0 .. 63)
OCR0A = 64;
for (;;) {
// Debug用: PB2
PORTB |= (1 << PORTB2);
cv = getCV();
setDDSParameter(cv * FREQUENCY_MAX / 4096.0f);
amp = getGate();
v = generateSawWave();
setPWMDuty(v);
// Debug用: PB2
PORTB &= ~(1 << PORTB2);
_delay_us(125); // 8,000Hz
}
}
// EOF
AVRのTimer0はWGMをFast PWMのモード7にすると周期を可変にできるようだ。OCR0Aで周期を設定、OCR0BでDuty比を設定する。
ちゃんと確認したわけではないがモード7の場合PWM出力はOC0A(PB0)は使えなくて、OC0B(PB1)に限定されるみたいだ。
矩形波だけのファンクションジェネレーターを作るとき「
ATMega328PのTimer1でPWMを使ってみる」でFast PWMがうまく使えなかった原因はこれかな?
16bitのTimer1より8bitのTimer0の方が設定するレジスタが少なくて多少わかりやすいかも。
波形テーブルの要素数と分解能
OCR0Aを64に設定した。こうすると周期が64になってOCR0Bを0..63に設定してDuty比を設定できる。64段階なのでbit数にすると6bit。6bitのなんちゃってDACだ。
周期が64なので、9.6MHz駆動させた場合
9.6MHz / 64 = 150kHz
と、波形をのせる基本のパルス波の周波数を上げられた。
オシロで波形をみると
※オシロ画面のキャプチャーだと横幅が狭くて見づらいのでUSB経由でPCのオシロに付属していたユーティリティ・ソフトで表示させた。(このソフトあんまり使い勝手がよくないのでほとんど使っていない(^q^;)
ch1(赤)がOC0Bの出力波形、ch2(黄色)がプログラムで波形計算の前後でH/Lさせたもの
ch1は143.799kHzとなっていて粗密がノコギリ波的な感じになっている。
ch2はHiの区間が波形を計算するのに使った時間でだいたい100uSぐらいだ。本当は外部割り込み(ラッチ)でサンプリング周期を与えようと思っているが、今回は単純にメインループで順次処理している。
出力波形をオーディオインタフェース経由でPCのWaveSpectraで見てみた。
無事ノコギリ波が出力されている(^q^/
パルス波が150kHzで可聴帯域外なのでLPFをいれなくてもパルス波は除去されているようだ(オーディオインタフェースがLPF)
6bitなのでガタガタだが。
メモ:
- スレーブをいっぱい並べると駆動クロックは同期させておかないとやっぱりマズそう。スレーブごとに内部クロックで動かすと波形がずれてジッターみたいになりそう。→それはそれで面白いかもしれない(^q^;
- tuningWordを求めるときに浮動小数点演算をしているのでメモリを90%以上使ってしまっている。これを整数演算にしないと後はもう何もできない。
- 振幅も可変にできるようにしたいが、和音とかSuperSawなら別にスレーブごとに音量を変えなくてもいいかのか?う~ん。
PSoC1
Tiny85とLPC810を代替案に考えていたが、PSoC1に8pinのものがあったのを思い出した。
CY8C24123A-24PXI 240円@秋月
I2CありSPIあり
9bitDACあり14bitADCあり
いっぱいならべるとまあまあ高いが5個なら1,200円か。