我正在为一个只有几个字节RAM的小型8位微控制器编写代码。它有一个简单的工作,即传输7个16位字,然后传输这些字的CRC。在编译时选择单词的值。 CRC具体是“剩余部分 字0到字6为无符号数除以多项式x ^ 8 +x²+ x + 1(初始值0xFF)。“
是否可以使用 C 预处理器在编译时计算这些字节的CRC?
#define CALC_CRC(a,b,c,d,e,f,g) /* what goes here? */
#define W0 0x6301
#define W1 0x12AF
#define W2 0x7753
#define W3 0x0007
#define W4 0x0007
#define W5 0x5621
#define W6 0x5422
#define CRC CALC_CRC(W0, W1, W2, W3, W4, W5, W6)
答案 0 :(得分:2)
可以设计一个在编译时执行CRC计算的宏。像
// Choosing names to be short and hopefully unique. #define cZX((n),b,v) (((n) & (1 << b)) ? v : 0) #define cZY((n),b, w,x,y,z) (cZX((n),b,w)^CzX((n),b+1,x)^CzX((n),b+2,y)^cZX((n),b+3,z)) #define CRC(n) (cZY((n),0,cZ0,cZ1,cZ2,cZ3)^cZY((n),4,cZ4,cZ5,cZ6,cZ7))之类的东西 应该可以正常工作,并且如果可以将
(n)
评估为编译时常量,那么效率会非常高效;它只会简单地评估一个常数。另一方面,如果n
是一个表达式,那么该表达式将最终重新计算八次。即使n
是一个简单的变量,结果代码也可能比最快的非基于表的编写方式大得多,并且可能比最紧凑的编写方式慢。< / p> BTW,我真的希望在C和C ++标准中看到的一件事是指定过载的方法,只有当特定参数可以作为编译时常量计算时才会用于内联声明的函数。语义将是这样的,即没有“保证”任何这样的重载将在编译器可能能够确定值的每种情况下使用,但是将保证(1)不会使用这样的重载在任何情况下,必须在运行时评估“compile-time-const”参数,以及(2)在一个这样的重载中被认为是常量的任何参数将被视为从它调用的任何函数中的常量。在很多情况下,如果函数的参数是常量,那么函数可以编写为评估编译时常量,但运行时评估绝对可怕。例如:
#define bit_reverse_byte(n) ( (((n) & 128)>>7)|(((n) & 64)>>5)|(((n) & 32)>>3)|(((n) & 16)>>1)| (((n) & 8)<<1)|(((n) & 4)<<3)|(((n) & 2)<<5)|(((n) & 1)<<7) ) #define bit_reverse_word(n) (bit_reverse_byte((n) >> 8) | (bit_reverse_byte(n) << 8))
PIC上C中非循环单字节位反转功能的简单渲染将是大约17-19个单周期指令;一个字位反转将是34,或大约10加一个字节反转函数(将执行两次)。最佳汇编代码约为15个字节反转的单周期指令或17个字反转指令。对某些字节变量bit_reverse_byte(b)
计算b
将需要许多指令,总共需要数十个周期。计算bit_reverse_word(
w ) for some 16-bit word
w`可能需要数百条指令才能执行数百或数千个周期。如果可以使用类似上面的公式来标记要扩展内联的函数,它将扩展到总共四条指令(基本上只是加载结果),但在内联方案中使用函数调用,这将是非常好的。扩张将是令人发指的。
答案 1 :(得分:1)
最简单的校验和算法是所谓的纵向奇偶校验,它将数据分成具有固定数量n位的“字”,然后计算所有这些字的异或。结果作为额外的单词附加到消息中。
为了检查消息的完整性,接收者计算其所有单词或其所有单词,包括校验和;如果结果不是带有n个零的字,则接收方知道发生了传输错误。
(来源:维基)
在你的例子中:
#define CALC_LRC(a,b,c,d,e,f) ((a)^(b)^(c)^(d)^(e)^(f))
答案 2 :(得分:0)
免责声明:这不是一个直接的答案,而是一系列问题和建议,这些问题和建议对于评论来说太长了。
第一个问题:您是否可以控制协议的两端,例如您可以通过自己或同事控制另一端的代码来选择校验和算法吗?
如果问题#1为“是”:
您需要评估为什么需要校验和,哪些校验和是合适的,以及接收具有有效校验和的损坏消息的后果(这会影响到什么和为什么)。
您的传输媒体,协议,比特率等是什么?你期待/观察比特错误吗?因此,例如,在同一块电路板上从一个芯片到另一个芯片的SPI或I2C,如果您有位错误,可能是硬件工程师故障或您需要降低时钟速率,或两者兼而有之。校验和不会受到伤害,但实际上并不是必需的。另一方面,在嘈杂的环境中使用红外信号,您将有更高的错误概率。
错误信息的后果始终是最重要的问题。因此,如果您正在为数字室内温度计编写控制器并发送消息以每秒更新显示器10x,那么1000个消息中的一个坏消息几乎没有任何真正的危害。没有校验和或弱校验和应该没问题。
如果这6个字节发射导弹,设置机器人手术刀的位置,或导致货币转移,你最好确定你有正确的校验和,甚至可能想要查看加密哈希(可能需要比你更多的RAM。
对于中间产品,明显不利于产品的性能/满意度,但没有真正的伤害,它是你的电话。例如,偶尔改变音量而不是频道的电视可能会让客户感到厌烦 - 更多的是如果一个好的CRC检测到错误,只需丢弃命令,但如果你正在做便宜的事情/如果能够更快地将产品推向市场,那么可能没问题的电视就可以了。
那么你需要什么校验和?
如果其中一端或两端都有内置校验和的硬件支持(例如在SPI中相当常见),那么这可能是明智的选择。然后它或多或少地自由计算。
正如vulkanino的回答所建议的那样,LRC是最简单的算法。
如果您真的需要CRC,维基百科有关于如何/为何选择多项式的一些不错的信息: http://en.wikipedia.org/wiki/Cyclic_redundancy_check
如果对问题#1不回答:
另一端需要什么CRC算法/多项式?这就是你所坚持的,但告诉我们可能会给你一个更好/更完整的答案。
关于实施的想法:
大多数算法在RAM /寄存器方面非常轻量级,只需要几个额外的字节。通常,函数将产生更好,更清晰,更易读,更易于调试的代码。
您应该将宏观解决方案视为一种优化技巧,并且像所有优化技巧一样,早期跳转到它们可能会浪费开发时间并导致更多问题而不是它的价值。
使用宏也有一些您可能尚未考虑的奇怪含义:
您知道预处理器只能在编译时修复消息中的所有字节才能进行计算,对吧?如果你有一个变量,编译器必须生成代码。如果没有函数,每次使用时都会内联该代码(是的,这可能意味着大量的ROM使用)。如果所有字节都是可变的,那么该代码可能比在C中编写函数更糟糕。或者使用一个好的编译器,它可能会更好。很难说肯定。另一方面,如果根据发送的消息,不同的字节数是可变的,您最终可能会得到几个版本的代码,每个版本都针对该特定用法进行了优化。