嵌入式软件C语言强化知识

嵌入式开发常用的C语言技巧

函数指针的巧妙使用

我们在平时的开发过程中,通常用指针来指向整型变量,字符串,数组…然而很多人也许忽略了函数指针的使用。

函数指针的定义

函数指针就是指向函数的指针。也就是将函数的入口地址赋值给指针。这样我们在访问函数的时候可以用指针访问。

函数指针可以当成参数传递,下面给出实例

#include <stdio.h>
/*比较函数*/
int max(int a,int b)
{
    
    
        return (a>b ? a:b);
}

/*指向函数的指针声明(此刻指针未指向任何一个函数)*/
int (*test)(int a,int b);

int main(int argc,char* argv[])
{
    
    
  int maxNum;

/*将max函数的入口地址赋值给
 *函数指针test
 */
  test=max;

/*通过指针test调用函数max实
 *现比较大小
 */
  maxNum = (*test)(1,2);
  printf("maxNum = %d\n", maxNum);
  return 0;
}

根据注释大家应该很容易地去理解,函数指针其实和平时我们用的其他指针没什么太大的区别。
看懂了上面的程序,我们来看一下s3c2440的NandFlash源码

typedef struct {
    
    
    void (*nand_reset)(void);
    void (*wait_idle)(void);
    void (*nand_select_chip)(void);
    void (*nand_deselect_chip)(void);
    void (*write_cmd)(int cmd);
    void (*write_addr)(unsigned int addr);
    unsigned char (*read_data)(void);
}t_nand_chip;

static t_nand_chip nand_chip;

/* NAND Flash操作的总入口, 它们将调用S3C2410或S3C2440的相应函数 */
static void nand_reset(void);
static void wait_idle(void);
static void nand_select_chip(void);
static void nand_deselect_chip(void);
static void write_cmd(int cmd);
static void write_addr(unsigned int addr);
static unsigned char read_data(void);

/* S3C2410的NAND Flash处理函数 */
static void s3c2410_nand_reset(void);
static void s3c2410_wait_idle(void);
static void s3c2410_nand_select_chip(void);
static void s3c2410_nand_deselect_chip(void);
static void s3c2410_write_cmd(int cmd);
static void s3c2410_write_addr(unsigned int addr);
static unsigned char s3c2410_read_data();

/* S3C2440的NAND Flash处理函数 */
static void s3c2440_nand_reset(void);
static void s3c2440_wait_idle(void);
static void s3c2440_nand_select_chip(void);
static void s3c2440_nand_deselect_chip(void);
static void s3c2440_write_cmd(int cmd);
static void s3c2440_write_addr(unsigned int addr);
static unsigned char s3c2440_read_data(void);


/* 初始化NAND Flash */
void nand_init(void)
{
    
    
#define TACLS   0
#define TWRPH0  3
#define TWRPH1  0

    /* 判断是S3C2410还是S3C2440 */
    if ((GSTATUS1 == 0x32410000) || (GSTATUS1 == 0x32410002))
    {
    
    
        nand_chip.nand_reset         = s3c2410_nand_reset;
        nand_chip.wait_idle          = s3c2410_wait_idle;
        nand_chip.nand_select_chip   = s3c2410_nand_select_chip;
        nand_chip.nand_deselect_chip = s3c2410_nand_deselect_chip;
        nand_chip.write_cmd          = s3c2410_write_cmd;
        nand_chip.write_addr         = s3c2410_write_addr;
        nand_chip.read_data          = s3c2410_read_data;

        /* 使能NAND Flash控制器, 初始化ECC, 禁止片选, 设置时序 */
        s3c2410nand->NFCONF = (1<<15)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
    }
    else
    {
    
    
        nand_chip.nand_reset         = s3c2440_nand_reset;
        nand_chip.wait_idle          = s3c2440_wait_idle;
        nand_chip.nand_select_chip   = s3c2440_nand_select_chip;
        nand_chip.nand_deselect_chip = s3c2440_nand_deselect_chip;
        nand_chip.write_cmd          = s3c2440_write_cmd;
#ifdef LARGER_NAND_PAGE
        nand_chip.write_addr         = s3c2440_write_addr_lp;
#else
        nand_chip.write_addr         = s3c2440_write_addr;
#endif
        nand_chip.read_data          = s3c2440_read_data;

        /* 设置时序 */
        s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
        /* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */
        s3c2440nand->NFCONT = (1<<4)|(1<<1)|(1<<0);
    }
    
    /* 复位NAND Flash */
    nand_reset();
}

这段代码是用于操作s3c2440的NandFlash的源代码。下面我们来对这段代码进行分析
开始时,定义了一个结构体,结构体内部存放着一堆函数指针

随后定义了一个结构体的实例化变量nand_chip

再往下就是一堆函数的声明,这些函数声明作为入口等待被进入

然后跳入nand_init函数里面进行分析:该函数根据不同的芯片型号(s3c2410和s3c2440)来选择结构体里的函数指针赋值该赋值哪些函数。把对应的函数入口地址赋值给函数指针。

这么写的优势有两点

