電子書籍の厳選無料作品が豊富!

サインカーブを算出するために以下の質問をし、計算式を回答していただきました
http://okwave.jp/qa4751105.html


ですが、別の問題にあたってしまいました
上記回答の計算式をプログラムに書き込むと、コンパイル後の容量が跳ね上がってしまいます
同様の計算式で、x y に直接数字を指定すると容量が跳ね上がることはなさそうです

ですが、変数を指定できなければ意味がありません
なぜこのようなことが起こるのでしょうか?
また、解決方法はありますか?

#define F_CPU 800000UL

#include <avr/io.h>

#define sbi(BYTE,BIT) BYTE|=_BV(BIT)
#define cbi(BYTE,BIT) BYTE&=~_BV(BIT)

#include <math.h>

const int sin_max1 = 127,
sin_max2 = 255;


int main(void)
{

DDRA = 0b111;
DDRB = 0b11111111;
DDRD = 0b1111100;

PORTA = 0;
PORTB = 0;
cbi(PORTD, PD2);
cbi(PORTD, PD3);
cbi(PORTD, PD4);
cbi(PORTD, PD5);
cbi(PORTD, PD6);

int i = 0;

while (1)
{

sin_get(sin_max1, i);

i++;
if (i > sin_max1) i = 0;

}

}

void sin_get(const int x, const int y)
{

int z;

z = 127.5 * sin(2 * M_PI * y / x) + 127.5; // y x を直接数字に置き換えると容量は跳ね上がらない

PORTB = z;

}

「マイコン(AVR)で、ある式を記述すると」の質問画像

A 回答 (6件)

>const int sin_max1 = 127,


>sin_max2 = 255;

間違ってます。

http://okwave.jp/qa4751105.html
で寄せられた回答は
const int sin_max1 = 128,
sin_max2 = 256;
を想定していて
sin_get(sin_max1, i);
の「i」は「0~127、0~255」として呼ばれるのを前提にしています。

const int sin_max1 = 127,
sin_max2 = 255;
と定義して
sin_get(sin_max1, i);
の「i」は「0~127、0~255」として呼ばれるのを前提にするなら、計算式は
z = 127.5 * sin(2 * M_PI * y / (x + 1)) + 127.5;
にしなければなりません。

この式にすれば「sin_max1 = 127の時は、iは0~127」になりますし「sin_max1 = 255の時は、iは0~255」になります。

>コンパイル後の容量が跳ね上がってしまいます

「実数演算」と「数学関数ライブラリ」は、かなり「大規模なプログラム」と言えます。

sin関数は、テイラー展開(マクローリン展開)により、実数の階乗とべき乗に展開されます。

プログラムサイズを小さくするには「実数」から「固定小数点数」に変えた物を自作するとか、あらかじめ計算した値を配列に入れておいて使うとか、かなり工夫しないとならないでしょう。

で、自作の場合、アセンブラで書かない限り「4000バイトでは済まず、余計に肥大化する」と思われますので、素直に「元からあるsin関数を使う方が良い」でしょう。

あらかじめ計算した値を用意するなら、0度~90度までの値を以下のように65段階で用意しましょう。

unsigned char sin_table[65] = {
128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173,
176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215,
218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244,
245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255,
255
};

中身は
sin_table[0]は0度の時の値
sin_table[32]は45度の時の値
sin_table[64]は90度の時の値
になっています。

sin_get関数の中は、以下のようにします。

1.
idx = (256 * y / (x + 1)) & 255;
により、インデックス値を求める。

このidxは、xやyが幾つであっても(127だろうが255だろうが)
・0度なら0
・45度なら32
・90度なら64
・135度なら96
・180度なら128
・225度なら160
・270度なら192
・315度なら224
・360度なら0(256が1周して0になる)
になります。

2.
以下のように、4つに場合分けします。

・idxが0~64のとき
「sin_table[idx]」を求める

・idxが65~128のとき
「sin_table[128 - idx]」を求める

・idxが129~192のとき
「255 - sin_table[idx - 128]」を求める

・idxが193~255のとき
「255 - sin_table[256 - idx]」を求める

以下のようになります。
void sin_get(const int x, const int y)
{

int idx;
int z;

idx = (256 * y / (x + 1)) & 255;

if (idx < 65) z = sin_table[idx];
else if (idx < 129) z = sin_table[128 - idx];
else if (idx < 193) z = 255 - sin_table[idx - 128];
else z = 255 - sin_table[256 - idx];

PORTB = z;
}

「あらかじめ計算しておく方法」では、精度が落ちますが「どうせ、256階調しかない」ので、ラダーでDA変換して鳴らす「てきと~音」なら、これで十分でしょう。

精度を上げるには「sin_tableの要素数を増やして、0~90度を0~128の129段階にする、0~90度を0~256の257段階にする」などの必要があります。

sin_tableの要素数が増えるのは「実行バイナリのサイズ増加に直結」するので、もし「0~90度を0~4096の4097段階にする」とかやってしまうと、sin_tableが4000バイトを超える大きさになるので「元の木阿弥」です。

現状のままsin関数を使っても4000バイト増えるんですから、4000バイトを超えるsin_tableを作るのは無駄です。

この回答への補足

