Using Tiny4kOLED in multiple files

So, I am building a Toaster Reflow Oven. The controller is a Attiny85 with code based on
this project:

The original code was done about 10 years ago and was somewhat bare bones. The
Attiny45 in the project used one pin to read a thermistor, one pin to drive a heater solenoid,
one pin to drive a "status" Led, and pin to read a button (to start and stop the program, and to
select between a couple of options). I have successfully compiled the code "as is".

Now I would like to add a 128x32 OLED and use it to display temperature. I am attempting to
use Tiny4kOLED to drive the display. I found info on the library at:

So far, so good. Now I am in the process of modifying the original controller software to include
the Tiny4kOLED display bits.

The original software consists of:
ReflowController.ino (main program)
Heater.h
Heater.cpp (module to read oven temperature and adjust PWM to follow a specific profile)
Thermistor.h
Thermistor.cpp (thermistor data to provide accurate reading)
PID.h
PID.cpp (algorithm to control heater PWM)

Since the Attiny85 has only 6 pins, and since the OLED needs 2 specific pins to use I2C comms,
I adjusted the pins in the original software so that I could include all of the original functionality
and just "add" the OLED display. So far so good.

Now, the problem I am having is using the Tiny4kOLED functions in different parts of the
code. I realize this is a "basic newbie" issue and not specifically related to the OLED library,
but didn't know where else to post the question.

I have included TinyWireM.h and Tiny4kOLED.h in my .ino file, as well as adding code in
setup to initialize the display-

#include <TinyWireM.h>
#include <Tiny4kOLED.h>
...
void setup();
...
oled.begin ();
oled.clear ();
oled.on ();

At this point everything compiles okay.

Finally, I need to add "display code" in Heater.cpp (to send the current temperature to the OLED).
When I try to use oled.print(current_temperature) in Heater.cpp I get

'oled' was not declared in this scope.