1.我们定义的结构体变量nand_chip在被赋值之后,里面存放着多个函数指针作为入口可供调用。要访问函数,仅需要访问这个结构体即可。

2.如果我们把芯片型号从2440换成2410,那么不必重新写一份代码,我们只需要在nand_chip函数里面加入条件判断,然后给结构体变量重新赋值即可。增加了可移植性。

寄存器操作

violatile是干嘛的

很多人在被面试时,很容易被问到violatile修饰是什么意思。相信很多人能朗朗上口地回答上来“防止被优化”,但是并不知道具体的使用场景是什么,下面来举出具体的例子来说明

#define GSTATUS1        (*(volatile unsigned int *)0x560000B0)

这是一个GPIO口引脚状态寄存器的宏定义,我们现在模拟一个场景:加入现在的GPIO口处于输入模式,我要在程序中实时地获取GPIO口的引脚信息。如果不加volatile修饰,那么cpu获取这个引脚的信息默认会在cache中获取,这里简单描述下cache的功能。

cache的功能

cache是处于CPU边上的高速缓存区,CPU通过SDRAM读取数据,如果CPU在一段时间内需要重复地访问一段数据,系统会将这段数据从SDRAM中将数据搬运到cache里面,这样大大增加了访问的速度。

然而我刚刚在说GPIO口引脚的模式是输入模式,它的数据是从外部不断获取到SDRAM中的。然而我们把某一时间的数据存储到cache后,那么CPU在访问的时候不会再去访问SDRAM中存储的数据,而是去访问cache中存储的数据。然而我们最新的数据永远是存储在SDRAM中,这样就造成我们无法获取到最新的数据。所以我们要避免这段数据存储到cache中,所以这个时候使用violatile进行修饰可以避免数据被存储到cache中,从而让CPU老老实实地去SDRAM中去读取我们的最新数据。

指针的巧妙运用

#define GSTATUS1        (*(volatile unsigned int *)0x560000B0)

在这里(volatile unsigned int )是一个指针,它存储的地址在0x560000B0,最后在通过“ * ”来访问这片地址里面的值。所以代码变成了((volatile unsigned int *)0x560000B0)。

这样,在后面的代码中可以直接通过访问GSTATUS1来访问寄存器的值。

下面再举个例子

/* NAND FLASH (see S3C2410 manual chapter 6) */
typedef struct {
    
    
    S3C24X0_REG32   NFCONF;
    S3C24X0_REG32   NFCMD;
    S3C24X0_REG32   NFADDR;
    S3C24X0_REG32   NFDATA;
    S3C24X0_REG32   NFSTAT;
    S3C24X0_REG32   NFECC;
} S3C2410_NAND;

static S3C2410_NAND * s3c2410nand = (S3C2410_NAND *)0x4e000000;

volatile unsigned char *p = (volatile unsigned char *)&s3c2410nand->NFSTAT;

这里就比较有意思了,首先定义了一个结构体,里面都是些32位的变量。
然后定义了一个结构体指针,并且让他指向0x4e000000这块地址。
最后通过先前定义的s3c2410nand这个结构体指针来访问结构体内部的NFSTAT变量。但是这里左边是一个指针,所以要取出它的地址,最后在前面加上(volatile unsigned char *)进行强制转换来赋值给左边的指针p。这样就可以直接通过p这个指针来给这个结构体里面的某个变量来赋值。

如何操作寄存器

#define GPFCON      (*(volatile unsigned long *)0x56000050)
GPFCON &=~ (0x1<<3);
GPFCON |= (0x1<<3);

结合刚刚讲的指针,我们通过指针可以轻而易举地访问寄存器的地址。这里用个宏定义了起来,然后可以通过宏直接对它进行位操作。

拓展:带参宏的应用

刚刚我们在寄存器里面用到过宏定义,这里拓展一个宏定义的用法,直接上代码吧。

#include <stdio.h>
#define MAX(a,b) (a>b) ? a : b
int main()
{
    
    
  int x;
  int y;
  int max;
  printf(“input two numbers:);
  scanf("%d %d", &x, &y);
  max = MAX(x, y);
  printf("max=%d\n", max);
  return 0;
}

运行结果

1
2
max = 2

由此可见,宏定义也可以像函数一样传入参数。只要在宏定义的右边加入程序逻辑即可。
为了方便理解,再举个例子

#include <stdio.h>
#define SQ(y) (y)*(y)
int main()
{
    
    
  int a, sq;
  printf(“input a number:);
  scanf("%d", &a);
  sq = SQ(a+1);
  printf("sq=%d\n", sq);
  return 0;
}

运行结果

input a number: 10
sq=121

下面整理一下带参宏和函数传参的区别

1.函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符替换。

2.函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。

3.对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。

4.调用函数只可得到一个返回值,而用宏可以设法得到几个结果。

5.使用宏次数多时,宏展开后源程序变长,因为每展开一次都使程序增长,而函数调用不使源程序变长。

6.宏替换不占运行时间,只占编译时间;而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。

总的来说,用宏来代表简短的表达式比较合适。

猜你喜欢

转载自blog.csdn.net/Stone831143/article/details/110292343