如何从二进制数中获取每个数字的ascii

发布于 2025-01-10 05:52:00 字数 177 浏览 0 评论 0原文

我得到一个 8 位二进制数,并希望在 LCD 屏幕上将其表示为十进制数。

例如,如果我得到 01000011 作为输入,十进制为 67,我首先必须获取 6 的 8 位 ascii 代码,然后获取 7 的 8 位 ascii 代码,并将它们发送到 LCD。

知道如何在 AVR Assembler 中完成此操作吗?

I am getting an 8 bit binary number and want to represent that as a decimal number on a LCD screen.

So for example if I get 01000011 as input, which is 67 in decimal I first have to get the 8 bit ascii code for 6, then the 8 bit ascii code for 7 and send those to the LCD.

Any idea on how this could be done in AVR Assembler?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

錯遇了你 2025-01-17 05:52:00

这是来自 @Sebastian 的算法,它计算除以 10 后的商,正确的范围是 [0, 99]

typedef unsigned char byte;

byte div10(byte x) {
    x >>= 1;
    return (byte)((byte)(x * 3) + (x >> 2)) >> 4;
}

转换为 byte 是必要的,因为 C 标准要求将任何中间结果提升为至少 16 位的 int,而这种转换会导致 8 位的低效代码位处理器,如 AVR。

GCC 翻译过来就是这个。

div10:
        mov r18,r24
        lsr r18
        mov r25,r24
        andi r25,lo8(-2)
        add r25,r18
        lsr r24
        lsr r24
        lsr r24
        add r24,r25
        swap r24
        andi r24,lo8(15)
        ret

您始终可以通过被除数 - 商 * 10 计算余数。


以下是根据@Sebastian 的评论对上述方法如何工作的解释。如果您想要数学上复杂的解释,请阅读评论,但这是我可以通过一些基本数学模糊掌握的内容。

基本上,您可以通过乘以除数的倒数来除以一个数字。 n / 3 = n * 0.33..

要计算 n / 3 的整数商,您可以使用这些源自 1 / 3 = 0.33.. 的表达式。

n * (3 + 1) / 10^1 ; n <= 4
n * (33 + 1) / 10^2 ; n <= 49
n * (333 + 1) / 10^3 ; n <= 499
n * (3333 + 1) / 10^4 ; n <= 4999
...

使用更大的乘数,您可以获得更高的精度,因此结果将是准确的以获得更大的股息。

与二进制数相同。您可以通过这些表达式计算 n / 5 的整数商,这些表达式源自 1 / 5 = 0.001100110011..(2) 的二进制小数点表达式。

n * (11(2) + 1) / 2^4 ; n <= 3
n * (110(2) + 1) / 2^5 ; n <= 13
n * (1100(2) + 1) / 2^6 ; n <= 63
n * (11001(2) + 1) / 2^7 ; n <= 63
n * (110011(2) + 1) / 2^8 ; n <= 63
n * (1100110(2) + 1) / 2^9 ; n <= 173
n * (11001100(2) + 1) / 2^10 ; n <= 1023

特定精度所需的乘数大小看起来有点不规则,我仍然没有弄清楚它是如何工作的,但出于我们的目的,我们需要在 中除以一个数字 N [0, 99] 乘以 10,即 N / 2 / 5N / 2 <= 49,因此 n * (1100(2) + 1) / 2^6 最多可达 n <= 63< /代码> 就足够了。

因此,我们可以将 N / 10 转换为 N / 2 * 13 >> 6.。让h = N / 2h * 13 溢出 8 位,但由于 >> 6 会在乘法后丢弃一些低位,可以提前进行一些移位。

h * 13 >> 6
= h * 12 + h >> 6
= h * 6 + (h >> 1) >> 5
= h * 3 + (h >> 2) >> 4

由于 h <= 49h * 3 + (h >> 2) 适合 8 位,这在我们的 C 代码中表示以前见过。

