• Cortex-M架构MCU位带操作最详细解析(主要以STM32为例,包括判断哪些MCU可用)


    【Cortex-M架构】MCU位带操作最详细解析(主要以STM32为例,包括判断哪些MCU可用)


    代码实践部分直接跳转:

    blog.csdn.net/weixin_53403301/article/details/125543844
    
    • 1

    一、位带操作理论及实践

    位带操作的概念其实30年前就有了,那还是 CM3 将此能力进化,这里的位带操作是 8051 位寻址区的威力大幅加强版

    位带区: 支持位带操作的地址区

    位带别名: 对别名地址的访问最终作 用到位带区的访问上(注意:这中途有一个 地址映射过程)

    位带操作对于硬件 I/O 密集型的底层程序最有用处

    支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在CM4中,有两个区中实现了位带。其中一个是SRAM区的最低1MB范围,第二个则是片内外设区的最低1MB范围。这两个区中的地址除了可以像普通的RAM一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个32位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。

    位操作就是可以单独的对一个比特位读和写,类似与51中sbit定义的变量,stm32中通过访问位带别名区来实现位操作的功能
    STM32中有两个地方实现了位带,一个是SRAM,一个是片上外设。
    在这里插入图片描述
    (1)位带本质上是一块地址区(例如每一位地址位对应一个寄存器)映射到另一片地址区(实现每一位地址位对应一个寄存器中的一位),该区域就叫做位带别名区,将每一位膨胀成一个32位的字。
    (2)位带区的4个字节对应实际寄存器或内存区的一个位,虽然变大到4个字节,但实际上只有最低位有效(代表0或1)

    只有位带可以直接用=赋值的方式来操作寄存器 位带是把寄存器上的每一位 膨胀到32位 映射到位带区 比如0x4002 0000地址的第0个bit 映射到位带区的0地址 那么其对应的位带映射地址为0x00 - 0x04 一共32位 但只有LSB有效 采用位带的方式用=赋值时 就是把位带区对应的LSB赋值 然后MCU再转到寄存器对应的位里面 寄存器操作时 如果不改变其他位上面的值 那就只能通过&=或者|=的方式进行

    在这里插入图片描述

    要设置0x2000 0000这个字节的第二个位bit2为1,使用位带操作的步骤有:
    1、将1写入位 带别名区对应的映射地址(即0x22000008,因为1bit对应4个byte);
    2、将0x2000 0000的值 读取到内部的缓冲区(这一步骤是内核完成的,属于原子操作,不需要用户操作);
    3、将bit2置1,再把值写 回到0x2000 0000(属于原子操作,不需要用户操作)。

    关于GPIO引脚对应的访问地址,可以参考以下公式
    寄存器位带别名 = 0x42000000 + (寄存器的地址-0x40000000)32 + 引脚编号4

    如:端口F访问的起始地址GPIOF_BASE

    #define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)

    在这里插入图片描述

    但好在官方库里面都帮我们定义好了 只需要在BASE地址加上便宜即可

    例如:

    GPIOF的ODR寄存器的地址 = GPIOF_BASE + 0x14

    寄存器位带别名 = 0x42000000 + (寄存器的地址-0x40000000)32 + 引脚编号4

    设置PF9引脚的话:

    uint32_t *PF9_BitBand =
    *(uint32_t *)(0x42000000 + ((uint32_t )&GPIOF->ODR– 0x40000000) *32 + 9*4)
    
    
    • 1
    • 2
    • 3

    封装一下:

    #define PFout(x) *(volatile uint32_t *)(0x42000000 + ((uint32_t )&GPIOF->ODR – 0x40000000) *32 + x*4)
    
    
    • 1
    • 2

    现在 可以把通用部分封装成一个小定义:

    #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
    #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
    #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))
    
    • 1
    • 2
    • 3

    那么 设置PF引脚的函数可以定义:

    #define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414   
    #define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
    
    #define PF_O(n)   	BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
    #define PF_I(n)    	BIT_ADDR(GPIOF_IDR_Addr,n)  //输入
    
    • 1
    • 2
    • 3
    • 4
    • 5

    若使PF9输入输出则:

    PF_O(9)=1;  //输出高电平
    uint8_t dat = PF_I(9);  //获取PF9引脚的值
    
    • 1
    • 2

    总线输入输出:

    void PF_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PF_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PF_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PF_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    适用于所有STM32的位带操作和其他CM架构的位带请看:
    https://blog.csdn.net/weixin_53403301/article/details/125543844
    【最新】基于Cortex-M0、M3、M4的GPIO口位带操作宏定义(可总线输入输出,可用于STM32、ADuCM4050等)

    STM32的可用下面的函数:

    #ifndef __GPIO_H__
    #define __GPIO_H__
    #include "stm32l496xx.h"
    
    #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
    #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
    #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))
    
    #define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
    #define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
    #define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
    #define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14 
    #define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014 
    #define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414    
    #define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814   
    #define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14    
    #define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014     
    
    #define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
    #define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
    #define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
    #define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 
    #define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010 
    #define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
    #define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810 
    #define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 
    #define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010 
     
    #define PA_O(n)   	BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
    #define PA_I(n)    	BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 
    
    #define PB_O(n)   	BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
    #define PB_I(n)    	BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 
    
    #define PC_O(n)   	BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
    #define PC_I(n)    	BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 
    
    #define PD_O(n)   	BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
    #define PD_I(n)    	BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 
    
    #define PE_O(n)   	BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
    #define PE_I(n)    	BIT_ADDR(GPIOE_IDR_Addr,n)  //输入
    
    #define PF_O(n)   	BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
    #define PF_I(n)    	BIT_ADDR(GPIOF_IDR_Addr,n)  //输入
    
    #define PG_O(n)   	BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
    #define PG_I(n)    	BIT_ADDR(GPIOG_IDR_Addr,n)  //输入
    
    #define PH_O(n)   	BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
    #define PH_I(n)    	BIT_ADDR(GPIOH_IDR_Addr,n)  //输入
    
    #define PI_O(n)			BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
    #define PI_I(n)   	BIT_ADDR(GPIOI_IDR_Addr,n)  //输入
    
    void PA_BUS_O(unsigned int num);
    unsigned int PA_BUS_I(void);
    
    void PB_BUS_O(unsigned int num);
    unsigned int PB_BUS_I(void);
    
    void PC_BUS_O(unsigned int num);
    unsigned int PC_BUS_I(void);
    
    void PD_BUS_O(unsigned int num);
    unsigned int PD_BUS_I(void);
    
    void PE_BUS_O(unsigned int num);
    unsigned int PE_BUS_I(void);
    
    void PF_BUS_O(unsigned int num);
    unsigned int PF_BUS_I(void);
    
    void PG_BUS_O(unsigned int num);
    unsigned int PG_BUS_I(void);
    
    void PH_BUS_O(unsigned int num);
    unsigned int PH_BUS_I(void);
    
    void PI_BUS_O(unsigned int num);
    unsigned int PI_BUS_I(void);
    
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    #include "GPIO.h"
    
    void PA_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PA_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PA_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PA_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    void PB_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PB_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PB_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PB_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    void PC_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PC_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PC_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PC_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    void PD_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PD_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PD_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PD_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    void PE_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PE_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PE_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PE_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    void PF_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PF_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PF_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PF_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    void PG_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PG_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PG_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PG_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    void PH_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PH_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PH_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PH_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    void PI_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
    {
    	int i;
    	for(i=0;i<16;i++)
    	{
    		PI_O(i)=(num>>i)&0x0001;
    	}
    }
    unsigned int PI_BUS_I(void)  //输出值num最大为0xFFFF
    {
    	unsigned int num;
    	int i;
    	for(i=0;i<16;i++)
    	{
    		num=num+(PI_I(i)<<i)&0xFFFF;
    	}
    	return num;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173

    二、如何判断MCU的外设是否支持位带

    根据《ARM Cortex-M3与Cortex-M4权威指南(第3版)》中第6章第7节描述
    在这里插入图片描述
    也就是说 要实现对GPIO的位带操作 必须保证GPIO位于外设区域的第一个1MB中
    第一个1MB应该是0x4010 0000之前 位带不是直接操作地址 而是操作地址映射 地址映射被操作以后 MCU自动会修改对应寄存器的值

    位带区只有1MB 所以只能改0x4000 0000 - 0x400F FFFF的寄存器
    像F4系列 GPIO的首地址为0x4002 0000 就可以用位带来更改

    STM32L476的GPIO就不行:
    在这里插入图片描述
    AHB2的都不能用位带
    ABP 还有AHB1都可以用
    在这里插入图片描述
    但是L476的寄存器里面 GPIO和ADC都是AHB2

    附录:压缩字符串、大小端格式转换

    压缩字符串

    首先HART数据格式如下:
    在这里插入图片描述
    在这里插入图片描述
    重点就是浮点数和字符串类型
    Latin-1就不说了 基本用不到

    浮点数

    浮点数里面 如 0x40 80 00 00表示4.0f

    在HART协议里面 浮点数是按大端格式发送的 就是高位先发送 低位后发送

    发送出来的数组为:40,80,00,00

    但在C语言对浮点数的存储中 是按小端格式来存储的 也就是40在高位 00在低位
    浮点数:4.0f
    地址0x1000对应00
    地址0x1001对应00
    地址0x1002对应80
    地址0x1003对应40

    若直接使用memcpy函数 则需要进行大小端转换 否则会存储为:
    地址0x1000对应40
    地址0x1001对应80
    地址0x1002对应00
    地址0x1003对应00

    大小端转换:

    void swap32(void * p)
    {
       uint32_t *ptr=p;
       uint32_t x = *ptr;
       x = (x << 16) | (x >> 16);
       x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF);
    
       *ptr=x;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    压缩Packed-ASCII字符串

    本质上是将原本的ASCII的最高2位去掉 然后拼接起来 比如空格(0x20)
    四个空格拼接后就成了
    1000 0010 0000 1000 0010 0000
    十六进制:82 08 20
    对了一下表 0x20之前的识别不了
    也就是只能识别0x20-0x5F的ASCII表
    在这里插入图片描述

    压缩/解压函数后面再写:

    //传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数
    uint8_t Trans_ASCII_to_Pack(uint8_t * str,uint8_t * buf,const uint8_t str_len)
    {
       if(str_len%4)
       {
          return 0;
       }
    	 
       uint8_t i=0;
       memset(buf,0,str_len/4*3);	  
       for(i=0;i<str_len;i++)
       {
          if(str[i]==0x00)
          {
             str[i]=0x20;
          }
       }
    
       for(i=0;i<str_len/4;i++)
       {
          buf[3*i]=(str[4*i]<<2)|((str[4*i+1]>>4)&0x03);
          buf[3*i+1]=(str[4*i+1]<<4)|((str[4*i+2]>>2)&0x0F);
          buf[3*i+2]=(str[4*i+2]<<6)|(str[4*i+3]&0x3F);
       }
    
       return 1;
    }
    
    //传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数
    uint8_t Trans_Pack_to_ASCII(uint8_t * str,uint8_t * buf,const uint8_t str_len)
    {
       if(str_len%4)
       {
          return 0;
       }
    
       uint8_t i=0;
    
       memset(str,0,str_len);
    
       for(i=0;i<str_len/4;i++)
       {
          str[4*i]=(buf[3*i]>>2)&0x3F;
          str[4*i+1]=((buf[3*i]<<4)&0x30)|(buf[3*i+1]>>4);
          str[4*i+2]=((buf[3*i+1]<<2)&0x3C)|(buf[3*i+2]>>6);
          str[4*i+3]=buf[3*i+2]&0x3F;
       }
    
       return 1;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    大小端转换

    在串口等数据解析中 难免遇到大小端格式问题

    什么是大端和小端

    所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

    所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

    简单来说:大端——高尾端,小端——低尾端

    举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:

    1)大端模式:

    低地址 -----------------> 高地址

    0x12 | 0x34 | 0x56 | 0x78

    2)小端模式:

    低地址 ------------------> 高地址

    0x78 | 0x56 | 0x34 | 0x12

    可见,大端模式和字符串的存储模式类似。

    数据传输中的大小端

    比如地址位、起止位一般都是大端格式
    如:
    起始位:0x520A
    则发送的buf应为{0x52,0x0A}

    而数据位一般是小端格式(单字节无大小端之分)
    如:
    一个16位的数据发送出来为{0x52,0x0A}
    则对应的uint16_t类型数为: 0x0A52

    而对于浮点数4.0f 转为32位应是:
    40 80 00 00

    以大端存储来说 发送出来的buf就是依次发送 40 80 00 00

    以小端存储来说 则发送 00 00 80 40

    由于memcpy等函数 是按字节地址进行复制 其复制的格式为小端格式 所以当数据为小端存储时 不用进行大小端转换
    如:

    uint32_t dat=0;
    uint8_t buf[]={0x00,0x00,0x80,0x40};
       memcpy(&dat,buf,4);
       float f=0.0f;
       f=*((float*)&dat); //地址强转
       printf("%f",f);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    或更优解:

       uint8_t buf[]={0x00,0x00,0x80,0x40};   
       float f=0.0f;
       memcpy(&f,buf,4);
    
    • 1
    • 2
    • 3

    而对于大端存储的数据(如HART协议数据 全为大端格式) 其复制的格式仍然为小端格式 所以当数据为小端存储时 要进行大小端转换
    如:

    uint32_t dat=0;
    uint8_t buf[]={0x40,0x80,0x00,0x00};
       memcpy(&dat,buf,4);
       float f=0.0f;
       swap32(&dat); //大小端转换
       f=*((float*)&dat); //地址强转
       printf("%f",f);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    或:

    uint8_t buf[]={0x40,0x80,0x00,0x00};
       memcpy(&dat,buf,4);
       float f=0.0f;
       swap32(&f); //大小端转换
       printf("%f",f);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    或更优解:

    uint32_t dat=0;
    uint8_t buf[]={0x40,0x80,0x00,0x00};
       float f=0.0f;
       dat=(buf[0]<<24)|(buf[0]<<16)|(buf[0]<<8)|(buf[0]<<0)
       f=*((float*)&dat);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    总结

    固 若数据为小端格式 则可以直接用memcpy函数进行转换 否则通过移位的方式再进行地址强转

    对于多位数据 比如同时传两个浮点数 则可以定义结构体之后进行memcpy复制(数据为小端格式)

    对于小端数据 直接用memcpy写入即可 若是浮点数 也不用再进行强转

    对于大端数据 如果不嫌麻烦 或想使代码更加简洁(但执行效率会降低) 也可以先用memcpy写入结构体之后再调用大小端转换函数 但这里需要注意的是 结构体必须全为无符号整型 浮点型只能在大小端转换写入之后再次强转 若结构体内采用浮点型 则需要强转两次

    所以对于大端数据 推荐通过移位的方式来进行赋值 然后再进行个别数的强转 再往通用结构体进行写入

    多个不同变量大小的结构体 要主要字节对齐的问题
    可以用#pragma pack(1) 使其对齐为1
    但会影响效率

    大小端转换函数

    直接通过对地址的操作来实现 传入的变量为32位的变量
    中间变量ptr是传入变量的地址

    void swap16(void * p)
    {
       uint16_t *ptr=p;
       uint16_t x = *ptr;
       x = (x << 8) | (x >> 8);
    
       *ptr=x;
    }
    
    void swap32(void * p)
    {
       uint32_t *ptr=p;
       uint32_t x = *ptr;
       x = (x << 16) | (x >> 16);
       x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF);
    
       *ptr=x;
    }
    
    void swap64(void * p)
    {
       uint64_t *ptr=p;
       uint64_t x = *ptr;
       x = (x << 32) | (x >> 32);
       x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF);
       x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF);
    
       *ptr=x;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    golang学习笔记(defer基础知识)
    案例:MySQL主从复制与读写分离
    使用nohup命令 或者 代码创建守护进程
    2020年数维杯数学建模B题以家庭为单位的小规模多品种绿色农畜产品经营模式开发求解全过程文档及程序
    ts重点学习143-描述文件声明
    java计算机毕业设计家教平台系统源码+mysql数据库+系统+lw文档+部署
    Xorm 使用手册,面向工作学习
    Win10系统C++调用OpenCV实现网络摄像头录像和抓拍图片
    [二分][状压dp]Boss Rush 2022杭电多校第3场 1002
    Linux 配置mail 发送qq邮箱
  • 原文地址:https://blog.csdn.net/weixin_53403301/article/details/127887607