如何从二进制数中获取每个数字的ascii
How to get ascii for each digit from a binary number
我得到一个 8 位二进制数,想在 LCD 屏幕上将其表示为十进制数。
因此,例如,如果我将 01000011 作为输入,即十进制的 67,我首先必须获得 6 的 8 位 ascii 码,然后是 7 的 8 位 ascii 码,并将它们发送到 LCD。
知道如何在 AVR 汇编程序中完成此操作吗?
这是来自@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
您始终可以通过 dividend - quotient * 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
某个精度所需的乘数大小看起来有点不规则,我还没有弄清楚它是如何工作的,但为了我们的目的,我们需要在 [0, 99]
由 10
,即 N / 2 / 5
。 N / 2 <= 49
,所以 n * (1100(2) + 1) / 2^6
到 n <= 63
就足够了。
因此我们可以将N / 10
转换为N / 2 * 13 >> 6
。让h = N / 2
。 h * 13
在8位溢出,但是由于>> 6
会在乘法后丢弃一些低位,所以事先做一些移位是可以的。
h * 13 >> 6
= h * 12 + h >> 6
= h * 6 + (h >> 1) >> 5
= h * 3 + (h >> 2) >> 4
因为h <= 49
,h * 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 汇编程序中,这段代码可以解决问题。
但是不要问我它是如何工作的。它是 AVR GCC 翻译我写的一个 C 函数的优化输出 reverse-engineering x86 Clang 的优化输出。所以我基本上偷了两个编译器的工作。
来自这个 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 的软件部门更快地完成这项工作 built-in。
我得到一个 8 位二进制数,想在 LCD 屏幕上将其表示为十进制数。
因此,例如,如果我将 01000011 作为输入,即十进制的 67,我首先必须获得 6 的 8 位 ascii 码,然后是 7 的 8 位 ascii 码,并将它们发送到 LCD。
知道如何在 AVR 汇编程序中完成此操作吗?
这是来自@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
您始终可以通过 dividend - quotient * 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
某个精度所需的乘数大小看起来有点不规则,我还没有弄清楚它是如何工作的,但为了我们的目的,我们需要在 [0, 99]
由 10
,即 N / 2 / 5
。 N / 2 <= 49
,所以 n * (1100(2) + 1) / 2^6
到 n <= 63
就足够了。
因此我们可以将N / 10
转换为N / 2 * 13 >> 6
。让h = N / 2
。 h * 13
在8位溢出,但是由于>> 6
会在乘法后丢弃一些低位,所以事先做一些移位是可以的。
h * 13 >> 6
= h * 12 + h >> 6
= h * 6 + (h >> 1) >> 5
= h * 3 + (h >> 2) >> 4
因为h <= 49
,h * 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 汇编程序中,这段代码可以解决问题。
但是不要问我它是如何工作的。它是 AVR GCC 翻译我写的一个 C 函数的优化输出 reverse-engineering x86 Clang 的优化输出。所以我基本上偷了两个编译器的工作。
来自这个 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 的软件部门更快地完成这项工作 built-in。