CPU大小端模式对C语言底层开发的影响

CPU大小端模式对C语言底层开发的影响

​ 在嵌入式的c语言底层开发过程中,经常会遇到CPU大小端模式的问题,对底层软件的数据结构构建、数据解析、甚至功能实现上均有一定的影响,我自己在ECU的底层软件开发过程中也踩过相应的坑,这里主要和大家进行一定的探讨,把我对这方面知识的理解以及应用表述出来,不足或错误之处请指出。

大小端模式的定义

​ 计算机中的数据均是byte(字节)为单位进行存储的,为了能够对这些byte进行读取和写入操作,对这些byte进行编号,便有了地址的概念。在每个不同的地址内存中存入不同的byte,而存放方式包括地址编号小的存放低位字节、地址编号小的存放高位字节两种方式,这两种编号方式的不同,也就导致了芯片模式的不同,从而引出CPU的大小端模式问题。

芯片大小端模式的定义如下:

CPU大小端模式对C语言底层开发的影响_第1张图片

​ 这个表是以一个32bit的处理器的大小端模式为例:数据、地址均为32bit。从表中的数据字节和存放地址对应关系,我们可以得到CPU大小端模式最核心的区别:低地址存放的是高位数据还是低位数据低地址存放高位数据->大端模式;低地址存放低位数据->小端模式。有一点需要注意的是这里的数据并不仅仅指我们存放在RAM、FLASH等存储单元中的数据,也包括我们底层开发过程中打交道最多的寄存器,这也是对我们软件功能开发影响最大的环节,因此在读写寄存器软件代码编写时,一定要确认处理器是大端模式还是小端模式。

数据构建的影响

​ 前面讲过,在涉及读写寄存器时,一定要确认处理器的大小端模式。验证处理器大小端的C代码实现方法有很多,大家可以去查找自己喜欢的方式,我这里使用的是一种共用体数据组包的形式,与之后文章中要讲的数据大小端数据解析相关。

#include 
typedef unsigned char uint8;//一个byte数据类型
typedef unsigned short int uint16;//两个byte数据类型
typedef unsigned int uint32;//四个byte数据类型
typedef union
{
    uint32 Data;
    struct 
    {
        uint8 byte0;
        uint8 byte1;
        uint8 byte2;
        uint8 byte3;
    }Bytes;
}TestType;//共用体数据类型

int main(void)
{
    TestType TestData = {
        .Data = 0x00000001
    };//测试数据变量初始化,最低位数据赋值为1
    if(0b01 == TestData.Bytes.byte0)//低地址存储低位数据
    {
        printf("该处理器为小端处理器!\n");
    }
    else//低地址存储高位数据
    {
        printf("该处理器为大端处理器!\n");       
    }
  	printf("最低位字节值为%d\n",TestData.Bytes.byte0);
    getchar();
}

CPU大小端模式对C语言底层开发的影响_第2张图片

​ 程序执行的结果如上,因为我使用的电脑是小端处理器,而且现在大多数PC的处理器也都为小端处理器。但我们嵌入式开发中用到的MCU还是有很多大端模式。上面代码采用的是使用printf函数打印输出芯片大小端信息,实际嵌入式底层软件开发过程中,并不能直接采用printf函数打印的方式,但我们可以通过仿真器debug获取TestData.Bytes.byte0的值,或将上面if else判断条件改为执行某些动作,例如切换io口电平等。

​ 从上面的例子可以看出,一个占用32bit内存的变量,在大小端模式的影响下,其高低位数据值解析会有所不同,也包括对数据的初始化。大端模与端模式对数据字节的赋值完全是相反的,如果处理不好,会带来完全相反的结果。还是以上面的代码为例,目标是将TestData中的byte0值更为1,通过对TestData中的Data赋值实现,其实可以采用共用体成员直接赋值实现更为方便,但这里为了说明大小端的影响,采用这种稍微麻烦一点的方式,从上面的结构体定义可知byte0在结构体中属于最低地址存放位置,对应到Data上的高低位就与大小端模式有关了,利用大小端存数据的特性,实现代码实现如下。

   //TODO:实现TestData.Bytes.byte0 = 1
    TestData.Data = 0x00000001;//小端模式:byte0在最低地址,对应于数据Data的最低字节
    TestData.Data = 0x01000000;//大端模式: byte0在最低地址,对应于数据Data的最高字节

对读写CPU寄存器的影响

​ 嵌入式底层开发过程中,会在C代码中加入寄存器的读写操作,如果没有官方接口封装的情况下,我们一般直接通过指针指向寄存器的地址进行赋值。以一个英飞凌的MCU为例进行说明大小端模式对读写寄存器的影响:

​ 例如想要将DMA_CLREx寄存器中bit27~bit31RES这段位域中bit27值配置为0b01。小端模式是bit31~bit0对应高地址到低地址,大端模式是bit31~bit0对应低地址到高地址。大端模式与小端模式的C语言实现代码如下:

REGPTR->DMA_CLREx |= 0x08000000;//小端模式:低地址存放低位数据,因此依次索引赋值即可
REGPTR->DMA_CLREx |= 0x00000010;//大端模式:低地址存放高位数据,与小端赋值完全相反

​ 从上面的例子可见,区分大小端是多么的重要,如果按照小端模式的赋值去配置大端芯片的寄存器,配置其实是bit4,完全超出自己想要的配置结果,博主就踩过这个坑,控制一个驱动的使能,配错了差点烧毁一个驱动。

CPU大小端模式影响总结

​ 这里面只简单的分析了CPU大小端模式的基本概念,判断CPU的大小端方法有很多种,文中的代码例子相对麻烦一些,但是对于后面我介绍大小端对SPI等数据通信过程中的数据组包与解析有关键的说明作用,这部分内容我接触的较多,后面会针对SPI的数据结构组建单独写篇文章,总结一下开发过程中使用的方法,包括结构体、共用体、位域与大小端的关联。

易踩的坑:不判断大小端模式,直接按照自己的习惯读写寄存器!!这点大家开发过程中一定要谨慎再谨慎,先获取芯片的大小端模式。

总结:其实大小端不难理解,只要记住地址与存放数据的排列方式即可,难的是如何利用大小端模式与自己的C代码关联,这个我也在积累中,毕竟这东西踩坑越多就越熟。

你可能感兴趣的:(ECU底层软件开发,c语言,单片机)