【分享】+#define 让程序移植性更好

2019-07-19 13:04发布

本帖最后由 gaoyang9992006 于 2013-12-23 21:22 编辑

话说嵌入式编程,最大的敌人就是自己,自己编的程序,过了很久自己都看不懂。:lol

因为要把51的程序,PIC的程序换到MSP430上时候,会发现寄存器不一样,移植性问题让人很纠结,这时候通常菜鸟就该重新写了。
其实吧,完全可以利用#define写一个宏把实现同样功能的东西封装起来,然后遇到不同的平台时候
修改这个封装就可以了。然后其他的部分不变,将程序中所有跟寄存器相关的函数全部用#define代替掉
这样你转战到另外的平台,只需要把那一堆#define修改一下就行了,通常他们放到了头文件中,这样只需要动一动头文件就搞定所有了。
LaunchPad开发板让大家都亲切的体会到了MSP430的优越和性能。好多其他MCU起家的同学肯定会为移植问题抓鸡,特别51来的,当年我学51就是,直接P0,P1的弄,就那么一个IO相关寄存器,写的是很happy,寄存器很少,而且功能单一,当到了MSP430这里就不同了,因此我们要有个长远打算就是可移植性,我们不用一遍又一遍的做出力不讨好的工作。比如在LaunchPad上关于P1OUT,P1DIR ,P1REN ,P1IN等等,其实有时候我们只需要实现某一个功能,只不过在别的MCU上可能是两个寄存器参与,或者一个,或者三个,而在430上可能1个可能2个可能3个或者四个。由于寄存器功能可能定义不同,所以如果程序中出现了很多寄存器,那么当然就让人捉急了。
这时候一个伟大的东西出现了,#define ,预编译处理的一个。还有一个问题,就是用#define实现位的“可视化”定义,
比如一个端口8位吧,
你写成0x01
0x02
.
.
0x80
这么看你还需要迷一下才知道是哪个位置。
如果你用BIT0 BIT1 ……BIT7代替,你将一眼就看出来了。
*********************************************************
其实吧,#define只是预编译的一部分,你如果看头文件会发现好多#开头的,那些都素预编译,下面好好说说这个玩意儿。
      预编译指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令:

        指令              用途
         #                  空指令,无任何效果
         #include      包含一个源代码文件
         #define        定义宏
         #undef         取消已定义的宏
         #if               如果给定条件为真,则编译下面代码
         #ifdef          如果宏已经定义,则编译下面代码
         #ifndef        如果宏没有定义,则编译下面代码
         #elif            如果前面的#if给定条件不为真,当前条件为真,则编译下面代码,相当于#elseif
         #endif         结束一个#if……#else条件编译块
         #error        停止编译并显示错误信息

  一、文件包含
        #include预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的,也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。
        预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时,通过给定编译时的条件来达到不同的效果。例如:

        #define ABC
        #i nclude "aaa.c"
        #undef ABC
        #i nclude "aaa.c"

        为了避免那些只能包含一次的头文件被多次包含,可以在头文件中用编译时条件来进行控制。例如:

        #ifndef MY_H
        #define MY_H
          ……
        #endif

        在程序中包含头文件有两种格式:
        #i nclude <my.h>
        #i nclude "my.h"
         第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。

二、宏
        宏定义了一个代表特定内容的标识符。预处理过程会把源代码中出现的宏标识符替换成宏定义时的值。宏最常见的用法是定义代表某个值的全局符号。宏的第二种用法是定义带参数的宏,这样的宏可以象函数一样被调用,但它是在调用语句处展开宏,并用调用时的实际参数来代替定义中的形式参数。
    1.#define指令
        #define预处理指令是用来定义宏的。该指令最简单的格式是:首先声明一个标识符,然后给出这个标识符代表的代码。在后面的源代码中,就用这些代码来替代该标识符。
