[嵌入式软件入门案例]crc的代码实现理解与推导
什么是CRC
Wiki: 循环冗余校验(英语:Cyclic redundancy check,通称“CRC”),主要用来检测或校验数据传输或者保存后可能出现的错误。生成的数字在传输或者存储之前计算出来并且附加到数据后面,然后接收方进行检验确定数据是否发生变化。
个人本科时期对嵌入式掌握不好,通过这个推导和理解,感受到对位操作的理解大大加深,现在认为做嵌入式是在这儿入了门。个人认为非常适合做嵌入式软件编程的入门实战。
MS5611气压计的crc4校验案例及其解说
static u8 MS5611PromCrc(u16 Prom[]) // QB: it means each member has 2bytes(16bits).
{
s16 i; // simple counter
u16 n_rem = 0; // crc reminder. QB: 0x0000 at first
u16 Prom7 = Prom[7]; // original value of the crc
u8 n_bit;
Prom[7] = (0xFF00 & (Prom[7])); //CRC byte is replaced by 0 QB: P[n] is saved as each member with 2bytes
for (i = 0; i < 16; i++) // operation is performed on bytes. QB: Prom[] is divided into 16 parts.
// QB: u16(2 bytes) to u8(1 byte & 16 parts) indeed.
{ // choose LSB or MSB QB: MSB: Most Significant Bit
if (i%2==1) // QB: division operation.
n_rem ^= (u16) ((Prom[i>>1]) & 0x00FF);
else
n_rem ^= (u16) (Prom[i>>1]>>8);
for (n_bit = 8; n_bit > 0; n_bit--) // QB: CRC processing. Totally, it mod 0b10011.
{
if (n_rem & (0x8000)) // Sign: The highest bit is 1
{
n_rem = (n_rem << 1) ^ 0x3000; // 0b0011... So the relationship between 0x3000 & hand calculator?
}
else
{
n_rem = (n_rem << 1);
}
}
}
n_rem= (0x000F & (n_rem >> 12)); // qb: final 4-bit reminder is CRC code
Prom[7] = Prom7; // restore the crc_read to its original place
return (n_rem ^ 0x00); // QB: though it's saved as 1 byte, valid data is only 0.5 byte.
Prom[]是一个有8个元素,每个元素为2字节的消息数组,Prom[7]的低八位是主机发送的CRC校验码,所以需要先屏蔽置零。
首先考虑Prom[i>>1]操作。这一操作是i/2并作截尾近似,所以i=0,1时,表达式为Prom[0];…;i=14,15时,表达式为Prom[7]。这种方式完成了i的16次循环与Prom数组8个元素的映射联系。
接着考虑条件结构。i从0增加到15,当i是奇数时,取i关联的Prom元素的低8位,再与n_rem异或;当i是偶数时,取i关联的Prom元素的高8位并移动至低8位位置,再与n_rem异或。此处的n_rem不断更新,n_rem与之前处理过的数据具有计算来的相关性,体现出了之前数据的特征,并且该n_rem的特点是:高8位为特征值,第8位全是0,原因见下一段。而Prom不管取高8位还是低8位都会被移动到低8位去,而高8位都是0。由于0与二进制a异或的结果还是a,所以这里的异或,其实就是n_rem高8位与Prom低8位的非全0部分进行拼接。
完成了体现以前数据特征的n_rem与当前一个字节(无论是原Prom的高8还是低8)的拼接,接下来对n_rem进行运算。也就是说,将新的特征与旧的特征一同运算,如此计算出的新特征,就体现了从开始到当前字节的特征。如果n_rem的最高位不是1,那么左移1位。如果n_rem最高位是1,那么左移一位后的新n_rem,与0x3000进行异或,计算机的操作是:新n_rem的左数第3、4位翻转,其他不变。当然,我们不需要局限于计算机的处理手段中,而应该抽象为数学的理解:这8次循环完成的是以0b10011为除数的模2除法运算,关于模2除法和这种数学理解的内容,在下一章节介绍。
较为特别的是,由于Prom[7]的低8位是主机传来的校验码,所以在最开始,这个字节就设成了0。所以i=15时,当前要处理字节全是0,所以n_rem与该字节进行拼接,获得的还是i=15时内循环完成后的n_rem,即没有变化,低字节位全是0。这与此前拼接后的n_rem不同:此前的n_rem的高字节和低字节都非0(除非安排的数据很巧,算出来就是0)。将该n_rem与0b10011进行模2除法,就得到了内外循环全部完成后的n_rem,它的低12位全是0,只有高4位有意义。
最后,对n_rem进行提取。将有意义的高4位右移12位,移动到低4位上,再通过与0x00异或,提取出一个字节的数字(尽管该字节内高4位一定是0),那么此时这个字节就是本机接收数据后计算出的crc校验码。当然,也不要忘了,把原本发送来的crc校验码进行还原。
在本程序之外,调用本程序,能够计算出这段数据的crc校验码,再与主机发送来的crc校验码进行比较,如果不相同,那么从谨慎的角度出发,就应当认为传输过程可能发生了错误,要求主机重新发送消息。如果相同,就认为通讯正确,数据传输成功。(实际上,此处校验码只有4位,要想提升鲁棒性,扩展校验码有效位数是一个方法)
下面我们来考虑模2除法——这个提取出数据列特征的算法——讲解完成后,再来进行最终总结。
模2除法
百度百科:模2除法,从字面上可理解为二进制下的除法。模2除法与算术除法类似,但每一位除的结果不影响其它位,即不向上一位借位,所以实际上就是异或。在CRC运算中,总能保证除数的首位为1,则模2除法运算的商是由余数首位与除数首位的模2除法运算结果确定。因为除数首位总是1,按照模2除法运算法则,那么余数首位是1就商1,是0就商0。百科链接:https://baike.baidu.com/item/%E6%A8%A12%E9%99%A4%E6%B3%95/10416971
还可以参考:https://blog.csdn.net/weixin_39450145/article/details/83987836
而CRC的原理和计算,这个文章写的很好:https://zhuanlan.zhihu.com/p/348823629。不赘述(其实是懒)。
这里还可以再讲一讲,下次有时间再补上。
总结
推导完还挺有感受的,随便写的,懒得打字了,现在有空再总结下。