gpt4 book ai didi

c++ - 嵌入式系统的独立于硬件的C++ HAL

转载 作者:行者123 更新时间:2023-11-30 01:35:01 25 4
gpt4 key购买 nike

我正在研究如何实现针对多个微 Controller 的自定义C++ HAL,这些微 Controller 可能具有不同的体系结构(ARM,AVR,PIC等),同时又保持良好的状态。

我继承了几个大型的,困惑的代码库,这些代码库在当前状态下无法维护,因此需要更结构化的东西。

在阅读了许多好的文章和设计指南之后,我正在考虑PIMPL实现。

考虑下面的UART /串行端口示例:

// -----------------------------
// High-level HAL
// -----------------------------

// serialport.h
class SerialPortPrivate;

class SerialPort {

public:
SerialPort(uint8_t portNumber);
~SerialPort();

bool open();
void close();

void setBaudRate(uint32_t baudRate = 115200);

private:
SerialPortPrivate *_impl;
};
// serialport_p.h
class SerialPort;

class SerialPortPrivate {

public:
SerialPortPrivate(uint8_t portNumber, SerialPort *parent) {
// Store the parent (q_ptr)
_parent = parent;

// Store the port number, this is used to access UART
// specific registers UART->D[portNumber] = 0x10;
_portNumber = portNumber;
}
~SerialPortPrivate();

bool open() = 0;
void close() = 0;

void setBaudRate(uint32_t baudRate) = 0;

protected:
uint8_t _portNumber;

private:
SerialPort *_parent;

};
// serialport.cpp
#include "serialport.h"
#include "serialport_p.h"

#include "stm32serialport_p.h"
#include "avr32serialport_p.h"
#include "nrf52serialport_p.h"
#include "kinetisserialport_p.h"

SerialPort::SerialPort(uint8_t portNumber) {
#if MCU_STM32
_impl = new Stm32SerialPortPrivate(portNumber, this);
#elif MCU_AVR32
_impl = new Avr32SerialPortPrivate(portNumber, this);
#elif MCU_NRF52
_impl = new Nrf52SerialPortPrivate(portNumber, this);
#elif MCU_KINETIS
_impl = new KinetisSerialPortPrivate(portNumber, this);
#endif
}

void SerialPort::setBaudRate(uint32_t baudRate) {
_impl->setBaudRate(baudRate);
}
// -----------------------------
// Low-level BSP
// Hardware-specific overrides
// -----------------------------

// stm32serialport_p.h
class Stm32SerialPortPrivate : public SerialPortPrivate {

};

// nrf52serialport_p.h
class Nrf52SerialPortPrivate : public SerialPortPrivate {

};

// kinetisserialport_p.h
class KinetisSerialPortPrivate : public SerialPortPrivate {

};

