[技术问答] mbed sdk抽象原理

[复制链接]
1202|5
 楼主| 玛尼玛尼哄 发表于 2015-12-19 15:36 | 显示全部楼层 |阅读模式
mbed sdk抽象原理
      对于传统的MCU开发人员来说,单片机的学习都是从了解寄存器开始的,但有了mbed以后,用户却可以绕过这些直接开发复杂的MCU应用,这具体是怎么实现的呢?下面我们用一个简单的闪灯程序来了解硬件抽象包装的原理。  

基于寄存器操作的闪灯程序
   Led亮灭变化的本质是led对应的Gpio管脚高低电平变化,我们通过查看LPC1768微处理的Datasheet发现,任何一个gpio都有以下寄存器和它相关:   

      其作用如下:
FIODIR:GPIO管脚方向控制寄存器。该寄存器独立控制每个管脚方向,写1置相应管脚位为输出,0为输出。LPC1768一个有5个PORT,其寄存器名称分别为FIO0DIR,FIO1DIR,FIO2DIR,FIO3DIR,FIO4DIR,每个PORT可以有32个管脚,也就是说每个FIODIR寄存器可以控制32fe管脚。
FIOMASK:快速GPIO管脚屏蔽寄存器,对快速IO管脚的任何操作只有在对该寄存器对应位激活(写0)时有效。
FIOPIN:GPIO管脚值寄存器。不论管脚方向如何,管脚当前值都可以在该寄存器读出。
FIOSET:GPIO管脚置位寄存器。写1将相应管脚置高电平,写低无效。
FIOCLR:GPIO管脚清除寄存器。写1将相应管脚置低电平,写低无效。
所以,我们可以用下面的代码来实现一个BLINK程序(这里的wait函数还是使用了mbed提供的api):
int main() {
    unsigned int mask_pin18 = 1 << 18;
    volatile unsigned int *port1_dir = (unsigned int *)0x2009C020;
    volatile unsigned int *port1_set = (unsigned int *)0x2009C038;
    volatile unsigned int *port1_clr = (unsigned int *)0x2009C03C;
     *port1_dir |= mask_pin18;
     while (true) {
        *port1_set |= mask_pin18;
        wait(0.5);
         *port1_clr |= mask_pin18;
        wait(0.5);
    }
}
也可以直接用FIOMASK寄存器来简化操作,代码如下:
int main() {
    unsigned int mask_pin18 = 1 << 18;
    volatile unsigned int *port1_mask = (unsigned int *)0x2009C030;
    volatile unsigned int *port1_dir = (unsigned int *)0x2009C020;
    volatile unsigned int *port1_set = (unsigned int *)0x2009C038;
    volatile unsigned int *port1_clr = (unsigned int *)0x2009C03C;
     *port1_mask = ~ mask_pin18;
    *port1_dir= 0xffffffff;
     while (true) {
        *port1_set = 0xffffffff;
        wait(0.5);
         *port1_clr= 0xffffffff;
        wait(0.5);
    }
}
     


 楼主| 玛尼玛尼哄 发表于 2015-12-19 15:36 | 显示全部楼层

基于CMSIS-CORE操作的闪灯程序

     前面的代码已经表明,基于MCU寄存器的应用程序其大量代码是寄存器地址的定义,为了减少用户的代码量,ARM公司已经要求各个厂商把这部分代码写成统一的接口格式给用户使用,这就是CMSIS-CORE的目标,如在mbed cmsis目录下的LPC17XX.h文件中,你可以找到以下相关代码:

typedef struct

{

  union {

    __IO uint32_t FIODIR;

    ……

  };

  uint32_t RESERVED0[3];

  union {

    __IO uint32_t FIOMASK;

    ……

  };

  union {

    __IO uint32_t FIOPIN;

    ……

  };

  union {

    __IO uint32_t FIOSET;

    ……

  };

  union {

    __O  uint32_t FIOCLR;

    ……

  };

} LPC_GPIO_TypeDef;