非常に丁寧なご回答ありがとうございます!
そこへ至る考え方など、非常に有益なものでした
早速上記計算式でコンパイルしたところ、見事に ROM 限界容量内に収まりました

ですが出力させたところ、カーブの頂点での波形がずれてしまっているようでした
山や谷に差し掛かるところで急激に反対側へ飛んでまた戻る感じです

計算式をよく診てみようと思います

補足日時:2009/02/27 14:12
    • good
    • 0
この回答へのお礼

こちらのミスでした;

非常にきれいな正弦波になりました!
ただ、現時点では数十 Hz 程度の周波数しか出せませんので、いろいろと考えたいと思います

問題解決への的確なアドバイス、大変感謝しております
本当にありがとうございました!

お礼日時:2009/02/27 14:45

こんにちは。


xとyを変数にすると、実際にSINの計算を行わなければならないので、ライブラリにSIN関数が追加され、オブジェクトサイズが増大します。
xとyを定数にすると、「sin(2 * M_PI * y / x)」は定数となりますので、定数に置き換えられ、SIN関数自体は追加されません。
#2さんの言われる、「コンパイラで面倒を見ている」状態に間違いないと思われます。
一番手っ取り早いのは、SINの結果をdoubleのテーブルにする事かと思います。
もし、それでもオブジェクトが大きくなるようでしたら、固定小数点を考えていく必要があるかと思います。

この回答への補足

GCC のコンパイラが如何に優秀か思い知らされました

あらかじめテーブルを用意する方法、何となく理解できました
精度を求めない場合、マイコンにとって非常に理にかなった方法ですね
有益な情報、ありがとうございます!

補足日時:2009/02/27 14:24
    • good
    • 0
この回答へのお礼

どなたにも点数を差し上げたかったのですが、分かりやすい説明ということで pyonmae 様へつけさせていただきました
ありがとうございました!

お礼日時:2009/02/27 14:47

そういう場合は、sinの計算結果を配列で持つのが普通です。

時間軸で256個あっても0~255の値の範囲なら256バイトで済みます。
配列は必要に応じて飛び飛びでアクセスすれば自由なサンプリング周波数を実現できます。
AVRで浮動小数点の計算は遅い&ライブラリが容量を食うので止めたほうが良いでしょう。

ちなみに配列に入れるSinの計算結果の定数は別にWindowsなどの環境で計算しておきます。

この回答への補足

非力なマイコンに直接計算させることが如何に非効率的なのか分かりました
そもそも、容量以内に収まったとしても速度の関係で周波数が極端に落ちてしまっていたと思います

アセンブラを理解しないまま C に頼ったプログラムをしていたので、このような問題にあたってしまいました

補足日時:2009/02/27 14:20
    • good
    • 0

マイコンでこのてのことをやろうと思ったら、予め整数のテーブルを計算してソースに埋め込むのが定番ですが、0~255、256個のテーブルだとしても256バイト必要です。



跳ね上がるといいますが、いったい何バイト程度違ってくるのでしょうか?

もっとも、精度が低くていいなら、容量はもっと減ります。
sinカーブを見ればわかりますが、0~90度までのデータでいいので、10個もあればそれっぽい感じを出すには十分でしょう。

この回答への補足

正確な容量としては、回答1で補足させていただいたとおりです
あらかじめ計算したものを変数へ格納しておくという方法、大変ためになりました
実際にそれほどの精度は必要ないので、十分です

この辺の計算は本当に苦手で、お恥ずかしい限りです

補足日時:2009/02/27 14:17
    • good
    • 0

そもそも浮動小数の四則演算がハードウェアでできないんだったら, ライブラリでやるしかないよね. そして, そのライブラリはダウンロードするための hex ファイルに追加しないと動かないよね. 「x や y を直接数字で書くと容量が大きくならない」というのは, コンパイラが面倒を見てる可能性があります.


解決策は.... sin の計算そのものはたぶんそんなに重くないだろうし, ひたすら「浮動小数演算を回避する」しかないような気がする. ±1 の誤差を許してくれれば固定小数演算でなんとでもなる.
    • good
    • 0
この回答へのお礼

出力するデータは音として、ラダー抵抗回路でアナログに変換したものを出力します
このため、正確な正弦波を再現する必要は無く、小数点以下まで必要ありません

ですが、当方この手の計算やプログラムに疎く、恥ずかしながらこのような問題にあたってしまいます

浮動少数演算を回避するような方法、たとえばご回答いただいた固定小数演算なるもので回避できる問題なのでしょうか?

お礼日時:2009/02/27 11:44

「跳ね上がる」がどの程度のサイズか分からないけど、


アセンブラレベルで考えれば、変数(レジストリ・スタック)のPUSH/POP等の処理もはいるから、プログラム量は直値と比べれば大きくなりますが?

この回答への補足

ご回答ありがとうございます

プログラムに変更を加えずにコンパイルすると、以下の容量になります

Program: 4334 bytes (211.6% Full)
Data: 268 bytes (209.4% Full)


該当の部分を削除すると以下の容量になります

Program: 162 bytes (7.9% Full)
Data: 4 bytes (3.1% Full)


該当部分の変数を直接適当な数字に置き換えると以下の容量になります

Program: 164 bytes (8.0% Full)
Data: 4 bytes (3.1% Full)

補足日時:2009/02/27 11:29
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!