上面的代码在高级接口(interface)的构造函数( #if/#endif)中只有一组 SerialPort语句,并且特定于硬件的代码(寄存器访问等)是在私有(private)实现中完成的。

更进一步,我可以看到上述实现对于 I2cPortSpiPortUsbSerialPort之类的类非常有效,但对于其他与端口无关的外设集(如时钟,硬件定时器)则很好。

我敢肯定,上述概念中存在一些漏洞,有人可以从经验中建议避免或是否有更好的抽象方法吗?

最佳答案

以下是我对您的方法存在的一些担忧:

首先,假设一个平台上的外围设备具有一些配置选项,而其他平台上的等效外围设备根本不存在。有关如何处理此问题的一些选项,例如:

  • 为该选项
  • 硬编码一个特定值
  • 包括一个提供该选项的配置值的文件,但不向该文件提供hal。每个使用hal的项目也必须提供此文件。
  • 扩展SerialPort来配置选项(附加功能?某种回调?)。

  • 前两个不是很灵活(无法在运行时更改),第三个破坏了抽象-平台必须提供功能来配置可能不存在的选项,否则 SerialPort用户必须知道底层平台的详细信息。我认为所有这些都是困惑的代码库的组成部分。

    其次,假设一个平台具有可以提供相同功能的多个不同外围设备。例如,我目前正在使用具有 USARTLPUART外设的STM32,它们都可以提供UART功能。要解决此问题,您将需要在运行时根据端口实例化不同的pimpl,或者为可处理的平台设置一个。可行,但会变得凌乱。

    第三,要添加对另一个平台的支持,您现在需要修改许多其他代码以添加新的 #elif子句。同样, #if- #elif- #endif使代码的可读性较差,尽管良好的语法突出显示将遮盖代码的非 Activity 部分。

    至于我的建议:

    找到正确的接口(interface)。尝试为硬件可以做什么创建接口(interface)是一种诱惑-这是硬件抽象层,对吗?但是,我发现最好从接口(interface)客户端的 Angular 来看它-HAL的用例是什么。如果找到一个可以满足大多数或所有用例的简单界面,则可能是一个很好的界面。

    (我认为,这可能与您有关时钟和硬件计时器的观点最相关。问自己:您的用例是什么?)

    I2C是一个很好的例子。以我的经验,大多数时候,特定的I2C外设永久是主机或从机。我很少碰到需要在运行时在主从之间交换。考虑到这一点,最好提供一个 I2CDriver来尝试封装任何平台上的“典型” I2C外设能够提供的功能,或者提供一对接口(interface) I2CMasterDriverI2CSlaveDriver,每个接口(interface)仅提供一个的用例。 I2C交易结束。

    我认为后者是最好的起点。典型的用例是主用例或从属用例,并且在编译时已知用例。

    将接口(interface)限制为“通用”。某些平台可能会提供执行SPI / I2C的单个外设,另一些平台会提供单独的外设。如上所述,相同的外围设备在平台之间可能具有不同的配置选项。

    为“通用”功能提供抽象接口(interface)。

    提供该接口(interface)的特定于平台的实现。这些还可以提供任何所需的特定于平台的配置。

    我认为这样做-将“通用”和特定于硬件分开-使接口(interface)更小,更简单。这样一来,当开始变得凌乱时,就更容易发现它。

    这是我将如何处理的示例。首先,为通用功能定义一个抽象接口(interface)。
    /* hal/uart.h */
    namespace hal
    {
    struct Uart
    {
    virtual ~Uart() {};
    virtual void configure( baud_rate, framing_spec ) = 0;
    /* further universally common functions */
    };
    }

    接下来,创建此接口(interface)的实现,其中可以包括特定于平台的详细信息-配置选项,资源管理。配置您的工具链,使其仅包含特定平台的工具链
    /* hal/avr32/uart.h */
    namespace hal::avr
    {
    struct Uart : public hal::Uart
    {
    Uart( port_id );
    ~Uart();
    void configure( /*platform-specific options */ );
    virtual void configure( baud_rate, framing_spec );
    /* the rest of the pure virtual functions required by hal::Uart */
    };
    }

    为了完整起见,让我们在上面的界面中添加一些更高级别的“客户端”。请注意,它们通过引用采用抽象接口(interface)(可以是指针,但不能按值,因为它将对对象进行 slice )。我在这里省略了 namespace 和基类,因为我认为它们可以更好地说明。

    /* elsewhere */
    struct MaestroA5135Driver : public GPSDriver
    {
    MaestroA5135Driver( hal::Uart& uart );
    }
    struct MicrochipRN4871Driver : public BluetoothDriver
    {
    MicrochipRN4871Driver( hal::Uart& uart );
    }
    struct ContrivedPositionAdvertiser
    {
    ContrivedPositionAdvertiser( GPSDriver& gps, BluetoothDriver& bluetooth );
    }

    最后,让我们将其放到一个人为的示例中。请注意,特定于硬件的配置已完成,因为客户端无法访问它。
    /* main.cpp */
    void main()
    {
    hal::avr::Uart gps_uart( Uart1 );
    gps_uart.configure(); /* do the hardware-specific config here */
    MaestroA5135Driver gps( gps_uart ); /* can do the generic UART config */

    hal::avr::Uart bluetooth_uart( Uart2 );
    bluetooth_uart.configure(); /* do the hardware-specific config here */
    MicrochipRN4871Driver bluetooth( bluetooth_uart ); /* can do the generic UART config */

    ContrivedPositionAdvertiser cpa( gps, bluetooth );
    for(;;)
    {
    /* do something */
    }
    }

    这种方法也有一些缺点。例如,将实例传递给更高级别的类的构造函数可能会迅速增长。因此,所有实例都需要进行管理。但总的来说,我认为弊大于弊,例如,易于添加另一个平台,易于使用测试双倍对hal客户进行单元测试。

    关于c++ - 嵌入式系统的独立于硬件的C++ HAL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54983120/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com