(六)基于FreeRTOS的BF移植到Keil——BF源码LED驱动

六、基于FreeRTOS的BF移植到Keil

(记)6.1随记

6.1.1 C语言中的#与##

参考:https://www.jb51.net/article/282832.htm

在C语言中#与##是两个常用的预处理运算符。分别代表 记号串化(#)、记号黏结(##),其常用于宏定义中的形参字符串操作。如

记号串化(#):

记号串化可以将函数式宏定义中的实参转换为字符串。在函数式宏定义中,如果替换列表中有“#”,则其后的预处理记号必须是当前宏的形参。在预处理期间,“#”连同它后面的形参一起被实参取代。例如

#include <stdio.h>
#define PSQR(x) printf("The square of " #x " is %d.\n",((x)*(x)))
int main(void)
{
    int y = 5;
   PSQR(y);
   PSQR(2 + 4);
   PSQR( 3   *   2  );
   return 0;
}

程序运行结果如下:

第1次调用宏时,用"y"替换#x。第2次调用宏时,用"2 + 4"替换#x。第3次调用宏时,用"3 * 2"替换#x。

ANSI C字符串的串联特性将这些字符串与printf()语句的其他字符串组合,生成最终的字符串。例如,第1次调用变成:

printf(“The square of " “y” " is %d.\n”,((y)*(y)));

然后,字符串串联功能将这3个相邻的字符串组合成一个字符串:

“The square of y is %d.\n”

如果传入的实参中间有空白,则不管有多少,都被转换为一个空格,参数开头和末尾的空白都被删除。例如第3次调用宏时,实参“3 * 2 ”转换为“3 * 2”。

记号黏结(##)

与#运算符类似,##运算符可用于函数式宏的替换部分,它把两个记号组合成一个记号。例如,可以这样定义函数式宏:

#define XNAME(n) x ## n

然后,展开宏XNAME(4)为x4。

记号黏结的作用是将几个预处理记号合并为一个。在一个函数式宏定义中,如果一个预处理记号的前面或者后面有"##",则该记号将与它前面或者后面的记号合并,如果该预处理记号是宏的形参,则用实参执行合并。例如:

#define F(x, y, z)   x##y##r
char F(a, b, c);

第2行的宏调用,其扩展之后如下:

char abr;

6.1 LED(GPIO)——IO初始化

###6.1.1 IOInitGlobal()函数
该函数在初始化init()中初调用,定义在“src/main/drivers/io.c”中284行,函数原型为:

void IOInitGlobal(void)
{
    ioRec_t *ioRec = ioRecs;

    for (unsigned port = 0; port < ARRAYLEN(ioDefUsedMask); port++) {			//循环扫描引脚组(port: 端口)
        for (unsigned pin = 0; pin < sizeof(ioDefUsedMask[0]) * 8; pin++) {		//循环扫描每组各引脚
            if (ioDefUsedMask[port] & (1 << pin)) {								//如果位设置为1
                ioRec->gpio = (gpio_type *)(GPIOA_BASE + (port << 10));   		//“<< 10”代表*0x400,为各端口(A~E)偏移地址
                ioRec->pin = 1 << pin;
                ioRec++;
            }
        }
    }
}
(1) ioRec_t结构体数据类型

函数头使用该结构体体数据类型定义了变量。
Rec全称record,记录?。该数据类型主要包含了GPIO属性相关信息,定义在“src/main/drivers/io_impl.h” 32行处,原型为:

typedef struct ioRec_s {
    gpio_type *gpio;		//GPIO所有相关寄存器
    uint16_t pin;			//引脚号(0~15)
    resourceOwner_e owner;	//所属驱动,LED、Gyro等
    uint8_t index;			//引脚自定义序号
} ioRec_t;  
(2) ioDefUsedMask[]数组

ARRAYLEN是一个判断数组元素个数的宏定义。在for循环中使用了ARRAYLEN来判断ioDefUsedMask[]数组的元素个数。
ioDefUsedMask[]数组原型为:

static const uint16_t ioDefUsedMask[DEFIO_PORT_USED_COUNT] = { DEFIO_PORT_USED_LIST };		//IO使用遮掩数组,位置1表使用,0表不使用

ioDefUsedMask[]数组是一个“IO使用遮掩数组”,“DEFIO_PORT_USED_COUNT”的宏定义值为5,而DEFIO_PORT_USED_LIST宏定义为:

# define DEFIO_PORT_USED_LIST DEFIO_PORT_A_USED_MASK,DmEFIO_PORT_B_USED_MASK,DEFIO_PORT_C_USED_MASK,DEFIO_PORT_D_USED_MASK,DEFIO_PORT_E_USED_MASK  

这是一个“引脚底遮掩列表”,其有5个元素,对应A~E五组IO引脚,也是赋给ioDefUsedMask[]数组各元素的值,该数组是uint16_t 类型的,每个元素的每个位都对应一个引脚,置1表示使用,置0表示遮掩而不使用。

在这里插入图片描述

该值最终在 中定义:

#define TARGET_IO_PORTA         0xffff		//目标使用的端口?只用低16位?1表使用?
#define TARGET_IO_PORTB         0xffff
#define TARGET_IO_PORTC         0xffff
#define TARGET_IO_PORTD         0xffff
#define TARGET_IO_PORTE         0xffff  

可以看到,默认使用所有引脚。

6.2 LED(GPIO)——LED初始化

inty中LED初始化的函数为:

ledInit(statusLedConfig());  

其中statusLedConfig()为一个函数指针。

6.2.1 statusLedConfig()函数

该函数在“1_keil/src/main/drivers/light_led.h”声明:

typedef struct statusLedConfig_s {
    ioTag_t ioTags[STATUS_LED_NUMBER];			//Tag:标签
    uint8_t inversion;							//电平反转
} statusLedConfig_t;

PG_DECLARE(statusLedConfig_t, statusLedConfig);		//声明对应函数指针等
(1)statusLedConfig_t LED状态设置结构体数据类型

与LED设置相关的结构体数据类型,STATUS_LED_NUMBER为使用的LED数量(Github源程序设置为3)。ioTag_t数据类型实际定义为uint8_t,而ioTags[]数组元素的作用为储存对应LED标签,之后程序会根据这个标签查询到对应的IO寄存器等?

####(2)PG_DECLARE PG声明宏定义
其中“PG”的含义暂时还不太清楚,猜测应该与存在硬盘中参数的设置有关。
该宏定义原型在“src/main/pg/pg.h”中:

// Declare system config
#define PG_DECLARE(_type, _name)                                        \
    extern _type _name ## _System;                                      \
    extern _type _name ## _Copy;                                        \
    static inline const _type* _name(void) { return &_name ## _System; }\
    static inline _type* _name ## Mutable(void) { return &_name ## _System; }\
    struct _dummy                                                       \
    /**/  

其主要作用是根据输入的_type数据类型与_name名称,声明一些变量,定义一些函数指针。这之间运用到了之前章节讲到的“##”记号黏结预处理运算符和“inline”内联函数关键字。

综上所述,statusLedConfig()函数的定义如下:

static inline const statusLedConfig_t* statusLedConfig(void)   
{   
    return &statusLedConfig_System;   
}    

即运行statusLedConfig()函数后返回一个statusLedConfig_System指针,该指针是statusLedConfig_t类型的

(3)statusLedConfig_System变量的定义与初始化

但是statusLedConfig()函数只是返回这样一个指针,却并未对statusLedConfig_System变量其进行其它操作,其还有其他定义与初始化的地方。
在源码中,statusLedConfig_System变量在“src/main/drivers/light_led.c”中定义:

PG_REGISTER_WITH_RESET_FN(statusLedConfig_t, statusLedConfig, PG_STATUS_LED_CONFIG, 0);  

PG_REGISTER_WITH_RESET_FN宏在“src/main/pg/pg.h”中定义,原型为:

#define PG_REGISTER_WITH_RESET_FN(_type, _name, _pgn, _version)         \
    extern void pgResetFn_ ## _name(_type *);                           \
    PG_REGISTER_I(_type, _name, _pgn, _version, .reset = {.fn = (pgResetFunc*)&pgResetFn_ ## _name }) \
    /**/  

而宏PG_REGISTER_I的定义中涉及许多汇编代码段部分,暂时还未搞清楚具体原理。

猜测statusLedConfig_System变量在pgResetFn_statusLedConfig()函数中初始化,不知道是不是编译环境原因,这个函数在程序进行时并未被执行,这里先手动将它加入init()函数中。
pgResetFn_statusLedConfig()函数原型为:

void pgResetFn_statusLedConfig(statusLedConfig_t *statusLedConfig)
{
    statusLedConfig->ioTags[0] = IO_TAG(LED0_PIN);
    statusLedConfig->ioTags[1] = IO_TAG(LED1_PIN);
    statusLedConfig->ioTags[2] = IO_TAG(LED2_PIN);

    statusLedConfig->inversion = 0
#ifdef LED0_INVERTED
    | BIT(0)
#endif
#ifdef LED1_INVERTED
    | BIT(1)
#endif
#ifdef LED2_INVERTED
    | BIT(2)
#endif
    ;
}    

6.2.2 ledInit函数

定义在“src/main/drivers/light_led.c”中,函数原型为:  

void ledInit(const statusLedConfig_t *statusLedConfig)
{
ledInversion = statusLedConfig->inversion; //电平反转标志

for (int i = 0; i < STATUS_LED_NUMBER; i++) {
    if (statusLedConfig->ioTags[i]) {
        leds[i] = IOGetByTag(statusLedConfig->ioTags[i]);
        IOInit(leds[i], OWNER_LED, RESOURCE_INDEX(i));
        IOConfigGPIO(leds[i], IOCFG_OUT_PP);
    } else {
        leds[i] = IO_NONE;
    }
}

LED0_OFF;
LED1_OFF;
LED2_OFF;

}

(1)IO_TAG(LED1_PIN)

这段代码运用了许多宏定义。
首先LED0_PIN等都在“src/main/target/AT32F437DEV/target.h”中定义,该文件是用来总体设置每个引脚功能与连接设备的。LED1_PIN定义如下:

#define LED1_PIN                PD14 //confirm on LQFP64  

其次IO_TAG宏的作用是运用“##”字符黏结加入“DEFIO_TAG__”前缀(好几层调用啊,DEF:def,定义; TAG:标签)。所以IO_TAG(LED1_PIN)展开后为:

DEFIO_TAG__PD14  

这又是一个宏了,经过若干调用展开为:

((((gpioid) + 1) << 4) | (pin))  

其中gpioid代表寄存器组(A:0 …… D:3 ……),pin为引脚号(1~15)。DEFIO_TAG__PD14对应的就是:

((((3) + 1) << 4) | (13)) = 77  
(2) IOGetByTag(statusLedConfig->ioTags[i])

for循环中的这段代码,根据之前定义的IO标签,从好久之前(6.1小节)中所提到的ioRecs[]数组中找到对应的元素,并反回其指针。ioRecs[]有80个元素(对应5*16个引脚),如若是定义为PD14引脚,则返回 “&ioRecs[ 4 * 16 + 14]”

(3)IOInit()函数

这个函数只是用来设置输入ioRec_t类型变量指针的值,而没有真正初始化GPIO

(4)IOConfigGPIO()函数

这个函数老师真正GPIO外设的初始化函数,输入ioRec_t类型变量指针与IO电气参数。
而这个函数主要调用了RCC_ClockCmd()函数用来使能GPIO外设时钟,调用了gpio_init()函数用来初始化GPIO其它参数。其中gpio_init()函数是AT32库中的一个函数。

(5)LED外设宏操作

对LED操作的宏,以LED1为例来说,有LED1_TOGGLE、LED1_OFF、LED1_ON分别代表LED电平反转、关、开。对LED的GPIO的操作主要是通过对“6.3.7 GPIO设置/清除寄存器(GPIOx_SCR)”、“6.3.6 GPIO输出数据寄存器(GPIOx_ODT)”两个GPIO外设寄存器来实现的。

o_init()函数用来初始化GPIO其它参数。其中gpio_init()函数是AT32库中的一个函数。

####(5)LED外设宏操作

对LED操作的宏,以LED1为例来说,有LED1_TOGGLE、LED1_OFF、LED1_ON分别代表LED电平反转、关、开。对LED的GPIO的操作主要是通过对“6.3.7 GPIO设置/清除寄存器(GPIOx_SCR)”、“6.3.6 GPIO输出数据寄存器(GPIOx_ODT)”两个GPIO外设寄存器来实现的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值