精华内容
下载资源
问答
  • copy_to_user和copy_from_user就是在进行驱动相关程序设计的时候,要经常遇到的两个函数。由于内核空间与用户空间的内存不能直接互访,因此借助函数...首先,我们来看一下这两个函数的在源码文件是如何定义的: ~/ar
      copy_to_user和copy_from_user就是在进行驱动相关程序设计的时候,要经常遇到的两个函数。由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_to_user()完成用户空间到内核空间的复制,函数copy_from_user()完成内核空间到用户空间的复制。下面我们来仔细的理一下这两个函数的来龙去脉。

    首先,我们来看一下这两个函数的在源码文件中是如何定义的:

    ~/arch/i386/lib/usercopy.c

    unsigned long

    copy_to_user(void __user *to, constvoid *from, unsigned long n)

    {

    might_sleep();

    BUG_ON((long) n < 0);

    if (access_ok(VERIFY_WRITE, to, n))

    n = __copy_to_user(to, from, n);

    return n;

    }

    EXPORT_SYMBOL(copy_to_user);

     

    这个函数的主要作用就是从内核空间拷贝一块儿数据到用户空间,由于这个函数有可能睡眠,所以只能用于用户空间。

     

    ①三个参数:

    to 目标地址,这个地址是用户空间的地址;

    from 源地址,这个地址是内核空间的地址;

    n 将要拷贝的数据的字节数。

    如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。

    参数to的时候有个__user限定,这个在~/include/linux/compiler.h中有如下定义:

    # define __user __attribute__((noderef,address_space(1)))

    表示这是一个用户空间的地址,即其指向的为用户空间的内存

    大家可能对这个__attribute__感到比较迷惑,不过没关系,google一下嘛

    __attribute__是gnuc编译器的一个功能,它用来让开发者使用此功能给所声明的函数或者变量附加一个属性,以方便编译器进行错误检查,其实就是一个内核检查器。

    具体可以参考如下:

    http://unixwiz.net/techtips/gnu-c-attributes.html

     

    以上是对函数的一些说明,接下来让我们看看这个函数的内部面目:

    ②might_sleep():

    它有两个实现版本,debug版本和非debug版本:

    在debug版本中,在有可能引起sleep的函数中会给出相应的提示,如果是在原子的上下文中执行,则会打印出栈跟踪的信息,这是通过__might_sleep(__FILE__,__LINE__);函数来实现的,并且接着调用might_resched()函数进行重新调度。

    在非debug版本中直接调用might_resched()函数进行重新调度。

    其实现方式为,在~/ include/linux/kernel.h中:

    #ifdef CONFIG_DEBUG_SPINLOCK_SLEEP

    void __might_sleep(char *file, intline);

    # define might_sleep() /

    do { __might_sleep(__FILE__, __LINE__);might_resched(); } while (0)

    #else

    # define might_sleep() do {might_resched(); } while (0)

    #endif

     

    ③检查参数合法性的宏:BUG_ON((long) n < 0);

    其实现为如下(在~/include/asm-generic/bug.h):

    它通过检查条件,根据结果来决定是否打印相应的提示信息;

    #ifdef CONFIG_BUG

    #ifndef HAVE_ARCH_BUG

    #define BUG() do { /

    printk("BUG: failure at %s:%d/%s()!/n",__FILE__, __LINE__, __FUNCTION__); /

    panic("BUG!"); /

    } while (0)

    #endif

    #ifndef HAVE_ARCH_BUG_ON

    #define BUG_ON(condition) do { if(unlikely((condition)!=0)) BUG(); } while(0)

    #endif

     

    ④access_ok(VERIFY_WRITE, to, n)

    它是用来检查参数中一个指向用户空间数据块的指针是否有效,如果有效返回非零,否则返回零。其实现如下(在/include/asm-i386/uaccess.h中):

    #define access_ok(type,addr,size)(likely(__range_ok(addr,size) == 0))

    其中__range_ok(addr,size)的实现是通过内嵌汇编来实现的,内容如下(在/include/asm-i386/uaccess.h中):

    #define __range_ok(addr,size) ({ /

    unsigned long flag,sum; /

    __chk_user_ptr(addr); /

    asm("addl %3,%1 ; sbbl %0,%0; cmpl%1,%4; sbbl $0,%0" /

    :"=&r" (flag), "=r" (sum) /

    :"1" (addr),"g" ((int)(size)),"g"(current_thread_info()->addr_limit.seg)); /

    flag; })

    其实现的功能为:

    (u33)addr + (u33)size >=(u33)current->addr_limit.seg

    判断上式是否成立,若不成立则表示地址有效,返回零;否则返回非零

     

    ⑤接下来才是最重要的函数,它实现了拷贝的工作:__copy_to_user(to, from, n)

    其实现方式如下(在/include/asm-i386/uaccess.h中):

    static __always_inline unsigned long__must_check

    __copy_to_user(void __user *to, constvoid *from, unsigned long n)

    {

    might_sleep();

    return __copy_to_user_inatomic(to,from, n);

    }

    有一个__always_inline宏,其内容就是inline,一个__must_check,其内容是在gcc3和gcc4版本里为__attribute__((warn_unused_result))

    其中might_sleep同上面__user时候的注释。

    最终调用的是__copy_to_user_inatomic(to, from,n)来完成拷贝工作的,此函数的实现如下(在/include/asm-i386/uaccess.h中):

    static __always_inline unsigned long__must_check

    __copy_to_user_inatomic(void __user*to, const void *from, unsigned long n)

    {

    if (__builtin_constant_p(n)) {

    unsigned long ret;

     

    switch (n) {

    case 1:

    __put_user_size(*(u8 *)from, (u8 __user*)to, 1, ret, 1);

    return ret;

    case 2:

    __put_user_size(*(u16 *)from, (u16__user *)to, 2, ret, 2);

    return ret;

    case 4:

    __put_user_size(*(u32 *)from, (u32__user *)to, 4, ret, 4);

    return ret;

    }

    }

    return __copy_to_user_ll(to, from,n);

    }

    其中__builtin_constant_p(n)为gcc的内建函数,__builtin_constant_p用于判断一个值是否为编译时常熟,如果参数n的值为常数,函数返回1,否则返回0。很多计算或操作在参数为常数时有更优化的实现,在GNU C中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。

    如果n为常数1、2或者4,就会选择某个swith来执行拷贝动作,拷贝是通过如下函数来实现的(在/include/asm-i386/uaccess.h中):

    #ifdef CONFIG_X86_WP_WORKS_OK

    #define__put_user_size(x,ptr,size,retval,errret) /

    do { /

    retval = 0; /

    __chk_user_ptr(ptr); /

    switch (size) { /

    case 1:__put_user_asm(x,ptr,retval,"b","b","iq",errret);break; /

    case 2:__put_user_asm(x,ptr,retval,"w","w","ir",errret);break; /

    case 4:__put_user_asm(x,ptr,retval,"l","","ir",errret); break; /

    case 8:__put_user_u64((__typeof__(*ptr))(x),ptr,retval); break;/

    default: __put_user_bad(); /

    } /

    } while (0)

    #else

    #define__put_user_size(x,ptr,size,retval,errret) /

    do { /

    __typeof__(*(ptr)) __pus_tmp = x; /

    retval = 0; /

    /

    if(unlikely(__copy_to_user_ll(ptr,&__pus_tmp, size) != 0)) /

    retval = errret; /

    } while (0)

    #endif

    其中__put_user_asm为一个宏,拷贝工作是通过如下的内联汇编来实现的(在/include/asm-i386/uaccess.h中):

    #define __put_user_asm(x, addr, err,itype, rtype, ltype, errret) /

    __asm__ __volatile__( /

    "1: mov"itype" %"rtype"1,%2/n" /

    "2:/n" /

    ".section .fixup,/"ax/"/n" /

    "3: movl %3,%0/n" /

    " jmp 2b/n" /

    ".previous/n" /

    ".section __ex_table,/"a/"/n" /

    " .align 4/n" /

    " .long 1b,3b/n" /

    ".previous" /

    : "=r"(err) /

    : ltype (x), "m"(__m(addr)),"i"(errret), "0"(err))

    展开全文
  • 嵌入式开发(一):嵌入式开发新手入门

    万次阅读 多人点赞 2019-07-26 15:54:18
    本篇文章整理下嵌入式开发一些入门的基础技能,都是根据以往的工程经验整理,适用于之前没做过嵌入式开发的新手。 嵌入式开发流程一般如下,一般是在PC机的Windows系统下安装Ubuntu虚拟机,搭建嵌入式开发环境及...

    系列文章:

    嵌入式开发(一):嵌入式开发新手入门
    嵌入式开发(二):开发板配置(自用)
    嵌入式开发(三):海思Hi3559a交叉编译live555

    嵌入式开发(四):海思Hi3559AV100交叉编译ffmpeg
    嵌入式开发(五):海思Hi3559AV100交叉编译boost
    嵌入式开发(六):websocketpp服务端海思交叉编译及使用
    嵌入式开发(七):thrift库ubuntu端编译
    嵌入式开发(八):makefie文件


    本篇文章整理下嵌入式开发中一些入门的基础技能,都是根据以往的工程经验整理,适用于之前没做过嵌入式开发的新手。

    嵌入式开发流程一般如下,一般是在PC机的Windows系统下安装Ubuntu虚拟机,搭建嵌入式开发环境及交叉编译环境,开发完成后,编译得到bin文件,然后在Windows下将bin文件通过串口或网络下发到嵌入式板卡,程序在嵌入式板卡上运行。

     

    1. RS232串口接孔图解

    串口,一般也指COM接口,传输速度较慢,适合远距离传输。这里用于主机与嵌入式版之间的数据通信,一般用于指令下发等。按照协议标准还分为RS-232-C、RS-422、RS485等。其中RS-232也是最常用的串口,称为标准串口。下面仅介绍RS-232的的接线图。RS232串口为9针接口,分为公头与母头,PC机上的串口一般为公头,如下图。

     各个针孔功能如下:

    最简单的串行接口需要的信号线是2数据发送(TxD)3数据接收(RxD)和5信号地(GnD) ,只需要接通这3根线即可实现上位机PC与嵌入式板的通信,如下图:

     2. 上位机与嵌入式板串口通信工具

    一般使用SecureCRT工具进行上位机与板卡通信。

    (1)串口连接

    打开快速连接

    协议选择Serial,端口与波特率根据实际情况而设置,

    点击连接,弹出新窗口,敲回车键,出现以下界面表示上位机与板卡连接成功。

    (2)使用Telnet连接

    Telnet连接就是使用网络(即网线)实现PC机与板卡通信,而不需要串口。

    Tlenet新建连接,选择Telnet协议,主机名是板卡的ip。

     点击连接,弹出新窗口,输入用户名和密码:

     

    3. 上位机与嵌入式板卡实现文件传输

    在SecureCRT中使用命令ifconfig查看板卡的ip地址:

    将上位机PC与嵌入式板卡用网线直接连接,将PC机的本地连接ip地址改成与嵌入式板卡同一个网段,例如:

    在上位机中打开tftpd32工具,在Server interface中选择上位机与嵌入式板卡相同网段的ip,如下:

    注意之类PC机是作为TFTP协议的服务端,板卡作为客户端。

    将文件从上位机下载到板卡,在SecureCRT中输入以下命令:

    tftp -g -r 1080P.jpg 192.168.1.100

     这里-g表示从PC下载文件到板卡,-r表示远程服务器(即PC)的文件, 1080P.jpg表示要传输的文件,后面的ip地址就是PC机的IP地址,例如:

    输入以上命令后,板卡在当前目录下即收到该文件。如果需要在某个目录下接收文件,需要切换到该目录下,再输入以上命令。

    将文件从板卡传输到上位机,在SecureCRT中输入以下命令:

    tftp -p -l 10801P.jpg 192.168.1.100

    这里-p即推送的意思,-l表示本地(板卡)的文件。

     

    4. 常用Linux命令

    • 回到根目录:cd /
    • 解压文件:tar -xvf data.tar
    • 删除文件: rm -f 1.txt
    • 改变目录的读写权限:sudo chmod 777 /usr/share/themes

                 说明:这里777表示每个用户都可以读写,后面设的是路径

    • 重命名:mv aaa.h264 bbb.h264

                 说明:将aaa.h264重命名为bbb.264

    • 查看ip地址:ifconfig
    • 查看文件属性:stat sample_nnie_main
    • 查看当前路径:pwd
    • 创建文件夹:mkdir
    • 更改ip地址:ifconfig eth0 192.168.6.111  (重启后会丢失)

    (持续更新)

    5. MP4、AVI等转换为*.h264格式

    在嵌入式系统中,程序往往无法直接解码mp4,avi等格式视频文件,需将其转换为裸流.h264格式,方法如下:

    PC上下载ffmpeg工具,切换到ffmpeg.exe的目录,用cmd打开命令行窗口:

    .mp4转.h264:

    ffmpeg -i 1920x1080.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 1920x1080.h264

    .avi转.h264:

    ffmpeg -i 160x120.avi -vcodec h264 -s 160*120 -an -f m4v test.h264

    mov转h264

    ffmpeg -i test.mov -vbsf h264_mp4toannexb -vcodec copy -an 2.h264 

    即在ffmpeg目录下生成所需格式的文件。

    展开全文
  • 嵌入式】C语言volatile关键字

    千次阅读 多人点赞 2020-07-31 16:37:33
    嵌入式系统应用05. volatile官方说明volatile06. 附录 01. volatile概述 volatile是C语言的一个关键字。将变量定义为volatile就表示告诉编译器这个变量可能会被竟想不到地改变,在这种情况下,编译器就不会去...

    00. 目录

    01. volatile概述

    volatile是C语言中的一个关键字。将变量定义为volatile就表示告诉编译器这个变量可能会被竟想不到地改变,在这种情况下,编译器就不会去假设这个变量的值了,及优化器在用到这个变量是必须每次重新读取他的值。

    02. volatile应用场景

    在程序中,volatile变量常用于以下几种情况:

    • 并行设备的硬件寄存器(例如:状态寄存器)
    • 在中断服务子程序中会访问到的非自动变量(即全局变量)
    • 多线程应用中被几个任务共享的变量

    对于一般变量,其存储的位置是在内存中,但也有可能存储在处理器的寄存器中。在程序中,只要寄存器的内容没有被更改,对变量访问就不需要访问内存,只需要直接使用寄存器的变量。

    例如:在程序中可以按照以下的形式定义volatile变量:

    void test()
    {
        volatile char temp;
    }
    

    当变量temp被定义成volatile类型的时候,它就不会被编译器优化,在每次访问temp变量的时候都将重新在内存中读取它的值。

    事实上,在编译器的优化中,类似temp这种建立在函数栈上的变量是不太可能被外部更改的。在程序中,一般容易被更改的变量是指针指向的内容。

    03. volatile应用示例

    C语言编译器一般都有优化的功能,对代码进行优化。例如:

    int tmp, a1, a2;
    tmp = (unsigned int *)0x4004;
    a1 = *tmp;
    a2 = *tmp;
    

    在某些编译器中,这段代码很可能被编译器优化,优化的结果等同如下代码。

    int tmp, a1, a2;
    tmp = (unsigned int *)0x4004;
    a1 = *tmp;
    a2 = a1;
    

    这种优化在一般的情况下没有什么错误,但是在特殊的情况下却可能引发错误。例如:第一次读操作(a1 = *tmp)后,*tmp的内容有可能已经被更新,在这种情况下,第2次读操作读出的内容与第一次不一样。原本程序的含义也是在两个不同的时刻读出两个不同的值,但是经过优化后的程序只能读出相同的值。这就需要使用volatile关键字。上述的程序英嘎携程如下形式:

    volatile unsigned int *tmp;
    int a1, a2;
    tmp = (volatile unsigned int *)0x4004;
    a1 = *tmp;
    a2 = *tmp;
    

    总结

    volatile在嵌入式系统中普通用于可能具有并行操作性质的数据,这些变量可能是被外部改变或者内部并行的程序改变。

    04. 嵌入式系统中应用

    在程序中对GPIO相关寄存器的定义

    #define PINSEL0 (*((volatile unsigned long *) 0xE002C000))
    #define PINSEL1 (*((volatile unsigned long *) 0xE002C004))
    #define PINSEL2 (*((volatile unsigned long *) 0xE002C008))
    #define PINSEL3 (*((volatile unsigned long *) 0xE002C00C))
    

    寄存器的定义应该用volatile修饰,避免其在编译过程中被编译器优化,产生意想不到的后果。

    05. volatile官方说明

    原文如下

    volatile

    Indicates that a variable can be changed by a background routine.

    Keyword volatile is an extreme opposite of const. It indicates that a variable may be changed in a way which is absolutely unpredictable by analysing the normal program flow (for example, a variable which may be changed by an interrupt handler). This keyword uses the following syntax:

    volatile data-definition;
    

    Every reference to the variable will reload the contents from memory rather than take advantage of situations where a copy can be in a register.

    表明变量能被后台程序修改

    关键字volatile和const是完全相反的。它表明变量可能会通过某种方式发生改变,而这种方式是你通过分析正常的程序流程完全预测不出来的。(例如,一个变量可能被中断处理程序修改)。关键字使用语法如下:

    volatile data-definition;
    

    每次对变量内容的引用会重新从内存中加载而不是从变量在寄存器里面的拷贝加载。

    我的理解:以中断处理程序修改变量解释可能不太合适,以GPIO为例最合适。首先什么是变量?变量就是一块编了地址的内存区域。GPIO的数据寄存器有一个地址,大小一般为32bit,所以这个数据寄存器可以认为就是一个变量,我们可以读写它。如果GPIO设置为输入,修改GPIO数据寄存器这个变量的就是这个GPIO的引脚,不管你如何分析你的程序,你不可能知道这个GPIO数据寄存器里面值是多少,你得读它。你此刻读到数据和下一刻读到的完全可能是不一样的。简单的说就是你要的数据不同步。使用volatile修饰后,会强制你每次引用GPIO寄存器对应的变量时都会去它的寄存器里面读。

    防止编译器优化掉操作变量的语句

    a.c文件

    int main(void)
    {
    	volatile char a;
    
    	a = 5;
    	a = 7;
    	
    	return 0;
    }
    

    b.c文件

    int main(void)
    {
    	char a;
    
    	a = 5;
    	a = 7;
    	
    	return 0;
    }
    

    编译生成汇编文件

    deng@itcast:~/tmp$ arm-linux-gcc -S a.c  -o a.s 
    deng@itcast:~/tmp$ arm-linux-gcc -S b.c -o b.s
    deng@itcast:~/tmp$ diff a.s b.s
    12c12
    <       .file   "a.c"
    ---
    >       .file   "b.c"
    deng@itcast:~/tmp$ 
    

    发现两个汇编文件相差不大,接下来调整优化等级

    编译生成汇编文件

    deng@itcast:~/tmp$ arm-linux-gcc -O3 -S b.c -o b.s
    deng@itcast:~/tmp$ arm-linux-gcc -O3 -S a.c  -o a.s 
    deng@itcast:~/tmp$ diff a.s b.s
    12c12
    <       .file   "a.c"
    ---
    >       .file   "b.c"
    18c18
    <       @ args = 0, pretend = 0, frame = 8
    ---
    >       @ args = 0, pretend = 0, frame = 0
    21,24d20
    <       sub     sp, sp, #8
    <       mov     r3, #5
    <       strb    r3, [sp, #4]
    <       mov     r3, #7
    26,27d21
    <       strb    r3, [sp, #4]
    <       add     sp, sp, #8
    deng@itcast:~/tmp$ 
    

    可以看到未加volatile修饰的文件b.c,在优化后,汇编对应的a=5;a=7;这两个语句直接优化没了。a=1;a=0;假设a是控制GPIO的语句,原来打算是让GPIO先拉高,再拉低,实现某种时序,结果优化一开,这两句直接废了。这让你在调试硬件的时候会感到莫名其妙。所以这种情况得像a.c那样用volatile来修饰。

    防止编译器优化变量的存取对象(memory or register)

    a.c文件

    int main(void)
    {
    	int b;
    	int c;
    	volatile int* a = (int*)0x30000000;
    	
    	b = *a;
    	c = *a;
    	
    	return c + b;
    }
    

    b.c文件

    int main(void)
    {
    	int b;
    	int c;
    	int* a = (int*)0x30000000;
    	
    	b = *a;
    	c = *a;
    	
    	return c + b;
    }
    

    生成对应的a.s文件

    mov     r3, #805306368
    ldr     r2, [r3]		@ b = *a;
    ldr     r0, [r3]		@ c = *a;
    add     r0, r0, r2		@ b + c;
    bx      lr
    

    生成对应的b.s文件

    mov     r3, #805306368
    ldr     r0, [r3]		@ b = *a;
    mov     r0, r0, asl #1  	@ b << 2; 也就是 b * 2;也就是 b + b;也就是 add r0, r0, r0(可能这句汇编不合法)
    bx      lr
    

    可以看到b.s被优化后,第一次取*a的值时,是从地址0x30000000取出来的(ldr r0, [r3]),第二次就直接没取了,是直接使用了r0的值。这里的r0就是*a的缓存。

    访问被volatile修饰的变量时,强制访问内存中的值,而不是缓存中的。

    volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错

    int main(void)
    {
    	int b;
    	volatile int* a = (int*)0x30000000;
    	
    	b = (*a) * (*a);
    	
    	return b;
    }
    

    生成对应的汇编

    mov     r3, #805306368
    ldr     r2, [r3]	①
    ldr     r0, [r3]	②
    mul     r0, r2, r0
    bx      lr
    

    程序本意是要计算平方。如果这段代码在运行至①这行汇编时,被调度开了,过了一阵调度回来继续运行②行,此时完全有可能 R2 != R0。那么计算出来的结果R0必然不等于那个平方值。

    06. 附录

    6.1 volatile官方描述

    展开全文
  • 转自 liyuanbhu&amp;nbsp;...   写给嵌入式程序员的循环冗余校验(CRC)算法入门引导 前言 ...在嵌入式软件开发,经常要用到CRC 算法对各种数据进行校验。因此,掌握基本的CRC算法应是嵌入式...

    转自 liyuanbhu&nbsp;http://blog.csdn.net/liyuanbhu/article/details/8746044#comments

     

    写给嵌入式程序员的循环冗余校验(CRC)算法入门引导

    前言

    CRC校验(循环冗余校验)是数据通讯中最常采用的校验方式。在嵌入式软件开发中,经常要用到CRC 算法对各种数据进行校验。因此,掌握基本的CRC算法应是嵌入式程序员的基本技能。可是,我认识的嵌入式程序员中能真正掌握CRC算法的人却很少,平常在项目中见到的CRC的代码多数都是那种效率非常低下的实现方式。

    其实,在网上有一篇介绍CRC 算法的非常好的文章,作者是Ross Williams,题目叫:“A PAINLESS GUIDETO CRC ERROR DETECTION ALGORITHMS”。我常将这篇文章推荐给向我询问CRC算法的朋友,但不少朋友向我抱怨原文太长了,而且是英文的。希望我能写篇短点的文章,因此就有了本文。不过,我的水平比不了Ross Williams,我的文章肯定也没Ross Williams的写的好。因此,阅读英文没有障碍的朋友还是去读Ross Williams的原文吧。

    本文的读者群设定为软件开发人员,尤其是从事嵌入式软件开发的程序员,而不是专业从事数学或通讯领域研究的学者(我也没有这个水平写的这么高深)。因此,本文的目标是介绍CRC算法的基本原理和实现方式,用到的数学尽量控制在高中生可以理解的深度。

    另外,鉴于大多数嵌入式程序员都是半路出家转行过来的,不少人只会C语言。因此,文中的示例代码全部采用C语言来实现。作为一篇入门短文,文中给出的代码更注重于示范性,尽可能的保持易读性。因此,文中的代码并不追求最高效的实现,但对于一般的应用却也足够快速了。

    从奇偶校验说起

    所谓通讯过程的校验是指在通讯数据后加上一些附加信息,通过这些附加信息来判断接收到的数据是否和发送出的数据相同。比如说RS232串行通讯可以设置奇偶校验位,所谓奇偶校验就是在发送的每一个字节后都加上一位,使得每个字节中1的个数为奇数个或偶数个。比如我们要发送的字节是0x1a,二进制表示为0001 1010。

    采用奇校验,则在数据后补上个0,数据变为0001 1010 0,数据中1的个数为奇数个(3个)

    采用奇校验,则在数据后补上个1,数据变为0001 1010 1,数据中1的个数为偶数个(4个)

    接收方通过计算数据中1个数是否满足奇偶性来确定数据是否有错。

    奇偶校验的缺点也很明显,首先,它对错误的检测概率大约只有50%。也就是只有一半的错误它能够检测出来。另外,每传输一个字节都要附加一位校验位,对传输效率的影响很大。因此,在高速数据通讯中很少采用奇偶校验。奇偶校验优点也很明显,它很简单,因此可以用硬件来实现,这样可以减少软件的负担。因此,奇偶校验也被广泛的应用着。

    奇偶校验就先介绍到这来,之所以从奇偶校验说起,是因为这种校验方式最简单,而且后面将会知道奇偶校验其实就是CRC 校验的一种(CRC-1)。

    累加和校验

    另一种常见的校验方式是累加和校验。所谓累加和校验实现方式有很多种,最常用的一种是在一次通讯数据包的最后加入一个字节的校验数据。这个字节内容为前面数据包中全部数据的忽略进位的按字节累加和。比如下面的例子:

    我们要传输的信息为: 6、23、4

    加上校验和后的数据包:6、23、4、33

    这里 33 为前三个字节的校验和。接收方收到全部数据后对前三个数据进行同样的累加计算,如果累加和与最后一个字节相同的话就认为传输的数据没有错误。

    累加和校验由于实现起来非常简单,也被广泛的采用。但是这种校验方式的检错能力也比较一般,对于单字节的校验和大概有1/256 的概率将原本是错误的通讯数据误判为正确数据。之所以这里介绍这种校验,是因为CRC校验在传输数据的形式上与累加和校验是相同的,都可以表示为:通讯数据 校验字节(也可能是多个字节)

    初识 CRC 算法

    CRC 算法的基本思想是将传输的数据当做一个位数很长的数。将这个数除以另一个数。得到的余数作为校验数据附加到原数据后面。还以上面例子中的数据为例:

    6、23、4 可以看做一个2进制数: 000001100001011100000010

    假如被除数选9,二进制表示为:1001

    则除法运算可以表示为:

     

    可以看到,最后的余数为1。如果我们将这个余数作为校验和的话,传输的数据则是:6、23、4、1

    CRC 算法和这个过程有点类似,不过采用的不是上面例子中的通常的这种除法。在CRC算法中,将二进制数据流作为多项式的系数,然后进行的是多项式的乘除法。还是举个例子吧。

    比如说我们有两个二进制数,分别为:1101 和1011。

    1101 与如下的多项式相联系:1x3+1x2+0x1+1x0=x3+x2+x0

    1011与如下的多项式相联系:1x3+0x2+1x1+1x0=x3+x1+x0

    两个多项式的乘法:(x3+x2+x0)(x3+x1+x0)=x6+x5+x4+x3+x3+x3+x2+x1+x0

    得到结果后,合并同类项时采用模2运算。也就是说乘除法采用正常的多项式乘除法,而加减法都采用模2运算。所谓模2运算就是结果除以2后取余数。比如3 mod 2 = 1。因此,上面最终得到的多项式为:x6+x5+x4+x3+x2+x1+x0,对应的二进制数:111111

    加减法采用模2运算后其实就成了一种运算了,就是我们通常所说的异或运算:

    0+0=0

    0+1=1

    1+0=1

    1+1=0

    0-0=0

    1-0=1

    0-1=1

    1-1=0

    上面说了半天多项式,其实就算是不引入多项式乘除法的概念也可以说明这些运算的特殊之处。只不过几乎所有讲解 CRC 算法的文献中都会提到多项式,因此这里也简单的写了一点基本的概念。不过总用这种多项式表示也很罗嗦,下面的讲解中将尽量采用更简洁的写法。

    除法运算与上面给出的乘法概念类似,还是遇到加减的地方都用异或运算来代替。下面是一个例子:

    要传输的数据为:1101011011

    除数设为:10011

    在计算前先将原始数据后面填上4个0:11010110110000,之所以要补0,后面再做解释。

     

    从这个例子可以看出,采用了模2的加减法后,不需要考虑借位的问题,所以除法变简单了。最后得到的余数就是CRC 校验字。为了进行CRC运算,也就是这种特殊的除法运算,必须要指定个被除数,在CRC算法中,这个被除数有一个专有名称叫做“生成多项式”。生成多项式的选取是个很有难度的问题,如果选的不好,那么检出错误的概率就会低很多。好在这个问题已经被专家们研究了很长一段时间了,对于我们这些使用者来说,只要把现成的成果拿来用就行了。

    最常用的几种生成多项式如下:

    CRC8=X8+X5+X4+X0

    CRC-CCITT=X16+X12+X5+X0

    CRC16=X16+X15+X2+X0

    CRC12=X12+X11+X3+X2+X0

    CRC32=X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+X0

    有一点要特别注意,文献中提到的生成多项式经常会说到多项式的位宽(Width,简记为W),这个位宽不是多项式对应的二进制数的位数,而是位数减1。比如CRC8中用到的位宽为8的生成多项式,其实对应得二进制数有九位:100110001。另外一点,多项式表示和二进制表示都很繁琐,交流起来不方便,因此,文献中多用16进制简写法来表示,因为生成多项式的最高位肯定为1,最高位的位置由位宽可知,故在简记式中,将最高的1统一去掉了,如CRC32的生成多项式简记为04C11DB7实际上表示的是104C11DB7。当然,这样简记除了方便外,在编程计算时也有它的用处。

    对于上面的例子,位宽为4(W=4),按照CRC算法的要求,计算前要在原始数据后填上W个0,也就是4个0。

    位宽W=1的生成多项式(CRC1)有两种,分别是X1和X1+X0,读者可以自己证明10 对应的就是奇偶校验中的奇校验,而11对应则是偶校验。因此,写到这里我们知道了奇偶校验其实就是CRC校验的一种特例,这也是我要以奇偶校验作为开篇介绍的原因了。

    CRC算法的编程实现

    说了这么多总算到了核心部分了。从前面的介绍我们知道CRC校验核心就是实现无借位的除法运算。下面还是通过一个例子来说明如何实现CRC校验。

    假设我们的生成多项式为:100110001(简记为0x31),也就是CRC-8

    则计算步骤如下:

    (1)      将CRC寄存器(8-bits,比生成多项式少1bit)赋初值0

    (2)      在待传输信息流后面加入8个0

    (3)      While (数据未处理完)

    (4)      Begin

    (5)          If (CRC寄存器首位是1)

    (6)              reg= reg XOR 0x31

    (7)          CRC寄存器左移一位,读入一个新的数据于CRC寄存器的0 bit的位置。

    (8)      End

    (9)      CRC寄存器就是我们所要求的余数。

     

    实际上,真正的CRC 计算通常与上面描述的还有些出入。这是因为这种最基本的CRC除法有个很明显的缺陷,就是数据流的开头添加一些0并不影响最后校验字的结果。这个问题很让人恼火啊,因此真正应用的CRC 算法基本都在原始的CRC算法的基础上做了些小的改动。

    所谓的改动,也就是增加了两个概念,第一个是“余数初始值”,第二个是“结果异或值”。

    所谓的“余数初始值”就是在计算CRC值的开始,给CRC寄存器一个初始值。“结果异或值”是在其余计算完成后将CRC寄存器的值在与这个值进行一下异或操作作为最后的校验值。

    常见的三种CRC 标准用到个各个参数如下表。

     

    CCITT

    CRC16

    CRC32

    校验和位宽W

    16

    16

    32

    生成多项式

    x16+x12+x5+1

    x16+x15+x2+1

    x32+x26+x23+x22+x16+

    x12+x11+x10+x8+x7+x5+

    x4+x2+x1+1

    除数(多项式)

    0x1021

    0x8005

    0x04C11DB7

    余数初始值

    0xFFFF

    0x0000

    0xFFFFFFFF

    结果异或值

    0x0000

    0x0000

    0xFFFFFFFF

     

    加入这些变形后,常见的算法描述形式就成了这个样子了:

    (1)      设置CRC寄存器,并给其赋值为“余数初始值”。

    (2)      将数据的第一个8-bit字符与CRC寄存器进行异或,并把结果存入CRC寄存器。

    (3)      CRC寄存器向右移一位,MSB补零,移出并检查LSB。

    (4)      如果LSB为0,重复第三步;若LSB为1,CRC寄存器与0x31相异或。

    (5)      重复第3与第4步直到8次移位全部完成。此时一个8-bit数据处理完毕。

    (6)      重复第2至第5步直到所有数据全部处理完成。

    (7)      最终CRC寄存器的内容与“结果异或值”进行或非操作后即为CRC值。

    示例性的C代码如下所示,因为效率很低,项目中如对计算时间有要求应该避免采用这样的代码。不过这个代码已经比网上常见的计算代码要好了,因为这个代码有一个crc的参数,可以将上次计算的crc结果传入函数中作为这次计算的初始值,这对大数据块的CRC计算是很有用的,不需要一次将所有数据读入内存,而是读一部分算一次,全读完后就计算完了。这对内存受限系统还是很有用的。

    [cpp] view plaincopy

    1.  #define POLY        0x1021  

    2.  /** 

    3.   * Calculating CRC-16 in 'C' 

    4.   * @para addr, start of data 

    5.   * @para num, length of data 

    6.   * @para crc, incoming CRC 

    7.   */  

    8.  uint16_t crc16(unsigned char *addr, int num, uint16_t crc)  

    9.  {  

    10.     int i;  

    11.     for (; num > 0; num--)              /* Step through bytes in memory */  

    12.     {  

    13.         crc = crc ^ (*addr++ << 8);     /* Fetch byte from memory, XOR into CRC top byte*/  

    14.         for (i = 0; i < 8; i++)             /* Prepare to rotate 8 bits */  

    15.         {  

    16.             if (crc & 0x8000)            /* b15 is set... */  

    17.                 crc = (crc << 1) ^ POLY;    /* rotate and XOR with polynomic */  

    18.             else                          /* b15 is clear... */  

    19.                 crc <<= 1;                  /* just rotate */  

    20.         }                             /* Loop for 8 bits */  

    21.         crc &= 0xFFFF;                  /* Ensure CRC remains 16-bit value */  

    22.     }                               /* Loop until num=0 */  

    23.     return(crc);                    /* Return updated CRC */  

    24. }  


     

    上面的代码是我从http://mdfs.net/Info/Comp/Comms/CRC16.htm找到的,不过原始代码有错误,我做了些小的修改。

    下面对这个函数给出个例子片段代码:

    [cpp] view plaincopy

    1.  unsigned char data1[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};  

    2.  unsigned char data2[] = {'5', '6', '7', '8', '9'};  

    3.  unsigned short c1, c2;  

    4.  c1 = crc16(data1, 9, 0xffff);  

    5.  c2 = crc16(data1, 4, 0xffff);  

    6.  c2 = crc16(data2, 5, c2);  

    7.  printf("%04x\n", c1);  

    8.  printf("%04x\n", c2);  


     

    读者可以验算,c1、c2 的结果都为 29b1。上面代码中crc 的初始值之所以为0xffff,是因为CCITT标准要求的除数初始值就是0xffff。 

    上面的算法对数据流逐位进行计算,效率很低。实际上仔细分析CRC计算的数学性质后我们可以多位多位计算,最常用的是一种按字节查表的快速算法。该算法基于这样一个事实:计算本字节后的CRC码,等于上一字节余式CRC码的低8位左移8位,加上上一字节CRC右移 8位和本字节之和后所求得的CRC码。如果我们把8位二进制序列数的CRC(共256个)全部计算出来,放在一个表里,编码时只要从表中查找对应的值进行处理即可。

    按照这个方法,可以有如下的代码(这个代码也不是我写的,是我在Micbael Barr的书“Programming Embedded Systems in C and C++” 中找到的,同样,我做了点小小的改动。):

    [cpp] view plaincopy

    1.  /* 

    2.  crc.h 

    3.  */  

    4.    

    5.  #ifndef CRC_H_INCLUDED  

    6.  #define CRC_H_INCLUDED  

    7.    

    8.  /* 

    9.  * The CRC parameters. Currently configured for CCITT. 

    10. * Simply modify these to switch to another CRC Standard. 

    11. */  

    12. /* 

    13. #define POLYNOMIAL          0x8005 

    14. #define INITIAL_REMAINDER   0x0000 

    15. #define FINAL_XOR_VALUE     0x0000 

    16. */  

    17. #define POLYNOMIAL          0x1021  

    18. #define INITIAL_REMAINDER   0xFFFF  

    19. #define FINAL_XOR_VALUE     0x0000  

    20.   

    21. /* 

    22. #define POLYNOMIAL          0x1021 

    23. #define POLYNOMIAL          0xA001 

    24. #define INITIAL_REMAINDER   0xFFFF 

    25. #define FINAL_XOR_VALUE     0x0000 

    26. */  

    27.   

    28. /* 

    29. * The width of the CRC calculation and result. 

    30. * Modify the typedef for an 8 or 32-bit CRC standard. 

    31. */  

    32. typedef unsigned short width_t;  

    33. #define WIDTH (8 * sizeof(width_t))  

    34. #define TOPBIT (1 << (WIDTH - 1))  

    35.   

    36. /** 

    37.  * Initialize the CRC lookup table. 

    38.  * This table is used by crcCompute() to make CRC computation faster. 

    39.  */  

    40. void crcInit(void);  

    41.   

    42. /** 

    43.  * Compute the CRC checksum of a binary message block. 

    44.  * @para message, 用来计算的数据 

    45.  * @para nBytes, 数据的长度 

    46.  * @note This function expects that crcInit() has been called 

    47.  *       first to initialize the CRC lookup table. 

    48.  */  

    49. width_t crcCompute(unsigned char * message, unsigned int nBytes);  

    50.   

    51. #endif // CRC_H_INCLUDED  

     

    [cpp] view plaincopy

    1.  /* 

    2.   *crc.c 

    3.   */  

    4.    

    5.  #include "crc.h"  

    6.  /* 

    7.  * An array containing the pre-computed intermediate result for each 

    8.  * possible byte of input. This is used to speed up the computation. 

    9.  */  

    10. static width_t crcTable[256];  

    11.   

    12. /** 

    13.  * Initialize the CRC lookup table. 

    14.  * This table is used by crcCompute() to make CRC computation faster. 

    15.  */  

    16. void crcInit(void)  

    17. {  

    18.     width_t remainder;  

    19.     width_t dividend;  

    20.     int bit;  

    21.     /* Perform binary long division, a bit at a time. */  

    22.     for(dividend = 0; dividend < 256; dividend++)  

    23.     {  

    24.         /* Initialize the remainder.  */  

    25.         remainder = dividend << (WIDTH - 8);  

    26.         /* Shift and XOR with the polynomial.   */  

    27.         for(bit = 0; bit < 8; bit++)  

    28.         {  

    29.             /* Try to divide the current data bit.  */  

    30.             if(remainder & TOPBIT)  

    31.             {  

    32.                 remainder = (remainder << 1) ^ POLYNOMIAL;  

    33.             }  

    34.             else  

    35.             {  

    36.                 remainder = remainder << 1;  

    37.             }  

    38.         }  

    39.         /* Save the result in the table. */  

    40.         crcTable[dividend] = remainder;  

    41.     }  

    42. } /* crcInit() */  

    43.   

    44. /** 

    45.  * Compute the CRC checksum of a binary message block. 

    46.  * @para message, 用来计算的数据 

    47.  * @para nBytes, 数据的长度 

    48.  * @note This function expects that crcInit() has been called 

    49.  *       first to initialize the CRC lookup table. 

    50.  */  

    51. width_t crcCompute(unsigned char * message, unsigned int nBytes)  

    52. {  

    53.     unsigned int offset;  

    54.     unsigned char byte;  

    55.     width_t remainder = INITIAL_REMAINDER;  

    56.     /* Divide the message by the polynomial, a byte at a time. */  

    57.     for( offset = 0; offset < nBytes; offset++)  

    58.     {  

    59.         byte = (remainder >> (WIDTH - 8)) ^ message[offset];  

    60.         remainder = crcTable[byte] ^ (remainder << 8);  

    61.     }  

    62.     /* The final remainder is the CRC result. */  

    63.     return (remainder ^ FINAL_XOR_VALUE);  

    64. } /* crcCompute() */  

     

    上面代码中crcInit() 函数用来计算crcTable,因此在调用 crcCompute 前必须先调用 crcInit()。不过,对于嵌入式系统,RAM是很紧张的,最好将 crcTable 提前算好,作为常量数据存到程序存储区而不占用RAM空间。CRC 计算实际上还有很多内容可以介绍,不过对于一般的程序员来说,知道这些也就差不多了。余下的部分以后有时间了我再写篇文章来介绍吧。

    上篇博客给出了 STM32F10X 系列单片机中CRC 单元的用法。还指出了这个CRC 单元计算的结果与常见的CRC32 算法得到的结果不相同。但是为什么不相同,是什么原因造成的却没有写出来。这里再补一篇,把这些都说清楚。

    下面先给个crc32的计算函数,这个函数计算的结果与STM32F 单片机上硬件单元的计算结果相同。

    [cpp] viewplaincopy

    1. uint32_t crc32(uint32_t *addr, int num, uint32_t crc)  

    2.  {  

    3.      int i;  

    4.      for (; num > 0; num--)                

    5.      {  

    6.          crc = crc ^ (*addr++);       

    7.          for (i = 0; i < 32; i++)               

    8.          {  

    9.              if (crc & 0x80000000)              

    10.                 crc = (crc << 1) ^ POLY;     

    11.             else                            

    12.                 crc <<= 1;                   

    13.         }                               

    14.         crc &= 0xFFFFFFFF;              

    15.     }                                 

    16.     return(crc);                     

    17. }  


    STM32F 系列的单片机内部带了CRC32 计算单元。这个内置CRC模块的方法使用非常简单。其操作如下图所示。

     

    图 1 CRC计算单元框图

    归纳起来有如下几步操作:

    1. 开启CRC单元的时钟。RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE)
    2. 复位CRC模块(设置CRC_CR=0x01),这个操作把CRC余数初始化为0xFFFFFFFF
    3. 把要计算的数据按逐个地写入CRC_DR寄存器
    4. 写完所有的数据字后,从CRC_DR寄存器读出计算的结果
     

    STM32F10x StdPeriph Driver 中提供了几个函数。

    CRC_ResetDR(void) 
    用来复位CRC模块。

    uint32_tCRC_CalcCRC(uint32_t Data)
    将一个数据写入CRC_DR寄存器,返回值为计算结果。

    uint32_tCRC_CalcBlockCRC(uint32_t pBuffer[], uint32_t BufferLength)
    计算一个数组的CRC 值。

    uint32_tCRC_GetCRC(void)
    读取CRC_DR寄存器的结果。

    另外,CRC 模块中还有个独立数据寄存器(CRC_IDR)。这是个单字节的寄存器,用于临时存放1字节的数据,不受复位操作影响。相应的操作函数有两个。

    voidCRC_SetIDRegister(uint8_t IDValue)
    uint8_tCRC_GetIDRegister(void)
    分别是写CRC_IDR和读 CRC_IDR 寄存器。

    虽然STM32F 上的CRC 单元用起来很简单,但是似乎它计算出来的结果与传统的CRC32算法得到的结果有些不同。
    下面是个简单的例子。

    [cpp] viewplaincopy

    1. #include "stm32f10x.h"  

    2.    

    3. int main(void)  

    4.  {  

    5.      uint32_t j;  

    6.      uint32_t str[11] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', ' '};  

    7.        

    8.      SystemInit();  

    9.      RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);  

    10.     CRC_ResetDR();  

    11.   

    12.     str[9] = CRC_CalcBlockCRC(str, 1);  

    13.     CRC_ResetDR();  

    14.     CRC_CalcCRC(0xA5A5A5A5);  

    15.     j = CRC_GetCRC();  

    16.     CRC_CalcCRC(j);  

    17.     for(;;)  

    18.     {  

    19.     }     

    20. }  

     

    在我写的文章《写给嵌入式程序员的循环冗余校验(CRC)算法入门引导》(http://blog.csdn.net/liyuanbhu/article/details/7882789) 中给了个利用查表法计算crc 的程序。那个程序稍微修改一点就能计算CRC32。下面给出改动后的程序。

    [cpp] viewplaincopy

    1. //crc32.h  

    2.    

    3. #ifndef CRC32_H_INCLUDED  

    4. #define CRC32_H_INCLUDED  

    5.    

    6. #ifdef __cplusplus  

    7. #if __cplusplus  

    8. extern "C"{  

    9.     #endif  

    10.    #endif /* __cplusplus */  

    11.   

    12.   

    13.#include<stdint.h>  

    14.   

    15./* 

    16.* The CRC parameters. Currently configured for CRC32. 

    17.* CRC32=X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+X0 

    18.*/  

    19.   

    20.#define POLYNOMIAL          0x04C11DB7  

    21.#define INITIAL_REMAINDER   0xFFFFFFFF  

    22.#define FINAL_XOR_VALUE     0x00000000  

    23.   

    24./* 

    25.* The width of the CRC calculation and result. 

    26.* Modify the typedef for an 8 or 32-bit CRC standard. 

    27.*/  

    28.typedef uint32_t width_t;  

    29.#define WIDTH (8 * sizeof(width_t))  

    30.#define TOPBIT (1 << (WIDTH - 1))  

    31.   

    32./** 

    33. * Initialize the CRC lookup table. 

    34. * This table is used by crcCompute() to make CRC computation faster. 

    35. */  

    36.void crcInit(void);  

    37.   

    38./** 

    39. * Compute the CRC checksum of a binary message block. 

    40. * @para message, 用来计算的数据 

    41. * @para nBytes, 数据的长度 

    42. * @note This function expects that crcInit() has been called 

    43. *       first to initialize the CRC lookup table. 

    44. */  

    45. width_t crcCompute(unsigned char * message, unsigned int nBytes, width_t remainder);  

    46.   

    47.   

    48.#ifdef __cplusplus  

    49.    #if __cplusplus  

    50. }  

    51.#endif  

    52.#endif /* __cplusplus */  

    53.   

    54.#endif // CRC32_H_INCLUDED  

    对应的C程序如下:

    [cpp] viewplaincopy

    1. #include "crc32.h"  

    2. /* 

    3. * An array containing the pre-computed intermediate result for each 

    4. * possible byte of input. This is used to speed up the computation. 

    5. */  

    6. static width_t crcTable[256];  

    7.    

    8. /** 

    9.  * Initialize the CRC lookup table. 

    10. * This table is used by crcCompute() to make CRC computation faster. 

    11. */  

    12.void crcInit(void)  

    13. {  

    14.     width_t remainder;  

    15.     width_t dividend;  

    16.     int bit;  

    17.     /* Perform binary long division, a bit at a time. */  

    18.     for(dividend = 0; dividend < 256; dividend++)  

    19.     {  

    20.         /* Initialize the remainder.  */  

    21.         remainder = dividend << (WIDTH - 8);  

    22.         /* Shift and XOR with the polynomial.   */  

    23.         for(bit = 0; bit < 8; bit++)  

    24.         {  

    25.             /* Try to divide the current data bit.  */  

    26.             if(remainder & TOPBIT)  

    27.             {  

    28.                 remainder = (remainder << 1) ^ POLYNOMIAL;  

    29.             }  

    30.             else  

    31.             {  

    32.                 remainder = remainder << 1;  

    33.             }  

    34.         }  

    35.         /* Save the result in the table. */  

    36.         crcTable[dividend] = remainder;  

    37.     }  

    38. } /* crcInit() */  

    39.   

    40./** 

    41. * Compute the CRC checksum of a binary message block. 

    42. * @para message, 用来计算的数据 

    43. * @para nBytes, 数据的长度 

    44. * @note This function expects that crcInit() has been called 

    45. *       first to initialize the CRC lookup table. 

    46. */  

    47. width_t crcCompute(unsigned char * message, unsigned int nBytes, width_t remainder)  

    48. {  

    49.     unsigned int offset;  

    50.     unsigned char byte;  

    51.     //width_t remainder = INITIAL_REMAINDER;  

    52.     /* Divide the message by the polynomial, a byte at a time. */  

    53.     for( offset = 0; offset < nBytes; offset++)  

    54.     {  

    55.         byte = (remainder >> (WIDTH - 8)) ^ message[offset];  

    56.         remainder = crcTable[byte] ^ (remainder << 8);  

    57.     }  

    58.     /* The final remainder is the CRC result. */  

    59.     return (remainder ^ FINAL_XOR_VALUE);  

    60. } /* crcCompute() */  


     

    不过用这个程序直接计算得到的CRC 值与STM32 给出的并不相同。之所以会这样是因为字节序的原因。可以举个例子来说明这个问题。比如我们有一片内存区域要计算CRC值。这片内存区域的起始地址是 0x1000,共有8个字节。用 crcCompute() 函数计算时是按照地址顺序依次传入各个字节。也就是先计算0x1000 处的字节,再计算0x0001 处的字节,以此类推最后计算0x1007 地址处的字节。而 STM32 的硬件CRC单元是以32位的字为单位计算的。我们知道CRC 实际上是个多项式的除法运算,而除法运算是从高位算起的。也就是相当于它是按照 0x1003、0x1002、0x1001、0x1000 这个顺序计算第一个字,然后按照0x1007、0x1006、0x1005、x1004 的顺序计算第二个字。因此。我们要是预先将字节序调换一下得到结果就没有问题了。这就有了下面的改造。其中 remainder 传入 0xffffffff。因为STM32 中的CRC余数初始值为0xffffffff。

    [cpp] viewplaincopy

    1. uint32_t stm32crc32(uint32_t * message, unsigned int nWords, uint32_t remainder)  

    2.  {  

    3.      unsigned int offset;  

    4.      unsigned char byte;  

    5.      unsigned char *p = (unsigned char *)message;  

    6.      //width_t remainder = INITIAL_REMAINDER;  

    7.      /* Divide the message by the polynomial, a byte at a time. */  

    8.      for( offset = 0; offset < nWords; offset++)  

    9.      {  

    10.         byte = (remainder >> (WIDTH - 8)) ^ p[3];  

    11.         remainder = crcTable[byte] ^ (remainder << 8);  

    12.   

    13.         byte = (remainder >> (WIDTH - 8)) ^ p[2];  

    14.         remainder = crcTable[byte] ^ (remainder << 8);  

    15.   

    16.         byte = (remainder >> (WIDTH - 8)) ^ p[1];  

    17.         remainder = crcTable[byte] ^ (remainder << 8);  

    18.   

    19.         byte = (remainder >> (WIDTH - 8)) ^ p[0];  

    20.         remainder = crcTable[byte] ^ (remainder << 8);  

    21.   

    22.         p += 4;  

    23.     }  

    24.     /* The final remainder is the CRC result. */  

    25.     return (remainder);  

    26. } /* crcCompute() */  


     

    大家可以验证这个函数的计算结果与STM32上的结果完全一样。

    写到这里本该就结束了,不过我要多说一句,之所以要这么麻烦的调换字节序,都是小端(little endian)惹的祸。要是都采用大端格式就没这些麻烦的转换了。

     

    展开全文
  • *cp (copy,复制) 作用:复制文件或文件夹 cp 源文件pathname 目标文件pathname 举例:cp abc/abc.txt def/def.txt 将目录abc下的文件abc.txt复制到def目录下的def.txt下 内容改变。 cp -r 用来复制文件夹 cp -...
  • 嵌入式开发的防御性C语言编程

    千次阅读 多人点赞 2021-08-12 22:16:09
    嵌入式产品的可靠性自然与硬件密不可分,但在硬件确定、并且没有第三方测试的前提下,使用防御性编程思想写出的代码,往往具有更高的稳定性。 防御性编程首先需要认清C语言的种种缺陷和陷阱,C语言对于运行时的检查...
  • 嵌入式中__main 和main 作用

    千次阅读 2014-04-16 18:23:14
    copy RW (linux 的data段) 2. zero ZI ( bss段) 3. init LIB(初始化库)__main就干了两件事初始化C/C++所需的资源,调用main函数。 __main可以看成使程序符合C的环境;如C初始化的全局变量放到data段;而未...
  • 嵌入式linux的dhcp服务器

    千次阅读 2013-03-12 15:37:42
    嵌入式系统下,用开发板建立dhcp服务器 1.重新编译内核,添加对dhcp的支持 2.busybox是否带有dhcp的应用,如无,则需要重新编译busybox,或者直接下一个独立的也可。 3.dhcp服务器在etc目录下的配置文件,...
  • view plaincopyprint? while 命令 do 命令1 命令2 ... done  while 命令 do 命令1 命令2 ... done while循环计数 [plain] view plaincopyprint? #!/bin/bash #whil
  • 来源:电子技术应用 作者:陈 曦 李汉宁 摘要:描述了在实时嵌入式系统开发遇到的与CACHE有关的问题。对引起这些问题的原因——CACHE和RAM的不一致性进行了讨论。最后,提出了解决问题的方法。 ...
  • 嵌入式系统视频解码优化

    千次阅读 2010-08-13 16:15:00
    嵌入式系统上进行开发很重要一点就是必须要让你的程序更有效率,因为嵌入式系统往往配置有限,甚至很差,而当你的程序需要进行大量的运算时,就更需要注意这一点,通过剖析一些经验来了解视频解码优化.
  •  嵌入式C语言开发通常使用Doxygen进行文档的生成。Doxygen支持多种格式,非常灵活,但排版不好就会显的比较杂乱,不便于阅读。下面给出一份注释模板。 一、文件注释,放于文件的开头 [cpp] view ...
  • 1、用预处理指令#define 声明一个常数,用以表明1年有多少秒(忽略闰年问题) 解答:这一题主要容易错的地方就是:意识到这个表达式将使一个16位机的整型数溢出,因此要用到长整型符号L,告诉编译器这个常数是的...
  • 嵌入式设备使用QWS_KEYBOARD

    千次阅读 2015-01-12 09:45:15
    最近发现一个问题,当我在嵌入式设备使用环境变量QWS_KEYBOARD的时候,运行Qt程序,程序会被挂起。串口终端那边无法再输入任何东西。上网查找原因,发现也有其他的人碰到过这个问题,他们大多数的解决办法就是使用...
  • http://blogold.chinaunix.net/u3/101649/showart.php?id=2073912<br />  按以上步骤得出结论如下: Copy linux kernel from 0x00060000 to 0x30008000, size = 0x00500000 ... done zImage ...
  • volatile在嵌入式系统的用法

    千次阅读 2013-10-31 21:46:25
    今天参加一家公司的嵌入式C语言笔试,其中有道主观题谈到在嵌入式系统volatile变量的用法。平时学习C语言没怎么用到,只用到过static和extern的变量,很惭愧没答上来。嵌入式C语言笔试经常会出现的题目有:  1、...
  • 嵌入式操作系统

    千次阅读 2017-12-18 10:15:47
    教材:嵌入式系统及应用,罗蕾、李允、陈丽蓉等,电子工业出版社 嵌入式软件系统 嵌入式操作系统 嵌入式操作系统的演变 嵌入式操作系统分类 嵌入式操作系统体系结构 嵌入式操作系统的组成 嵌入式内核 嵌入式TCPIP ...
  • 系统处理器启动时,首先执行一个预定地址处的指令,通常这个位置是只读内存,存放系统初始化或引导程序,其功能是执行CPU初始化并配置其他硬件,然后copy操作系统到RAM。  在嵌入式Linux系统,这些工作由...
  • 摘要:本文论述了MPlayer在嵌入式开发系统的应用,说明了如何在嵌入式开发板的平台上实现MPlayer的配置、编译、安装及调试过程。 关键词:嵌入式系统;MPlayer;MEncoder 引言 嵌入式系统是以应用...
  • 嵌入式数据库

    2016-08-14 14:26:34
    这种数据库嵌入到了应用程序进程,消除了与客户机服务器配置相关的开销。嵌入式数据库实际上是轻量级的,在运行时,它们需要较少的内存。它们是使用精简代码编写的,对于嵌入式设备,其速度更快,效果更理想。...
  • 嵌入式mysql

    2020-08-17 08:04:34
    1 潜入式mysql 1.1 引入包 <dependency> <groupId>mysql</groupId> <artifactId>... } } } 1.4 copy kill 把 kill.exe 复制到 c-mxj-utils 目录下, 下次重启可以杀mysql进程, 也可以编写停止mysql 方法.
  • 嵌入式Linux程序自启动问题

    千次阅读 2014-06-05 15:39:06
     买来的开发板一启动就进入...将嵌入式设备的文件系统挂载到计算机时,要注意路径问题,不要使用vim /usr/etc/rc.local这样的命令,你会打开了本地计算机的rc.local,我就是因为这个问题咪了一上午。
  • 使用权限: 所有使用者dd 这个指令在 manual 里的定义是 convert and copy a file 使用方式: dd [option] 如果你想要在线看 manual, 可以试试: dd --help 或是 info dd 如果你想要看看这个版本如何: ...
  • 嵌入式系统的镜像升级

    千次阅读 2017-02-22 20:10:34
    $RAMFS_COPY_DATA [ -L "/lib64" ] && ln -s /lib $RAM_ROOT /lib64 supivot $RAM_ROOT /mnt || { echo "Failed to switch over to ramfs. Please reboot." exit 1 } # /bin/mount -o ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,377
精华内容 12,550
关键字:

嵌入式中的copy