嵌入式知识结构体和联合体和枚举配合使用规范及宏定义使用

看公司40多万行代码,觉得重要的我记录一下

编译程序四个阶段:预处理、编译、汇编、链接


#ifdef __cplusplus//跨平台编译    表示下面这些函数是C语言函数,使用C编译方式

使用场合:

#ifndef GD32_SYSTEMINIT_H
#define GD32_SYSTEMINIT_H

//==============================
#ifdef __cplusplus //入口
extern "C" {
#endif
//===============================

/*
 * GD32F30x  AHB APB2 APB1最高频率为120 120 60MHz.在医疗项目,设定AHB 1分频,APB2 1分频,APB1 2分频。
 * TIMER时钟来源为APB2和APB1,APBx分频为1,则TIMER时钟为APBx * 1,否则*2。所以所以TIMER时钟频率相同。
 * USART0时钟由APB2分频得到,其它:APB1
 */

#ifdef HXTAL_8M
    #define SYSCLK_FREQ                120000000
    #define AHBCLK_FREQ                120000000
    #define APB1CLK_FREQ             60000000
    #define APB2CLK_FREQ            120000000
    #define TIMERCLK_FREQ            120000000
#elif HXTAL_25M
    #define SYSCLK_FREQ                100000000
    #define AHBCLK_FREQ                100000000
    #define APB1CLK_FREQ             50000000
    #define APB2CLK_FREQ            100000000
    #define TIMERCLK_FREQ            100000000
#endif
        #define USART0CLK_FREQ            APB2CLK_FREQ
        #define USART1CLK_FREQ            APB1CLK_FREQ
        #define USART2CLK_FREQ            APB1CLK_FREQ
        #define UART3CLK_FREQ            APB1CLK_FREQ
        #define UART4CLK_FREQ            APB1CLK_FREQ

    extern void SystemInit (void);//系统初始化函数声明
//=========================================================
#ifdef __cplusplus//关闭入口
}
#endif
//=========================================================
#endif 


volatile 的作用就是指示编译器不要因优化而省略此指令,必须每次都直接读写其值。 
写一段测试代码如下 
  u8 test; 
  
  test = 1; 
  test = 2; 
  test = 3; 

设置优化级别中级 
运行后test会被直接取值为3 只有最后一个语句被编译【重点】 【特别是对位操作的时候,改变的位都是0和1,但是每一个地方都有他的使用意义】
如用volatile 
  volatile u8 test; 
  
  test = 1; 
  test = 2; 
  test = 3; 

则所有语句都会被编译。test先后被设置成1、2、3 
由此可以看出这个作用在IO操作,寄存器操作,特殊变量,多线程变量读写都是很重要。 

使用__IO修饰变量时,则不从cache读取值,而是从变量地址处读取值,防止因其他地方(如硬件引脚、程序其他地方)修改变量值后导致读取变量值不准确的问题。

一般说来,volatile用在如下的几个地方: 
1、中断服务程序中修改的供其它程序检测的变量需要加volatile; 
2、多任务环境下各任务间共享的标志应该加volatile; 
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义; 

__I :输入口。既然是输入,那么寄存器的值就随时会外部修改,那就【不能进行优化,每次都要重新从寄存器中读取】。也不能写,即【只读】,不然就不是输入而是输出了。 
    #define __I     volatile const
__O :输出口,也不能进行优化,不然你连续两次输出相同值,编译器认为没改变,就忽略了后面那一次输出,假如外部在两次输出中间修改了值,那就影响输出。
    
__IO:输入输出口,同上 
#define __IO    volatile

嵌入式做常用写法:
#define     __I   volatile
#define    __IO    volatile  const 

为什么加下划线? 原因是:避免命名冲突 
一般宏定义都是大写,但因为这里的字母比较少,所以再添加下划线来区分。这样一般都可以避免命名冲突问题,因为很少人这样命名,这样命名的人肯定知道这些是有什么用的。 
经常写大工程时,都会发现老是命名冲突,要不是全局变量冲突,要不就是宏定义冲突,所以我们要尽量避免这些问题,不然出问题了都不知道问题在哪里。

