pp.271-278
DDSで正弦波を生成してみる。「LEDをぼわーんと光らせる」の明暗周波数を可聴周波数まで高速化するだけのことである。
左はDDS出力を直接観測した波形。pwmのパルス幅が高速に変化しているだけである。このままでもスピーカーに通せば鳴る。
右はカットオフが約1.6 kHzのローパスフィルターに通した波形。かなり整った正弦波に見える。残っているギザギザの1ギザがpwmの1周期。
#include <avr/io.h> #include <math.h> #include "mylib.h" void createSine(int8_t* tbl){ for(int i=0; i<256; i++){ tbl[i] = round(scale(sin(i * 2.0F * M_PI/256.0F), -1.0F, 1.0F, -128.0F, 127.0F)); } } void initTimer0(){ TCCR0A |= (1 << WGM00) | (1 << WGM01); // ウェーブフォームモードの設定: Fast PWMモード TCCR0A |= (1 << COM0A1); // 出力モードの設定: コンペアマッチ時にOC0A (PD6)をクリア、ボトム時にOC0A (PD6)をセット TCCR0B |= (1 << CS00); // 分周比の設定: 1分周。8 MHzをそのままカウントする。 } int main(){ int8_t fullSine[256]; createSine(fullSine); // 正弦波1周期分のテーブル。 uint16_t counter16bit = 0; uint16_t waveStep16bit = 880; // 正弦波テーブルの要素数が2^16個だと假定して何個おきに飛ばすか。これで音程が決まる。 uint8_t waveStep; uint8_t pwmValue; initTimer0(); PORTD |= (1 << PD2); // スイッチを接続する端子を内部プルアップする。 while(1){ if(bit_is_clear(PIND, PD2)){ // スイッチが押されていたら、 DDRD |= (1 << PD6); // スピーカーへのIOをOUTにして音が出るようにする。 counter16bit += waveStep16bit; // 要素数が2^16個であると假定して何個かおきに飛ばしてカウントアップし、 waveStep = counter16bit >> 8; // 2^16個と假定していたカウンターを実際の256個に減らし、 pwmValue = fullSine[waveStep]; // 実際の正弦波テーブルから値を読み出す。 loop_until_bit_is_set(TIFR0, TOV0); // タイマー0がオーバーフローする(Timer/Counter0 Overflow Flagがセットされる)のを待ってから、 OCR0A = 128 + pwmValue; // コンペア値を更新して、(※128を足しているのは-128~127を0~255にするため) TIFR0 |= (1 << TOV0); // 再度Timer/Counter0 Overflow Flagを手動でクリアする。 } else{ // スイッチが押されていなかったら、 DDRD &= ~(1 << PD6); // スピーカーへのIOをINにして音が出ないようにする。 } } return 0; }