宏表示的值可以是一个常量表达式,其中允许包括前面已经定义的宏标识符。例如:
            #define ONE 1
            #define TWO 2
            #define THREE (ONE+TWO)
        注意上面的宏定义使用了括号。尽管它们并不是必须的。但出于谨慎考虑,还是应该加上括号的。



3.#运算符
        出现在宏定义中的#运算符把跟在其后的参数转换成一个字符串。有时把这种用法的#称为字符串化运算符。例如:

            #define PASTE(n) "adhfkj"#n

            main()
            {
               printf("%s ",PASTE(15));
            }
        宏定义中的#运算符告诉预处理程序,把源代码中任何传递给该宏的参数转换成一个字符串。所以输出应该是adhfkj15。


    4.##运算符
        ##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。看下面的例子:

            #define NUM(a,b,c) a##b##c
            #define STR(a,b,c) a##b##c

            main()
            {
                printf("%d ",NUM(1,2,3));
                printf("%s ",STR("aa","bb","cc"));
            }

        最后程序的输出为:
                 123
                 aabbcc


三、条件编译指令
    条件编译指令将决定那些代码被编译,而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。
    1.#if指令
        #if指令检测跟在其后的宏或表达式是否为真。如果表达式为真,则编译后面的代码,知道出现#else、#elif或#endif为止;否则就不编译。

    2.#endif指令
        #endif用于终止#if预处理指令。

            #define DEBUG 0
            main()
            {
                #if DEBUG
                    printf("Debugging ");
                #endif
                    printf("Running ");
            }

        由于程序定义DEBUG宏代表0,所以#if条件为假,不编译后面的代码直到#endif,所以程序直接输出Running。
        如果去掉#define语句,效果是一样的。


    3.#ifdef和#ifndef
        #define DEBUG

         main()
        {
            #ifdef DEBUG
                printf("yes ");
            #endif
            #ifndef DEBUG
                printf("no ");
            #endif
        }
        #if defined等价于#ifdef; #if !defined等价于#ifndef


    4.#else指令
        #else指令用于某个#if指令之后,当前面的#if指令的条件不为真时,就编译#else后面的代码。#endif指令将中指上面的条件块。

        #define DEBUG

        main()
        {
            #ifdef DEBUG
                printf("Debugging ");
            #else
                printf("Not debugging ");
            #endif
                printf("Running ");
       }



    5.#elif指令
        #elif预处理指令综合了#else和#if指令的作用。

        #define TWO

        main()
        {
            #ifdef ONE
                printf("1 ");
            #elif defined TWO
                printf("2 ");
            #else
                printf("3 ");
            #endif
        }
        程序很好理解,最后输出结果是2。

     6.其他一些标准指令
        #error指令将使编译器显示一条错误信息,然后停止编译。
        #line指令可以改变编译器用来指出警告和错误信息的文件号和行号。
        #pragma指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。
以上预编译的应用就是我在从51过渡到MSP430中学到的,另外推荐大家多看看430的各种头文件和库函数,那是写的很经典的,不失为学习MSP430嵌入式C编程的好教程。



友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
19条回答
lofky
1楼-- · 2019-07-19 14:04
:lol刚好顺路,遇到
kk5290122
2楼-- · 2019-07-19 15:23
 精彩回答 2  元偷偷看……
gaoyang9992006
3楼-- · 2019-07-19 16:27
比如
P1OUT=BIT0;
比如
#define clrBIT(REG,BIT) (REG =&~BIT)
#define setBIT(REG,BIT) (REG = |BIT)
看着直观还爽。
gaoyang9992006
4楼-- · 2019-07-19 16:35
lofky 发表于 2013-12-23 20:59
刚好顺路,遇到

谢谢参观。
gaoyang9992006
5楼-- · 2019-07-19 16:56
其实上面的例子有个是不允许的
P1OUT=BIT0;
这句,因为出现了寄存器名字,程序的移植性就在于程序内与硬件无关,这就是移植性的关键。
因此可以用#define P1OUT LEDOUT
这样的句子代替。
dirtwillfly
6楼-- · 2019-07-19 21:38
支持一个

一周热门 更多>