再给出常用的C变量的定义方式: 
a) 一个整型数(An integer) 
b) 一个指向整型数的指针(A pointer to an integer) 
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer) 
d) 一个有10个整型数的数组(An array of 10 integers) 
e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers) 
f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers) 
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer) 
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer) 

答案是: 
a) int a;               // An integer 
b) int *a;              // A pointer to an integer 
c) int **a;             // A pointer to a pointer to an integer 
d) int a[10];           // An array of 10 integers 
e) int *a[10];          // An array of 10 pointers to integers 
f) int (*a)[10];        // A pointer to an array of 10 integers 
g) int (*a)(int);       // A pointer to a function a that takes an integer argument and returns an integer 
h) int (*a[10])(int);   // An array of 10 pointers to functions that take an integer argument and return an integer


同时需要在多个.c文件中使用该struct, 但是多次include 该.h文件提示重复定义,应该修改如下:
在其它.c文件中需要使用该结构体时,直接include .h文件即可这样编译通过。实际上结构体是一种数据类型,.h文件定义了一种类型的结构体,并声明为extern形式允许外部调用它,而初始化code GB_16[] 这个结构体应当在.c文件中进行。

1.extern是C语言的一个关键字,它通常的作用是用来修饰全局变量或者函数。被修饰的变量和函数在别的地方已经声明定义过,在另一个文件.c文件引用可以它的对应.h头文件引入,就用extern来实现。
 例如:
 函数void ui_action(UI_MSG_ACTION act, int arg);在ui.h中已经声明并在ui.c文件下调用,我们要在另一个eye.c使用void ui_action(UI_MSG_ACTION act, int arg);函数,可以在eye.h文件下添加:extern void ui_action(UI_MSG_ACTION act, int arg);这一句即可,为了代码的美观可读性,我们不要在.c源文件下引入。 
2.在C++文件中,对嵌入C语言的部分指定使用C编译器解析程序时,用使用:extern "C" 来指明


结构体起别名后,如果这个结构体内的变量,有很多.c文件要使用,就要用extern在自身的.h文件声明清楚。防止重定义。
枚举变量也可以取别名,但是没有外部声明之类的,枚举内的变量直接访问。主要目的就是在函数传值的时候,形式参数需要用别名定义形式变量。
结构体变量和枚举变量之间的合作:相互赋值的过程。


//联合体里面的结构体相当于一个普通变量,
typedef struct  //这个typedef好比3个,里面的联合体、结构体都可以取别名
{
    union{//4字节对齐方式
        uint32_t SetStatus;//联合体里面的结构体就像一个变量,所以这个联合体有两个变量,实际操作都是对外面的SetStatus操作【32位数据】,
                //因为联合体的特点就是覆盖,后面的内容总会覆盖前面的,被覆盖的变量的值,由SetStatus得出
        struct{
            uint32_t Set_Bypass_Status: 1;                            //旁路状态    
            uint32_t Set_BLDP_RunStatus: 1;                            //血泵运行状态
            uint32_t Set_GP01_RunStatus: 1;                        //供水泵状态
            uint32_t Set_GP02_RunStatus: 1;                        //循环泵状态
            uint32_t Set_CP01_RunStatus: 1;                        //B液泵状态
            uint32_t Set_CP02_RunStatus: 1;                        //A液泵状态
            uint32_t Set_CP03_RunStatus: 1;                        //超滤泵状态
            uint32_t Set_Clamp_Status: 1;                            //静脉夹状态
            uint32_t Set_OverRide_RunStatus: 1;                        //OverRide运行状态
            uint32_t Set_Fan1_Status: 1;                            //  风扇1状态
            uint32_t Set_Fan2_Status: 1;                            //  风扇2状态    
            uint32_t Set_Fan3_Status: 1;                            //  风扇3状态
            uint32_t Heater_Status:1 ;                            //  加热器状态
            
            uint32_t Set_SyringePump_RunStstus:3;                           //注射泵状态
        }Bit;
    }UNION;
} Set_DeviceStatus_Struct;       //设置器件状态(1:ON ,0:OFF) 结构体

