5
char byte_to_ascii(char value_to_convert, volatile char *converted_value) {

 if (value_to_convert < 10) {
  return (value_to_convert + 48);
 } else {
  char a = value_to_convert / 10;
  double x = fmod((double)value_to_convert, 10.0);
  char b = (char)x;
  a = a + 48;
  b = b + 48;
  *converted_value = a;
  *(converted_value+1) = b;
  return 0;
 }
}

この関数の目的は、0 ~ 99 の unsigned char 値を取り、それが 0 ~ 9 の場合は同等の ascii を返すか、関数の完了後に呼び出し元のコードから参照できる小さなグローバル文字配列を操作することです。

この質問をするのは、同じベンダーの 2 つのコンパイラがこのコードを異なる方法で解釈するためです。

このコードは、RS485 経由で送信されたアドレス バイトを解析して、send-lcd-string 関数に簡単に渡すことができる文字列にする方法として書かれています。

このコードは、PIC18 アーキテクチャ (8 ビット uC) 用に書かれています。

問題は、特定のコンパイラの無料/評価版が、パフォーマンス ヒットを被りながら動作する完璧なアセンブリ コードを生成することですが、有料でおそらく優れていると思われるコンパイラは、すべてのバイト配列のアドレスを参照できるという犠牲を払って、より効率的にコードを生成することです。私のLCDディスプレイのグラフィックスを駆動するために使用されます。

典型的ではないアーキテクチャに独自のコンパイラを使用することで、多くの泥を水に入れていることは知っていますが、誰かがいくつかの提案をしてくれることを願っています.

ありがとう。

4

11 に答える 11

7

PICで浮動小数点を使用することは絶対に避けます。そして、私はどのような分割も使用しないようにします。非ASCII文字をLCDに送信するのを何回見ますか? それをLCDのメモリに保存してから、そのメモリ位置で呼び出すことはできますか?

私のコードでは 10 で割ると次のようになります。完了するまでに 17 サイクルが必要であることに注意してください。どれくらいの時間がかかるかを考え、他に何も待っていないことを確認してください。

61:                         q = d2 / 10;
 01520  90482E     mov.b [0x001c+10],0x0000
 01522  FB8000     ze 0x0000,0x0000
 01524  2000A2     mov.w #0xa,0x0004
 01526  090011     repeat #17
 01528  D88002     div.uw 0x0000,0x0004
 0152A  984F00     mov.b 0x0000,[0x001c+8]

コード内で浮動小数点を使用する場合は、コンパイル後にプログラム メモリを調べて (実際に読み取ることができるように)、[シンボリック] タブで、含める必要がある浮動小数点コードを探します。_reset ラベルの直後 (っぽい) に (コードに応じて) 上部近くに表示されます。

私は、行番号 223 でメモリ アドレス 001BC から _ floatsisf で始まり、いくつかの追加ラベル (_fpack、_divsf3 など) まで続き、最後の行 535 でメモリ アドレス 0042C の _funpack で終わります。(42C-1BC = 0x270 =) 624 バイトの失われたプログラム スペースを処理できる場合、すばらしいことですが、一部のチップには 2k のスペースしかなく、それはオプションではありません。

可能であれば、浮動小数点の代わりに、基数 2 の固定小数点演算を使用してみてください。

LCD のすべてのバイト配列を参照できない限り、null を送信しようとしていないこと (これは細かいアドレスです) を確認しましたか?アスキー文字列?(それは前に私に起こった)。

于 2009-12-04T14:19:22.447 に答える
4

モジュロ除算と整数除算は非常にコストがかかる可能性があります。私はあなたの特定のアーキテクチャについて知りませんが、そこでも高価だと思います。

除算とモジュロの両方が必要な場合は、一方を実行し、乗算/差でもう一方を取得します。

q =p/10;
r = p - q*10;
于 2009-12-04T04:46:20.937 に答える
4

私はおそらくそれを次のように書くでしょう:

char byte_to_ascii(char value_to_convert, volatile char *converted_value)
{
 if (value_to_convert < 10) {
  return value_to_convert + '0';
 } else {
  converted_value[0] = (value_to_convert / 10) + '0';
  converted_value[1] = (value_to_convert % 10) + '0';
  return 0;
 }
}
于 2009-12-04T04:32:39.980 に答える
3

% 演算子を使用する代わりに、浮動小数点数に変換し、fmod を呼び出し、整数に変換するのは不適切な形式ですか? 私はそう言うでしょう。forループでスリープするなど、タイミング要件を満たすためにプログラムを遅くするより読みやすい方法があります。コンパイラやアセンブリ コードの微調整などに関係なく、これはプログラムの実行速度を制御するための高度に難読化された方法であり、私はこれを貧弱な形式と呼んでいます。