So the first thing I thought was that I needed to include Tiny4kOLED.h in Heater.cpp.
When I included (#include <Tiny4kOLED.h>) in Heater.cpp, I got a bunch of errors relating
to "Wire", so I decided that I needed to include (#include <TinyWireM.h>) in Heater.cpp.
With TinyWireM.h included, the "Wire" errors went away, but then I get:

sketch/ReflowController-gg.ino.cpp.o (symbol from plugin): In function oled': (.text+0x0): multiple definition of oled'
sketch/Heater.cpp.o (symbol from plugin):(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
exit status 1

So if I don't put Tiny4kOLED in Heater.cpp, I get " 'oled' not declared". If I do include Tiny4kOLED
in Heater.cpp I get "multiple definition". I know this is a "newbie dumb error". Can someone
help me with the basics here? Do I need to use EXTERN, or something like that in Heater.cpp,
or somehow figure out the right declaration for 'oled'? The header files all seem to have
"guards".

I can include a zip file with all my files once I figure out how to include an upload!

Didn't it come with example sketches? Documentation?

As far as the placement of different types of code in different file types such as .c .cpp .h and so on, it's a "general C/C++" question. I suppose this is where your code differs from the basic usage? Anyway, I had some troubles with that recently, with some patience and common sense I was able to make it work.

There should be a guide online somewhere, but as I suggest, don't limit yourself to Arduino guides because the structure of multiple files has been developed over many decades and is therefore well known to people that use them.

I think my issue is a more fundamental "newbie" issue. If I include any of the Tiny4kOLED functions in my main program (my .ino file), everything works fine.

When I try to use some of the Tiny4kOLED functions in additional program files (like Heater.cpp) I
can't figure out whether I need to include the Tiny4kOLED headers or not. Either I get "not declared", or "multiple declarations".

You definitely need to include header files. That much I can assure you. I guess the question is "where?".

How large are these files? Can you post them inline in a message, in code tags?

Usually snippets aren't liked here, but since it's mainly about headers, I guess it wouldn't hurt to post just the first 20 lines or so of each one so that all the includes are visible and tell us where it's relatively located on the file tree, from where the .ino is.

Anecdotally, last night I struggled to put good code in the right file because I haven't written a class in so long, I always make a .h and .cpp file for that. It really had me going!

I don't know of any thing that would prevent you from partitioning your programs like that, it's a good idea and it seems natural that they would have access to each others functions, within the defined scope.

It's helpful if you can cut and paste error messages in their entirety, rather than summarizing them.

Okay. Here are the files.

Controller.ino is my main file.

Heater.h and Heater.cpp read the temperature and adjust the PWM to the heater (this is where I
think I need to write the temperature to the OLED).

Thermistor.h and Thermistor.cpp have a lookup table to convert the analog signal to a temperature.

PID.h and PID.cpp control how much to change the PWM output based on setpoint vs actual.

My approach was to "initialize" the OLED in setup(); (in my main program), and then write
the "current temperature" to the OLED from the Heater.cpp.

Thanks for being willing to look!

Main file (controller.ino)


#include <Bounce2.h>

/*
 * Temperature control software for a Reflow Soldering Oven
 * This firmware can be used on a ATtiny 45 µController
 * For schematics of the hardware, please see
 * http://www.pleasantsoftware.com/developer/3d/reflow
 *
 * (c) 2011 Eberhard Rensch, Pleasant Software
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */


#include <TinyWireM.h>
#include <Tiny4kOLED.h>

#include "Thermistor.h"
#include "Heater.h"

// Use these Pins on a ATtiny
  uint8_t sensorPin = 3;
  uint8_t heaterPin = 4;
  uint8_t ledPin = 1;
  uint8_t buttonPin= 6;
    
// These pins might be used for debugging with an Arduino instead
//  uint8_t sensorPin = 3;
//  uint8_t heaterPin = 2;
//  uint8_t ledPin = 3;
//  uint8_t buttonPin= 4;


Thermistor  thermistor(sensorPin, 0);
Heater heater;
Bounce button = Bounce(buttonPin, 10);

void setup() {

// Send the initialization sequence to the oled. This leaves the display turned off
  oled.begin();

  // Two rotations are supported,
  // The begin() method sets the rotation to 1.
  //oled.setRotation(0);

  // Some newer devices do not contain an external current reference.
  // Older devices may also support using the internal curret reference,
  // which provides more consistent brightness across devices.
  // The internal current reference can be configured as either low current, or high current.
  // Using true as the parameter value choses the high current internal current reference,
  // resulting in a brighter display, and a more effective contrast setting.
  //oled.setInternalIref(true);

  // Two fonts are supplied with this library, FONT8X16 and FONT6X8
  // Other fonts are available from the TinyOLED-Fonts library
  oled.setFont(FONT8X16);

  // Clear the memory before turning on the display
  oled.clear();

  // Turn on the display
  oled.on();

  // Switch the half of RAM that we are writing to, to be the half that is non currently displayed
  oled.switchRenderFrame();


  
#if !defined(__AVR_ATtiny85__)    
  Serial.begin(115200); 
  Serial.println("Init");
#endif



  pinMode(heaterPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, HIGH);
  digitalWrite(heaterPin, LOW);

  heater.set_target_temperature(0);
  
  // Signal ready for duty
//  for(uint8_t i=0;i<5;i++) {
//     delay(100);
//     digitalWrite(ledPin,HIGH);
//     delay(150);
//     digitalWrite(ledPin,LOW);
//   }

}

uint8_t phase = 0;
uint8_t program = 0;

boolean running = false;
boolean programFinished = false;

uint8_t ledState=0;
uint8_t ledPhase = 0;

uint32_t blinkTimer=0L;
uint32_t tempManagerTimer=0L;
uint32_t currentTime=0L;
uint32_t nextPhaseChange=0;
boolean buttonHandled=true;

void loop() 
{ 
  boolean buttonChanged = button.update();
  currentTime = millis();
  if(running)
  {
    if(blinkTimer<currentTime)
    {
      ledPhase+=ledState;
      ledState = 1-ledState;
      digitalWrite(ledPin, ledState);
      blinkTimer=currentTime+100UL;
      if(ledPhase>(phase-1)/2) {
        blinkTimer+=500UL;
        ledPhase = 0;
      }
    }
    if(tempManagerTimer<currentTime)
    {
      tempManagerTimer = currentTime+500UL;
      heater.manage_temperature();

#if !defined(__AVR_ATtiny85__)    
      Serial.print("Phase ");
      Serial.print(phase);
      Serial.print(" SollTemp: ");
      Serial.print(heater.get_target_temperature(),DEC);
      Serial.print(" IstTemp: ");
      Serial.print(thermistor.getTemperature(),DEC);
      Serial.print(" nextTime: ");
      Serial.print(nextPhaseChange/1000L,DEC);
      Serial.print(" current Time: ");
      Serial.println(currentTime/1000L,DEC);
#endif
    }
    if(buttonChanged && button.read()==LOW) {
      heater.set_target_temperature(0);
      digitalWrite(heaterPin, LOW);
      digitalWrite(ledPin, LOW);
#if !defined(__AVR_ATtiny85__)    
      Serial.print("Program Abort");
#endif
      running=false;
    }
    else {
      if( (nextPhaseChange!=-1UL && nextPhaseChange<currentTime) || (nextPhaseChange==-1UL && heater.hasReachedTargetTemperature()) )
      {
 #if !defined(__AVR_ATtiny85__)    
       Serial.print("PhaseChange to ");
       Serial.println(phase, DEC);
 #endif
        switch(phase++)
        {
        case 0:  
          heater.set_target_temperature(150);
          nextPhaseChange = -1UL;
          break;
        case 1:  
          nextPhaseChange = currentTime + 40000L; // Hold Temperature for 40 Seconds
          break;
        case 2:  
          heater.set_target_temperature(190);
          nextPhaseChange = -1UL;
          break;
        case 3:  
          nextPhaseChange = currentTime + 10000L; // Hold Temperature for 10 Seconds
          break;
        case 4:  
          heater.set_target_temperature(245);
          nextPhaseChange = -1UL;
          break;
        case 5:  
          nextPhaseChange = currentTime + 10000L; // Hold Temperature for 20 Seconds
          break;
        case 10: // Always on
          heater.set_target_temperature(500);
          nextPhaseChange = -1UL; // Hold Temperature for 10 Seconds
          // This phase never ends (500°C are never reached in a usual toaster oven!)
          break;
        case 20: // One hour 110°C
          heater.set_target_temperature(110);
          nextPhaseChange = currentTime + 3600000UL; // Hold Temperature for 1 hour
          break;
        default: 
          heater.set_target_temperature(0);
          programFinished = true;
          running=false;
#if !defined(__AVR_ATtiny85__)    
          Serial.println("Program End");
#endif
          digitalWrite(heaterPin, LOW); 
          digitalWrite(ledPin, HIGH);
          break;
        }
      }
    }

  }
  else {
    if(tempManagerTimer<currentTime)
    {
      tempManagerTimer = currentTime+1000L;
      thermistor.update();
#if !defined(__AVR_ATtiny85__)    
      Serial.print("IstTemp: ");
      Serial.println(thermistor.getTemperature());
#endif
      if(programFinished && thermistor.getTemperature()<55)
         resetProgram();
    }

    if( button.read()==LOW ) {
      if(button.duration() > 1000) {
        running = true;
        programFinished = false;
        switch(program)
        {
           case 0: phase=0;
                   break;
           case 1: phase=20;
                   break;
           case 2: phase=10;
                   break;
        }
        //phase = (program==0)?0:10; // 10 -> Always on!
        nextPhaseChange = 0L;
        buttonHandled=true;
 #if !defined(__AVR_ATtiny85__)    
        Serial.println("Start Program");
 #endif
      }
      else if(buttonChanged && programFinished)
      {
        resetProgram();
        buttonHandled=true;
      }
    }  
    if(buttonChanged && button.read()==HIGH)
    {
      if(!buttonHandled) {
        program = (program+1)%3;
        for(uint8_t i=0;i<=program;i++)
        {
           delay(200);
           digitalWrite(ledPin,HIGH);
           delay(250);
           digitalWrite(ledPin,LOW);
        }

#if !defined(__AVR_ATtiny85__)    
        Serial.println("Change Program");
#endif
      }
      buttonHandled=false;
    }  
  }
}

void resetProgram()
{
  programFinished = false;
//  digitalWrite(ledPin, LOW);
#if !defined(__AVR_ATtiny85__)    
  Serial.println("Reset Program");
#endif
}

Heater.h

/*
 * 2011 Eberhard Rensch, Pleasant Software
 *
 * Based on parts of the Makerbot firmware
 * Copyright 2010 by Adam Mayer	 <adam@makerbot.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

#ifndef HEATER_H
#define HEATER_H

#include "Thermistor.h"
#include "PID.h"


class Heater
{
  private:
    int current_temperature;
    PID pid;

  public:
    Heater();

    void set_target_temperature(int temp);
    int  get_target_temperature();
    bool hasReachedTargetTemperature();

    // Call once each temperature interval
    void manage_temperature();


    // Reset to board-on state
    void reset();
};

#endif // HEATER_H

Heater.cpp

type /*
 * 2011 Eberhard Rensch, Pleasant Software
 *
 * Based on parts of the Makerbot firmware
 * Copyright 2010 by Adam Mayer	 <adam@makerbot.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */



#include "Heater.h"
#include "Thermistor.h"
//#include "WProgram.h"
#include "Arduino.h"
#include <TinyWireM.h>
//#include <Tiny4kOLED.h>


// Offset to compensate for range clipping and bleed-off
#define HEATER_OFFSET_ADJUSTMENT 0

extern int heaterPin;
extern Thermistor thermistor;


Heater::Heater()
{
  reset();
}

void Heater::reset() {
	current_temperature = 0;
}

void Heater::set_target_temperature(int temp)
{
	pid.setTarget(temp);
}

int Heater::get_target_temperature()
{
	return pid.getTarget();
}

// We now define target hysteresis in absolute degrees.  The original
// implementation (+/-5%) was giving us swings of 10% in either direction
// *before* any artifacts of process instability came in.
#define TARGET_HYSTERESIS 2

bool Heater::hasReachedTargetTemperature()
{
  return (current_temperature >= (pid.getTarget() - TARGET_HYSTERESIS)) &&
	 (current_temperature <= (pid.getTarget() + TARGET_HYSTERESIS));
}

/**
 *  Samples the temperature and converts it to degrees celsius.
 *  Returns degrees celsius.
 */

/*!
 Manages motor and heater based on measured temperature:
 o If temp is too low, don't start the motor
 o Adjust the heater power to keep the temperature at the target
 */
void Heater::manage_temperature()
{
  thermistor.update();
		
  // update the temperature reading.
  current_temperature = thermistor.getTemperature();

oled.print(current_temperature);

  
  int mv = pid.calculate(current_temperature);
  // offset value to compensate for heat bleed-off.
  // There are probably more elegant ways to do this,
  // but this works pretty well.
  mv += HEATER_OFFSET_ADJUSTMENT;
  // clamp value
  if (mv < 0) { mv = 0; }
  if (mv >255) { mv = 255; }
  digitalWrite(heaterPin,(mv>0));
}

Thermistor.h

/*
 * 2011 Eberhard Rensch, Pleasant Software
 *
 * Based on parts of the Makerbot firmware
 * Copyright 2010 by Adam Mayer	 <adam@makerbot.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

#ifndef THERMISTOR_HH_
#define THERMISTOR_HH_

#include <stdint.h>

#define THERM_TABLE_SIZE 20
#define SAMPLE_COUNT 4

struct ThermTableEntry {
	int16_t adc;
	int16_t celsius;
} __attribute__ ((packed));

class Thermistor {
private:
        uint16_t current_temp;
	uint8_t analog_pin; // index of analog pin
	const static int ADC_RANGE = 1024;
	int16_t sample_buffer[SAMPLE_COUNT];
	uint8_t next_sample;
	const uint8_t table_index;

public:
	Thermistor(uint8_t analog_pin, uint8_t table_index);

        int16_t getTemperature() const { return current_temp; }

	// True if update initiated, false otherwise
	bool update();
};

#endif //THERMISTOR_H

Thermistor.cpp

/*
  * 2011 Eberhard Rensch, Pleasant Software
 *
 * Based on parts of the Makerbot firmware
 * Copyright 2010 by Adam Mayer	 <adam@makerbot.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

#include "Thermistor.h"
//#include "WProgram.h"
#include "Arduino.h"

// Default thermistor table.  If no thermistor table is loaded into eeprom,
// this will be copied in by the initTable() method.
//
// Thermistor lookup table for RepRap Temperature Sensor Boards (http://make.rrrf.org/ts)
// Made with createTemperatureLookup.py (http://svn.reprap.org/trunk/reprap/firmware/Arduino/utilities/createTemperatureLookup.py)
// ./createTemperatureLookup.py --r0=100000 --t0=25 --r1=0 --r2=4700 --beta=4066 --max-adc=1023
// r0: 100000
// t0: 25
// r1: 0
// r2: 4700
// beta: 4066
// max adc: 1023
typedef int16_t TempTable[THERM_TABLE_SIZE][2];
TempTable default_table = {
  {1, 841},
  {54, 255},
  {107, 209},
  {160, 184},
  {213, 166},
  {266, 153},
  {319, 142},
  {372, 132},
  {425, 124},
  {478, 116},
  {531, 108},
  {584, 101},
  {637, 93},
  {690, 86},
  {743, 78},
  {796, 70},
  {849, 61},
  {902, 50},
  {955, 34},
  {1008, 3}
};

int16_t thermistorToCelsius(int16_t reading, int8_t table_idx) {
  int16_t celsius = 0;
  int8_t i;
  for (i=1; i<THERM_TABLE_SIZE; i++)
  {
	  if (default_table[i][0] > reading)
	  {
		  celsius  = default_table[i-1][1] +
				  (reading - default_table[i-1][0]) *
				  (default_table[i][1] - default_table[i-1][1]) /
				  (default_table[i][0] - default_table[i-1][0]);
		  if (celsius > 255)
			  celsius = 255;
		  break;
	  }
  }
  // Overflow: We just clamp to 255 degrees celsius to ensure
  // that the heater gets shut down if something goes wrong.
  if (i == THERM_TABLE_SIZE) {
    celsius = 255;
  }
  return celsius;
}

Thermistor::Thermistor(uint8_t analog_pin_in, uint8_t table_index_in) :
analog_pin(analog_pin_in), next_sample(0), table_index(table_index_in) {
	for (int i = 0; i < SAMPLE_COUNT; i++) { sample_buffer[i] = 0; }
}

bool Thermistor::update() {
	int16_t temp;
	temp = analogRead(analog_pin);

	sample_buffer[next_sample] = temp;
	next_sample = (next_sample+1) % SAMPLE_COUNT;

	// average
	int16_t cumulative = 0;
	for (int i = 0; i < SAMPLE_COUNT; i++) {
		cumulative += sample_buffer[i];
	}
	int16_t avg = cumulative / SAMPLE_COUNT;

	//current_temp = thermistorToCelsius(avg,table_index);
	current_temp = thermistorToCelsius(temp,table_index);
	return true;
}

PID.h

/*
 * 2011 Eberhard Rensch, Pleasant Software
 *
 * Based on parts of the Makerbot firmware
 * Copyright 2010 by Adam Mayer	 <adam@makerbot.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 * This simplified PID controller was written with reference to:
 * * The Heater.h implementation (lookup credits)
 * * Brett Beauregard's Arduino PID implementation
 *  Created on: Feb 19, 2010
 *      Author: phooky
 */

#ifndef PID_HH_
#define PID_HH_

#include <stdint.h>

#define DELTA_SAMPLES 4

/// This simplified PID controller makes several assumptions:
/// * The output range is limited to 0-255.
class PID {
private:
    // Data for approximating d (smoothing to handle discrete nature of sampling).
    // See PID.cc for a description of why we do this.
    int16_t delta_history[DELTA_SAMPLES];
    float delta_summation;
    uint8_t delta_idx;
    int prev_error; // previous input for calculating next delta
    int error_acc;  // accumulated error, for calculating integral

    int sp; // set point


public:
    PID() { reset(); }

    void setTarget(const int target) { sp = target; }
    const int getTarget() const { return sp; }

    /// Reset the PID to board-on values
    void reset();
    /// PV is the process value; that is, the measured value
    /// Returns the new value of the manipulated value; that is, the output
    int calculate(int pv);
};

#endif /* PID_HH_ */

PID.cpp

/*
 * 2011 Eberhard Rensch, Pleasant Software
 *
 * Based on parts of the Makerbot firmware
 * Copyright 2010 by Adam Mayer	 <adam@makerbot.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 * This simplified PID controller was written with reference to:
 * * The Heater.h implementation (lookup credits)
 * * Brett Beauregard's Arduino PID implementation
 *  Created on: Feb 19, 2010
 *      Author: phooky
 *
 */

#include "PID.h"

#define ERR_ACC_MAX 256
#define ERR_ACC_MIN -ERR_ACC_MAX

#define P_GAIN (float)7.0
#define I_GAIN (float)0.325
#define D_GAIN (float)36.0

// scale the output term to account for our fixed-point bounds
#define OUTPUT_SCALE 2

void PID::reset() {
	error_acc = 0;
	prev_error = 0;
	delta_idx = 0;
	sp = 0;
	for (delta_idx = 0; delta_idx < DELTA_SAMPLES; delta_idx++) {
		delta_history[delta_idx] = 0;
	}
	delta_idx = 0;
	delta_summation = 0;
}

// We're modifying the way we compute delta by averaging the deltas over a
// series of samples.  This helps us get a reasonable delta despite the discrete
// nature of the samples; on average we will get a delta of maybe 1/deg/second,
// which will give us a delta impulse for that one calculation round and then
// the D term will immediately disappear.  By averaging the last N deltas, we
// allow changes to be registered rather than get subsumed in the sampling noise.
int PID::calculate(const int pv) {
	int e = sp - pv;
	error_acc += e;
	// Clamp the error accumulator at accepted values.
	// This will help control overcorrection for accumulated error during the run-up
	// and allow the I term to be integrated away more quickly as we approach the
	// setpoint.
	if (error_acc > ERR_ACC_MAX) {
		error_acc = ERR_ACC_MAX;
	}
	if (error_acc < ERR_ACC_MIN) {
		error_acc = ERR_ACC_MIN;
	}
	float p_term = (float)e * P_GAIN;
	float i_term = (float)error_acc * I_GAIN;
	int delta = e - prev_error;
	// Add to delta history
	delta_summation -= delta_history[delta_idx];
	delta_history[delta_idx] = delta;
	delta_summation += (float)delta;
	delta_idx = (delta_idx+1) % DELTA_SAMPLES;
	// Use the delta over the whole window
	float d_term = delta_summation * D_GAIN;

	prev_error = e;

	return ((int)(p_term + i_term + d_term))*OUTPUT_SCALE;
}

From the link provided:

The current firmware uses 4084 bytes of the available 4096 byte maximum of the ATtiny 45.
The sources are available at GitHub: GitHub - zaggo/ReflowController: Temperature control software for a Reflow Soldering Oven This firmware can be used on a ATtiny 45 µController For schematics of the hardware, please see http://www.pleasantsoftware.com/developer/3d/reflow

UNO atmega328P is appropriate, likely as it has more memory space.

Yes, the original project used an ATtiny45 (with 4k bytes memory). I'm planning on using an
ATtiny85 (with 8k memory). A lot of the "newer" controllers use an Atmega328P because, as
you say, it's got a lot more memory.

I understand all that, but I believe I'll still run into the same issue with regards to the headers/declarations. I put in all the info about my project for context, but believe
the issue is generic to how to include functions across multiple files.

So this is probably a dumb idea, but at least it is getting things working for now.
Instead of having multiple sources files (controller.ino, Heater.cpp, Thermistor.cpp, PID.cpp)
I have simply moved all of the files into controller.ino. I left the headers in the same folder
and removed the few duplicate lines (like multiple #include).

That's working for now, so I'm sure my problem is in figuring out how to use "multiple source
files", and not much to do with the particular library!

I'll do more homework and try to better understand the concept of "scope" across multiple files.

I think your problem is in the Tiny4kOLED library. It defines the global 'oled' object in the "Tiny4kOLED_TinyWireM.h" file. That means that the include file can't be included in more than one compilation unit.

I think the work-around is to move this part of Tiny4kOLED_TinyWireM.h into your .ino file:

#ifndef TINY4KOLED_NO_PRINT
SSD1306PrintDevice oled(&tiny4koled_begin_tinywirem, &tiny4koled_beginTransmission_tinywirem, &datacute_write_tinywirem, &datacute_endTransmission_tinywirem);
#else
SSD1306Device oled(&tiny4koled_begin_tinywirem, &tiny4koled_beginTransmission_tinywirem, &datacute_write_tinywirem, &datacute_endTransmission_tinywirem);
#endif

and in Tiny4kOLED_TinyWireM.h, replace those lines with:

#ifndef TINY4KOLED_NO_PRINT
extern SSD1306PrintDevice oled;
#else
extern SSD1306Device oled;
#endif

That way you can include the library in many places and they will all use the same global 'oled' object.

Hey, John! Thanks so much for the useful reply - that's exactly what I was looking for.

Sorry I'm such a newbie, but I did do a lot of studying since last night after my first post. Do I understand it correctly that this is the "ODR" One Definition Rule? And
that the code you indicate in my .ino is the "One Definition", and that the second
change in the .h (using extern) makes that just a "declaration"?

If that's right, and I make these changes, then that means I must recompile the
library? If that's the case, then I should pass along your solution to the library
author, yes? I assume that a lot of people that might use the library might have
multiple souce file code for larger projects.

I see that there are several articles on making libraries for Arduino. I'll look at those
and figure out how to recompile and save the Tiny4kOLED library to the right spot.

Thanks again!

I think all you have to do is change the selected 'Board' to a different model and change it back. Each time the Board changes a flag is set to tell the build process to re-build the necessary libraries.

Yippee! You were spot on. Everything works fine now with multiple source files.
I'm learning a lot!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.