extern Set_DeviceStatus_Struct Set_DeviceStatus;//声明为外部变量,只要添加了其头文件的.c文件皆可使用

其他函数:
【DeviceStatusSave.UNION.Bit.Bypass_Status = Set_DeviceStatus.UNION.Bit.Set_Bypass_Status;    // 报警处理前保存之前的旁路状态,用于报警恢复】

{

}
软件测试最常用:
#ifdef
#endif

结构体的大小计算

分为两种情况:
当没有定义 #pragma pack(value) 这种指定 value 字节进行对齐时,它的计算规则是:整体的大小在满足为最大数据类型所占字节的倍数下要达到所占内存最小。举例如下:
#include "stdio.h"

typedef struct
{
    char a[5];
    int b;
    double c;
}Test;

int main(void)
{
    printf("sizeof(Test) = %d\n",sizeof(Test));

    system("pause");
}

根据上述规则,由于没有指定对齐字节,则在结构体中 成员a 占5字节,成员b 占4字节,成员c 占8字节,这样的话就是5+4+8=17 字节,但是这里整体大小17字节 不是 8 的最小倍数,因此,成员a 需要补 3个空字节 进行对齐,成员b 需要补 4个空字节 进行对齐,即该结构体的大小为:5+3(空字节) +4+4(空字节)+8=24 字节,总大小是8的最小倍数且占用内存最小


联合体的大小计算

同样也分为两种情况:
当没有定义 #pragma pack(value) 这种指定 value 字节进行对齐时,它的计算规则是:联合体中最大成员所占内存的大小且必须为最大类型所占字节的最小倍数。举例如下:
#include "stdio.h"

union
{
    char a[7];
    int b[2];
    double c;
}Test;

int main(void)
{
    printf("sizeof(Test) = %d\n",sizeof(Test));

    system("pause");
}

根据上述例子,该联合体,成员a 占7字节,成员b 占 4*2=8 字节,成员c 占8字节,最大字节数8同时也满足是 8(double型所对应的字节数)因此,根据计算规则,该联合体的大小为 8字节【最大那个为标准,就是最大值】


结构体嵌套联合体的大小计算

同理也是分为两种情况:
当没有定义 #pragma pack(value) 这种指定 value 字节进行对齐时,它的计算规则是:联合体按照最大成员所占字节且为最大数据类型所对应的字节的最小整数倍的原则进行计算,它所占的字节数与结构体中其他成员所占字节的总和应为结构体中最大数据类型所对应的字节的最小倍数。举例如下:

#include "stdio.h"

typedef struct//以第一个为标准  然后对齐向下
{
    union 
    {
        char a[10];  //10字节   不能减  又要是8的最小倍数,那就只有16了
        int b[2];
        double c;
    }test;    //16字节对齐   8  -  8
    
    char d[5]; //5+4+8=17超了   5+3(空格)=8
    int e;   //4+4(空格)
    double f;  //8
 }Test;

int main(void)
{
    printf("sizeof(Test) = %d\n",sizeof(Test));

    system("pause");
}

根据上述规则,则联合体中,最大占10字节,但又要为8的最小倍数,因此联合体占16字节,然后 16+5+4+8=33 字节,不是结构体中最大数据类型 double 所对应的字节数 8的最小倍数,根据规则故结构体的大小为:16(联合体所占字节)+5+3(空字节)+4+4(空字节)+8=40 字节。


//对寄存器操作时,将结构体变量宏定义
typedef struct
{
    volatile int a;
    volatile char b;
}IO_t;

#define BASEADDPRESS 0x1000C000
#define  IO   ((IO_t*)(BASEADDPRESS))//IO就是定义的结构体变量