完全なアセンブリ コードが正しく動作することを意味するが、浮動小数点への変換とその逆の変換よりもさらに遅い場合は、整数を使用して for ループでスリープします。

不完全なアセンブリ コードについては、何が問題なのですか? 「すべてのバイト配列のアドレスを参照できることを犠牲にして」? あなたのコードでは char* 型が機能しているように見えるので、C 標準で示されている方法ですべてのバイト配列をアドレス指定できるようです。どうしたの?

于 2009-12-04T04:06:54.077 に答える
2

率直に言って、私はそう言うだろう..

bを剰余にしたい場合は、MOD または独自のロールを使用します。

char a = value_to_convert / 10;
char b = value_to_convert - (10 * a);

値が実際に浮動小数点数でない限り、浮動小数点数への/からの変換は決して物事を行う方法ではありません。

さらに、データ型を明示的に 'signed' または 'unsigned' として参照するという規則に固執し、それが実際に文字 (文字列の一部) である場合に備えて裸の 'char' を残すことを強くお勧めします。生データを渡していますが、これは unsigned char である必要があると思います (もちろん、ソースunsigned であると仮定します!)。何かが署名されているか署名されていないかを忘れがちで、裸の char を使用すると、あらゆる種類のロールオーバー エラーが発生します。

ほとんどの 8 ビット マイクロは、乗算に永遠に (そして除算には永遠以上) かかるため、これらを最小限に抑えるようにしてください。

お役に立てれば..

于 2009-12-04T04:32:16.530 に答える
1

このコードは、0 ~ 9 の範囲の数値を指定するか、10 ~ 99 の範囲の数値を指定するかによって、2 つの非常に異なることを行っているようです。そのため、この関数は貧弱な形式で記述されていると言えます。関数を 2 つの関数に分割します。

于 2009-12-04T04:18:05.687 に答える
1

ここでは 10 による除算について説明しているので..

これが私の見解です。シンプルな操作だけで、広いレジスタも必要ありません。

unsigned char divide_by_10 (unsigned char value)
{
  unsigned char q;
  q = (value>>1) + (value>>2);
  q += (q>>4);
  q >>= 3;
  value -= (q<<3)+q+q;
  return q+((value+6)>>4);
}

乾杯、ニルス

于 2009-12-05T10:16:00.557 に答える
0

return余談ですが、同じ関数からの複数のステートメントは不適切な形式 (MISRA) と見なされる可能性があります。

また、上記の議論のいくつかは、パーマチュア最適化の限界に関するものです。一部のタスクはコンパイラに任せる必要があります。ただし、このような最小限の組み込み環境では、これらのトリックはまだ有効な場合があります。

于 2009-12-20T13:01:23.347 に答える
0

内部をいじると、オプティマイザーが時々望ましくないことをするのが一般的です。

あなたの convert_value はグローバルな値ですか、それともコンパイラがそれに触れないように知っているような方法で割り当てられていますか?

于 2009-12-04T03:59:36.833 に答える
0

PIC はポインター演算を嫌います。

Windows プログラマーが指摘するように、mod 演算子を使用します (以下を参照してください)。

char byte_to_ascii(char value_to_convert, volatile char *converted_value) {

 if (value_to_convert < 10) {
  return (value_to_convert + 48);
 } else {
  char a = value_to_convert / 10;  
  char b = value_TO_convert%10;
  a = a + 48;
  b = b + 48;
  *converted_value = a;
  *(converted_value+1) = b;
  return 0;
 }
}
于 2009-12-04T04:12:57.030 に答える
0

はい、私はあなたの機能を信じています:
char byte_to_ascii(char value_to_convert, volatile char *converted_value) {

if (value_to_convert < 10) {
    return (value_to_convert + 48);
} else {

char a = value_to_convert / 10;
double x = fmod((double)value_to_convert, 10.0);
char b = (char)x;
a = a + 48;
b = b + 48;
*converted_value = a;
*(converted_value+1) = b;
return 0;

}
}
is in poor form:

Don't use decimal numbers for ASCII chars, use the character, i.e. '@' instead of 0x40.
There is no need for using the fmode function.

Here is my example:
// Assuming 8-bit octet
char value;
char largeValue;
value = value_to_convert / 100;
value += '0';
converted_value[0] = value;
largeValue = value_to_convert - value * 100;
value = largeValue / 10; value += '0';
converted_value[1] = value;
largeValue = largeValue - value * 10; value += '0';
converted_value[2] = value;
converted_value[3] = '\0'; // Null terminator.

Since there are only 3 digits, I decided to unroll the loop. There are no branches to interrupt the prefetching of instructions. No floating point exceptions, just integer arithmetic.

If you leading spaces instead of zeros, you can try this:
value = (value == 0) ? ' ' : value + '0';

于 2009-12-05T21:04:13.170 に答える