……

#define LPC_GPIO0_BASE        (LPC_GPIO_BASE + 0x00000)

#define LPC_GPIO1_BASE        (LPC_GPIO_BASE + 0x00020)

#define LPC_GPIO2_BASE        (LPC_GPIO_BASE + 0x00040)

#define LPC_GPIO3_BASE        (LPC_GPIO_BASE + 0x00060)

#define LPC_GPIO4_BASE        (LPC_GPIO_BASE + 0x00080)

……

#define LPC_GPIO0             ((LPC_GPIO_TypeDef      *) LPC_GPIO0_BASE    )

#define LPC_GPIO1             ((LPC_GPIO_TypeDef      *) LPC_GPIO1_BASE    )

#define LPC_GPIO2             ((LPC_GPIO_TypeDef      *) LPC_GPIO2_BASE    )

#define LPC_GPIO3             ((LPC_GPIO_TypeDef      *) LPC_GPIO3_BASE    )

#define LPC_GPIO4             ((LPC_GPIO_TypeDef      *) LPC_GPIO4_BASE    )

    这样一来,我们就可以基于CMSIS-CORE使用以下代码来完成我们的功能了。

int main() {

    unsigned int mask_pin18 = 1 << 18;

     while (true) {

        LPC_GPIO1->FIOSET |= mask_pin18;

        wait(0.5);

        LPC_GPIO1->FIOCLR |= mask_pin18;

        wait(0.5);

    }

}


 楼主| 玛尼玛尼哄 发表于 2015-12-19 15:38 | 显示全部楼层
     基于mbed hal操作的闪灯程序

虽然从代码量上来说,基于CMSIS-CORE的闪灯程序已经比原先的改进了不少,但操作方式上并没有任何变化,用户依然需要理解寄存器的相关概念,而对于应用程序开发人员来说,他们更能理解的是SDK,即各种各样得函数调用,于是,mbed在cmsis-core的基础之上专门设计了一个硬件抽象层,把MCU的所有寄存器操作都整合成hal api函数调用方式,对于应用开发人员来说,他只需要调用hal api即可,而对于底层开发人员来说,他只需要实现hal api即可。

Hal api的所有函数原型都在hal目录下,与闪灯相关的gpio 函数原型如下:

  1. uint32_t gpio_set(PinName pin);

  2. void gpio_init (gpio_t *obj, PinName pin, PinDirection direction);

  3. void gpio_mode (gpio_t *obj, PinMode mode);

  4. void gpio_dir  (gpio_t *obj, PinDirection direction);

  5. void gpio_write(gpio_t *obj, int value);

  6. int  gpio_read (gpio_t *obj);

     这样一来,我们就可以基于hal api使用以下代码来完成我们的功能了。

  1. int main() {

  2.       gpio_set(LED1);

  3.       gpio_t gpio;

  4.       gpio_init(&gpio, LED1, PIN_OUTPUT);

  5.      while (true) {

  6.                        gpio_write(&gpio,1);

  7.         wait(0.5);

  8.         gpio_write(&gpio,0);

  9.         wait(0.5);

  10.     }

  11. }

