6

我创建了一个处理将数据发送到 LED 矩阵 (max7219) 的类。

这就是我为创建 LedControl 类的实例所做的事情。

LedControl lc=LedControl(11, 13, 12);// data, clock, latch;

void setup() 
{
   ...
}

现在我正在尝试在我的班级中添加计时器中断。但是我发现我无法在初始化程序(LedControl::LedControl())中设置适当的注册表。如果我将此代码移至setup,则一切正常。我的猜测是,用于 PWM 的 Arduino 引导加载程序会在调用之前但在我的对象初始化之后timer1覆盖这些注册表。setup()

所以我的想法是像这样将对象创建移动到设置函数

// FAIL
LedControl lc;

void setup() 
{
   lc=LedControl(11, 13, 12);// data, clock, latch;
   ...
}

但后来我得到了错误no matching function for call to 'LedControl::LedControl()'

我尝试使用指针 ( LedControl *lc; lc=&LedControl(11, 13, 12);),但据我所知,这意味着我必须在(*lc).someFunction()任何地方写而不是lc.someFunction(). 甚至比将注册表设置代码移动到 setup() 更不优雅。

所以我的问题是。如何在setup()函数中创建一个对象,但仍然有一个指向它的全局变量?

4

2 回答 2

12

您的第一个错误“不匹配..”是因为您没有默认构造函数。您可以使该方法起作用。

向类添加一个无参数,AKA 默认构造函数,如下所示:

class LedControl {
  LedControl();
  LedControl(uint8_t pin1, uint8_t pin2, uint8_t pin3);

private:
  uint8_t pin1;
  uint8_t pin2;
  uint8_t pin3;
};

LedControl::LedControl() : pin1(0), pin2(0), pin3(0) {
  // this constructor leaves the class unusable
}

LedControl::Ledcontrol(uint8_t p1, uint8_t p2, uint8_t p3) 
  : pin1(p1), pin2(p2), pin3(p3)
{
  // this object is ready to use    
}

使用此类,您的方法将起作用,但不是最佳方法。这条线做了太多的工作:

void setup() {
  lc = LedControl(11, 13, 12);// data, clock, latch;
}

这行代码涉及编译器为您创建一些代码:

  • 首先使用参数 11、13、12 在堆栈上构造该类的另一个实例。
  • 然后它应用= 运算符将数据从堆栈对象复制到您的全局对象。
  • 当 setup() 退出时,堆栈对象被刷新。

因为临时对象在堆栈上,所以您的程序并没有使用太多内存,但是您的代码大小更大,因为它涉及额外的操作来构造临时对象,然后从临时对象复制到永久对象。

请注意,编译器正在为您创建一个= 运算符,以填充该行的功能

  lc = LedControl(11, 13, 12);

这可能会也可能不会起作用,具体取决于您的构造函数中的内容。编译器只能假定您需要一个简单的= 运算符。所有基本的赋值运算符都会将所有数据成员从 = 右侧的实例复制到左侧的实例中。编译器构造的 = 将不包含任何代码。

如果您的构造函数做了任何重要的事情(除了保存参数),那么编译器构造的(猜测的)赋值运算符可能无法按您的预期工作。对于您的情况,构造函数可能会设置引脚模式,如下所示:

LedControl::LedControl(uint8_t p1, uint8_t p2, uint8_t p3) 
  : pin1(p1), pin2(p2), pin3(p3)
{
  pinMode(pin1, INPUT);
  pinMode(pin2, OUTPUT);
  pinMode(pin3, OUTPUT);
  return;
}

这碰巧起作用,但只是偶然。pinMode() 调用是在临时对象被构造并从该对象调用时进行的,而不是从全局lc调用。因为 pinMode() 是全局的,所以这种情况将达到正确的目标,但可能不是预期的方式。对于更复杂的操作,例如注册中断处理程序,您需要创建自己的赋值运算符:

LedControl& operator= (const LedControl & other);

在这种方法中,您可以确保全局lc的状态是您所需要的。一种更简单/更安全的方法是根本不处理它。

您可能在其他库中看到过一种简单而有效的方法,即向分配引脚的类添加一个方法:

class LedControl {
  void attach(uint8_t pin1, uint8_t pin2, uint8_t pin3);
};

void LedControl::attach(uint8_t pin1, uint8_t pin2, uint8_t pin3) {
  this.pin1 = pin1;
  this.pin2 = pin2;
  this.pin3 = pin3;
  // do other setup type operations
  return;
}

现在您的程序构造空白对象,并在 setup() 期间分配引脚:

LedControl lc; // not ready to use until attach pins

void setup() {
  lc.attach(11, 13, 12);// data, clock, latch;
  ...
}

这不涉及临时对象构造,也不涉及赋值运算符。关于设计,有些人可能会评论说用户可能忘记调用 attach() 并让全局 lc 对象不可用。对于桌面应用程序,您可能会添加一些代码来防止出现这种故障情况。对于嵌入式应用程序,这是您接受的风险,但可以通过代码大小或内存节省的收益来平衡。

于 2013-09-15T05:07:38.333 回答
5

我只是偶然发现了同样的问题。我想创建一个全局调试端口对象,但我无法将其创建为全局实例,因为调用 setup() 后端口将无法工作。我有同样的假设,arduino 代码在我的构造函数(全局对象的)之后和 setup() 之前做了一些设置。

我发现它是最简单的解决方案,将我的对象声明为指针并在设置中对其进行初始化。在您的情况下,它将是:

LedControl* lc;

void setup() 
{ 
   lc = new LedControl(11, 13, 12);// data, clock, latch;
}

void loop()
{
   lc->doSomething();
}

所以不需要额外的构造函数或操作符。由于对象在断电之前一直使用,因此在这种情况下不需要删除。

于 2017-12-27T10:26:02.613 回答