byte div10(byte x) {
    x >>= 1;
    return (byte)((byte)(x * 3) + (x >> 2)) >> 4;
}

GCC 认为不同的计算方式更好。 GCC 的汇编输出可以用 C 重写,如下所示。

byte div10(byte x) {
    return (byte)((x & 0b11111110) + (x >> 1) + (x >> 3)) >> 4;
}

/*
div10:
        mov r18,r24
        lsr r18
        mov r25,r24
        andi r25,lo8(-2)
        add r25,r18
        lsr r24
        lsr r24
        lsr r24
        add r24,r25
        swap r24
        andi r24,lo8(15)
        ret
*/

旧答案

如果您正在寻找一种算法来计算 8 位数字除以 10 时的商和余数,在 AVR 汇编器中,这段代码可以解决问题。

但不要问我它是如何工作的。这是我通过对 x86 Clang 的优化输出进行逆向工程编写的 AVR GCC 翻译 C 函数的优化输出。所以我基本上偷了两个编译器的工作。

从这个 C 代码。

#include <stdint.h>

typedef uint8_t byte;

typedef struct {
    byte _0;
    byte _1;
} bytepair;

bytepair divmod10(byte x) {
    return (bytepair){x / 10, x % 10};
}

x86 Clang 制作了这个。

imul    ecx, edi, 205
shr     ecx, 11
lea     eax, [rcx + rcx]
lea     eax, [rax + 4*rax]
sub     dil, al
movzx   eax, dil
shl     eax, 8
or      eax, ecx
ret

我将其翻译为 C,

bytepair divmod10(byte x) {
    byte y = (uint16_t)x * 205 >> 11;
    return (bytepair){y, x - y * 10};
}

然后将其放入 AVR GCC 中。

mov r20,r24
ldi r25,0
mov r19,r25
mov r18,r24
lsl r18
rol r19
lsl r18
rol r19
add r18,r24
adc r19,r25
lsl r18
rol r19
lsl r18
rol r19
lsl r18
rol r19
add r18,r24
adc r19,r25
mov r25,r19
mov r24,r18
lsl r24
rol r25
lsl r24
rol r25
add r18,r24
adc r19,r25
mov r24,r19
lsr r24
lsr r24
lsr r24
mov r25,r24
swap r25
lsl r25
andi r25,lo8(-32)
sub r25,r24
lsl r25
lsl r25
sub r25,r24
lsl r25
add r25,r20
ret

看起来AVR是一个非常简单的8位机器,甚至没有变量移位。好吧,它仍然可能比 GCC 内置的软件部门更快地完成这项工作。


This is an algorithm from @Sebastian which computes the quotient after dividing by 10, correctly in the range of [0, 99].

typedef unsigned char byte;

byte div10(byte x) {
    x >>= 1;
    return (byte)((byte)(x * 3) + (x >> 2)) >> 4;
}

The casts to byte is necessary because the C standard requires to promote any intermediate result to an int which is at least 16-bit, and such conversion results to inefficient code for 8-bit processors like AVR.

GCC translates to this.

div10:
        mov r18,r24
        lsr r18
        mov r25,r24
        andi r25,lo8(-2)
        add r25,r18
        lsr r24
        lsr r24
        lsr r24
        add r24,r25
        swap r24
        andi r24,lo8(15)
        ret

You can always calculate the remainder by dividend - quotient * 10.


Here is an explanation on how the above method works based on @Sebastian's comments. Read the comments if you'd like a mathematically sophisticated explanation, but this is what I can vaguely grasp with some basic math.

Basically, you can divide a number by multiplying the divisor's inverse. n / 3 = n * 0.33...

To calculate the integer quotient of n / 3, you can use these expressions, derived from 1 / 3 = 0.33...

n * (3 + 1) / 10^1 ; n <= 4
n * (33 + 1) / 10^2 ; n <= 49
n * (333 + 1) / 10^3 ; n <= 499
n * (3333 + 1) / 10^4 ; n <= 4999
...

