65.9K
CodeProject 正在变化。 阅读更多。
Home

HWA:微控制器的硬件抽象

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.62/5 (9投票s)

2015年8月26日

CPOL

5分钟阅读

viewsIcon

19202

本文介绍了一种用于微控制器编程的硬件抽象工具。

引言

微控制器编程基本上涉及将位写入硬件寄存器以操作设备内置的外围控制器。

尽管数值常数通常会替换为更具描述性的符号,但这种做法并未解决使代码移植,甚至仅仅是更换外围控制器变得痛苦的主要问题:不同设备之间,甚至是同一供应商的不同设备之间,以及有时甚至是同一设备的不同控制器之间,寄存器的映射很少一致。

近年来,出现了更精密的硬件编程接口。例如,仅举三例:Atmel软件框架(ASF),Cortex微控制器软件接口标准(CMSIS),或自由软件libopencm3。我没有怎么尝试过这些工具,只是足以得出结论,它们不会令我满意,因为它们仍然太接近寄存器并且过于冗长。

至于“Arduino语言”,如果它似乎为硬件提供了一致的抽象接口,那是以牺牲性能为代价的,程序存储器消耗(大得多),并且需要C++编译器。

这些问题启发我尝试创建一个具有以下目标的硬件编程工具:

  • 为尽可能多样化的硬件提供一致的接口
  • 帮助编写简洁易读的代码
  • 生成零代价的二进制代码
  • 无需外部程序,只需标准的C编程工具链

HWA通过为程序员提供一个通用的硬件接口来实现这些目标,该接口由一小组通用指令组成,旨在应用于代表目标设备中嵌入的外围控制器的各种对象。这就是所谓的临时多态

与其他硬件抽象工具的关键区别在于,HWA不是库:它仅使用标准的C99 可变参数宏、C结构和编译器优化器丢弃的C内联函数来实现临时多态机制。

第一个示例

让我们从微控制器程序员著名的“Hello World!”开始:让LED闪烁。这通常是如何编写的(我从网上某处借用了这段代码,用于Atmel AVR,希望作者不会起诉我!)。

#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>

int
main (void)
{
    DDRB |= _BV(DDB0); 
    
    while(1) 
    {
        PORTB ^= _BV(PB0);
        _delay_ms(500);
    }
}

现在,使用HWA来实现相同的功能。

//  The device is clocked by its internal RC oscillator.
//  Device configuration must be declared before loading HWA since it will use
//  it to compute the values of the fuse bytes and the system clock frequency (hw_syshz).
//  Note: HWA assumes factory defaults for undefined configuration parameters. That would
//  produce the same result in the present case, resulting in hw_syshz = 1,000,000 Hz.
//
#define HW_DEVICE_CLK_SRC               rc_8MHz

#define HW_DEVICE_CLK_PSC               8

//  Load HWA for this device.
//  Use the full device name so that HWA knows the packaging and pin numbers
//  can be used in addition to pin names.
//
#include <hwa/attiny85_20pu.h>

//  The pin at which the LED is connected
//
#define PIN_LED                         hw_pin_5  //  Is 'hw_pin_pb0' on ATtiny85 PU

int main ( )
{
   hw_config( PIN_LED, direction, output );

   while(1) {
      hw_toggle( PIN_LED );
      hw_delay_cycles( 0.5 * hw_syshz );  //  0.5 s delay
   }
}

没有寄存器名称,没有神秘的符号。您是否更喜欢hw_toggle(PIN_LED)还是原始代码中的PORTB ^= _BV(PB0),顺便说一句,它应该是PINB = _BV(PB0)

一个更复杂的例子

现在,让我们使用中断使LED闪烁,并在中断之间将设备置于睡眠模式。

//  Target device
//
#include <hwa/attiny85_20pu.h>

//  The pin at which the LED is connected
//
#define PIN_LED                 hw_pin_5

//  The blinking period (in seconds)
//
#define PERIOD                  0.5

/*  The counter and its clock prescaling factor
 */
#define COUNTER                 hw_counter0

#define CLKDIV                  64


/*  Service the counter overflow IRQ
 */
HW_ISR( COUNTER, overflow )
{
  static uint8_t n ;
  n++ ;
  if ( n >= (uint8_t)(0.5 + PERIOD / 0.001 / 2) ) {
    n = 0 ;
    hw_toggle( PIN_LED );
  }
}