宏函数的使用


#define _bytes2short(byte1, byte0)                           (((((uint16_t)byte1) << 8)   & 0xff00) | \ (((uint16_t)byte0)         & 0x00ff))//计算合并后的值
 switch (_bytes2short(Array[1], Array[2]))//调用的地方
        {
        case READY:
            Step_RunStatus.WaterFill_StatusFlag = START;  //液体填充状态标志开始
            printf("MON's CMD = FILL START\r\n");
            break;
        case END:
            Step_RunStatus.WaterFill_StatusFlag = END;    //液体填充状态标志结束
            printf("MON's CMD = FILL END\r\n");
            break;
        }

函数指针的学习

static    const    struct          //200个报警函数的地址
{
    void    (*func)( uint8_t );
} Alm_AlarmManage[] = {
    { Alm_Alarm0            },                    // TMP上限警報
    { Alm_Alarm1            },                    // TMP下限警報
    { Alm_Alarm2            },                    // 静脈圧上限警報
    { Alm_Alarm3            },                    // 静脈圧下限警報
    { Alm_Alarm4            },                    // 静脈圧最大警報

};

静态测试工具-MISRA

#if defined ( __ICCARM__ )//将文件视为系统包含文件以进行 MISRA 检查
 #pragma system_include  /* treat file as system include file for MISRA check */
#endif
__ICCARM__对应的平台是:IAR(指令地址寄存器,一款编译器 ) EWARM:
Embedded Workbench for ARM 是IARSystems 公司为ARM 微处理器开发的一个集成开发环境(下面简称IAR EWARM)。
比较其他的ARM 开发环境,IAR EWARM 具有入门容易、使用方便和代码紧凑等特点


编程标准合规性
 

MISRA
① MISRA编码标准检查安全关键系统的潜在问题。MSIRA C和MISRA C++合规性模块指出违背这些规则的代码段。
② MISRA C模块强制实施MISRA C:1998、MISRA C:2004和MISRA C:2012。
③ MISRA C++模块强制实施MISRA C++:2008。
④ 在MISRA规则检查方面,Helix QAC的准确性远高于其他工具。它对规则的违背划分出严重度的优先级,你可以据此修改最重要的问题。


//如果没有定义就定义
#ifndef __CORE_CM4_H_GENERIC
#define __CORE_CM4_H_GENERIC

STM32中ARM系列编译工具链的编译宏选择(__CC_ARM、__ICCARM__、__GNUC__、__TASKING__)
2  这几个宏都是什么含义呢?分别对应什么平台呢?
   A  __CC_ARM对应的平台是:ARM RealView:
RealView,是一套包含编译、调试和模拟的开发工具,需结合开发环境如uvision、eclipse或者CodeWarrior,形成集成开发环境来使用。
   B   __ICCARM__对应的平台是:IAR EWARM:
 Embedded Workbench for ARM 是IARSystems 公司为ARM 微处理器开发的一个集成开发环境(下面简称IAR EWARM)。比较其他的ARM 开发环境,IAR EWARM 具有入门容易、使用方便和代码紧凑等特点
   C __GNUC__对应的平台是:GNU Compiler Collection:
GCC的初衷是为GNU操作系统专门编写的一款编译器。GNU系统是彻底的自由软件。
   D __TASKING__对应的平台是:Altinum Designer;
       Altium Designer 是原Protel软件开发商Altium公司推出的一体化的电子产品开发系统,主要运行在Windows操作系统。这套软件通过把原理图设计、电路仿真、PCB绘制编辑、拓扑逻辑自动布线、信号完整性分析和设计输出等技术的完美融合,为设计者提供了全新的设计解决方案,使设计者可以轻松进行设计,熟练使用这一软件使电路设计的质量和效率大大提高。  

获取系统时间的方式时分秒

