最近我花了很多时间研究“最好的”(读起来最优雅)。健壮、使用简单和资源友好的方法,为C++中的嵌入式系统开发低级别的硬件抽象。我目前的目标是想出一些方法来表示、配置和与Port交互。
以下是我发现的一些很好的参考资料:
虽然在这些资源中找到的所有方法都是好的,但我想要某种“混合”方法,它允许我:
基于这些要求,我想出了一种可行的方法,对我来说似乎“很好”。我的问题是,根据别人的经验,我所写的是“好”的方法吗?
#include <iostream>
/**
* @brief Describes what port to use
*/
class PortDescriptor {
public:
constexpr PortDescriptor(unsigned int* p) : _p(p) {}
inline unsigned int* ptr() const { return _p; }
private:
unsigned int* _p;
};
/**
* @brief Describes what pin within a port to use
*/
class PinDescriptor {
public:
constexpr PinDescriptor(const PortDescriptor& port, unsigned int pin) :
_port(port),
_pin(pin)
{
}
inline const PortDescriptor& port() const { return _port; }
inline unsigned int index() const { return _pin; }
private:
const PortDescriptor& _port;
unsigned int _pin;
};
/**
* @brief Describes the configuration for a portpin
*/
class PinConfiguration {
public:
enum class Mode {
INPUT,
OUTPUT,
ANALOG
};
enum class Pull {
NONE,
UP,
DOWN
};
//Default constructor
constexpr PinConfiguration() :
_mode(Mode::OUTPUT),
_openDrain(false),
_pull(Pull::NONE)
{
}
//Builder methods
constexpr PinConfiguration& setMode(Mode v) { _mode = v; return *this; }
constexpr PinConfiguration& setOpenDrain(bool v) { _openDrain = v; return *this; }
constexpr PinConfiguration& setPull(Pull v) { _pull = v; return *this; }
//Accessors
inline constexpr Mode mode() const { return _mode; }
inline constexpr bool openDrain() const { return _openDrain; }
inline constexpr Pull pull() const { return _pull; }
private:
Mode _mode;
bool _openDrain;
Pull _pull;
};
/**
* @brief Class which represents a port peripheral within the MCU
*
* "Class overlay" will be used to map this class onto the actual hardware
* memory address.
*/
class Port {
public:
void configurePin(const PinDescriptor& pin, const PinConfiguration& config) {
std::cout << "port=" << pin.port().ptr() << " pin=" << pin.index() << std::endl;
//Example code
_modeRegister |= static_cast<unsigned int>(config.mode()) << (pin.index() * 2);
_drainRegister |= static_cast<unsigned int>(config.openDrain()) << (pin.index());
_pullRegister |= static_cast<unsigned int>(config.pull()) << (pin.index() * 2);
}
static Port* get(const PortDescriptor& p) { return reinterpret_cast<Port*>(p.ptr()); }
static Port* get(const PinDescriptor& p) { return reinterpret_cast<Port*>(p.port().ptr()); }
private:
unsigned int _modeRegister;
unsigned int _drainRegister;
unsigned int _pullRegister;
};
//------------------------------------------------------------------------------
//Fake peripheral registers (would normally be actual hardware registers)
unsigned int porta_registers[3];
unsigned int portb_registers[3];
//Available ports
static constexpr PortDescriptor PORTA(porta_registers);
static constexpr PortDescriptor PORTB(portb_registers);
//Available pins
static constexpr PinDescriptor PA0(PORTA, 0);
static constexpr PinDescriptor PA1(PORTA, 1);
static constexpr PinDescriptor PA2(PORTA, 2);
static constexpr PinDescriptor PB0(PORTB, 0);
static constexpr PinDescriptor PB1(PORTB, 1);
static constexpr PinDescriptor PB2(PORTB, 2);
//------------------------------------------------------------------------------
//Example configuration file for a project making use of the above HAL
static constexpr PinDescriptor RED_LED_PIN = PA0;
static constexpr PinConfiguration RED_LED_PIN_CONFIG = PinConfiguration().setMode(PinConfiguration::Mode::OUTPUT);
static constexpr PinDescriptor GREEN_LED_PIN = PB2;
static constexpr PinConfiguration GREEN_LED_PIN_CONFIG = PinConfiguration().setMode(PinConfiguration::Mode::OUTPUT).setOpenDrain(true);
int main() {
std::cout << "TEST 1" << std::endl;
std::cout << "porta_registers[0]=" << porta_registers[0] << std::endl;
std::cout << "porta_registers[1]=" << porta_registers[1] << std::endl;
std::cout << "porta_registers[2]=" << porta_registers[2] << std::endl;
Port::get(RED_LED_PIN)->configurePin(RED_LED_PIN, RED_LED_PIN_CONFIG);
std::cout << "porta_registers[0]=" << porta_registers[0] << std::endl;
std::cout << "porta_registers[1]=" << porta_registers[1] << std::endl;
std::cout << "porta_registers[2]=" << porta_registers[2] << std::endl;
std::cout << "TEST 2" << std::endl;
std::cout << "portb_registers[0]=" << portb_registers[0] << std::endl;
std::cout << "portb_registers[1]=" << portb_registers[1] << std::endl;
std::cout << "portb_registers[2]=" << portb_registers[2] << std::endl;
Port::get(GREEN_LED_PIN)->configurePin(GREEN_LED_PIN, GREEN_LED_PIN_CONFIG);
std::cout << "portb_registers[0]=" << portb_registers[0] << std::endl;
std::cout << "portb_registers[1]=" << portb_registers[1] << std::endl;
std::cout << "portb_registers[2]=" << portb_registers[2] << std::endl;
return 0;
}上面的例子并不代表真正的硬件代码,但希望能给您提供一般的想法。
预先感谢您的投入!
发布于 2021-08-17 20:30:09
您是否试图为嵌入式开发设计一个框架?我会从上面开始工作下去。也就是说,首先考虑应用程序代码的需求,而不是考虑任何特定微处理器的功能。
在过去,我从顶部开始有几个界面,从而获得了很多里程。例如;
class DigitalOutput {
public:
virtual ~DigitalOutput();
virtual void set() = 0;
virtual void clear() = 0;
virtual boolean get() = 0;
};
class DigitalInput {
public:
virtual ~DigitalInput();
virtual boolean get() = 0;
};
class AnalogOutput {
public:
...
};
...您提供的实现此类接口的所有框架都可以了解任何给定微处理器的功能。所有依赖于这些接口的应用程序代码都可以幸亏不知道它运行的硬件。该接口帮助您将这两件事分开,并且当应用程序开发人员想要测试代码的“幸福无知”部分时,它会有很大帮助。
*希望这是大多数应用程序代码。
https://softwareengineering.stackexchange.com/questions/431135
复制相似问题