int main ( )
{
  /*  Create a HWA context to collect the hardware configuration.
   *  Preload this context with RESET values.
   */
  hwa_begin_from_reset();

  /*  Configure the LED pin.
   */
  hwa_config( PIN_LED, direction, output );

  /*  Have the CPU enter idle mode when the 'sleep' instruction is executed.
   */
  hwa_config( hw_core0,
              sleep,      enabled,
              sleep_mode, idle );

  /*  Configure the counter to overflow every 0.001 s.
   *
   *  The compare unit `compare0` of the counter (OCRxA) is used to store the top value.
   *  Unless otherwise stated, the overflow will be automatically set to occur
   *  when the 'count' value equals the 'top' value in 'loop_up' counting mode.
   */
  hwa_config( COUNTER,
              clock,     prescaler_output(CLKDIV),
              countmode, loop_up,
              bottom,    0,
              top,       compare0
              );

  /*  Set the TOP value of the counter.
   */
  hwa_write( hw_rel(COUNTER, compare0), 0.001 * hw_syshz / CLKDIV );

  /*  Enable overflow IRQs.
   */
  hwa_turn_irq( COUNTER, overflow, on );

  /*  Write this configuration into the hardware.
   */
  hwa_commit();

  hw_enable_interrupts();

  /*  Keep the device in sleep mode between interrupts.
   */
  for(;;)
    hw_sleep();
}

这表明:

  • hwa_config()是一个通用指令(它与配置计数器、设备核心或I/O引脚的指令相同),它接受跟在对象名称后面的可变长度的键/值对列表。
  • HWA指令和对象名称都以hw_hwa_开头。
  • 代码非常简洁,没有冗余;没有提到寄存器、复杂的结构或符号名称,而是用简单的词语描述了想要的结果。

未显示的内容

  • 如果使用另一个计数器(hw_counter1hw_counter2...当然,前提是您的设备有它),相同的代码也可以编译;
  • 相同的代码可以编译用于ATtinyX4,ATtinyX5和ATmegaX8,这是HWA目前支持的唯一设备,并且应该为HWA将来支持的任何其他Atmel AVR编译;
  • 如果不是完全相同,那么对于HWA将来支持的任何其他微控制器,非常相似的代码也应该可以编译;
  • 生成的二进制代码没有任何缺点。

HWA上下文

第二个示例中的指令以hwa_开头。这些指令等同于以hw_开头的指令,但它们不会立即作用于硬件:它们仅将信息存储在一个隐藏的结构中,称为HWA上下文,直到遇到hwa_commit()指令。

hwa_commit()指令计算上下文中存储的所有信息,以确定必须写入硬件寄存器的值(或者告诉程序员他想要的可配置性无法实现),并有效地写入它们。

在Atmel AVR设备上,使用HWA上下文对于获得最佳二进制代码是必不可少的,因为它最大限度地减少了对多个控制器共享的硬件寄存器的访问,并且一个控制器的配置有时与一些相关外围设备的配置相关联。计时器/计数器就是一个典型例子。HWA通过几个对象实现了这些计时器/计数器:

  • 一个计数单元,其时钟源可以是系统时钟预分频器(这使得计数器成为计时器)。
  • 通常有2个比较单元,每个单元有一个(或两个)输出引脚。
  • 可能有一个捕获单元,一个死区发生器...

所有这些对象都被称为“相关”对象,因为它们之间存在关系,一个对象的配置可能会影响其他对象的配置。典型地,计数器的WGM位的数值会受到比较输出配置的影响。

hw_rel()指令给出另一个相关对象的名称(或产生错误)。这也使得编写更容易进行外围设备替换的代码。

项目状态

HWA托管在Github上。

尽管HWA目前支持三种不同的Atmel AVR设备,并且可以为它们编译大约20个不同的示例,但在有足够的用户进行测试并声明“稳定”之前,它仍应被视为“进行中”,至少在Atmel AVR系列方面。

关于HWA的路线图,我实际上是在2010年开始的,当时我第一次尝试编程32位微控制器,STM32F103系列正在为HWA支持做准备。

历史

  • 初始版本
© . All rights reserved.