With a larger multiplier, you get higher precision, so the result will be accurate for a bigger dividend.

Same with binary numbers. You can calculate the integer quotient of n / 5 by these expressions, derived from the binary point expression of 1 / 5 = 0.001100110011..(2).

n * (11(2) + 1) / 2^4 ; n <= 3
n * (110(2) + 1) / 2^5 ; n <= 13
n * (1100(2) + 1) / 2^6 ; n <= 63
n * (11001(2) + 1) / 2^7 ; n <= 63
n * (110011(2) + 1) / 2^8 ; n <= 63
n * (1100110(2) + 1) / 2^9 ; n <= 173
n * (11001100(2) + 1) / 2^10 ; n <= 1023

The required size of the multiplier for a certain precision looks kind of irregular, and I still haven't figured out how it works, but for our purpose, we need to divide a number N in [0, 99] by 10, which is N / 2 / 5. N / 2 <= 49, so n * (1100(2) + 1) / 2^6 which works up to n <= 63 suffices.

We can thus transform N / 10 to N / 2 * 13 >> 6. Let h = N / 2. h * 13 overflows in 8 bits, but since >> 6 will discard some of the lower bits after the multiplication, it's okay to do some shifts beforehand.

h * 13 >> 6
= h * 12 + h >> 6
= h * 6 + (h >> 1) >> 5
= h * 3 + (h >> 2) >> 4

Since h <= 49, h * 3 + (h >> 2) fits in 8 bits, and this is represented in the C code that we've seen before.

byte div10(byte x) {
    x >>= 1;
    return (byte)((byte)(x * 3) + (x >> 2)) >> 4;
}

GCC thinks a different way of calculation is better. The assembly output of GCC can be rewritten in C as follows.

byte div10(byte x) {
    return (byte)((x & 0b11111110) + (x >> 1) + (x >> 3)) >> 4;
}

/*
div10:
        mov r18,r24
        lsr r18
        mov r25,r24
        andi r25,lo8(-2)
        add r25,r18
        lsr r24
        lsr r24
        lsr r24
        add r24,r25
        swap r24
        andi r24,lo8(15)
        ret
*/

old answer

If you are looking for an algorithm to calculate the quotient and remainder when an 8-bit number is divided by 10, in AVR assembler, this code does the trick.

But don't ask me how it works. It is the optimized output of AVR GCC translating a C function that I wrote by reverse-engineering the optimized output of x86 Clang. So I basically stole the work of two compilers.

From this C code.

#include <stdint.h>

typedef uint8_t byte;

typedef struct {
    byte _0;
    byte _1;
} bytepair;

bytepair divmod10(byte x) {
    return (bytepair){x / 10, x % 10};
}

x86 Clang produced this.

imul    ecx, edi, 205
shr     ecx, 11
lea     eax, [rcx + rcx]
lea     eax, [rax + 4*rax]
sub     dil, al
movzx   eax, dil
shl     eax, 8
or      eax, ecx
ret

which I translated to C.

bytepair divmod10(byte x) {
    byte y = (uint16_t)x * 205 >> 11;
    return (bytepair){y, x - y * 10};
}

which then I put into AVR GCC.

mov r20,r24
ldi r25,0
mov r19,r25
mov r18,r24
lsl r18
rol r19
lsl r18
rol r19
add r18,r24
adc r19,r25
lsl r18
rol r19
lsl r18
rol r19
lsl r18
rol r19
add r18,r24
adc r19,r25
mov r25,r19
mov r24,r18
lsl r24
rol r25
lsl r24
rol r25
add r18,r24
adc r19,r25
mov r24,r19
lsr r24
lsr r24
lsr r24
mov r25,r24
swap r25
lsl r25
andi r25,lo8(-32)
sub r25,r24
lsl r25
lsl r25
sub r25,r24
lsl r25
add r25,r20
ret

It seems AVR is a very simple 8-bit machine without even variable shifts. Well, still it will do the job probably faster than GCC's software division built-in.


~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文