#define KEIL_MDK_COMPILE_HOUR        ( (__TIME__[0] - '0') * 10 + (__TIME__[1] - '0') )
#define KEIL_MDK_COMPILE_MINUTE        ( (__TIME__[3] - '0') * 10 + (__TIME__[4] - '0') )
#define KEIL_MDK_COMPILE_SECOND        ( (__TIME__[6] - '0') * 10 + (__TIME__[7] - '0') )

 获取年


#define KEIL_MDK_COMPILE_YEAR        ( (__DATE__[7] - '0') * 1000 + (__DATE__[8] - '0') * 100 + (__DATE__[9] - '0') * 10 + (__DATE__[10] - '0') )
获取月:
#define KEIL_MDK_COMPILE_MONTH        (     (__DATE__[2] == 'b' ? 2 :    \
                                        (__DATE__[2] == 'y' ? 5 :    \
                                        (__DATE__[2] == 'l' ? 7 :    \
                                        (__DATE__[2] == 'g' ? 8 :    \
                                        (__DATE__[2] == 'p' ? 9 :    \
                                        (__DATE__[2] == 't' ? 10 :    \
                                        (__DATE__[2] == 'v' ? 11 :    \
                                        (__DATE__[2] == 'c' ? 12 :    \
                                        (__DATE__[2] == 'n' ?        \
                                        (__DATE__[1] == 'a' ? 1 : 6) :    \
                                       (__DATE__[1] == 'a' ? 3 : 4)))))))))) )

获取日


#define KEIL_MDK_COMPILE_DATE        ( (__DATE__[4] == ' ') ? (__DATE__[5] - '0') : ((__DATE__[4] - '0') * 10 + (__DATE__[5] - '0')) )


控制和监控通信解析;
控制:

void CtrlSys_HeartBeat_Process(void)        
{    
    static uint32_t beats = 0;
    static uint32_t timer = 0;
    static uint8_t send_cnt = 0;
    uint8_t array[7] = {0xFD, 0x0D, 0x0A};//7个字节
    
    if (UserTimer_Read(&timer) >= TIMEOUT_1S)
    {
        UserTimer_Reset(&timer);
        beats += 1;
        if (++send_cnt >= 5)
        {
            send_cnt = 0;
            array[3] = beats >> 24;//右移
            array[4] = beats >> 16;
            array[5] = beats >> 8;
            array[6] = beats;
            SendToMon(array, sizeof(array));
        }
    }
}
监控:
void From_ConSys_OtherInfo(uint8_t *p,uint16_t len)//p[0]=0xFD
{
    uint8_t Cmd1,Cmd2;
    uint32_t Data;
    Cmd1 = p[1];//0x0D
    Cmd2 = p[2];//0x0A
    Data = (uint32_t)(((uint32_t)p[3])<<24)|(((uint32_t)p[4])<<16)|(((uint32_t)p[5])<<8)|((uint32_t)p[6]);//beats的数值
}

typedef struct
{
=====================================    
union{  //联合体里面的结构体是一个整体,大小等于结构体的大小
        struct{
                    int a:1;
                    int b:1;
                    int c:1;
                    int d:1;
                    int e:1;
                    int f:27;//28+5=33
                    double y;
                }ST_t;  //这部分的大小是16字节
               long double tt;//16字节        //联合体的大小是以最大的那个为标准,所以这个里面两个都是16字节,就按16字节作为联合体的大小
        }U_t;
======================================
    int gg;  //这个是4字节
}node_t;   //最外层结构体按照最大的一个为标准,16最大,int  gg只能4+28(空格)才能对齐

对齐方式:
1.使用#pragma pack(n)来指定数据结构的对齐值
2.使用 __attribute__ ((packed)) ,让编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,这样子两边都需要使用 __attribute__ ((packed))取消优化对齐
 

GCC下:struct my{ char ch; int a;} sizeof(int)=4;sizeof(my)=8;(非紧凑模式)
GCC下:struct my{ char ch; int a;}__attrubte__ ((packed)) sizeof(int)=4;sizeof(my)=5


 

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qt历险记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值