Ich habe das mal mit zwei Lüftern ausprobiert. Funktioniert.
Das Bild ist jetzt nur nicht mehr ganz so übersichtlich wie das erste:
PWM ist parallel angeschlossen. Die Anschlüsse für 12V und Tacho wurden einfach zwei mal (fast) identisch (entsprechend dem Schaltplan #7) aufgebaut, wobei der Tachoanschluss von Lüfter Eins an Pin D2, der von Lüfter Zwei an D3 angeschlossen ist.
Der angepasste Quellcode für zwei Lüfter:
#include <Arduino.h>
#include <util/atomic.h>
#include <Streaming.h>
Print &cout = Serial;
#define MAX_FANS 2U // Define number of fans 1 or 2 !
static_assert(MAX_FANS> 0 && MAX_FANS < 3,"Only 1U or 2U are allowed");
//////////////////////////////////////////////////////////////////////////////
/// @brief Timerclass for millis()
///
//////////////////////////////////////////////////////////////////////////////
class Timer {
public:
void start() { timeStamp = millis(); }
bool operator()(const unsigned long duration) const { return (millis() - timeStamp >= duration) ? true : false; }
private:
unsigned long timeStamp {0};
};
//////////////////////////////////////////////////////////////////////////////
/// @brief Class for calculating a simple average
///
/// @tparam T Datatype
/// @tparam MAX_SIZE Maximum number of individual values for calculating the average
//////////////////////////////////////////////////////////////////////////////
template <typename T, uint8_t MAX_SIZE = 1> class Average {
public:
uint8_t getIdx() { return idx; };
const void setValue(T val) {
values[idx] = val;
if (++idx >= maxIdx) idx = 0;
}
T getAverage() {
T tmp = 0;
for (auto value : values) { tmp += value; }
return tmp / maxIdx;
}
private:
const uint8_t maxIdx {MAX_SIZE};
T values[MAX_SIZE];
uint8_t idx {0};
};
//////////////////////////////////////////////////////////////////////////////
/// @brief Calculates the average rotation frequency of a fan.
///
/// @tparam T Type
/// @tparam MAX_SIZE Arraysize
/// @param duration Time between two edge signals
/// @param readings Object which is used to calculate the average
/// @return uint32_t Average value
//////////////////////////////////////////////////////////////////////////////
template <typename T, uint8_t MAX_SIZE = 1> uint32_t calcAvgFrequency(volatile uint32_t &duration, T &readings) {
uint32_t freq {0};
if (duration > 0) { freq = 100000000 / duration; }
readings.setValue(freq);
return readings.getAverage();
}
//////////////////////////////////////////////////////////////////////////////
/// global constants
///
//////////////////////////////////////////////////////////////////////////////
constexpr uint8_t PIN_ANALOG {A0};
constexpr uint16_t HERTZ {25000};
constexpr uint16_t PRESCALER {1};
constexpr uint8_t CLOCKSET {_BV(CS10)};
constexpr uint16_t ICOUNTER {static_cast<uint16_t>((F_CPU / (2UL * PRESCALER * HERTZ)) - 1)};
constexpr uint16_t INTERVAL_MS {1000};
constexpr uint8_t MAX_AVERAGE_IDX {12};
volatile uint32_t durationInt0;
volatile uint32_t lastDurInt0;
volatile uint32_t durationInt1;
volatile uint32_t lastDurInt1;
//////////////////////////////////////////////////////////////////////////////
/// Global variables / objects
///
//////////////////////////////////////////////////////////////////////////////
Timer timer;
Average<uint32_t, MAX_AVERAGE_IDX> rpmInt[MAX_FANS];
//////////////////////////////////////////////////////////////////////////////
/// @brief Enable external interrupts for INT0 and INT1 Pins (D2, D3)
///
//////////////////////////////////////////////////////////////////////////////
void enableExternalInterrupts() {
EICRA = _BV(ISC11) | _BV(ISC10) | _BV(ISC01) | _BV(ISC00); // Int0 & Int1 -> rising edge
EIMSK = _BV(INT1) | _BV(INT0);
}
// External Interrupt Pin D2
//
//////////////////////////////////////////////////////////////////////////////
/// @brief To determine the speed, measure the time between the edge changes (rising edge).
/// A fan provides two edge changes per revolution.
//////////////////////////////////////////////////////////////////////////////
ISR(INT0_vect) {
uint32_t timestamp = micros();
durationInt0 = (timestamp - lastDurInt0);
lastDurInt0 = timestamp;
}
// External Interrupt Pin D3 second fan
ISR(INT1_vect) {
uint32_t timestamp = micros();
durationInt1 = (timestamp - lastDurInt1);
lastDurInt1 = timestamp;
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Init PWM signal (25kHz)
///
//////////////////////////////////////////////////////////////////////////////
void pwmInit() {
DDRB |= _BV(PB1); // OCR1A Pin (PB1 / D9)
TCCR1A = _BV(WGM11);
TCCR1B = _BV(WGM13);
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ICR1 = ICOUNTER; }
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Set dutycycle of the PWM signal
///
/// @param dc 0-100%
//////////////////////////////////////////////////////////////////////////////
void pwmSetDutyCycle(uint8_t dc) {
uint16_t ocr1a = (dc > 100) ? 100 : dc;
ocr1a = static_cast<uint16_t>((((ICR1 * 10UL * ocr1a) / 100) + 5) / 10); // Result must be 16Bit
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR1A = ocr1a; }
}
//////////////////////////////////////////////////////////////////////////////
/// @brief start PWM signal
///
//////////////////////////////////////////////////////////////////////////////
void pwmStart() {
TCCR1A |= _BV(COM1A1);
TCCR1B |= CLOCKSET;
}
//////////////////////////////////////////////////////////////////////////////
/// @brief stop PWM signal
///
//////////////////////////////////////////////////////////////////////////////
void pwmStop() {
TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
TCCR1A &= ~(_BV(COM1A1));
PORTB &= ~(_BV(PB1)); // OCR1A Pin (PB1 / D9) to LOW
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Show Frequency and RPM on the serial console
///
/// @param freq
//////////////////////////////////////////////////////////////////////////////
void showRPM(uint32_t freq, uint8_t fanNr) {
uint32_t rpm = freq * 60;
rpm /= 200;
cout << "Lüfter Nr: " << fanNr << " Frequenz: " << freq / 100 << "." << freq % 100 << "Hz Rpm: " << rpm << endl;
}
//////////////////////////////////////////////////////////////////////////////////////
// Main program
//////////////////////////////////////////////////////////////////////////////////////
void setup(void) {
Serial.begin(115200);
enableExternalInterrupts();
pwmInit();
pwmSetDutyCycle(100);
pwmStart();
delay(10000); // full speed for 10 seconds
timer.start();
}
void loop() {
static uint16_t oldValue {0};
static uint8_t show[MAX_FANS] {0};
static uint32_t freq[MAX_FANS] {0};
// Determine the potentiometer position and set the duty cycle accordingly.
uint16_t value = map(analogRead(PIN_ANALOG), 0, 1015, 20, 100);
if (value != oldValue) {
oldValue = value;
pwmSetDutyCycle(value);
}
// Determine the fan speed and output it via the serial interface.
// Do this every INTERVAL_MS milliseconds
if (timer(INTERVAL_MS) == true) {
timer.start();
freq[0] = calcAvgFrequency(durationInt0, rpmInt[0]);
#if (MAX_FANS > 1)
freq[1] = calcAvgFrequency(durationInt1, rpmInt[1]);
for (uint8_t i = 0; i < MAX_FANS; ++i) {
if (show[i] < (MAX_AVERAGE_IDX + 1)) {
++show[i];
} else {
showRPM(freq[i],i);
}
}
cout << endl;
#else
if (show[0] < (MAX_AVERAGE_IDX + 1)) {
++show[0];
} else {
showRPM(freq[0],1);
}
#endif
}
}
Eine Ausgabe auf ein Display sollte sich da leicht einbauen lassen.