而lpc1768对用的hal api实现代码在target/hal目录下,对应的gpio_write和gpio_read在gpio_object.h中,代码如下:

  1. typedef struct {

  2.     PinName  pin;

  3.     uint32_t mask;

  4.      __IO uint32_t *reg_dir;

  5.     __IO uint32_t *reg_set;

  6.     __IO uint32_t *reg_clr;

  7.     __I  uint32_t *reg_in;

  8. } gpio_t;

  9. static inline void gpio_write(gpio_t *obj, int value) {

  10.     if (value)

  11.         *obj->reg_set = obj->mask;

  12.     else

  13.         *obj->reg_clr = obj->mask;

  14. }

  15. static inline int gpio_read(gpio_t *obj) {

  16.     return ((*obj->reg_in & obj->mask) ? 1 : 0);

  17. }

     其它代码在gpio_api.c中:

  1. uint32_t gpio_set(PinName pin) {

  2.     pin_function(pin, 0);

  3.     return (1 << ((int)pin & 0x1F));

  4. }

  5. void gpio_init(gpio_t *obj, PinName pin, PinDirection direction) {

  6.     if(pin == NC) return;

  7.      obj->pin = pin;

  8.     obj->mask = gpio_set(pin);

  9.      LPC_GPIO_TypeDef *port_reg = (LPC_GPIO_TypeDef *) ((int)pin & ~0x1F);

  10.     obj->reg_set = &port_reg->FIOSET;

  11.     obj->reg_clr = &port_reg->FIOCLR;

  12.     obj->reg_in  = &port_reg->FIOPIN;

  13.     obj->reg_dir = &port_reg->FIODIR;

  14.     gpio_dir(obj, direction);

  15.     switch (direction) {

  16.         case PIN_OUTPUT: pin_mode(pin, PullNone); break;

  17.         case PIN_INPUT : pin_mode(pin, PullDown); break;

  18.     }

  19. }

  20. void gpio_mode(gpio_t *obj, PinMode mode) {

  21.     pin_mode(obj->pin, mode);

  22. }

  23. void gpio_dir(gpio_t *obj, PinDirection direction) {

  24.     switch (direction) {

  25.         case PIN_INPUT : *obj->reg_dir &= ~obj->mask; break;

  26.         case PIN_OUTPUT: *obj->reg_dir |=  obj->mask; break;

  27.     }

  28. }


 楼主| 玛尼玛尼哄 发表于 2015-12-19 15:38 | 显示全部楼层
基于mbed api操作的闪灯程序

基于hal api操作的应用程序虽然已经可以做到与具体的mcu无关了,但习惯了java,c#程序设计的程序员来说还是不太习惯,于是,mbed又针对hal api再次作了面向对象的封装,使得用户可以更简单地来编写上层应用,它对应的函数定义在api目录下,相应的实现在common目录下,闪灯程序相关的定义在DigitalOut.h中,其中也包括了对应的实现,代码如下:

  1. class DigitalOut {

  2. public:

  3.     DigitalOut(PinName pin) {

  4.         gpio_init(&gpio, pin, PIN_OUTPUT);

  5.     }

  6.     void write(int value) {

  7.         gpio_write(&gpio, value);

  8.     }

  9.     int read() {

  10.         return gpio_read(&gpio);

  11.     }

  12. #ifdef MBED_OPERATORS

  13.     DigitalOut& operator= (int value) {

  14.         write(value);

  15.         return *this;

  16.     }

  17.     DigitalOut& operator= (DigitalOut& rhs) {

  18.         write(rhs.read());

  19.         return *this;

  20.     }

  21.     operator int() {

  22.         return read();

  23.     }

  24. #endif

  25. protected:

  26.     gpio_t gpio;

  27. };

    这样一来,我们就可以基于mbed api使用以下代码来完成我们的功能了。

  1. DigitalOut led1(LED1);

  2. int main() {

  3.     while (true) {

  4.         led1 = 1;

  5.         wait(0.5);   

  6.         led1 = 0;

  7.         wait(0.5);

  8.     }

  9. }


cowboy2014 发表于 2015-12-19 20:19 | 显示全部楼层
玛尼玛尼哄 发表于 2015-12-19 15:36
基于CMSIS-CORE操作的闪灯程序     前面的代码已经表明,基于MCU寄存器的应用程序其大量代码是寄存器地址的 ...

这个操作起来感觉比常规的简单啊
598330983 发表于 2015-12-19 22:06 | 显示全部楼层
wait(0.5);应该就是等待0.5秒的意思,这个函数的单位是秒
您需要登录后才可以回帖 登录 | 注册

本版积分规则

196

主题

3261

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部